VirtualBox

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

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

ValidationKit/vboxinstaller.py: Temporary hack to skip installing the kernel drivers on testboxs running BigSur and later until the proper solution is ready, bugref:9044 [fix]

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 47.3 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: 93184 $"
34
35
36# Standard Python imports.
37import os
38import sys
39import re
40import 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
698 # TEMPORARY HACK - START
699 # Don't install the kernel drivers on the testboxes with BigSur and later
700 # Needs a more generic approach but that one needs more effort.
701 sHostName = socket.getfqdn();
702 if sHostName.startswith('testboxmac10') \
703 or sHostName.startswith('testboxmac11'):
704 self._fKernelDrivers = False;
705 # TEMPORARY HACK - END
706
707 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
708 if sDmg is None:
709 return False;
710
711 # Mount the DMG.
712 fRc = self._darwinMountDmg(sDmg);
713 if fRc is not True:
714 return False;
715
716 # Uninstall any previous vbox version first.
717 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
718 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
719 if fRc is True:
720
721 # Install the package.
722 sPkg = os.path.join(self._darwinDmgPath(), 'VirtualBox.pkg');
723 if self._fKernelDrivers:
724 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, '-target', '/']);
725 else:
726 sChoicesXml = self._generateWithoutKextsChoicesXmlOnDarwin();
727 if sChoicesXml is not None:
728 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, \
729 '-applyChoiceChangesXML', sChoicesXml, '-target', '/']);
730 else:
731 fRc = False;
732
733 # Unmount the DMG and we're done.
734 if not self._darwinUnmountDmg(fIgnoreError = False):
735 fRc = False;
736 return fRc;
737
738 def _uninstallVBoxOnDarwin(self):
739 """ Uninstalls VBox on Mac OS X."""
740
741 # Is VirtualBox installed? If not, don't try uninstall it.
742 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
743 if sVBox is None:
744 return True;
745
746 # Find the dmg.
747 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
748 if sDmg is None:
749 return False;
750 if not os.path.exists(sDmg):
751 return True;
752
753 # Mount the DMG.
754 fRc = self._darwinMountDmg(sDmg);
755 if fRc is not True:
756 return False;
757
758 # Execute the uninstaller.
759 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
760 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
761
762 # Unmount the DMG and we're done.
763 if not self._darwinUnmountDmg(fIgnoreError = False):
764 fRc = False;
765 return fRc;
766
767 #
768 # GNU/Linux
769 #
770
771 def _installVBoxOnLinux(self):
772 """ Installs VBox on Linux."""
773 sRun = self._findFile('^VirtualBox-.*\\.run$');
774 if sRun is None:
775 return False;
776 utils.chmodPlusX(sRun);
777
778 # Install the new one.
779 fRc, _ = self._sudoExecuteSync([sRun,]);
780 return fRc;
781
782 def _uninstallVBoxOnLinux(self):
783 """ Uninstalls VBox on Linux."""
784
785 # Is VirtualBox installed? If not, don't try uninstall it.
786 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
787 if sVBox is None:
788 return True;
789
790 # Find the .run file and use it.
791 sRun = self._findFile('^VirtualBox-.*\\.run$', fMandatory = False);
792 if sRun is not None:
793 utils.chmodPlusX(sRun);
794 fRc, _ = self._sudoExecuteSync([sRun, 'uninstall']);
795 return fRc;
796
797 # Try the installed uninstaller.
798 for sUninstaller in [os.path.join(sVBox, 'uninstall.sh'), '/opt/VirtualBox/uninstall.sh', ]:
799 if os.path.isfile(sUninstaller):
800 reporter.log('Invoking "%s"...' % (sUninstaller,));
801 fRc, _ = self._sudoExecuteSync([sUninstaller, 'uninstall']);
802 return fRc;
803
804 reporter.log('Did not find any VirtualBox install to uninstall.');
805 return True;
806
807
808 #
809 # Solaris
810 #
811
812 def _generateAutoResponseOnSolaris(self):
813 """
814 Generates an autoresponse file on solaris, returning the name.
815 None is return on failure.
816 """
817 sPath = os.path.join(self.sScratchPath, 'SolarisAutoResponse');
818 oFile = utils.openNoInherit(sPath, 'wt');
819 oFile.write('basedir=default\n'
820 'runlevel=nocheck\n'
821 'conflict=quit\n'
822 'setuid=nocheck\n'
823 'action=nocheck\n'
824 'partial=quit\n'
825 'instance=unique\n'
826 'idepend=quit\n'
827 'rdepend=quit\n'
828 'space=quit\n'
829 'mail=\n');
830 oFile.close();
831 return sPath;
832
833 def _installVBoxOnSolaris(self):
834 """ Installs VBox on Solaris."""
835 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = False);
836 if sPkg is None:
837 sTar = self._findFile('^VirtualBox-.*-SunOS-.*\\.tar.gz$', fMandatory = False);
838 if sTar is not None:
839 if self._maybeUnpackArchive(sTar) is not True:
840 return False;
841 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = True);
842 sRsp = self._findFile('^autoresponse$', fMandatory = True);
843 if sPkg is None or sRsp is None:
844 return False;
845
846 # Uninstall first (ignore result).
847 self._uninstallVBoxOnSolaris(False);
848
849 # Install the new one.
850 fRc, _ = self._sudoExecuteSync(['pkgadd', '-d', sPkg, '-n', '-a', sRsp, 'SUNWvbox']);
851 return fRc;
852
853 def _uninstallVBoxOnSolaris(self, fRestartSvcConfigD):
854 """ Uninstalls VBox on Solaris."""
855 reporter.flushall();
856 if utils.processCall(['pkginfo', '-q', 'SUNWvbox']) != 0:
857 return True;
858 sRsp = self._generateAutoResponseOnSolaris();
859 fRc, _ = self._sudoExecuteSync(['pkgrm', '-n', '-a', sRsp, 'SUNWvbox']);
860
861 #
862 # Restart the svc.configd as it has a tendency to clog up with time and
863 # become unresponsive. It will handle SIGHUP by exiting the sigwait()
864 # look in the main function and shut down the service nicely (backend_fini).
865 # The restarter will then start a new instance of it.
866 #
867 if fRestartSvcConfigD:
868 time.sleep(1); # Give it a chance to flush pkgrm stuff.
869 self._sudoExecuteSync(['pkill', '-HUP', 'svc.configd']);
870 time.sleep(5); # Spare a few cpu cycles it to shutdown and restart.
871
872 return fRc;
873
874 #
875 # Windows
876 #
877
878 ## VBox windows services we can query the status of.
879 kasWindowsServices = [ 'vboxdrv', 'vboxusbmon', 'vboxnetadp', 'vboxnetflt', 'vboxnetlwf' ];
880
881 def _installVBoxOnWindows(self):
882 """ Installs VBox on Windows."""
883 sExe = self._findFile('^VirtualBox-.*-(MultiArch|Win).exe$');
884 if sExe is None:
885 return False;
886
887 # TEMPORARY HACK - START
888 # It seems that running the NDIS cleanup script upon uninstallation is not
889 # a good idea, so let's run it before installing VirtualBox.
890 #sHostName = socket.getfqdn();
891 #if not sHostName.startswith('testboxwin3') \
892 # and not sHostName.startswith('testboxharp2') \
893 # and not sHostName.startswith('wei01-b6ka-3') \
894 # and utils.getHostOsVersion() in ['8', '8.1', '9', '2008Server', '2008ServerR2', '2012Server']:
895 # reporter.log('Peforming extra NDIS cleanup...');
896 # sMagicScript = os.path.abspath(os.path.join(g_ksValidationKitDir, 'testdriver', 'win-vbox-net-uninstall.ps1'));
897 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-Command', 'set-executionpolicy unrestricted']);
898 # if not fRc2:
899 # reporter.log('set-executionpolicy failed.');
900 # self._sudoExecuteSync(['powershell.exe', '-Command', 'get-executionpolicy']);
901 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-File', sMagicScript]);
902 # if not fRc2:
903 # reporter.log('NDIS cleanup failed.');
904 # TEMPORARY HACK - END
905
906 # Uninstall any previous vbox version first.
907 fRc = self._uninstallVBoxOnWindows('install');
908 if fRc is not True:
909 return None; # There shouldn't be anything to uninstall, and if there is, it's not our fault.
910
911 # Install the new one.
912 asArgs = [sExe, '-vvvv', '--silent', '--logging'];
913 asArgs.extend(['--msiparams', 'REBOOT=ReallySuppress']);
914 sVBoxInstallPath = os.environ.get('VBOX_INSTALL_PATH', None);
915 if sVBoxInstallPath is not None:
916 asArgs.extend(['INSTALLDIR="%s"' % (sVBoxInstallPath,)]);
917
918 fRc2, iRc = self._sudoExecuteSync(asArgs);
919 if fRc2 is False:
920 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
921 reporter.error('Installer required a reboot to complete installation (ERROR_SUCCESS_REBOOT_REQUIRED)');
922 else:
923 reporter.error('Installer failed, exit code: %s' % (iRc,));
924 fRc = False;
925
926 sLogFile = os.path.join(tempfile.gettempdir(), 'VirtualBox', 'VBoxInstallLog.txt');
927 if os.path.isfile(sLogFile):
928 reporter.addLogFile(sLogFile, 'log/installer', "Verbose MSI installation log file");
929 self._waitForTestManagerConnectivity(30);
930 return fRc;
931
932 def _isProcessPresent(self, sName):
933 """ Checks whether the named process is present or not. """
934 for oProcess in utils.processListAll():
935 sBase = oProcess.getBaseImageNameNoExeSuff();
936 if sBase is not None and sBase.lower() == sName:
937 return True;
938 return False;
939
940 def _killProcessesByName(self, sName, sDesc, fChildren = False):
941 """ Kills the named process, optionally including children. """
942 cKilled = 0;
943 aoProcesses = utils.processListAll();
944 for oProcess in aoProcesses:
945 sBase = oProcess.getBaseImageNameNoExeSuff();
946 if sBase is not None and sBase.lower() == sName:
947 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
948 utils.processKill(oProcess.iPid);
949 cKilled += 1;
950
951 if fChildren:
952 for oChild in aoProcesses:
953 if oChild.iParentPid == oProcess.iPid and oChild.iParentPid is not None:
954 reporter.log('Killing %s child process: %s (%s)' % (sDesc, oChild.iPid, sBase));
955 utils.processKill(oChild.iPid);
956 cKilled += 1;
957 return cKilled;
958
959 def _terminateProcessesByNameAndArgSubstr(self, sName, sArg, sDesc):
960 """
961 Terminates the named process using taskkill.exe, if any of its args
962 contains the passed string.
963 """
964 cKilled = 0;
965 aoProcesses = utils.processListAll();
966 for oProcess in aoProcesses:
967 sBase = oProcess.getBaseImageNameNoExeSuff();
968 if sBase is not None and sBase.lower() == sName and any(sArg in s for s in oProcess.asArgs):
969
970 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
971 self._executeSync(['taskkill.exe', '/pid', '%u' % (oProcess.iPid,)]);
972 cKilled += 1;
973 return cKilled;
974
975 def _uninstallVBoxOnWindows(self, sMode):
976 """
977 Uninstalls VBox on Windows, all installations we find to be on the safe side...
978 """
979 assert sMode in ['install', 'uninstall',];
980
981 import win32com.client; # pylint: disable=import-error
982 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0);
983 oInstaller = win32com.client.Dispatch('WindowsInstaller.Installer',
984 resultCLSID = '{000C1090-0000-0000-C000-000000000046}')
985
986 # Search installed products for VirtualBox.
987 asProdCodes = [];
988 for sProdCode in oInstaller.Products:
989 try:
990 sProdName = oInstaller.ProductInfo(sProdCode, "ProductName");
991 except:
992 reporter.logXcpt();
993 continue;
994 #reporter.log('Info: %s=%s' % (sProdCode, sProdName));
995 if sProdName.startswith('Oracle VM VirtualBox') \
996 or sProdName.startswith('Sun VirtualBox'):
997 asProdCodes.append([sProdCode, sProdName]);
998
999 # Before we start uninstalling anything, just ruthlessly kill any cdb,
1000 # msiexec, drvinst and some rundll process we might find hanging around.
1001 if self._isProcessPresent('rundll32'):
1002 cTimes = 0;
1003 while cTimes < 3:
1004 cTimes += 1;
1005 cKilled = self._terminateProcessesByNameAndArgSubstr('rundll32', 'InstallSecurityPromptRunDllW',
1006 'MSI driver installation');
1007 if cKilled <= 0:
1008 break;
1009 time.sleep(10); # Give related drvinst process a chance to clean up after we killed the verification dialog.
1010
1011 if self._isProcessPresent('drvinst'):
1012 time.sleep(15); # In the hope that it goes away.
1013 cTimes = 0;
1014 while cTimes < 4:
1015 cTimes += 1;
1016 cKilled = self._killProcessesByName('drvinst', 'MSI driver installation', True);
1017 if cKilled <= 0:
1018 break;
1019 time.sleep(10); # Give related MSI process a chance to clean up after we killed the driver installer.
1020
1021 if self._isProcessPresent('msiexec'):
1022 cTimes = 0;
1023 while cTimes < 3:
1024 reporter.log('found running msiexec process, waiting a bit...');
1025 time.sleep(20) # In the hope that it goes away.
1026 if not self._isProcessPresent('msiexec'):
1027 break;
1028 cTimes += 1;
1029 ## @todo this could also be the msiexec system service, try to detect this case!
1030 if cTimes >= 6:
1031 cKilled = self._killProcessesByName('msiexec', 'MSI driver installation');
1032 if cKilled > 0:
1033 time.sleep(16); # fudge.
1034
1035 # cdb.exe sometimes stays running (from utils.getProcessInfo), blocking
1036 # the scratch directory. No idea why.
1037 if self._isProcessPresent('cdb'):
1038 cTimes = 0;
1039 while cTimes < 3:
1040 cKilled = self._killProcessesByName('cdb', 'cdb.exe from getProcessInfo');
1041 if cKilled <= 0:
1042 break;
1043 time.sleep(2); # fudge.
1044
1045 # Do the uninstalling.
1046 fRc = True;
1047 sLogFile = os.path.join(self.sScratchPath, 'VBoxUninstallLog.txt');
1048 for sProdCode, sProdName in asProdCodes:
1049 reporter.log('Uninstalling %s (%s)...' % (sProdName, sProdCode));
1050 fRc2, iRc = self._sudoExecuteSync(['msiexec', '/uninstall', sProdCode, '/quiet', '/passive', '/norestart',
1051 '/L*v', '%s' % (sLogFile), ]);
1052 if fRc2 is False:
1053 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
1054 reporter.error('Uninstaller required a reboot to complete uninstallation');
1055 else:
1056 reporter.error('Uninstaller failed, exit code: %s' % (iRc,));
1057 fRc = False;
1058
1059 self._waitForTestManagerConnectivity(30);
1060
1061 # Upload the log on failure. Do it early if the extra cleanups below causes trouble.
1062 if fRc is False and os.path.isfile(sLogFile):
1063 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1064 sLogFile = None;
1065
1066 # Log driver service states (should ls \Driver\VBox* and \Device\VBox*).
1067 fHadLeftovers = False;
1068 asLeftovers = [];
1069 for sService in reversed(self.kasWindowsServices):
1070 cTries = 0;
1071 while True:
1072 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'query', sService]);
1073 if not fRc2:
1074 break;
1075 fHadLeftovers = True;
1076
1077 cTries += 1;
1078 if cTries > 3:
1079 asLeftovers.append(sService,);
1080 break;
1081
1082 # Get the status output.
1083 try:
1084 sOutput = utils.sudoProcessOutputChecked(['sc.exe', 'query', sService]);
1085 except:
1086 reporter.logXcpt();
1087 else:
1088 if re.search(r'STATE\s+:\s*1\s*STOPPED', sOutput) is None:
1089 reporter.log('Trying to stop %s...' % (sService,));
1090 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'stop', sService]);
1091 time.sleep(1); # fudge
1092
1093 reporter.log('Trying to delete %s...' % (sService,));
1094 self._sudoExecuteSync(['sc.exe', 'delete', sService]);
1095
1096 time.sleep(1); # fudge
1097
1098 if asLeftovers:
1099 reporter.log('Warning! Leftover VBox drivers: %s' % (', '.join(asLeftovers),));
1100 fRc = False;
1101
1102 if fHadLeftovers:
1103 self._waitForTestManagerConnectivity(30);
1104
1105 # Upload the log if we have any leftovers and didn't upload it already.
1106 if sLogFile is not None and (fRc is False or fHadLeftovers) and os.path.isfile(sLogFile):
1107 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1108
1109 return fRc;
1110
1111
1112 #
1113 # Extension pack.
1114 #
1115
1116 def _getVBoxInstallPath(self, fFailIfNotFound):
1117 """ Returns the default VBox installation path. """
1118 sHost = utils.getHostOs();
1119 if sHost == 'win':
1120 sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files');
1121 asLocs = [
1122 os.path.join(sProgFiles, 'Oracle', 'VirtualBox'),
1123 os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'),
1124 os.path.join(sProgFiles, 'Sun', 'VirtualBox'),
1125 ];
1126 elif sHost in ('linux', 'solaris',):
1127 asLocs = [ '/opt/VirtualBox', '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0'];
1128 elif sHost == 'darwin':
1129 asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ];
1130 else:
1131 asLocs = [ '/opt/VirtualBox' ];
1132 if 'VBOX_INSTALL_PATH' in os.environ:
1133 asLocs.insert(0, os.environ.get('VBOX_INSTALL_PATH', None));
1134
1135 for sLoc in asLocs:
1136 if os.path.isdir(sLoc):
1137 return sLoc;
1138 if fFailIfNotFound:
1139 reporter.error('Failed to locate VirtualBox installation: %s' % (asLocs,));
1140 else:
1141 reporter.log2('Failed to locate VirtualBox installation: %s' % (asLocs,));
1142 return None;
1143
1144 def _installExtPack(self):
1145 """ Installs the extension pack. """
1146 sVBox = self._getVBoxInstallPath(fFailIfNotFound = True);
1147 if sVBox is None:
1148 return False;
1149 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1150
1151 if self._uninstallAllExtPacks() is not True:
1152 return False;
1153
1154 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.vbox-extpack');
1155 if sExtPack is None:
1156 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.*.vbox-extpack');
1157 if sExtPack is None:
1158 return True;
1159
1160 sDstDir = os.path.join(sExtPackDir, 'Oracle_VM_VirtualBox_Extension_Pack');
1161 reporter.log('Installing extension pack "%s" to "%s"...' % (sExtPack, sExtPackDir));
1162 fRc, _ = self._sudoExecuteSync([ self.getBinTool('vts_tar'),
1163 '--extract',
1164 '--verbose',
1165 '--gzip',
1166 '--file', sExtPack,
1167 '--directory', sDstDir,
1168 '--file-mode-and-mask', '0644',
1169 '--file-mode-or-mask', '0644',
1170 '--dir-mode-and-mask', '0755',
1171 '--dir-mode-or-mask', '0755',
1172 '--owner', '0',
1173 '--group', '0',
1174 ]);
1175 return fRc;
1176
1177 def _uninstallAllExtPacks(self):
1178 """ Uninstalls all extension packs. """
1179 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
1180 if sVBox is None:
1181 return True;
1182
1183 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1184 if not os.path.exists(sExtPackDir):
1185 return True;
1186
1187 fRc, _ = self._sudoExecuteSync([self.getBinTool('vts_rm'), '-Rfv', '--', sExtPackDir]);
1188 return fRc;
1189
1190
1191
1192if __name__ == '__main__':
1193 sys.exit(VBoxInstallerTestDriver().main(sys.argv));
1194
Note: See TracBrowser for help on using the repository browser.

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