VirtualBox

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

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

Validation Kit: Use environ.get() instead of getDirEnv() in utils.processCollectCrashInfo().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 88.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 97670 2022-11-23 14:33:46Z 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: 97670 $"
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 elif sOs == 'solaris':
1434 asDmpDirs = [];
1435 try:
1436 sScratchPath = environ.get('TESTBOX_PATH_SCRATCH', None);
1437 asDmpDirs.extend([ sScratchPath ]);
1438 except:
1439 pass;
1440 # Some other useful locations as fallback.
1441 asDmpDirs.extend([
1442 u'/var/cores/',
1443 u'/var/core/',
1444 u'/var/tmp/',
1445 ]);
1446 #
1447 # Solaris by default creates a core file in the directory of the crashing process with the name 'core'.
1448 #
1449 # As we need to distinguish the core files correlating to their PIDs and have a persistent storage location,
1450 # the host needs to be tweaked via:
1451 #
1452 # ```coreadm -g /path/to/cores/core.%f.%p```
1453 #
1454 sMatchPrefix = 'core';
1455 sMatchSuffix = '.%u' % (uPid,);
1456 for sDir in asDmpDirs:
1457 sDir = os.path.expandvars(sDir);
1458 if not os.path.isdir(sDir):
1459 continue;
1460 try:
1461 asDirEntries = os.listdir(sDir);
1462 except:
1463 continue;
1464 for sEntry in asDirEntries:
1465 if sEntry.startswith(sMatchPrefix) \
1466 and sEntry.endswith(sMatchSuffix):
1467 sFull = os.path.join(sDir, sEntry);
1468 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1469 fnCrashFile(sFull, True);
1470 else:
1471 pass; ## TODO
1472 return None;
1473
1474
1475#
1476# Time.
1477#
1478
1479#
1480# The following test case shows how time.time() only have ~ms resolution
1481# on Windows (tested W10) and why it therefore makes sense to try use
1482# performance counters.
1483#
1484# Note! We cannot use time.clock() as the timestamp must be portable across
1485# processes. See timeout testcase problem on win hosts (no logs).
1486# Also, time.clock() was axed in python 3.8 (https://bugs.python.org/issue31803).
1487#
1488#import sys;
1489#import time;
1490#from common import utils;
1491#
1492#atSeries = [];
1493#for i in xrange(1,160):
1494# if i == 159: time.sleep(10);
1495# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
1496#
1497#tPrev = atSeries[0]
1498#for tCur in atSeries:
1499# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
1500# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
1501# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
1502# print '';
1503# tPrev = tCur
1504#
1505#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
1506#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
1507#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
1508
1509g_fWinUseWinPerfCounter = sys.platform == 'win32';
1510g_fpWinPerfCounterFreq = None;
1511g_oFuncwinQueryPerformanceCounter = None;
1512
1513def _winInitPerfCounter():
1514 """ Initializes the use of performance counters. """
1515 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
1516
1517 uFrequency = ctypes.c_ulonglong(0);
1518 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
1519 if uFrequency.value >= 1000:
1520 #print 'uFrequency = %s' % (uFrequency,);
1521 #print 'type(uFrequency) = %s' % (type(uFrequency),);
1522 g_fpWinPerfCounterFreq = float(uFrequency.value);
1523
1524 # Check that querying the counter works too.
1525 global g_oFuncwinQueryPerformanceCounter
1526 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
1527 uCurValue = ctypes.c_ulonglong(0);
1528 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1529 if uCurValue.value > 0:
1530 return True;
1531 g_fWinUseWinPerfCounter = False;
1532 return False;
1533
1534def _winFloatTime():
1535 """ Gets floating point time on windows. """
1536 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
1537 uCurValue = ctypes.c_ulonglong(0);
1538 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1539 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
1540 return time.time();
1541
1542def timestampNano():
1543 """
1544 Gets a nanosecond timestamp.
1545 """
1546 if g_fWinUseWinPerfCounter is True:
1547 return long(_winFloatTime() * 1000000000);
1548 return long(time.time() * 1000000000);
1549
1550def timestampMilli():
1551 """
1552 Gets a millisecond timestamp.
1553 """
1554 if g_fWinUseWinPerfCounter is True:
1555 return long(_winFloatTime() * 1000);
1556 return long(time.time() * 1000);
1557
1558def timestampSecond():
1559 """
1560 Gets a second timestamp.
1561 """
1562 if g_fWinUseWinPerfCounter is True:
1563 return long(_winFloatTime());
1564 return long(time.time());
1565
1566def secondsSinceUnixEpoch():
1567 """
1568 Returns unix time, floating point second count since 1970-01-01T00:00:00Z
1569 """
1570 ## ASSUMES This returns unix epoch time on all systems we care about...
1571 return time.time();
1572
1573def getTimePrefix():
1574 """
1575 Returns a timestamp prefix, typically used for logging. UTC.
1576 """
1577 try:
1578 oNow = datetime.datetime.utcnow();
1579 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1580 except:
1581 sTs = 'getTimePrefix-exception';
1582 return sTs;
1583
1584def getTimePrefixAndIsoTimestamp():
1585 """
1586 Returns current UTC as log prefix and iso timestamp.
1587 """
1588 try:
1589 oNow = datetime.datetime.utcnow();
1590 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1591 sTsIso = formatIsoTimestamp(oNow);
1592 except:
1593 sTsPrf = sTsIso = 'getTimePrefix-exception';
1594 return (sTsPrf, sTsIso);
1595
1596class UtcTzInfo(datetime.tzinfo):
1597 """UTC TZ Info Class"""
1598 def utcoffset(self, _):
1599 return datetime.timedelta(0);
1600 def tzname(self, _):
1601 return "UTC";
1602 def dst(self, _):
1603 return datetime.timedelta(0);
1604
1605class GenTzInfo(datetime.tzinfo):
1606 """Generic TZ Info Class"""
1607 def __init__(self, offInMin):
1608 datetime.tzinfo.__init__(self);
1609 self.offInMin = offInMin;
1610 def utcoffset(self, _):
1611 return datetime.timedelta(minutes = self.offInMin);
1612 def tzname(self, _):
1613 if self.offInMin >= 0:
1614 return "+%02d%02d" % (self.offInMin // 60, self.offInMin % 60);
1615 return "-%02d%02d" % (-self.offInMin // 60, -self.offInMin % 60);
1616 def dst(self, _):
1617 return datetime.timedelta(0);
1618
1619def formatIsoTimestamp(oNow):
1620 """Formats the datetime object as an ISO timestamp."""
1621 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1622 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1623 return sTs;
1624
1625def getIsoTimestamp():
1626 """Returns the current UTC timestamp as a string."""
1627 return formatIsoTimestamp(datetime.datetime.utcnow());
1628
1629def formatShortIsoTimestamp(oNow):
1630 """Formats the datetime object as an ISO timestamp, but w/o microseconds."""
1631 assert oNow.tzinfo is None or isinstance(oNow.tzinfo, UtcTzInfo);
1632 return oNow.strftime('%Y-%m-%dT%H:%M:%SZ');
1633
1634def getShortIsoTimestamp():
1635 """Returns the current UTC timestamp as a string, but w/o microseconds."""
1636 return formatShortIsoTimestamp(datetime.datetime.utcnow());
1637
1638def convertDateTimeToZulu(oDateTime):
1639 """ Converts oDateTime to zulu time if it has timezone info. """
1640 if oDateTime.tzinfo is not None:
1641 oDateTime = oDateTime.astimezone(UtcTzInfo());
1642 else:
1643 oDateTime = oDateTime.replace(tzinfo = UtcTzInfo());
1644 return oDateTime;
1645
1646def parseIsoTimestamp(sTs):
1647 """
1648 Parses a typical ISO timestamp, returing a datetime object, reasonably
1649 forgiving, but will throw weird indexing/conversion errors if the input
1650 is malformed.
1651 """
1652 # YYYY-MM-DD
1653 iYear = int(sTs[0:4]);
1654 assert(sTs[4] == '-');
1655 iMonth = int(sTs[5:7]);
1656 assert(sTs[7] == '-');
1657 iDay = int(sTs[8:10]);
1658
1659 # Skip separator
1660 sTime = sTs[10:];
1661 while sTime[0] in 'Tt \t\n\r':
1662 sTime = sTime[1:];
1663
1664 # HH:MM[:SS]
1665 iHour = int(sTime[0:2]);
1666 assert(sTime[2] == ':');
1667 iMin = int(sTime[3:5]);
1668 if sTime[5] == ':':
1669 iSec = int(sTime[6:8]);
1670
1671 # Fraction?
1672 offTime = 8;
1673 iMicroseconds = 0;
1674 if offTime < len(sTime) and sTime[offTime] in '.,':
1675 offTime += 1;
1676 cchFraction = 0;
1677 while offTime + cchFraction < len(sTime) and sTime[offTime + cchFraction] in '0123456789':
1678 cchFraction += 1;
1679 if cchFraction > 0:
1680 iMicroseconds = int(sTime[offTime : (offTime + cchFraction)]);
1681 offTime += cchFraction;
1682 while cchFraction < 6:
1683 iMicroseconds *= 10;
1684 cchFraction += 1;
1685 while cchFraction > 6:
1686 iMicroseconds = iMicroseconds // 10;
1687 cchFraction -= 1;
1688
1689 else:
1690 iSec = 0;
1691 iMicroseconds = 0;
1692 offTime = 5;
1693
1694 # Naive?
1695 if offTime >= len(sTime):
1696 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1697
1698 # Zulu?
1699 if offTime >= len(sTime) or sTime[offTime] in 'Zz':
1700 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = UtcTzInfo());
1701
1702 # Some kind of offset afterwards, and strptime is useless. sigh.
1703 if sTime[offTime] in '+-':
1704 chSign = sTime[offTime];
1705 offTime += 1;
1706 cMinTz = int(sTime[offTime : (offTime + 2)]) * 60;
1707 offTime += 2;
1708 if offTime < len(sTime) and sTime[offTime] in ':':
1709 offTime += 1;
1710 if offTime + 2 <= len(sTime):
1711 cMinTz += int(sTime[offTime : (offTime + 2)]);
1712 offTime += 2;
1713 assert offTime == len(sTime);
1714 if chSign == '-':
1715 cMinTz = -cMinTz;
1716 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds, tzinfo = GenTzInfo(cMinTz));
1717 assert False, sTs;
1718 return datetime.datetime(iYear, iMonth, iDay, iHour, iMin, iSec, iMicroseconds);
1719
1720def normalizeIsoTimestampToZulu(sTs):
1721 """
1722 Takes a iso timestamp string and normalizes it (basically parseIsoTimestamp
1723 + convertDateTimeToZulu + formatIsoTimestamp).
1724 Returns ISO tiemstamp string.
1725 """
1726 return formatIsoTimestamp(convertDateTimeToZulu(parseIsoTimestamp(sTs)));
1727
1728def getLocalHourOfWeek():
1729 """ Local hour of week (0 based). """
1730 oNow = datetime.datetime.now();
1731 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1732
1733
1734def formatIntervalSeconds(cSeconds):
1735 """ Format a seconds interval into a nice 01h 00m 22s string """
1736 # Two simple special cases.
1737 if cSeconds < 60:
1738 return '%ss' % (cSeconds,);
1739 if cSeconds < 3600:
1740 cMins = cSeconds // 60;
1741 cSecs = cSeconds % 60;
1742 if cSecs == 0:
1743 return '%sm' % (cMins,);
1744 return '%sm %ss' % (cMins, cSecs,);
1745
1746 # Generic and a bit slower.
1747 cDays = cSeconds // 86400;
1748 cSeconds %= 86400;
1749 cHours = cSeconds // 3600;
1750 cSeconds %= 3600;
1751 cMins = cSeconds // 60;
1752 cSecs = cSeconds % 60;
1753 sRet = '';
1754 if cDays > 0:
1755 sRet = '%sd ' % (cDays,);
1756 if cHours > 0:
1757 sRet += '%sh ' % (cHours,);
1758 if cMins > 0:
1759 sRet += '%sm ' % (cMins,);
1760 if cSecs > 0:
1761 sRet += '%ss ' % (cSecs,);
1762 assert sRet; assert sRet[-1] == ' ';
1763 return sRet[:-1];
1764
1765def formatIntervalSeconds2(oSeconds):
1766 """
1767 Flexible input version of formatIntervalSeconds for use in WUI forms where
1768 data is usually already string form.
1769 """
1770 if isinstance(oSeconds, (int, long)):
1771 return formatIntervalSeconds(oSeconds);
1772 if not isString(oSeconds):
1773 try:
1774 lSeconds = long(oSeconds);
1775 except:
1776 pass;
1777 else:
1778 if lSeconds >= 0:
1779 return formatIntervalSeconds2(lSeconds);
1780 return oSeconds;
1781
1782def parseIntervalSeconds(sString):
1783 """
1784 Reverse of formatIntervalSeconds.
1785
1786 Returns (cSeconds, sError), where sError is None on success.
1787 """
1788
1789 # We might given non-strings, just return them without any fuss.
1790 if not isString(sString):
1791 if isinstance(sString, (int, long)) or sString is None:
1792 return (sString, None);
1793 ## @todo time/date objects?
1794 return (int(sString), None);
1795
1796 # Strip it and make sure it's not empty.
1797 sString = sString.strip();
1798 if not sString:
1799 return (0, 'Empty interval string.');
1800
1801 #
1802 # Split up the input into a list of 'valueN, unitN, ...'.
1803 #
1804 # Don't want to spend too much time trying to make re.split do exactly what
1805 # I need here, so please forgive the extra pass I'm making here.
1806 #
1807 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1808 asParts = [];
1809 for sPart in asRawParts:
1810 sPart = sPart.strip();
1811 if sPart:
1812 asParts.append(sPart);
1813 if not asParts:
1814 return (0, 'Empty interval string or something?');
1815
1816 #
1817 # Process them one or two at the time.
1818 #
1819 cSeconds = 0;
1820 asErrors = [];
1821 i = 0;
1822 while i < len(asParts):
1823 sNumber = asParts[i];
1824 i += 1;
1825 if sNumber.isdigit():
1826 iNumber = int(sNumber);
1827
1828 sUnit = 's';
1829 if i < len(asParts) and not asParts[i].isdigit():
1830 sUnit = asParts[i];
1831 i += 1;
1832
1833 sUnitLower = sUnit.lower();
1834 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1835 pass;
1836 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1837 iNumber *= 60;
1838 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1839 iNumber *= 3600;
1840 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1841 iNumber *= 86400;
1842 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1843 iNumber *= 7 * 86400;
1844 else:
1845 asErrors.append('Unknown unit "%s".' % (sUnit,));
1846 cSeconds += iNumber;
1847 else:
1848 asErrors.append('Bad number "%s".' % (sNumber,));
1849 return (cSeconds, None if not asErrors else ' '.join(asErrors));
1850
1851def formatIntervalHours(cHours):
1852 """ Format a hours interval into a nice 1w 2d 1h string. """
1853 # Simple special cases.
1854 if cHours < 24:
1855 return '%sh' % (cHours,);
1856
1857 # Generic and a bit slower.
1858 cWeeks = cHours / (7 * 24);
1859 cHours %= 7 * 24;
1860 cDays = cHours / 24;
1861 cHours %= 24;
1862 sRet = '';
1863 if cWeeks > 0:
1864 sRet = '%sw ' % (cWeeks,);
1865 if cDays > 0:
1866 sRet = '%sd ' % (cDays,);
1867 if cHours > 0:
1868 sRet += '%sh ' % (cHours,);
1869 assert sRet; assert sRet[-1] == ' ';
1870 return sRet[:-1];
1871
1872def parseIntervalHours(sString):
1873 """
1874 Reverse of formatIntervalHours.
1875
1876 Returns (cHours, sError), where sError is None on success.
1877 """
1878
1879 # We might given non-strings, just return them without any fuss.
1880 if not isString(sString):
1881 if isinstance(sString, (int, long)) or sString is None:
1882 return (sString, None);
1883 ## @todo time/date objects?
1884 return (int(sString), None);
1885
1886 # Strip it and make sure it's not empty.
1887 sString = sString.strip();
1888 if not sString:
1889 return (0, 'Empty interval string.');
1890
1891 #
1892 # Split up the input into a list of 'valueN, unitN, ...'.
1893 #
1894 # Don't want to spend too much time trying to make re.split do exactly what
1895 # I need here, so please forgive the extra pass I'm making here.
1896 #
1897 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1898 asParts = [];
1899 for sPart in asRawParts:
1900 sPart = sPart.strip();
1901 if sPart:
1902 asParts.append(sPart);
1903 if not asParts:
1904 return (0, 'Empty interval string or something?');
1905
1906 #
1907 # Process them one or two at the time.
1908 #
1909 cHours = 0;
1910 asErrors = [];
1911 i = 0;
1912 while i < len(asParts):
1913 sNumber = asParts[i];
1914 i += 1;
1915 if sNumber.isdigit():
1916 iNumber = int(sNumber);
1917
1918 sUnit = 'h';
1919 if i < len(asParts) and not asParts[i].isdigit():
1920 sUnit = asParts[i];
1921 i += 1;
1922
1923 sUnitLower = sUnit.lower();
1924 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1925 pass;
1926 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1927 iNumber *= 24;
1928 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1929 iNumber *= 7 * 24;
1930 else:
1931 asErrors.append('Unknown unit "%s".' % (sUnit,));
1932 cHours += iNumber;
1933 else:
1934 asErrors.append('Bad number "%s".' % (sNumber,));
1935 return (cHours, None if not asErrors else ' '.join(asErrors));
1936
1937
1938#
1939# Introspection.
1940#
1941
1942def getCallerName(oFrame=None, iFrame=2):
1943 """
1944 Returns the name of the caller's caller.
1945 """
1946 if oFrame is None:
1947 try:
1948 raise Exception();
1949 except:
1950 oFrame = sys.exc_info()[2].tb_frame.f_back;
1951 while iFrame > 1:
1952 if oFrame is not None:
1953 oFrame = oFrame.f_back;
1954 iFrame = iFrame - 1;
1955 if oFrame is not None:
1956 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1957 return sName;
1958 return "unknown";
1959
1960
1961def getXcptInfo(cFrames = 1):
1962 """
1963 Gets text detailing the exception. (Good for logging.)
1964 Returns list of info strings.
1965 """
1966
1967 #
1968 # Try get exception info.
1969 #
1970 try:
1971 oType, oValue, oTraceback = sys.exc_info();
1972 except:
1973 oType = oValue = oTraceback = None;
1974 if oType is not None:
1975
1976 #
1977 # Try format the info
1978 #
1979 asRet = [];
1980 try:
1981 try:
1982 asRet = asRet + traceback.format_exception_only(oType, oValue);
1983 asTraceBack = traceback.format_tb(oTraceback);
1984 if cFrames is not None and cFrames <= 1:
1985 asRet.append(asTraceBack[-1]);
1986 else:
1987 asRet.append('Traceback:')
1988 for iFrame in range(min(cFrames, len(asTraceBack))):
1989 asRet.append(asTraceBack[-iFrame - 1]);
1990 asRet.append('Stack:')
1991 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1992 except:
1993 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
1994
1995 if not asRet:
1996 asRet.append('No exception info...');
1997 except:
1998 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
1999 else:
2000 asRet = ['Couldn\'t find exception traceback.'];
2001 return asRet;
2002
2003
2004def getObjectTypeName(oObject):
2005 """
2006 Get the type name of the given object.
2007 """
2008 if oObject is None:
2009 return 'None';
2010
2011 # Get the type object.
2012 try:
2013 oType = type(oObject);
2014 except:
2015 return 'type-throws-exception';
2016
2017 # Python 2.x only: Handle old-style object wrappers.
2018 if sys.version_info[0] < 3:
2019 try:
2020 from types import InstanceType; # pylint: disable=no-name-in-module
2021 if oType == InstanceType:
2022 oType = oObject.__class__;
2023 except:
2024 pass;
2025
2026 # Get the name.
2027 try:
2028 return oType.__name__;
2029 except:
2030 return '__type__-throws-exception';
2031
2032
2033def chmodPlusX(sFile):
2034 """
2035 Makes the specified file or directory executable.
2036 Returns success indicator, no exceptions.
2037
2038 Note! Symbolic links are followed and the target will be changed.
2039 """
2040 try:
2041 oStat = os.stat(sFile);
2042 except:
2043 return False;
2044 try:
2045 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
2046 except:
2047 return False;
2048 return True;
2049
2050
2051#
2052# TestSuite stuff.
2053#
2054
2055def isRunningFromCheckout(cScriptDepth = 1):
2056 """
2057 Checks if we're running from the SVN checkout or not.
2058 """
2059
2060 try:
2061 sFile = __file__;
2062 cScriptDepth = 1;
2063 except:
2064 sFile = sys.argv[0];
2065
2066 sDir = os.path.abspath(sFile);
2067 while cScriptDepth >= 0:
2068 sDir = os.path.dirname(sDir);
2069 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
2070 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
2071 return True;
2072 cScriptDepth -= 1;
2073
2074 return False;
2075
2076
2077#
2078# Bourne shell argument fun.
2079#
2080
2081
2082def argsSplit(sCmdLine):
2083 """
2084 Given a bourne shell command line invocation, split it up into arguments
2085 assuming IFS is space.
2086 Returns None on syntax error.
2087 """
2088 ## @todo bourne shell argument parsing!
2089 return sCmdLine.split(' ');
2090
2091def argsGetFirst(sCmdLine):
2092 """
2093 Given a bourne shell command line invocation, get return the first argument
2094 assuming IFS is space.
2095 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
2096 """
2097 asArgs = argsSplit(sCmdLine);
2098 if not asArgs:
2099 return None;
2100
2101 return asArgs[0];
2102
2103#
2104# String helpers.
2105#
2106
2107def stricmp(sFirst, sSecond):
2108 """
2109 Compares to strings in an case insensitive fashion.
2110
2111 Python doesn't seem to have any way of doing the correctly, so this is just
2112 an approximation using lower.
2113 """
2114 if sFirst == sSecond:
2115 return 0;
2116 sLower1 = sFirst.lower();
2117 sLower2 = sSecond.lower();
2118 if sLower1 == sLower2:
2119 return 0;
2120 if sLower1 < sLower2:
2121 return -1;
2122 return 1;
2123
2124
2125def versionCompare(sVer1, sVer2):
2126 """
2127 Compares to version strings in a fashion similar to RTStrVersionCompare.
2128 """
2129
2130 ## @todo implement me!!
2131
2132 if sVer1 == sVer2:
2133 return 0;
2134 if sVer1 < sVer2:
2135 return -1;
2136 return 1;
2137
2138
2139def formatNumber(lNum, sThousandSep = ' '):
2140 """
2141 Formats a decimal number with pretty separators.
2142 """
2143 sNum = str(lNum);
2144 sRet = sNum[-3:];
2145 off = len(sNum) - 3;
2146 while off > 0:
2147 off -= 3;
2148 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
2149 return sRet;
2150
2151
2152def formatNumberNbsp(lNum):
2153 """
2154 Formats a decimal number with pretty separators.
2155 """
2156 sRet = formatNumber(lNum);
2157 return unicode(sRet).replace(' ', u'\u00a0');
2158
2159
2160def isString(oString):
2161 """
2162 Checks if the object is a string object, hiding difference between python 2 and 3.
2163
2164 Returns True if it's a string of some kind.
2165 Returns False if not.
2166 """
2167 if sys.version_info[0] >= 3:
2168 return isinstance(oString, str);
2169 return isinstance(oString, basestring); # pylint: disable=undefined-variable
2170
2171
2172def hasNonAsciiCharacters(sText):
2173 """
2174 Returns True is specified string has non-ASCII characters, False if ASCII only.
2175 """
2176 if isString(sText):
2177 for ch in sText:
2178 if ord(ch) >= 128:
2179 return True;
2180 else:
2181 # Probably byte array or some such thing.
2182 for ch in sText:
2183 if ch >= 128 or ch < 0:
2184 return True;
2185 return False;
2186
2187
2188#
2189# Unpacking.
2190#
2191
2192def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2193 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2194 """
2195 Worker for unpackFile that deals with ZIP files, same function signature.
2196 """
2197 import zipfile
2198 if fnError is None:
2199 fnError = fnLog;
2200
2201 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
2202
2203 # Open it.
2204 try: oZipFile = zipfile.ZipFile(sArchive, 'r'); # pylint: disable=consider-using-with
2205 except Exception as oXcpt:
2206 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2207 return None;
2208
2209 # Extract all members.
2210 asMembers = [];
2211 try:
2212 for sMember in oZipFile.namelist():
2213 if fnFilter is None or fnFilter(sMember) is not False:
2214 if sMember.endswith('/'):
2215 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2)
2216 else:
2217 oZipFile.extract(sMember, sDstDir);
2218 asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
2219 except Exception as oXcpt:
2220 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2221 asMembers = None;
2222
2223 # close it.
2224 try: oZipFile.close();
2225 except Exception as oXcpt:
2226 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2227 asMembers = None;
2228
2229 return asMembers;
2230
2231
2232## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already.
2233g_fTarCopyFileObjOverriddend = False;
2234
2235def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError, bufsize = None):
2236 """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """
2237 _ = bufsize;
2238 if length is None:
2239 __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize);
2240 elif length > 0:
2241 cFull, cbRemainder = divmod(length, g_cbGoodBufferSize);
2242 for _ in xrange(cFull):
2243 abBuffer = src.read(g_cbGoodBufferSize);
2244 dst.write(abBuffer);
2245 if len(abBuffer) != g_cbGoodBufferSize:
2246 raise exception('unexpected end of source file');
2247 if cbRemainder > 0:
2248 abBuffer = src.read(cbRemainder);
2249 dst.write(abBuffer);
2250 if len(abBuffer) != cbRemainder:
2251 raise exception('unexpected end of source file');
2252
2253
2254def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2255 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2256 """
2257 Worker for unpackFile that deals with tarballs, same function signature.
2258 """
2259 import shutil;
2260 import tarfile;
2261 if fnError is None:
2262 fnError = fnLog;
2263
2264 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
2265
2266 #
2267 # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows.
2268 # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs.
2269 # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs.
2270 #
2271 if True is True: # pylint: disable=comparison-with-itself
2272 __installShUtilHacks(shutil);
2273 global g_fTarCopyFileObjOverriddend;
2274 if g_fTarCopyFileObjOverriddend is False:
2275 g_fTarCopyFileObjOverriddend = True;
2276 #if sys.hexversion < 0x03060000:
2277 tarfile.copyfileobj = __mytarfilecopyfileobj;
2278
2279 #
2280 # Open it.
2281 #
2282 # Note! We not using 'r:*' because we cannot allow seeking compressed files!
2283 # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb).
2284 #
2285 try:
2286 if sys.hexversion >= 0x03060000:
2287 oTarFile = tarfile.open(sArchive, 'r|*', # pylint: disable=consider-using-with
2288 bufsize = g_cbGoodBufferSize, copybufsize = g_cbGoodBufferSize);
2289 else:
2290 oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize); # pylint: disable=consider-using-with
2291 except Exception as oXcpt:
2292 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
2293 return None;
2294
2295 # Extract all members.
2296 asMembers = [];
2297 try:
2298 for oTarInfo in oTarFile:
2299 try:
2300 if fnFilter is None or fnFilter(oTarInfo.name) is not False:
2301 if oTarInfo.islnk():
2302 # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking
2303 # in the compressed tar stream. So, fall back on shutil.copy2 instead.
2304 sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep));
2305 sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep));
2306 sParentDir = os.path.dirname(sLinkFile);
2307 try: os.unlink(sLinkFile);
2308 except: pass;
2309 if sParentDir and not os.path.exists(sParentDir):
2310 os.makedirs(sParentDir);
2311 try: os.link(sLinkTarget, sLinkFile);
2312 except: shutil.copy2(sLinkTarget, sLinkFile);
2313 else:
2314 if oTarInfo.isdir():
2315 # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right.
2316 oTarInfo.mode |= 0x1c0; # (octal: 0700)
2317 oTarFile.extract(oTarInfo, sDstDir);
2318 asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep)));
2319 except Exception as oXcpt:
2320 fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt));
2321 for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]:
2322 fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),));
2323 for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]:
2324 fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),));
2325 asMembers = None;
2326 break;
2327 except Exception as oXcpt:
2328 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2329 asMembers = None;
2330
2331 #
2332 # Finally, close it.
2333 #
2334 try: oTarFile.close();
2335 except Exception as oXcpt:
2336 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2337 asMembers = None;
2338
2339 return asMembers;
2340
2341
2342def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2343 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2344 """
2345 Unpacks the given file if it has a know archive extension, otherwise do
2346 nothing.
2347
2348 fnLog & fnError both take a string parameter.
2349
2350 fnFilter takes a member name (string) and returns True if it's included
2351 and False if excluded.
2352
2353 Returns list of the extracted files (full path) on success.
2354 Returns empty list if not a supported archive format.
2355 Returns None on failure. Raises no exceptions.
2356 """
2357 sBaseNameLower = os.path.basename(sArchive).lower();
2358
2359 #
2360 # Zip file?
2361 #
2362 if sBaseNameLower.endswith('.zip'):
2363 return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2364
2365 #
2366 # Tarball?
2367 #
2368 if sBaseNameLower.endswith('.tar') \
2369 or sBaseNameLower.endswith('.tar.gz') \
2370 or sBaseNameLower.endswith('.tgz') \
2371 or sBaseNameLower.endswith('.tar.bz2'):
2372 return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2373
2374 #
2375 # Cannot classify it from the name, so just return that to the caller.
2376 #
2377 fnLog('Not unpacking "%s".' % (sArchive,));
2378 return [];
2379
2380
2381#
2382# Misc.
2383#
2384def areBytesEqual(oLeft, oRight):
2385 """
2386 Compares two byte arrays, strings or whatnot.
2387
2388 returns true / false accordingly.
2389 """
2390
2391 # If both are None, consider them equal (bogus?):
2392 if oLeft is None and oRight is None:
2393 return True;
2394
2395 # If just one is None, they can't match:
2396 if oLeft is None or oRight is None:
2397 return False;
2398
2399 # If both have the same type, use the compare operator of the class:
2400 if type(oLeft) is type(oRight):
2401 #print('same type: %s' % (oLeft == oRight,));
2402 return oLeft == oRight;
2403
2404 # On the offchance that they're both strings, but of different types.
2405 if isString(oLeft) and isString(oRight):
2406 #print('string compare: %s' % (oLeft == oRight,));
2407 return oLeft == oRight;
2408
2409 #
2410 # See if byte/buffer stuff that can be compared directory. If not convert
2411 # strings to bytes.
2412 #
2413 # Note! For 2.x, we must convert both sides to the buffer type or the
2414 # comparison may fail despite it working okay in test cases.
2415 #
2416 if sys.version_info[0] >= 3:
2417 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2418 return oLeft == oRight;
2419
2420 if isString(oLeft):
2421 try: oLeft = bytes(oLeft, 'utf-8');
2422 except: pass;
2423 if isString(oRight):
2424 try: oRight = bytes(oRight, 'utf-8');
2425 except: pass;
2426 else:
2427 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2428 if isinstance(oLeft, bytearray):
2429 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2430 else:
2431 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2432 #print('buf/byte #1 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2433 return oLeft == oRight;
2434
2435 if isString(oLeft):
2436 try: oLeft = bytearray(oLeft, 'utf-8'); # pylint: disable=redefined-variable-type
2437 except: pass;
2438 if isString(oRight):
2439 try: oRight = bytearray(oRight, 'utf-8'); # pylint: disable=redefined-variable-type
2440 except: pass;
2441
2442 # Check if we now have the same type for both:
2443 if type(oLeft) is type(oRight):
2444 #print('same type now: %s' % (oLeft == oRight,));
2445 return oLeft == oRight;
2446
2447 # Check if we now have buffer/memoryview vs bytes/bytesarray again.
2448 if sys.version_info[0] >= 3:
2449 if isinstance(oLeft, (bytearray, memoryview, bytes)) and isinstance(oRight, (bytearray, memoryview, bytes)): # pylint: disable=undefined-variable
2450 return oLeft == oRight;
2451 else:
2452 if isinstance(oLeft, (bytearray, buffer)) and isinstance(oRight, (bytearray, buffer)): # pylint: disable=undefined-variable
2453 if isinstance(oLeft, bytearray):
2454 oLeft = buffer(oLeft); # pylint: disable=redefined-variable-type,undefined-variable
2455 else:
2456 oRight = buffer(oRight); # pylint: disable=redefined-variable-type,undefined-variable
2457 #print('buf/byte #2 compare: %s (%s vs %s)' % (oLeft == oRight, type(oLeft), type(oRight),));
2458 return oLeft == oRight;
2459
2460 # Do item by item comparison:
2461 if len(oLeft) != len(oRight):
2462 #print('different length: %s vs %s' % (len(oLeft), len(oRight)));
2463 return False;
2464 i = len(oLeft);
2465 while i > 0:
2466 i = i - 1;
2467
2468 iElmLeft = oLeft[i];
2469 if not isinstance(iElmLeft, int) and not isinstance(iElmLeft, long):
2470 iElmLeft = ord(iElmLeft);
2471
2472 iElmRight = oRight[i];
2473 if not isinstance(iElmRight, int) and not isinstance(iElmRight, long):
2474 iElmRight = ord(iElmRight);
2475
2476 if iElmLeft != iElmRight:
2477 #print('element %d differs: %x %x' % (i, iElmLeft, iElmRight,));
2478 return False;
2479 return True;
2480
2481
2482def calcCrc32OfFile(sFile):
2483 """
2484 Simple helper for calculating the CRC32 of a file.
2485
2486 Throws stuff if the file cannot be opened or read successfully.
2487 """
2488 import zlib;
2489
2490 uCrc32 = 0;
2491 with open(sFile, 'rb') as oFile:
2492 while True:
2493 oBuf = oFile.read(1024 * 1024);
2494 if not oBuf:
2495 break
2496 uCrc32 = zlib.crc32(oBuf, uCrc32);
2497
2498 return uCrc32 % 2**32;
2499
2500
2501#
2502# Unit testing.
2503#
2504
2505# pylint: disable=missing-docstring
2506# pylint: disable=undefined-variable
2507class BuildCategoryDataTestCase(unittest.TestCase):
2508 def testIntervalSeconds(self):
2509 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
2510 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
2511 self.assertEqual(parseIntervalSeconds('123'), (123, None));
2512 self.assertEqual(parseIntervalSeconds(123), (123, None));
2513 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
2514 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
2515 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
2516 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
2517 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
2518 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
2519 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
2520
2521 def testZuluNormalization(self):
2522 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:34:25.000000000Z'), '2011-01-02T03:34:25.000000000Z');
2523 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25-0030'), '2011-01-02T03:34:25.000000000Z');
2524 self.assertEqual(normalizeIsoTimestampToZulu('2011-01-02T03:04:25+0030'), '2011-01-02T02:34:25.000000000Z');
2525 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863+01:00'), '2020-03-20T19:47:39.832312000Z');
2526 self.assertEqual(normalizeIsoTimestampToZulu('2020-03-20T20:47:39,832312863-02:00'), '2020-03-20T22:47:39.832312000Z');
2527
2528 def testHasNonAsciiChars(self):
2529 self.assertEqual(hasNonAsciiCharacters(''), False);
2530 self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False);
2531 self.assertEqual(hasNonAsciiCharacters('\x80 '), True);
2532 self.assertEqual(hasNonAsciiCharacters('\x79 '), False);
2533 self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False);
2534 self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False);
2535 self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True);
2536 self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True);
2537 self.assertEqual(hasNonAsciiCharacters(b'\x20\x20\x20'), False);
2538 self.assertEqual(hasNonAsciiCharacters(b'\x20\x81\x20'), True);
2539
2540 def testAreBytesEqual(self):
2541 self.assertEqual(areBytesEqual(None, None), True);
2542 self.assertEqual(areBytesEqual(None, ''), False);
2543 self.assertEqual(areBytesEqual('', ''), True);
2544 self.assertEqual(areBytesEqual('1', '1'), True);
2545 self.assertEqual(areBytesEqual('12345', '1234'), False);
2546 self.assertEqual(areBytesEqual('1234', '1234'), True);
2547 self.assertEqual(areBytesEqual('1234', b'1234'), True);
2548 self.assertEqual(areBytesEqual(b'1234', b'1234'), True);
2549 self.assertEqual(areBytesEqual(b'1234', '1234'), True);
2550 self.assertEqual(areBytesEqual(b'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2551 self.assertEqual(areBytesEqual('1234', bytearray([0x31,0x32,0x33,0x34])), True);
2552 self.assertEqual(areBytesEqual(u'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2553 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x33,0x34])), True);
2554 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), '1224'), False);
2555 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x32,0x34])), False);
2556 if sys.version_info[0] >= 3:
2557 pass;
2558 else:
2559 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2560 bytearray([0x31,0x32,0x33,0x34])), True);
2561 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2562 bytearray([0x99,0x32,0x32,0x34])), False);
2563 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2564 buffer(bytearray([0x31,0x32,0x33,0x34,0x34]), 0, 4)), True);
2565 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2566 buffer(bytearray([0x99,0x32,0x33,0x34,0x34]), 0, 4)), False);
2567 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), b'1234'), True);
2568 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), '1234'), True);
2569 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), u'1234'), True);
2570
2571if __name__ == '__main__':
2572 unittest.main();
2573 # not reached.
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