VirtualBox

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

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

ValidationKit/common: pylint 2.0.0 fixes.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette