VirtualBox

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

Last change on this file since 86728 was 86588, checked in by vboxsync, 4 years ago

common/utils.py: Adding whichProgram. bugref:9841

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