VirtualBox

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

Last change on this file since 66472 was 66472, checked in by vboxsync, 8 years ago

common/utils.py: make asArgs at least an array containing one string, not just a string

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