VirtualBox

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

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

Audio/Validation Kit: More code to handle host process execution in a separate thread to not block required event processing [build fix, forgot to commit stuff]. bugref:10008

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

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