VirtualBox

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

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

ValKit/utils.py,tests: Move crc32_of_file to common/utils.py as calcCrc32OfFile, no point in having two copies of it. pylint fixes.

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