VirtualBox

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

Last change on this file since 77807 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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