VirtualBox

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

Last change on this file since 78658 was 78472, checked in by vboxsync, 6 years ago

validationkit/common/utils.py: areBytesEqual adjustment

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 78472 2019-05-12 18:28:36Z 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: 78472 $"
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
573def getDiskUsage(sPath):
574 """
575 Get free space of a partition that corresponds to specified sPath in MB.
576
577 Returns partition free space value in MB.
578 """
579 if platform.system() == 'Windows':
580 oCTypeFreeSpace = ctypes.c_ulonglong(0);
581 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None,
582 ctypes.pointer(oCTypeFreeSpace));
583 cbFreeSpace = oCTypeFreeSpace.value;
584 else:
585 oStats = os.statvfs(sPath); # pylint: disable=E1101
586 cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree;
587
588 # Convert to MB
589 cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024);
590
591 return cMbFreeSpace;
592
593
594
595#
596# SubProcess.
597#
598
599def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs):
600 """
601 If the "executable" is a python script, insert the python interpreter at
602 the head of the argument list so that it will work on systems which doesn't
603 support hash-bang scripts.
604 """
605
606 asArgs = dKeywordArgs.get('args');
607 if asArgs is None:
608 asArgs = aPositionalArgs[0];
609
610 if asArgs[0].endswith('.py'):
611 if sys.executable:
612 asArgs.insert(0, sys.executable);
613 else:
614 asArgs.insert(0, 'python');
615
616 # paranoia...
617 if dKeywordArgs.get('args') is not None:
618 dKeywordArgs['args'] = asArgs;
619 else:
620 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
621 return None;
622
623def processPopenSafe(*aPositionalArgs, **dKeywordArgs):
624 """
625 Wrapper for subprocess.Popen that's Ctrl-C safe on windows.
626 """
627 if getHostOs() == 'win':
628 if dKeywordArgs.get('creationflags', 0) == 0:
629 dKeywordArgs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP;
630 return subprocess.Popen(*aPositionalArgs, **dKeywordArgs);
631
632def processCall(*aPositionalArgs, **dKeywordArgs):
633 """
634 Wrapper around subprocess.call to deal with its absence in older
635 python versions.
636 Returns process exit code (see subprocess.poll).
637 """
638 assert dKeywordArgs.get('stdout') is None;
639 assert dKeywordArgs.get('stderr') is None;
640 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
641 oProcess = processPopenSafe(*aPositionalArgs, **dKeywordArgs);
642 return oProcess.wait();
643
644def processOutputChecked(*aPositionalArgs, **dKeywordArgs):
645 """
646 Wrapper around subprocess.check_output to deal with its absense in older
647 python versions.
648
649 Extra keywords for specifying now output is to be decoded:
650 sEncoding='utf-8
651 fIgnoreEncoding=True/False
652 """
653 sEncoding = dKeywordArgs.get('sEncoding');
654 if sEncoding is not None: del dKeywordArgs['sEncoding'];
655 else: sEncoding = 'utf-8';
656
657 fIgnoreEncoding = dKeywordArgs.get('fIgnoreEncoding');
658 if fIgnoreEncoding is not None: del dKeywordArgs['fIgnoreEncoding'];
659 else: fIgnoreEncoding = True;
660
661 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
662 oProcess = processPopenSafe(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
663
664 sOutput, _ = oProcess.communicate();
665 iExitCode = oProcess.poll();
666
667 if iExitCode is not 0:
668 asArgs = dKeywordArgs.get('args');
669 if asArgs is None:
670 asArgs = aPositionalArgs[0];
671 print(sOutput);
672 raise subprocess.CalledProcessError(iExitCode, asArgs);
673
674 if hasattr(sOutput, 'decode'):
675 sOutput = sOutput.decode(sEncoding, 'ignore' if fIgnoreEncoding else 'strict');
676 return sOutput;
677
678g_fOldSudo = None;
679def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True):
680 """
681 Adds 'sudo' (or similar) to the args parameter, whereever it is.
682 """
683
684 # Are we root?
685 fIsRoot = True;
686 try:
687 fIsRoot = os.getuid() == 0; # pylint: disable=E1101
688 except:
689 pass;
690
691 # If not, prepend sudo (non-interactive, simulate initial login).
692 if fIsRoot is not True:
693 asArgs = dKeywordArgs.get('args');
694 if asArgs is None:
695 asArgs = aPositionalArgs[0];
696
697 # Detect old sudo.
698 global g_fOldSudo;
699 if g_fOldSudo is None:
700 try:
701 sVersion = str(processOutputChecked(['sudo', '-V']));
702 except:
703 sVersion = '1.7.0';
704 sVersion = sVersion.strip().split('\n')[0];
705 sVersion = sVersion.replace('Sudo version', '').strip();
706 g_fOldSudo = len(sVersion) >= 4 \
707 and sVersion[0] == '1' \
708 and sVersion[1] == '.' \
709 and sVersion[2] <= '6' \
710 and sVersion[3] == '.';
711
712 asArgs.insert(0, 'sudo');
713 if not g_fOldSudo:
714 asArgs.insert(1, '-n');
715 if fInitialEnv and not g_fOldSudo:
716 asArgs.insert(1, '-i');
717
718 # paranoia...
719 if dKeywordArgs.get('args') is not None:
720 dKeywordArgs['args'] = asArgs;
721 else:
722 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
723 return None;
724
725
726def sudoProcessCall(*aPositionalArgs, **dKeywordArgs):
727 """
728 sudo (or similar) + subprocess.call
729 """
730 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
731 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
732 return processCall(*aPositionalArgs, **dKeywordArgs);
733
734def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs):
735 """
736 sudo (or similar) + subprocess.check_output.
737 """
738 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
739 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
740 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
741
742def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs):
743 """
744 sudo (or similar) + subprocess.check_output, except '-i' isn't used.
745 """
746 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
747 _sudoFixArguments(aPositionalArgs, dKeywordArgs, False);
748 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
749
750def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs):
751 """
752 sudo (or similar) + processPopenSafe.
753 """
754 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
755 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
756 return processPopenSafe(*aPositionalArgs, **dKeywordArgs);
757
758
759#
760# Generic process stuff.
761#
762
763def processInterrupt(uPid):
764 """
765 Sends a SIGINT or equivalent to interrupt the specified process.
766 Returns True on success, False on failure.
767
768 On Windows hosts this may not work unless the process happens to be a
769 process group leader.
770 """
771 if sys.platform == 'win32':
772 try:
773 win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, # pylint: disable=no-member,c-extension-no-member
774 uPid);
775 fRc = True;
776 except:
777 fRc = False;
778 else:
779 try:
780 os.kill(uPid, signal.SIGINT);
781 fRc = True;
782 except:
783 fRc = False;
784 return fRc;
785
786def sendUserSignal1(uPid):
787 """
788 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
789 (VBoxSVC) or something.
790 Returns True on success, False on failure or if not supported (win).
791
792 On Windows hosts this may not work unless the process happens to be a
793 process group leader.
794 """
795 if sys.platform == 'win32':
796 fRc = False;
797 else:
798 try:
799 os.kill(uPid, signal.SIGUSR1); # pylint: disable=E1101
800 fRc = True;
801 except:
802 fRc = False;
803 return fRc;
804
805def processTerminate(uPid):
806 """
807 Terminates the process in a nice manner (SIGTERM or equivalent).
808 Returns True on success, False on failure.
809 """
810 fRc = False;
811 if sys.platform == 'win32':
812 try:
813 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, # pylint: disable=no-member,c-extension-no-member
814 False, uPid);
815 except:
816 pass;
817 else:
818 try:
819 win32process.TerminateProcess(hProcess, # pylint: disable=no-member,c-extension-no-member
820 0x40010004); # DBG_TERMINATE_PROCESS
821 fRc = True;
822 except:
823 pass;
824 hProcess.Close(); #win32api.CloseHandle(hProcess)
825 else:
826 try:
827 os.kill(uPid, signal.SIGTERM);
828 fRc = True;
829 except:
830 pass;
831 return fRc;
832
833def processKill(uPid):
834 """
835 Terminates the process with extreme prejudice (SIGKILL).
836 Returns True on success, False on failure.
837 """
838 if sys.platform == 'win32':
839 fRc = processTerminate(uPid);
840 else:
841 try:
842 os.kill(uPid, signal.SIGKILL); # pylint: disable=E1101
843 fRc = True;
844 except:
845 fRc = False;
846 return fRc;
847
848def processKillWithNameCheck(uPid, sName):
849 """
850 Like processKill(), but checks if the process name matches before killing
851 it. This is intended for killing using potentially stale pid values.
852
853 Returns True on success, False on failure.
854 """
855
856 if processCheckPidAndName(uPid, sName) is not True:
857 return False;
858 return processKill(uPid);
859
860
861def processExists(uPid):
862 """
863 Checks if the specified process exits.
864 This will only work if we can signal/open the process.
865
866 Returns True if it positively exists, False otherwise.
867 """
868 if sys.platform == 'win32':
869 fRc = False;
870 # We try open the process for waiting since this is generally only forbidden in a very few cases.
871 try:
872 hProcess = win32api.OpenProcess(win32con.SYNCHRONIZE, # pylint: disable=no-member,c-extension-no-member
873 False, uPid);
874 except pywintypes.error as oXcpt: # pylint: disable=no-member
875 if oXcpt.winerror == winerror.ERROR_ACCESS_DENIED:
876 fRc = True;
877 except Exception as oXcpt:
878 pass;
879 else:
880 hProcess.Close();
881 fRc = True;
882 else:
883 try:
884 os.kill(uPid, 0);
885 fRc = True;
886 except: ## @todo check error code.
887 fRc = False;
888 return fRc;
889
890def processCheckPidAndName(uPid, sName):
891 """
892 Checks if a process PID and NAME matches.
893 """
894 fRc = processExists(uPid);
895 if fRc is not True:
896 return False;
897
898 if sys.platform == 'win32':
899 try:
900 from win32com.client import GetObject; # pylint: disable=F0401
901 oWmi = GetObject('winmgmts:');
902 aoProcesses = oWmi.InstancesOf('Win32_Process');
903 for oProcess in aoProcesses:
904 if long(oProcess.Properties_("ProcessId").Value) == uPid:
905 sCurName = oProcess.Properties_("Name").Value;
906 #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName));
907 sName = sName.lower();
908 sCurName = sCurName.lower();
909 if os.path.basename(sName) == sName:
910 sCurName = os.path.basename(sCurName);
911
912 if sCurName == sName \
913 or sCurName + '.exe' == sName \
914 or sCurName == sName + '.exe':
915 fRc = True;
916 break;
917 except:
918 #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName));
919 pass;
920 else:
921 if sys.platform in ('linux2', 'linux', 'linux3', 'linux4', 'linux5', 'linux6'):
922 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
923 elif sys.platform in ('sunos5',):
924 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
925 elif sys.platform in ('darwin',):
926 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
927 else:
928 asPsCmd = None;
929
930 if asPsCmd is not None:
931 try:
932 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE);
933 sCurName = oPs.communicate()[0];
934 iExitCode = oPs.wait();
935 except:
936 #reporter.logXcpt();
937 return False;
938
939 # ps fails with non-zero exit code if the pid wasn't found.
940 if iExitCode is not 0:
941 return False;
942 if sCurName is None:
943 return False;
944 sCurName = sCurName.strip();
945 if not sCurName:
946 return False;
947
948 if os.path.basename(sName) == sName:
949 sCurName = os.path.basename(sCurName);
950 elif os.path.basename(sCurName) == sCurName:
951 sName = os.path.basename(sName);
952
953 if sCurName != sName:
954 return False;
955
956 fRc = True;
957 return fRc;
958
959def processGetInfo(uPid, fSudo = False):
960 """
961 Tries to acquire state information of the given process.
962
963 Returns a string with the information on success or None on failure or
964 if the host is not supported.
965
966 Note that the format of the information is host system dependent and will
967 likely differ much between different hosts.
968 """
969 fRc = processExists(uPid);
970 if fRc is not True:
971 return None;
972
973 sHostOs = getHostOs();
974 if sHostOs in [ 'linux',]:
975 sGdb = '/usr/bin/gdb';
976 if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb';
977 if not os.path.isfile(sGdb): sGdb = 'gdb';
978 aasCmd = [
979 [ sGdb, '-batch',
980 '-ex', 'set pagination off',
981 '-ex', 'thread apply all bt',
982 '-ex', 'info proc mapping',
983 '-ex', 'info sharedlibrary',
984 '-p', '%u' % (uPid,), ],
985 ];
986 elif sHostOs == 'darwin':
987 # LLDB doesn't work in batch mode when attaching to a process, at least
988 # with macOS Sierra (10.12). GDB might not be installed. Use the sample
989 # tool instead with a 1 second duration and 1000ms sampling interval to
990 # get one stack trace. For the process mappings use vmmap.
991 aasCmd = [
992 [ '/usr/bin/sample', '-mayDie', '%u' % (uPid,), '1', '1000', ],
993 [ '/usr/bin/vmmap', '%u' % (uPid,), ],
994 ];
995 elif sHostOs == 'solaris':
996 aasCmd = [
997 [ '/usr/bin/pstack', '%u' % (uPid,), ],
998 [ '/usr/bin/pmap', '%u' % (uPid,), ],
999 ];
1000 else:
1001 aasCmd = [];
1002
1003 sInfo = '';
1004 for asCmd in aasCmd:
1005 try:
1006 if fSudo:
1007 sThisInfo = sudoProcessOutputChecked(asCmd);
1008 else:
1009 sThisInfo = processOutputChecked(asCmd);
1010 if sThisInfo is not None:
1011 sInfo += sThisInfo;
1012 except:
1013 pass;
1014 if not sInfo:
1015 sInfo = None;
1016
1017 return sInfo;
1018
1019
1020class ProcessInfo(object):
1021 """Process info."""
1022 def __init__(self, iPid):
1023 self.iPid = iPid;
1024 self.iParentPid = None;
1025 self.sImage = None;
1026 self.sName = None;
1027 self.asArgs = None;
1028 self.sCwd = None;
1029 self.iGid = None;
1030 self.iUid = None;
1031 self.iProcGroup = None;
1032 self.iSessionId = None;
1033
1034 def loadAll(self):
1035 """Load all the info."""
1036 sOs = getHostOs();
1037 if sOs == 'linux':
1038 sProc = '/proc/%s/' % (self.iPid,);
1039 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None);
1040 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None);
1041 if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00');
1042 #elif sOs == 'solaris': - doesn't work for root processes, suid proces, and other stuff.
1043 # sProc = '/proc/%s/' % (self.iPid,);
1044 # if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None);
1045 # if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None);
1046 else:
1047 pass;
1048 if self.sName is None and self.sImage is not None:
1049 self.sName = self.sImage;
1050
1051 def windowsGrabProcessInfo(self, oProcess):
1052 """Windows specific loadAll."""
1053 try: self.sName = oProcess.Properties_("Name").Value;
1054 except: pass;
1055 try: self.sImage = oProcess.Properties_("ExecutablePath").Value;
1056 except: pass;
1057 try: self.asArgs = [oProcess.Properties_("CommandLine").Value]; ## @todo split it.
1058 except: pass;
1059 try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value;
1060 except: pass;
1061 try: self.iSessionId = oProcess.Properties_("SessionId").Value;
1062 except: pass;
1063 if self.sName is None and self.sImage is not None:
1064 self.sName = self.sImage;
1065
1066 def getBaseImageName(self):
1067 """
1068 Gets the base image name if available, use the process name if not available.
1069 Returns image/process base name or None.
1070 """
1071 sRet = self.sImage if self.sName is None else self.sName;
1072 if sRet is None:
1073 self.loadAll();
1074 sRet = self.sImage if self.sName is None else self.sName;
1075 if sRet is None:
1076 if not self.asArgs:
1077 return None;
1078 sRet = self.asArgs[0];
1079 if not sRet:
1080 return None;
1081 return os.path.basename(sRet);
1082
1083 def getBaseImageNameNoExeSuff(self):
1084 """
1085 Same as getBaseImageName, except any '.exe' or similar suffix is stripped.
1086 """
1087 sRet = self.getBaseImageName();
1088 if sRet is not None and len(sRet) > 4 and sRet[-4] == '.':
1089 if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]:
1090 sRet = sRet[:-4];
1091 return sRet;
1092
1093
1094def processListAll(): # pylint: disable=R0914
1095 """
1096 Return a list of ProcessInfo objects for all the processes in the system
1097 that the current user can see.
1098 """
1099 asProcesses = [];
1100
1101 sOs = getHostOs();
1102 if sOs == 'win':
1103 from win32com.client import GetObject; # pylint: disable=F0401
1104 oWmi = GetObject('winmgmts:');
1105 aoProcesses = oWmi.InstancesOf('Win32_Process');
1106 for oProcess in aoProcesses:
1107 try:
1108 iPid = int(oProcess.Properties_("ProcessId").Value);
1109 except:
1110 continue;
1111 oMyInfo = ProcessInfo(iPid);
1112 oMyInfo.windowsGrabProcessInfo(oProcess);
1113 asProcesses.append(oMyInfo);
1114 return asProcesses;
1115
1116 if sOs in [ 'linux', ]: # Not solaris, ps gets more info than /proc/.
1117 try:
1118 asDirs = os.listdir('/proc');
1119 except:
1120 asDirs = [];
1121 for sDir in asDirs:
1122 if sDir.isdigit():
1123 asProcesses.append(ProcessInfo(int(sDir),));
1124 return asProcesses;
1125
1126 #
1127 # The other OSes parses the output from the 'ps' utility.
1128 #
1129 asPsCmd = [
1130 '/bin/ps', # 0
1131 '-A', # 1
1132 '-o', 'pid=', # 2,3
1133 '-o', 'ppid=', # 4,5
1134 '-o', 'pgid=', # 6,7
1135 '-o', 'sid=', # 8,9
1136 '-o', 'uid=', # 10,11
1137 '-o', 'gid=', # 12,13
1138 '-o', 'comm=' # 14,15
1139 ];
1140
1141 if sOs == 'darwin':
1142 assert asPsCmd[9] == 'sid=';
1143 asPsCmd[9] = 'sess=';
1144 elif sOs == 'solaris':
1145 asPsCmd[0] = '/usr/bin/ps';
1146
1147 try:
1148 sRaw = processOutputChecked(asPsCmd);
1149 except:
1150 return asProcesses;
1151
1152 for sLine in sRaw.split('\n'):
1153 sLine = sLine.lstrip();
1154 if len(sLine) < 7 or not sLine[0].isdigit():
1155 continue;
1156
1157 iField = 0;
1158 off = 0;
1159 aoFields = [None, None, None, None, None, None, None];
1160 while iField < 7:
1161 # Eat whitespace.
1162 while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'):
1163 off += 1;
1164
1165 # Final field / EOL.
1166 if iField == 6:
1167 aoFields[6] = sLine[off:];
1168 break;
1169 if off >= len(sLine):
1170 break;
1171
1172 # Generic field parsing.
1173 offStart = off;
1174 off += 1;
1175 while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t':
1176 off += 1;
1177 try:
1178 if iField != 3:
1179 aoFields[iField] = int(sLine[offStart:off]);
1180 else:
1181 aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address.
1182 except:
1183 pass;
1184 iField += 1;
1185
1186 if aoFields[0] is not None:
1187 oMyInfo = ProcessInfo(aoFields[0]);
1188 oMyInfo.iParentPid = aoFields[1];
1189 oMyInfo.iProcGroup = aoFields[2];
1190 oMyInfo.iSessionId = aoFields[3];
1191 oMyInfo.iUid = aoFields[4];
1192 oMyInfo.iGid = aoFields[5];
1193 oMyInfo.sName = aoFields[6];
1194 asProcesses.append(oMyInfo);
1195
1196 return asProcesses;
1197
1198
1199def processCollectCrashInfo(uPid, fnLog, fnCrashFile):
1200 """
1201 Looks for information regarding the demise of the given process.
1202 """
1203 sOs = getHostOs();
1204 if sOs == 'darwin':
1205 #
1206 # On darwin we look for crash and diagnostic reports.
1207 #
1208 asLogDirs = [
1209 u'/Library/Logs/DiagnosticReports/',
1210 u'/Library/Logs/CrashReporter/',
1211 u'~/Library/Logs/DiagnosticReports/',
1212 u'~/Library/Logs/CrashReporter/',
1213 ];
1214 for sDir in asLogDirs:
1215 sDir = os.path.expanduser(sDir);
1216 if not os.path.isdir(sDir):
1217 continue;
1218 try:
1219 asDirEntries = os.listdir(sDir);
1220 except:
1221 continue;
1222 for sEntry in asDirEntries:
1223 # Only interested in .crash files.
1224 _, sSuff = os.path.splitext(sEntry);
1225 if sSuff != '.crash':
1226 continue;
1227
1228 # The pid can be found at the end of the first line.
1229 sFull = os.path.join(sDir, sEntry);
1230 try:
1231 oFile = open(sFull, 'r');
1232 sFirstLine = oFile.readline();
1233 oFile.close();
1234 except:
1235 continue;
1236 if len(sFirstLine) <= 4 or sFirstLine[-2] != ']':
1237 continue;
1238 offPid = len(sFirstLine) - 3;
1239 while offPid > 1 and sFirstLine[offPid - 1].isdigit():
1240 offPid -= 1;
1241 try: uReportPid = int(sFirstLine[offPid:-2]);
1242 except: continue;
1243
1244 # Does the pid we found match?
1245 if uReportPid == uPid:
1246 fnLog('Found crash report for %u: %s' % (uPid, sFull,));
1247 fnCrashFile(sFull, False);
1248 elif sOs == 'win':
1249 #
1250 # Getting WER reports would be great, however we have trouble match the
1251 # PID to those as they seems not to mention it in the brief reports.
1252 # Instead we'll just look for crash dumps in C:\CrashDumps (our custom
1253 # location - see the windows readme for the testbox script) and what
1254 # the MSDN article lists for now.
1255 #
1256 # It's been observed on Windows server 2012 that the dump files takes
1257 # the form: <processimage>.<decimal-pid>.dmp
1258 #
1259 asDmpDirs = [
1260 u'%SystemDrive%/CrashDumps/', # Testboxes.
1261 u'%LOCALAPPDATA%/CrashDumps/', # MSDN example.
1262 u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service.
1263 u'%WINDIR%/ServiceProfiles/NetworkSerices/',
1264 u'%WINDIR%/ServiceProfiles/',
1265 u'%WINDIR%/System32/Config/SystemProfile/', # System services.
1266 ];
1267 sMatchSuffix = '.%u.dmp' % (uPid,);
1268
1269 for sDir in asDmpDirs:
1270 sDir = os.path.expandvars(sDir);
1271 if not os.path.isdir(sDir):
1272 continue;
1273 try:
1274 asDirEntries = os.listdir(sDir);
1275 except:
1276 continue;
1277 for sEntry in asDirEntries:
1278 if sEntry.endswith(sMatchSuffix):
1279 sFull = os.path.join(sDir, sEntry);
1280 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
1281 fnCrashFile(sFull, True);
1282
1283 else:
1284 pass; ## TODO
1285 return None;
1286
1287
1288#
1289# Time.
1290#
1291
1292#
1293# The following test case shows how time.time() only have ~ms resolution
1294# on Windows (tested W10) and why it therefore makes sense to try use
1295# performance counters.
1296#
1297# Note! We cannot use time.clock() as the timestamp must be portable across
1298# processes. See timeout testcase problem on win hosts (no logs).
1299#
1300#import sys;
1301#import time;
1302#from common import utils;
1303#
1304#atSeries = [];
1305#for i in xrange(1,160):
1306# if i == 159: time.sleep(10);
1307# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
1308#
1309#tPrev = atSeries[0]
1310#for tCur in atSeries:
1311# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
1312# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
1313# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
1314# print '';
1315# tPrev = tCur
1316#
1317#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
1318#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
1319#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
1320
1321g_fWinUseWinPerfCounter = sys.platform == 'win32';
1322g_fpWinPerfCounterFreq = None;
1323g_oFuncwinQueryPerformanceCounter = None;
1324
1325def _winInitPerfCounter():
1326 """ Initializes the use of performance counters. """
1327 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
1328
1329 uFrequency = ctypes.c_ulonglong(0);
1330 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
1331 if uFrequency.value >= 1000:
1332 #print 'uFrequency = %s' % (uFrequency,);
1333 #print 'type(uFrequency) = %s' % (type(uFrequency),);
1334 g_fpWinPerfCounterFreq = float(uFrequency.value);
1335
1336 # Check that querying the counter works too.
1337 global g_oFuncwinQueryPerformanceCounter
1338 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
1339 uCurValue = ctypes.c_ulonglong(0);
1340 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1341 if uCurValue.value > 0:
1342 return True;
1343 g_fWinUseWinPerfCounter = False;
1344 return False;
1345
1346def _winFloatTime():
1347 """ Gets floating point time on windows. """
1348 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
1349 uCurValue = ctypes.c_ulonglong(0);
1350 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
1351 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
1352 return time.time();
1353
1354
1355def timestampNano():
1356 """
1357 Gets a nanosecond timestamp.
1358 """
1359 if g_fWinUseWinPerfCounter is True:
1360 return long(_winFloatTime() * 1000000000);
1361 return long(time.time() * 1000000000);
1362
1363def timestampMilli():
1364 """
1365 Gets a millisecond timestamp.
1366 """
1367 if g_fWinUseWinPerfCounter is True:
1368 return long(_winFloatTime() * 1000);
1369 return long(time.time() * 1000);
1370
1371def timestampSecond():
1372 """
1373 Gets a second timestamp.
1374 """
1375 if g_fWinUseWinPerfCounter is True:
1376 return long(_winFloatTime());
1377 return long(time.time());
1378
1379def getTimePrefix():
1380 """
1381 Returns a timestamp prefix, typically used for logging. UTC.
1382 """
1383 try:
1384 oNow = datetime.datetime.utcnow();
1385 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1386 except:
1387 sTs = 'getTimePrefix-exception';
1388 return sTs;
1389
1390def getTimePrefixAndIsoTimestamp():
1391 """
1392 Returns current UTC as log prefix and iso timestamp.
1393 """
1394 try:
1395 oNow = datetime.datetime.utcnow();
1396 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
1397 sTsIso = formatIsoTimestamp(oNow);
1398 except:
1399 sTsPrf = sTsIso = 'getTimePrefix-exception';
1400 return (sTsPrf, sTsIso);
1401
1402def formatIsoTimestamp(oNow):
1403 """Formats the datetime object as an ISO timestamp."""
1404 assert oNow.tzinfo is None;
1405 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1406 return sTs;
1407
1408def getIsoTimestamp():
1409 """Returns the current UTC timestamp as a string."""
1410 return formatIsoTimestamp(datetime.datetime.utcnow());
1411
1412
1413def getLocalHourOfWeek():
1414 """ Local hour of week (0 based). """
1415 oNow = datetime.datetime.now();
1416 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1417
1418
1419def formatIntervalSeconds(cSeconds):
1420 """ Format a seconds interval into a nice 01h 00m 22s string """
1421 # Two simple special cases.
1422 if cSeconds < 60:
1423 return '%ss' % (cSeconds,);
1424 if cSeconds < 3600:
1425 cMins = cSeconds // 60;
1426 cSecs = cSeconds % 60;
1427 if cSecs == 0:
1428 return '%sm' % (cMins,);
1429 return '%sm %ss' % (cMins, cSecs,);
1430
1431 # Generic and a bit slower.
1432 cDays = cSeconds // 86400;
1433 cSeconds %= 86400;
1434 cHours = cSeconds // 3600;
1435 cSeconds %= 3600;
1436 cMins = cSeconds // 60;
1437 cSecs = cSeconds % 60;
1438 sRet = '';
1439 if cDays > 0:
1440 sRet = '%sd ' % (cDays,);
1441 if cHours > 0:
1442 sRet += '%sh ' % (cHours,);
1443 if cMins > 0:
1444 sRet += '%sm ' % (cMins,);
1445 if cSecs > 0:
1446 sRet += '%ss ' % (cSecs,);
1447 assert sRet; assert sRet[-1] == ' ';
1448 return sRet[:-1];
1449
1450def formatIntervalSeconds2(oSeconds):
1451 """
1452 Flexible input version of formatIntervalSeconds for use in WUI forms where
1453 data is usually already string form.
1454 """
1455 if isinstance(oSeconds, (int, long)):
1456 return formatIntervalSeconds(oSeconds);
1457 if not isString(oSeconds):
1458 try:
1459 lSeconds = long(oSeconds);
1460 except:
1461 pass;
1462 else:
1463 if lSeconds >= 0:
1464 return formatIntervalSeconds2(lSeconds);
1465 return oSeconds;
1466
1467def parseIntervalSeconds(sString):
1468 """
1469 Reverse of formatIntervalSeconds.
1470
1471 Returns (cSeconds, sError), where sError is None on success.
1472 """
1473
1474 # We might given non-strings, just return them without any fuss.
1475 if not isString(sString):
1476 if isinstance(sString, (int, long)) or sString is None:
1477 return (sString, None);
1478 ## @todo time/date objects?
1479 return (int(sString), None);
1480
1481 # Strip it and make sure it's not empty.
1482 sString = sString.strip();
1483 if not sString:
1484 return (0, 'Empty interval string.');
1485
1486 #
1487 # Split up the input into a list of 'valueN, unitN, ...'.
1488 #
1489 # Don't want to spend too much time trying to make re.split do exactly what
1490 # I need here, so please forgive the extra pass I'm making here.
1491 #
1492 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1493 asParts = [];
1494 for sPart in asRawParts:
1495 sPart = sPart.strip();
1496 if sPart:
1497 asParts.append(sPart);
1498 if not asParts:
1499 return (0, 'Empty interval string or something?');
1500
1501 #
1502 # Process them one or two at the time.
1503 #
1504 cSeconds = 0;
1505 asErrors = [];
1506 i = 0;
1507 while i < len(asParts):
1508 sNumber = asParts[i];
1509 i += 1;
1510 if sNumber.isdigit():
1511 iNumber = int(sNumber);
1512
1513 sUnit = 's';
1514 if i < len(asParts) and not asParts[i].isdigit():
1515 sUnit = asParts[i];
1516 i += 1;
1517
1518 sUnitLower = sUnit.lower();
1519 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1520 pass;
1521 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1522 iNumber *= 60;
1523 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1524 iNumber *= 3600;
1525 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1526 iNumber *= 86400;
1527 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1528 iNumber *= 7 * 86400;
1529 else:
1530 asErrors.append('Unknown unit "%s".' % (sUnit,));
1531 cSeconds += iNumber;
1532 else:
1533 asErrors.append('Bad number "%s".' % (sNumber,));
1534 return (cSeconds, None if not asErrors else ' '.join(asErrors));
1535
1536def formatIntervalHours(cHours):
1537 """ Format a hours interval into a nice 1w 2d 1h string. """
1538 # Simple special cases.
1539 if cHours < 24:
1540 return '%sh' % (cHours,);
1541
1542 # Generic and a bit slower.
1543 cWeeks = cHours / (7 * 24);
1544 cHours %= 7 * 24;
1545 cDays = cHours / 24;
1546 cHours %= 24;
1547 sRet = '';
1548 if cWeeks > 0:
1549 sRet = '%sw ' % (cWeeks,);
1550 if cDays > 0:
1551 sRet = '%sd ' % (cDays,);
1552 if cHours > 0:
1553 sRet += '%sh ' % (cHours,);
1554 assert sRet; assert sRet[-1] == ' ';
1555 return sRet[:-1];
1556
1557def parseIntervalHours(sString):
1558 """
1559 Reverse of formatIntervalHours.
1560
1561 Returns (cHours, sError), where sError is None on success.
1562 """
1563
1564 # We might given non-strings, just return them without any fuss.
1565 if not isString(sString):
1566 if isinstance(sString, (int, long)) or sString is None:
1567 return (sString, None);
1568 ## @todo time/date objects?
1569 return (int(sString), None);
1570
1571 # Strip it and make sure it's not empty.
1572 sString = sString.strip();
1573 if not sString:
1574 return (0, 'Empty interval string.');
1575
1576 #
1577 # Split up the input into a list of 'valueN, unitN, ...'.
1578 #
1579 # Don't want to spend too much time trying to make re.split do exactly what
1580 # I need here, so please forgive the extra pass I'm making here.
1581 #
1582 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1583 asParts = [];
1584 for sPart in asRawParts:
1585 sPart = sPart.strip();
1586 if sPart:
1587 asParts.append(sPart);
1588 if not asParts:
1589 return (0, 'Empty interval string or something?');
1590
1591 #
1592 # Process them one or two at the time.
1593 #
1594 cHours = 0;
1595 asErrors = [];
1596 i = 0;
1597 while i < len(asParts):
1598 sNumber = asParts[i];
1599 i += 1;
1600 if sNumber.isdigit():
1601 iNumber = int(sNumber);
1602
1603 sUnit = 'h';
1604 if i < len(asParts) and not asParts[i].isdigit():
1605 sUnit = asParts[i];
1606 i += 1;
1607
1608 sUnitLower = sUnit.lower();
1609 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1610 pass;
1611 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1612 iNumber *= 24;
1613 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1614 iNumber *= 7 * 24;
1615 else:
1616 asErrors.append('Unknown unit "%s".' % (sUnit,));
1617 cHours += iNumber;
1618 else:
1619 asErrors.append('Bad number "%s".' % (sNumber,));
1620 return (cHours, None if not asErrors else ' '.join(asErrors));
1621
1622
1623#
1624# Introspection.
1625#
1626
1627def getCallerName(oFrame=None, iFrame=2):
1628 """
1629 Returns the name of the caller's caller.
1630 """
1631 if oFrame is None:
1632 try:
1633 raise Exception();
1634 except:
1635 oFrame = sys.exc_info()[2].tb_frame.f_back;
1636 while iFrame > 1:
1637 if oFrame is not None:
1638 oFrame = oFrame.f_back;
1639 iFrame = iFrame - 1;
1640 if oFrame is not None:
1641 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1642 return sName;
1643 return "unknown";
1644
1645
1646def getXcptInfo(cFrames = 1):
1647 """
1648 Gets text detailing the exception. (Good for logging.)
1649 Returns list of info strings.
1650 """
1651
1652 #
1653 # Try get exception info.
1654 #
1655 try:
1656 oType, oValue, oTraceback = sys.exc_info();
1657 except:
1658 oType = oValue = oTraceback = None;
1659 if oType is not None:
1660
1661 #
1662 # Try format the info
1663 #
1664 asRet = [];
1665 try:
1666 try:
1667 asRet = asRet + traceback.format_exception_only(oType, oValue);
1668 asTraceBack = traceback.format_tb(oTraceback);
1669 if cFrames is not None and cFrames <= 1:
1670 asRet.append(asTraceBack[-1]);
1671 else:
1672 asRet.append('Traceback:')
1673 for iFrame in range(min(cFrames, len(asTraceBack))):
1674 asRet.append(asTraceBack[-iFrame - 1]);
1675 asRet.append('Stack:')
1676 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1677 except:
1678 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
1679
1680 if not asRet:
1681 asRet.append('No exception info...');
1682 except:
1683 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
1684 else:
1685 asRet = ['Couldn\'t find exception traceback.'];
1686 return asRet;
1687
1688
1689def getObjectTypeName(oObject):
1690 """
1691 Get the type name of the given object.
1692 """
1693 if oObject is None:
1694 return 'None';
1695
1696 # Get the type object.
1697 try:
1698 oType = type(oObject);
1699 except:
1700 return 'type-throws-exception';
1701
1702 # Python 2.x only: Handle old-style object wrappers.
1703 if sys.version_info[0] < 3:
1704 try:
1705 from types import InstanceType; # pylint: disable=no-name-in-module
1706 if oType == InstanceType:
1707 oType = oObject.__class__;
1708 except:
1709 pass;
1710
1711 # Get the name.
1712 try:
1713 return oType.__name__;
1714 except:
1715 return '__type__-throws-exception';
1716
1717
1718def chmodPlusX(sFile):
1719 """
1720 Makes the specified file or directory executable.
1721 Returns success indicator, no exceptions.
1722
1723 Note! Symbolic links are followed and the target will be changed.
1724 """
1725 try:
1726 oStat = os.stat(sFile);
1727 except:
1728 return False;
1729 try:
1730 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
1731 except:
1732 return False;
1733 return True;
1734
1735
1736#
1737# TestSuite stuff.
1738#
1739
1740def isRunningFromCheckout(cScriptDepth = 1):
1741 """
1742 Checks if we're running from the SVN checkout or not.
1743 """
1744
1745 try:
1746 sFile = __file__;
1747 cScriptDepth = 1;
1748 except:
1749 sFile = sys.argv[0];
1750
1751 sDir = os.path.abspath(sFile);
1752 while cScriptDepth >= 0:
1753 sDir = os.path.dirname(sDir);
1754 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
1755 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
1756 return True;
1757 cScriptDepth -= 1;
1758
1759 return False;
1760
1761
1762#
1763# Bourne shell argument fun.
1764#
1765
1766
1767def argsSplit(sCmdLine):
1768 """
1769 Given a bourne shell command line invocation, split it up into arguments
1770 assuming IFS is space.
1771 Returns None on syntax error.
1772 """
1773 ## @todo bourne shell argument parsing!
1774 return sCmdLine.split(' ');
1775
1776def argsGetFirst(sCmdLine):
1777 """
1778 Given a bourne shell command line invocation, get return the first argument
1779 assuming IFS is space.
1780 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
1781 """
1782 asArgs = argsSplit(sCmdLine);
1783 if not asArgs:
1784 return None;
1785
1786 return asArgs[0];
1787
1788#
1789# String helpers.
1790#
1791
1792def stricmp(sFirst, sSecond):
1793 """
1794 Compares to strings in an case insensitive fashion.
1795
1796 Python doesn't seem to have any way of doing the correctly, so this is just
1797 an approximation using lower.
1798 """
1799 if sFirst == sSecond:
1800 return 0;
1801 sLower1 = sFirst.lower();
1802 sLower2 = sSecond.lower();
1803 if sLower1 == sLower2:
1804 return 0;
1805 if sLower1 < sLower2:
1806 return -1;
1807 return 1;
1808
1809
1810def versionCompare(sVer1, sVer2):
1811 """
1812 Compares to version strings in a fashion similar to RTStrVersionCompare.
1813 """
1814
1815 ## @todo implement me!!
1816
1817 if sVer1 == sVer2:
1818 return 0;
1819 if sVer1 < sVer2:
1820 return -1;
1821 return 1;
1822
1823
1824def formatNumber(lNum, sThousandSep = ' '):
1825 """
1826 Formats a decimal number with pretty separators.
1827 """
1828 sNum = str(lNum);
1829 sRet = sNum[-3:];
1830 off = len(sNum) - 3;
1831 while off > 0:
1832 off -= 3;
1833 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
1834 return sRet;
1835
1836
1837def formatNumberNbsp(lNum):
1838 """
1839 Formats a decimal number with pretty separators.
1840 """
1841 sRet = formatNumber(lNum);
1842 return unicode(sRet).replace(' ', u'\u00a0');
1843
1844
1845def isString(oString):
1846 """
1847 Checks if the object is a string object, hiding difference between python 2 and 3.
1848
1849 Returns True if it's a string of some kind.
1850 Returns False if not.
1851 """
1852 if sys.version_info[0] >= 3:
1853 return isinstance(oString, str);
1854 return isinstance(oString, basestring); # pylint: disable=undefined-variable
1855
1856
1857def hasNonAsciiCharacters(sText):
1858 """
1859 Returns True is specified string has non-ASCII characters, False if ASCII only.
1860 """
1861 if isString(sText):
1862 for ch in sText:
1863 if ord(ch) >= 128:
1864 return True;
1865 else:
1866 # Probably byte array or some such thing.
1867 for ch in sText:
1868 if ch >= 128 or ch < 0:
1869 return True;
1870 return False;
1871
1872
1873#
1874# Unpacking.
1875#
1876
1877def unpackZipFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
1878 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
1879 """
1880 Worker for unpackFile that deals with ZIP files, same function signature.
1881 """
1882 import zipfile
1883 if fnError is None:
1884 fnError = fnLog;
1885
1886 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
1887
1888 # Open it.
1889 try: oZipFile = zipfile.ZipFile(sArchive, 'r')
1890 except Exception as oXcpt:
1891 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
1892 return None;
1893
1894 # Extract all members.
1895 asMembers = [];
1896 try:
1897 for sMember in oZipFile.namelist():
1898 if fnFilter is None or fnFilter(sMember) is not False:
1899 if sMember.endswith('/'):
1900 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0x1fd); # octal: 0775 (python 3/2)
1901 else:
1902 oZipFile.extract(sMember, sDstDir);
1903 asMembers.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
1904 except Exception as oXcpt:
1905 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
1906 asMembers = None;
1907
1908 # close it.
1909 try: oZipFile.close();
1910 except Exception as oXcpt:
1911 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
1912 asMembers = None;
1913
1914 return asMembers;
1915
1916
1917## Set if we've replaced tarfile.copyfileobj with __mytarfilecopyfileobj already.
1918g_fTarCopyFileObjOverriddend = False;
1919
1920def __mytarfilecopyfileobj(src, dst, length = None, exception = OSError, bufsize = None):
1921 """ tarfile.copyfileobj with different buffer size (16384 is slow on windows). """
1922 _ = bufsize;
1923 if length is None:
1924 __myshutilcopyfileobj(src, dst, g_cbGoodBufferSize);
1925 elif length > 0:
1926 cFull, cbRemainder = divmod(length, g_cbGoodBufferSize);
1927 for _ in xrange(cFull):
1928 abBuffer = src.read(g_cbGoodBufferSize);
1929 dst.write(abBuffer);
1930 if len(abBuffer) != g_cbGoodBufferSize:
1931 raise exception('unexpected end of source file');
1932 if cbRemainder > 0:
1933 abBuffer = src.read(cbRemainder);
1934 dst.write(abBuffer);
1935 if len(abBuffer) != cbRemainder:
1936 raise exception('unexpected end of source file');
1937
1938
1939def unpackTarFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
1940 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
1941 """
1942 Worker for unpackFile that deals with tarballs, same function signature.
1943 """
1944 import shutil;
1945 import tarfile;
1946 if fnError is None:
1947 fnError = fnLog;
1948
1949 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
1950
1951 #
1952 # Default buffer sizes of 16384 bytes is causing too many syscalls on Windows.
1953 # 60%+ speedup for python 2.7 and 50%+ speedup for python 3.5, both on windows with PDBs.
1954 # 20%+ speedup for python 2.7 and 15%+ speedup for python 3.5, both on windows skipping PDBs.
1955 #
1956 if True is True:
1957 __installShUtilHacks(shutil);
1958 global g_fTarCopyFileObjOverriddend;
1959 if g_fTarCopyFileObjOverriddend is False:
1960 g_fTarCopyFileObjOverriddend = True;
1961 #if sys.hexversion < 0x03060000:
1962 tarfile.copyfileobj = __mytarfilecopyfileobj;
1963
1964 #
1965 # Open it.
1966 #
1967 # Note! We not using 'r:*' because we cannot allow seeking compressed files!
1968 # That's how we got a 13 min unpack time for VBoxAll on windows (hardlinked pdb).
1969 #
1970 try:
1971 if sys.hexversion >= 0x03060000:
1972 oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize, copybufsize = g_cbGoodBufferSize);
1973 else:
1974 oTarFile = tarfile.open(sArchive, 'r|*', bufsize = g_cbGoodBufferSize);
1975 except Exception as oXcpt:
1976 fnError('Error opening "%s" for unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt,));
1977 return None;
1978
1979 # Extract all members.
1980 asMembers = [];
1981 try:
1982 for oTarInfo in oTarFile:
1983 try:
1984 if fnFilter is None or fnFilter(oTarInfo.name) is not False:
1985 if oTarInfo.islnk():
1986 # Links are trouble, especially on Windows. We must avoid the falling that will end up seeking
1987 # in the compressed tar stream. So, fall back on shutil.copy2 instead.
1988 sLinkFile = os.path.join(sDstDir, oTarInfo.name.rstrip('/').replace('/', os.path.sep));
1989 sLinkTarget = os.path.join(sDstDir, oTarInfo.linkname.rstrip('/').replace('/', os.path.sep));
1990 sParentDir = os.path.dirname(sLinkFile);
1991 try: os.unlink(sLinkFile);
1992 except: pass;
1993 if sParentDir and not os.path.exists(sParentDir):
1994 os.makedirs(sParentDir);
1995 try: os.link(sLinkTarget, sLinkFile);
1996 except: shutil.copy2(sLinkTarget, sLinkFile);
1997 else:
1998 if oTarInfo.isdir():
1999 # Just make sure the user (we) got full access to dirs. Don't bother getting it 100% right.
2000 oTarInfo.mode |= 0x1c0; # (octal: 0700)
2001 oTarFile.extract(oTarInfo, sDstDir);
2002 asMembers.append(os.path.join(sDstDir, oTarInfo.name.replace('/', os.path.sep)));
2003 except Exception as oXcpt:
2004 fnError('Error unpacking "%s" member "%s" into "%s": %s' % (sArchive, oTarInfo.name, sDstDir, oXcpt));
2005 for sAttr in [ 'name', 'linkname', 'type', 'mode', 'size', 'mtime', 'uid', 'uname', 'gid', 'gname' ]:
2006 fnError('Info: %8s=%s' % (sAttr, getattr(oTarInfo, sAttr),));
2007 for sFn in [ 'isdir', 'isfile', 'islnk', 'issym' ]:
2008 fnError('Info: %8s=%s' % (sFn, getattr(oTarInfo, sFn)(),));
2009 asMembers = None;
2010 break;
2011 except Exception as oXcpt:
2012 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
2013 asMembers = None;
2014
2015 #
2016 # Finally, close it.
2017 #
2018 try: oTarFile.close();
2019 except Exception as oXcpt:
2020 fnError('Error closing "%s" after unpacking into "%s": %s' % (sArchive, sDstDir, oXcpt));
2021 asMembers = None;
2022
2023 return asMembers;
2024
2025
2026def unpackFile(sArchive, sDstDir, fnLog, fnError = None, fnFilter = None):
2027 # type: (string, string, (string) -> None, (string) -> None, (string) -> bool) -> list[string]
2028 """
2029 Unpacks the given file if it has a know archive extension, otherwise do
2030 nothing.
2031
2032 fnLog & fnError both take a string parameter.
2033
2034 fnFilter takes a member name (string) and returns True if it's included
2035 and False if excluded.
2036
2037 Returns list of the extracted files (full path) on success.
2038 Returns empty list if not a supported archive format.
2039 Returns None on failure. Raises no exceptions.
2040 """
2041 sBaseNameLower = os.path.basename(sArchive).lower();
2042
2043 #
2044 # Zip file?
2045 #
2046 if sBaseNameLower.endswith('.zip'):
2047 return unpackZipFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2048
2049 #
2050 # Tarball?
2051 #
2052 if sBaseNameLower.endswith('.tar') \
2053 or sBaseNameLower.endswith('.tar.gz') \
2054 or sBaseNameLower.endswith('.tgz') \
2055 or sBaseNameLower.endswith('.tar.bz2'):
2056 return unpackTarFile(sArchive, sDstDir, fnLog, fnError, fnFilter);
2057
2058 #
2059 # Cannot classify it from the name, so just return that to the caller.
2060 #
2061 fnLog('Not unpacking "%s".' % (sArchive,));
2062 return [];
2063
2064
2065#
2066# Misc.
2067#
2068def areBytesEqual(oLeft, oRight):
2069 """
2070 Compares two byte arrays, strings or whatnot.
2071
2072 returns true / false accordingly.
2073 """
2074
2075 # If both are None, consider them equal (bogus?):
2076 if oLeft is None and oRight is None:
2077 return True;
2078
2079 # If just one is None, they can't match:
2080 if oLeft is None or oRight is None:
2081 return False;
2082
2083 # If both have the same type, use the compare operator of the class:
2084 if type(oLeft) is type(oRight):
2085 #print('same type: %s' % (oLeft == oRight,));
2086 return oLeft == oRight;
2087
2088 # On the offchance that they're both strings, but of different types.
2089 if isString(oLeft) and isString(oRight):
2090 #print('string compare: %s' % (oLeft == oRight,));
2091 return oLeft == oRight;
2092
2093 # Convert strings to byte arrays:
2094 if sys.version_info[0] >= 3:
2095 if isString(oLeft):
2096 try: oLeft = bytes(oLeft, 'utf-8');
2097 except: pass;
2098 if isString(oRight):
2099 try: oRight = bytes(oRight, 'utf-8');
2100 except: pass;
2101 else:
2102 if isString(oLeft):
2103 try: oLeft = bytearray(oLeft, 'utf-8'); # pylint: disable=redefined-variable-type
2104 except: pass;
2105 if isString(oRight):
2106 try: oRight = bytearray(oRight, 'utf-8'); # pylint: disable=redefined-variable-type
2107 except: pass;
2108
2109 # Check if we now have the same type for both:
2110 if type(oLeft) is type(oRight):
2111 #print('same type now: %s' % (oLeft == oRight,));
2112 return oLeft == oRight;
2113
2114 # Do item by item comparison:
2115 if len(oLeft) != len(oRight):
2116 #print('different length: %s vs %s' % (len(oLeft), len(oRight)));
2117 return False;
2118 i = len(oLeft);
2119 while i > 0:
2120 i = i - 1;
2121
2122 iElmLeft = oLeft[i];
2123 if not isinstance(iElmLeft, int) and not isinstance(iElmLeft, long):
2124 iElmLeft = ord(iElmLeft);
2125
2126 iElmRight = oRight[i];
2127 if not isinstance(iElmRight, int) and not isinstance(iElmRight, long):
2128 iElmRight = ord(iElmRight);
2129
2130 if iElmLeft != iElmRight:
2131 #print('element %d differs: %x %x' % (i, iElmLeft, iElmRight,));
2132 return False;
2133 return True;
2134
2135
2136#
2137# Unit testing.
2138#
2139
2140# pylint: disable=C0111
2141# pylint: disable=undefined-variable
2142class BuildCategoryDataTestCase(unittest.TestCase):
2143 def testIntervalSeconds(self):
2144 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
2145 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
2146 self.assertEqual(parseIntervalSeconds('123'), (123, None));
2147 self.assertEqual(parseIntervalSeconds(123), (123, None));
2148 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
2149 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
2150 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
2151 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
2152 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
2153 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
2154 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
2155
2156 def testHasNonAsciiChars(self):
2157 self.assertEqual(hasNonAsciiCharacters(''), False);
2158 self.assertEqual(hasNonAsciiCharacters('asdfgebASDFKJ@#$)(!@#UNASDFKHB*&$%&)@#(!)@(#!(#$&*#$&%*Y@#$IQWN---00;'), False);
2159 self.assertEqual(hasNonAsciiCharacters('\x80 '), True);
2160 self.assertEqual(hasNonAsciiCharacters('\x79 '), False);
2161 self.assertEqual(hasNonAsciiCharacters(u'12039889y!@#$%^&*()0-0asjdkfhoiuyweasdfASDFnvV'), False);
2162 self.assertEqual(hasNonAsciiCharacters(u'\u0079'), False);
2163 self.assertEqual(hasNonAsciiCharacters(u'\u0080'), True);
2164 self.assertEqual(hasNonAsciiCharacters(u'\u0081 \u0100'), True);
2165 self.assertEqual(hasNonAsciiCharacters(b'\x20\x20\x20'), False);
2166 self.assertEqual(hasNonAsciiCharacters(b'\x20\x81\x20'), True);
2167
2168 def testAreBytesEqual(self):
2169 self.assertEqual(areBytesEqual(None, None), True);
2170 self.assertEqual(areBytesEqual(None, ''), False);
2171 self.assertEqual(areBytesEqual('', ''), True);
2172 self.assertEqual(areBytesEqual('1', '1'), True);
2173 self.assertEqual(areBytesEqual('12345', '1234'), False);
2174 self.assertEqual(areBytesEqual('1234', '1234'), True);
2175 self.assertEqual(areBytesEqual('1234', b'1234'), True);
2176 self.assertEqual(areBytesEqual(b'1234', b'1234'), True);
2177 self.assertEqual(areBytesEqual(b'1234', '1234'), True);
2178 self.assertEqual(areBytesEqual(b'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2179 self.assertEqual(areBytesEqual('1234', bytearray([0x31,0x32,0x33,0x34])), True);
2180 self.assertEqual(areBytesEqual(u'1234', bytearray([0x31,0x32,0x33,0x34])), True);
2181 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x33,0x34])), True);
2182 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), '1224'), False);
2183 self.assertEqual(areBytesEqual(bytearray([0x31,0x32,0x33,0x34]), bytearray([0x31,0x32,0x32,0x34])), False);
2184 if sys.version_info[0] >= 3:
2185 pass;
2186 else:
2187 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2188 bytearray([0x31,0x32,0x32,0x34])), False);
2189 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1),
2190 buffer(bytearray([0x31,0x32,0x33,0x34,0x34]), 0, 4)), True);
2191 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), b'1234'), True);
2192 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), '1234'), True);
2193 self.assertEqual(areBytesEqual(buffer(bytearray([0x30,0x31,0x32,0x33,0x34]), 1), u'1234'), True);
2194
2195if __name__ == '__main__':
2196 unittest.main();
2197 # not reached.
2198
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