VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/common/utils.py@ 96969

Last change on this file since 96969 was 96969, checked in by vboxsync, 3 years ago

remove extra print

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 87.0 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 96969 2022-10-03 13:57:05Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6Common Utility Functions.
7"""
8
9from __future__ import print_function;
10
11__copyright__ = \
12"""
13Copyright (C) 2012-2022 Oracle and/or its affiliates.
14
15This file is part of VirtualBox base platform packages, as
16available from https://www.virtualbox.org.
17
18This program is free software; you can redistribute it and/or
19modify it under the terms of the GNU General Public License
20as published by the Free Software Foundation, in version 3 of the
21License.
22
23This program is distributed in the hope that it will be useful, but
24WITHOUT ANY WARRANTY; without even the implied warranty of
25MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26General Public License for more details.
27
28You should have received a copy of the GNU General Public License
29along with this program; if not, see <https://www.gnu.org/licenses>.
30
31The contents of this file may alternatively be used under the terms
32of the Common Development and Distribution License Version 1.0
33(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
34in the VirtualBox distribution, in which case the provisions of the
35CDDL are applicable instead of those of the GPL.
36
37You may elect to license modified versions of this file under the
38terms and conditions of either the GPL or the CDDL or both.
39
40SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
41"""
42__version__ = "$Revision: 96969 $"
43
44
45# Standard Python imports.
46import datetime;
47import errno;
48import os;
49import platform;
50import re;
51import stat;
52import subprocess;
53import sys;
54import time;
55import traceback;
56import unittest;
57
58if sys.platform == 'win32':
59 import ctypes;
60 import msvcrt; # pylint: disable=import-error
61 import win32api; # pylint: disable=import-error
62 import win32con; # pylint: disable=import-error
63 import win32console; # pylint: disable=import-error
64 import win32file; # pylint: disable=import-error
65 import win32process; # pylint: disable=import-error
66 import winerror; # pylint: disable=import-error
67 import pywintypes; # pylint: disable=import-error
68else:
69 import signal;
70
71# Python 3 hacks:
72if sys.version_info[0] >= 3:
73 unicode = str; # pylint: disable=redefined-builtin,invalid-name
74 xrange = range; # pylint: disable=redefined-builtin,invalid-name
75 long = int; # pylint: disable=redefined-builtin,invalid-name
76
77
78#
79# Strings.
80#
81
82def toUnicode(sString, encoding = None, errors = 'strict'):
83 """
84 A little like the python 2 unicode() function.
85 """
86 if sys.version_info[0] >= 3:
87 if isinstance(sString, bytes):
88 return str(sString, encoding if encoding else 'utf-8', errors);
89 else:
90 if not isinstance(sString, unicode):
91 return unicode(sString, encoding if encoding else 'utf-8', errors);
92 return sString;
93
94
95
96#
97# Output.
98#
99
100def printOut(sString):
101 """
102 Outputs a string to standard output, dealing with python 2.x encoding stupidity.
103 """
104 sStreamEncoding = sys.stdout.encoding;
105 if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here)
106 sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type
107 if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode):
108 print(sString);
109 else:
110 print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding));
111
112def printErr(sString):
113 """
114 Outputs a string to standard error, dealing with python 2.x encoding stupidity.
115 """
116 sStreamEncoding = sys.stderr.encoding;
117 if sStreamEncoding is None: # Files, pipes and such on 2.x. (pylint is confused here)
118 sStreamEncoding = 'US-ASCII'; # pylint: disable=redefined-variable-type
119 if sStreamEncoding == 'UTF-8' or not isinstance(sString, unicode):
120 print(sString, file = sys.stderr);
121 else:
122 print(sString.encode(sStreamEncoding, 'backslashreplace').decode(sStreamEncoding), file = sys.stderr);
123
124
125#
126# Host OS and CPU.
127#
128
129def getHostOs():
130 """
131 Gets the host OS name (short).
132
133 See the KBUILD_OSES variable in kBuild/header.kmk for possible return values.
134 """
135 sPlatform = platform.system();
136 if sPlatform in ('Linux', 'Darwin', 'Solaris', 'FreeBSD', 'NetBSD', 'OpenBSD'):
137 sPlatform = sPlatform.lower();
138 elif sPlatform == 'Windows':
139 sPlatform = 'win';
140 elif sPlatform == 'SunOS':
141 sPlatform = 'solaris';
142 else:
143 raise Exception('Unsupported platform "%s"' % (sPlatform,));
144 return sPlatform;
145
146g_sHostArch = None;
147
148def getHostArch():
149 """
150 Gets the host CPU architecture.
151
152 See the KBUILD_ARCHES variable in kBuild/header.kmk for possible return values.
153 """
154 global g_sHostArch;
155 if g_sHostArch is None:
156 sArch = platform.machine();
157 if sArch in ('i386', 'i486', 'i586', 'i686', 'i786', 'i886', 'x86'):
158 sArch = 'x86';
159 elif sArch in ('AMD64', 'amd64', 'x86_64'):
160 sArch = 'amd64';
161 elif sArch == 'i86pc': # SunOS
162 if platform.architecture()[0] == '64bit':
163 sArch = 'amd64';
164 else:
165 try:
166 sArch = str(processOutputChecked(['/usr/bin/isainfo', '-n',]));
167 except:
168 pass;
169 sArch = sArch.strip();
170 if sArch != 'amd64':
171 sArch = 'x86';
172 else:
173 raise Exception('Unsupported architecture/machine "%s"' % (sArch,));
174 g_sHostArch = sArch;
175 return g_sHostArch;
176
177
178def getHostOsDotArch():
179 """
180 Gets the 'os.arch' for the host.
181 """
182 return '%s.%s' % (getHostOs(), getHostArch());
183
184
185def isValidOs(sOs):
186 """
187 Validates the OS name.
188 """
189 if sOs in ('darwin', 'dos', 'dragonfly', 'freebsd', 'haiku', 'l4', 'linux', 'netbsd', 'nt', 'openbsd', \
190 'os2', 'solaris', 'win', 'os-agnostic'):
191 return True;
192 return False;
193
194
195def isValidArch(sArch):
196 """
197 Validates the CPU architecture name.
198 """
199 if sArch in ('x86', 'amd64', 'sparc32', 'sparc64', 's390', 's390x', 'ppc32', 'ppc64', \
200 'mips32', 'mips64', 'ia64', 'hppa32', 'hppa64', 'arm', 'alpha'):
201 return True;
202 return False;
203
204def isValidOsDotArch(sOsDotArch):
205 """
206 Validates the 'os.arch' string.
207 """
208
209 asParts = sOsDotArch.split('.');
210 if asParts.length() != 2:
211 return False;
212 return isValidOs(asParts[0]) \
213 and isValidArch(asParts[1]);
214
215def getHostOsVersion():
216 """
217 Returns the host OS version. This is platform.release with additional
218 distro indicator on linux.
219 """
220 sVersion = platform.release();
221 sOs = getHostOs();
222 if sOs == 'linux':
223 sDist = '';
224 try:
225 # try /etc/lsb-release first to distinguish between Debian and Ubuntu
226 with open('/etc/lsb-release') as oFile:
227 for sLine in oFile:
228 oMatch = re.search(r'(?:DISTRIB_DESCRIPTION\s*=)\s*"*(.*)"', sLine);
229 if oMatch is not None:
230 sDist = oMatch.group(1).strip();
231 except:
232 pass;
233 if sDist:
234 sVersion += ' / ' + sDist;
235 else:
236 asFiles = \
237 [
238 [ '/etc/debian_version', 'Debian v'],
239 [ '/etc/gentoo-release', '' ],
240 [ '/etc/oracle-release', '' ],
241 [ '/etc/redhat-release', '' ],
242 [ '/etc/SuSE-release', '' ],
243 ];
244 for sFile, sPrefix in asFiles:
245 if os.path.isfile(sFile):
246 try:
247 with open(sFile) as oFile:
248 sLine = oFile.readline();
249 except:
250 continue;
251 sLine = sLine.strip()
252 if sLine:
253 sVersion += ' / ' + sPrefix + sLine;
254 break;
255
256 elif sOs == 'solaris':
257 sVersion = platform.version();
258 if os.path.isfile('/etc/release'):
259 try:
260 with open('/etc/release') as oFile:
261 sLast = oFile.readlines()[-1];
262 sLast = sLast.strip();
263 if sLast:
264 sVersion += ' (' + sLast + ')';
265 except:
266 pass;
267
268 elif sOs == 'darwin':`
269 def getMacVersionName(sVersion):
270 aOsVersion = sVersion.split('.') # example: ('10','15','7')
271 codenames = {"4": "Tiger",
272 "5": "Leopard",
273 "6": "Snow Leopard",
274 "7": "Lion",
275 "8": "Mountain Lion",
276 "9": "Mavericks",
277 "10": "Yosemite",
278 "11": "El Capitan",
279 "12": "Sierra",
280 "13": "High Sierra",
281 "14": "Mojave",
282 "15": "Catalina",
283 "16": "Wrong version",
284 }
285 codenames_afterCatalina = {"11": "Big Sur",
286 "12": "Monterey",
287 "13": "Ventura",
288 "14": "Unknown 15",
289 "15": "Unknown 16"}
290
291 if aOsVersion[0] == '10':
292 return codenames[aOsVersion[1]]
293 else:
294 return codenames_afterCatalina[aOsVersion[0]]
295
296 sOsxVersion = platform.mac_ver()[0]
297 sVersion += ' / OS X ' + sOsxVersion + ' (' + getMacVersionName(sOsxVersion) + ')'
298
299 elif sOs == 'win':
300 class OSVersionInfoEx(ctypes.Structure):
301 """ OSVERSIONEX """
302 kaFields = [
303 ('dwOSVersionInfoSize', ctypes.c_ulong),
304 ('dwMajorVersion', ctypes.c_ulong),
305 ('dwMinorVersion', ctypes.c_ulong),
306 ('dwBuildNumber', ctypes.c_ulong),
307 ('dwPlatformId', ctypes.c_ulong),
308 ('szCSDVersion', ctypes.c_wchar*128),
309 ('wServicePackMajor', ctypes.c_ushort),
310 ('wServicePackMinor', ctypes.c_ushort),
311 ('wSuiteMask', ctypes.c_ushort),
312 ('wProductType', ctypes.c_byte),
313 ('wReserved', ctypes.c_byte)]
314 _fields_ = kaFields # pylint: disable=invalid-name
315
316 def __init__(self):
317 super(OSVersionInfoEx, self).__init__()
318 self.dwOSVersionInfoSize = ctypes.sizeof(self)
319
320 oOsVersion = OSVersionInfoEx()
321 rc = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(oOsVersion))
322 if rc == 0:
323 # Python platform.release() is not reliable for newer server releases
324 if oOsVersion.wProductType != 1:
325 if oOsVersion.dwMajorVersion == 10 and oOsVersion.dwMinorVersion == 0:
326 sVersion = '2016Server';
327 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 3:
328 sVersion = '2012ServerR2';
329 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 2:
330 sVersion = '2012Server';
331 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 1:
332 sVersion = '2008ServerR2';
333 elif oOsVersion.dwMajorVersion == 6 and oOsVersion.dwMinorVersion == 0:
334 sVersion = '2008Server';
335 elif oOsVersion.dwMajorVersion == 5 and oOsVersion.dwMinorVersion == 2:
336 sVersion = '2003Server';
337 sVersion += ' build ' + str(oOsVersion.dwBuildNumber)
338 if oOsVersion.wServicePackMajor:
339 sVersion += ' SP' + str(oOsVersion.wServicePackMajor)
340 if oOsVersion.wServicePackMinor:
341 sVersion += '.' + str(oOsVersion.wServicePackMinor)
342
343 return sVersion;
344
345def getPresentCpuCount():
346 """
347 Gets the number of CPUs present in the system.
348
349 This differs from multiprocessor.cpu_count() and os.cpu_count() on windows in
350 that we return the active count rather than the maximum count. If we don't,
351 we will end up thinking testboxmem1 has 512 CPU threads, which it doesn't and
352 never will have.
353
354 @todo This is probably not exactly what we get on non-windows...
355 """
356
357 if getHostOs() == 'win':
358 fnGetActiveProcessorCount = getattr(ctypes.windll.kernel32, 'GetActiveProcessorCount', None);
359 if fnGetActiveProcessorCount:
360 cCpus = fnGetActiveProcessorCount(ctypes.c_ushort(0xffff));
361 if cCpus > 0:
362 return cCpus;
363
364 import multiprocessing
365 return multiprocessing.cpu_count();
366
367
368#
369# File system.
370#
371
372def openNoInherit(sFile, sMode = 'r'):
373 """
374 Wrapper around open() that tries it's best to make sure the file isn't
375 inherited by child processes.
376
377 This is a best effort thing at the moment as it doesn't synchronizes with
378 child process spawning in any way. Thus it can be subject to races in
379 multithreaded programs.
380 """
381
382 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
383 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
384 if uPythonVer >= ((3 << 16) | 4):
385 oFile = open(sFile, sMode); # pylint: disable=consider-using-with
386 else:
387 try:
388 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error
389 except:
390 # On windows, we can use the 'N' flag introduced in Visual C++ 7.0 or 7.1 with python 2.x.
391 if getHostOs() == 'win':
392 if uPythonVer < (3 << 16):
393 offComma = sMode.find(',');
394 if offComma < 0:
395 return open(sFile, sMode + 'N'); # pylint: disable=consider-using-with
396 return open(sFile, # pylint: disable=consider-using-with,bad-open-mode
397 sMode[:offComma] + 'N' + sMode[offComma:]);
398
399 # Just in case.
400 return open(sFile, sMode); # pylint: disable=consider-using-with
401
402 oFile = open(sFile, sMode); # pylint: disable=consider-using-with
403 #try:
404 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
405 #except:
406 # pass;
407 return oFile;
408
409def openNoDenyDeleteNoInherit(sFile, sMode = 'r'):
410 """
411 Wrapper around open() that tries it's best to make sure the file isn't
412 inherited by child processes.
413
414 This is a best effort thing at the moment as it doesn't synchronizes with
415 child process spawning in any way. Thus it can be subject to races in
416 multithreaded programs.
417 """
418
419 if getHostOs() == 'win':
420 # Need to use CreateFile directly to open the file so we can feed it FILE_SHARE_DELETE.
421 # pylint: disable=no-member,c-extension-no-member
422 fAccess = 0;
423 fDisposition = win32file.OPEN_EXISTING;
424 if 'r' in sMode or '+' in sMode:
425 fAccess |= win32file.GENERIC_READ;
426 if 'a' in sMode:
427 fAccess |= win32file.GENERIC_WRITE;
428 fDisposition = win32file.OPEN_ALWAYS;
429 elif 'w' in sMode:
430 fAccess = win32file.GENERIC_WRITE;
431 if '+' in sMode:
432 fDisposition = win32file.OPEN_ALWAYS;
433 fAccess |= win32file.GENERIC_READ;
434 else:
435 fDisposition = win32file.CREATE_ALWAYS;
436 if not fAccess:
437 fAccess |= win32file.GENERIC_READ;
438 fSharing = (win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE
439 | win32file.FILE_SHARE_DELETE);
440 hFile = win32file.CreateFile(sFile, fAccess, fSharing, None, fDisposition, 0, None);
441 if 'a' in sMode:
442 win32file.SetFilePointer(hFile, 0, win32file.FILE_END);
443
444 # Turn the NT handle into a CRT file descriptor.
445 hDetachedFile = hFile.Detach();
446 if fAccess == win32file.GENERIC_READ:
447 fOpen = os.O_RDONLY;
448 elif fAccess == win32file.GENERIC_WRITE:
449 fOpen = os.O_WRONLY;
450 else:
451 fOpen = os.O_RDWR;
452 # pulint: enable=no-member,c-extension-no-member
453 if 'a' in sMode:
454 fOpen |= os.O_APPEND;
455 if 'b' in sMode or 't' in sMode:
456 fOpen |= os.O_TEXT; # pylint: disable=no-member
457 fdFile = msvcrt.open_osfhandle(hDetachedFile, fOpen);
458
459 # Tell python to use this handle.
460 oFile = os.fdopen(fdFile, sMode);
461 else:
462 oFile = open(sFile, sMode); # pylint: disable=consider-using-with
463
464 # Python 3.4 and later automatically creates non-inherit handles. See PEP-0446.
465 uPythonVer = (sys.version_info[0] << 16) | (sys.version_info[1] & 0xffff);
466 if uPythonVer < ((3 << 16) | 4):
467 try:
468 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=import-error
469 except:
470 pass;
471 else:
472 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
473 return oFile;
474
475def noxcptReadLink(sPath, sXcptRet, sEncoding = 'utf-8'):
476 """
477 No exceptions os.readlink wrapper.
478 """
479 try:
480 sRet = os.readlink(sPath); # pylint: disable=no-member
481 except:
482 return sXcptRet;
483 if hasattr(sRet, 'decode'):
484 sRet = sRet.decode(sEncoding, 'ignore');
485 return sRet;
486
487def readFile(sFile, sMode = 'rb'):
488 """
489 Reads the entire file.
490 """
491 with open(sFile, sMode) as oFile:
492 sRet = oFile.read();
493 return sRet;
494
495def noxcptReadFile(sFile, sXcptRet, sMode = 'rb', sEncoding = 'utf-8'):
496 """
497 No exceptions common.readFile wrapper.
498 """
499 try:
500 sRet = readFile(sFile, sMode);
501 except:
502 sRet = sXcptRet;
503 if sEncoding is not None and hasattr(sRet, 'decode'):
504 sRet = sRet.decode(sEncoding, 'ignore');
505 return sRet;
506
507def noxcptRmDir(sDir, oXcptRet = False):
508 """
509 No exceptions os.rmdir wrapper.
510 """
511 oRet = True;
512 try:
513 os.rmdir(sDir);
514 except:
515 oRet = oXcptRet;
516 return oRet;
517
518def noxcptDeleteFile(sFile, oXcptRet = False):
519 """
520 No exceptions os.remove wrapper.
521 """
522 oRet = True;
523 try:
524 os.remove(sFile);
525 except:
526 oRet = oXcptRet;
527 return oRet;
528
529
530def dirEnumerateTree(sDir, fnCallback, fIgnoreExceptions = True):
531 # type: (string, (string, stat) -> bool) -> bool
532 """
533 Recursively walks a directory tree, calling fnCallback for each.
534
535 fnCallback takes a full path and stat object (can be None). It
536 returns a boolean value, False stops walking and returns immediately.
537
538 Returns True or False depending on fnCallback.
539 Returns None fIgnoreExceptions is True and an exception was raised by listdir.
540 """
541 def __worker(sCurDir):
542 """ Worker for """
543 try:
544 asNames = os.listdir(sCurDir);
545 except:
546 if not fIgnoreExceptions:
547 raise;
548 return None;
549 rc = True;
550 for sName in asNames:
551 if sName not in [ '.', '..' ]:
552 sFullName = os.path.join(sCurDir, sName);
553 try: oStat = os.lstat(sFullName);
554 except: oStat = None;
555 if fnCallback(sFullName, oStat) is False:
556 return False;
557 if oStat is not None and stat.S_ISDIR(oStat.st_mode):
558 rc = __worker(sFullName);
559 if rc is False:
560 break;
561 return rc;
562
563 # Ensure unicode path here so listdir also returns unicode on windows.
564 ## @todo figure out unicode stuff on non-windows.
565 if sys.platform == 'win32':
566 sDir = unicode(sDir);
567 return __worker(sDir);
568
569
570
571def formatFileMode(uMode):
572 # type: (int) -> string
573 """
574 Format a st_mode value 'ls -la' fasion.
575 Returns string.
576 """
577 if stat.S_ISDIR(uMode): sMode = 'd';
578 elif stat.S_ISREG(uMode): sMode = '-';
579 elif stat.S_ISLNK(uMode): sMode = 'l';
580 elif stat.S_ISFIFO(uMode): sMode = 'p';
581 elif stat.S_ISCHR(uMode): sMode = 'c';
582 elif stat.S_ISBLK(uMode): sMode = 'b';
583 elif stat.S_ISSOCK(uMode): sMode = 's';
584 else: sMode = '?';
585 ## @todo sticky bits.
586 sMode += 'r' if uMode & stat.S_IRUSR else '-';
587 sMode += 'w' if uMode & stat.S_IWUSR else '-';
588 sMode += 'x' if uMode & stat.S_IXUSR else '-';
589 sMode += 'r' if uMode & stat.S_IRGRP else '-';
590 sMode += 'w' if uMode & stat.S_IWGRP else '-';
591 sMode += 'x' if uMode & stat.S_IXGRP else '-';
592 sMode += 'r' if uMode & stat.S_IROTH else '-';
593 sMode += 'w' if uMode & stat.S_IWOTH else '-';
594 sMode += 'x' if uMode & stat.S_IXOTH else '-';
595 sMode += ' ';
596 return sMode;
597
598
599def formatFileStat(oStat):
600 # type: (stat) -> string
601 """
602 Format a stat result 'ls -la' fasion (numeric IDs).
603 Returns string.
604 """
605 return '%s %3s %4s %4s %10s %s' \
606 % (formatFileMode(oStat.st_mode), oStat.st_nlink, oStat.st_uid, oStat.st_gid, oStat.st_size,
607 time.strftime('%Y-%m-%d %H:%M', time.localtime(oStat.st_mtime)), );
608
609## Good buffer for file operations.
610g_cbGoodBufferSize = 256*1024;
611
612## The original shutil.copyfileobj.
613g_fnOriginalShCopyFileObj = None;
614
615def __myshutilcopyfileobj(fsrc, fdst, length = g_cbGoodBufferSize):
616 """ shutil.copyfileobj with different length default value (16384 is slow with python 2.7 on windows). """
617 return g_fnOriginalShCopyFileObj(fsrc, fdst, length);
618
619def __installShUtilHacks(shutil):
620 """ Installs the shutil buffer size hacks. """
621 global g_fnOriginalShCopyFileObj;
622 if g_fnOriginalShCopyFileObj is None:
623 g_fnOriginalShCopyFileObj = shutil.copyfileobj;
624 shutil.copyfileobj = __myshutilcopyfileobj;
625 return True;
626
627
628def copyFileSimple(sFileSrc, sFileDst):
629 """
630 Wrapper around shutil.copyfile that simply copies the data of a regular file.
631 Raises exception on failure.
632 Return True for show.
633 """
634 import shutil;
635 __installShUtilHacks(shutil);
636 return shutil.copyfile(sFileSrc, sFileDst);
637
638
639def getDiskUsage(sPath):
640 """
641 Get free space of a partition that corresponds to specified sPath in MB.
642
643 Returns partition free space value in MB.
644 """
645 if platform.system() == 'Windows':
646 oCTypeFreeSpace = ctypes.c_ulonglong(0);
647 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None,
648 ctypes.pointer(oCTypeFreeSpace));
649 cbFreeSpace = oCTypeFreeSpace.value;
650 else:
651 oStats = os.statvfs(sPath); # pylint: disable=no-member
652 cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree;
653
654 # Convert to MB
655 cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024);
656
657 return cMbFreeSpace;
658
659
660
661#
662# SubProcess.
663#
664
665def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs):
666 """
667 If the "executable" is a python script, insert the python interpreter at
668 the head of the argument list so that it will work on systems which doesn't
669 support hash-bang scripts.
670 """
671
672 asArgs = dKeywordArgs.get('args');
673 if asArgs is None:
674 asArgs = aPositionalArgs[0];
675
676 if asArgs[0].endswith('.py'):
677 if sys.executable:
678 asArgs.insert(0, sys.executable);
679 else:
680 asArgs.insert(0, 'python');
681
682 # paranoia...
683 if dKeywordArgs.get('args') is not None:
684 dKeywordArgs['args'] = asArgs;
685 else:
686 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
687 return None;
688
689def processPopenSafe(*aPositionalArgs, **dKeywordArgs):
690 """
691 Wrapper for subprocess.Popen that's Ctrl-C safe on windows.
692 """
693 if getHostOs() == 'win':
694 if dKeywordArgs.get('creationflags', 0) == 0:
695 dKeywordArgs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP;
696 return subprocess.Popen(*aPositionalArgs, **dKeywordArgs); # pylint: disable=consider-using-with
697
698def processStart(*aPositionalArgs, **dKeywordArgs):
699 """
700 Wrapper around subprocess.Popen to deal with its absence in older
701 python versions.
702 Returns process object on success which can be worked on.
703 """
704 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
705 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
706
707def processCall(*aPositionalArgs, **dKeywordArgs):
708 """
709 Wrapper around subprocess.call to deal with its absence in older
710 python versions.
711 Returns process exit code (see subprocess.poll).
712 """
713 assert dKeywordArgs.get('stdout') is None;
714 assert dKeywordArgs.get('stderr') is None;
715 oProcess = processStart(*aPositionalArgs, **dKeywordArgs);
716 return oProcess.wait();
717
718def processOutputChecked(*aPositionalArgs, **dKeywordArgs):
719 """
720 Wrapper around subprocess.check_output to deal with its absense in older
721 python versions.
722
723 Extra keywords for specifying now output is to be decoded:
724 sEncoding='utf-8
725 fIgnoreEncoding=True/False
726 """
727 sEncoding = dKeywordArgs.get('sEncoding');
728 if sEncoding is not None: del dKeywordArgs['sEncoding'];
729 else: sEncoding = 'utf-8';
730
731 fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding');
732 if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding'];
733 else: fIgnoreEncoding = True;
734
735 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
736 oProcess = processPopenSafe(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
737
738 sOutput, _ = oProcess.communicate();
739 iExitCode = oProcess.poll();
740
741 if iExitCode != 0:
742 asArgs = dKeywordArgs.get('args');
743 if asArgs is None:
744 asArgs = aPositionalArgs[0];
745 print(sOutput);
746 raise subprocess.CalledProcessError(iExitCode, asArgs);
747
748 if hasattr(sOutput, 'decode'):
749 sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
750 return sOutput;
751
752def processOutputUnchecked(*aPositionalArgs, **dKeywordArgs):
753 """
754 Similar to processOutputChecked, but returns status code and both stdout
755 and stderr results.
756
757 Extra keywords for specifying now output is to be decoded:
758 sEncoding='utf-8
759 fIgnoreEncoding=True/False
760 """
761 sEncoding = dKeywordArgs.get('sEncoding');
762 if sEncoding is not None: del dKeywordArgs['sEncoding'];
763 else: sEncoding = 'utf-8';
764
765 fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding');
766 if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding'];
767 else: fIgnoreEncoding = True;
768
769 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
770 oProcess = processPopenSafe(stdout = subprocess.PIPE, stderr = subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
771
772 sOutput, sError = oProcess.communicate();
773 iExitCode = oProcess.poll();
774
775 if hasattr(sOutput, 'decode'):
776 sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
777 if hasattr(sError, 'decode'):
778 sError = sError.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
779 return (iExitCode, sOutput, sError);
780
781g_fOldSudo = None;
782def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True):
783 """
784 Adds 'sudo' (or similar) to the args parameter, whereever it is.
785 """
786
787 # Are we root?
788 fIsRoot = True;
789 try:
790 fIsRoot = os.getuid() == 0; # pylint: disable=no-member
791 except:
792 pass;
793
794 # If not, prepend sudo (non-interactive, simulate initial login).
795 if fIsRoot is not True:
796 asArgs = dKeywordArgs.get('args');
797 if asArgs is None:
798 asArgs = aPositionalArgs[0];
799
800 # Detect old sudo.
801 global g_fOldSudo;
802 if g_fOldSudo is None:
803 try:
804 sVersion = str(processOutputChecked(['sudo', '-V']));
805 except:
806 sVersion = '1.7.0';
807 sVersion = sVersion.strip().split('\n', 1)[0];
808 sVersion = sVersion.replace('Sudo version', '').strip();
809 g_fOldSudo = len(sVersion) >= 4 \
810 and sVersion[0] == '1' \
811 and sVersion[1] == '.' \
812 and sVersion[2] <= '6' \
813 and sVersion[3] == '.';
814
815 asArgs.insert(0, 'sudo');
816 if not g_fOldSudo:
817 asArgs.insert(1, '-n');
818 if fInitialEnv and not g_fOldSudo:
819 asArgs.insert(1, '-i');
820
821 # paranoia...
822 if dKeywordArgs.get('args') is not None:
823 dKeywordArgs['args'] = asArgs;
824 else:
825 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
826 return None;
827
828
829def sudoProcessStart(*aPositionalArgs, **dKeywordArgs):
830 """
831 sudo (or similar) + subprocess.Popen,
832 returning the process object on success.
833 """
834 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
835 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
836 return processStart(*aPositionalArgs, **dKeywordArgs);
837
838def sudoProcessCall(*aPositionalArgs, **dKeywordArgs):
839 """
840 sudo (or similar) + subprocess.call
841 """
842 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
843 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
844 return processCall(*aPositionalArgs, **dKeywordArgs);
845
846def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs):
847 """
848 sudo (or similar) + subprocess.check_output.
849 """
850 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
851 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
852 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
853
854def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs):
855 """
856 sudo (or similar) + subprocess.check_output, except '-i' isn't used.
857 """
858 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
859 _sudoFixArguments(aPositionalArgs, dKeywordArgs, False);
860 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
861
862def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs):
863 """
864 sudo (or similar) + processPopenSafe.
865 """
866 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
867 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
868 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
869
870
871def whichProgram(sName, sPath = None):
872 """
873 Works similar to the 'which' utility on unix.
874
875 Returns path to the given program if found.
876 Returns None if not found.
877 """
878 sHost = getHostOs();
879 sSep = ';' if sHost in [ 'win', 'os2' ] else ':';
880
881 if sPath is None:
882 if sHost == 'win':
883 sPath = os.environ.get('Path', None);
884 else:
885 sPath = os.environ.get('PATH', None);
886 if sPath is None:
887 return None;
888
889 for sDir in sPath.split(sSep):
890 if sDir.strip() != '':
891 sTest = os.path.abspath(os.path.join(sDir, sName));
892 else:
893 sTest = os.path.abspath(sName);
894 if os.path.exists(sTest):
895 return sTest;
896
897 return None;
898
899#
900# Generic process stuff.
901#
902
903def processInterrupt(uPid):
904 """
905 Sends a SIGINT or equivalent to interrupt the specified process.
906 Returns True on success, False on failure.
907
908 On Windows hosts this may not work unless the process happens to be a
909 process group leader.
910 """
911 if sys.platform == 'win32':
912 try:
913 win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, # pylint: disable=no-member,c-extension-no-member
914 uPid);
915 fRc = True;
916 except:
917 fRc = False;
918 else:
919 try:
920 os.kill(uPid, signal.SIGINT);
921 fRc = True;
922 except:
923 fRc = False;
924 return fRc;
925
926def sendUserSignal1(uPid):
927 """
928 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
929 (VBoxSVC) or something.
930 Returns True on success, False on failure or if not supported (win).
931
932 On Windows hosts this may not work unless the process happens to be a
933 process group leader.
934 """
935 if sys.platform == 'win32':
936 fRc = False;
937 else:
938 try:
939 os.kill(uPid, signal.SIGUSR1); # pylint: disable=no-member
940 fRc = True;
941 except:
942 fRc = False;
943 return fRc;
944
945def processTerminate(uPid):
946 """
947 Terminates the process in a nice manner (SIGTERM or equivalent).
948 Returns True on success, False on failure.
949 """
950 fRc = False;
951 if sys.platform == 'win32':
952 try:
953 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, # pylint: disable=no-member,c-extension-no-member
954 False, uPid);
955 except:
956 pass;
957 else:
958 try:
959 win32process.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member
960 0x40010004); # DBG_TERMINATE_PROCESS
961 fRc = True;
962 except:
963 pass;
964 hProcess.Close(); #win32api.CloseHandle(hProcess)
965 else:
966 try:
967 os.kill(uPid, signal.SIGTERM);
968 fRc = True;
969 except:
970 pass;
971 return fRc;
972
973def processKill(uPid):
974 """
975 Terminates the process with extreme prejudice (SIGKILL).
976 Returns True on success, False on failure.
977 """
978 if sys.platform == 'win32':
979 fRc = processTerminate(uPid);
980 else:
981 try:
982 os.kill(uPid, signal.SIGKILL); # pylint: disable=no-member
983 fRc = True;
984 except:
985 fRc = False;
986 return fRc;
987
988def processKillWithNameCheck(uPid, sName):
989 """
990 Like processKill(), but checks if the process name matches before killing
991 it. This is intended for killing using potentially stale pid values.
992
993 Returns True on success, False on failure.
994 """
995
996 if processCheckPidAndName(uPid, sName) is not True:
997 return False;
998 return processKill(uPid);
999
1000
1001def processExists(uPid):
1002 """
1003 Checks if the specified process exits.
1004 This will only work if we can signal/open the process.
1005
1006 Returns True if it positively exists, False otherwise.
1007 """
1008 sHostOs = getHostOs();
1009 if sHostOs == 'win':
1010 fRc = False;
1011 # We try open the process for waiting since this is generally only forbidden in a very few cases.
1012 try:
1013 hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, # pylint: disable=no-member,c-extension-no-member
1014 False, uPid);
1015 except pywintypes.error as oXcpt: # pylint: disable=no-member
1016 if oXcpt.winerror == winerror.ERROR_ACCESS_DENIED:
1017 fRc = True;
1018 except Exception as oXcpt:
1019 pass;
1020 else:
1021 hProcess.Close();
1022 fRc = True;
1023 else:
1024 fRc = False;
1025 try:
1026 os.kill(uPid, 0);
1027 fRc = True;
1028 except OSError as oXcpt:
1029 if oXcpt.errno == errno.EPERM:
1030 fRc = True;
1031 except:
1032 pass;
1033 return fRc;
1034
1035def processCheckPidAndName(uPid, sName):
1036 """
1037 Checks if a process PID and NAME matches.
1038 """
1039 fRc = processExists(uPid);
1040 if fRc is not True:
1041 return False;
1042
1043 if sys.platform == 'win32':
1044 try:
1045 from win32com.client import GetObject; # pylint: disable=import-error
1046 oWmi = GetObject('winmgmts:');
1047 aoProcesses = oWmi.InstancesOf('Win32_Process');
1048 for oProcess in aoProcesses:
1049 if long(oProcess.Properties_("ProcessId").Value) == uPid:
1050 sCurName = oProcess.Properties_("Name").Value;
1051 #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName));
1052 sName = sName.lower();
1053 sCurName = sCurName.lower();
1054 if os.path.basename(sName) == sName:
1055 sCurName = os.path.basename(sCurName);
1056
1057 if sCurName == sName \
1058 or sCurName + '.exe' == sName \
1059 or sCurName == sName + '.exe':
1060 fRc = True;
1061 break;
1062 except:
1063 #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName));
1064 pass;
1065 else:
1066 if sys.platform in ('linux2', 'linux', 'linux3', 'linux4', 'linux5', 'linux6'):
1067 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
1068 elif sys.platform in ('sunos5',):
1069 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
1070 elif sys.platform in ('darwin',):
1071 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
1072 else:
1073 asPsCmd = None;
1074
1075 if asPsCmd is not None:
1076 try:
1077 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE); # pylint: disable=consider-using-with
1078 sCurName = oPs.communicate()[0];
1079 iExitCode = oPs.wait();
1080 except:
1081 #reporter.logXcpt();
1082 return False;
1083
1084 # ps fails with non-zero exit code if the pid wasn't found.
1085 if iExitCode != 0:
1086 return False;
1087 if sCurName is None:
1088 return False;
1089 sCurName = sCurName.strip();
1090 if not sCurName:
1091 return False;
1092
1093 if os.path.basename(sName) == sName:
1094 sCurName = os.path.basename(sCurName);
1095 elif os.path.basename(sCurName) == sCurName:
1096 sName = os.path.basename(sName);
1097
1098 if sCurName != sName:
1099 return False;
1100
1101 fRc = True;
1102 return fRc;
1103
1104def processGetInfo(uPid, fSudo = False):
1105 """
1106 Tries to acquire state information of the given process.
1107
1108 Returns a string with the information on success or None on failure or
1109 if the host is not supported.
1110
1111 Note that the format of the information is host system dependent and will
1112 likely differ much between different hosts.
1113 """
1114 fRc = processExists(uPid);
1115 if fRc is not True:
1116 return None;
1117
1118 sHostOs = getHostOs();
1119 if sHostOs in [ 'linux',]:
1120 sGdb = '/usr/bin/gdb';
1121 if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb';
1122 if not os.path.isfile(sGdb): sGdb = 'gdb';
1123 aasCmd = [
1124 [ sGdb, '-batch',
1125 '-ex', 'set pagination off',
1126 '-ex', 'thread apply all bt',
1127 '-ex', 'info proc mapping',
1128 '-ex', 'info sharedlibrary',
1129 '-p', '%u' % (uPid,), ],
1130 ];
1131 elif sHostOs == 'darwin':
1132 # LLDB doesn't work in batch mode when attaching to a process, at least
1133 # with macOS Sierra (10.12). GDB might not be installed. Use the sample
1134 # tool instead with a 1 second duration and 1000ms sampling interval to
1135 # get one stack trace. For the process mappings use vmmap.
1136 aasCmd = [
1137 [ '/usr/bin/sample', '-mayDie', '%u' % (uPid,), '1', '1000', ],
1138 [ '/usr/bin/vmmap', '%u' % (uPid,), ],
1139 ];
1140 elif sHostOs == 'solaris':
1141 aasCmd = [
1142 [ '/usr/bin/pstack', '%u' % (uPid,), ],
1143 [ '/usr/bin/pmap', '%u' % (uPid,), ],
1144 ];
1145 else:
1146 aasCmd = [];
1147
1148 sInfo = '';
1149 for asCmd in aasCmd:
1150 try:
1151 if fSudo:
1152 sThisInfo = sudoProcessOutputChecked(asCmd);
1153 else:
1154 sThisInfo = processOutputChecked(asCmd);
1155 if sThisInfo is not None:
1156 sInfo += sThisInfo;
1157 except:
1158 pass;
1159 if not sInfo:
1160 sInfo = None;
1161
1162 return sInfo;
1163
1164
1165class ProcessInfo(object):
1166 """Process info."""
1167 def __init__(self, iPid):
1168 self.iPid = iPid;
1169 self.iParentPid = None;
1170 self.sImage = None;
1171 self.sName = None;
1172 self.asArgs = None;
1173 self.sCwd = None;
1174 self.iGid = None;
1175 self.iUid = None;
1176 self.iProcGroup = None;
1177 self.iSessionId = None;
1178
1179 def loadAll(self):
1180 """Load all the info."""
1181 sOs = getHostOs();
1182 if sOs == 'linux':
1183 sProc = '/proc/%s/' % (self.iPid,);
1184 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None);
1185 if self.sImage is None:
1186 self.sImage = noxcptReadFile(sProc + 'comm', None);
1187 if self.sImage: self.sImage = self.sImage.strip();
1188 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None);
1189 if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00');
1190 #elif sOs == 'solaris': - doesn't work for root processes, suid proces, and other stuff.
1191 # sProc = '/proc/%s/' % (self.iPid,);
1192 # if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None);
1193 # if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None);
1194 else:
1195 pass;
1196 if self.sName is None and self.sImage is not None:
1197 self.sName = self.sImage;
1198
1199 def windowsGrabProcessInfo(self, oProcess):
1200 """Windows specific loadAll."""
1201 try: self.sName = oProcess.Properties_("Name").Value;
1202 except: pass;
1203 try: self.sImage = oProcess.Properties_("ExecutablePath").Value;
1204 except: pass;
1205 try: self.asArgs = [oProcess.Properties_("CommandLine").Value]; ## @todo split it.
1206 except: pass;
1207 try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value;
1208 except: pass;
1209 try: self.iSessionId = oProcess.Properties_("SessionId").Value;
1210 except: pass;
1211 if self.sName is None and self.sImage is not None:
1212 self.sName = self.sImage;
1213
1214 def getBaseImageName(self):
1215 """
1216 Gets the base image name if available, use the process name if not available.
1217 Returns image/process base name or None.
1218 """
1219 sRet = self.sImage if self.sName is None else self.sName;
1220 if sRet is None:
1221 self.loadAll();
1222 sRet = self.sImage if self.sName is None else self.sName;
1223 if sRet is None:
1224 if not self.asArgs:
1225 return None;
1226 sRet = self.asArgs[0];
1227 if not sRet:
1228 return None;
1229 return os.path.basename(sRet);
1230
1231 def getBaseImageNameNoExeSuff(self):
1232 """
1233 Same as getBaseImageName, except any '.exe' or similar suffix is stripped.
1234 """
1235 sRet = self.getBaseImageName();
1236 if sRet is not None and len(sRet) > 4 and sRet[-4] == '.':
1237 if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]:
1238 sRet = sRet[:-4];
1239 return sRet;
1240
1241
1242def processListAll():
1243 """
1244 Return a list of ProcessInfo objects for all the processes in the system
1245 that the current user can see.
1246 """
1247 asProcesses = [];
1248
1249 sOs = getHostOs();
1250 if sOs == 'win':
1251 from win32com.client import GetObject; # pylint: disable=import-error
1252 oWmi = GetObject('winmgmts:');
1253 aoProcesses = oWmi.InstancesOf('Win32_Process');
1254 for oProcess in aoProcesses:
1255 try:
1256 iPid = int(oProcess.Properties_("ProcessId").Value);
1257 except:
1258 continue;
1259 oMyInfo = ProcessInfo(iPid);
1260 oMyInfo.windowsGrabProcessInfo(oProcess);
1261 asProcesses.append(oMyInfo);
1262 return asProcesses;
1263
1264 if sOs in [ 'linux', ]: # Not solaris, ps gets more info than /proc/.
1265 try:
1266 asDirs = os.listdir('/proc');
1267 except:
1268 asDirs = [];
1269 for sDir in asDirs:
1270 if sDir.isdigit():
1271 asProcesses.append(ProcessInfo(int(sDir),));
1272 return asProcesses;
1273
1274 #
1275 # The other OSes parses the output from the 'ps' utility.
1276 #
1277 asPsCmd = [
1278 '/bin/ps', # 0
1279 '-A', # 1
1280 '-o', 'pid=', # 2,3
1281 '-o', 'ppid=', # 4,5
1282 '-o', 'pgid=', # 6,7
1283 '-o', 'sid=', # 8,9
1284 '-o', 'uid=', # 10,11
1285 '-o', 'gid=', # 12,13
1286 '-o', 'comm=' # 14,15
1287 ];
1288
1289 if sOs == 'darwin':
1290 assert asPsCmd[9] == 'sid=';
1291 asPsCmd[9] = 'sess=';
1292 elif sOs == 'solaris':
1293 asPsCmd[0] = '/usr/bin/ps';
1294
1295 try:
1296 sRaw = processOutputChecked(asPsCmd);
1297 except:
1298 return asProcesses;
1299
1300 for sLine in sRaw.split('\n'):
1301 sLine = sLine.lstrip();
1302 if len(sLine) < 7 or not sLine[0].isdigit():
1303 continue;
1304
1305 iField = 0;
1306 off = 0;
1307 aoFields = [None, None, None, None, None, None, None];
1308 while iField < 7:
1309 # Eat whitespace.
1310 while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'):
1311 off += 1;
1312
1313 # Final field / EOL.
1314 if iField == 6:
1315 aoFields[6] = sLine[off:];
1316 break;
1317 if off >= len(sLine):
1318 break;
1319
1320 # Generic field parsing.
1321 offStart = off;
1322 off += 1;
1323 while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t':
1324 off += 1;
1325 try:
1326 if iField != 3:
1327 aoFields[iField] = int(sLine[offStart:off]);
1328 else:
1329 aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address.
1330 except:
1331 pass;
1332 iField += 1;
1333
1334 if aoFields[0] is not None:
1335 oMyInfo = ProcessInfo(aoFields[0]);
1336 oMyInfo.iParentPid = aoFields[1];
1337 oMyInfo.iProcGroup = aoFields[2];
1338 oMyInfo.iSessionId = aoFields[3];
1339 oMyInfo.iUid = aoFields[4];
1340 oMyInfo.iGid = aoFields[5];
1341 oMyInfo.sName = aoFields[6];
1342 asProcesses.append(oMyInfo);
1343
1344 return asProcesses;
1345
1346
1347def processCollectCrashInfo(uPid, fnLog, fnCrashFile):
1348 """
1349 Looks for information regarding the demise of the given process.
1350 """
1351 sOs = getHostOs();
1352 if sOs == 'darwin':
1353 #
1354 # On darwin we look for crash and diagnostic reports.
1355 #
1356 asLogDirs = [
1357 u'/Library/Logs/DiagnosticReports/',
1358 u'/Library/Logs/CrashReporter/',
1359 u'~/Library/Logs/DiagnosticReports/',
1360 u'~/Library/Logs/CrashReporter/',
1361 ];
1362 for sDir in asLogDirs:
1363 sDir = os.path.expanduser(sDir);
1364 if not os.path.isdir(sDir):
1365 continue;
1366 try:
1367 asDirEntries = os.listdir(sDir);
1368 except:
1369 continue;
1370 for sEntry in asDirEntries:
1371 # Only interested in .crash files.
1372 _, sSuff = os.path.splitext(sEntry);
1373 if sSuff != '.crash':
1374 continue;
1375
1376 # The pid can be found at the end of the first line.
1377 sFull = os.path.join(sDir, sEntry);
1378 try:
1379 with open(sFull, 'r') as oFile:
1380 sFirstLine = oFile.readline();
1381 except:
1382 continue;
1383 if len(sFirstLine) <= 4 or sFirstLine[-2] != ']':
1384 continue;
1385 offPid = len(sFirstLine) - 3;
1386 while offPid > 1 and sFirstLine[offPid - 1].isdigit():
1387 offPid -= 1;
1388 try: uReportPid = int(sFirstLine[offPid:-2]);
1389 except: continue;
1390
1391 # Does the pid we found match?
1392 if uReportPid == uPid:
1393 fnLog('Found crash report for %u: %s' % (uPid, sFull,));
1394 fnCrashFile(sFull, False);
1395 elif sOs == 'win':
1396 #
1397 # Getting WER reports would be great, however we have trouble match the
1398 # PID to those as they seems not to mention it in the brief reports.
1399 # Instead we'll just look for crash dumps in C:\CrashDumps (our custom
1400 # location - see the windows readme for the testbox script) and what
1401 # the MSDN article lists for now.
1402 #
1403 # It's been observed on Windows server 2012 that the dump files takes
1404 # the form: <processimage>.<decimal-pid>.dmp
1405 #
1406 asDmpDirs = [
1407 u'%SystemDrive%/CrashDumps/', # Testboxes.
1408 u'%LOCALAPPDATA%/CrashDumps/', # MSDN example.
1409 u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service.
1410 u'%WINDIR%/ServiceProfiles/NetworkSerices/',
1411 u'%WINDIR%/ServiceProfiles/',
1412 u'%WINDIR%/System32/Config/SystemProfile/', # System services.
1413 ];
1414 sMatchSuffix = '.%u.dmp' % (uPid,);
1415
1416 for sDir in asDmpDirs:
1417 sDir = os.path.expandvars(sDir);
1418 if not os.path.isdir(sDir):
1419 continue;
1420 try:
1421 asDirEntries = os.listdir(sDir);
1422 except:
1423 continue;
1424 for sEntry in asDirEntries:
1425 if sEntry.endswith(sMatchSuffix):
1426 sFull = os.path.join(sDir, sEntry);
1427 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1428 fnCrashFile(sFull, True);
1429
1430 else:
1431 pass; ## TODO
1432 return None;
1433
1434
1435#
1436# Time.
1437#
1438
1439#
1440# The following test case shows how time.time() only have ~ms resolution
1441# on Windows (tested W10) and why it therefore makes sense to try use
1442# performance counters.
1443#
1444# Note! We cannot use time.clock() as the timestamp must be portable across
1445# processes. See timeout testcase problem on win hosts (no logs).
1446# Also, time.clock() was axed in python 3.8 (https://bugs.python.org/issue31803).
1447#
1448#import sys;
1449#import time;
1450#from common import utils;
1451#
1452#atSeries = [];
1453#for i in xrange(1,160):
1454# if i == 159: time.sleep(10);
1455# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
1456#
1457#tPrev = atSeries[0]
1458#for tCur in atSeries:
1459# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
1460# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
1461# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
1462# print '';
1463# tPrev = tCur
1464#
1465#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
1466#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
1467#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
1468
1469g_fWinUseWinPerfCounter = sys.platform == 'win32';
1470g_fpWinPerfCounterFreq = None;
1471g_oFuncwinQueryPerformanceCounter = None;
1472
1473def _winInitPerfCounter():
1474 """ Initializes the use of performance counters. """
1475 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
1476
1477 uFrequency = ctypes.c_ulonglong(0);
1478 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
1479 if uFrequency.value >= 1000:
1480 #print 'uFrequency = %s' % (uFrequency,);
1481 #print 'type(uFrequency) = %s' % (type(uFrequency),);
1482 g_fpWinPerfCounterFreq = float(uFrequency.value);
1483
1484 # Check that querying the counter works too.
1485 global g_oFuncwinQueryPerformanceCounter
1486 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
1487 uCurValue = ctypes.c_ulonglong(0);
1488 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1489 if uCurValue.value > 0:
1490 return True;
1491 g_fWinUseWinPerfCounter = False;
1492 return False;
1493
1494def _winFloatTime():
1495 """ Gets floating point time on windows. """
1496 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
1497 uCurValue = ctypes.c_ulonglong(0);
1498 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1499 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
1500 return time.time();
1501
1502def timestampNano():
1503 """
1504 Gets a nanosecond timestamp.
1505 """
1506 if g_fWinUseWinPerfCounter is True:
1507 return long(_winFloatTime() * 1000000000);
1508 return long(time.time() * 1000000000);
1509
1510def timestampMilli():
1511 """
1512 Gets a millisecond timestamp.
1513 """
1514 if g_fWinUseWinPerfCounter is True:
1515 return long(_winFloatTime() * 1000);
1516 return long(time.time() * 1000);
1517
1518def timestampSecond():
1519 """
1520 Gets a second timestamp.
1521 """
1522 if g_fWinUseWinPerfCounter is True:
1523 return long(_winFloatTime());
1524 return long(time.time());
1525
1526def secondsSinceUnixEpoch():
1527 """
1528 Returns unix time, floating point second count since 1970-01-01T00:00:00Z
1529 """
1530 ## ASSUMES This returns unix epoch time on all systems we care about...
1531 return time.time();
1532
1533def getTimePrefix():
1534 """
1535 Returns a timestamp prefix, typically used for logging. UTC.
1536 """
1537 try:
1538 oNow = datetime.datetime.utcnow();
1539 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1540 except:
1541 sTs = 'getTimePrefix-exception';
1542 return sTs;
1543
1544def getTimePrefixAndIsoTimestamp():
1545 """
1546 Returns current UTC as log prefix and iso timestamp.
1547 """
1548 try:
1549 oNow = datetime.datetime.utcnow();
1550 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1551 sTsIso = formatIsoTimestamp(oNow);
1552 except:
1553 sTsPrf = sTsIso = 'getTimePrefix-exception';
1554 return (sTsPrf, sTsIso);
1555
1556class UtcTzInfo(datetime.tzinfo):
1557 """UTC TZ Info Class"""
1558 def utcoffset(self, _):
1559 return datetime.timedelta(0);
1560 def tzname(self, _):
1561 return "UTC";
1562 def dst(self, _):
1563 return datetime.timedelta(0);
1564
1565class GenTzInfo(datetime.tzinfo):
1566 """Generic TZ Info Class"""
1567 def __init__(self, offInMin):
1568 datetime.tzinfo.__init__(self);
1569 self.offInMin = offInMin;
1570 def utcoffset(self, _):
1571 return datetime.timedelta(minutes = self.offInMin);
1572 def tzname(self, _):
1573 if self.offInMin >= 0:
1574 return "+%02d%02d" % (self.offInMin // 60, self.offInMin % 60);
1575 return "-%02d%02d" % (-self.offInMin // 60, -self.offInMin % 60);
1576 def dst(self, _):
1577 return datetime.timedelta(0);
1578
1579def formatIsoTimestamp(oNow):
1580 """Formats the datetime object as an ISO timestamp."""
1581 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1582 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1583 return sTs;
1584
1585def getIsoTimestamp():
1586 """Returns the current UTC timestamp as a string."""
1587 return formatIsoTimestamp(datetime.datetime.utcnow());
1588
1589def formatShortIsoTimestamp(oNow):
1590 """Formats the datetime object as an ISO timestamp, but w/o microseconds."""
1591 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1592 return oNow.strftime('%Y-%m-%dT%H:%M:%SZ');
1593
1594def getShortIsoTimestamp():
1595 """Returns the current UTC timestamp as a string, but w/o microseconds."""
1596 return formatShortIsoTimestamp(datetime.datetime.utcnow());
1597
1598def convertDateTimeToZulu(oDateTime):
1599 """ Converts oDateTime to zulu time if it has timezone info. """
1600 if oDateTime.tzinfo is not None:
1601 oDateTime = oDateTime.astimezone(UtcTzInfo());
1602 else:
1603 oDateTime = oDateTime.replace(tzinfo = UtcTzInfo());
1604 return oDateTime;
1605
1606def parseIsoTimestamp(sTs):
1607 """
1608 Parses a typical ISO timestamp, returing a datetime object, reasonably
1609 forgiving, but will throw weird indexing/conversion errors if the input
1610 is malformed.
1611 """
1612 # YYYY-MM-DD
1613 iYear = int(sTs[0:4]);
1614 assert(sTs[4] == '-');
1615 iMonth = int(sTs[5:7]);
1616 assert(sTs[7] == '-');
1617 iDay = int(sTs[8:10]);
1618
1619 # Skip separator
1620 sTime = sTs[10:];
1621 while sTime[0] in 'Tt \t\n\r':
1622 sTime = sTime[1:];
1623
1624 # HH:MM[:SS]
1625 iHour = int(sTime[0:2]);
1626 assert(sTime[2] == ':');
1627 iMin = int(sTime[3:5]);
1628 if sTime[5] == ':':
1629 iSec = int(sTime[6:8]);
1630
1631 # Fraction?
1632 offTime = 8;
1633 iMicroseconds = 0;
1634 if offTime < len(sTime) and sTime[offTime] in '.,':
1635 offTime += 1;
1636 cchFraction = 0;
1637 while offTime + cchFraction < len(sTime) and sTime[offTime + cchFraction] in '0123456789':
1638 cchFraction += 1;
1639 if cchFraction > 0:
1640 iMicroseconds = int(sTime[offTime : (offTime + cchFraction)]);
1641 offTime += cchFraction;
1642 while cchFraction < 6:
1643 iMicroseconds *= 10;
1644 cchFraction += 1;
1645 while cchFraction > 6:
1646 iMicroseconds = iMicroseconds // 10;
1647 cchFraction -= 1;
1648
1649 else:
1650 iSec = 0;
1651 iMicroseconds = 0;
1652 offTime = 5;
1653
1654 # Naive?
1655 if offTime >= len(sTime):
1656 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1657
1658 # Zulu?
1659 if offTime >= len(sTime) or sTime[offTime] in 'Zz':
1660 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = UtcTzInfo());
1661
1662 # Some kind of offset afterwards, and strptime is useless. sigh.
1663 if sTime[offTime] in '+-':
1664 chSign = sTime[offTime];
1665 offTime += 1;
1666 cMinTz = int(sTime[offTime : (offTime + 2)]) * 60;
1667 offTime += 2;
1668 if offTime < len(sTime) and sTime[offTime] in ':':
1669 offTime += 1;
1670 if offTime + 2 <= len(sTime):
1671 cMinTz += int(sTime[offTime : (offTime + 2)]);
1672 offTime += 2;
1673 assert offTime == len(sTime);
1674 if chSign == '-':
1675 cMinTz = -cMinTz;
1676 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = GenTzInfo(cMinTz));
1677 assert False, sTs;
1678 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1679
1680def normalizeIsoTimestampToZulu(sTs):
1681 """
1682 Takes a iso timestamp string and normalizes it (basically parseIsoTimestamp
1683 + convertDateTimeToZulu + formatIsoTimestamp).
1684 Returns ISO tiemstamp string.
1685 """
1686 return formatIsoTimestamp(convertDateTimeToZulu(parseIsoTimestamp(sTs)));
1687
1688def getLocalHourOfWeek():
1689 """ Local hour of week (0 based). """
1690 oNow = datetime.datetime.now();
1691 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1692
1693
1694def formatIntervalSeconds(cSeconds):
1695 """ Format a seconds interval into a nice 01h 00m 22s string """
1696 # Two simple special cases.
1697 if cSeconds < 60:
1698 return '%ss' % (cSeconds,);
1699 if cSeconds < 3600:
1700 cMins = cSeconds // 60;
1701 cSecs = cSeconds % 60;
1702 if cSecs == 0:
1703 return '%sm' % (cMins,);
1704 return '%sm %ss' % (cMins, cSecs,);
1705
1706 # Generic and a bit slower.
1707 cDays = cSeconds // 86400;
1708 cSeconds %= 86400;
1709 cHours = cSeconds // 3600;
1710 cSeconds %= 3600;
1711 cMins = cSeconds // 60;
1712 cSecs = cSeconds % 60;
1713 sRet = '';
1714 if cDays > 0:
1715 sRet = '%sd ' % (cDays,);
1716 if cHours > 0:
1717 sRet += '%sh ' % (cHours,);
1718 if cMins > 0:
1719 sRet += '%sm ' % (cMins,);
1720 if cSecs > 0:
1721 sRet += '%ss ' % (cSecs,);
1722 assert sRet; assert sRet[-1] == ' ';
1723 return sRet[:-1];
1724
1725def formatIntervalSeconds2(oSeconds):
1726 """
1727 Flexible input version of formatIntervalSeconds for use in WUI forms where
1728 data is usually already string form.
1729 """
1730 if isinstance(oSeconds, (int, long)):
1731 return formatIntervalSeconds(oSeconds);
1732 if not isString(oSeconds):
1733 try:
1734 lSeconds = long(oSeconds);
1735 except:
1736 pass;
1737 else:
1738 if lSeconds >= 0:
1739 return formatIntervalSeconds2(lSeconds);
1740 return oSeconds;
1741
1742def parseIntervalSeconds(sString):
1743 """
1744 Reverse of formatIntervalSeconds.
1745
1746 Returns (cSeconds, sError), where sError is None on success.
1747 """
1748
1749 # We might given non-strings, just return them without any fuss.
1750 if not isString(sString):
1751 if isinstance(sString, (int, long)) or sString is None:
1752 return (sString, None);
1753 ## @todo time/date objects?
1754 return (int(sString), None);
1755
1756 # Strip it and make sure it's not empty.
1757 sString = sString.strip();
1758 if not sString:
1759 return (0, 'Empty interval string.');
1760
1761 #
1762 # Split up the input into a list of 'valueN, unitN, ...'.
1763 #
1764 # Don't want to spend too much time trying to make re.split do exactly what
1765 # I need here, so please forgive the extra pass I'm making here.
1766 #
1767 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1768 asParts = [];
1769 for sPart in asRawParts:
1770 sPart = sPart.strip();
1771 if sPart:
1772 asParts.append(sPart);
1773 if not asParts:
1774 return (0, 'Empty interval string or something?');
1775
1776 #
1777 # Process them one or two at the time.
1778 #
1779 cSeconds = 0;
1780 asErrors = [];
1781 i = 0;
1782 while i < len(asParts):
1783 sNumber = asParts[i];
1784 i += 1;
1785 if sNumber.isdigit():
1786 iNumber = int(sNumber);
1787
1788 sUnit = 's';
1789 if i < len(asParts) and not asParts[i].isdigit():
1790 sUnit = asParts[i];
1791 i += 1;
1792
1793 sUnitLower = sUnit.lower();
1794 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1795 pass;
1796 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1797 iNumber *= 60;
1798 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1799 iNumber *= 3600;
1800 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1801 iNumber *= 86400;
1802 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1803 iNumber *= 7 * 86400;
1804 else:
1805 asErrors.append('Unknown unit "%s".' % (sUnit,));
1806 cSeconds += iNumber;
1807 else:
1808 asErrors.append('Bad number "%s".' % (sNumber,));
1809 return (cSeconds, None if not asErrors else ' '.join(asErrors));
1810
1811def formatIntervalHours(cHours):
1812 """ Format a hours interval into a nice 1w 2d 1h string. """
1813 # Simple special cases.
1814 if cHours < 24:
1815 return '%sh' % (cHours,);
1816
1817 # Generic and a bit slower.
1818 cWeeks = cHours / (7 * 24);
1819 cHours %= 7 * 24;
1820 cDays = cHours / 24;
1821 cHours %= 24;
1822 sRet = '';
1823 if cWeeks > 0:
1824 sRet = '%sw ' % (cWeeks,);
1825 if cDays > 0:
1826 sRet = '%sd ' % (cDays,);
1827 if cHours > 0:
1828 sRet += '%sh ' % (cHours,);
1829 assert sRet; assert sRet[-1] == ' ';
1830 return sRet[:-1];
1831
1832def parseIntervalHours(sString):
1833 """
1834 Reverse of formatIntervalHours.
1835
1836 Returns (cHours, sError), where sError is None on success.
1837 """
1838
1839 # We might given non-strings, just return them without any fuss.
1840 if not isString(sString):
1841 if isinstance(sString, (int, long)) or sString is None:
1842 return (sString, None);
1843 ## @todo time/date objects?
1844 return (int(sString), None);
1845
1846 # Strip it and make sure it's not empty.
1847 sString = sString.strip();
1848 if not sString:
1849 return (0, 'Empty interval string.');
1850
1851 #
1852 # Split up the input into a list of 'valueN, unitN, ...'.
1853 #
1854 # Don't want to spend too much time trying to make re.split do exactly what
1855 # I need here, so please forgive the extra pass I'm making here.
1856 #
1857 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1858 asParts = [];
1859 for sPart in asRawParts:
1860 sPart = sPart.strip();
1861 if sPart:
1862 asParts.append(sPart);
1863 if not asParts:
1864 return (0, 'Empty interval string or something?');
1865
1866 #
1867 # Process them one or two at the time.
1868 #
1869 cHours = 0;
1870 asErrors = [];
1871 i = 0;
1872 while i < len(asParts):
1873 sNumber = asParts[i];
1874 i += 1;
1875 if sNumber.isdigit():
1876 iNumber = int(sNumber);
1877
1878 sUnit = 'h';
1879 if i < len(asParts) and not asParts[i].isdigit():
1880 sUnit = asParts[i];
1881 i += 1;
1882
1883 sUnitLower = sUnit.lower();
1884 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1885 pass;
1886 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1887 iNumber *= 24;
1888 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1889 iNumber *= 7 * 24;
1890 else:
1891 asErrors.append('Unknown unit "%s".' % (sUnit,));
1892 cHours += iNumber;
1893 else:
1894 asErrors.append('Bad number "%s".' % (sNumber,));
1895 return (cHours, None if not asErrors else ' '.join(asErrors));
1896
1897
1898#
1899# Introspection.
1900#
1901
1902def getCallerName(oFrame=None, iFrame=2):
1903 """
1904 Returns the name of the caller's caller.
1905 """
1906 if oFrame is None:
1907 try:
1908 raise Exception();
1909 except:
1910 oFrame = sys.exc_info()[2].tb_frame.f_back;
1911 while iFrame > 1:
1912 if oFrame is not None:
1913 oFrame = oFrame.f_back;
1914 iFrame = iFrame - 1;
1915 if oFrame is not None:
1916 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1917 return sName;
1918 return "unknown";
1919
1920
1921def getXcptInfo(cFrames = 1):
1922 """
1923 Gets text detailing the exception. (Good for logging.)
1924 Returns list of info strings.
1925 """
1926
1927 #
1928 # Try get exception info.
1929 #
1930 try:
1931 oType, oValue, oTraceback = sys.exc_info();
1932 except:
1933 oType = oValue = oTraceback = None;
1934 if oType is not None:
1935
1936 #
1937 # Try format the info
1938 #
1939 asRet = [];
1940 try:
1941 try:
1942 asRet = asRet + traceback.format_exception_only(oType, oValue);
1943 asTraceBack = traceback.format_tb(oTraceback);
1944 if cFrames is not None and cFrames <= 1:
1945 asRet.append(asTraceBack[-1]);
1946 else:
1947 asRet.append('Traceback:')
1948 for iFrame in range(min(cFrames, len(asTraceBack))):
1949 asRet.append(asTraceBack[-iFrame - 1]);
1950 asRet.append('Stack:')
1951 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1952 except:
1953 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
1954
1955 if not asRet:
1956 asRet.append('No exception info...');
1957 except:
1958 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
1959 else:
1960 asRet = ['Couldn\'t find exception traceback.'];
1961 return asRet;
1962
1963
1964def getObjectTypeName(oObject):
1965 """
1966 Get the type name of the given object.
1967 """
1968 if oObject is None:
1969 return 'None';
1970
1971 # Get the type object.
1972 try:
1973 oType = type(oObject);
1974 except:
1975 return 'type-throws-exception';
1976
1977 # Python 2.x only: Handle old-style object wrappers.
1978 if sys.version_info[0] < 3:
1979 try:
1980 from types import InstanceType; # pylint: disable=no-name-in-module
1981 if oType == InstanceType:
1982 oType = oObject.__class__;
1983 except:
1984 pass;
1985
1986 # Get the name.
1987 try:
1988 return oType.__name__;
1989 except:
1990 return '__type__-throws-exception';
1991
1992
1993def chmodPlusX(sFile):
1994 """
1995 Makes the specified file or directory executable.
1996 Returns success indicator, no exceptions.
1997
1998 Note! Symbolic links are followed and the target will be changed.
1999 """
2000 try:
2001 oStat = os.stat(sFile);
2002 except:
2003 return False;
2004 try:
2005 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
2006 except:
2007 return False;
2008 return True;
2009
2010
2011#
2012# TestSuite stuff.
2013#
2014
2015def isRunningFromCheckout(cScriptDepth = 1):
2016 """
2017 Checks if we're running from the SVN checkout or not.
2018 """
2019
2020 try:
2021 sFile = __file__;
2022 cScriptDepth = 1;
2023 except:
2024 sFile = sys.argv[0];
2025
2026 sDir = os.path.abspath(sFile);
2027 while cScriptDepth >= 0:
2028 sDir = os.path.dirname(sDir);
2029 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
2030 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
2031 return True;
2032 cScriptDepth -= 1;
2033
2034 return False;
2035
2036
2037#
2038# Bourne shell argument fun.
2039#
2040
2041
2042def argsSplit(sCmdLine):
2043 """
2044 Given a bourne shell command line invocation, split it up into arguments
2045 assuming IFS is space.
2046 Returns None on syntax error.
2047 """
2048 ## @todo bourne shell argument parsing!
2049 return sCmdLine.split(' ');
2050
2051def argsGetFirst(sCmdLine):
2052 """
2053 Given a bourne shell command line invocation, get return the first argument
2054 assuming IFS is space.
2055 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
2056 """
2057 asArgs = argsSplit(sCmdLine);
2058 if not asArgs:
2059 return None;
2060
2061 return asArgs[0];
2062
2063#
2064# String helpers.
2065#
2066
2067def stricmp(sFirst, sSecond):
2068 """
2069 Compares to strings in an case insensitive fashion.
2070
2071 Python doesn't seem to have any way of doing the correctly, so this is just
2072 an approximation using lower.
2073 """
2074 if sFirst == sSecond:
2075 return 0;
2076 sLower1 = sFirst.lower();
2077 sLower2 = sSecond.lower();
2078 if sLower1 == sLower2:
2079 return 0;
2080 if sLower1 < sLower2:
2081 return -1;
2082 return 1;
2083
2084
2085def versionCompare(sVer1, sVer2):
2086 """
2087 Compares to version strings in a fashion similar to RTStrVersionCompare.
2088 """
2089
2090 ## @todo implement me!!
2091
2092 if sVer1 == sVer2:
2093 return 0;
2094 if sVer1 < sVer2:
2095 return -1;
2096 return 1;
2097
2098
2099def formatNumber(lNum, sThousandSep = ' '):
2100 """
2101 Formats a decimal number with pretty separators.
2102 """
2103 sNum = str(lNum);
2104 sRet = sNum[-3:];
2105 off = len(sNum) - 3;
2106 while off > 0:
2107 off -= 3;
2108 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
2109 return sRet;
2110
2111
2112def formatNumberNbsp(lNum):
2113 """
2114 Formats a decimal number with pretty separators.
2115 """
2116 sRet = formatNumber(lNum);
2117 return unicode(sRet).replace(' ', u'\u00a0');
2118
2119
2120def isString(oString):
2121 """
2122 Checks if the object is a string object, hiding difference between python 2 and 3.
2123
2124 Returns True if it's a string of some kind.
2125 Returns False if not.
2126 """
2127 if sys.version_info[0] >= 3:
2128 return isinstance(oString, str);
2129 return isinstance(oString, basestring); # pylint: disable=undefined-variable
2130
2131
2132def hasNonAsciiCharacters(sText):
2133 """
2134 Returns True is specified string has non-ASCII characters, False if ASCII only.
2135 """
2136 if isString(sText):
2137 for ch in sText:
2138 if ord(ch) >= 128:
2139 return True;
2140 else:
2141 # Probably byte array or some such thing.
2142 for ch in sText:
2143 if ch >= 128 or ch < 0:
2144 return True;
2145 return False;
2146
2147
2148#
2149# Unpacking.
2150#
2151
2152def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2153 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2154 """
2155 Worker for unpackFile that deals with ZIP files, same function signature.
2156 """
2157 import zipfile
2158 if fnError is None:
2159 fnError = fnLog;
2160
2161 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
2162
2163 # Open it.
2164 try: oZipFile = zipfile.ZipFile(sArchive, 'r'); # pylint: disable=consider-using-with
2165 except Exception as oXcpt:
2166 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2167 return None;
2168
2169 # Extract all members.
2170 asMembers = [];
2171 try:
2172 for sMember in oZipFile.namelist():
2173 if fnFilter is None or fnFilter(sMember) is not False:
2174 if sMember.endswith('/'):
2175 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2)
2176 else:
2177 oZipFile.extract(sMember, sDstDir);
2178 asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
2179 except Exception as oXcpt:
2180 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2181 asMembers = None;
2182
2183 # close it.
2184 try: oZipFile.close();
2185 except Exception as oXcpt:
2186 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2187 asMembers = None;
2188
2189 return asMembers;
2190
2191
2192## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already.
2193g_fTarCopyFileObjOverriddend = False;
2194
2195def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError, bufsize = None):
2196 """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """
2197 _ = bufsize;
2198 if length is None:
2199 __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize);
2200 elif length > 0:
2201 cFull, cbRemainder = divmod(length, g_cbGoodBufferSize);
2202 for _ in xrange(cFull):
2203 abBuffer = src.read(g_cbGoodBufferSize);
2204 dst.write(abBuffer);
2205 if len(abBuffer) != g_cbGoodBufferSize:
2206 raise exception('unexpected end of source file');
2207 if cbRemainder > 0:
2208 abBuffer = src.read(cbRemainder);
2209 dst.write(abBuffer);
2210 if len(abBuffer) != cbRemainder:
2211 raise exception('unexpected end of source file');
2212
2213
2214def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2215 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2216 """
2217 Worker for unpackFile that deals with tarballs, same function signature.
2218 """
2219 import shutil;
2220 import tarfile;
2221 if fnError is None:
2222 fnError = fnLog;
2223
2224 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
2225
2226 #
2227 # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows.
2228 # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs.
2229 # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs.
2230 #
2231 if True is True: # pylint: disable=comparison-with-itself
2232 __installShUtilHacks(shutil);
2233 global g_fTarCopyFileObjOverriddend;
2234 if g_fTarCopyFileObjOverriddend is False:
2235 g_fTarCopyFileObjOverriddend = True;
2236 #if sys.hexversion < 0x03060000:
2237 tarfile.copyfileobj = __mytarfilecopyfileobj;
2238
2239 #
2240 # Open it.
2241 #
2242 # Note! We not using 'r:*' because we cannot allow seeking compressed files!
2243 # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb).
2244 #
2245 try:
2246 if sys.hexversion >= 0x03060000:
2247 oTarFile = tarfile.open(sArchive, 'r|*', # pylint: disable=consider-using-with
2248 bufsize = g_cbGoodBufferSize, copybufsize = g_cbGoodBufferSize);
2249 else:
2250 oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize); # pylint: disable=consider-using-with
2251 except Exception as oXcpt:
2252 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2253 return None;
2254
2255 # Extract all members.
2256 asMembers = [];
2257 try:
2258 for oTarInfo in oTarFile:
2259 try:
2260 if fnFilter is None or fnFilter(oTarInfo.name) is not False:
2261 if oTarInfo.islnk():
2262 # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking
2263 # in the compressed tar stream. So, fall back on shutil.copy2 instead.
2264 sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep));
2265 sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep));
2266 sParentDir = os.path.dirname(sLinkFile);
2267 try: os.unlink(sLinkFile);
2268 except: pass;
2269 if sParentDir and not os.path.exists(sParentDir):
2270 os.makedirs(sParentDir);
2271 try: os.link(sLinkTarget, sLinkFile);
2272 except: shutil.copy2(sLinkTarget, sLinkFile);
2273 else:
2274 if oTarInfo.isdir():
2275 # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right.
2276 oTarInfo.mode |= 0x1c0; # (octal: 0700)
2277 oTarFile.extract(oTarInfo, sDstDir);
2278 asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep)));
2279 except Exception as oXcpt:
2280 fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt));
2281 for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]:
2282 fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),));
2283 for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]:
2284 fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),));
2285 asMembers = None;
2286 break;
2287 except Exception as oXcpt:
2288 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2289 asMembers = None;
2290
2291 #
2292 # Finally, close it.
2293 #
2294 try: oTarFile.close();
2295 except Exception as oXcpt:
2296 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2297 asMembers = None;
2298
2299 return asMembers;
2300
2301
2302def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2303 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2304 """
2305 Unpacks the given file if it has a know archive extension, otherwise do
2306 nothing.
2307
2308 fnLog & fnError both take a string parameter.
2309
2310 fnFilter takes a member name (string) and returns True if it's included
2311 and False if excluded.
2312
2313 Returns list of the extracted files (full path) on success.
2314 Returns empty list if not a supported archive format.
2315 Returns None on failure. Raises no exceptions.
2316 """
2317 sBaseNameLower = os.path.basename(sArchive).lower();
2318
2319 #
2320 # Zip file?
2321 #
2322 if sBaseNameLower.endswith('.zip'):
2323 return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2324
2325 #
2326 # Tarball?
2327 #
2328 if sBaseNameLower.endswith('.tar') \
2329 or sBaseNameLower.endswith('.tar.gz') \
2330 or sBaseNameLower.endswith('.tgz') \
2331 or sBaseNameLower.endswith('.tar.bz2'):
2332 return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2333
2334 #
2335 # Cannot classify it from the name, so just return that to the caller.
2336 #
2337 fnLog('Not unpacking "%s".' % (sArchive,));
2338 return [];
2339
2340
2341#
2342# Misc.
2343#
2344def areBytesEqual(oLeft, oRight):
2345 """
2346 Compares two byte arrays, strings or whatnot.
2347
2348 returns true / false accordingly.
2349 """
2350
2351 # If both are None, consider them equal (bogus?):
2352 if oLeft is None and oRight is None:
2353 return True;
2354
2355 # If just one is None, they can't match:
2356 if oLeft is None or oRight is None:
2357 return False;
2358
2359 # If both have the same type, use the compare operator of the class:
2360 if type(oLeft) is type(oRight):
2361 #print('same type: %s' % (oLeft == oRight,));
2362 return oLeft == oRight;
2363
2364 # On the offchance that they're both strings, but of different types.
2365 if isString(oLeft) and isString(oRight):
2366 #print('string compare: %s' % (oLeft == oRight,));
2367 return oLeft == oRight;
2368
2369 #
2370 # See if byte/buffer stuff that can be compared directory. If not convert
2371 # strings to bytes.
2372 #
2373 # Note! For 2.x, we must convert both sides to the buffer type or the
2374 # comparison may fail despite it working okay in test cases.
2375 #
2376 if sys.version_info[0] >= 3:
2377 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2378 return oLeft == oRight;
2379
2380 if isString(oLeft):
2381 try: oLeft = bytes(oLeft, 'utf-8');
2382 except: pass;
2383 if isString(oRight):
2384 try: oRight = bytes(oRight, 'utf-8');
2385 except: pass;
2386 else:
2387 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2388 if isinstance(oLeft, bytearray):
2389 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2390 else:
2391 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2392 #print('buf/byte #1 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2393 return oLeft == oRight;
2394
2395 if isString(oLeft):
2396 try: oLeft = bytearray(oLeft, 'utf-8'); # pylint: disable=redefined-variable-type
2397 except: pass;
2398 if isString(oRight):
2399 try: oRight = bytearray(oRight, 'utf-8'); # pylint: disable=redefined-variable-type
2400 except: pass;
2401
2402 # Check if we now have the same type for both:
2403 if type(oLeft) is type(oRight):
2404 #print('same type now: %s' % (oLeft == oRight,));
2405 return oLeft == oRight;
2406
2407 # Check if we now have buffer/memoryview vs bytes/bytesarray again.
2408 if sys.version_info[0] >= 3:
2409 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2410 return oLeft == oRight;
2411 else:
2412 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2413 if isinstance(oLeft, bytearray):
2414 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2415 else:
2416 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2417 #print('buf/byte #2 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2418 return oLeft == oRight;
2419
2420 # Do item by item comparison:
2421 if len(oLeft) != len(oRight):
2422 #print('different length: %s vs %s' % (len(oLeft), len(oRight)));
2423 return False;
2424 i = len(oLeft);
2425 while i > 0:
2426 i = i - 1;
2427
2428 iElmLeft = oLeft[i];
2429 if not isinstance(iElmLeft, int) and not isinstance(iElmLeft, long):
2430 iElmLeft = ord(iElmLeft);
2431
2432 iElmRight = oRight[i];
2433 if not isinstance(iElmRight, int) and not isinstance(iElmRight, long):
2434 iElmRight = ord(iElmRight);
2435
2436 if iElmLeft != iElmRight:
2437 #print('element %d differs: %x %x' % (i, iElmLeft, iElmRight,));
2438 return False;
2439 return True;
2440
2441
2442def calcCrc32OfFile(sFile):
2443 """
2444 Simple helper for calculating the CRC32 of a file.
2445
2446 Throws stuff if the file cannot be opened or read successfully.
2447 """
2448 import zlib;
2449
2450 uCrc32 = 0;
2451 with open(sFile, 'rb') as oFile:
2452 while True:
2453 oBuf = oFile.read(1024 * 1024);
2454 if not oBuf:
2455 break
2456 uCrc32 = zlib.crc32(oBuf, uCrc32);
2457
2458 return uCrc32 % 2**32;
2459
2460
2461#
2462# Unit testing.
2463#
2464
2465# pylint: disable=missing-docstring
2466# pylint: disable=undefined-variable
2467class BuildCategoryDataTestCase(unittest.TestCase):
2468 def testIntervalSeconds(self):
2469 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
2470 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
2471 self.assertEqual(parseIntervalSeconds('123'), (123, None));
2472 self.assertEqual(parseIntervalSeconds(123), (123, None));
2473 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
2474 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
2475 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
2476 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
2477 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
2478 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
2479 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
2480
2481 def testZuluNormalization(self):
2482 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:34:25.000000000Z'), '2011-01-02T03:34:25.000000000Z');
2483 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25-0030'), '2011-01-02T03:34:25.000000000Z');
2484 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25+0030'), '2011-01-02T02:34:25.000000000Z');
2485 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863+01:00'), '2020-03-20T19:47:39.832312000Z');
2486 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863-02:00'), '2020-03-20T22:47:39.832312000Z');
2487
2488 def testHasNonAsciiChars(self):
2489 self.assertEqual(hasNonAsciiCharacters(''), False);
2490 self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False);
2491 self.assertEqual(hasNonAsciiCharacters('\x80 '), True);
2492 self.assertEqual(hasNonAsciiCharacters('\x79 '), False);
2493 self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False);
2494 self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False);
2495 self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True);
2496 self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True);
2497 self.assertEqual(hasNonAsciiCharacters(b'\x20\x20\x20'), False);
2498 self.assertEqual(hasNonAsciiCharacters(b'\x20\x81\x20'), True);
2499
2500 def testAreBytesEqual(self):
2501 self.assertEqual(areBytesEqual(None, None), True);
2502 self.assertEqual(areBytesEqual(None, ''), False);
2503 self.assertEqual(areBytesEqual('', ''), True);
2504 self.assertEqual(areBytesEqual('1', '1'), True);
2505 self.assertEqual(areBytesEqual('12345', '1234'), False);
2506 self.assertEqual(areBytesEqual('1234', '1234'), True);
2507 self.assertEqual(areBytesEqual('1234', b'1234'), True);
2508 self.assertEqual(areBytesEqual(b'1234', b'1234'), True);
2509 self.assertEqual(areBytesEqual(b'1234', '1234'), True);
2510 self.assertEqual(areBytesEqual(b'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2511 self.assertEqual(areBytesEqual('1234', bytearray([0x31,0x32,0x33,0x34])), True);
2512 self.assertEqual(areBytesEqual(u'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2513 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x33,0x34])), True);
2514 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), '1224'), False);
2515 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x32,0x34])), False);
2516 if sys.version_info[0] >= 3:
2517 pass;
2518 else:
2519 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2520 bytearray([0x31,0x32,0x33,0x34])), True);
2521 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2522 bytearray([0x99,0x32,0x32,0x34])), False);
2523 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2524 buffer(bytearray([0x31,0x32,0x33,0x34,0x34]), 0, 4)), True);
2525 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2526 buffer(bytearray([0x99,0x32,0x33,0x34,0x34]), 0, 4)), False);
2527 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), b'1234'), True);
2528 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), '1234'), True);
2529 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), u'1234'), True);
2530
2531if __name__ == '__main__':
2532 unittest.main();
2533 # not reached.
2534
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette