VirtualBox

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

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

ValidationKit: Implement support to query some process state on Windows when cdb.exe is available

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