VirtualBox

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

Last change on this file since 95715 was 95715, 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.

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 47.5 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: 95715 $"
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 if self.fpApiVer >= 6.1:
915 # We need to explicitly specify the location, otherwise the log would end up at a random location.
916 sLogFile = os.path.join(tempfile.gettempdir(), 'VBoxInstallLog.txt');
917 asArgs.extend(['--msi-log-file', sLogFile]);
918 else: # Prior to 6.1 the location was hardcoded.
919 sLogFile = os.path.join(tempfile.gettempdir(), 'VirtualBox', 'VBoxInstallLog.txt');
920 fRc2, iRc = self._sudoExecuteSync(asArgs);
921 if fRc2 is False:
922 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
923 reporter.error('Installer required a reboot to complete installation (ERROR_SUCCESS_REBOOT_REQUIRED)');
924 else:
925 reporter.error('Installer failed, exit code: %s' % (iRc,));
926 fRc = False;
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.

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