Ejemplo n.º 1
0
    def _reinitScratch(self, fnLog, fUseTheForce):
        """
        Wipes the scratch directories and re-initializes them.

        No exceptions raise, returns success indicator instead.
        """
        if fUseTheForce is None:
            fUseTheForce = self._fFirstSignOn

        class ErrorCallback(object):  # pylint: disable=R0903
            """
            Callbacks + state for the cleanup.
            """
            def __init__(self):
                self.fRc = True

            def onErrorCallback(self, sFnName, sPath, aXcptInfo):
                """ Logs error during shutil.rmtree operation. """
                fnLog('Error removing "%s": fn=%s %s' %
                      (sPath, sFnName, aXcptInfo[1]))
                self.fRc = False

        oRc = ErrorCallback()

        #
        # Cleanup.
        #
        for sName in os.listdir(self._oOptions.sScratchRoot):
            sFullName = os.path.join(self._oOptions.sScratchRoot, sName)
            try:
                if os.path.isdir(sFullName):
                    shutil.rmtree(sFullName, False, oRc.onErrorCallback)
                else:
                    os.remove(sFullName)
                if os.path.exists(sFullName):
                    raise Exception('Still exists after deletion, weird.')
            except Exception as oXcpt:
                if    fUseTheForce is True \
                  and utils.getHostOs() not in ['win', 'os2'] \
                  and len(sFullName) >= 8 \
                  and sFullName[0] == '/' \
                  and sFullName[1] != '/' \
                  and sFullName.find('/../') < 0:
                    fnLog('Problems deleting "%s" (%s) using the force...' %
                          (sFullName, oXcpt))
                    try:
                        if os.path.isdir(sFullName):
                            iRc = utils.sudoProcessCall(
                                ['/bin/rm', '-Rf', sFullName])
                        else:
                            iRc = utils.sudoProcessCall(
                                ['/bin/rm', '-f', sFullName])
                        if iRc != 0:
                            raise Exception('exit code %s' % iRc)
                        if os.path.exists(sFullName):
                            raise Exception(
                                'Still exists after forced deletion, weird^2.')
                    except:
                        fnLog('Error sudo deleting "%s": %s' %
                              (sFullName, oXcpt))
                        oRc.fRc = False
                else:
                    fnLog('Error deleting "%s": %s' % (sFullName, oXcpt))
                    oRc.fRc = False

        # Display files left behind.
        def dirEnumCallback(sName, oStat):
            """ callback for dirEnumerateTree """
            fnLog(u'%s %s' % (utils.formatFileStat(oStat)
                              if oStat is not None else '????????????', sName))

        utils.dirEnumerateTree(self._oOptions.sScratchRoot, dirEnumCallback)

        #
        # Re-create the directories.
        #
        for sDir in [
                self._oOptions.sScratchRoot, self._sScratchSpill,
                self._sScratchScripts, self._sScratchState
        ]:
            if not os.path.isdir(sDir):
                try:
                    os.makedirs(sDir, 0o700)
                except Exception as oXcpt:
                    fnLog('Error creating "%s": %s' % (sDir, oXcpt))
                    oRc.fRc = False

        if oRc.fRc is True:
            self._cReinitScratchErrors = 0
        else:
            self._cReinitScratchErrors += 1
        return oRc.fRc
Ejemplo n.º 2
0
class TestBoxScript(object):
    """
    Implementation of the test box script.
    Communicate with test manager and perform offered actions.
    """

    ## @name Class Constants.
    # @{

    # Scratch space round value (MB).
    kcMbScratchSpaceRounding = 64
    # Memory size round value (MB).
    kcMbMemoryRounding = 4
    # A NULL UUID in string form.
    ksNullUuid = '00000000-0000-0000-0000-000000000000';
    # The minimum dispatch loop delay.
    kcSecMinDelay = 12;
    # The maximum dispatch loop delay (inclusive).
    kcSecMaxDelay = 24;
    # The minimum sign-on delay.
    kcSecMinSignOnDelay = 30;
    # The maximum sign-on delay (inclusive).
    kcSecMaxSignOnDelay = 60;

    # Keys for config params
    VALUE = 'value'
    FN = 'fn'                           # pylint: disable=C0103

    ## @}


    def __init__(self, oOptions):
        """
        Initialize internals
        """
        self._oOptions        = oOptions;
        self._sTestBoxHelper  = None;

        # Signed-on state
        self._cSignOnAttempts = 0;
        self._fSignedOn       = False;
        self._fNeedReSignOn   = False;
        self._fFirstSignOn    = True;
        self._idTestBox       = None;
        self._sTestBoxName    = '';
        self._sTestBoxUuid    = self.ksNullUuid; # convenience, assigned below.

        # Command processor.
        self._oCommand = TestBoxCommand(self);

        #
        # Scratch dir setup.  Use /var/tmp instead of /tmp because we may need
        # many many GBs for some test scenarios and /tmp can be backed by swap
        # or be a fast+small disk of some kind, while /var/tmp is normally
        # larger, if slower.  /var/tmp is generally not cleaned up on reboot,
        # /tmp often is, this would break host panic / triple-fault detection.
        #
        if self._oOptions.sScratchRoot is None:
            if utils.getHostOs() in ('win', 'os2', 'haiku', 'dos'):
                # We need *lots* of space, so avoid /tmp as it may be a memory
                # file system backed by the swap file, or worse.
                self._oOptions.sScratchRoot = tempfile.gettempdir();
            else:
                self._oOptions.sScratchRoot = '/var/tmp';
            sSubDir = 'testbox';
            try:
                sSubDir = '%s-%u' % (sSubDir, os.getuid()); # pylint: disable=E1101
            except:
                pass;
            self._oOptions.sScratchRoot = os.path.join(self._oOptions.sScratchRoot, sSubDir);

        self._sScratchSpill   = os.path.join(self._oOptions.sScratchRoot, 'scratch');
        self._sScratchScripts = os.path.join(self._oOptions.sScratchRoot, 'scripts');
        self._sScratchState   = os.path.join(self._oOptions.sScratchRoot, 'state');   # persistant storage.

        for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]:
            if not os.path.isdir(sDir):
                os.makedirs(sDir, 0700);

        # We count consecutive reinitScratch failures and will reboot the
        # testbox after a while in the hope that it will correct the issue.
        self._cReinitScratchErrors = 0;

        #
        # Mount builds and test resources if requested.
        #
        self.mountShares();

        #
        # Sign-on parameters: Packed into list of records of format:
        # { <Parameter ID>: { <Current value>, <Check function> } }
        #
        self._ddSignOnParams = \
        {
            constants.tbreq.ALL_PARAM_TESTBOX_UUID:        { self.VALUE: self._getHostSystemUuid(),    self.FN: None },
            constants.tbreq.SIGNON_PARAM_OS:               { self.VALUE: utils.getHostOs(),            self.FN: None },
            constants.tbreq.SIGNON_PARAM_OS_VERSION:       { self.VALUE: utils.getHostOsVersion(),     self.FN: None },
            constants.tbreq.SIGNON_PARAM_CPU_ARCH:         { self.VALUE: utils.getHostArch(),          self.FN: None },
            constants.tbreq.SIGNON_PARAM_CPU_VENDOR:       { self.VALUE: self._getHostCpuVendor(),     self.FN: None },
            constants.tbreq.SIGNON_PARAM_CPU_NAME:         { self.VALUE: self._getHostCpuName(),       self.FN: None },
            constants.tbreq.SIGNON_PARAM_CPU_REVISION:     { self.VALUE: self._getHostCpuRevision(),   self.FN: None },
            constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT:      { self.VALUE: self._hasHostHwVirt(),        self.FN: None },
            constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING:{ self.VALUE: self._hasHostNestedPaging(),  self.FN: None },
            constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST: { self.VALUE: self._can64BitGuest(),        self.FN: None },
            constants.tbreq.SIGNON_PARAM_HAS_IOMMU:        { self.VALUE: self._hasHostIoMmu(),         self.FN: None },
            #constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE:    { self.VALUE: self._withRawModeSupport(),   self.FN: None },
            constants.tbreq.SIGNON_PARAM_SCRIPT_REV:       { self.VALUE: self._getScriptRev(),         self.FN: None },
            constants.tbreq.SIGNON_PARAM_REPORT:           { self.VALUE: self._getHostReport(),        self.FN: None },
            constants.tbreq.SIGNON_PARAM_PYTHON_VERSION:   { self.VALUE: self._getPythonHexVersion(),  self.FN: None },
            constants.tbreq.SIGNON_PARAM_CPU_COUNT:        { self.VALUE: None,     self.FN: multiprocessing.cpu_count },
            constants.tbreq.SIGNON_PARAM_MEM_SIZE:         { self.VALUE: None,     self.FN: self._getHostMemSize },
            constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE:     { self.VALUE: None,     self.FN: self._getFreeScratchSpace },
        }
        for sItem in self._ddSignOnParams:
            if self._ddSignOnParams[sItem][self.FN] is not None:
                self._ddSignOnParams[sItem][self.VALUE] = self._ddSignOnParams[sItem][self.FN]()

        testboxcommons.log('Starting Test Box script (%s)' % (self._getScriptRev(),));
        testboxcommons.log('Test Manager URL: %s' % self._oOptions.sTestManagerUrl,)
        testboxcommons.log('Scratch root path: %s' % self._oOptions.sScratchRoot,)
        for sItem in self._ddSignOnParams:
            testboxcommons.log('Sign-On value %18s: %s' % (sItem, self._ddSignOnParams[sItem][self.VALUE]));

        #
        # The System UUID is the primary identification of the machine, so
        # refuse to cooperate if it's NULL.
        #
        self._sTestBoxUuid = self.getSignOnParam(constants.tbreq.ALL_PARAM_TESTBOX_UUID);
        if self._sTestBoxUuid == self.ksNullUuid:
            raise TestBoxScriptException('Couldn\'t determine the System UUID, please use --system-uuid to specify it.');

        #
        # Export environment variables, clearing any we don't know yet.
        #
        for sEnvVar in self._oOptions.asEnvVars:
            iEqual = sEnvVar.find('=');
            if iEqual == -1:    # No '=', remove it.
                if sEnvVar in os.environ:
                    del os.environ[sEnvVar];
            elif iEqual > 0:    # Set it.
                os.environ[sEnvVar[:iEqual]] = sEnvVar[iEqual+1:];
            else:               # Starts with '=', bad user.
                raise TestBoxScriptException('Invalid -E argument: "%s"' % (sEnvVar,));

        os.environ['TESTBOX_PATH_BUILDS']       = self._oOptions.sBuildsPath;
        os.environ['TESTBOX_PATH_RESOURCES']    = self._oOptions.sTestRsrcPath;
        os.environ['TESTBOX_PATH_SCRATCH']      = self._sScratchSpill;
        os.environ['TESTBOX_PATH_SCRIPTS']      = self._sScratchScripts;
        os.environ['TESTBOX_PATH_UPLOAD']       = self._sScratchSpill; ## @todo drop the UPLOAD dir?
        os.environ['TESTBOX_HAS_HW_VIRT']       = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT);
        os.environ['TESTBOX_HAS_NESTED_PAGING'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING);
        os.environ['TESTBOX_HAS_IOMMU']         = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_IOMMU);
        os.environ['TESTBOX_SCRIPT_REV']        = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRIPT_REV);
        os.environ['TESTBOX_CPU_COUNT']         = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_CPU_COUNT);
        os.environ['TESTBOX_MEM_SIZE']          = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_MEM_SIZE);
        os.environ['TESTBOX_SCRATCH_SIZE']      = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE);
        #TODO: os.environ['TESTBOX_WITH_RAW_MODE']     = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_WITH_RAW_MODE);
        os.environ['TESTBOX_WITH_RAW_MODE']     = str(self._withRawModeSupport());
        os.environ['TESTBOX_MANAGER_URL']       = self._oOptions.sTestManagerUrl;
        os.environ['TESTBOX_UUID']              = self._sTestBoxUuid;
        os.environ['TESTBOX_REPORTER']          = 'remote';
        os.environ['TESTBOX_NAME']              = '';
        os.environ['TESTBOX_ID']                = '';
        os.environ['TESTBOX_TEST_SET_ID']       = '';
        os.environ['TESTBOX_TIMEOUT']           = '0';
        os.environ['TESTBOX_TIMEOUT_ABS']       = '0';

        if utils.getHostOs() == 'win':
            os.environ['COMSPEC']            = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe');
        # Currently omitting any kBuild tools.

    def mountShares(self):
        """
        Mounts the shares.
        Raises exception on failure.
        """
        self._mountShare(self._oOptions.sBuildsPath, self._oOptions.sBuildsServerType, self._oOptions.sBuildsServerName,
                         self._oOptions.sBuildsServerShare,
                         self._oOptions.sBuildsServerUser, self._oOptions.sBuildsServerPasswd, 'builds');
        self._mountShare(self._oOptions.sTestRsrcPath, self._oOptions.sTestRsrcServerType, self._oOptions.sTestRsrcServerName,
                         self._oOptions.sTestRsrcServerShare,
                         self._oOptions.sTestRsrcServerUser, self._oOptions.sTestRsrcServerPasswd, 'testrsrc');
        return True;

    def _mountShare(self, sMountPoint, sType, sServer, sShare, sUser, sPassword, sWhat):
        """
        Mounts the specified share if needed.
        Raises exception on failure.
        """
        # Only mount if the type is specified.
        if sType is None:
            return True;

        # Test if already mounted.
        sTestFile = os.path.join(sMountPoint + os.path.sep, sShare + '-new.txt');
        if os.path.isfile(sTestFile):
            return True;

        #
        # Platform specific mount code.
        #
        sHostOs = utils.getHostOs()
        if sHostOs in ('darwin', 'freebsd'):
            utils.sudoProcessCall(['/sbin/umount', sMountPoint]);
            utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
            utils.sudoProcessCall(['/usr/sbin/chown', str(os.getuid()), sMountPoint]); # pylint: disable=E1101
            if sType == 'cifs':
                # Note! no smb://server/share stuff here, 10.6.8 didn't like it.
                utils.processOutputChecked(['/sbin/mount_smbfs', '-o', 'automounted,nostreams,soft,noowners,noatime,rdonly',
                                            '-f', '0555', '-d', '0555',
                                            '//%s:%s@%s/%s' % (sUser, sPassword, sServer, sShare),
                                            sMountPoint]);
            else:
                raise TestBoxScriptException('Unsupported server type %s.' % (sType,));

        elif sHostOs == 'linux':
            utils.sudoProcessCall(['/bin/umount', sMountPoint]);
            utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
            if sType == 'cifs':
                utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'cifs',
                                                '-o',
                                                'user='******',password='******',sec=ntlmv2'
                                                + ',uid=' + str(os.getuid()) # pylint: disable=E1101
                                                + ',gid=' + str(os.getgid()) # pylint: disable=E1101
                                                + ',nounix,file_mode=0555,dir_mode=0555,soft,ro',
                                                '//%s/%s' % (sServer, sShare),
                                                sMountPoint]);
            elif sType == 'nfs':
                utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'nfs',
                                                '-o', 'soft,ro',
                                                '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)),
                                                sMountPoint]);

            else:
                raise TestBoxScriptException('Unsupported server type %s.' % (sType,));

        elif sHostOs == 'solaris':
            utils.sudoProcessCall(['/sbin/umount', sMountPoint]);
            utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
            if sType == 'cifs':
                ## @todo This stuff doesn't work on wei01-x4600b.de.oracle.com running 11.1. FIXME!
                oPasswdFile = tempfile.TemporaryFile();
                oPasswdFile.write(sPassword + '\n');
                oPasswdFile.flush();
                utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'smbfs',
                                                '-o',
                                                'user='******',uid=' + str(os.getuid()) # pylint: disable=E1101
                                                + ',gid=' + str(os.getgid()) # pylint: disable=E1101
                                                + ',fileperms=0555,dirperms=0555,noxattr,ro',
                                                '//%s/%s' % (sServer, sShare),
                                                sMountPoint],
                                               stdin = oPasswdFile);
                oPasswdFile.close();
            elif sType == 'nfs':
                utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'nfs',
                                                '-o', 'noxattr,ro',
                                                '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)),
                                                sMountPoint]);

            else:
                raise TestBoxScriptException('Unsupported server type %s.' % (sType,));


        elif sHostOs == 'win':
            if sType != 'cifs':
                raise TestBoxScriptException('Only CIFS mounts are supported on Windows.');
            utils.processCall(['net', 'use', sMountPoint, '/d']);
            utils.processOutputChecked(['net', 'use', sMountPoint,
                                        '\\\\' + sServer + '\\' + sShare,
                                        sPassword,
                                        '/USER:'******'Unsupported host %s' % (sHostOs,));

        #
        # Re-test.
        #
        if not os.path.isfile(sTestFile):
            raise TestBoxException('Failed to mount %s (%s[%s]) at %s: %s not found'
                                   % (sWhat, sServer, sShare, sMountPoint, sTestFile));

        return True;

    ## @name Signon property releated methods.
    # @{

    def _getHelperOutput(self, sCmd):
        """
        Invokes TestBoxHelper to obtain information hard to access from python.
        """
        if self._sTestBoxHelper is None:
            if not utils.isRunningFromCheckout():
                # See VBoxTestBoxScript.zip for layout.
                self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch(), \
                                                    'TestBoxHelper');
            else: # Only for in-tree testing, so don't bother be too accurate right now.
                sType = os.environ.get('KBUILD_TYPE', os.environ.get('BUILD_TYPE', 'debug'));
                self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', \
                                                    utils.getHostOsDotArch(), sType, 'testboxscript', \
                                                    utils.getHostOs(), utils.getHostArch(), \
                                                    'TestBoxHelper');
            if utils.getHostOs() in ['win', 'os2']:
                self._sTestBoxHelper += '.exe';

        return utils.processOutputChecked([self._sTestBoxHelper, sCmd]).strip();

    def _getHelperOutputTristate(self, sCmd, fDunnoValue):
        """
        Invokes TestBoxHelper to obtain information hard to access from python.
        """
        sValue = self._getHelperOutput(sCmd);
        sValue = sValue.lower();
        if sValue == 'true':
            return True;
        if sValue == 'false':
            return False;
        if sValue != 'dunno' and sValue != 'none':
            raise TestBoxException('Unexpected response "%s" to helper command "%s"' % (sValue, sCmd));
        return fDunnoValue;


    @staticmethod
    def _isUuidGood(sUuid):
        """
        Checks if the UUID looks good.

        There are systems with really bad UUIDs, for instance
        "03000200-0400-0500-0006-000700080009".
        """
        if sUuid == TestBoxScript.ksNullUuid:
            return False;
        sUuid = sUuid.lower();
        for sDigit in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']:
            if sUuid.count(sDigit) > 16:
                return False;
        return True;

    def _getHostSystemUuid(self):
        """
        Get the system UUID string from the System, return null-uuid if
        unable to get retrieve it.
        """
        if self._oOptions.sSystemUuid is not None:
            return self._oOptions.sSystemUuid;

        sUuid = self.ksNullUuid;

        #
        # Try get at the firmware UUID.
        #
        if utils.getHostOs() == 'linux':
            # NOTE: This requires to have kernel option enabled:
            #       Firmware Drivers -> Export DMI identification via sysfs to userspace
            if os.path.exists('/sys/devices/virtual/dmi/id/product_uuid'):
                try:
                    sVar = utils.sudoProcessOutputChecked(['cat', '/sys/devices/virtual/dmi/id/product_uuid']);
                    sUuid = str(uuid.UUID(sVar.strip()));
                except:
                    pass;
            ## @todo consider dmidecoder? What about EFI systems?

        elif utils.getHostOs() == 'win':
            # Windows: WMI
            try:
                import win32com.client;  # pylint: disable=F0401
                oWmi  = win32com.client.Dispatch('WbemScripting.SWbemLocator');
                oWebm = oWmi.ConnectServer('.', 'root\\cimv2');
                for oItem in oWebm.ExecQuery('SELECT * FROM Win32_ComputerSystemProduct'):
                    if oItem.UUID != None:
                        sUuid = str(uuid.UUID(oItem.UUID));
            except:
                pass;

        elif utils.getHostOs() == 'darwin':
            try:
                sVar = utils.processOutputChecked(['/bin/sh', '-c',
                                                   '/usr/sbin/ioreg -k IOPlatformUUID' \
                                                   + '| /usr/bin/grep IOPlatformUUID' \
                                                   + '| /usr/bin/head -1']);
                sVar = sVar.strip()[-(len(self.ksNullUuid) + 1):-1];
                sUuid = str(uuid.UUID(sVar));
            except:
                pass;

        elif utils.getHostOs() == 'solaris':
            # Solaris: The smbios util.
            try:
                sVar = utils.processOutputChecked(['/bin/sh', '-c',
                                                   '/usr/sbin/smbios ' \
                                                   + '| /usr/xpg4/bin/sed -ne \'s/^.*UUID: *//p\'' \
                                                   + '| /usr/bin/head -1']);
                sUuid = str(uuid.UUID(sVar.strip()));
            except:
                pass;

        if self._isUuidGood(sUuid):
            return sUuid;

        #
        # Try add the MAC address.
        # uuid.getnode may provide it, or it may return a random number...
        #
        lMacAddr = uuid.getnode();
        sNode = '%012x' % (lMacAddr,)
        if lMacAddr == uuid.getnode() and lMacAddr != 0 and len(sNode) == 12:
            return sUuid[:-12] + sNode;

        return sUuid;

    def _getHostCpuVendor(self):
        """
        Get the CPUID vendor string on intel HW.
        """
        return self._getHelperOutput('cpuvendor');

    def _getHostCpuName(self):
        """
        Get the CPU name/description string.
        """
        return self._getHelperOutput('cpuname');

    def _getHostCpuRevision(self):
        """
        Get the CPU revision (family/model/stepping) value.
        """
        return self._getHelperOutput('cpurevision');

    def _hasHostHwVirt(self):
        """
        Check if the host supports AMD-V or VT-x
        """
        if self._oOptions.fHasHwVirt is None:
            self._oOptions.fHasHwVirt = self._getHelperOutput('cpuhwvirt');
        return self._oOptions.fHasHwVirt;

    def _hasHostNestedPaging(self):
        """
        Check if the host supports nested paging.
        """
        if not self._hasHostHwVirt():
            return False;
        if self._oOptions.fHasNestedPaging is None:
            self._oOptions.fHasNestedPaging = self._getHelperOutputTristate('nestedpaging', False);
        return self._oOptions.fHasNestedPaging;

    def _can64BitGuest(self):
        """
        Check if the we (VBox) can run 64-bit guests.
        """
        if not self._hasHostHwVirt():
            return False;
        if self._oOptions.fCan64BitGuest is None:
            self._oOptions.fCan64BitGuest = self._getHelperOutputTristate('longmode', True);
        return self._oOptions.fCan64BitGuest;

    def _hasHostIoMmu(self):
        """
        Check if the host has an I/O MMU of the VT-d kind.
        """
        if not self._hasHostHwVirt():
            return False;
        if self._oOptions.fHasIoMmu is None:
            ## @todo Any way to figure this one out on any host OS?
            self._oOptions.fHasIoMmu = False;
        return self._oOptions.fHasIoMmu;

    def _withRawModeSupport(self):
        """
        Check if the testbox is configured with raw-mode support or not.
        """
        if self._oOptions.fWithRawMode is None:
            self._oOptions.fWithRawMode = True;
        return self._oOptions.fWithRawMode;

    def _getHostReport(self):
        """
        Generate a report about the host hardware and software.
        """
        return self._getHelperOutput('report');


    def _getHostMemSize(self):
        """
        Gets the amount of physical memory on the host (and accessible to the
        OS, i.e. don't report stuff over 4GB if Windows doesn't wanna use it).
        Unit: MiB.
        """
        cMbMemory = long(self._getHelperOutput('memsize').strip()) / (1024 * 1024);

        # Round it.
        cMbMemory = long(math.floor(cMbMemory / self.kcMbMemoryRounding)) * self.kcMbMemoryRounding;
        return cMbMemory;

    def _getFreeScratchSpace(self):
        """
        Get free space on the volume where scratch directory is located and
        return it in bytes rounded down to nearest 64MB
        (currently works on Linux only)
        Unit: MiB.
        """
        if platform.system() == 'Windows':
            import ctypes
            cTypeMbFreeSpace = ctypes.c_ulonglong(0)
            ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(self._oOptions.sScratchRoot), None, None,
                                                       ctypes.pointer(cTypeMbFreeSpace))
            cMbFreeSpace = cTypeMbFreeSpace.value
        else:
            stats = os.statvfs(self._oOptions.sScratchRoot); # pylint: disable=E1101
            cMbFreeSpace = stats.f_frsize * stats.f_bfree

        # Convert to MB
        cMbFreeSpace = long(cMbFreeSpace) /(1024 * 1024)

        # Round free space size
        cMbFreeSpace = long(math.floor(cMbFreeSpace / self.kcMbScratchSpaceRounding)) * self.kcMbScratchSpaceRounding;
        return cMbFreeSpace;

    def _getScriptRev(self):
        """
        The script (subversion) revision number.
        """
        sRev = '@VBOX_SVN_REV@';
        sRev = sRev.strip();            # just in case...
        try:
            _ = int(sRev);
        except:
            return __version__[11:-1].strip();
        return sRev;

    def _getPythonHexVersion(self):
        """
        The python hex version number.
        """
        uHexVersion = getattr(sys, 'hexversion', None);
        if uHexVersion is None:
            uHexVersion = (sys.version_info[0] << 24) | (sys.version_info[1] << 16) | (sys.version_info[2] << 8);
            if sys.version_info[3] == 'final':
                uHexVersion |= 0xf0;
        return uHexVersion;

    # @}

    def openTestManagerConnection(self):
        """
        Opens up a connection to the test manager.

        Raises exception on failure.
        """
        return TestBoxConnection(self._oOptions.sTestManagerUrl, self._idTestBox, self._sTestBoxUuid);

    def getSignOnParam(self, sName):
        """
        Returns a sign-on parameter value as string.
        Raises exception if the name is incorrect.
        """
        return str(self._ddSignOnParams[sName][self.VALUE]);

    def getPathState(self):
        """
        Get the path to the state dir in the scratch area.
        """
        return self._sScratchState;

    def getPathScripts(self):
        """
        Get the path to the scripts dir (TESTBOX_PATH_SCRIPTS) in the scratch area.
        """
        return self._sScratchScripts;

    def getPathSpill(self):
        """
        Get the path to the spill dir (TESTBOX_PATH_SCRATCH) in the scratch area.
        """
        return self._sScratchSpill;

    def getPathBuilds(self):
        """
        Get the path to the builds.
        """
        return self._oOptions.sBuildsPath;

    def getTestBoxId(self):
        """
        Get the TestBox ID for state saving purposes.
        """
        return self._idTestBox;

    def getTestBoxName(self):
        """
        Get the TestBox name for state saving purposes.
        """
        return self._sTestBoxName;

    def _reinitScratch(self, fnLog, fUseTheForce):
        """
        Wipes the scratch directories and re-initializes them.

        No exceptions raise, returns success indicator instead.
        """
        if fUseTheForce is None:
            fUseTheForce = self._fFirstSignOn;

        class ErrorCallback(object): # pylint: disable=R0903
            """
            Callbacks + state for the cleanup.
            """
            def __init__(self):
                self.fRc = True;
            def onErrorCallback(self, sFnName, sPath, aXcptInfo):
                """ Logs error during shutil.rmtree operation. """
                fnLog('Error removing "%s": fn=%s %s' % (sPath, sFnName, aXcptInfo[1]));
                self.fRc = False;
        oRc = ErrorCallback();

        #
        # Cleanup.
        #
        for sName in os.listdir(self._oOptions.sScratchRoot):
            sFullName = os.path.join(self._oOptions.sScratchRoot, sName);
            try:
                if os.path.isdir(sFullName):
                    shutil.rmtree(sFullName, False, oRc.onErrorCallback);
                else:
                    os.remove(sFullName);
                if os.path.exists(sFullName):
                    raise Exception('Still exists after deletion, weird.');
            except Exception, oXcpt:
                if    fUseTheForce is True \
                  and utils.getHostOs() not in ['win', 'os2'] \
                  and len(sFullName) >= 8 \
                  and sFullName[0] == '/' \
                  and sFullName[1] != '/' \
                  and sFullName.find('/../') < 0:
                    fnLog('Problems deleting "%s" (%s) using the force...' % (sFullName, oXcpt));
                    try:
                        if os.path.isdir(sFullName):
                            iRc = utils.sudoProcessCall(['/bin/rm', '-Rf', sFullName])
                        else:
                            iRc = utils.sudoProcessCall(['/bin/rm', '-f', sFullName])
                        if iRc != 0:
                            raise Exception('exit code %s' % iRc);
                        if os.path.exists(sFullName):
                            raise Exception('Still exists after forced deletion, weird^2.');
                    except:
                        fnLog('Error sudo deleting "%s": %s' % (sFullName, oXcpt));
                        oRc.fRc = False;
                else:
                    fnLog('Error deleting "%s": %s' % (sFullName, oXcpt));
                    oRc.fRc = False;

        # Display files left behind.
        def dirEnumCallback(sName, oStat):
            """ callback for dirEnumerateTree """
            fnLog(u'%s %s' % (utils.formatFileStat(oStat) if oStat is not None else '????????????', sName));
        utils.dirEnumerateTree(self._oOptions.sScratchRoot, dirEnumCallback);

        #
        # Re-create the directories.
        #
        for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]:
            if not os.path.isdir(sDir):
                try:
                    os.makedirs(sDir, 0700);
                except Exception, oXcpt:
                    fnLog('Error creating "%s": %s' % (sDir, oXcpt));
                    oRc.fRc = False;
    def _reinitScratch(self, fnLog, fUseTheForce):
        """
        Wipes the scratch directories and re-initializes them.

        No exceptions raise, returns success indicator instead.
        """
        if fUseTheForce is None:
            fUseTheForce = self._fFirstSignOn;

        class ErrorCallback(object): # pylint: disable=R0903
            """
            Callbacks + state for the cleanup.
            """
            def __init__(self):
                self.fRc = True;
            def onErrorCallback(self, sFnName, sPath, aXcptInfo):
                """ Logs error during shutil.rmtree operation. """
                fnLog('Error removing "%s": fn=%s %s' % (sPath, sFnName, aXcptInfo[1]));
                self.fRc = False;
        oRc = ErrorCallback();

        #
        # Cleanup.
        #
        for sName in os.listdir(self._oOptions.sScratchRoot):
            sFullName = os.path.join(self._oOptions.sScratchRoot, sName);
            try:
                if os.path.isdir(sFullName):
                    shutil.rmtree(sFullName, False, oRc.onErrorCallback);
                else:
                    os.remove(sFullName);
                if os.path.exists(sFullName):
                    raise Exception('Still exists after deletion, weird.');
            except Exception as oXcpt:
                if    fUseTheForce is True \
                  and utils.getHostOs() not in ['win', 'os2'] \
                  and len(sFullName) >= 8 \
                  and sFullName[0] == '/' \
                  and sFullName[1] != '/' \
                  and sFullName.find('/../') < 0:
                    fnLog('Problems deleting "%s" (%s) using the force...' % (sFullName, oXcpt));
                    try:
                        if os.path.isdir(sFullName):
                            iRc = utils.sudoProcessCall(['/bin/rm', '-Rf', sFullName])
                        else:
                            iRc = utils.sudoProcessCall(['/bin/rm', '-f', sFullName])
                        if iRc != 0:
                            raise Exception('exit code %s' % iRc);
                        if os.path.exists(sFullName):
                            raise Exception('Still exists after forced deletion, weird^2.');
                    except:
                        fnLog('Error sudo deleting "%s": %s' % (sFullName, oXcpt));
                        oRc.fRc = False;
                else:
                    fnLog('Error deleting "%s": %s' % (sFullName, oXcpt));
                    oRc.fRc = False;

        # Display files left behind.
        def dirEnumCallback(sName, oStat):
            """ callback for dirEnumerateTree """
            fnLog(u'%s %s' % (utils.formatFileStat(oStat) if oStat is not None else '????????????', sName));
        utils.dirEnumerateTree(self._oOptions.sScratchRoot, dirEnumCallback);

        #
        # Re-create the directories.
        #
        for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]:
            if not os.path.isdir(sDir):
                try:
                    os.makedirs(sDir, 0o700);
                except Exception as oXcpt:
                    fnLog('Error creating "%s": %s' % (sDir, oXcpt));
                    oRc.fRc = False;

        if oRc.fRc is True:
            self._cReinitScratchErrors = 0;
        else:
            self._cReinitScratchErrors += 1;
        return oRc.fRc;