VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testdriver/vboxinstaller.py@ 93115

Last change on this file since 93115 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 46.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5VirtualBox Installer Wrapper Driver.
6
7This installs VirtualBox, starts a sub driver which does the real testing,
8and then uninstall VirtualBox afterwards. This reduces the complexity of the
9other VBox test drivers.
10"""
11
12__copyright__ = \
13"""
14Copyright (C) 2010-2022 Oracle Corporation
15
16This file is part of VirtualBox Open Source Edition (OSE), as
17available from http://www.virtualbox.org. This file is free software;
18you can redistribute it and/or modify it under the terms of the GNU
19General Public License (GPL) as published by the Free Software
20Foundation, in version 2 as it comes in the "COPYING" file of the
21VirtualBox OSE distribution. VirtualBox OSE is distributed in the
22hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
23
24The contents of this file may alternatively be used under the terms
25of the Common Development and Distribution License Version 1.0
26(CDDL) only, as it comes in the "COPYING.CDDL" file of the
27VirtualBox OSE distribution, in which case the provisions of the
28CDDL are applicable instead of those of the GPL.
29
30You may elect to license modified versions of this file under the
31terms and conditions of either the GPL or the CDDL or both.
32"""
33__version__ = "$Revision: 93115 $"
34
35
36# Standard Python imports.
37import os
38import sys
39import re
40#import socket
41import tempfile
42import time
43
44# Only the main script needs to modify the path.
45try: __file__
46except: __file__ = sys.argv[0];
47g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)));
48sys.path.append(g_ksValidationKitDir);
49
50# Validation Kit imports.
51from common import utils, webutils;
52from common.constants import rtexitcode;
53from testdriver import reporter;
54from testdriver.base import TestDriverBase;
55
56
57
58class VBoxInstallerTestDriver(TestDriverBase):
59 """
60 Implementation of a top level test driver.
61 """
62
63
64 ## State file indicating that we've skipped installation.
65 ksVar_Skipped = 'vboxinstaller-skipped';
66
67
68 def __init__(self):
69 TestDriverBase.__init__(self);
70 self._asSubDriver = []; # The sub driver and it's arguments.
71 self._asBuildUrls = []; # The URLs passed us on the command line.
72 self._asBuildFiles = []; # The downloaded file names.
73 self._fUnpackedBuildFiles = False;
74 self._fAutoInstallPuelExtPack = True;
75 self._fKernelDrivers = True;
76
77 #
78 # Base method we override
79 #
80
81 def showUsage(self):
82 rc = TestDriverBase.showUsage(self);
83 # 0 1 2 3 4 5 6 7 8
84 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890
85 reporter.log('');
86 reporter.log('vboxinstaller Options:');
87 reporter.log(' --vbox-build <url[,url2[,...]]>');
88 reporter.log(' Comma separated list of URL to file to download and install or/and');
89 reporter.log(' unpack. URLs without a schema are assumed to be files on the');
90 reporter.log(' build share and will be copied off it.');
91 reporter.log(' --no-puel-extpack');
92 reporter.log(' Indicates that the PUEL extension pack should not be installed if found.');
93 reporter.log(' The default is to install it if found in the vbox-build.');
94 reporter.log(' --no-kernel-drivers');
95 reporter.log(' Indicates that the kernel drivers should not be installed on platforms where this is supported.');
96 reporter.log(' The default is to install them.');
97 reporter.log(' --');
98 reporter.log(' Indicates the end of our parameters and the start of the sub');
99 reporter.log(' testdriver and its arguments.');
100 return rc;
101
102 def parseOption(self, asArgs, iArg):
103 """
104 Parse our arguments.
105 """
106 if asArgs[iArg] == '--':
107 # End of our parameters and start of the sub driver invocation.
108 iArg = self.requireMoreArgs(1, asArgs, iArg);
109 assert not self._asSubDriver;
110 self._asSubDriver = asArgs[iArg:];
111 self._asSubDriver[0] = self._asSubDriver[0].replace('/', os.path.sep);
112 iArg = len(asArgs) - 1;
113 elif asArgs[iArg] == '--vbox-build':
114 # List of files to copy/download and install.
115 iArg = self.requireMoreArgs(1, asArgs, iArg);
116 self._asBuildUrls = asArgs[iArg].split(',');
117 elif asArgs[iArg] == '--no-puel-extpack':
118 self._fAutoInstallPuelExtPack = False;
119 elif asArgs[iArg] == '--puel-extpack':
120 self._fAutoInstallPuelExtPack = True;
121 elif asArgs[iArg] == '--no-kernel-drivers':
122 self._fKernelDrivers = False;
123 elif asArgs[iArg] == '--kernel-drivers':
124 self._fKernelDrivers = True;
125 else:
126 return TestDriverBase.parseOption(self, asArgs, iArg);
127 return iArg + 1;
128
129 def completeOptions(self):
130 #
131 # Check that we've got what we need.
132 #
133 if not self._asBuildUrls:
134 reporter.error('No build files specified ("--vbox-build file1[,file2[...]]")');
135 return False;
136 if not self._asSubDriver:
137 reporter.error('No sub testdriver specified. (" -- test/stuff/tdStuff1.py args")');
138 return False;
139
140 #
141 # Construct _asBuildFiles as an array parallel to _asBuildUrls.
142 #
143 for sUrl in self._asBuildUrls:
144 sDstFile = os.path.join(self.sScratchPath, webutils.getFilename(sUrl));
145 self._asBuildFiles.append(sDstFile);
146
147 return TestDriverBase.completeOptions(self);
148
149 def actionExtract(self):
150 reporter.error('vboxinstall does not support extracting resources, you have to do that using the sub testdriver.');
151 return False;
152
153 def actionCleanupBefore(self):
154 """
155 Kills all VBox process we see.
156
157 This is only supposed to execute on a testbox so we don't need to go
158 all complicated wrt other users.
159 """
160 return self._killAllVBoxProcesses();
161
162 def actionConfig(self):
163 """
164 Install VBox and pass on the configure request to the sub testdriver.
165 """
166 fRc = self._installVBox();
167 if fRc is None:
168 self._persistentVarSet(self.ksVar_Skipped, 'true');
169 self.fBadTestbox = True;
170 else:
171 self._persistentVarUnset(self.ksVar_Skipped);
172
173 ## @todo vbox.py still has bugs preventing us from invoking it seperately with each action.
174 if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions:
175 fRc = self._executeSubDriver([ 'verify', ]);
176 if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions:
177 fRc = self._executeSubDriver([ 'config', ], fPreloadASan = True);
178 return fRc;
179
180 def actionExecute(self):
181 """
182 Execute the sub testdriver.
183 """
184 return self._executeSubDriver(self.asActions, fPreloadASan = True);
185
186 def actionCleanupAfter(self):
187 """
188 Forward this to the sub testdriver, then uninstall VBox.
189 """
190 fRc = True;
191 if 'execute' not in self.asActions and 'all' not in self.asActions:
192 fRc = self._executeSubDriver([ 'cleanup-after', ], fMaySkip = False);
193
194 if not self._killAllVBoxProcesses():
195 fRc = False;
196
197 if not self._uninstallVBox(self._persistentVarExists(self.ksVar_Skipped)):
198 fRc = False;
199
200 if utils.getHostOs() == 'darwin':
201 self._darwinUnmountDmg(fIgnoreError = True); # paranoia
202
203 if not TestDriverBase.actionCleanupAfter(self):
204 fRc = False;
205
206 return fRc;
207
208
209 def actionAbort(self):
210 """
211 Forward this to the sub testdriver first, then wipe all VBox like
212 processes, and finally do the pid file processing (again).
213 """
214 fRc1 = self._executeSubDriver([ 'abort', ], fMaySkip = False, fPreloadASan = True);
215 fRc2 = self._killAllVBoxProcesses();
216 fRc3 = TestDriverBase.actionAbort(self);
217 return fRc1 and fRc2 and fRc3;
218
219
220 #
221 # Persistent variables.
222 #
223 ## @todo integrate into the base driver. Persistent accross scratch wipes?
224
225 def __persistentVarCalcName(self, sVar):
226 """Returns the (full) filename for the given persistent variable."""
227 assert re.match(r'^[a-zA-Z0-9_-]*$', sVar) is not None;
228 return os.path.join(self.sScratchPath, 'persistent-%s.var' % (sVar,));
229
230 def _persistentVarSet(self, sVar, sValue = ''):
231 """
232 Sets a persistent variable.
233
234 Returns True on success, False + reporter.error on failure.
235
236 May raise exception if the variable name is invalid or something
237 unexpected happens.
238 """
239 sFull = self.__persistentVarCalcName(sVar);
240 try:
241 oFile = open(sFull, 'w');
242 if sValue:
243 oFile.write(sValue.encode('utf-8'));
244 oFile.close();
245 except:
246 reporter.errorXcpt('Error creating "%s"' % (sFull,));
247 return False;
248 return True;
249
250 def _persistentVarUnset(self, sVar):
251 """
252 Unsets a persistent variable.
253
254 Returns True on success, False + reporter.error on failure.
255
256 May raise exception if the variable name is invalid or something
257 unexpected happens.
258 """
259 sFull = self.__persistentVarCalcName(sVar);
260 if os.path.exists(sFull):
261 try:
262 os.unlink(sFull);
263 except:
264 reporter.errorXcpt('Error unlinking "%s"' % (sFull,));
265 return False;
266 return True;
267
268 def _persistentVarExists(self, sVar):
269 """
270 Checks if a persistent variable exists.
271
272 Returns true/false.
273
274 May raise exception if the variable name is invalid or something
275 unexpected happens.
276 """
277 return os.path.exists(self.__persistentVarCalcName(sVar));
278
279 def _persistentVarGet(self, sVar):
280 """
281 Gets the value of a persistent variable.
282
283 Returns variable value on success.
284 Returns None if the variable doesn't exist or if an
285 error (reported) occured.
286
287 May raise exception if the variable name is invalid or something
288 unexpected happens.
289 """
290 sFull = self.__persistentVarCalcName(sVar);
291 if not os.path.exists(sFull):
292 return None;
293 try:
294 oFile = open(sFull, 'r');
295 sValue = oFile.read().decode('utf-8');
296 oFile.close();
297 except:
298 reporter.errorXcpt('Error creating "%s"' % (sFull,));
299 return None;
300 return sValue;
301
302
303 #
304 # Helpers.
305 #
306
307 def _killAllVBoxProcesses(self):
308 """
309 Kills all virtual box related processes we find in the system.
310 """
311 sHostOs = utils.getHostOs();
312 asDebuggers = [ 'cdb', 'windbg', ] if sHostOs == 'windows' else [ 'gdb', 'gdb-i386-apple-darwin', 'lldb' ];
313
314 for iIteration in range(22):
315 # Gather processes to kill.
316 aoTodo = [];
317 aoDebuggers = [];
318 for oProcess in utils.processListAll():
319 sBase = oProcess.getBaseImageNameNoExeSuff();
320 if sBase is None:
321 continue;
322 sBase = sBase.lower();
323 if sBase in [ 'vboxsvc', 'vboxsds', 'virtualbox', 'virtualboxvm', 'vboxheadless', 'vboxmanage', 'vboxsdl',
324 'vboxwebsrv', 'vboxautostart', 'vboxballoonctrl', 'vboxbfe', 'vboxextpackhelperapp', 'vboxnetdhcp',
325 'vboxnetnat', 'vboxnetadpctl', 'vboxtestogl', 'vboxtunctl', 'vboxvmmpreload', 'vboxxpcomipcd', ]:
326 aoTodo.append(oProcess);
327 if sBase.startswith('virtualbox-') and sBase.endswith('-multiarch.exe'):
328 aoTodo.append(oProcess);
329 if sBase in asDebuggers:
330 aoDebuggers.append(oProcess);
331 if iIteration in [0, 21]:
332 reporter.log('Warning: debugger running: %s (%s %s)' % (oProcess.iPid, sBase, oProcess.asArgs));
333 if not aoTodo:
334 return True;
335
336 # Are any of the debugger processes hooked up to a VBox process?
337 if sHostOs == 'windows':
338 # On demand debugging windows: windbg -p <decimal-pid> -e <decimal-event> -g
339 for oDebugger in aoDebuggers:
340 for oProcess in aoTodo:
341 # The whole command line is asArgs[0] here. Fix if that changes.
342 if oDebugger.asArgs and oDebugger.asArgs[0].find('-p %s ' % (oProcess.iPid,)) >= 0:
343 aoTodo.append(oDebugger);
344 break;
345 else:
346 for oDebugger in aoDebuggers:
347 for oProcess in aoTodo:
348 # Simplistic approach: Just check for argument equaling our pid.
349 if oDebugger.asArgs and ('%s' % oProcess.iPid) in oDebugger.asArgs:
350 aoTodo.append(oDebugger);
351 break;
352
353 # Kill.
354 for oProcess in aoTodo:
355 reporter.log('Loop #%d - Killing %s (%s, uid=%s)'
356 % ( iIteration, oProcess.iPid, oProcess.sImage if oProcess.sName is None else oProcess.sName,
357 oProcess.iUid, ));
358 if not utils.processKill(oProcess.iPid) \
359 and sHostOs != 'windows' \
360 and utils.processExists(oProcess.iPid):
361 # Many of the vbox processes are initially set-uid-to-root and associated debuggers are running
362 # via sudo, so we might not be able to kill them unless we sudo and use /bin/kill.
363 try: utils.sudoProcessCall(['/bin/kill', '-9', '%s' % (oProcess.iPid,)]);
364 except: reporter.logXcpt();
365
366 # Check if they're all dead like they should be.
367 time.sleep(0.1);
368 for oProcess in aoTodo:
369 if utils.processExists(oProcess.iPid):
370 time.sleep(2);
371 break;
372
373 return False;
374
375 def _executeSync(self, asArgs, fMaySkip = False):
376 """
377 Executes a child process synchronously.
378
379 Returns True if the process executed successfully and returned 0.
380 Returns None if fMaySkip is true and the child exits with RTEXITCODE_SKIPPED.
381 Returns False for all other cases.
382 """
383 reporter.log('Executing: %s' % (asArgs, ));
384 reporter.flushall();
385 try:
386 iRc = utils.processCall(asArgs, shell = False, close_fds = False);
387 except:
388 reporter.errorXcpt();
389 return False;
390 reporter.log('Exit code: %s (%s)' % (iRc, asArgs));
391 if fMaySkip and iRc == rtexitcode.RTEXITCODE_SKIPPED:
392 return None;
393 return iRc == 0;
394
395 def _sudoExecuteSync(self, asArgs):
396 """
397 Executes a sudo child process synchronously.
398 Returns a tuple [True, 0] if the process executed successfully
399 and returned 0, otherwise [False, rc] is returned.
400 """
401 reporter.log('Executing [sudo]: %s' % (asArgs, ));
402 reporter.flushall();
403 iRc = 0;
404 try:
405 iRc = utils.sudoProcessCall(asArgs, shell = False, close_fds = False);
406 except:
407 reporter.errorXcpt();
408 return (False, 0);
409 reporter.log('Exit code [sudo]: %s (%s)' % (iRc, asArgs));
410 return (iRc == 0, iRc);
411
412 def _findASanLibsForASanBuild(self):
413 """
414 Returns a list of (address) santizier related libraries to preload
415 when launching the sub driver.
416 Returns empty list for non-asan builds or on platforms where this isn't needed.
417 """
418 # Note! We include libasan.so.X in the VBoxAll tarball for asan builds, so we
419 # can use its presence both to detect an 'asan' build and to return it.
420 # Only the libasan.so.X library needs preloading at present.
421 if self.sHost in ('linux',):
422 sLibASan = self._findFile(r'libasan\.so\..*');
423 if sLibASan:
424 return [sLibASan,];
425 return [];
426
427 def _executeSubDriver(self, asActions, fMaySkip = True, fPreloadASan = True):
428 """
429 Execute the sub testdriver with the specified action.
430 """
431 asArgs = list(self._asSubDriver)
432 asArgs.append('--no-wipe-clean');
433 asArgs.extend(asActions);
434
435 asASanLibs = [];
436 if fPreloadASan:
437 asASanLibs = self._findASanLibsForASanBuild();
438 if asASanLibs:
439 os.environ['LD_PRELOAD'] = ':'.join(asASanLibs);
440 os.environ['LSAN_OPTIONS'] = 'detect_leaks=0'; # We don't want python leaks. vbox.py disables this.
441
442 # Because of https://github.com/google/sanitizers/issues/856 we must try use setarch to disable
443 # address space randomization.
444
445 reporter.log('LD_PRELOAD...')
446 if utils.getHostArch() == 'amd64':
447 sSetArch = utils.whichProgram('setarch');
448 reporter.log('sSetArch=%s' % (sSetArch,));
449 if sSetArch:
450 asArgs = [ sSetArch, 'x86_64', '-R', sys.executable ] + asArgs;
451 reporter.log('asArgs=%s' % (asArgs,));
452
453 rc = self._executeSync(asArgs, fMaySkip = fMaySkip);
454
455 del os.environ['LSAN_OPTIONS'];
456 del os.environ['LD_PRELOAD'];
457 return rc;
458
459 return self._executeSync(asArgs, fMaySkip = fMaySkip);
460
461 def _maybeUnpackArchive(self, sMaybeArchive, fNonFatal = False):
462 """
463 Attempts to unpack the given build file.
464 Updates _asBuildFiles.
465 Returns True/False. No exceptions.
466 """
467 def unpackFilter(sMember):
468 # type: (string) -> bool
469 """ Skips debug info. """
470 sLower = sMember.lower();
471 if sLower.endswith('.pdb'):
472 return False;
473 return True;
474
475 asMembers = utils.unpackFile(sMaybeArchive, self.sScratchPath, reporter.log,
476 reporter.log if fNonFatal else reporter.error,
477 fnFilter = unpackFilter);
478 if asMembers is None:
479 return False;
480 self._asBuildFiles.extend(asMembers);
481 return True;
482
483
484 def _installVBox(self):
485 """
486 Download / copy the build files into the scratch area and install them.
487 """
488 reporter.testStart('Installing VirtualBox');
489 reporter.log('CWD=%s' % (os.getcwd(),)); # curious
490
491 #
492 # Download the build files.
493 #
494 for i in range(len(self._asBuildUrls)):
495 if webutils.downloadFile(self._asBuildUrls[i], self._asBuildFiles[i],
496 self.sBuildPath, reporter.log, reporter.log) is not True:
497 reporter.testDone(fSkipped = True);
498 return None; # Failed to get binaries, probably deleted. Skip the test run.
499
500 #
501 # Unpack anything we know what is and append it to the build files
502 # list. This allows us to use VBoxAll*.tar.gz files.
503 #
504 for sFile in list(self._asBuildFiles): # Note! We copy the list as _maybeUnpackArchive updates it.
505 if self._maybeUnpackArchive(sFile, fNonFatal = True) is not True:
506 reporter.testDone(fSkipped = True);
507 return None; # Failed to unpack. Probably local error, like busy
508 # DLLs on windows, no reason for failing the build.
509 self._fUnpackedBuildFiles = True;
510
511 #
512 # Go to system specific installation code.
513 #
514 sHost = utils.getHostOs()
515 if sHost == 'darwin': fRc = self._installVBoxOnDarwin();
516 elif sHost == 'linux': fRc = self._installVBoxOnLinux();
517 elif sHost == 'solaris': fRc = self._installVBoxOnSolaris();
518 elif sHost == 'win': fRc = self._installVBoxOnWindows();
519 else:
520 reporter.error('Unsupported host "%s".' % (sHost,));
521 if fRc is False:
522 reporter.testFailure('Installation error.');
523 elif fRc is not True:
524 reporter.log('Seems installation was skipped. Old version lurking behind? Not the fault of this build/test run!');
525
526 #
527 # Install the extension pack.
528 #
529 if fRc is True and self._fAutoInstallPuelExtPack:
530 fRc = self._installExtPack();
531 if fRc is False:
532 reporter.testFailure('Extension pack installation error.');
533
534 # Some debugging...
535 try:
536 cMbFreeSpace = utils.getDiskUsage(self.sScratchPath);
537 reporter.log('Disk usage after VBox install: %d MB available at %s' % (cMbFreeSpace, self.sScratchPath,));
538 except:
539 reporter.logXcpt('Unable to get disk free space. Ignored. Continuing.');
540
541 reporter.testDone(fRc is None);
542 return fRc;
543
544 def _uninstallVBox(self, fIgnoreError = False):
545 """
546 Uninstall VirtualBox.
547 """
548 reporter.testStart('Uninstalling VirtualBox');
549
550 sHost = utils.getHostOs()
551 if sHost == 'darwin': fRc = self._uninstallVBoxOnDarwin();
552 elif sHost == 'linux': fRc = self._uninstallVBoxOnLinux();
553 elif sHost == 'solaris': fRc = self._uninstallVBoxOnSolaris(True);
554 elif sHost == 'win': fRc = self._uninstallVBoxOnWindows('uninstall');
555 else:
556 reporter.error('Unsupported host "%s".' % (sHost,));
557 if fRc is False and not fIgnoreError:
558 reporter.testFailure('Uninstallation failed.');
559
560 fRc2 = self._uninstallAllExtPacks();
561 if not fRc2 and fRc:
562 fRc = fRc2;
563
564 reporter.testDone(fSkipped = (fRc is None));
565 return fRc;
566
567 def _findFile(self, sRegExp, fMandatory = False):
568 """
569 Returns the first build file that matches the given regular expression
570 (basename only).
571
572 Returns None if no match was found, logging it as an error if
573 fMandatory is set.
574 """
575 oRegExp = re.compile(sRegExp);
576
577 reporter.log('_findFile: %s' % (sRegExp,));
578 for sFile in self._asBuildFiles:
579 if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sFile):
580 return sFile;
581
582 # If we didn't unpack the build files, search all the files in the scratch area:
583 if not self._fUnpackedBuildFiles:
584 for sDir, _, asFiles in os.walk(self.sScratchPath):
585 for sFile in asFiles:
586 #reporter.log('_findFile: considering %s' % (sFile,));
587 if oRegExp.match(sFile):
588 return os.path.join(sDir, sFile);
589
590 if fMandatory:
591 reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, self._asBuildFiles,));
592 return None;
593
594 def _waitForTestManagerConnectivity(self, cSecTimeout):
595 """
596 Check and wait for network connectivity to the test manager.
597
598 This is used with the windows installation and uninstallation since
599 these usually disrupts network connectivity when installing the filter
600 driver. If we proceed to quickly, we might finish the test at a time
601 when we cannot report to the test manager and thus end up with an
602 abandonded test error.
603 """
604 cSecElapsed = 0;
605 secStart = utils.timestampSecond();
606 while reporter.checkTestManagerConnection() is False:
607 cSecElapsed = utils.timestampSecond() - secStart;
608 if cSecElapsed >= cSecTimeout:
609 reporter.log('_waitForTestManagerConnectivity: Giving up after %u secs.' % (cSecTimeout,));
610 return False;
611 time.sleep(2);
612
613 if cSecElapsed > 0:
614 reporter.log('_waitForTestManagerConnectivity: Waited %s secs.' % (cSecTimeout,));
615 return True;
616
617
618 #
619 # Darwin (Mac OS X).
620 #
621
622 def _darwinDmgPath(self):
623 """ Returns the path to the DMG mount."""
624 return os.path.join(self.sScratchPath, 'DmgMountPoint');
625
626 def _darwinUnmountDmg(self, fIgnoreError):
627 """
628 Umount any DMG on at the default mount point.
629 """
630 sMountPath = self._darwinDmgPath();
631 if not os.path.exists(sMountPath):
632 return True;
633
634 # Unmount.
635 fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]);
636 if not fRc and not fIgnoreError:
637 # In case it's busy for some reason or another, just retry after a little delay.
638 for iTry in range(6):
639 time.sleep(5);
640 reporter.error('Retry #%s unmount DMT at %s' % (iTry + 1, sMountPath,));
641 fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]);
642 if fRc:
643 break;
644 if not fRc:
645 reporter.error('Failed to unmount DMG at %s' % (sMountPath,));
646
647 # Remove dir.
648 try:
649 os.rmdir(sMountPath);
650 except:
651 if not fIgnoreError:
652 reporter.errorXcpt('Failed to remove directory %s' % (sMountPath,));
653 return fRc;
654
655 def _darwinMountDmg(self, sDmg):
656 """
657 Mount the DMG at the default mount point.
658 """
659 self._darwinUnmountDmg(fIgnoreError = True)
660
661 sMountPath = self._darwinDmgPath();
662 if not os.path.exists(sMountPath):
663 try:
664 os.mkdir(sMountPath, 0o755);
665 except:
666 reporter.logXcpt();
667 return False;
668
669 return self._executeSync(['hdiutil', 'attach', '-readonly', '-mount', 'required', '-mountpoint', sMountPath, sDmg, ]);
670
671 def _generateWithoutKextsChoicesXmlOnDarwin(self):
672 """
673 Generates the choices XML when kernel drivers are disabled.
674 None is returned on failure.
675 """
676 sPath = os.path.join(self.sScratchPath, 'DarwinChoices.xml');
677 oFile = utils.openNoInherit(sPath, 'wt');
678 oFile.write('<?xml version="1.0" encoding="UTF-8"?>\n'
679 '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
680 '<plist version="1.0">\n'
681 '<array>\n'
682 ' <dict>\n'
683 ' <key>attributeSetting</key>\n'
684 ' <integer>0</integer>\n'
685 ' <key>choiceAttribute</key>\n'
686 ' <string>selected</string>\n'
687 ' <key>choiceIdentifier</key>\n'
688 ' <string>choiceVBoxKEXTs</string>\n'
689 ' </dict>\n'
690 '</array>\n'
691 '</plist>\n');
692 oFile.close();
693 return sPath;
694
695 def _installVBoxOnDarwin(self):
696 """ Installs VBox on Mac OS X."""
697 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
698 if sDmg is None:
699 return False;
700
701 # Mount the DMG.
702 fRc = self._darwinMountDmg(sDmg);
703 if fRc is not True:
704 return False;
705
706 # Uninstall any previous vbox version first.
707 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
708 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
709 if fRc is True:
710
711 # Install the package.
712 sPkg = os.path.join(self._darwinDmgPath(), 'VirtualBox.pkg');
713 if self._fKernelDrivers:
714 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, '-target', '/']);
715 else:
716 sChoicesXml = self._generateWithoutKextsChoicesXmlOnDarwin();
717 if sChoicesXml is not None:
718 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, \
719 '-applyChoiceChangesXML', sChoicesXml, '-target', '/']);
720 else:
721 fRc = False;
722
723 # Unmount the DMG and we're done.
724 if not self._darwinUnmountDmg(fIgnoreError = False):
725 fRc = False;
726 return fRc;
727
728 def _uninstallVBoxOnDarwin(self):
729 """ Uninstalls VBox on Mac OS X."""
730
731 # Is VirtualBox installed? If not, don't try uninstall it.
732 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
733 if sVBox is None:
734 return True;
735
736 # Find the dmg.
737 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
738 if sDmg is None:
739 return False;
740 if not os.path.exists(sDmg):
741 return True;
742
743 # Mount the DMG.
744 fRc = self._darwinMountDmg(sDmg);
745 if fRc is not True:
746 return False;
747
748 # Execute the uninstaller.
749 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
750 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
751
752 # Unmount the DMG and we're done.
753 if not self._darwinUnmountDmg(fIgnoreError = False):
754 fRc = False;
755 return fRc;
756
757 #
758 # GNU/Linux
759 #
760
761 def _installVBoxOnLinux(self):
762 """ Installs VBox on Linux."""
763 sRun = self._findFile('^VirtualBox-.*\\.run$');
764 if sRun is None:
765 return False;
766 utils.chmodPlusX(sRun);
767
768 # Install the new one.
769 fRc, _ = self._sudoExecuteSync([sRun,]);
770 return fRc;
771
772 def _uninstallVBoxOnLinux(self):
773 """ Uninstalls VBox on Linux."""
774
775 # Is VirtualBox installed? If not, don't try uninstall it.
776 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
777 if sVBox is None:
778 return True;
779
780 # Find the .run file and use it.
781 sRun = self._findFile('^VirtualBox-.*\\.run$', fMandatory = False);
782 if sRun is not None:
783 utils.chmodPlusX(sRun);
784 fRc, _ = self._sudoExecuteSync([sRun, 'uninstall']);
785 return fRc;
786
787 # Try the installed uninstaller.
788 for sUninstaller in [os.path.join(sVBox, 'uninstall.sh'), '/opt/VirtualBox/uninstall.sh', ]:
789 if os.path.isfile(sUninstaller):
790 reporter.log('Invoking "%s"...' % (sUninstaller,));
791 fRc, _ = self._sudoExecuteSync([sUninstaller, 'uninstall']);
792 return fRc;
793
794 reporter.log('Did not find any VirtualBox install to uninstall.');
795 return True;
796
797
798 #
799 # Solaris
800 #
801
802 def _generateAutoResponseOnSolaris(self):
803 """
804 Generates an autoresponse file on solaris, returning the name.
805 None is return on failure.
806 """
807 sPath = os.path.join(self.sScratchPath, 'SolarisAutoResponse');
808 oFile = utils.openNoInherit(sPath, 'wt');
809 oFile.write('basedir=default\n'
810 'runlevel=nocheck\n'
811 'conflict=quit\n'
812 'setuid=nocheck\n'
813 'action=nocheck\n'
814 'partial=quit\n'
815 'instance=unique\n'
816 'idepend=quit\n'
817 'rdepend=quit\n'
818 'space=quit\n'
819 'mail=\n');
820 oFile.close();
821 return sPath;
822
823 def _installVBoxOnSolaris(self):
824 """ Installs VBox on Solaris."""
825 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = False);
826 if sPkg is None:
827 sTar = self._findFile('^VirtualBox-.*-SunOS-.*\\.tar.gz$', fMandatory = False);
828 if sTar is not None:
829 if self._maybeUnpackArchive(sTar) is not True:
830 return False;
831 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = True);
832 sRsp = self._findFile('^autoresponse$', fMandatory = True);
833 if sPkg is None or sRsp is None:
834 return False;
835
836 # Uninstall first (ignore result).
837 self._uninstallVBoxOnSolaris(False);
838
839 # Install the new one.
840 fRc, _ = self._sudoExecuteSync(['pkgadd', '-d', sPkg, '-n', '-a', sRsp, 'SUNWvbox']);
841 return fRc;
842
843 def _uninstallVBoxOnSolaris(self, fRestartSvcConfigD):
844 """ Uninstalls VBox on Solaris."""
845 reporter.flushall();
846 if utils.processCall(['pkginfo', '-q', 'SUNWvbox']) != 0:
847 return True;
848 sRsp = self._generateAutoResponseOnSolaris();
849 fRc, _ = self._sudoExecuteSync(['pkgrm', '-n', '-a', sRsp, 'SUNWvbox']);
850
851 #
852 # Restart the svc.configd as it has a tendency to clog up with time and
853 # become unresponsive. It will handle SIGHUP by exiting the sigwait()
854 # look in the main function and shut down the service nicely (backend_fini).
855 # The restarter will then start a new instance of it.
856 #
857 if fRestartSvcConfigD:
858 time.sleep(1); # Give it a chance to flush pkgrm stuff.
859 self._sudoExecuteSync(['pkill', '-HUP', 'svc.configd']);
860 time.sleep(5); # Spare a few cpu cycles it to shutdown and restart.
861
862 return fRc;
863
864 #
865 # Windows
866 #
867
868 ## VBox windows services we can query the status of.
869 kasWindowsServices = [ 'vboxdrv', 'vboxusbmon', 'vboxnetadp', 'vboxnetflt', 'vboxnetlwf' ];
870
871 def _installVBoxOnWindows(self):
872 """ Installs VBox on Windows."""
873 sExe = self._findFile('^VirtualBox-.*-(MultiArch|Win).exe$');
874 if sExe is None:
875 return False;
876
877 # TEMPORARY HACK - START
878 # It seems that running the NDIS cleanup script upon uninstallation is not
879 # a good idea, so let's run it before installing VirtualBox.
880 #sHostName = socket.getfqdn();
881 #if not sHostName.startswith('testboxwin3') \
882 # and not sHostName.startswith('testboxharp2') \
883 # and not sHostName.startswith('wei01-b6ka-3') \
884 # and utils.getHostOsVersion() in ['8', '8.1', '9', '2008Server', '2008ServerR2', '2012Server']:
885 # reporter.log('Peforming extra NDIS cleanup...');
886 # sMagicScript = os.path.abspath(os.path.join(g_ksValidationKitDir, 'testdriver', 'win-vbox-net-uninstall.ps1'));
887 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-Command', 'set-executionpolicy unrestricted']);
888 # if not fRc2:
889 # reporter.log('set-executionpolicy failed.');
890 # self._sudoExecuteSync(['powershell.exe', '-Command', 'get-executionpolicy']);
891 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-File', sMagicScript]);
892 # if not fRc2:
893 # reporter.log('NDIS cleanup failed.');
894 # TEMPORARY HACK - END
895
896 # Uninstall any previous vbox version first.
897 fRc = self._uninstallVBoxOnWindows('install');
898 if fRc is not True:
899 return None; # There shouldn't be anything to uninstall, and if there is, it's not our fault.
900
901 # Install the new one.
902 asArgs = [sExe, '-vvvv', '--silent', '--logging'];
903 asArgs.extend(['--msiparams', 'REBOOT=ReallySuppress']);
904 sVBoxInstallPath = os.environ.get('VBOX_INSTALL_PATH', None);
905 if sVBoxInstallPath is not None:
906 asArgs.extend(['INSTALLDIR="%s"' % (sVBoxInstallPath,)]);
907
908 fRc2, iRc = self._sudoExecuteSync(asArgs);
909 if fRc2 is False:
910 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
911 reporter.error('Installer required a reboot to complete installation (ERROR_SUCCESS_REBOOT_REQUIRED)');
912 else:
913 reporter.error('Installer failed, exit code: %s' % (iRc,));
914 fRc = False;
915
916 sLogFile = os.path.join(tempfile.gettempdir(), 'VirtualBox', 'VBoxInstallLog.txt');
917 if os.path.isfile(sLogFile):
918 reporter.addLogFile(sLogFile, 'log/installer', "Verbose MSI installation log file");
919 self._waitForTestManagerConnectivity(30);
920 return fRc;
921
922 def _isProcessPresent(self, sName):
923 """ Checks whether the named process is present or not. """
924 for oProcess in utils.processListAll():
925 sBase = oProcess.getBaseImageNameNoExeSuff();
926 if sBase is not None and sBase.lower() == sName:
927 return True;
928 return False;
929
930 def _killProcessesByName(self, sName, sDesc, fChildren = False):
931 """ Kills the named process, optionally including children. """
932 cKilled = 0;
933 aoProcesses = utils.processListAll();
934 for oProcess in aoProcesses:
935 sBase = oProcess.getBaseImageNameNoExeSuff();
936 if sBase is not None and sBase.lower() == sName:
937 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
938 utils.processKill(oProcess.iPid);
939 cKilled += 1;
940
941 if fChildren:
942 for oChild in aoProcesses:
943 if oChild.iParentPid == oProcess.iPid and oChild.iParentPid is not None:
944 reporter.log('Killing %s child process: %s (%s)' % (sDesc, oChild.iPid, sBase));
945 utils.processKill(oChild.iPid);
946 cKilled += 1;
947 return cKilled;
948
949 def _terminateProcessesByNameAndArgSubstr(self, sName, sArg, sDesc):
950 """
951 Terminates the named process using taskkill.exe, if any of its args
952 contains the passed string.
953 """
954 cKilled = 0;
955 aoProcesses = utils.processListAll();
956 for oProcess in aoProcesses:
957 sBase = oProcess.getBaseImageNameNoExeSuff();
958 if sBase is not None and sBase.lower() == sName and any(sArg in s for s in oProcess.asArgs):
959
960 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
961 self._executeSync(['taskkill.exe', '/pid', '%u' % (oProcess.iPid,)]);
962 cKilled += 1;
963 return cKilled;
964
965 def _uninstallVBoxOnWindows(self, sMode):
966 """
967 Uninstalls VBox on Windows, all installations we find to be on the safe side...
968 """
969 assert sMode in ['install', 'uninstall',];
970
971 import win32com.client; # pylint: disable=import-error
972 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0);
973 oInstaller = win32com.client.Dispatch('WindowsInstaller.Installer',
974 resultCLSID = '{000C1090-0000-0000-C000-000000000046}')
975
976 # Search installed products for VirtualBox.
977 asProdCodes = [];
978 for sProdCode in oInstaller.Products:
979 try:
980 sProdName = oInstaller.ProductInfo(sProdCode, "ProductName");
981 except:
982 reporter.logXcpt();
983 continue;
984 #reporter.log('Info: %s=%s' % (sProdCode, sProdName));
985 if sProdName.startswith('Oracle VM VirtualBox') \
986 or sProdName.startswith('Sun VirtualBox'):
987 asProdCodes.append([sProdCode, sProdName]);
988
989 # Before we start uninstalling anything, just ruthlessly kill any cdb,
990 # msiexec, drvinst and some rundll process we might find hanging around.
991 if self._isProcessPresent('rundll32'):
992 cTimes = 0;
993 while cTimes < 3:
994 cTimes += 1;
995 cKilled = self._terminateProcessesByNameAndArgSubstr('rundll32', 'InstallSecurityPromptRunDllW',
996 'MSI driver installation');
997 if cKilled <= 0:
998 break;
999 time.sleep(10); # Give related drvinst process a chance to clean up after we killed the verification dialog.
1000
1001 if self._isProcessPresent('drvinst'):
1002 time.sleep(15); # In the hope that it goes away.
1003 cTimes = 0;
1004 while cTimes < 4:
1005 cTimes += 1;
1006 cKilled = self._killProcessesByName('drvinst', 'MSI driver installation', True);
1007 if cKilled <= 0:
1008 break;
1009 time.sleep(10); # Give related MSI process a chance to clean up after we killed the driver installer.
1010
1011 if self._isProcessPresent('msiexec'):
1012 cTimes = 0;
1013 while cTimes < 3:
1014 reporter.log('found running msiexec process, waiting a bit...');
1015 time.sleep(20) # In the hope that it goes away.
1016 if not self._isProcessPresent('msiexec'):
1017 break;
1018 cTimes += 1;
1019 ## @todo this could also be the msiexec system service, try to detect this case!
1020 if cTimes >= 6:
1021 cKilled = self._killProcessesByName('msiexec', 'MSI driver installation');
1022 if cKilled > 0:
1023 time.sleep(16); # fudge.
1024
1025 # cdb.exe sometimes stays running (from utils.getProcessInfo), blocking
1026 # the scratch directory. No idea why.
1027 if self._isProcessPresent('cdb'):
1028 cTimes = 0;
1029 while cTimes < 3:
1030 cKilled = self._killProcessesByName('cdb', 'cdb.exe from getProcessInfo');
1031 if cKilled <= 0:
1032 break;
1033 time.sleep(2); # fudge.
1034
1035 # Do the uninstalling.
1036 fRc = True;
1037 sLogFile = os.path.join(self.sScratchPath, 'VBoxUninstallLog.txt');
1038 for sProdCode, sProdName in asProdCodes:
1039 reporter.log('Uninstalling %s (%s)...' % (sProdName, sProdCode));
1040 fRc2, iRc = self._sudoExecuteSync(['msiexec', '/uninstall', sProdCode, '/quiet', '/passive', '/norestart',
1041 '/L*v', '%s' % (sLogFile), ]);
1042 if fRc2 is False:
1043 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
1044 reporter.error('Uninstaller required a reboot to complete uninstallation');
1045 else:
1046 reporter.error('Uninstaller failed, exit code: %s' % (iRc,));
1047 fRc = False;
1048
1049 self._waitForTestManagerConnectivity(30);
1050
1051 # Upload the log on failure. Do it early if the extra cleanups below causes trouble.
1052 if fRc is False and os.path.isfile(sLogFile):
1053 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1054 sLogFile = None;
1055
1056 # Log driver service states (should ls \Driver\VBox* and \Device\VBox*).
1057 fHadLeftovers = False;
1058 asLeftovers = [];
1059 for sService in reversed(self.kasWindowsServices):
1060 cTries = 0;
1061 while True:
1062 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'query', sService]);
1063 if not fRc2:
1064 break;
1065 fHadLeftovers = True;
1066
1067 cTries += 1;
1068 if cTries > 3:
1069 asLeftovers.append(sService,);
1070 break;
1071
1072 # Get the status output.
1073 try:
1074 sOutput = utils.sudoProcessOutputChecked(['sc.exe', 'query', sService]);
1075 except:
1076 reporter.logXcpt();
1077 else:
1078 if re.search(r'STATE\s+:\s*1\s*STOPPED', sOutput) is None:
1079 reporter.log('Trying to stop %s...' % (sService,));
1080 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'stop', sService]);
1081 time.sleep(1); # fudge
1082
1083 reporter.log('Trying to delete %s...' % (sService,));
1084 self._sudoExecuteSync(['sc.exe', 'delete', sService]);
1085
1086 time.sleep(1); # fudge
1087
1088 if asLeftovers:
1089 reporter.log('Warning! Leftover VBox drivers: %s' % (', '.join(asLeftovers),));
1090 fRc = False;
1091
1092 if fHadLeftovers:
1093 self._waitForTestManagerConnectivity(30);
1094
1095 # Upload the log if we have any leftovers and didn't upload it already.
1096 if sLogFile is not None and (fRc is False or fHadLeftovers) and os.path.isfile(sLogFile):
1097 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1098
1099 return fRc;
1100
1101
1102 #
1103 # Extension pack.
1104 #
1105
1106 def _getVBoxInstallPath(self, fFailIfNotFound):
1107 """ Returns the default VBox installation path. """
1108 sHost = utils.getHostOs();
1109 if sHost == 'win':
1110 sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files');
1111 asLocs = [
1112 os.path.join(sProgFiles, 'Oracle', 'VirtualBox'),
1113 os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'),
1114 os.path.join(sProgFiles, 'Sun', 'VirtualBox'),
1115 ];
1116 elif sHost in ('linux', 'solaris',):
1117 asLocs = [ '/opt/VirtualBox', '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0'];
1118 elif sHost == 'darwin':
1119 asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ];
1120 else:
1121 asLocs = [ '/opt/VirtualBox' ];
1122 if 'VBOX_INSTALL_PATH' in os.environ:
1123 asLocs.insert(0, os.environ.get('VBOX_INSTALL_PATH', None));
1124
1125 for sLoc in asLocs:
1126 if os.path.isdir(sLoc):
1127 return sLoc;
1128 if fFailIfNotFound:
1129 reporter.error('Failed to locate VirtualBox installation: %s' % (asLocs,));
1130 else:
1131 reporter.log2('Failed to locate VirtualBox installation: %s' % (asLocs,));
1132 return None;
1133
1134 def _installExtPack(self):
1135 """ Installs the extension pack. """
1136 sVBox = self._getVBoxInstallPath(fFailIfNotFound = True);
1137 if sVBox is None:
1138 return False;
1139 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1140
1141 if self._uninstallAllExtPacks() is not True:
1142 return False;
1143
1144 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.vbox-extpack');
1145 if sExtPack is None:
1146 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.*.vbox-extpack');
1147 if sExtPack is None:
1148 return True;
1149
1150 sDstDir = os.path.join(sExtPackDir, 'Oracle_VM_VirtualBox_Extension_Pack');
1151 reporter.log('Installing extension pack "%s" to "%s"...' % (sExtPack, sExtPackDir));
1152 fRc, _ = self._sudoExecuteSync([ self.getBinTool('vts_tar'),
1153 '--extract',
1154 '--verbose',
1155 '--gzip',
1156 '--file', sExtPack,
1157 '--directory', sDstDir,
1158 '--file-mode-and-mask', '0644',
1159 '--file-mode-or-mask', '0644',
1160 '--dir-mode-and-mask', '0755',
1161 '--dir-mode-or-mask', '0755',
1162 '--owner', '0',
1163 '--group', '0',
1164 ]);
1165 return fRc;
1166
1167 def _uninstallAllExtPacks(self):
1168 """ Uninstalls all extension packs. """
1169 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
1170 if sVBox is None:
1171 return True;
1172
1173 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1174 if not os.path.exists(sExtPackDir):
1175 return True;
1176
1177 fRc, _ = self._sudoExecuteSync([self.getBinTool('vts_rm'), '-Rfv', '--', sExtPackDir]);
1178 return fRc;
1179
1180
1181
1182if __name__ == '__main__':
1183 sys.exit(VBoxInstallerTestDriver().main(sys.argv));
1184
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