VirtualBox

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

Last change on this file since 96750 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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