VirtualBox

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

Last change on this file since 95722 was 95722, checked in by vboxsync, 2 years ago

Validation Kit/vboxinstaller: Tweaked the stub loader MSI logging path for VBox >= 6.1 to use a deterministic location, which then can be uploaded to the test manager [build fix, no fpApiVer here].

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