VirtualBox

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

Last change on this file since 97544 was 96987, checked in by vboxsync, 2 years ago

ValKit/utils.py: Make pylint happy by documenting the newly introduced function.

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