VirtualBox

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

Last change on this file since 68155 was 68139, checked in by vboxsync, 7 years ago

ValidationKit: disable gdb pagination

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