VirtualBox

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

Last change on this file since 61464 was 61464, checked in by vboxsync, 9 years ago

common/utils.py: Corrected the timestamp implementation on windows to produce the same timestamps in two different processes. (time.clock() doesn't do this, as per it's documentation.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.7 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: utils.py 61464 2016-06-04 02:37:02Z vboxsync $
3# pylint: disable=C0302
4
5"""
6Common Utility Functions.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2012-2015 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: 61464 $"
31
32
33# Standard Python imports.
34import datetime;
35import os;
36import platform;
37import re;
38import stat;
39import subprocess;
40import sys;
41import tarfile;
42import time;
43import traceback;
44import unittest;
45import zipfile
46
47if sys.platform == 'win32':
48 import ctypes;
49 import win32api; # pylint: disable=F0401
50 import win32con; # pylint: disable=F0401
51 import win32console; # pylint: disable=F0401
52 import win32process; # pylint: disable=F0401
53else:
54 import signal;
55
56# Python 3 hacks:
57if sys.version_info[0] >= 3:
58 long = int; # pylint: disable=W0622,C0103
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": "Fuji" }
217 sVersion += ' / OS X ' + sOsxVersion + ' (' + codenames[sOsxVersion.split('.')[1]] + ')'
218
219 return sVersion;
220
221#
222# File system.
223#
224
225def openNoInherit(sFile, sMode = 'r'):
226 """
227 Wrapper around open() that tries it's best to make sure the file isn't
228 inherited by child processes.
229
230 This is a best effort thing at the moment as it doesn't synchronizes with
231 child process spawning in any way. Thus it can be subject to races in
232 multithreaded programs.
233 """
234
235 try:
236 from fcntl import FD_CLOEXEC, F_GETFD, F_SETFD, fcntl; # pylint: disable=F0401
237 except:
238 return open(sFile, sMode);
239
240 oFile = open(sFile, sMode)
241 #try:
242 fcntl(oFile, F_SETFD, fcntl(oFile, F_GETFD) | FD_CLOEXEC);
243 #except:
244 # pass;
245 return oFile;
246
247def noxcptReadLink(sPath, sXcptRet):
248 """
249 No exceptions os.readlink wrapper.
250 """
251 try:
252 sRet = os.readlink(sPath); # pylint: disable=E1101
253 except:
254 sRet = sXcptRet;
255 return sRet;
256
257def readFile(sFile, sMode = 'rb'):
258 """
259 Reads the entire file.
260 """
261 oFile = open(sFile, sMode);
262 sRet = oFile.read();
263 oFile.close();
264 return sRet;
265
266def noxcptReadFile(sFile, sXcptRet, sMode = 'rb'):
267 """
268 No exceptions common.readFile wrapper.
269 """
270 try:
271 sRet = readFile(sFile, sMode);
272 except:
273 sRet = sXcptRet;
274 return sRet;
275
276def noxcptRmDir(sDir, oXcptRet = False):
277 """
278 No exceptions os.rmdir wrapper.
279 """
280 oRet = True;
281 try:
282 os.rmdir(sDir);
283 except:
284 oRet = oXcptRet;
285 return oRet;
286
287def noxcptDeleteFile(sFile, oXcptRet = False):
288 """
289 No exceptions os.remove wrapper.
290 """
291 oRet = True;
292 try:
293 os.remove(sFile);
294 except:
295 oRet = oXcptRet;
296 return oRet;
297
298
299#
300# SubProcess.
301#
302
303def _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs):
304 """
305 If the "executable" is a python script, insert the python interpreter at
306 the head of the argument list so that it will work on systems which doesn't
307 support hash-bang scripts.
308 """
309
310 asArgs = dKeywordArgs.get('args');
311 if asArgs is None:
312 asArgs = aPositionalArgs[0];
313
314 if asArgs[0].endswith('.py'):
315 if sys.executable is not None and len(sys.executable) > 0:
316 asArgs.insert(0, sys.executable);
317 else:
318 asArgs.insert(0, 'python');
319
320 # paranoia...
321 if dKeywordArgs.get('args') is not None:
322 dKeywordArgs['args'] = asArgs;
323 else:
324 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
325 return None;
326
327def processCall(*aPositionalArgs, **dKeywordArgs):
328 """
329 Wrapper around subprocess.call to deal with its absense in older
330 python versions.
331 Returns process exit code (see subprocess.poll).
332 """
333 assert dKeywordArgs.get('stdout') is None;
334 assert dKeywordArgs.get('stderr') is None;
335 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
336 oProcess = subprocess.Popen(*aPositionalArgs, **dKeywordArgs);
337 return oProcess.wait();
338
339def processOutputChecked(*aPositionalArgs, **dKeywordArgs):
340 """
341 Wrapper around subprocess.check_output to deal with its absense in older
342 python versions.
343 """
344 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
345 oProcess = subprocess.Popen(stdout=subprocess.PIPE, *aPositionalArgs, **dKeywordArgs);
346
347 sOutput, _ = oProcess.communicate();
348 iExitCode = oProcess.poll();
349
350 if iExitCode is not 0:
351 asArgs = dKeywordArgs.get('args');
352 if asArgs is None:
353 asArgs = aPositionalArgs[0];
354 print(sOutput);
355 raise subprocess.CalledProcessError(iExitCode, asArgs);
356
357 return str(sOutput); # str() make pylint happy.
358
359g_fOldSudo = None;
360def _sudoFixArguments(aPositionalArgs, dKeywordArgs, fInitialEnv = True):
361 """
362 Adds 'sudo' (or similar) to the args parameter, whereever it is.
363 """
364
365 # Are we root?
366 fIsRoot = True;
367 try:
368 fIsRoot = os.getuid() == 0; # pylint: disable=E1101
369 except:
370 pass;
371
372 # If not, prepend sudo (non-interactive, simulate initial login).
373 if fIsRoot is not True:
374 asArgs = dKeywordArgs.get('args');
375 if asArgs is None:
376 asArgs = aPositionalArgs[0];
377
378 # Detect old sudo.
379 global g_fOldSudo;
380 if g_fOldSudo is None:
381 try:
382 sVersion = processOutputChecked(['sudo', '-V']);
383 except:
384 sVersion = '1.7.0';
385 sVersion = sVersion.strip().split('\n')[0];
386 sVersion = sVersion.replace('Sudo version', '').strip();
387 g_fOldSudo = len(sVersion) >= 4 \
388 and sVersion[0] == '1' \
389 and sVersion[1] == '.' \
390 and sVersion[2] <= '6' \
391 and sVersion[3] == '.';
392
393 asArgs.insert(0, 'sudo');
394 if not g_fOldSudo:
395 asArgs.insert(1, '-n');
396 if fInitialEnv and not g_fOldSudo:
397 asArgs.insert(1, '-i');
398
399 # paranoia...
400 if dKeywordArgs.get('args') is not None:
401 dKeywordArgs['args'] = asArgs;
402 else:
403 aPositionalArgs = (asArgs,) + aPositionalArgs[1:];
404 return None;
405
406
407def sudoProcessCall(*aPositionalArgs, **dKeywordArgs):
408 """
409 sudo (or similar) + subprocess.call
410 """
411 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
412 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
413 return processCall(*aPositionalArgs, **dKeywordArgs);
414
415def sudoProcessOutputChecked(*aPositionalArgs, **dKeywordArgs):
416 """
417 sudo (or similar) + subprocess.check_output.
418 """
419 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
420 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
421 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
422
423def sudoProcessOutputCheckedNoI(*aPositionalArgs, **dKeywordArgs):
424 """
425 sudo (or similar) + subprocess.check_output, except '-i' isn't used.
426 """
427 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
428 _sudoFixArguments(aPositionalArgs, dKeywordArgs, False);
429 return processOutputChecked(*aPositionalArgs, **dKeywordArgs);
430
431def sudoProcessPopen(*aPositionalArgs, **dKeywordArgs):
432 """
433 sudo (or similar) + subprocess.Popen.
434 """
435 _processFixPythonInterpreter(aPositionalArgs, dKeywordArgs);
436 _sudoFixArguments(aPositionalArgs, dKeywordArgs);
437 return subprocess.Popen(*aPositionalArgs, **dKeywordArgs);
438
439
440#
441# Generic process stuff.
442#
443
444def processInterrupt(uPid):
445 """
446 Sends a SIGINT or equivalent to interrupt the specified process.
447 Returns True on success, False on failure.
448
449 On Windows hosts this may not work unless the process happens to be a
450 process group leader.
451 """
452 if sys.platform == 'win32':
453 try:
454 win32console.GenerateConsoleCtrlEvent(win32con.CTRL_BREAK_EVENT, uPid); # pylint: disable=no-member
455 fRc = True;
456 except:
457 fRc = False;
458 else:
459 try:
460 os.kill(uPid, signal.SIGINT);
461 fRc = True;
462 except:
463 fRc = False;
464 return fRc;
465
466def sendUserSignal1(uPid):
467 """
468 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
469 (VBoxSVC) or something.
470 Returns True on success, False on failure or if not supported (win).
471
472 On Windows hosts this may not work unless the process happens to be a
473 process group leader.
474 """
475 if sys.platform == 'win32':
476 fRc = False;
477 else:
478 try:
479 os.kill(uPid, signal.SIGUSR1); # pylint: disable=E1101
480 fRc = True;
481 except:
482 fRc = False;
483 return fRc;
484
485def processTerminate(uPid):
486 """
487 Terminates the process in a nice manner (SIGTERM or equivalent).
488 Returns True on success, False on failure.
489 """
490 fRc = False;
491 if sys.platform == 'win32':
492 try:
493 hProcess = win32api.OpenProcess(win32con.PROCESS_TERMINATE, False, uPid); # pylint: disable=no-member
494 except:
495 pass;
496 else:
497 try:
498 win32process.TerminateProcess(hProcess, 0x40010004); # DBG_TERMINATE_PROCESS # pylint: disable=no-member
499 fRc = True;
500 except:
501 pass;
502 win32api.CloseHandle(hProcess) # pylint: disable=no-member
503 else:
504 try:
505 os.kill(uPid, signal.SIGTERM);
506 fRc = True;
507 except:
508 pass;
509 return fRc;
510
511def processKill(uPid):
512 """
513 Terminates the process with extreme prejudice (SIGKILL).
514 Returns True on success, False on failure.
515 """
516 if sys.platform == 'win32':
517 fRc = processTerminate(uPid);
518 else:
519 try:
520 os.kill(uPid, signal.SIGKILL); # pylint: disable=E1101
521 fRc = True;
522 except:
523 fRc = False;
524 return fRc;
525
526def processKillWithNameCheck(uPid, sName):
527 """
528 Like processKill(), but checks if the process name matches before killing
529 it. This is intended for killing using potentially stale pid values.
530
531 Returns True on success, False on failure.
532 """
533
534 if processCheckPidAndName(uPid, sName) is not True:
535 return False;
536 return processKill(uPid);
537
538
539def processExists(uPid):
540 """
541 Checks if the specified process exits.
542 This will only work if we can signal/open the process.
543
544 Returns True if it positively exists, False otherwise.
545 """
546 if sys.platform == 'win32':
547 fRc = False;
548 try:
549 hProcess = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, uPid); # pylint: disable=no-member
550 except:
551 pass;
552 else:
553 win32api.CloseHandle(hProcess); # pylint: disable=no-member
554 fRc = True;
555 else:
556 try:
557 os.kill(uPid, 0);
558 fRc = True;
559 except:
560 fRc = False;
561 return fRc;
562
563def processCheckPidAndName(uPid, sName):
564 """
565 Checks if a process PID and NAME matches.
566 """
567 fRc = processExists(uPid);
568 if fRc is not True:
569 return False;
570
571 if sys.platform == 'win32':
572 try:
573 from win32com.client import GetObject; # pylint: disable=F0401
574 oWmi = GetObject('winmgmts:');
575 aoProcesses = oWmi.InstancesOf('Win32_Process');
576 for oProcess in aoProcesses:
577 if long(oProcess.Properties_("ProcessId").Value) == uPid:
578 sCurName = oProcess.Properties_("Name").Value;
579 #reporter.log2('uPid=%s sName=%s sCurName=%s' % (uPid, sName, sCurName));
580 sName = sName.lower();
581 sCurName = sCurName.lower();
582 if os.path.basename(sName) == sName:
583 sCurName = os.path.basename(sCurName);
584
585 if sCurName == sName \
586 or sCurName + '.exe' == sName \
587 or sCurName == sName + '.exe':
588 fRc = True;
589 break;
590 except:
591 #reporter.logXcpt('uPid=%s sName=%s' % (uPid, sName));
592 pass;
593 else:
594 if sys.platform in ('linux2', ):
595 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
596 elif sys.platform in ('sunos5',):
597 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
598 elif sys.platform in ('darwin',):
599 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
600 else:
601 asPsCmd = None;
602
603 if asPsCmd is not None:
604 try:
605 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE);
606 sCurName = oPs.communicate()[0];
607 iExitCode = oPs.wait();
608 except:
609 #reporter.logXcpt();
610 return False;
611
612 # ps fails with non-zero exit code if the pid wasn't found.
613 if iExitCode is not 0:
614 return False;
615 if sCurName is None:
616 return False;
617 sCurName = sCurName.strip();
618 if sCurName is '':
619 return False;
620
621 if os.path.basename(sName) == sName:
622 sCurName = os.path.basename(sCurName);
623 elif os.path.basename(sCurName) == sCurName:
624 sName = os.path.basename(sName);
625
626 if sCurName != sName:
627 return False;
628
629 fRc = True;
630 return fRc;
631
632
633class ProcessInfo(object):
634 """Process info."""
635 def __init__(self, iPid):
636 self.iPid = iPid;
637 self.iParentPid = None;
638 self.sImage = None;
639 self.sName = None;
640 self.asArgs = None;
641 self.sCwd = None;
642 self.iGid = None;
643 self.iUid = None;
644 self.iProcGroup = None;
645 self.iSessionId = None;
646
647 def loadAll(self):
648 """Load all the info."""
649 sOs = getHostOs();
650 if sOs == 'linux':
651 sProc = '/proc/%s/' % (self.iPid,);
652 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'exe', None);
653 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'cwd', None);
654 if self.asArgs is None: self.asArgs = noxcptReadFile(sProc + 'cmdline', '').split('\x00');
655 elif sOs == 'solaris':
656 sProc = '/proc/%s/' % (self.iPid,);
657 if self.sImage is None: self.sImage = noxcptReadLink(sProc + 'path/a.out', None);
658 if self.sCwd is None: self.sCwd = noxcptReadLink(sProc + 'path/cwd', None);
659 else:
660 pass;
661 if self.sName is None and self.sImage is not None:
662 self.sName = self.sImage;
663
664 def windowsGrabProcessInfo(self, oProcess):
665 """Windows specific loadAll."""
666 try: self.sName = oProcess.Properties_("Name").Value;
667 except: pass;
668 try: self.sImage = oProcess.Properties_("ExecutablePath").Value;
669 except: pass;
670 try: self.asArgs = oProcess.Properties_("CommandLine").Value; ## @todo split it.
671 except: pass;
672 try: self.iParentPid = oProcess.Properties_("ParentProcessId").Value;
673 except: pass;
674 try: self.iSessionId = oProcess.Properties_("SessionId").Value;
675 except: pass;
676 if self.sName is None and self.sImage is not None:
677 self.sName = self.sImage;
678
679 def getBaseImageName(self):
680 """
681 Gets the base image name if available, use the process name if not available.
682 Returns image/process base name or None.
683 """
684 sRet = self.sImage if self.sName is None else self.sName;
685 if sRet is None:
686 self.loadAll();
687 sRet = self.sImage if self.sName is None else self.sName;
688 if sRet is None:
689 if self.asArgs is None or len(self.asArgs) == 0:
690 return None;
691 sRet = self.asArgs[0];
692 if len(sRet) == 0:
693 return None;
694 return os.path.basename(sRet);
695
696 def getBaseImageNameNoExeSuff(self):
697 """
698 Same as getBaseImageName, except any '.exe' or similar suffix is stripped.
699 """
700 sRet = self.getBaseImageName();
701 if sRet is not None and len(sRet) > 4 and sRet[-4] == '.':
702 if (sRet[-4:]).lower() in [ '.exe', '.com', '.msc', '.vbs', '.cmd', '.bat' ]:
703 sRet = sRet[:-4];
704 return sRet;
705
706
707def processListAll(): # pylint: disable=R0914
708 """
709 Return a list of ProcessInfo objects for all the processes in the system
710 that the current user can see.
711 """
712 asProcesses = [];
713
714 sOs = getHostOs();
715 if sOs == 'win':
716 from win32com.client import GetObject; # pylint: disable=F0401
717 oWmi = GetObject('winmgmts:');
718 aoProcesses = oWmi.InstancesOf('Win32_Process');
719 for oProcess in aoProcesses:
720 try:
721 iPid = int(oProcess.Properties_("ProcessId").Value);
722 except:
723 continue;
724 oMyInfo = ProcessInfo(iPid);
725 oMyInfo.windowsGrabProcessInfo(oProcess);
726 asProcesses.append(oMyInfo);
727
728 elif sOs in [ 'linux', 'solaris' ]:
729 try:
730 asDirs = os.listdir('/proc');
731 except:
732 asDirs = [];
733 for sDir in asDirs:
734 if sDir.isdigit():
735 asProcesses.append(ProcessInfo(int(sDir),));
736
737 elif sOs == 'darwin':
738 # Try our best to parse ps output. (Not perfect but does the job most of the time.)
739 try:
740 sRaw = processOutputChecked([ '/bin/ps', '-A',
741 '-o', 'pid=',
742 '-o', 'ppid=',
743 '-o', 'pgid=',
744 '-o', 'sess=',
745 '-o', 'uid=',
746 '-o', 'gid=',
747 '-o', 'comm=' ]);
748 except:
749 return asProcesses;
750
751 for sLine in sRaw.split('\n'):
752 sLine = sLine.lstrip();
753 if len(sLine) < 7 or not sLine[0].isdigit():
754 continue;
755
756 iField = 0;
757 off = 0;
758 aoFields = [None, None, None, None, None, None, None];
759 while iField < 7:
760 # Eat whitespace.
761 while off < len(sLine) and (sLine[off] == ' ' or sLine[off] == '\t'):
762 off += 1;
763
764 # Final field / EOL.
765 if iField == 6:
766 aoFields[6] = sLine[off:];
767 break;
768 if off >= len(sLine):
769 break;
770
771 # Generic field parsing.
772 offStart = off;
773 off += 1;
774 while off < len(sLine) and sLine[off] != ' ' and sLine[off] != '\t':
775 off += 1;
776 try:
777 if iField != 3:
778 aoFields[iField] = int(sLine[offStart:off]);
779 else:
780 aoFields[iField] = long(sLine[offStart:off], 16); # sess is a hex address.
781 except:
782 pass;
783 iField += 1;
784
785 if aoFields[0] is not None:
786 oMyInfo = ProcessInfo(aoFields[0]);
787 oMyInfo.iParentPid = aoFields[1];
788 oMyInfo.iProcGroup = aoFields[2];
789 oMyInfo.iSessionId = aoFields[3];
790 oMyInfo.iUid = aoFields[4];
791 oMyInfo.iGid = aoFields[5];
792 oMyInfo.sName = aoFields[6];
793 asProcesses.append(oMyInfo);
794
795 return asProcesses;
796
797
798def processCollectCrashInfo(uPid, fnLog, fnCrashFile):
799 """
800 Looks for information regarding the demise of the given process.
801 """
802 sOs = getHostOs();
803 if sOs == 'darwin':
804 #
805 # On darwin we look for crash and diagnostic reports.
806 #
807 asLogDirs = [
808 u'/Library/Logs/DiagnosticReports/',
809 u'/Library/Logs/CrashReporter/',
810 u'~/Library/Logs/DiagnosticReports/',
811 u'~/Library/Logs/CrashReporter/',
812 ];
813 for sDir in asLogDirs:
814 sDir = os.path.expanduser(sDir);
815 if not os.path.isdir(sDir):
816 continue;
817 try:
818 asDirEntries = os.listdir(sDir);
819 except:
820 continue;
821 for sEntry in asDirEntries:
822 # Only interested in .crash files.
823 _, sSuff = os.path.splitext(sEntry);
824 if sSuff != '.crash':
825 continue;
826
827 # The pid can be found at the end of the first line.
828 sFull = os.path.join(sDir, sEntry);
829 try:
830 oFile = open(sFull, 'r');
831 sFirstLine = oFile.readline();
832 oFile.close();
833 except:
834 continue;
835 if len(sFirstLine) <= 4 or sFirstLine[-2] != ']':
836 continue;
837 offPid = len(sFirstLine) - 3;
838 while offPid > 1 and sFirstLine[offPid - 1].isdigit():
839 offPid -= 1;
840 try: uReportPid = int(sFirstLine[offPid:-2]);
841 except: continue;
842
843 # Does the pid we found match?
844 if uReportPid == uPid:
845 fnLog('Found crash report for %u: %s' % (uPid, sFull,));
846 fnCrashFile(sFull, False);
847 elif sOs == 'win':
848 #
849 # Getting WER reports would be great, however we have trouble match the
850 # PID to those as they seems not to mention it in the brief reports.
851 # Instead we'll just look for crash dumps in C:\CrashDumps (our custom
852 # location - see the windows readme for the testbox script) and what
853 # the MSDN article lists for now.
854 #
855 # It's been observed on Windows server 2012 that the dump files takes
856 # the form: <processimage>.<decimal-pid>.dmp
857 #
858 asDmpDirs = [
859 u'%SystemDrive%/CrashDumps/', # Testboxes.
860 u'%LOCALAPPDATA%/CrashDumps/', # MSDN example.
861 u'%WINDIR%/ServiceProfiles/LocalServices/', # Local and network service.
862 u'%WINDIR%/ServiceProfiles/NetworkSerices/',
863 u'%WINDIR%/ServiceProfiles/',
864 u'%WINDIR%/System32/Config/SystemProfile/', # System services.
865 ];
866 sMatchSuffix = '.%u.dmp' % (uPid,);
867
868 for sDir in asDmpDirs:
869 sDir = os.path.expandvars(sDir);
870 if not os.path.isdir(sDir):
871 continue;
872 try:
873 asDirEntries = os.listdir(sDir);
874 except:
875 continue;
876 for sEntry in asDirEntries:
877 if sEntry.endswith(sMatchSuffix):
878 sFull = os.path.join(sDir, sEntry);
879 fnLog('Found crash dump for %u: %s' % (uPid, sFull,));
880 fnCrashFile(sFull, True);
881
882 else:
883 pass; ## TODO
884 return None;
885
886
887#
888# Time.
889#
890
891#
892# The following test case shows how time.time() only have ~ms resolution
893# on Windows (tested W10) and why it therefore makes sense to try use
894# performance counters.
895#
896# Note! We cannot use time.clock() as the timestamp must be portable across
897# processes. See timeout testcase problem on win hosts (no logs).
898#
899#import sys;
900#import time;
901#from common import utils;
902#
903#atSeries = [];
904#for i in xrange(1,160):
905# if i == 159: time.sleep(10);
906# atSeries.append((utils.timestampNano(), long(time.clock() * 1000000000), long(time.time() * 1000000000)));
907#
908#tPrev = atSeries[0]
909#for tCur in atSeries:
910# print 't1=%+22u, %u' % (tCur[0], tCur[0] - tPrev[0]);
911# print 't2=%+22u, %u' % (tCur[1], tCur[1] - tPrev[1]);
912# print 't3=%+22u, %u' % (tCur[2], tCur[2] - tPrev[2]);
913# print '';
914# tPrev = tCur
915#
916#print 't1=%u' % (atSeries[-1][0] - atSeries[0][0]);
917#print 't2=%u' % (atSeries[-1][1] - atSeries[0][1]);
918#print 't3=%u' % (atSeries[-1][2] - atSeries[0][2]);
919
920g_fWinUseWinPerfCounter = sys.platform == 'win32';
921g_fpWinPerfCounterFreq = None;
922g_oFuncwinQueryPerformanceCounter = None;
923
924def _winInitPerfCounter():
925 """ Initializes the use of performance counters. """
926 global g_fWinUseWinPerfCounter, g_fpWinPerfCounterFreq, g_oFuncwinQueryPerformanceCounter
927
928 uFrequency = ctypes.c_ulonglong(0);
929 if ctypes.windll.kernel32.QueryPerformanceFrequency(ctypes.byref(uFrequency)):
930 if uFrequency.value >= 1000:
931 print 'uFrequency = %s' % (uFrequency,);
932 print 'type(uFrequency) = %s' % (type(uFrequency),);
933 g_fpWinPerfCounterFreq = float(uFrequency.value);
934
935 # Check that querying the counter works too.
936 global g_oFuncwinQueryPerformanceCounter
937 g_oFuncwinQueryPerformanceCounter = ctypes.windll.kernel32.QueryPerformanceCounter;
938 uCurValue = ctypes.c_ulonglong(0);
939 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
940 if uCurValue.value > 0:
941 return True;
942 g_fWinUseWinPerfCounter = False;
943 return False;
944
945def _winFloatTime():
946 """ Gets floating point time on windows. """
947 if g_fpWinPerfCounterFreq is not None or _winInitPerfCounter():
948 uCurValue = ctypes.c_ulonglong(0);
949 if g_oFuncwinQueryPerformanceCounter(ctypes.byref(uCurValue)):
950 return float(uCurValue.value) / g_fpWinPerfCounterFreq;
951 return time.time();
952
953
954def timestampNano():
955 """
956 Gets a nanosecond timestamp.
957 """
958 if g_fWinUseWinPerfCounter is True:
959 return long(_winFloatTime() * 1000000000);
960 return long(time.time() * 1000000000);
961
962def timestampMilli():
963 """
964 Gets a millisecond timestamp.
965 """
966 if g_fWinUseWinPerfCounter is True:
967 return long(_winFloatTime() * 1000);
968 return long(time.time() * 1000);
969
970def timestampSecond():
971 """
972 Gets a second timestamp.
973 """
974 if g_fWinUseWinPerfCounter is True:
975 return long(_winFloatTime());
976 return long(time.time());
977
978def getTimePrefix():
979 """
980 Returns a timestamp prefix, typically used for logging. UTC.
981 """
982 try:
983 oNow = datetime.datetime.utcnow();
984 sTs = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
985 except:
986 sTs = 'getTimePrefix-exception';
987 return sTs;
988
989def getTimePrefixAndIsoTimestamp():
990 """
991 Returns current UTC as log prefix and iso timestamp.
992 """
993 try:
994 oNow = datetime.datetime.utcnow();
995 sTsPrf = '%02u:%02u:%02u.%06u' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond);
996 sTsIso = formatIsoTimestamp(oNow);
997 except:
998 sTsPrf = sTsIso = 'getTimePrefix-exception';
999 return (sTsPrf, sTsIso);
1000
1001def formatIsoTimestamp(oNow):
1002 """Formats the datetime object as an ISO timestamp."""
1003 assert oNow.tzinfo is None;
1004 sTs = '%s.%09uZ' % (oNow.strftime('%Y-%m-%dT%H:%M:%S'), oNow.microsecond * 1000);
1005 return sTs;
1006
1007def getIsoTimestamp():
1008 """Returns the current UTC timestamp as a string."""
1009 return formatIsoTimestamp(datetime.datetime.utcnow());
1010
1011
1012def getLocalHourOfWeek():
1013 """ Local hour of week (0 based). """
1014 oNow = datetime.datetime.now();
1015 return (oNow.isoweekday() - 1) * 24 + oNow.hour;
1016
1017
1018def formatIntervalSeconds(cSeconds):
1019 """ Format a seconds interval into a nice 01h 00m 22s string """
1020 # Two simple special cases.
1021 if cSeconds < 60:
1022 return '%ss' % (cSeconds,);
1023 if cSeconds < 3600:
1024 cMins = cSeconds / 60;
1025 cSecs = cSeconds % 60;
1026 if cSecs == 0:
1027 return '%sm' % (cMins,);
1028 return '%sm %ss' % (cMins, cSecs,);
1029
1030 # Generic and a bit slower.
1031 cDays = cSeconds / 86400;
1032 cSeconds %= 86400;
1033 cHours = cSeconds / 3600;
1034 cSeconds %= 3600;
1035 cMins = cSeconds / 60;
1036 cSecs = cSeconds % 60;
1037 sRet = '';
1038 if cDays > 0:
1039 sRet = '%sd ' % (cDays,);
1040 if cHours > 0:
1041 sRet += '%sh ' % (cHours,);
1042 if cMins > 0:
1043 sRet += '%sm ' % (cMins,);
1044 if cSecs > 0:
1045 sRet += '%ss ' % (cSecs,);
1046 assert len(sRet) > 0; assert sRet[-1] == ' ';
1047 return sRet[:-1];
1048
1049def formatIntervalSeconds2(oSeconds):
1050 """
1051 Flexible input version of formatIntervalSeconds for use in WUI forms where
1052 data is usually already string form.
1053 """
1054 if isinstance(oSeconds, int) or isinstance(oSeconds, long):
1055 return formatIntervalSeconds(oSeconds);
1056 if not isString(oSeconds):
1057 try:
1058 lSeconds = long(oSeconds);
1059 except:
1060 pass;
1061 else:
1062 if lSeconds >= 0:
1063 return formatIntervalSeconds2(lSeconds);
1064 return oSeconds;
1065
1066def parseIntervalSeconds(sString):
1067 """
1068 Reverse of formatIntervalSeconds.
1069
1070 Returns (cSeconds, sError), where sError is None on success.
1071 """
1072
1073 # We might given non-strings, just return them without any fuss.
1074 if not isString(sString):
1075 if isinstance(sString, int) or isinstance(sString, long) or sString is None:
1076 return (sString, None);
1077 ## @todo time/date objects?
1078 return (int(sString), None);
1079
1080 # Strip it and make sure it's not empty.
1081 sString = sString.strip();
1082 if len(sString) == 0:
1083 return (0, 'Empty interval string.');
1084
1085 #
1086 # Split up the input into a list of 'valueN, unitN, ...'.
1087 #
1088 # Don't want to spend too much time trying to make re.split do exactly what
1089 # I need here, so please forgive the extra pass I'm making here.
1090 #
1091 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1092 asParts = [];
1093 for sPart in asRawParts:
1094 sPart = sPart.strip();
1095 if len(sPart) > 0:
1096 asParts.append(sPart);
1097 if len(asParts) == 0:
1098 return (0, 'Empty interval string or something?');
1099
1100 #
1101 # Process them one or two at the time.
1102 #
1103 cSeconds = 0;
1104 asErrors = [];
1105 i = 0;
1106 while i < len(asParts):
1107 sNumber = asParts[i];
1108 i += 1;
1109 if sNumber.isdigit():
1110 iNumber = int(sNumber);
1111
1112 sUnit = 's';
1113 if i < len(asParts) and not asParts[i].isdigit():
1114 sUnit = asParts[i];
1115 i += 1;
1116
1117 sUnitLower = sUnit.lower();
1118 if sUnitLower in [ 's', 'se', 'sec', 'second', 'seconds' ]:
1119 pass;
1120 elif sUnitLower in [ 'm', 'mi', 'min', 'minute', 'minutes' ]:
1121 iNumber *= 60;
1122 elif sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1123 iNumber *= 3600;
1124 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1125 iNumber *= 86400;
1126 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1127 iNumber *= 7 * 86400;
1128 else:
1129 asErrors.append('Unknown unit "%s".' % (sUnit,));
1130 cSeconds += iNumber;
1131 else:
1132 asErrors.append('Bad number "%s".' % (sNumber,));
1133 return (cSeconds, None if len(asErrors) == 0 else ' '.join(asErrors));
1134
1135def formatIntervalHours(cHours):
1136 """ Format a hours interval into a nice 1w 2d 1h string. """
1137 # Simple special cases.
1138 if cHours < 24:
1139 return '%sh' % (cHours,);
1140
1141 # Generic and a bit slower.
1142 cWeeks = cHours / (7 * 24);
1143 cHours %= 7 * 24;
1144 cDays = cHours / 24;
1145 cHours %= 24;
1146 sRet = '';
1147 if cWeeks > 0:
1148 sRet = '%sw ' % (cWeeks,);
1149 if cDays > 0:
1150 sRet = '%sd ' % (cDays,);
1151 if cHours > 0:
1152 sRet += '%sh ' % (cHours,);
1153 assert len(sRet) > 0; assert sRet[-1] == ' ';
1154 return sRet[:-1];
1155
1156def parseIntervalHours(sString):
1157 """
1158 Reverse of formatIntervalHours.
1159
1160 Returns (cHours, sError), where sError is None on success.
1161 """
1162
1163 # We might given non-strings, just return them without any fuss.
1164 if not isString(sString):
1165 if isinstance(sString, int) or isinstance(sString, long) or sString is None:
1166 return (sString, None);
1167 ## @todo time/date objects?
1168 return (int(sString), None);
1169
1170 # Strip it and make sure it's not empty.
1171 sString = sString.strip();
1172 if len(sString) == 0:
1173 return (0, 'Empty interval string.');
1174
1175 #
1176 # Split up the input into a list of 'valueN, unitN, ...'.
1177 #
1178 # Don't want to spend too much time trying to make re.split do exactly what
1179 # I need here, so please forgive the extra pass I'm making here.
1180 #
1181 asRawParts = re.split(r'\s*([0-9]+)\s*([^0-9,;]*)[\s,;]*', sString);
1182 asParts = [];
1183 for sPart in asRawParts:
1184 sPart = sPart.strip();
1185 if len(sPart) > 0:
1186 asParts.append(sPart);
1187 if len(asParts) == 0:
1188 return (0, 'Empty interval string or something?');
1189
1190 #
1191 # Process them one or two at the time.
1192 #
1193 cHours = 0;
1194 asErrors = [];
1195 i = 0;
1196 while i < len(asParts):
1197 sNumber = asParts[i];
1198 i += 1;
1199 if sNumber.isdigit():
1200 iNumber = int(sNumber);
1201
1202 sUnit = 'h';
1203 if i < len(asParts) and not asParts[i].isdigit():
1204 sUnit = asParts[i];
1205 i += 1;
1206
1207 sUnitLower = sUnit.lower();
1208 if sUnitLower in [ 'h', 'ho', 'hou', 'hour', 'hours' ]:
1209 pass;
1210 elif sUnitLower in [ 'd', 'da', 'day', 'days' ]:
1211 iNumber *= 24;
1212 elif sUnitLower in [ 'w', 'week', 'weeks' ]:
1213 iNumber *= 7 * 24;
1214 else:
1215 asErrors.append('Unknown unit "%s".' % (sUnit,));
1216 cHours += iNumber;
1217 else:
1218 asErrors.append('Bad number "%s".' % (sNumber,));
1219 return (cHours, None if len(asErrors) == 0 else ' '.join(asErrors));
1220
1221
1222#
1223# Introspection.
1224#
1225
1226def getCallerName(oFrame=None, iFrame=2):
1227 """
1228 Returns the name of the caller's caller.
1229 """
1230 if oFrame is None:
1231 try:
1232 raise Exception();
1233 except:
1234 oFrame = sys.exc_info()[2].tb_frame.f_back;
1235 while iFrame > 1:
1236 if oFrame is not None:
1237 oFrame = oFrame.f_back;
1238 iFrame = iFrame - 1;
1239 if oFrame is not None:
1240 sName = '%s:%u' % (oFrame.f_code.co_name, oFrame.f_lineno);
1241 return sName;
1242 return "unknown";
1243
1244
1245def getXcptInfo(cFrames = 1):
1246 """
1247 Gets text detailing the exception. (Good for logging.)
1248 Returns list of info strings.
1249 """
1250
1251 #
1252 # Try get exception info.
1253 #
1254 try:
1255 oType, oValue, oTraceback = sys.exc_info();
1256 except:
1257 oType = oValue = oTraceback = None;
1258 if oType is not None:
1259
1260 #
1261 # Try format the info
1262 #
1263 asRet = [];
1264 try:
1265 try:
1266 asRet = asRet + traceback.format_exception_only(oType, oValue);
1267 asTraceBack = traceback.format_tb(oTraceback);
1268 if cFrames is not None and cFrames <= 1:
1269 asRet.append(asTraceBack[-1]);
1270 else:
1271 asRet.append('Traceback:')
1272 for iFrame in range(min(cFrames, len(asTraceBack))):
1273 asRet.append(asTraceBack[-iFrame - 1]);
1274 asRet.append('Stack:')
1275 asRet = asRet + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames);
1276 except:
1277 asRet.append('internal-error: Hit exception #2! %s' % (traceback.format_exc(),));
1278
1279 if len(asRet) == 0:
1280 asRet.append('No exception info...');
1281 except:
1282 asRet.append('internal-error: Hit exception! %s' % (traceback.format_exc(),));
1283 else:
1284 asRet = ['Couldn\'t find exception traceback.'];
1285 return asRet;
1286
1287
1288#
1289# TestSuite stuff.
1290#
1291
1292def isRunningFromCheckout(cScriptDepth = 1):
1293 """
1294 Checks if we're running from the SVN checkout or not.
1295 """
1296
1297 try:
1298 sFile = __file__;
1299 cScriptDepth = 1;
1300 except:
1301 sFile = sys.argv[0];
1302
1303 sDir = os.path.abspath(sFile);
1304 while cScriptDepth >= 0:
1305 sDir = os.path.dirname(sDir);
1306 if os.path.exists(os.path.join(sDir, 'Makefile.kmk')) \
1307 or os.path.exists(os.path.join(sDir, 'Makefile.kup')):
1308 return True;
1309 cScriptDepth -= 1;
1310
1311 return False;
1312
1313
1314#
1315# Bourne shell argument fun.
1316#
1317
1318
1319def argsSplit(sCmdLine):
1320 """
1321 Given a bourne shell command line invocation, split it up into arguments
1322 assuming IFS is space.
1323 Returns None on syntax error.
1324 """
1325 ## @todo bourne shell argument parsing!
1326 return sCmdLine.split(' ');
1327
1328def argsGetFirst(sCmdLine):
1329 """
1330 Given a bourne shell command line invocation, get return the first argument
1331 assuming IFS is space.
1332 Returns None on invalid syntax, otherwise the parsed and unescaped argv[0] string.
1333 """
1334 asArgs = argsSplit(sCmdLine);
1335 if asArgs is None or len(asArgs) == 0:
1336 return None;
1337
1338 return asArgs[0];
1339
1340#
1341# String helpers.
1342#
1343
1344def stricmp(sFirst, sSecond):
1345 """
1346 Compares to strings in an case insensitive fashion.
1347
1348 Python doesn't seem to have any way of doing the correctly, so this is just
1349 an approximation using lower.
1350 """
1351 if sFirst == sSecond:
1352 return 0;
1353 sLower1 = sFirst.lower();
1354 sLower2 = sSecond.lower();
1355 if sLower1 == sLower2:
1356 return 0;
1357 if sLower1 < sLower2:
1358 return -1;
1359 return 1;
1360
1361
1362#
1363# Misc.
1364#
1365
1366def versionCompare(sVer1, sVer2):
1367 """
1368 Compares to version strings in a fashion similar to RTStrVersionCompare.
1369 """
1370
1371 ## @todo implement me!!
1372
1373 if sVer1 == sVer2:
1374 return 0;
1375 if sVer1 < sVer2:
1376 return -1;
1377 return 1;
1378
1379
1380def formatNumber(lNum, sThousandSep = ' '):
1381 """
1382 Formats a decimal number with pretty separators.
1383 """
1384 sNum = str(lNum);
1385 sRet = sNum[-3:];
1386 off = len(sNum) - 3;
1387 while off > 0:
1388 off -= 3;
1389 sRet = sNum[(off if off >= 0 else 0):(off + 3)] + sThousandSep + sRet;
1390 return sRet;
1391
1392
1393def formatNumberNbsp(lNum):
1394 """
1395 Formats a decimal number with pretty separators.
1396 """
1397 sRet = formatNumber(lNum);
1398 return unicode(sRet).replace(' ', u'\u00a0');
1399
1400
1401def isString(oString):
1402 """
1403 Checks if the object is a string object, hiding difference between python 2 and 3.
1404
1405 Returns True if it's a string of some kind.
1406 Returns False if not.
1407 """
1408 if sys.version_info[0] >= 3:
1409 return isinstance(oString, str);
1410 return isinstance(oString, basestring);
1411
1412
1413def hasNonAsciiCharacters(sText):
1414 """
1415 Returns True is specified string has non-ASCII characters.
1416 """
1417 sTmp = unicode(sText, errors='ignore') if isinstance(sText, str) else sText
1418 return not all(ord(cChar) < 128 for cChar in sTmp)
1419
1420
1421def chmodPlusX(sFile):
1422 """
1423 Makes the specified file or directory executable.
1424 Returns success indicator, no exceptions.
1425
1426 Note! Symbolic links are followed and the target will be changed.
1427 """
1428 try:
1429 oStat = os.stat(sFile);
1430 except:
1431 return False;
1432 try:
1433 os.chmod(sFile, oStat.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH);
1434 except:
1435 return False;
1436 return True;
1437
1438
1439def unpackFile(sArchive, sDstDir, fnLog, fnError = None):
1440 """
1441 Unpacks the given file if it has a know archive extension, otherwise do
1442 nothing.
1443
1444 Returns list of the extracted files (full path) on success.
1445 Returns empty list if not a supported archive format.
1446 Returns None on failure. Raises no exceptions.
1447 """
1448 if fnError is None:
1449 fnError = fnLog;
1450
1451 asMembers = [];
1452
1453 sBaseNameLower = os.path.basename(sArchive).lower();
1454 if sBaseNameLower.endswith('.zip'):
1455 fnLog('Unzipping "%s" to "%s"...' % (sArchive, sDstDir));
1456 try:
1457 oZipFile = zipfile.ZipFile(sArchive, 'r')
1458 asMembers = oZipFile.namelist();
1459 for sMember in asMembers:
1460 if sMember.endswith('/'):
1461 os.makedirs(os.path.join(sDstDir, sMember.replace('/', os.path.sep)), 0775);
1462 else:
1463 oZipFile.extract(sMember, sDstDir);
1464 oZipFile.close();
1465 except Exception, oXcpt:
1466 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
1467 return None;
1468
1469 elif sBaseNameLower.endswith('.tar') \
1470 or sBaseNameLower.endswith('.tar.gz') \
1471 or sBaseNameLower.endswith('.tgz') \
1472 or sBaseNameLower.endswith('.tar.bz2'):
1473 fnLog('Untarring "%s" to "%s"...' % (sArchive, sDstDir));
1474 try:
1475 oTarFile = tarfile.open(sArchive, 'r:*');
1476 asMembers = [oTarInfo.name for oTarInfo in oTarFile.getmembers()];
1477 oTarFile.extractall(sDstDir);
1478 oTarFile.close();
1479 except Exception, oXcpt:
1480 fnError('Error unpacking "%s" into "%s": %s' % (sArchive, sDstDir, oXcpt));
1481 return None;
1482
1483 else:
1484 fnLog('Not unpacking "%s".' % (sArchive,));
1485 return [];
1486
1487 #
1488 # Change asMembers to local slashes and prefix with path.
1489 #
1490 asMembersRet = [];
1491 for sMember in asMembers:
1492 asMembersRet.append(os.path.join(sDstDir, sMember.replace('/', os.path.sep)));
1493
1494 return asMembersRet;
1495
1496
1497def getDiskUsage(sPath):
1498 """
1499 Get free space of a partition that corresponds to specified sPath in MB.
1500
1501 Returns partition free space value in MB.
1502 """
1503 if platform.system() == 'Windows':
1504 oCTypeFreeSpace = ctypes.c_ulonglong(0);
1505 ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(sPath), None, None,
1506 ctypes.pointer(oCTypeFreeSpace));
1507 cbFreeSpace = oCTypeFreeSpace.value;
1508 else:
1509 oStats = os.statvfs(sPath); # pylint: disable=E1101
1510 cbFreeSpace = long(oStats.f_frsize) * oStats.f_bfree;
1511
1512 # Convert to MB
1513 cMbFreeSpace = long(cbFreeSpace) / (1024 * 1024);
1514
1515 return cMbFreeSpace;
1516
1517
1518#
1519# Unit testing.
1520#
1521
1522# pylint: disable=C0111
1523class BuildCategoryDataTestCase(unittest.TestCase):
1524 def testIntervalSeconds(self):
1525 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(3600)), (3600, None));
1526 self.assertEqual(parseIntervalSeconds(formatIntervalSeconds(1209438593)), (1209438593, None));
1527 self.assertEqual(parseIntervalSeconds('123'), (123, None));
1528 self.assertEqual(parseIntervalSeconds(123), (123, None));
1529 self.assertEqual(parseIntervalSeconds(99999999999), (99999999999, None));
1530 self.assertEqual(parseIntervalSeconds(''), (0, 'Empty interval string.'));
1531 self.assertEqual(parseIntervalSeconds('1X2'), (3, 'Unknown unit "X".'));
1532 self.assertEqual(parseIntervalSeconds('1 Y3'), (4, 'Unknown unit "Y".'));
1533 self.assertEqual(parseIntervalSeconds('1 Z 4'), (5, 'Unknown unit "Z".'));
1534 self.assertEqual(parseIntervalSeconds('1 hour 2m 5second'), (3725, None));
1535 self.assertEqual(parseIntervalSeconds('1 hour,2m ; 5second'), (3725, None));
1536
1537if __name__ == '__main__':
1538 unittest.main();
1539 # not reached.
1540
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