diff options
Diffstat (limited to '')
17 files changed, 6276 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/testboxscript/Makefile.kmk b/src/VBox/ValidationKit/testboxscript/Makefile.kmk new file mode 100644 index 00000000..70c892fd --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/Makefile.kmk @@ -0,0 +1,87 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - TestBox Script. +# + +# +# Copyright (C) 2012-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +# +# The TestBox script. +# +INSTALLS += testboxscript +testboxscript_TEMPLATE = VBoxValidationKitR3 +testboxscript_INST = $(INST_TESTBOXSCRIPT)testboxscript/ +testboxscript_EXEC_SOURCES = \ + testboxscript.py \ + $(testboxscript_0_OUTDIR)/testboxscript_real.py \ + setup.sh +$(call VBOX_EDIT_VERSION_RULE_FN,testboxscript,testboxscript_real.py) + +testboxscript_SOURCES = \ + testboxcommand.py \ + testboxcommons.py \ + testboxconnection.py \ + testboxtasks.py \ + testboxupgrade.py + +testboxscript_SOURCES.darwin = \ + darwin/setup-routines.sh=>darwin/setup-routines.sh + +testboxscript_EXEC_SOURCES.linux = \ + linux/testboxscript-service.sh=>linux/testboxscript-service.sh +testboxscript_SOURCES.linux = \ + ../../Installer/linux/routines.sh=>linux/setup-installer-routines.sh \ + linux/setup-routines.sh=>linux/setup-routines.sh + +testboxscript_SOURCES.solaris = \ + solaris/setup-routines.sh=>solaris/setup-routines.sh \ + +testboxscript_SOURCES.win = \ + win/autoexec-testbox.cmd=>win/autoexec-testbox.cmd \ + win/readme.txt=>win/readme.txt \ + $(if $(VBOX_OSE),,win/fix_stale_refs.py=>win/fix_stale_refs.py) + + +# +# Helper program, mostly for obtaining system information. +# +PROGRAMS += TestBoxHelper +TestBoxHelper_TEMPLATE = VBoxValidationKitR3 +TestBoxHelper_INST = $(INST_TESTBOXSCRIPT)$(KBUILD_TARGET)/$(KBUILD_TARGET_ARCH)/ +TestBoxHelper_SOURCES = TestBoxHelper.cpp +TestBoxHelper_LIBS.win = $(PATH_SDK_$(VBOX_WINDDK)_LIB)/wbemuuid.lib +TestBoxHelper_LDFLAGS.darwin = -framework CoreFoundation +TestBoxHelper_VBOX_IMPORT_CHECKER.win.x86 = $(NO_SUCH_VARIABLE) + + +# +# Generate pylint & pychecker targets. +# +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp b/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp new file mode 100644 index 00000000..12cd38f4 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp @@ -0,0 +1,769 @@ +/* $Id: TestBoxHelper.cpp $ */ +/** @file + * VirtualBox Validation Kit - Testbox C Helper Utility. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/buildconfig.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/mp.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/x86.h> +# include <iprt/asm-amd64-x86.h> +#endif + +#ifdef RT_OS_DARWIN +# include <sys/types.h> +# include <sys/sysctl.h> +#endif + + + +/** + * Does one free space wipe, using the given filename. + * + * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (fully + * bitched). + * @param pszFilename The filename to use for wiping free space. Will be + * replaced and afterwards deleted. + * @param pvFiller The filler block buffer. + * @param cbFiller The size of the filler block buffer. + * @param cbMinLeftOpt When to stop wiping. + */ +static RTEXITCODE doOneFreeSpaceWipe(const char *pszFilename, void const *pvFiller, size_t cbFiller, uint64_t cbMinLeftOpt) +{ + /* + * Open the file. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pszFilename, + RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE_REPLACE | (0775 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + /* + * Query the amount of available free space. Figure out which API we should use. + */ + RTFOFF cbTotal = 0; + RTFOFF cbFree = 0; + rc = RTFileQueryFsSizes(hFile, &cbTotal, &cbFree, NULL, NULL); + bool const fFileHandleApiSupported = rc != VERR_NOT_SUPPORTED && rc != VERR_NOT_IMPLEMENTED; + if (!fFileHandleApiSupported) + rc = RTFsQuerySizes(pszFilename, &cbTotal, &cbFree, NULL, NULL); + if (RT_SUCCESS(rc)) + { + RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free\n", pszFilename, cbFree / _1M, cbTotal / _1M); + + /* + * Start filling up the free space, down to the last 32MB. + */ + uint64_t const nsStart = RTTimeNanoTS(); /* for speed calcs */ + uint64_t nsStat = nsStart; /* for speed calcs */ + uint64_t cbStatWritten = 0; /* for speed calcs */ + RTFOFF const cbMinLeft = RT_MAX(cbMinLeftOpt, cbFiller * 2); + RTFOFF cbLeftToWrite = cbFree - cbMinLeft; + uint64_t cbWritten = 0; + uint32_t iLoop = 0; + while (cbLeftToWrite >= (RTFOFF)cbFiller) + { + rc = RTFileWrite(hFile, pvFiller, cbFiller, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_DISK_FULL) + RTPrintf("%s: Disk full after writing %'9RU64 MiB\n", pszFilename, cbWritten / _1M); + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Write error after %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, rc); + break; + } + + /* Flush every now and then as we approach a completely full disk. */ + if (cbLeftToWrite <= _1G && (iLoop & (cbLeftToWrite > _128M ? 15 : 3)) == 0) + RTFileFlush(hFile); + + /* + * Advance and maybe recheck the amount of free space. + */ + cbWritten += cbFiller; + cbLeftToWrite -= (ssize_t)cbFiller; + iLoop++; + if ((iLoop & (16 - 1)) == 0 || cbLeftToWrite < _256M) + { + RTFOFF cbFreeUpdated; + if (fFileHandleApiSupported) + rc = RTFileQueryFsSizes(hFile, NULL, &cbFreeUpdated, NULL, NULL); + else + rc = RTFsQuerySizes(pszFilename, NULL, &cbFreeUpdated, NULL, NULL); + if (RT_SUCCESS(rc)) + { + cbFree = cbFreeUpdated; + cbLeftToWrite = cbFree - cbMinLeft; + } + else + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to query free space after %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, rc); + break; + } + if ((iLoop & (512 - 1)) == 0) + { + uint64_t const nsNow = RTTimeNanoTS(); + uint64_t cNsInterval = nsNow - nsStat; + uint64_t cbInterval = cbWritten - cbStatWritten; + uint64_t cbIntervalPerSec = cbInterval ? (uint64_t)(cbInterval / (cNsInterval / (double)RT_NS_1SEC)) : 0; + + RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free after writing %'9RU64 MiB (%'5RU64 MiB/s)\n", + pszFilename, cbFree / _1M, cbTotal / _1M, cbWritten / _1M, cbIntervalPerSec / _1M); + nsStat = nsNow; + cbStatWritten = cbWritten; + } + } + } + + /* + * Now flush the file and then reduce the size a little before closing + * it so the system won't entirely run out of space. The flush should + * ensure the data has actually hit the disk. + */ + rc = RTFileFlush(hFile); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Flush failed at %'RU64 bytes: %Rrc\n", pszFilename, cbWritten, rc); + + uint64_t cbReduced = cbWritten > _512M ? cbWritten - _512M : cbWritten / 2; + rc = RTFileSetSize(hFile, cbReduced); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to reduce file size from %'RU64 to %'RU64 bytes: %Rrc\n", + pszFilename, cbWritten, cbReduced, rc); + + /* Issue a summary statements. */ + uint64_t cNsElapsed = RTTimeNanoTS() - nsStart; + uint64_t cbPerSec = cbWritten ? (uint64_t)(cbWritten / (cNsElapsed / (double)RT_NS_1SEC)) : 0; + RTPrintf("%s: Wrote %'RU64 MiB in %'RU64 s, avg %'RU64 MiB/s.\n", + pszFilename, cbWritten / _1M, cNsElapsed / RT_NS_1SEC, cbPerSec / _1M); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Initial free space query failed: %Rrc \n", pszFilename, rc); + + RTFileClose(hFile); + + /* + * Delete the file. + */ + rc = RTFileDelete(pszFilename); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Delete failed: %Rrc !!\n", pszFilename, rc); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Open failed: %Rrc\n", pszFilename, rc); + return rcExit; +} + + +/** + * Wipes free space on one or more volumes by creating large files. + */ +static RTEXITCODE handlerWipeFreeSpace(int argc, char **argv) +{ + /* + * Parse arguments. + */ + const char *apszDefFiles[2] = { "./wipefree.spc", NULL }; + bool fAll = false; + uint32_t u32Filler = UINT32_C(0xf6f6f6f6); + uint64_t cbMinLeftOpt = _32M; + + static RTGETOPTDEF const s_aOptions[] = + { + { "--all", 'a', RTGETOPT_REQ_NOTHING }, + { "--filler", 'f', RTGETOPT_REQ_UINT32 }, + { "--min-free", 'm', RTGETOPT_REQ_UINT64 }, + }; + RTGETOPTSTATE State; + RTGetOptInit(&State, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); + RTGETOPTUNION ValueUnion; + int chOpt; + while ( (chOpt = RTGetOpt(&State, &ValueUnion)) != 0 + && chOpt != VINF_GETOPT_NOT_OPTION) + { + switch (chOpt) + { + case 'a': + fAll = true; + break; + case 'f': + u32Filler = ValueUnion.u32; + break; + case 'm': + cbMinLeftOpt = ValueUnion.u64; + break; + case 'h': + RTPrintf("usage: wipefrespace [options] [filename1 [..]]\n" + "\n" + "Options:\n" + " -a, --all\n" + " Try do the free space wiping on all seemingly relevant file systems.\n" + " Changes the meaning of the filenames " + " This is not yet implemented\n" + " -p, --filler <32-bit value>\n" + " What to fill the blocks we write with.\n" + " Default: 0xf6f6f6f6\n" + " -m, --min-free <64-bit byte count>\n" + " Specifies when to stop in terms of free disk space (in bytes).\n" + " Default: 32MB\n" + "\n" + "Zero or more names of files to do the free space wiping thru can be given.\n" + "When --all is NOT used, each of the files are used to do free space wiping on\n" + "the volume they will live on. However, when --all is in effect the files are\n" + "appended to the volume mountpoints and only the first that can be created will\n" + "be used. Files (used ones) will be removed when done.\n" + "\n" + "If no filename is given, the default is: %s\n" + , apszDefFiles[0]); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } + + char **papszFiles; + if (chOpt == 0) + papszFiles = (char **)apszDefFiles; + else + papszFiles = RTGetOptNonOptionArrayPtr(&State); + + /* + * Allocate and prep a memory which we'll write over and over again. + */ + uint32_t cbFiller = _2M; + uint32_t *pu32Filler = (uint32_t *)RTMemPageAlloc(cbFiller); + while (!pu32Filler) + { + cbFiller <<= 1; + if (cbFiller >= _4K) + pu32Filler = (uint32_t *)RTMemPageAlloc(cbFiller); + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTMemPageAlloc failed for sizes between 4KB and 2MB!\n"); + } + for (uint32_t i = 0; i < cbFiller / sizeof(pu32Filler[0]); i++) + pu32Filler[i] = u32Filler; + + /* + * Do the requested work. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + if (!fAll) + { + for (uint32_t iFile = 0; papszFiles[iFile] != NULL; iFile++) + { + RTEXITCODE rcExit2 = doOneFreeSpaceWipe(papszFiles[iFile], pu32Filler, cbFiller, cbMinLeftOpt); + if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + } + else + { + /* + * Reject --all for now. + */ + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "The --all option is not yet implemented!\n"); + } + + RTMemPageFree(pu32Filler, cbFiller); + return rcExit; +} + + +/** + * Generates a kind of report of the hardware, software and whatever else we + * think might be useful to know about the testbox. + */ +static RTEXITCODE handlerReport(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + /* + * For now, a simple CPUID dump. Need to figure out how to share code + * like this with other bits, putting it in IPRT. + */ + RTPrintf("CPUID Dump\n" + "Leaf eax ebx ecx edx\n" + "---------------------------------------------\n"); + static uint32_t const s_auRanges[] = + { + UINT32_C(0x00000000), + UINT32_C(0x80000000), + UINT32_C(0x80860000), + UINT32_C(0xc0000000), + UINT32_C(0x40000000), + }; + for (uint32_t iRange = 0; iRange < RT_ELEMENTS(s_auRanges); iRange++) + { + uint32_t const uFirst = s_auRanges[iRange]; + + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuIdExSlow(uFirst, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); + if (uEax >= uFirst && uEax < uFirst + 100) + { + uint32_t const cLeafs = RT_MIN(uEax - uFirst + 1, 32); + for (uint32_t iLeaf = 0; iLeaf < cLeafs; iLeaf++) + { + uint32_t uLeaf = uFirst + iLeaf; + ASMCpuIdExSlow(uLeaf, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); + + /* Clear APIC IDs to avoid submitting new reports all the time. */ + if (uLeaf == 1) + uEbx &= UINT32_C(0x00ffffff); + if (uLeaf == 0xb) + uEdx = 0; + if (uLeaf == 0x8000001e) + uEax = 0; + + /* Clear some other node/cpu/core/thread ids. */ + if (uLeaf == 0x8000001e) + { + uEbx &= UINT32_C(0xffffff00); + uEcx &= UINT32_C(0xffffff00); + } + + RTPrintf("%08x: %08x %08x %08x %08x\n", uLeaf, uEax, uEbx, uEcx, uEdx); + } + } + } + RTPrintf("\n"); + + /* + * DMI info. + */ + RTPrintf("DMI Info\n" + "--------\n"); + static const struct { const char *pszName; RTSYSDMISTR enmDmiStr; } s_aDmiStrings[] = + { + { "Product Name", RTSYSDMISTR_PRODUCT_NAME }, + { "Product version", RTSYSDMISTR_PRODUCT_VERSION }, + { "Product UUID", RTSYSDMISTR_PRODUCT_UUID }, + { "Product Serial", RTSYSDMISTR_PRODUCT_SERIAL }, + { "System Manufacturer", RTSYSDMISTR_MANUFACTURER }, + }; + for (uint32_t iDmiString = 0; iDmiString < RT_ELEMENTS(s_aDmiStrings); iDmiString++) + { + char szTmp[4096]; + RT_ZERO(szTmp); + int rc = RTSystemQueryDmiString(s_aDmiStrings[iDmiString].enmDmiStr, szTmp, sizeof(szTmp) - 1); + if (RT_SUCCESS(rc)) + RTPrintf("%25s: %s\n", s_aDmiStrings[iDmiString].pszName, RTStrStrip(szTmp)); + else + RTPrintf("%25s: %s [rc=%Rrc]\n", s_aDmiStrings[iDmiString].pszName, RTStrStrip(szTmp), rc); + } + RTPrintf("\n"); + +#else +#endif + + /* + * Dump the environment. + */ + RTPrintf("Environment\n" + "-----------\n"); + RTENV hEnv; + int rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + uint32_t cVars = RTEnvCountEx(hEnv); + for (uint32_t iVar = 0; iVar < cVars; iVar++) + { + char szVar[1024]; + char szValue[16384]; + rc = RTEnvGetByIndexEx(hEnv, iVar, szVar, sizeof(szVar), szValue, sizeof(szValue)); + + /* zap the value of variables that are subject to change. */ + if ( (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) + && ( !strcmp(szVar, "TESTBOX_SCRIPT_REV") + || !strcmp(szVar, "TESTBOX_ID") + || !strcmp(szVar, "TESTBOX_SCRATCH_SIZE") + || !strcmp(szVar, "TESTBOX_TIMEOUT") + || !strcmp(szVar, "TESTBOX_TIMEOUT_ABS") + || !strcmp(szVar, "TESTBOX_TEST_SET_ID") + ) + ) + strcpy(szValue, "<volatile>"); + + if (RT_SUCCESS(rc)) + RTPrintf("%25s=%s\n", szVar, szValue); + else if (rc == VERR_BUFFER_OVERFLOW) + RTPrintf("%25s=%s [VERR_BUFFER_OVERFLOW]\n", szVar, szValue); + else + RTPrintf("rc=%Rrc\n", rc); + } + RTEnvDestroy(hEnv); + } + + /** @todo enumerate volumes and whatnot. */ + + int cch = RTPrintf("\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the total memory size in bytes. */ +static RTEXITCODE handlerMemSize(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + + uint64_t cb; + int rc = RTSystemQueryTotalRam(&cb); + if (RT_SUCCESS(rc)) + { + int cch = RTPrintf("%llu\n", cb); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + RTPrintf("%Rrc\n", rc); + return RTEXITCODE_FAILURE; +} + +typedef enum { HWVIRTTYPE_NONE, HWVIRTTYPE_VTX, HWVIRTTYPE_AMDV } HWVIRTTYPE; +static HWVIRTTYPE isHwVirtSupported(void) +{ +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + + /* VT-x */ + ASMCpuId(0x00000000, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidStdRange(uEax)) + { + ASMCpuId(0x00000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEcx & X86_CPUID_FEATURE_ECX_VMX) + return HWVIRTTYPE_VTX; + } + + /* AMD-V */ + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidExtRange(uEax)) + { + ASMCpuId(0x80000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEcx & X86_CPUID_AMD_FEATURE_ECX_SVM) + return HWVIRTTYPE_AMDV; + } +#endif + + return HWVIRTTYPE_NONE; +} + +/** Print the 'true' if VT-x or AMD-v is supported, 'false' it not. */ +static RTEXITCODE handlerCpuHwVirt(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + int cch = RTPrintf(isHwVirtSupported() != HWVIRTTYPE_NONE ? "true\n" : "false\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the 'true' if nested paging is supported, 'false' if not and + * 'dunno' if we cannot tell. */ +static RTEXITCODE handlerCpuNestedPaging(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + HWVIRTTYPE enmHwVirt = isHwVirtSupported(); + int fSupported = -1; + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + if (enmHwVirt == HWVIRTTYPE_AMDV) + { + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidExtRange(uEax) && uEax >= 0x8000000a) + { + ASMCpuId(0x8000000a, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & RT_BIT(0) /* AMD_CPUID_SVM_FEATURE_EDX_NESTED_PAGING */) + fSupported = 1; + else + fSupported = 0; + } + } +# if defined(RT_OS_LINUX) + else if (enmHwVirt == HWVIRTTYPE_VTX) + { + /* + * For Intel there is no generic way to query EPT support but on + * Linux we can resort to checking for the EPT flag in /proc/cpuinfo + */ + RTFILE hFileCpu; + int rc = RTFileOpen(&hFileCpu, "/proc/cpuinfo", RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Read enough to fit the first CPU entry in, we only check the first + * CPU as all the others should have the same features. + */ + char szBuf[_4K]; + size_t cbRead = 0; + + RT_ZERO(szBuf); /* Ensure proper termination. */ + rc = RTFileRead(hFileCpu, &szBuf[0], sizeof(szBuf) - 1, &cbRead); + if (RT_SUCCESS(rc)) + { + /* Look for the start of the flags section. */ + char *pszStrFlags = RTStrStr(&szBuf[0], "flags"); + if (pszStrFlags) + { + /* Look for the end as indicated by new line. */ + char *pszEnd = pszStrFlags; + while ( *pszEnd != '\0' + && *pszEnd != '\n') + pszEnd++; + *pszEnd = '\0'; /* Cut off everything after the flags section. */ + + /* + * Search for the ept flag indicating support and the absence meaning + * not supported. + */ + if (RTStrStr(pszStrFlags, "ept")) + fSupported = 1; + else + fSupported = 0; + } + } + RTFileClose(hFileCpu); + } + } +# elif defined(RT_OS_DARWIN) + else if (enmHwVirt == HWVIRTTYPE_VTX) + { + /* + * The kern.hv_support parameter indicates support for the hypervisor API in the + * kernel, which in turn is documented require nested paging and unrestricted + * guest mode. So, if it's there and set we've got nested paging. Howeber, if + * it's there and clear we have not definite answer as it might be due to lack + * of unrestricted guest mode support. + */ + int32_t fHvSupport = 0; + size_t cbOld = sizeof(fHvSupport); + if (sysctlbyname("kern.hv_support", &fHvSupport, &cbOld, NULL, 0) == 0) + { + if (fHvSupport != 0) + fSupported = true; + } + } +# endif +#endif + + int cch = RTPrintf(fSupported == 1 ? "true\n" : fSupported == 0 ? "false\n" : "dunno\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the 'true' if long mode guests are supported, 'false' if not and + * 'dunno' if we cannot tell. */ +static RTEXITCODE handlerCpuLongMode(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + HWVIRTTYPE enmHwVirt = isHwVirtSupported(); + int fSupported = 0; + + if (enmHwVirt != HWVIRTTYPE_NONE) + { +#if defined(RT_ARCH_AMD64) + fSupported = 1; /* We're running long mode, so it must be supported. */ + +#elif defined(RT_ARCH_X86) +# ifdef RT_OS_DARWIN + /* On darwin, we just ask the kernel via sysctl. Rules are a bit different here. */ + int f64bitCapable = 0; + size_t cbParameter = sizeof(f64bitCapable); + int rc = sysctlbyname("hw.cpu64bit_capable", &f64bitCapable, &cbParameter, NULL, NULL); + if (rc != -1) + fSupported = f64bitCapable != 0; + else +# endif + { + /* PAE and HwVirt are required */ + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0x00000000, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidStdRange(uEax)) + { + ASMCpuId(0x00000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & X86_CPUID_FEATURE_EDX_PAE) + { + /* AMD will usually advertise long mode in 32-bit mode. Intel OTOH, + won't necessarily do so. */ + ASMCpuId(0x80000000, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidExtRange(uEax)) + { + ASMCpuId(0x80000001, &uEax, &uEbx, &uEcx, &uEdx); + if (uEdx & X86_CPUID_EXT_FEATURE_EDX_LONG_MODE) + fSupported = 1; + else if (enmHwVirt != HWVIRTTYPE_AMDV) + fSupported = -1; + } + } + } + } +#endif + } + + int cch = RTPrintf(fSupported == 1 ? "true\n" : fSupported == 0 ? "false\n" : "dunno\n"); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** Print the CPU 'revision', if available. */ +static RTEXITCODE handlerCpuRevision(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0, &uEax, &uEbx, &uEcx, &uEdx); + if (ASMIsValidStdRange(uEax) && uEax >= 1) + { + uint32_t uEax1 = ASMCpuId_EAX(1); + uint32_t uVersion = (ASMGetCpuFamily(uEax1) << 24) + | (ASMGetCpuModel(uEax1, ASMIsIntelCpuEx(uEbx, uEcx, uEdx)) << 8) + | ASMGetCpuStepping(uEax1); + int cch = RTPrintf("%#x\n", uVersion); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } +#endif + return RTEXITCODE_FAILURE; +} + + +/** Print the CPU name, if available. */ +static RTEXITCODE handlerCpuName(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + + char szTmp[1024]; + int rc = RTMpGetDescription(NIL_RTCPUID, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(rc)) + { + int cch = RTPrintf("%s\n", RTStrStrip(szTmp)); + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + return RTEXITCODE_FAILURE; +} + + +/** Print the CPU vendor name, 'GenuineIntel' and such. */ +static RTEXITCODE handlerCpuVendor(int argc, char **argv) +{ + NOREF(argc); NOREF(argv); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + uint32_t uEax, uEbx, uEcx, uEdx; + ASMCpuId(0, &uEax, &uEbx, &uEcx, &uEdx); + int cch = RTPrintf("%.04s%.04s%.04s\n", &uEbx, &uEdx, &uEcx); +#else + int cch = RTPrintf("%s\n", RTBldCfgTargetArch()); +#endif + return cch > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * The first argument is a command. Figure out which and call its handler. + */ + static const struct + { + const char *pszCommand; + RTEXITCODE (*pfnHandler)(int argc, char **argv); + bool fNoArgs; + } s_aHandlers[] = + { + { "cpuvendor", handlerCpuVendor, true }, + { "cpuname", handlerCpuName, true }, + { "cpurevision", handlerCpuRevision, true }, + { "cpuhwvirt", handlerCpuHwVirt, true }, + { "nestedpaging", handlerCpuNestedPaging, true }, + { "longmode", handlerCpuLongMode, true }, + { "memsize", handlerMemSize, true }, + { "report", handlerReport, true }, + { "wipefreespace", handlerWipeFreeSpace, false } + }; + + if (argc < 2) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "expected command as the first argument"); + + for (unsigned i = 0; i < RT_ELEMENTS(s_aHandlers); i++) + { + if (!strcmp(argv[1], s_aHandlers[i].pszCommand)) + { + if ( s_aHandlers[i].fNoArgs + && argc != 2) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "the command '%s' does not take any arguments", argv[1]); + return s_aHandlers[i].pfnHandler(argc - 1, argv + 1); + } + } + + /* + * Help or version query? + */ + for (int i = 1; i < argc; i++) + if ( !strcmp(argv[i], "--help") + || !strcmp(argv[i], "-h") + || !strcmp(argv[i], "-?") + || !strcmp(argv[i], "help") ) + { + RTPrintf("usage: %s <cmd> [cmd specific args]\n" + "\n" + "commands:\n", argv[0]); + for (unsigned j = 0; j < RT_ELEMENTS(s_aHandlers); j++) + RTPrintf(" %s\n", s_aHandlers[j].pszCommand); + return RTEXITCODE_FAILURE; + } + else if ( !strcmp(argv[i], "--version") + || !strcmp(argv[i], "-V") ) + { + RTPrintf("%sr%u", RTBldCfgVersion(), RTBldCfgRevision()); + return argc == 2 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + } + + /* + * Syntax error. + */ + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "unknown command '%s'", argv[1]); +} + diff --git a/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh new file mode 100644 index 00000000..2ada0506 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh @@ -0,0 +1,168 @@ +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Mac OS X (darwin). +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +MY_CONFIG_FILE=/Library/LaunchDaemons/org.virtualbox.testboxscript.plist + +## +# Loads config values from the current installation. +# +os_load_config() { + if [ -r "${MY_CONFIG_FILE}" ]; then + # User. + MY_TMP=`/usr/bin/tr '\n' ' ' < "${MY_CONFIG_FILE}" \ + | /usr/bin/sed \ + -e 's/ */ /g' \ + -e 's|\(</[[:alnum:]]*>\)<|\1 <|g' \ + -e 's|^.*<key>UserName</key> *<string>\([^<>]*\)</string>.*$|\1|'`; + if [ -n "${MY_TMP}" ]; then + TESTBOXSCRIPT_USER="${MY_TMP}"; + fi + + # Arguments. + XMLARGS=`/usr/bin/tr '\n' ' ' < "${MY_CONFIG_FILE}" \ + | /usr/bin/sed \ + -e 's/ */ /g' \ + -e 's|\(</[[:alnum:]]*>\)<|\1 <|g' \ + -e 's|^.*ProgramArguments</key> *<array> *\(.*\)</array>.*$|\1|'`; + eval common_testboxscript_args_to_config `echo "${XMLARGS}" | sed -e "s/<string>/'/g" -e "s/<\/string>/'/g" `; + fi +} + +## +# Adds an argument ($1) to MY_ARGV (XML plist format). +# +os_add_args() { + while [ $# -gt 0 ]; + do + case "$1" in + *\<* | *\>* | *\&*) + MY_TMP='`echo "$1" | sed -e 's/&/&/g' -e 's/</</g' -e 's/>/>/g'`'; + MY_ARGV="${MY_ARGV} <string>${MY_TMP}</string>"; + ;; + *) + MY_ARGV="${MY_ARGV} <string>$1</string>"; + ;; + esac + shift; + done + MY_ARGV="${MY_ARGV}"' + '; + return 0; +} + +os_install_service() { + # Calc the command line. + MY_ARGV="" + common_compile_testboxscript_command_line + + + # Note! It's not possible to use screen 4.0.3 with the launchd due to buggy + # "setsid off" handling (and possible other things). + cat > "${MY_CONFIG_FILE}" <<EOF +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>Label</key> <string>org.virtualbox.testboxscript</string> + <key>UserName</key> <string>${TESTBOXSCRIPT_USER}</string> + <key>WorkingDirectory</key> <string>${TESTBOXSCRIPT_DIR}</string> + <key>Enabled</key> <true/> + <key>RunAtLoad</key> <true/> + <key>KeepAlive</key> <true/> + <key>StandardInPath</key> <string>/dev/null</string> + <key>StandardOutPath</key> <string>/dev/null</string> + <key>StandardErrorPath</key> <string>/dev/null</string> + <key>ProgramArguments</key> + <array> + ${MY_ARGV}</array> +</dict> +</plist> +EOF + + return 0; +} + +os_enable_service() { + launchctl load -w "${MY_CONFIG_FILE}" + return 0; +} + +os_disable_service() { + if [ -r "${MY_CONFIG_FILE}" ]; then + launchctl unload "${MY_CONFIG_FILE}" + fi + return 0; +} + +os_add_user() { + NEWUID=$(expr `dscl . -readall /Users UniqueID | sed -ne 's/UniqueID: *\([0123456789]*\) *$/\1/p' | sort -n | tail -1 ` + 1) + if [ -z "$NEWUID" -o "${NEWUID}" -lt 502 ]; then + NEWUID=502; + fi + + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" UserShell /bin/bash + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" RealName "VBox Test User" + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" UniqueID ${NEWUID} + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" PrimaryGroupID 80 + dscl . -create "/Users/${TESTBOXSCRIPT_USER}" NFSHomeDirectory "/Users/vbox" + dscl . -passwd "/Users/${TESTBOXSCRIPT_USER}" "password" + mkdir -p "/Users/${TESTBOXSCRIPT_USER}" +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Change the 'Energy Saver' options to never turn off the computer: + $ systemsetup -setcomputersleep Never -setdisplaysleep 5 -setharddisksleep 15 + 2. Check 'Restart automatically if the computer freezes' if available in + the 'Energy Saver' settings. + $ systemsetup -setrestartfreeze on + 3. In the 'Sharing' panel enable (VBox/Oracle): + a) 'Remote Login' so ssh works. + $ systemsetup -setremotelogin on + b) 'Remote Management, tick all the checkboxes in the sheet dialog. + Open the 'Computer Settings' and check 'Show Remote Management + status in menu bar', 'Anyone may request permission to control + screen' and 'VNC viewers may control screen with password'. Set the + VNC password to 'password'. + 4. Make sure the proxy is configured correctly for your network by going to + the 'Network' panel, open 'Advanced...'. For Oracle this means 'TCP/IP' + should be configured by 'DHCP' (IPv4) and 'automatically' (IPv6), and + the 'Proxies' tab should have 'Automatic Proxy Configuration' checked + with the URL containing 'http://wpad.oracle.com/wpad.dat'. (Make sure + to hit OK to close the dialog.) + 5. Configure NTP to the nearest local time source. For VBox/Oracle this + means wei01-time.de.oracle.com: + $ systemsetup -setnetworktimeserver wei01-time.de.oracle.com + 6. Configure the vbox (pw:password) account for automatic login. + 7. Configure the kernel to keep symbols: + $ sudo nvram boot-args="keepsyms=1" + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh new file mode 100755 index 00000000..387f8691 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh @@ -0,0 +1,162 @@ +#!/bin/sh +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + + +# Load the routines we share with the linux installer. +if test ! -r "${DIR}/linux/setup-installer-routines.sh" -a -r "${DIR}/../../Installer/linux/routines.sh"; then + . "${DIR}/../../Installer/linux/routines.sh" +else + . "${DIR}/linux/setup-installer-routines.sh" +fi + + +os_load_config() { + if [ -d /etc/conf.d/ ]; then + MY_CONFIG_FILE="/etc/conf.d/testboxscript" + elif [ -d /etc/default/ ]; then + MY_CONFIG_FILE="/etc/default/testboxscript" + else + echo "Port me!" + exit 1; + fi + if [ -r "${MY_CONFIG_FILE}" ]; then + . "${MY_CONFIG_FILE}" + fi +} + +os_install_service() { + # + # Install the runlevel script. + # + install_init_script "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" "testboxscript-service" + set +e + delrunlevel "testboxscript-service" > /dev/null 2>&1 + addrunlevel "testboxscript-service" 90 10 + set -e + + # + # Install the configuration file. + # + echo "# Generated by $0." > "${MY_CONFIG_FILE}" + for var in ${TESTBOXSCRIPT_CFG_NAMES}; + do + varcfg=TESTBOXSCRIPT_${var} + vardef=TESTBOXSCRIPT_DEFAULT_${var} + if [ "${!varcfg}" = "${!vardef}" ]; then + echo "# using default value: ${varcfg}=${!varcfg}" >> "${MY_CONFIG_FILE}" + else + echo "${varcfg}=${!varcfg}" >> "${MY_CONFIG_FILE}" + fi + done + + # Work around a bug with arrays in old bash versions. + if [ ${#TESTBOXSCRIPT_ENVVARS[@]} -ne 0 ]; then + set | sed -n -e '/^TESTBOXSCRIPT_ENVVARS=/p' >> "${MY_CONFIG_FILE}" + fi + return 0; +} + +os_enable_service() { + start_init_script testboxscript-service + return 0; +} + +os_disable_service() { + stop_init_script testboxscript-service 2>&1 || true # Ignore + return 0; +} + +os_add_user() { + ADD_GROUPS="" + if ! grep -q wheel /etc/group; then + ADD_GROUPS="-G wheel" + fi + set -e + useradd -m -U -p password -s /bin/bash ${ADD_GROUPS} "${TESTBOXSCRIPT_USER}" + set +e + return 0; +} + +check_for_cifs() { + test -x /sbin/mount.cifs -o -x /usr/sbin/mount.cifs + grep -wq cifs /proc/filesystems || modprobe cifs; + # Note! If modprobe doesn't work above, /sbin and /usr/sbin are probably missing from the search PATH. + return 0; +} + +## +# Test if core dumps are enabled. See https://wiki.ubuntu.com/Apport! +# +test_coredumps() { + if test "`lsb_release -is`" = "Ubuntu"; then + if grep -q "apport" /proc/sys/kernel/core_pattern; then + if grep -q "#.*problem_types" /etc/apport/crashdb.conf; then + echo "It looks like core dumps are properly configured, good!" + else + echo "Warning: Core dumps will be not always generated!" + fi + else + echo "Warning: Apport not installed! This package is required for core dump handling!" + fi + fi +} + +## +# Test if unattended updates are disabled. See +# http://ask.xmodulo.com/disable-automatic-updates-ubuntu.html +test_unattended_updates_disabled() { + if grep "APT::Periodic::Unattended-Upgrade.*1" /etc/apt/apt.conf.d/* 2>/dev/null; then + echo "Unattended updates enabled?" + return 1 + fi + if grep "APT::Periodic::Update-Package-List.*1" /etc/apt/apt.conf.d/* 2>/dev/null; then + echo "Unattended package updates enabled?" + return 1 + fi +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Check if the proxy settings are appropriate for reaching the test + manager host. Python does not support domain matches starting with ".". + + For Debian and Ubuntu: check /etc/environment. + For EL: check /etc/profile and/or the files in /etc/profile.d/. + + 2. If the system should be doing RAM disk based testing, add the following + (or something similar, adapted to the system) to /etc/fstab: + + tmpfs /var/tmp/testbox-1000 tmpfs defaults,size=16G 0 0 + +After making such adjustments, it's the easiest solution to reboot the testbox. + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh b/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh new file mode 100755 index 00000000..61c93365 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh @@ -0,0 +1,501 @@ +#!/bin/sh +## @file +# VirtualBox Validation Kit - TestBoxScript service init script. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# chkconfig: 35 35 65 +# description: TestBoxScript service +# +### BEGIN INIT INFO +# Provides: testboxscript-service +# Required-Start: $network +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: TestBoxScript service +### END INIT INFO + + +PATH=$PATH:/bin:/sbin:/usr/sbin + +# +# Load config and set up defaults. +# +service_name="testboxscript" + +[ -r /etc/default/${service_name} ] && . /etc/default/${service_name} +[ -r /etc/conf.d/${service_name} ] && . /etc/conf.d/${service_name} + +if [ -z "${TESTBOXSCRIPT_DIR}" ]; then + TESTBOXSCRIPT_DIR="/opt/testboxscript" +fi +if [ -z "${TESTBOXSCRIPT_USER}" ]; then + TESTBOXSCRIPT_USER="vbox" +fi +binary="${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" +binary_real="${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" + + + +# +# Detect and abstract distro +# +[ -f /etc/debian_release -a -f /lib/lsb/init-functions ] || NOLSB=yes + +system=unknown +if [ -f /etc/redhat-release ]; then + system=redhat + PIDFILE="/var/run/${service_name}-service.pid" +elif [ -f /etc/SuSE-release ]; then + system=suse + PIDFILE="/var/lock/subsys/${service_name}-service" +elif [ -f /etc/debian_version ]; then + system=debian + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/gentoo-release ]; then + system=gentoo + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/arch-release ]; then + system=arch + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/slackware-version ]; then + system=slackware + PIDFILE="/var/run/${service_name}-service" +elif [ -f /etc/lfs-release ]; then + system=lfs + PIDFILE="/var/run/${service_name}-service.pid" +else + system=other + if [ -d /var/run -a -w /var/run ]; then + PIDFILE="/var/run/${service_name}-service" + fi +fi + + +# +# Generic implementation. +# + +## Query daemon status. +# $1 = daemon-user; $2 = binary name +# returns 0 if running, 1 if started but no longer running, 3 if not started. +# When 0 is return the pid variable contains a list of relevant pids. +my_query_status() { + a_USER="$1"; + a_BINARY="$2"; + pid=""; + if [ -f "${PIDFILE}" -a -s "${PIDFILE}" ]; then + MY_LINE=""; + read MY_LINE < "${PIDFILE}"; + for MY_PID in `echo $MY_LINE | sed -e 's/[^0123456789 ]/ /g'`; + do + if [ "`stat -c '%U' /proc/$MY_PID 2> /dev/null `" = "$a_USER" ]; then + pid="${pid} ${MY_PID}"; + fi + done + if [ -n "${pid}" ]; then + RETVAL=0; + else + RETVAL=1; + fi + else + RETVAL=3 + fi + return $RETVAL; +} + +## Starts detached daeamon in screen. +# $1 = daemon-user; $2+ = daemon and its arguments +my_start_daemon() { + a_USER="$1" + shift + if touch "${PIDFILE}" && chown "${a_USER}" -- "${PIDFILE}"; then + ARGS="" + while [ $# -gt 0 ]; + do + ARGS="$ARGS '$1'"; + shift + done + ARGS="$ARGS --pidfile '$PIDFILE'"; + su - "${a_USER}" -c "screen -S ${service_name} -d -m ${ARGS} "; + RETVAL=$?; + if [ $RETVAL -eq 0 ]; then + sleep 0.6; + if [ ! -s "$PIDFILE" ]; then sleep 1; fi + if [ ! -s "$PIDFILE" ]; then sleep 2; fi + if [ ! -s "$PIDFILE" ]; then sleep 3; fi + if [ -s "$PIDFILE" ]; then + RETVAL=0; + else + RETVAL=1; + fi + else + fail_msg "su failed with exit code $RETVAL"; + fi + else + fail_msg "Failed to create pid file and change it's ownership to ${a_USER}." + RETVAL=1; + fi + return $RETVAL; +} + +## Stops the daemon. +# $1 = daemon-user; $2 = binary name +my_stop_daemon() { + a_USER="$1"; + a_BINARY="$2"; + my_query_status "$a_USER" "$a_BINARY" + RETVAL=$? + if [ $RETVAL -eq 0 -a -n "$pid" ]; then + kill $pid; + fi + sleep 0.6 + if my_query_status "$a_USER" "$a_BINARY"; then sleep 1; fi + if my_query_status "$a_USER" "$a_BINARY"; then sleep 2; fi + if my_query_status "$a_USER" "$a_BINARY"; then sleep 3; fi + if ! my_query_status "$a_USER" "$a_BINARY"; then + rm -f -- "${PIDFILE}" + return 0; + fi + return 1; +} + +if [ -z "$NOLSB" ]; then + . /lib/lsb/init-functions + fail_msg() { + echo "" + log_failure_msg "$1" + } + succ_msg() { + log_success_msg " done." + } + begin_msg() { + log_daemon_msg "$@" + } +else + fail_msg() { + echo " ...fail!" + echo "$@" + } + succ_msg() { + echo " ...done." + } + begin_msg() { + echo -n "$1" + } +fi + +# +# System specific overrides. +# + +if [ "$system" = "redhat" ]; then + . /etc/init.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + echo_failure + echo + } + succ_msg() { + echo_success + echo + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "suse" ]; then + . /etc/rc.status + if [ -n "$NOLSB" ]; then + fail_msg() { + rc_failed 1 + rc_status -v + } + succ_msg() { + rc_reset + rc_status -v + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "debian" ]; then + # Share my_start_daemon and my_stop_daemon with gentoo + if [ -n "$NOLSB" ]; then + fail_msg() { + echo " ...fail!" + } + succ_msg() { + echo " ...done." + } + begin_msg() { + echo -n "$1" + } + fi +fi + +if [ "$system" = "gentoo" ]; then + if [ -f /sbin/functions.sh ]; then + . /sbin/functions.sh + elif [ -f /etc/init.d/functions.sh ]; then + . /etc/init.d/functions.sh + fi + # Share my_start_daemon and my_stop_daemon with debian. + if [ -n "$NOLSB" ]; then + if [ "`which $0`" = "/sbin/rc" ]; then + shift + fi + fi +fi + +if [ "$system" = "debian" -o "$system" = "gentoo" ]; then + #my_start_daemon() { + # usr="$1" + # shift + # bin="$1" + # shift + # echo usr=$usr + # start-stop-daemon --start --background --pidfile "${PIDFILE}" --make-pidfile --chuid "${usr}" --user "${usr}" \ + # --exec $bin -- $@ + #} + my_stop_daemon() { + a_USER="$1" + a_BINARY="$2" + start-stop-daemon --stop --user "${a_USER}" --pidfile "${PIDFILE}" + RETVAL=$? + rm -f "${PIDFILE}" + return $RETVAL + } +fi + +if [ "$system" = "arch" ]; then + USECOLOR=yes + . /etc/rc.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + stat_fail + } + succ_msg() { + stat_done + } + begin_msg() { + stat_busy "$1" + } + fi +fi + +if [ "$system" = "lfs" ]; then + . /etc/rc.d/init.d/functions + if [ -n "$NOLSB" ]; then + fail_msg() { + echo_failure + } + succ_msg() { + echo_ok + } + begin_msg() { + echo $1 + } + fi +fi + +# +# Implement the actions. +# +check_single_user() { + if [ -n "$2" ]; then + fail_msg "TESTBOXSCRIPT_USER must not contain multiple users!" + exit 1 + fi +} + +# +# Open ports at the firewall: +# 6000..6100 / TCP for VRDP +# 5000..5032 / TCP for netperf +# 5000..5032 / UDP for netperf +# +set_iptables() { + if [ -x /sbin/iptables ]; then + I="/sbin/iptables -j ACCEPT -A INPUT -m state --state NEW" + if ! /sbin/iptables -L INPUT | grep -q "testsuite vrdp"; then + $I -m tcp -p tcp --dport 6000:6100 -m comment --comment "testsuite vrdp" + fi + if ! /sbin/iptables -L INPUT | grep -q "testsuite perftcp"; then + $I -m tcp -p tcp --dport 5000:5032 -m comment --comment "testsuite perftcp" + fi + if ! /sbin/iptables -L INPUT | grep -q "testsuite perfudp"; then + $I -m udp -p udp --dport 5000:5032 -m comment --comment "testsuite perfudp" + fi + fi +} + + +start() { + if [ ! -f "${PIDFILE}" ]; then + begin_msg "Starting TestBoxScript"; + + # + # Verify config and installation. + # + if [ ! -d "$TESTBOXSCRIPT_DIR" -o ! -r "$binary" -o ! -r "$binary_real" ]; then + fail_msg "Cannot find TestBoxScript installation under '$TESTBOXSCRIPT_DIR'!" + exit 0; + fi + ## @todo check ownership (for upgrade purposes) + check_single_user $TESTBOXSCRIPT_USER + + # + # Open some ports in the firewall + # Allows to access VMs remotely by VRDP, netperf + # + set_iptables + + # + # Set execute bits to make installation (unzip) easier. + # + chmod a+x > /dev/null 2>&1 \ + "${binary}" \ + "${binary_real}" \ + "${TESTBOXSCRIPT_DIR}/linux/amd64/TestBoxHelper" \ + "${TESTBOXSCRIPT_DIR}/linux/x86/TestBoxHelper" + + # + # Start the daemon as the specified user. + # + PARAMS="" + if [ "${TESTBOXSCRIPT_HWVIRT}" = "yes" ]; then PARAMS="${PARAMS} --hwvirt"; fi + if [ "${TESTBOXSCRIPT_HWVIRT}" = "no" ]; then PARAMS="${PARAMS} --no-hwvirt"; fi + if [ "${TESTBOXSCRIPT_NESTED_PAGING}" = "yes" ]; then PARAMS="${PARAMS} --nested-paging"; fi + if [ "${TESTBOXSCRIPT_NESTED_PAGING}" = "no" ]; then PARAMS="${PARAMS} --no-nested-paging"; fi + if [ "${TESTBOXSCRIPT_IOMMU}" = "yes" ]; then PARAMS="${PARAMS} --io-mmu"; fi + if [ "${TESTBOXSCRIPT_IOMMU}" = "no" ]; then PARAMS="${PARAMS} --no-io-mmu"; fi + if [ -n "${TESTBOXSCRIPT_SYSTEM_UUID}" ]; then PARAMS="${PARAMS} --system-uuid '${TESTBOXSCRIPT_SYSTEM_UUID}'"; fi + if [ -n "${TESTBOXSCRIPT_TEST_MANAGER}" ]; then PARAMS="${PARAMS} --test-manager '${TESTBOXSCRIPT_TEST_MANAGER}'"; fi + if [ -n "${TESTBOXSCRIPT_SCRATCH_ROOT}" ]; then PARAMS="${PARAMS} --scratch-root '${TESTBOXSCRIPT_SCRATCH_ROOT}'"; fi + + if [ -n "${TESTBOXSCRIPT_BUILDS_PATH}" ]; then PARAMS="${PARAMS} --builds-path '${TESTBOXSCRIPT_BUILDS_PATH}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_TYPE}" ]; then PARAMS="${PARAMS} --builds-server-type '${TESTBOXSCRIPT_BUILDS_TYPE}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_NAME}" ]; then PARAMS="${PARAMS} --builds-server-name '${TESTBOXSCRIPT_BUILDS_NAME}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_SHARE}" ]; then PARAMS="${PARAMS} --builds-server-share '${TESTBOXSCRIPT_BUILDS_SHARE}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_USER}" ]; then PARAMS="${PARAMS} --builds-server-user '${TESTBOXSCRIPT_BUILDS_USER}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_PASSWD}" ]; then PARAMS="${PARAMS} --builds-server-passwd '${TESTBOXSCRIPT_BUILDS_PASSWD}'"; fi + if [ -n "${TESTBOXSCRIPT_BUILDS_MOUNTOPT}" ]; then PARAMS="${PARAMS} --builds-server-mountopt '${TESTBOXSCRIPT_BUILDS_MOUNTOPT}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_PATH}" ]; then PARAMS="${PARAMS} --testrsrc-path '${TESTBOXSCRIPT_TESTRSRC_PATH}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_TYPE}" ]; then PARAMS="${PARAMS} --testrsrc-server-type '${TESTBOXSCRIPT_TESTRSRC_TYPE}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_NAME}" ]; then PARAMS="${PARAMS} --testrsrc-server-name '${TESTBOXSCRIPT_TESTRSRC_NAME}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_SHARE}" ]; then PARAMS="${PARAMS} --testrsrc-server-share '${TESTBOXSCRIPT_TESTRSRC_SHARE}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_USER}" ]; then PARAMS="${PARAMS} --testrsrc-server-user '${TESTBOXSCRIPT_TESTRSRC_USER}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_PASSWD}" ]; then PARAMS="${PARAMS} --testrsrc-server-passwd '${TESTBOXSCRIPT_TESTRSRC_PASSWD}'"; fi + if [ -n "${TESTBOXSCRIPT_TESTRSRC_MOUNTOPT}" ]; then PARAMS="${PARAMS} --testrsrc-server-mountopt '${TESTBOXSCRIPT_TESTRSRC_MOUNTOPT}'"; fi + + if [ -n "${TESTBOXSCRIPT_PYTHON}" ]; then + my_start_daemon "${TESTBOXSCRIPT_USER}" "${TESTBOXSCRIPT_PYTHON}" "${binary}" ${PARAMS} + else + my_start_daemon "${TESTBOXSCRIPT_USER}" "${binary}" ${PARAMS} + fi + RETVAL=$? + + if [ $RETVAL -eq 0 ]; then + succ_msg + else + fail_msg + fi + else + succ_msg "Already running." + RETVAL=0 + fi + return $RETVAL +} + +stop() { + if [ -f "${PIDFILE}" ]; then + begin_msg "Stopping TestBoxScript"; + my_stop_daemon "${TESTBOXSCRIPT_USER}" "${binary}" + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + succ_msg + else + fail_msg + fi + else + RETVAL=0 + fi + return $RETVAL +} + +restart() { + stop && sleep 1 && start +} + +status() { + echo -n "Checking for TestBoxScript" + my_query_status "${TESTBOXSCRIPT_USER}" "${binary}" + RETVAL=$? + if [ ${RETVAL} -eq 0 ]; then + echo " ...running" + elif [ ${RETVAL} -eq 3 ]; then + echo " ...stopped" + elif [ ${RETVAL} -eq 1 ]; then + echo " ...started but not running" + else + echo " ...unknown status '${RETVAL}'" + fi +} + + +# +# main(). +# +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + force-reload) + restart + ;; + status) + status + ;; + setup) + ;; + cleanup) + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL + diff --git a/src/VBox/ValidationKit/testboxscript/setup.sh b/src/VBox/ValidationKit/testboxscript/setup.sh new file mode 100755 index 00000000..ab838167 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/setup.sh @@ -0,0 +1,694 @@ +#!/usr/bin/env bash +# $Id: setup.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Unixy platforms. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + + +# +# !WARNING! Running the whole script in exit-on-failure mode. +# +# Note! Looking at the ash sources, it seems flags will be saved and restored +# when calling functions. That's comforting. +# +set -e +#set -x # debug only, disable! + +## +# Get the host OS name, returning it in RETVAL. +# +get_host_os() { + RETVAL=`uname` + case "$RETVAL" in + Darwin|darwin) + RETVAL=darwin + ;; + + DragonFly) + RETVAL=dragonfly + ;; + + freebsd|FreeBSD|FREEBSD) + RETVAL=freebsd + ;; + + Haiku) + RETVAL=haiku + ;; + + linux|Linux|GNU/Linux|LINUX) + RETVAL=linux + ;; + + netbsd|NetBSD|NETBSD) + RETVAL=netbsd + ;; + + openbsd|OpenBSD|OPENBSD) + RETVAL=openbsd + ;; + + os2|OS/2|OS2) + RETVAL=os2 + ;; + + SunOS) + RETVAL=solaris + ;; + + WindowsNT|CYGWIN_NT-*) + RETVAL=win + ;; + + *) + echo "$0: unknown os $RETVAL" 1>&2 + exit 1 + ;; + esac + return 0; +} + +## +# Get the host OS/CPU arch, returning it in RETVAL. +# +get_host_arch() { + if [ "${HOST_OS}" = "solaris" ]; then + RETVAL=`isainfo | cut -f 1 -d ' '` + else + RETVAL=`uname -m` + fi + case "${RETVAL}" in + amd64|AMD64|x86_64|k8|k8l|k9|k10) + RETVAL='amd64' + ;; + x86|i86pc|ia32|i[3456789]86|BePC) + RETVAL='x86' + ;; + sparc32|sparc|sparcv8|sparcv7|sparcv8e) + RETVAL='sparc32' + ;; + sparc64|sparcv9) + RETVAL='sparc64' + ;; + s390) + RETVAL='s390' + ;; + s390x) + RETVAL='s390x' + ;; + ppc32|ppc|powerpc) + RETVAL='ppc32' + ;; + ppc64|powerpc64) + RETVAL='ppc64' + ;; + mips32|mips) + RETVAL='mips32' + ;; + mips64) + RETVAL='mips64' + ;; + ia64) + RETVAL='ia64' + ;; + hppa32|parisc32|parisc) + RETVAL='hppa32' + ;; + hppa64|parisc64) + RETVAL='hppa64' + ;; + arm|armv4l|armv5tel|armv5tejl) + RETVAL='arm' + ;; + alpha) + RETVAL='alpha' + ;; + + *) + echo "$0: unknown cpu/arch - $RETVAL" 1>&$2 + exit 1 + ;; + esac + return 0; +} + + +## +# Loads config values from the current installation. +# +os_load_config() { + echo "os_load_config is not implemented" 2>&1 + exit 1 +} + +## +# Installs, configures and starts the service. +# +os_install_service() { + echo "os_install_service is not implemented" 2>&1 + exit 1 +} + +## +# Enables (starts) the service. +os_enable_service() { + echo "os_enable_service is not implemented" 2>&1 + return 0; +} + +## +# Disables (stops) the service. +os_disable_service() { + echo "os_disable_service is not implemented" 2>&1 + return 0; +} + +## +# Adds the testbox user +# +os_add_user() { + echo "os_add_user is not implemented" 2>&1 + exit 1 +} + +## +# Prints a final message after successful script execution. +# This can contain additional instructions which needs to be carried out +# manually or similar. +os_final_message() { + return 0; +} + +## +# Checks the installation, verifying that files are there and scripts work fine. +# +check_testboxscript_install() { + + # Presence + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" + test -r "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" -o "${HOST_OS}" != "linux" + test -r "${TESTBOXSCRIPT_DIR}/${HOST_OS}/${HOST_ARCH}/TestBoxHelper" + + # Zip file may be missing the x bits, so set them. + chmod a+x \ + "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" \ + "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" \ + "${TESTBOXSCRIPT_DIR}/${HOST_OS}/${HOST_ARCH}/TestBoxHelper" \ + "${TESTBOXSCRIPT_DIR}/testboxscript/linux/testboxscript-service.sh" + + + # Check that the scripts work. + set +e + "${TESTBOXSCRIPT_PYTHON}" "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" --version > /dev/null + if [ $? -ne 2 ]; then + echo "$0: error: testboxscript.py didn't respons correctly to the --version option." + exit 1; + fi + + "${TESTBOXSCRIPT_PYTHON}" "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript_real.py" --version > /dev/null + if [ $? -ne 2 ]; then + echo "$0: error: testboxscript.py didn't respons correctly to the --version option." + exit 1; + fi + set -e + + return 0; +} + +## +# Check that sudo is installed. +# +check_for_sudo() { + which sudo + test -f "${MY_ETC_SUDOERS}" +} + +## +# Check that sudo is installed. +# +check_for_cifs() { + return 0; +} + +## +# Checks if the testboxscript_user exists. +does_testboxscript_user_exist() { + id "${TESTBOXSCRIPT_USER}" > /dev/null 2>&1 + return $?; +} + +## +# hushes up the root login. +maybe_hush_up_root_login() { + # This is a solaris hook. + return 0; +} + +## +# Adds the testbox user and make sure it has unrestricted sudo access. +maybe_add_testboxscript_user() { + if ! does_testboxscript_user_exist; then + os_add_user "${TESTBOXSCRIPT_USER}" + fi + + SUDOERS_LINE="${TESTBOXSCRIPT_USER} ALL=(ALL) NOPASSWD: ALL" + if ! ${MY_FGREP} -q "${SUDOERS_LINE}" ${MY_ETC_SUDOERS}; then + echo "# begin tinderboxscript setup.sh" >> ${MY_ETC_SUDOERS} + echo "${SUDOERS_LINE}" >> ${MY_ETC_SUDOERS} + echo "# end tinderboxscript setup.sh" >> ${MY_ETC_SUDOERS} + fi + + maybe_hush_up_root_login; +} + + +## +# Test the user. +# +test_user() { + su - "${TESTBOXSCRIPT_USER}" -c "true" + + # sudo 1.7.0 adds the -n option. + MY_TMP="`sudo -V 2>&1 | head -1 | sed -e 's/^.*version 1\.[6543210]\..*$/old/'`" + if [ "${MY_TMP}" != "old" ]; then + echo "Warning: If sudo starts complaining about not having a tty," + echo " disable the requiretty option in /etc/sudoers." + su - "${TESTBOXSCRIPT_USER}" -c "sudo -n -i true" + else + echo "Warning: You've got an old sudo installed. If it starts" + echo " complaining about not having a tty, disable the" + echo " requiretty option in /etc/sudoers." + su - "${TESTBOXSCRIPT_USER}" -c "sudo true" + fi +} + +## +# Test if core dumps are enabled. See https://wiki.ubuntu.com/Apport! +# +test_coredumps() { + # This is a linux hook. + return 0; +} + +## +# Test if unattended updates are disabled. See +# http://ask.xmodulo.com/disable-automatic-updates-ubuntu.html +test_unattended_updates_disabled() { + # This is a linux hook. + return 0; +} + +## +# Grants the user write access to the testboxscript files so it can perform +# upgrades. +# +grant_user_testboxscript_write_access() { + chown -R "${TESTBOXSCRIPT_USER}" "${TESTBOXSCRIPT_DIR}" +} + +## +# Check the proxy setup. +# +check_proxy_config() { + if [ -n "${http_proxy}" -o -n "${ftp_proxy}" ]; then + if [ -z "${no_proxy}" ]; then + echo "Error: Env.vars. http_proxy/ftp_proxy without no_proxy is going to break upgrade among other things." + exit 1 + fi + fi +} + +## +# Parses the testboxscript.py invocation, setting TESTBOXSCRIPT_xxx config +# variables accordingly. Both darwin and solaris uses this. +common_testboxscript_args_to_config() +{ + MY_ARG=0 + while [ $# -gt 0 ]; + do + case "$1" in + # boolean + "--hwvirt") TESTBOXSCRIPT_HWVIRT="yes";; + "--no-hwvirt") TESTBOXSCRIPT_HWVIRT="no";; + "--nested-paging") TESTBOXSCRIPT_NESTED_PAGING="yes";; + "--no-nested-paging") TESTBOXSCRIPT_NESTED_PAGING="no";; + "--io-mmu") TESTBOXSCRIPT_IOMMU="yes";; + "--no-io-mmu") TESTBOXSCRIPT_IOMMU="no";; + # optios taking values. + "--system-uuid") TESTBOXSCRIPT_SYSTEM_UUID="$2"; shift;; + "--scratch-root") TESTBOXSCRIPT_SCRATCH_ROOT="$2"; shift;; + "--test-manager") TESTBOXSCRIPT_TEST_MANAGER="$2"; shift;; + "--builds-path") TESTBOXSCRIPT_BUILDS_PATH="$2"; shift;; + "--builds-server-type") TESTBOXSCRIPT_BUILDS_TYPE="$2"; shift;; + "--builds-server-name") TESTBOXSCRIPT_BUILDS_NAME="$2"; shift;; + "--builds-server-share") TESTBOXSCRIPT_BUILDS_SHARE="$2"; shift;; + "--builds-server-user") TESTBOXSCRIPT_BUILDS_USER="$2"; shift;; + "--builds-server-passwd") TESTBOXSCRIPT_BUILDS_PASSWD="$2"; shift;; + "--builds-server-mountopt") TESTBOXSCRIPT_BUILDS_MOUNTOPT="$2"; shift;; + "--testrsrc-path") TESTBOXSCRIPT_TESTRSRC_PATH="$2"; shift;; + "--testrsrc-server-type") TESTBOXSCRIPT_TESTRSRC_TYPE="$2"; shift;; + "--testrsrc-server-name") TESTBOXSCRIPT_TESTRSRC_NAME="$2"; shift;; + "--testrsrc-server-share") TESTBOXSCRIPT_TESTRSRC_SHARE="$2"; shift;; + "--testrsrc-server-user") TESTBOXSCRIPT_TESTRSRC_USER="$2"; shift;; + "--testrsrc-server-passwd") TESTBOXSCRIPT_TESTRSRC_PASSWD="$2"; shift;; + "--testrsrc-server-mountopt") TESTBOXSCRIPT_TESTRSRC_MOUNTOPT="$2"; shift;; + "--putenv") + MY_FOUND=no + MY_VAR=`echo $2 | sed -e 's/=.*$//' ` + for i in ${!TESTBOXSCRIPT_ENVVARS[@]}; + do + MY_CURVAR=`echo "${TESTBOXSCRIPT_ENVVARS[i]}" | sed -e 's/=.*$//' ` + if [ -n "${MY_CURVAR}" -a "${MY_CURVAR}" = "${MY_VAR}" ]; then + TESTBOXSCRIPT_ENVVARS[$i]="$2" + MY_FOUND=yes + fi + done + if [ "${MY_FOUND}" = "no" ]; then + TESTBOXSCRIPT_ENVVARS=( "${TESTBOXSCRIPT_ENVVARS[@]}" "$2" ); + fi + shift;; + --*) + echo "error: Unknown option '$1' in existing config" + exit 1 + ;; + + # Non-option bits. + *.py) ;; # ignored, should be the script. + + *) if [ ${MY_ARG} -ne 0 ]; then + echo "error: unknown non-option '$1' in existing config" + exit 1 + fi + TESTBOXSCRIPT_PYTHON="$1" + ;; + esac + shift + MY_ARG=$((${MY_ARG} + 1)) + done +} + +## +# Used by common_compile_testboxscript_command_line, please override. +# +os_add_args() { + echo "os_add_args is not implemented" 2>&1 + exit 1 +} + +## +# Compiles the testboxscript.py command line given the current +# configuration and defaults. +# +# This is used by solaris and darwin. +# +# The os_add_args function will be called several with one or two arguments +# each time. The caller must override it. +# +common_compile_testboxscript_command_line() { + if [ -n "${TESTBOXSCRIPT_PYTHON}" ]; then + os_add_args "${TESTBOXSCRIPT_PYTHON}" + fi + os_add_args "${TESTBOXSCRIPT_DIR}/testboxscript/testboxscript.py" + + for var in ${TESTBOXSCRIPT_CFG_NAMES}; + do + varcfg=TESTBOXSCRIPT_${var} + vardef=TESTBOXSCRIPT_DEFAULT_${var} + if [ "${!varcfg}" != "${!vardef}" -a "${var}" != "PYTHON" ]; then # PYTHON handled above. + my_opt=TESTBOXSCRIPT_OPT_${var} + if [ -n "${!my_opt}" ]; then + if [ "${!my_opt}" != "--skip" ]; then + os_add_args "${!my_opt}" "${!varcfg}" + fi + else + my_opt_yes=${my_opt}_YES + my_opt_no=${my_opt}_NO + if [ -n "${!my_opt_yes}" -a -n "${!my_opt_no}" ]; then + if [ "${!varcfg}" = "yes" ]; then + os_add_args "${!my_opt_yes}"; + else + if [ "${!varcfg}" != "no" ]; then + echo "internal option misconfig: var=${var} not a yes/no value: ${!varcfg}"; + exit 1; + fi + os_add_args "${!my_opt_yes}"; + fi + else + echo "internal option misconfig: var=${var} my_opt_yes=${my_opt_yes}=${!my_opt_yes} my_opt_no=${my_opt_no}=${!my_opt_no}" + exit 1; + fi + fi + fi + done + + i=0 + while [ "${i}" -lt "${#TESTBOXSCRIPT_ENVVARS[@]}" ]; + do + os_add_args "--putenv" "${TESTBOXSCRIPT_ENVVARS[${i}]}" + i=$((${i} + 1)) + done +} + + +# +# +# main() +# +# + + +# +# Get our bearings and include the host specific code. +# +MY_ETC_SUDOERS="/etc/sudoers" +MY_FGREP=fgrep +DIR=`dirname "$0"` +DIR=`cd "${DIR}"; /bin/pwd` + +get_host_os +HOST_OS=${RETVAL} +get_host_arch +HOST_ARCH=${RETVAL} + +. "${DIR}/${HOST_OS}/setup-routines.sh" + + +# +# Config. +# +TESTBOXSCRIPT_CFG_NAMES="DIR PYTHON USER HWVIRT IOMMU NESTED_PAGING SYSTEM_UUID PATH_TESTRSRC TEST_MANAGER SCRATCH_ROOT" +TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} BUILDS_PATH BUILDS_TYPE BUILDS_NAME BUILDS_SHARE BUILDS_USER BUILDS_PASSWD BUILDS_MOUNTOPT" +TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} TESTRSRC_PATH TESTRSRC_TYPE TESTRSRC_NAME TESTRSRC_SHARE TESTRSRC_USER TESTRSRC_PASSWD TESTRSRC_MOUNTOPT" + +# testboxscript.py option to config mappings. +TESTBOXSCRIPT_OPT_DIR="--skip" +TESTBOXSCRIPT_OPT_PYTHON="--skip" +TESTBOXSCRIPT_OPT_USER="--skip" +TESTBOXSCRIPT_OPT_HWVIRT_YES="--hwvirt" +TESTBOXSCRIPT_OPT_HWVIRT_NO="--no-hwvirt" +TESTBOXSCRIPT_OPT_NESTED_PAGING_YES="--nested-paging" +TESTBOXSCRIPT_OPT_NESTED_PAGING_NO="--no-nested-paging" +TESTBOXSCRIPT_OPT_IOMMU_YES="--io-mmu" +TESTBOXSCRIPT_OPT_IOMMU_NO="--no-io-mmu" +TESTBOXSCRIPT_OPT_SYSTEM_UUID="--system-uuid" +TESTBOXSCRIPT_OPT_TEST_MANAGER="--test-manager" +TESTBOXSCRIPT_OPT_SCRATCH_ROOT="--scratch-root" +TESTBOXSCRIPT_OPT_BUILDS_PATH="--builds-path" +TESTBOXSCRIPT_OPT_BUILDS_TYPE="--builds-server-type" +TESTBOXSCRIPT_OPT_BUILDS_NAME="--builds-server-name" +TESTBOXSCRIPT_OPT_BUILDS_SHARE="--builds-server-share" +TESTBOXSCRIPT_OPT_BUILDS_USER="--builds-server-user" +TESTBOXSCRIPT_OPT_BUILDS_PASSWD="--builds-server-passwd" +TESTBOXSCRIPT_OPT_BUILDS_MOUNTOPT="--builds-server-mountopt" +TESTBOXSCRIPT_OPT_PATH_TESTRSRC="--testrsrc-path" +TESTBOXSCRIPT_OPT_TESTRSRC_TYPE="--testrsrc-server-type" +TESTBOXSCRIPT_OPT_TESTRSRC_NAME="--testrsrc-server-name" +TESTBOXSCRIPT_OPT_TESTRSRC_SHARE="--testrsrc-server-share" +TESTBOXSCRIPT_OPT_TESTRSRC_USER="--testrsrc-server-user" +TESTBOXSCRIPT_OPT_TESTRSRC_PASSWD="--testrsrc-server-passwd" +TESTBOXSCRIPT_OPT_TESTRSRC_MOUNTOPT="--testrsrc-server-mountopt" + +# Defaults: +TESTBOXSCRIPT_DEFAULT_DIR="there-is-no-default-for-this-value" +TESTBOXSCRIPT_DEFAULT_PYTHON="" +TESTBOXSCRIPT_DEFAULT_USER="vbox" +TESTBOXSCRIPT_DEFAULT_HWVIRT="" +TESTBOXSCRIPT_DEFAULT_IOMMU="" +TESTBOXSCRIPT_DEFAULT_NESTED_PAGING="" +TESTBOXSCRIPT_DEFAULT_SYSTEM_UUID="" +TESTBOXSCRIPT_DEFAULT_PATH_TESTRSRC="" +TESTBOXSCRIPT_DEFAULT_TEST_MANAGER="" +TESTBOXSCRIPT_DEFAULT_SCRATCH_ROOT="" +TESTBOXSCRIPT_DEFAULT_BUILDS_PATH="" +TESTBOXSCRIPT_DEFAULT_BUILDS_TYPE="cifs" +TESTBOXSCRIPT_DEFAULT_BUILDS_NAME="vboxstor.de.oracle.com" +TESTBOXSCRIPT_DEFAULT_BUILDS_SHARE="builds" +TESTBOXSCRIPT_DEFAULT_BUILDS_USER="guestr" +TESTBOXSCRIPT_DEFAULT_BUILDS_PASSWD="guestr" +TESTBOXSCRIPT_DEFAULT_BUILDS_MOUNTOPT="" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_PATH="" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_TYPE="cifs" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_NAME="teststor.de.oracle.com" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_SHARE="testrsrc" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_USER="guestr" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_PASSWD="guestr" +TESTBOXSCRIPT_DEFAULT_TESTRSRC_MOUNTOPT="" + +# Set config values to defaults. +for var in ${TESTBOXSCRIPT_CFG_NAMES} +do + defvar=TESTBOXSCRIPT_DEFAULT_${var} + eval TESTBOXSCRIPT_${var}="${!defvar}" +done +declare -a TESTBOXSCRIPT_ENVVARS + +# Load old config values (platform specific). +os_load_config + + +# +# Config tweaks. +# + +# The USER must be a non-empty value for the successful execution of this script. +if [ -z "${TESTBOXSCRIPT_USER}" ]; then + TESTBOXSCRIPT_USER=${TESTBOXSCRIPT_DEFAULT_USER}; +fi; + +# The DIR must be according to the setup.sh location. +TESTBOXSCRIPT_DIR=`dirname "${DIR}"` + +# Storage server replacement trick. +if [ "${TESTBOXSCRIPT_BUILDS_NAME}" = "solserv.de.oracle.com" ]; then + TESTBOXSCRIPT_BUILDS_NAME=${TESTBOXSCRIPT_DEFAULT_BUILDS_NAME} +fi +if [ "${TESTBOXSCRIPT_TESTRSRC_NAME}" = "solserv.de.oracle.com" ]; then + TESTBOXSCRIPT_TESTRSRC_NAME=${TESTBOXSCRIPT_DEFAULT_TESTRSRC_NAME} +fi + + +# +# Parse arguments. +# +while test $# -gt 0; +do + case "$1" in + -h|--help) + echo "TestBox Script setup utility." + echo ""; + echo "Usage: setup.sh [options]"; + echo ""; + echo "Options:"; + echo " Later..."; + exit 0; + ;; + -V|--version) + echo '$Revision: 127855 $' + exit 0; + ;; + + --python) TESTBOXSCRIPT_PYTHON="$2"; shift;; + --test-manager) TESTBOXSCRIPT_TEST_MANAGER="$2"; shift;; + --scratch-root) TESTBOXSCRIPT_SCRATCH_ROOT="$2"; shift;; + --system-uuid) TESTBOXSCRIPT_SYSTEM_UUID="$2"; shift;; + --hwvirt) TESTBOXSCRIPT_HWVIRT="yes";; + --no-hwvirt) TESTBOXSCRIPT_HWVIRT="no";; + --nested-paging) TESTBOXSCRIPT_NESTED_PAGING="yes";; + --no-nested-paging) TESTBOXSCRIPT_NESTED_PAGING="no";; + --io-mmu) TESTBOXSCRIPT_IOMMU="yes";; + --no-io-mmu) TESTBOXSCRIPT_IOMMU="no";; + --builds-path) TESTBOXSCRIPT_BUILDS_PATH="$2"; shift;; + --builds-server-type) TESTBOXSCRIPT_BUILDS_TYPE="$2"; shift;; + --builds-server-name) TESTBOXSCRIPT_BUILDS_NAME="$2"; shift;; + --builds-server-share) TESTBOXSCRIPT_BUILDS_SHARE="$2"; shift;; + --builds-server-user) TESTBOXSCRIPT_BUILDS_USER="$2"; shift;; + --builds-server-passwd) TESTBOXSCRIPT_BUILDS_PASSWD="$2"; shift;; + --builds-server-mountopt) TESTBOXSCRIPT_BUILDS_MOUNTOPT="$2"; shift;; + --testrsrc-path) TESTBOXSCRIPT_TESTRSRC_PATH="$2"; shift;; + --testrsrc-server-type) TESTBOXSCRIPT_TESTRSRC_TYPE="$2"; shift;; + --testrsrc-server-name) TESTBOXSCRIPT_TESTRSRC_NAME="$2"; shift;; + --testrsrc-server-share) TESTBOXSCRIPT_TESTRSRC_SHARE="$2"; shift;; + --testrsrc-server-user) TESTBOXSCRIPT_TESTRSRC_USER="$2"; shift;; + --testrsrc-server-passwd) TESTBOXSCRIPT_TESTRSRC_PASSWD="$2"; shift;; + --testrsrc-server-mountopt) TESTBOXSCRIPT_TESTRSRC_MOUNTOPT="$2"; shift;; + *) + echo 'Syntax error: Unknown option:' "$1" >&2; + exit 1; + ;; + esac + shift; +done + + +# +# Find usable python if not already specified. +# +if [ -z "${TESTBOXSCRIPT_PYTHON}" ]; then + set +e + MY_PYTHON_VER_TEST="\ +import sys;\ +x = sys.version_info[0] == 2 and (sys.version_info[1] >= 6 or (sys.version_info[1] == 5 and sys.version_info[2] >= 1));\ +sys.exit(not x);\ +"; + for python in python2.7 python2.6 python2.5 python; + do + python=`which ${python} 2> /dev/null` + if [ -n "${python}" -a -x "${python}" ]; then + if ${python} -c "${MY_PYTHON_VER_TEST}"; then + TESTBOXSCRIPT_PYTHON="${python}"; + break; + fi + fi + done + set -e + test -n "${TESTBOXSCRIPT_PYTHON}"; +fi + + +# +# Do the job +# +set -e +check_testboxscript_install; +check_for_sudo; +check_for_cifs; +check_proxy_config; + +maybe_add_testboxscript_user; +test_user; +test_coredumps; +test_unattended_updates_disabled; + +grant_user_testboxscript_write_access; + +os_disable_service; +os_install_service; +os_enable_service; + +# +# That's all folks. +# +echo "done" +os_final_message; diff --git a/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh b/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh new file mode 100644 index 00000000..ea80b2de --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh @@ -0,0 +1,321 @@ +# $Id: setup-routines.sh $ +## @file +# VirtualBox Validation Kit - TestBoxScript Service Setup on Solaris. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# + +# +# Detect solaris version. +# +MY_SOLARIS_VER=`uname -r` +case "${MY_SOLARIS_VER}" in + 5.10) MY_SOLARIS_VER=10;; + 5.11) MY_SOLARIS_VER=11;; + 5.12) MY_SOLARIS_VER=12;; + *) + echo "Your solaris version (${MY_SOLARIS_VER}) is not supported." >&2 + exit 1;; +esac + +# +# Overriding setup.sh bits. +# +MY_FGREP="/usr/xpg4/bin/fgrep" # The other one does grok -q. +if [ ! -f "${MY_ETC_SUDOERS}" ]; then # sudo isn't standard on S10. + if [ -f "/opt/csw/etc/sudoers" ]; then + MY_ETC_SUDOERS=/opt/csw/etc/sudoers + fi + if [ -f "/etc/opt/csw/sudoers" ]; then + MY_ETC_SUDOERS=/etc/opt/csw/sudoers + fi +fi + +# +# Solaris variables. +# +MY_SVC_TMP="/tmp/testboxscript.xml" +MY_SVC_FMRI="svc:/system/virtualbox/testboxscript" +MY_SVCCFG="/usr/sbin/svccfg" +MY_SVCADM="/usr/sbin/svcadm" +MY_TR="/usr/bin/tr" +MY_TAB=`printf "\t"` +if test "${MY_SOLARIS_VER}" -lt 11; then ## No gsed on S10?? ARG! + MY_SED="/usr/xpg4/bin/sed" +else + MY_SED="/usr/bin/gsed" +fi +if test "${MY_SOLARIS_VER}" -lt 11; then + MY_SCREEN="/opt/csw/bin/screen" +else + MY_SCREEN="screen" +fi + + +check_for_cifs() { + if [ ! -f /usr/kernel/fs/amd64/smbfs -a ! -f /usr/kernel/fs/smbfs -a "${MY_SOLARIS_VER}" -ge 11 ]; then + echo "error: smbfs client not installed?" >&2 + echo "Please install smbfs client support:" >&2 + echo " pkg install system/file-system/smb" >&2 + echo " svcadm enable svc:/system/idmap" >&2 + echo " svcadm enable svc:/network/smb/client" >&2 + echo " svcs svc:/system/idmap" >&2 + return 1; + fi + return 0; +} + +## +# Loads config values from the current installation. +# +os_load_config() { + # + # Adjust defaults. + # + # - Use NFS instead of CIFS because S10 doesn't have smbfs and S11 has + # problems getting the password. + # - Pass the PATH along so we'll find sudo and other stuff later. + # + TESTBOXSCRIPT_BUILDS_TYPE="nfs" + TESTBOXSCRIPT_TESTRSRC_TYPE="nfs" + TESTBOXSCRIPT_DEFAULT_BUILDS_TYPE="nfs" + TESTBOXSCRIPT_DEFAULT_TESTRSRC_TYPE="nfs" + TESTBOXSCRIPT_ENVVARS[${#TESTBOXSCRIPT_ENVVARS[@]}]="PATH=${PATH}"; + + # Load old current. + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + # User. ASSUMES single quoted attribs. + MY_TMP=`"${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" \ + | ${MY_TR} '\n' ' ' \ + `; + MY_TMP=`echo "${MY_TMP} " \ + | ${MY_SED} \ + -e 's/> */> /g' \ + -e 's/ *\/>/ \/>/g' \ + -e 's/^.*<method_credential \([^>]*\) \/>.*$/\1/' \ + -e "s/^.*user='\([^']*\)'.*\$/\1/" \ + `; + if [ -n "${MY_TMP}" ]; then + TESTBOXSCRIPT_USER="${MY_TMP}"; + fi + + # Arguments. ASSUMES sub-elements. ASSUMES single quoted attribs. + XMLARGS=`"${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" \ + | ${MY_TR} '\n' ' ' \ + `; + case "${XMLARGS}" in + *exec_method*) + XMLARGS=`echo "${XMLARGS} " \ + | ${MY_SED} \ + -e 's/> */> /g' \ + -e 's/ *\/>/ \/>/g' \ + -e "s/^.*<exec_method \([^>]*\)name='start'\([^>]*\)>.*\$/\1 \2/" \ + -e "s/^.*exec='\([^']*\)'.*\$/\1/" \ + -e 's/"/"/g' \ + -e 's/</</g' \ + -e 's/>/>/g' \ + -e 's/&/&/g' \ + | ${MY_SED} \ + -e 's/^.*testboxscript -d -m *//' \ + `; + eval common_testboxscript_args_to_config ${XMLARGS} + ;; + *) + echo "error: ${MY_SVCCFG}" "export" "${MY_SVC_FMRI} contains no exec_method element." >&2 + echo " Please delete the service manually and restart setup.sh" >&2 + exit 2 + ;; + esac + fi +} + +## +# Adds one or more arguments to MY_ARGV after checking them for conformity. +# +os_add_args() { + while [ $# -gt 0 ]; + do + case "$1" in + *\ *) + echo "error: Space in option value is not allowed ($1)" >&2 + exit 1; + ;; + *${MY_TAB}*) + echo "error: Tab in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\&*) + echo "error: Ampersand in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\<*) + echo "error: Greater-than in option value is not allowed ($1)" >&2 + exit 1; + ;; + *\>*) + echo "error: Less-than in option value is not allowed ($1)" >&2 + exit 1; + ;; + *) + MY_ARGV="${MY_ARGV} $1"; + ;; + esac + shift; + done + return 0; +} + +## +# Installs, configures and starts the service. +# +os_install_service() { + # Only NFS for S10. + if [ "${MY_SOLARIS_VER}" -lt 11 ]; then + if [ "${TESTBOXSCRIPT_BUILDS_TYPE}" != "nfs" -o "${TESTBOXSCRIPT_TESTRSRC_TYPE}" != "nfs" ]; then + echo "On solaris 10 both share types must be 'nfs', cifs (smbfs) is not supported." >&2 + return 1; + fi + fi + + # Calc the command line. + MY_ARGV="" + common_compile_testboxscript_command_line + + # Create the service xml config file. + cat > "${MY_SVC_TMP}" <<EOF +<?xml version='1.0'?> +<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> +<service_bundle type='manifest' name='export'> + <service name='system/virtualbox/testboxscript' type='service' version='1'> + <create_default_instance enabled='false' /> + <single_instance/> + + <!-- Wait for the network to start up --> + <dependency name='milestone-network' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/network:default' /> + </dependency> + + <!-- We wish to be started as late as possible... so go crazy with deps. --> + <dependency name='milestone-devices' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/devices:default' /> + </dependency> + <dependency name='multi-user' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user:default' /> + </dependency> + <dependency name='multi-user-server' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/milestone/multi-user-server:default' /> + </dependency> + <dependency name='filesystem-local' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/local:default' /> + </dependency> + <dependency name='filesystem-autofs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/autofs:default' /> + </dependency> +EOF + if [ "`uname -r`" = "5.10" ]; then # Seems to be gone in S11? + cat >> "${MY_SVC_TMP}" <<EOF + <dependency name='filesystem-volfs' grouping='require_all' restart_on='none' type='service'> + <service_fmri value='svc:/system/filesystem/volfs:default' /> + </dependency> +EOF + fi + cat >> "${MY_SVC_TMP}" <<EOF + <!-- start + stop methods --> + <exec_method type='method' name='start' exec='${MY_SCREEN} -S testboxscript -d -m ${MY_ARGV}' + timeout_seconds='30'> + <method_context working_directory='${TESTBOXSCRIPT_DIR}'> + <method_credential user='${TESTBOXSCRIPT_USER}' /> + <method_environment> + <envvar name='PATH' value='${PATH}' /> + </method_environment> + </method_context> + </exec_method> + + <exec_method type='method' name='stop' exec=':kill' timeout_seconds='60' /> + + <property_group name='startd' type='framework'> + <!-- sub-process core dumps/signals should not restart session --> + <propval name='ignore_error' type='astring' value='core,signal' /> + </property_group> + + <!-- Description --> + <template> + <common_name> + <loctext xml:lang='C'>VirtualBox TestBox Script</loctext> + </common_name> + </template> + </service> +</service_bundle> +EOF + + # Install the service, replacing old stuff. + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + "${MY_SVCCFG}" "delete" "${MY_SVC_FMRI}" + fi + "${MY_SVCCFG}" "import" "${MY_SVC_TMP}" + rm -f "${MY_SVC_TMP}" + return 0; +} + +os_enable_service() { + "${MY_SVCADM}" "enable" "${MY_SVC_FMRI}" + return 0; +} + +os_disable_service() { + if "${MY_SVCCFG}" "export" "${MY_SVC_FMRI}" > /dev/null 2>&1; then + "${MY_SVCADM}" "disable" "${MY_SVC_FMRI}" + sleep 1 + fi + return 0; +} + +os_add_user() { + useradd -m -s /usr/bin/bash -G staff "${TESTBOXSCRIPT_USER}" + passwd "${TESTBOXSCRIPT_USER}" # This sucker prompts, seemingly no way around that. + return 0; +} + + +maybe_hush_up_root_login() { + # We don't want /etc/profile to display /etc/motd, quotas and mail status + # every time we do sudo -i... It may screw up serious if we parse the + # output of the command we sudid. + > ~root/.hushlogin + return 0; +} + +os_final_message() { + cat <<EOF + +Additional things to do:" + 1. Configure NTP: + a) echo "server wei01-time.de.oracle.com" > /etc/inet/ntp.conf + echo "driftfile /var/ntp/ntp.drift" >> /etc/inet/ntp.conf + b) Enable the service: svcadm enable ntp + c) Sync once in case of big diff: ntpdate wei01-time.de.oracle.com + d) Check that it works: ntpq -p + +Enjoy! +EOF +} + diff --git a/src/VBox/ValidationKit/testboxscript/testboxcommand.py b/src/VBox/ValidationKit/testboxscript/testboxcommand.py new file mode 100755 index 00000000..070f6e7f --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxcommand.py @@ -0,0 +1,357 @@ +# -*- coding: utf-8 -*- +# $Id: testboxcommand.py $ + +""" +TestBox Script - Command Processor. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + +# Standard python imports. +import os; +import sys; +import threading; + +# Validation Kit imports. +from common import constants; +from common import utils, webutils; +import testboxcommons; +from testboxcommons import TestBoxException; +from testboxscript import TBS_EXITCODE_NEED_UPGRADE; +from testboxupgrade import upgradeFromZip; +from testboxtasks import TestBoxExecTask, TestBoxCleanupTask, TestBoxTestDriverTask; + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); + + + +class TestBoxCommand(object): + """ + Implementation of Test Box command. + """ + + ## The time to wait on the current task to abort. + kcSecStopTimeout = 360 + ## The time to wait on the current task to abort before rebooting. + kcSecStopBeforeRebootTimeout = 360 + + def __init__(self, oTestBoxScript): + """ + Class instance init + """ + self._oTestBoxScript = oTestBoxScript; + self._oCurTaskLock = threading.RLock(); + self._oCurTask = None; + + # List of available commands and their handlers + self._dfnCommands = \ + { + constants.tbresp.CMD_IDLE: self._cmdIdle, + constants.tbresp.CMD_WAIT: self._cmdWait, + constants.tbresp.CMD_EXEC: self._cmdExec, + constants.tbresp.CMD_ABORT: self._cmdAbort, + constants.tbresp.CMD_REBOOT: self._cmdReboot, + constants.tbresp.CMD_UPGRADE: self._cmdUpgrade, + constants.tbresp.CMD_UPGRADE_AND_REBOOT: self._cmdUpgradeAndReboot, + constants.tbresp.CMD_SPECIAL: self._cmdSpecial, + } + + def _cmdIdle(self, oResponse, oConnection): + """ + Idle response, no ACK. + """ + oResponse.checkParameterCount(1); + + # The dispatch loop will delay for us, so nothing to do here. + _ = oConnection; # Leave the connection open. + return True; + + def _cmdWait(self, oResponse, oConnection): + """ + Gang scheduling wait response, no ACK. + """ + oResponse.checkParameterCount(1); + + # The dispatch loop will delay for us, so nothing to do here. + _ = oConnection; # Leave the connection open. + return True; + + def _cmdExec(self, oResponse, oConnection): + """ + Execute incoming command + """ + + # Check if required parameters given and make a little sense. + idResult = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_RESULT_ID, 1); + sScriptZips = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_ZIPS); + sScriptCmdLine = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_CMD_LINE); + cSecTimeout = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_TIMEOUT, 30); + oResponse.checkParameterCount(5); + + sScriptFile = utils.argsGetFirst(sScriptCmdLine); + if sScriptFile is None: + raise TestBoxException('Bad script command line: "%s"' % (sScriptCmdLine,)); + if len(os.path.basename(sScriptFile)) < len('t.py'): + raise TestBoxException('Script file name too short: "%s"' % (sScriptFile,)); + if len(sScriptZips) < len('x.zip'): + raise TestBoxException('Script zip name too short: "%s"' % (sScriptFile,)); + + # One task at the time. + if self.isRunning(): + raise TestBoxException('Already running other command'); + + # Don't bother running the task without the shares mounted. + self._oTestBoxScript.mountShares(); # Raises exception on failure. + + # Kick off the task and ACK the command. + self._oCurTaskLock.acquire(); + try: + self._oCurTask = TestBoxExecTask(self._oTestBoxScript, idResult = idResult, sScriptZips = sScriptZips, + sScriptCmdLine = sScriptCmdLine, cSecTimeout = cSecTimeout); + finally: + self._oCurTaskLock.release(); + oConnection.sendAckAndClose(constants.tbresp.CMD_EXEC); + return True; + + def _cmdAbort(self, oResponse, oConnection): + """ + Abort background task + """ + oResponse.checkParameterCount(1); + oConnection.sendAck(constants.tbresp.CMD_ABORT); + + oCurTask = self._getCurTask(); + if oCurTask is not None: + oCurTask.terminate(); + oCurTask.flushLogOnConnection(oConnection); + oConnection.close(); + oCurTask.wait(self.kcSecStopTimeout); + + return True; + + def doReboot(self): + """ + Worker common to _cmdReboot and _doUpgrade that performs a system reboot. + """ + # !! Not more exceptions beyond this point !! + testboxcommons.log('Rebooting'); + + # Stop anything that might be executing at this point. + oCurTask = self._getCurTask(); + if oCurTask is not None: + oCurTask.terminate(); + oCurTask.wait(self.kcSecStopBeforeRebootTimeout); + + # Invoke shutdown command line utility. + sOs = utils.getHostOs(); + asCmd2 = None; + if sOs == 'win': + asCmd = ['shutdown', '/r', '/t', '0', '/c', '"ValidationKit triggered reboot"', '/d', '4:1']; + elif sOs == 'os2': + asCmd = ['setboot', '/B']; + elif sOs in ('solaris',): + asCmd = ['/usr/sbin/reboot', '-p']; + asCmd2 = ['/usr/sbin/reboot']; # Hack! S10 doesn't have -p, but don't know how to reliably detect S10. + else: + asCmd = ['/sbin/shutdown', '-r', 'now']; + try: + utils.sudoProcessOutputChecked(asCmd); + except Exception as oXcpt: + if asCmd2 is not None: + try: + utils.sudoProcessOutputChecked(asCmd2); + except Exception as oXcpt: + testboxcommons.log('Error executing reboot command "%s" as well as "%s": %s' % (asCmd, asCmd2, oXcpt)); + return False; + testboxcommons.log('Error executing reboot command "%s": %s' % (asCmd, oXcpt)); + return False; + + # Quit the script. + while True: + sys.exit(32); + return True; + + def _cmdReboot(self, oResponse, oConnection): + """ + Reboot Test Box + """ + oResponse.checkParameterCount(1); + oConnection.sendAckAndClose(constants.tbresp.CMD_REBOOT); + return self.doReboot(); + + def _doUpgrade(self, oResponse, oConnection, fReboot): + """ + Common worker for _cmdUpgrade and _cmdUpgradeAndReboot. + Will sys.exit on success! + """ + + # + # The server specifies a ZIP archive with the new scripts. It's ASSUMED + # that the zip is of selected files at g_ksValidationKitDir in SVN. It's + # further ASSUMED that we're executing from + # + sZipUrl = oResponse.getStringChecked(constants.tbresp.UPGRADE_PARAM_URL) + oResponse.checkParameterCount(2); + + if utils.isRunningFromCheckout(): + raise TestBoxException('Cannot upgrade when running from the tree!'); + oConnection.sendAckAndClose(constants.tbresp.CMD_UPGRADE_AND_REBOOT if fReboot else constants.tbresp.CMD_UPGRADE); + + testboxcommons.log('Upgrading...'); + + # + # Download the file and install it. + # + sDstFile = os.path.join(g_ksTestScriptDir, 'VBoxTestBoxScript.zip'); + if os.path.exists(sDstFile): + os.unlink(sDstFile); + fRc = webutils.downloadFile(sZipUrl, sDstFile, self._oTestBoxScript.getPathBuilds(), testboxcommons.log); + if fRc is not True: + return False; + + if upgradeFromZip(sDstFile) is not True: + return False; + + # + # Restart the system or the script (we have a parent script which + # respawns us when we quit). + # + if fReboot: + self.doReboot(); + sys.exit(TBS_EXITCODE_NEED_UPGRADE); + return False; # shuts up pylint (it will probably complain later when it learns DECL_NO_RETURN). + + def _cmdUpgrade(self, oResponse, oConnection): + """ + Upgrade Test Box Script + """ + return self._doUpgrade(oResponse, oConnection, False); + + def _cmdUpgradeAndReboot(self, oResponse, oConnection): + """ + Upgrade Test Box Script + """ + return self._doUpgrade(oResponse, oConnection, True); + + def _cmdSpecial(self, oResponse, oConnection): + """ + Reserved for future fun. + """ + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, constants.tbresp.CMD_SPECIAL); + testboxcommons.log('Special command %s not supported...' % (oResponse,)); + return False; + + + def handleCommand(self, oResponse, oConnection): + """ + Handles a command from the test manager. + + Some commands will close the connection, others (generally the simple + ones) wont, leaving the caller the option to use it for log flushing. + + Returns success indicator. + Raises no exception. + """ + try: + sCmdName = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT); + except Exception as oXcpt: + oConnection.close(); + return False; + + # Do we know the command? + fRc = False; + if sCmdName in self._dfnCommands: + testboxcommons.log(sCmdName); + try: + # Execute the handler. + fRc = self._dfnCommands[sCmdName](oResponse, oConnection) + except Exception as oXcpt: + # NACK the command if an exception is raised during parameter validation. + testboxcommons.log1Xcpt('Exception executing "%s": %s' % (sCmdName, oXcpt)); + if oConnection.isConnected(): + try: + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NACK, sCmdName); + except Exception as oXcpt2: + testboxcommons.log('Failed to NACK "%s": %s' % (sCmdName, oXcpt2)); + elif sCmdName in [constants.tbresp.STATUS_DEAD, constants.tbresp.STATUS_NACK]: + testboxcommons.log('Received status instead of command: %s' % (sCmdName, )); + else: + # NOTSUP the unknown command. + testboxcommons.log('Received unknown command: %s' % (sCmdName, )); + try: + oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, sCmdName); + except Exception as oXcpt: + testboxcommons.log('Failed to NOTSUP "%s": %s' % (sCmdName, oXcpt)); + return fRc; + + def resumeIncompleteCommand(self): + """ + Resumes an incomplete command at startup. + + The EXEC commands saves essential state information in the scratch area + so we can resume them in case the testbox panics or is rebooted. + Current "resume" means doing cleanups, but we may need to implement + test scenarios involving rebooting the testbox later. + + Returns (idTestBox, sTestBoxName, True) if a command was resumed, + otherwise (-1, '', False). Raises no exceptions. + """ + + try: + oTask = TestBoxCleanupTask(self._oTestBoxScript); + except: + return (-1, '', False); + + self._oCurTaskLock.acquire(); + self._oCurTask = oTask; + self._oCurTaskLock.release(); + + return (oTask.idTestBox, oTask.sTestBoxName, True); + + def isRunning(self): + """ + Check if we're running a task or not. + """ + oCurTask = self._getCurTask(); + return oCurTask is not None and oCurTask.isRunning(); + + def flushLogOnConnection(self, oGivenConnection): + """ + Flushes the log of any running task with a log buffer. + """ + oCurTask = self._getCurTask(); + if oCurTask is not None and isinstance(oCurTask, TestBoxTestDriverTask): + return oCurTask.flushLogOnConnection(oGivenConnection); + return None; + + def _getCurTask(self): + """ Gets the current task in a paranoidly safe manny. """ + self._oCurTaskLock.acquire(); + oCurTask = self._oCurTask; + self._oCurTaskLock.release(); + return oCurTask; + diff --git a/src/VBox/ValidationKit/testboxscript/testboxcommons.py b/src/VBox/ValidationKit/testboxscript/testboxcommons.py new file mode 100755 index 00000000..537d4075 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxcommons.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# $Id: testboxcommons.py $ + +""" +TestBox Script - Common Functions and Classes. + +This module contains constants and functions that are useful for all +the files in this (testbox) directory. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +# Standard python imports. +import sys +import traceback + +# Validation Kit imports. +from common import utils; + +# +# Exceptions. +# + +class TestBoxException(Exception): + """ + Custom exception class + """ + pass + +# +# Logging. +# + +def log(sMessage, sCaller = None, sTsPrf = None): + """ + Print out a message and flush stdout + """ + if sTsPrf is None: sTsPrf = utils.getTimePrefix(); + print('[%s] %s' % (sTsPrf, sMessage,)); + sys.stdout.flush(); + _ = sCaller; + +def log2(sMessage, sCaller = None, sTsPrf = None): + """ + Debug logging, will later be disabled by default. + """ + if True is True: + if sTsPrf is None: sTsPrf = utils.getTimePrefix(); + print('[%s] %s' % (sTsPrf, sMessage,)); + sys.stdout.flush() + _ = sCaller; + +def _logXcptWorker(fnLogger, sPrefix = '', sText = None, cFrames = 1, fnLogger1 = log): + """ + Log an exception, optionally with a preceeding message and more than one + call frame. + """ + ## @todo skip all this if iLevel is too high! + + # Try get exception info. + sTsPrf = utils.getTimePrefix(); + try: + oType, oValue, oTraceback = sys.exc_info(); + except: + oType = oValue = oTraceback = None; + if oType is not None: + + # Try format the info + try: + rc = 0; + sCaller = utils.getCallerName(oTraceback.tb_frame); + if sText is not None: + rc = fnLogger('%s%s' % (sPrefix, sText), sCaller, sTsPrf); + asInfo = []; + try: + asInfo = asInfo + traceback.format_exception_only(oType, oValue); + if cFrames is not None and cFrames <= 1: + asInfo = asInfo + traceback.format_tb(oTraceback, 1); + else: + asInfo.append('Traceback:') + asInfo = asInfo + traceback.format_tb(oTraceback, cFrames); + asInfo.append('Stack:') + asInfo = asInfo + traceback.format_stack(oTraceback.tb_frame.f_back, cFrames); + except: + fnLogger1('internal-error: Hit exception #2! %s' % (traceback.format_exc()), sCaller, sTsPrf); + + if asInfo: + # Do the logging. + for sItem in asInfo: + asLines = sItem.splitlines(); + for sLine in asLines: + rc = fnLogger('%s%s' % (sPrefix, sLine), sCaller, sTsPrf); + + else: + fnLogger('No exception info...', sCaller, sTsPrf); + rc = -3; + except: + fnLogger1('internal-error: Hit exception! %s' % (traceback.format_exc()), None, sTsPrf); + rc = -2; + else: + fnLogger1('internal-error: No exception! %s' % (utils.getCallerName(iFrame=3)), utils.getCallerName(iFrame=3), sTsPrf); + rc = -1; + + return rc; + + +def log1Xcpt(sText = None, cFrames = 1): + """Logs an exception.""" + return _logXcptWorker(log, '', sText, cFrames); + +def log2Xcpt(sText = None, cFrames = 1): + """Debug logging of an exception.""" + return _logXcptWorker(log2, '', sText, cFrames); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxconnection.py b/src/VBox/ValidationKit/testboxscript/testboxconnection.py new file mode 100755 index 00000000..9acef5a8 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxconnection.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +# $Id: testboxconnection.py $ + +""" +TestBox Script - HTTP Connection Handling. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +# Standard python imports. +import sys; +if sys.version_info[0] >= 3: + import http.client as httplib; # pylint: disable=import-error,no-name-in-module + import urllib.parse as urlparse; # pylint: disable=import-error,no-name-in-module + from urllib.parse import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module +else: + import httplib; # pylint: disable=import-error,no-name-in-module + import urlparse; # pylint: disable=import-error,no-name-in-module + from urllib import urlencode as urllib_urlencode; # pylint: disable=import-error,no-name-in-module + +# Validation Kit imports. +from common import constants +from common import utils +import testboxcommons + + + +class TestBoxResponse(object): + """ + Response object return by TestBoxConnection.request(). + """ + def __init__(self, oResponse): + """ + Convert the HTTPResponse to a dictionary, raising TestBoxException on + malformed response. + """ + if oResponse is not None: + # Read the whole response (so we can log it). + sBody = oResponse.read(); + sBody = sBody.decode('utf-8'); + + # Check the content type. + sContentType = oResponse.getheader('Content-Type'); + if sContentType is None or sContentType != 'application/x-www-form-urlencoded; charset=utf-8': + testboxcommons.log('SERVER RESPONSE: Content-Type: %s' % (sContentType,)); + testboxcommons.log('SERVER RESPONSE: %s' % (sBody.rstrip(),)) + raise testboxcommons.TestBoxException('Invalid server response type: "%s"' % (sContentType,)); + + # Parse the body (this should be the exact reverse of what + # TestBoxConnection.postRequestRaw). + ##testboxcommons.log2('SERVER RESPONSE: "%s"' % (sBody,)) + self._dResponse = urlparse.parse_qs(sBody, strict_parsing=True); + + # Convert the dictionary from 'field:values' to 'field:value'. Fail + # if a field has more than one value (i.e. given more than once). + for sField in self._dResponse: + if len(self._dResponse[sField]) != 1: + raise testboxcommons.TestBoxException('The field "%s" appears more than once in the server response' \ + % (sField,)); + self._dResponse[sField] = self._dResponse[sField][0] + else: + # Special case, dummy response object. + self._dResponse = dict(); + # Done. + + def getStringChecked(self, sField): + """ + Check if specified field is present in server response and returns it as string. + If not present, a fitting exception will be raised. + """ + if not sField in self._dResponse: + raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response'); + return str(self._dResponse[sField]).strip(); + + def getIntChecked(self, sField, iMin = None, iMax = None): + """ + Check if specified field is present in server response and returns it as integer. + If not present, a fitting exception will be raised. + + The iMin and iMax values are inclusive. + """ + if not sField in self._dResponse: + raise testboxcommons.TestBoxException('Required data (' + str(sField) + ') was not found in server response') + try: + iValue = int(self._dResponse[sField]); + except: + raise testboxcommons.TestBoxException('Malformed integer field %s: "%s"' % (sField, self._dResponse[sField])); + + if (iMin is not None and iValue < iMin) \ + or (iMax is not None and iValue > iMax): + raise testboxcommons.TestBoxException('Value (%d) of field %s is out of range [%s..%s]' \ + % (iValue, sField, iMin, iMax)); + return iValue; + + def checkParameterCount(self, cExpected): + """ + Checks the parameter count, raise TestBoxException if it doesn't meet + the expectations. + """ + if len(self._dResponse) != cExpected: + raise testboxcommons.TestBoxException('Expected %d parameters, server sent %d' % (cExpected, len(self._dResponse))); + return True; + + def toString(self): + """ + Convers the response to a string (for debugging purposes). + """ + return str(self._dResponse); + + +class TestBoxConnection(object): + """ + Wrapper around HTTPConnection. + """ + + def __init__(self, sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = False): + """ + Constructor. + """ + self._oConn = None; + self._oParsedUrl = urlparse.urlparse(sTestManagerUrl); + self._sTestBoxId = sTestBoxId; + self._sTestBoxUuid = sTestBoxUuid; + + # + # Connect to it - may raise exception on failure. + # When connecting we're using a 15 second timeout, we increase it later. + # + if self._oParsedUrl.scheme == 'https': # pylint: disable=E1101 + fnCtor = httplib.HTTPSConnection; + else: + fnCtor = httplib.HTTPConnection; + if sys.version_info[0] >= 3 \ + or (sys.version_info[0] == 2 and sys.version_info[1] >= 6): + + self._oConn = fnCtor(self._oParsedUrl.hostname, timeout=15); + else: + self._oConn = fnCtor(self._oParsedUrl.hostname); + + if self._oConn.sock is None: + self._oConn.connect(); + + # + # Increase the timeout for the non-connect operations. + # + try: + self._oConn.sock.settimeout(5*60 if fLongTimeout else 1 * 60); + except: + pass; + + ##testboxcommons.log2('hostname=%s timeout=%u' % (self._oParsedUrl.hostname, self._oConn.sock.gettimeout())); + + def __del__(self): + """ Makes sure the connection is really closed on destruction """ + self.close() + + def close(self): + """ Closes the connection """ + if self._oConn is not None: + self._oConn.close(); + self._oConn = None; + + def postRequestRaw(self, sAction, dParams): + """ + Posts a request to the test manager and gets the response. The dParams + argument is a dictionary of unencoded key-value pairs (will be + modified). + Raises exception on failure. + """ + dHeader = \ + { + 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent': 'TestBoxScript/%s.0 (%s, %s)' % (__version__, utils.getHostOs(), utils.getHostArch()), + 'Accept': 'text/plain,application/x-www-form-urlencoded', + 'Accept-Encoding': 'identity', + 'Cache-Control': 'max-age=0', + 'Connection': 'keep-alive', + }; + sServerPath = '/%s/testboxdisp.py' % (self._oParsedUrl.path.strip('/'),); # pylint: disable=E1101 + dParams[constants.tbreq.ALL_PARAM_ACTION] = sAction; + sBody = urllib_urlencode(dParams); + ##testboxcommons.log2('sServerPath=%s' % (sServerPath,)); + try: + self._oConn.request('POST', sServerPath, sBody, dHeader); + oResponse = self._oConn.getresponse(); + oResponse2 = TestBoxResponse(oResponse); + except: + testboxcommons.log2Xcpt(); + raise + return oResponse2; + + def postRequest(self, sAction, dParams = None): + """ + Posts a request to the test manager, prepending the testbox ID and + UUID to the arguments, and gets the response. The dParams argument is a + is a dictionary of unencoded key-value pairs (will be modified). + Raises exception on failure. + """ + if dParams is None: + dParams = dict(); + dParams[constants.tbreq.ALL_PARAM_TESTBOX_ID] = self._sTestBoxId; + dParams[constants.tbreq.ALL_PARAM_TESTBOX_UUID] = self._sTestBoxUuid; + return self.postRequestRaw(sAction, dParams); + + def sendReply(self, sReplyAction, sCmdName): + """ + Sends a reply to a test manager command. + Raises exception on failure. + """ + return self.postRequest(sReplyAction, { constants.tbreq.COMMAND_ACK_PARAM_CMD_NAME: sCmdName }); + + def sendReplyAndClose(self, sReplyAction, sCmdName): + """ + Sends a reply to a test manager command and closes the connection. + Raises exception on failure. + """ + self.sendReply(sReplyAction, sCmdName); + self.close(); + return True; + + def sendAckAndClose(self, sCmdName): + """ + Acks a command and closes the connection to the test manager. + Raises exception on failure. + """ + return self.sendReplyAndClose(constants.tbreq.COMMAND_ACK, sCmdName); + + def sendAck(self, sCmdName): + """ + Acks a command. + Raises exception on failure. + """ + return self.sendReply(constants.tbreq.COMMAND_ACK, sCmdName); + + @staticmethod + def sendSignOn(sTestManagerUrl, dParams): + """ + Sends a sign-on request to the server, returns the response (TestBoxResponse). + No exceptions will be raised. + """ + oConnection = None; + try: + oConnection = TestBoxConnection(sTestManagerUrl, None, None); + return oConnection.postRequestRaw(constants.tbreq.SIGNON, dParams); + except: + testboxcommons.log2Xcpt(); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + + return TestBoxResponse(None); + + @staticmethod + def requestCommandWithConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fBusy): + """ + Queries the test manager for a command and returns its respons + an open + connection for acking/nack the command (and maybe more). + + No exceptions will be raised. On failure (None, None) will be returned. + """ + oConnection = None; + try: + oConnection = TestBoxConnection(sTestManagerUrl, sTestBoxId, sTestBoxUuid, fLongTimeout = not fBusy); + if fBusy: + oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_BUSY); + else: + oResponse = oConnection.postRequest(constants.tbreq.REQUEST_COMMAND_IDLE); + return (oResponse, oConnection); + except: + testboxcommons.log2Xcpt(); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + return (None, None); + + def isConnected(self): + """ + Checks if we are still connected. + """ + return self._oConn is not None; diff --git a/src/VBox/ValidationKit/testboxscript/testboxscript.py b/src/VBox/ValidationKit/testboxscript/testboxscript.py new file mode 100755 index 00000000..24b1b1d5 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxscript.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: testboxscript.py $ + +""" +TestBox Script Wrapper. + +This script aimes at respawning the Test Box Script when it terminates +abnormally or due to an UPGRADE request. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + +import platform; +import subprocess; +import sys; +import os; +import time; + + +## @name Test Box script exit statuses (see also RTEXITCODE) +# @remarks These will _never_ change +# @{ +TBS_EXITCODE_FAILURE = 1; # RTEXITCODE_FAILURE +TBS_EXITCODE_SYNTAX = 2; # RTEXITCODE_SYNTAX +TBS_EXITCODE_NEED_UPGRADE = 9; +## @} + + +class TestBoxScriptWrapper(object): # pylint: disable=R0903 + """ + Wrapper class + """ + + TESTBOX_SCRIPT_FILENAME = 'testboxscript_real.py' + + def __init__(self): + """ + Init + """ + self.oTask = None + + def __del__(self): + """ + Cleanup + """ + if self.oTask is not None: + print('Wait for child task...'); + self.oTask.terminate() + self.oTask.wait() + print('done. Exiting'); + self.oTask = None; + + def run(self): + """ + Start spawning the real TestBox script. + """ + + # Figure out where we live first. + try: + __file__ + except: + __file__ = sys.argv[0]; + sTestBoxScriptDir = os.path.dirname(os.path.abspath(__file__)); + + # Construct the argument list for the real script (same dir). + sRealScript = os.path.join(sTestBoxScriptDir, TestBoxScriptWrapper.TESTBOX_SCRIPT_FILENAME); + asArgs = sys.argv[1:]; + asArgs.insert(0, sRealScript); + if sys.executable: + asArgs.insert(0, sys.executable); + + # Look for --pidfile <name> and write a pid file. + sPidFile = None; + for i, _ in enumerate(asArgs): + if asArgs[i] == '--pidfile' and i + 1 < len(asArgs): + sPidFile = asArgs[i + 1]; + break; + if asArgs[i] == '--': + break; + if sPidFile: + oPidFile = open(sPidFile, 'w'); + oPidFile.write(str(os.getpid())); + oPidFile.close(); + + # Execute the testbox script almost forever in a relaxed loop. + rcExit = TBS_EXITCODE_FAILURE; + while True: + fCreationFlags = 0; + if platform.system() == 'Windows': + fCreationFlags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0x00000200); # for Ctrl-C isolation (python 2.7) + self.oTask = subprocess.Popen(asArgs, shell = False, creationflags = fCreationFlags); + rcExit = self.oTask.wait(); + self.oTask = None; + if rcExit == TBS_EXITCODE_SYNTAX: + break; + + # Relax. + time.sleep(1); + return rcExit; + +if __name__ == '__main__': + sys.exit(TestBoxScriptWrapper().run()); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxscript_real.py b/src/VBox/ValidationKit/testboxscript/testboxscript_real.py new file mode 100755 index 00000000..11312ea9 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxscript_real.py @@ -0,0 +1,1056 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: testboxscript_real.py $ + +""" +TestBox Script - main(). +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +# Standard python imports. +import math +import multiprocessing +import os +from optparse import OptionParser # pylint: disable=deprecated-module +import platform +import random +import shutil +import sys +import tempfile +import time +import uuid + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); +g_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir); +sys.path.extend([g_ksTestScriptDir, g_ksValidationKitDir]); + +# Validation Kit imports. +from common import constants; +from common import utils; +import testboxcommons; +from testboxcommons import TestBoxException; +from testboxcommand import TestBoxCommand; +from testboxconnection import TestBoxConnection; +from testboxscript import TBS_EXITCODE_SYNTAX, TBS_EXITCODE_FAILURE; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class TestBoxScriptException(Exception): + """ For raising exceptions during TestBoxScript.__init__. """ + pass; + + +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, 0o700); + + # 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, + self._oOptions.sBuildsServerMountOpt, 'builds'); + self._mountShare(self._oOptions.sTestRsrcPath, self._oOptions.sTestRsrcServerType, self._oOptions.sTestRsrcServerName, + self._oOptions.sTestRsrcServerShare, + self._oOptions.sTestRsrcServerUser, self._oOptions.sTestRsrcServerPasswd, + self._oOptions.sTestRsrcServerMountOpt, 'testrsrc'); + return True; + + def _mountShare(self, sMountPoint, sType, sServer, sShare, sUser, sPassword, sMountOpt, 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'): + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + 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' + sMountOpt, + '-f', '0555', '-d', '0555', + '//%s:%s@%s/%s' % (sUser, sPassword, sServer, sShare), + sMountPoint]); + else: + raise TestBoxScriptException('Unsupported server type %s.' % (sType,)); + + elif sHostOs == 'linux': + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + utils.sudoProcessCall(['/bin/umount', sMountPoint]); + utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]); + if sType == 'cifs': + utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'cifs', + '-o', + 'user=' + sUser + + ',password=' + sPassword + + ',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' + + sMountOpt, + '//%s/%s' % (sServer, sShare), + sMountPoint]); + elif sType == 'nfs': + utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'nfs', + '-o', 'soft,ro' + sMountOpt, + '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)), + sMountPoint]); + + else: + raise TestBoxScriptException('Unsupported server type %s.' % (sType,)); + + elif sHostOs == 'solaris': + if sMountOpt != '': + sMountOpt = ',' + sMountOpt + 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=' + sUser + + ',uid=' + str(os.getuid()) # pylint: disable=E1101 + + ',gid=' + str(os.getgid()) # pylint: disable=E1101 + + ',fileperms=0555,dirperms=0555,noxattr,ro' + + sMountOpt, + '//%s/%s' % (sServer, sShare), + sMountPoint], + stdin = oPasswdFile); + oPasswdFile.close(); + elif sType == 'nfs': + utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'nfs', + '-o', 'noxattr,ro' + sMountOpt, + '%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:' + sUser,]); + else: + raise TestBoxScriptException('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 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; + + def reinitScratch(self, fnLog = testboxcommons.log, fUseTheForce = None, cRetries = 0, cMsDelay = 5000): + """ + Wipes the scratch directories and re-initializes them. + + Will retry according to the cRetries and cMsDelay parameters. Windows + forces us to apply this hack as it ships with services asynchronously + scanning files after they execute, thus racing us cleaning up after a + test. On testboxwin3 we had frequent trouble with aelupsvc.dll keeping + vts_rm.exe kind of open, somehow preventing us from removing the + directory containing it, despite not issuing any errors deleting the + file itself. The service is called "Application Experience", which + feels like a weird joke here. + + No exceptions raise, returns success indicator instead. + """ + fRc = self._reinitScratch(fnLog, fUseTheForce) + while fRc is False and cRetries > 0: + time.sleep(cMsDelay / 1000.0); + fnLog('reinitScratch: Retrying...'); + fRc = self._reinitScratch(fnLog, fUseTheForce) + cRetries -= 1; + return fRc; + + + def _doSignOn(self): + """ + Worker for _maybeSignOn that does the actual signing on. + """ + assert not self._oCommand.isRunning(); + + # Reset the siged-on state. + testboxcommons.log('Signing-on...') + self._fSignedOn = False + self._idTestBox = None + self._cSignOnAttempts += 1; + + # Assemble SIGN-ON request parameters and send the request. + dParams = {}; + for sParam in self._ddSignOnParams: + dParams[sParam] = self._ddSignOnParams[sParam][self.VALUE]; + oResponse = TestBoxConnection.sendSignOn(self._oOptions.sTestManagerUrl, dParams); + + # Check response. + try: + sResult = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT); + if sResult != constants.tbresp.STATUS_ACK: + raise TestBoxException('Result is %s' % (sResult,)); + oResponse.checkParameterCount(3); + idTestBox = oResponse.getIntChecked(constants.tbresp.SIGNON_PARAM_ID, 1, 0x7ffffffe); + sTestBoxName = oResponse.getStringChecked(constants.tbresp.SIGNON_PARAM_NAME); + except TestBoxException as err: + testboxcommons.log('Failed to sign-on: %s' % (str(err),)) + testboxcommons.log('Server response: %s' % (oResponse.toString(),)); + return False; + + # Successfully signed on, update the state. + self._fSignedOn = True; + self._fNeedReSignOn = False; + self._cSignOnAttempts = 0; + self._idTestBox = idTestBox; + self._sTestBoxName = sTestBoxName; + + # Update the environment. + os.environ['TESTBOX_ID'] = str(self._idTestBox); + os.environ['TESTBOX_NAME'] = sTestBoxName; + 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); + + testboxcommons.log('Successfully signed-on with Test Box ID #%s and given the name "%s"' \ + % (self._idTestBox, self._sTestBoxName)); + + # Set up the scratch area. + self.reinitScratch(fUseTheForce = self._fFirstSignOn, cRetries = 2); + + self._fFirstSignOn = False; + return True; + + def _maybeSignOn(self): + """ + Check if Test Box parameters were changed + and do sign-in in case of positive result + """ + + # Skip sign-on check if background command is currently in + # running state (avoid infinite signing on). + if self._oCommand.isRunning(): + return None; + + # Refresh sign-on parameters, changes triggers sign-on. + fNeedSignOn = (True if not self._fSignedOn or self._fNeedReSignOn else False) + for item in self._ddSignOnParams: + if self._ddSignOnParams[item][self.FN] is None: + continue + + sOldValue = self._ddSignOnParams[item][self.VALUE] + self._ddSignOnParams[item][self.VALUE] = self._ddSignOnParams[item][self.FN]() + if sOldValue != self._ddSignOnParams[item][self.VALUE]: + fNeedSignOn = True + testboxcommons.log('Detected %s parameter change: %s -> %s' % + (item, sOldValue, self._ddSignOnParams[item][self.VALUE])) + + if fNeedSignOn: + self._doSignOn(); + return None; + + def dispatch(self): + """ + Receive orders from Test Manager and execute them + """ + + (self._idTestBox, self._sTestBoxName, self._fSignedOn) = self._oCommand.resumeIncompleteCommand(); + self._fNeedReSignOn = self._fSignedOn; + if self._fSignedOn: + os.environ['TESTBOX_ID'] = str(self._idTestBox); + os.environ['TESTBOX_NAME'] = self._sTestBoxName; + + while True: + # Make sure we're signed on before trying to do anything. + self._maybeSignOn(); + while not self._fSignedOn: + iFactor = 1 if self._cSignOnAttempts < 100 else 4; + time.sleep(random.randint(self.kcSecMinSignOnDelay * iFactor, self.kcSecMaxSignOnDelay * iFactor)); + self._maybeSignOn(); + + # Retrieve and handle command from the TM. + (oResponse, oConnection) = TestBoxConnection.requestCommandWithConnection(self._oOptions.sTestManagerUrl, + self._idTestBox, + self._sTestBoxUuid, + self._oCommand.isRunning()); + if oResponse is not None: + self._oCommand.handleCommand(oResponse, oConnection); + if oConnection is not None: + if oConnection.isConnected(): + self._oCommand.flushLogOnConnection(oConnection); + oConnection.close(); + + # Automatically reboot if scratch init fails. + #if self._cReinitScratchErrors > 8 and self.reinitScratch(cRetries = 3) is False: + # testboxcommons.log('Scratch does not initialize cleanly after %d attempts, rebooting...' + # % ( self._cReinitScratchErrors, )); + # self._oCommand.doReboot(); + + # delay a wee bit before looping. + ## @todo We shouldn't bother the server too frequently. We should try combine the test reporting done elsewhere + # with the command retrieval done here. I believe tinderclient.pl is capable of doing that. + iFactor = 1; + if self._cReinitScratchErrors > 0: + iFactor = 4; + time.sleep(random.randint(self.kcSecMinDelay * iFactor, self.kcSecMaxDelay * iFactor)); + + # Not reached. + + + @staticmethod + def main(): + """ + Main function a la C/C++. Returns exit code. + """ + + # + # Parse arguments. + # + sDefShareType = 'nfs' if utils.getHostOs() == 'solaris' else 'cifs'; + if utils.getHostOs() in ('win', 'os2'): + sDefTestRsrc = 'T:'; + sDefBuilds = 'U:'; + elif utils.getHostOs() == 'darwin': + sDefTestRsrc = '/Volumes/testrsrc'; + sDefBuilds = '/Volumes/builds'; + else: + sDefTestRsrc = '/mnt/testrsrc'; + sDefBuilds = '/mnt/builds'; + + class MyOptionParser(OptionParser): + """ We need to override the exit code on --help, error and so on. """ + def __init__(self, *args, **kwargs): + OptionParser.__init__(self, *args, **kwargs); + def exit(self, status = 0, msg = None): + OptionParser.exit(self, TBS_EXITCODE_SYNTAX, msg); + + parser = MyOptionParser(version=__version__[11:-1].strip()); + for sMixed, sDefault, sDesc in [('Builds', sDefBuilds, 'builds'), ('TestRsrc', sDefTestRsrc, 'test resources') ]: + sLower = sMixed.lower(); + sPrefix = 's' + sMixed; + parser.add_option('--' + sLower + '-path', + dest=sPrefix + 'Path', metavar='<abs-path>', default=sDefault, + help='Where ' + sDesc + ' can be found'); + parser.add_option('--' + sLower + '-server-type', + dest=sPrefix + 'ServerType', metavar='<nfs|cifs>', default=sDefShareType, + help='The type of server, cifs (default) or nfs. If empty, we won\'t try mount anything.'); + parser.add_option('--' + sLower + '-server-name', + dest=sPrefix + 'ServerName', metavar='<server>', + default='vboxstor.de.oracle.com' if sLower == 'builds' else 'teststor.de.oracle.com', + help='The name of the server with the builds.'); + parser.add_option('--' + sLower + '-server-share', + dest=sPrefix + 'ServerShare', metavar='<share>', default=sLower, + help='The name of the builds share.'); + parser.add_option('--' + sLower + '-server-user', + dest=sPrefix + 'ServerUser', metavar='<user>', default='guestr', + help='The user name to use when accessing the ' + sDesc + ' share.'); + parser.add_option('--' + sLower + '-server-passwd', '--' + sLower + '-server-password', + dest=sPrefix + 'ServerPasswd', metavar='<password>', default='guestr', + help='The password to use when accessing the ' + sDesc + ' share.'); + parser.add_option('--' + sLower + '-server-mountopt', + dest=sPrefix + 'ServerMountOpt', metavar='<mountopt>', default='', + help='The mount options to use when accessing the ' + sDesc + ' share.'); + + parser.add_option("--test-manager", metavar="<url>", + dest="sTestManagerUrl", + help="Test Manager URL", + default="http://tindertux.de.oracle.com/testmanager") + parser.add_option("--scratch-root", metavar="<abs-path>", + dest="sScratchRoot", + help="Path to the scratch directory", + default=None) + parser.add_option("--system-uuid", metavar="<uuid>", + dest="sSystemUuid", + help="The system UUID of the testbox, used for uniquely identifiying the machine", + default=None) + parser.add_option("--hwvirt", + dest="fHasHwVirt", action="store_true", default=None, + help="Hardware virtualization available in the CPU"); + parser.add_option("--no-hwvirt", + dest="fHasHwVirt", action="store_false", default=None, + help="Hardware virtualization not available in the CPU"); + parser.add_option("--nested-paging", + dest="fHasNestedPaging", action="store_true", default=None, + help="Nested paging is available"); + parser.add_option("--no-nested-paging", + dest="fHasNestedPaging", action="store_false", default=None, + help="Nested paging is not available"); + parser.add_option("--64-bit-guest", + dest="fCan64BitGuest", action="store_true", default=None, + help="Host can execute 64-bit guests"); + parser.add_option("--no-64-bit-guest", + dest="fCan64BitGuest", action="store_false", default=None, + help="Host cannot execute 64-bit guests"); + parser.add_option("--io-mmu", + dest="fHasIoMmu", action="store_true", default=None, + help="I/O MMU available"); + parser.add_option("--no-io-mmu", + dest="fHasIoMmu", action="store_false", default=None, + help="No I/O MMU available"); + parser.add_option("--raw-mode", + dest="fWithRawMode", action="store_true", default=None, + help="Use raw-mode on this host."); + parser.add_option("--no-raw-mode", + dest="fWithRawMode", action="store_false", default=None, + help="Disables raw-mode tests on this host."); + parser.add_option("--pidfile", + dest="sPidFile", default=None, + help="For the parent script, ignored."); + parser.add_option("-E", "--putenv", metavar = "<variable>=<value>", action = "append", + dest = "asEnvVars", default = [], + help = "Sets an environment variable. Can be repeated."); + + (oOptions, args) = parser.parse_args() + # Check command line + if args != []: + parser.print_help(); + return TBS_EXITCODE_SYNTAX; + + if oOptions.sSystemUuid is not None: + uuid.UUID(oOptions.sSystemUuid); + if not oOptions.sTestManagerUrl.startswith('http://') \ + and not oOptions.sTestManagerUrl.startswith('https://'): + print('Syntax error: Invalid test manager URL "%s"' % (oOptions.sTestManagerUrl,)); + return TBS_EXITCODE_SYNTAX; + + for sPrefix in ['sBuilds', 'sTestRsrc']: + sType = getattr(oOptions, sPrefix + 'ServerType'); + if sType is None or not sType.strip(): + setattr(oOptions, sPrefix + 'ServerType', None); + elif sType not in ['cifs', 'nfs']: + print('Syntax error: Invalid server type "%s"' % (sType,)); + return TBS_EXITCODE_SYNTAX; + + + # + # Instantiate the testbox script and start dispatching work. + # + try: + oTestBoxScript = TestBoxScript(oOptions); + except TestBoxScriptException as oXcpt: + print('Error: %s' % (oXcpt,)); + return TBS_EXITCODE_SYNTAX; + oTestBoxScript.dispatch(); + + # Not supposed to get here... + return TBS_EXITCODE_FAILURE; + + + +if __name__ == '__main__': + sys.exit(TestBoxScript.main()); + diff --git a/src/VBox/ValidationKit/testboxscript/testboxtasks.py b/src/VBox/ValidationKit/testboxscript/testboxtasks.py new file mode 100755 index 00000000..5f244063 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxtasks.py @@ -0,0 +1,917 @@ +# -*- coding: utf-8 -*- +# $Id: testboxtasks.py $ + +""" +TestBox Script - Async Tasks. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +# Standard python imports. +from datetime import datetime +import os +import re +import signal; +import sys +import subprocess +import threading +import time + +# Validation Kit imports. +from common import constants +from common import utils; +from common import webutils; +import testboxcommons + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); + + + +class TestBoxBaseTask(object): + """ + Asynchronous task employing a thread to do the actual work. + """ + + ## Time to wait for a task to terminate. + kcSecTerminateTimeout = 60 + + def __init__(self, oTestBoxScript, cSecTimeout, fnThreadProc): + self._oTestBoxScript = oTestBoxScript; + self._cSecTimeout = cSecTimeout; + self._tsSecStarted = utils.timestampSecond(); + self.__oRLock = threading.RLock(); + self._oCv = threading.Condition(self.__oRLock); + self._fRunning = True; # Protected by lock. + self._fShouldTerminate = False; # Protected by lock. + + # Spawn the worker thread. + self._oThread = threading.Thread(target=fnThreadProc); + self._oThread.daemon = True; + self._oThread.start(); + + def _lock(self): + """ Take the CV lock. """ + self._oCv.acquire(); + + def _unlock(self): + """ Release the CV lock. """ + self._oCv.release(); + + def _complete(self): + """ + Indicate that the task is complete, waking up the main thread. + Usually called at the end of the thread procedure. + """ + self._lock(); + self._fRunning = False; + self._oCv.notifyAll(); + self._unlock(); + + def isRunning(self): + """ Check if the task is still running. """ + self._lock(); + fRunning = self._fRunning; + self._unlock(); + return fRunning; + + def wait(self, cSecTimeout): + """ Wait for the task to complete. """ + self._lock(); + fRunning = self._fRunning; + if fRunning is True and cSecTimeout > 0: + self._oCv.wait(cSecTimeout) + self._unlock(); + return fRunning; + + def terminate(self, cSecTimeout = kcSecTerminateTimeout): + """ Terminate the task. """ + self._lock(); + self._fShouldTerminate = True; + self._unlock(); + + return self.wait(cSecTimeout); + + def _shouldTerminate(self): + """ + Returns True if we should terminate, False if not. + """ + self._lock(); + fShouldTerminate = self._fShouldTerminate is True; + self._unlock(); + return fShouldTerminate; + + +class TestBoxTestDriverTask(TestBoxBaseTask): + """ + Base class for tasks involving test drivers. + """ + + ## When to flush the backlog of log messages. + kcchMaxBackLog = 32768; + + ## The backlog sync time (seconds). + kcSecBackLogFlush = 30; + + ## The timeout for the cleanup job (5 mins). + kcSecCleanupTimeout = 300; + ## The timeout to wait for the abort command before killing it. + kcSecAbortTimeout = 300; + + ## The timeout to wait for the final output to be processed. + kcSecFinalOutputTimeout = 180; + ## The timeout to wait for the abort command output to be processed. + kcSecAbortCmdOutputTimeout = 30; + ## The timeout to wait for the terminate output to be processed. + kcSecTerminateOutputTimeout = 30; + ## The timeout to wait for the kill output to be processed. + kcSecKillOutputTimeout = 30; + + ## The timeout for talking to the test manager. + ksecTestManagerTimeout = 60; + + + def __init__(self, oTestBoxScript, fnThreadProc, cSecTimeout, idResult, sScriptCmdLine): + """ + Class instance init + """ + # Init our instance data. + self._idResult = idResult; + self._sScriptCmdLine = sScriptCmdLine; + self._oChild = None; + self._oBackLogLock = threading.RLock(); + self._oBackLogFlushLock = threading.RLock(); + self._asBackLog = []; + self._cchBackLog = 0; + self._secTsBackLogFlush = utils.timestampSecond(); + + # Init super. + TestBoxBaseTask.__init__(self, oTestBoxScript, cSecTimeout, fnThreadProc); + + def terminate(self, cSecTimeout = kcSecCleanupTimeout): + """ Reimplement with higher default timeout. """ + return TestBoxBaseTask.terminate(self, cSecTimeout); + + def _logFlush(self, oGivenConnection = None): + """ + Flushes the log to the test manager. + + No exceptions. + """ + fRc = True; + self._oBackLogFlushLock.acquire(); + + # Grab the current back log. + self._oBackLogLock.acquire(); + asBackLog = self._asBackLog; + self._asBackLog = []; + self._cchBackLog = 0; + self._secTsBackLogFlush = utils.timestampSecond(); + self._oBackLogLock.release(); + + # If there is anything to flush, flush it. + if asBackLog: + sBody = ''; + for sLine in asBackLog: + sBody += sLine + '\n'; + + oConnection = None; + try: + if oGivenConnection is None: + oConnection = self._oTestBoxScript.openTestManagerConnection(); + oConnection.postRequest(constants.tbreq.LOG_MAIN, {constants.tbreq.LOG_PARAM_BODY: sBody}); + oConnection.close(); + else: + oGivenConnection.postRequest(constants.tbreq.LOG_MAIN, {constants.tbreq.LOG_PARAM_BODY: sBody}); + except Exception as oXcpt: + testboxcommons.log('_logFlush error: %s' % (oXcpt,)); + if len(sBody) < self.kcchMaxBackLog * 4: + self._oBackLogLock.acquire(); + asBackLog.extend(self._asBackLog); + self._asBackLog = asBackLog; + # Don't restore _cchBackLog as there is no point in retrying immediately. + self._oBackLogLock.release(); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + fRc = False; + + self._oBackLogFlushLock.release(); + return fRc; + + def flushLogOnConnection(self, oConnection): + """ + Attempts to flush the logon the given connection. + + No exceptions. + """ + return self._logFlush(oConnection); + + def _logInternal(self, sMessage, fPrefix = True, fFlushCheck = False): + """ + Internal logging. + Won't flush the backlog, returns a flush indicator so the caller can + do it instead. + """ + if fPrefix: + try: + oNow = datetime.utcnow(); + sTs = '%02u:%02u:%02u.%06u ' % (oNow.hour, oNow.minute, oNow.second, oNow.microsecond); + except Exception as oXcpt: + sTs = 'oXcpt=%s ' % (oXcpt); + sFullMsg = sTs + sMessage; + else: + sFullMsg = sMessage; + + self._oBackLogLock.acquire(); + self._asBackLog.append(sFullMsg); + cchBackLog = self._cchBackLog + len(sFullMsg) + 1; + self._cchBackLog = cchBackLog; + secTsBackLogFlush = self._secTsBackLogFlush; + self._oBackLogLock.release(); + + testboxcommons.log(sFullMsg); + return fFlushCheck \ + and ( cchBackLog >= self.kcchMaxBackLog \ + or utils.timestampSecond() - secTsBackLogFlush >= self.kcSecBackLogFlush); + + def _log(self, sMessage): + """ + General logging function, will flush. + """ + if self._logInternal(sMessage, fFlushCheck = True): + self._logFlush(); + return True; + + def _reportDone(self, sResult): + """ + Report EXEC job done to the test manager. + + sResult is a value from constants.result. + """ + ## @todo optimize this to use one server connection. + + # + # Log it. + # + assert sResult in constants.result.g_kasValidResults; + self._log('Done %s' % (sResult,)); + + # + # Report it. + # + fRc = True; + secStart = utils.timestampSecond(); + while True: + self._logFlush(); ## @todo Combine this with EXEC_COMPLETED. + oConnection = None; + try: + oConnection = self._oTestBoxScript.openTestManagerConnection(); + oConnection.postRequest(constants.tbreq.EXEC_COMPLETED, {constants.tbreq.EXEC_COMPLETED_PARAM_RESULT: sResult}); + oConnection.close(); + except Exception as oXcpt: + if utils.timestampSecond() - secStart < self.ksecTestManagerTimeout: + self._log('_reportDone exception (%s) - retrying...' % (oXcpt,)); + time.sleep(2); + continue; + self._log('_reportDone error: %s' % (oXcpt,)); + if oConnection is not None: # Be kind to apache. + try: oConnection.close(); + except: pass; + fRc = False; + break; + + # + # Mark the task as completed. + # + self._complete(); + return fRc; + + def _assembleArguments(self, sAction, fWithInterpreter = True): + """ + Creates an argument array for subprocess.Popen, splitting the + sScriptCmdLine like bourne shell would. + fWithInterpreter is used (False) when checking that the script exists. + + Returns None on bad input. + """ + + # + # This is a good place to export the test set id to the environment. + # + os.environ['TESTBOX_TEST_SET_ID'] = str(self._idResult); + cTimeoutLeft = utils.timestampSecond() - self._tsSecStarted; + cTimeoutLeft = 0 if cTimeoutLeft >= self._cSecTimeout else self._cSecTimeout - cTimeoutLeft; + os.environ['TESTBOX_TIMEOUT'] = str(cTimeoutLeft); + os.environ['TESTBOX_TIMEOUT_ABS'] = str(self._tsSecStarted + self._cSecTimeout); + + # + # Do replacements and split the command line into arguments. + # + if self._sScriptCmdLine.find('@ACTION@') >= 0: + sCmdLine = self._sScriptCmdLine.replace('@ACTION@', sAction); + else: + sCmdLine = self._sScriptCmdLine + ' ' + sAction; + for sVar in [ 'TESTBOX_PATH_BUILDS', 'TESTBOX_PATH_RESOURCES', 'TESTBOX_PATH_SCRATCH', 'TESTBOX_PATH_SCRIPTS', + 'TESTBOX_PATH_UPLOAD', 'TESTBOX_UUID', 'TESTBOX_REPORTER', 'TESTBOX_ID', 'TESTBOX_TEST_SET_ID', + 'TESTBOX_TIMEOUT', 'TESTBOX_TIMEOUT_ABS' ]: + if sCmdLine.find('${' + sVar + '}') >= 0: + sCmdLine = sCmdLine.replace('${' + sVar + '}', os.environ[sVar]); + + asArgs = utils.argsSplit(sCmdLine); + + # + # Massage argv[0]: + # - Convert portable slashes ('/') to the flavor preferred by the + # OS we're currently running on. + # - Run python script thru the current python interpreter (important + # on systems that doesn't sport native hash-bang script execution). + # + asArgs[0] = asArgs[0].replace('/', os.path.sep); + if not os.path.isabs(asArgs[0]): + asArgs[0] = os.path.join(self._oTestBoxScript.getPathScripts(), asArgs[0]); + + if asArgs[0].endswith('.py') and fWithInterpreter: + if sys.executable: + asArgs.insert(0, sys.executable); + else: + asArgs.insert(0, 'python'); + + return asArgs; + + def _outputThreadProc(self, oChild, oStdOut, sAction): + """ + Thread procedure for the thread that reads the output of the child + process. We use a dedicated thread for this purpose since non-blocking + I/O may be hard to keep portable according to hints around the web... + """ + oThread = oChild.oOutputThread; + while not oThread.fPleaseQuit: + # Get a line. + try: + sLine = oStdOut.readline(); + except Exception as oXcpt: + self._log('child (%s) pipe I/O error: %s' % (sAction, oXcpt,)); + break; + + # EOF? + if not sLine: + break; + + # Strip trailing new line (DOS and UNIX). + if sLine.endswith("\r\n"): + sLine = sLine[0:-2]; + elif sLine.endswith("\n"): + sLine = sLine[0:-1]; + + # Log it. + if self._logInternal(sLine, fPrefix = False, fFlushCheck = True): + self._logFlush(); + + # Close the stdout pipe in case we were told to get lost. + try: + oStdOut.close(); + except Exception as oXcpt: + self._log('warning: Exception closing stdout pipe of "%s" child: %s' % (sAction, oXcpt,)); + + # This is a bit hacky, but try reap the child so it won't hang as + # defunkt during abort/timeout. + if oChild.poll() is None: + for _ in range(15): + time.sleep(0.2); + if oChild.poll() is not None: + break; + + oChild = None; + return None; + + def _spawnChild(self, sAction): + """ + Spawns the child process, returning success indicator + child object. + """ + + # Argument list. + asArgs = self._assembleArguments(sAction) + if asArgs is None: + self._log('Malformed command line: "%s"' % (self._sScriptCmdLine,)); + return (False, None); + + # Spawn child. + try: + oChild = utils.processPopenSafe(asArgs, + shell = False, + bufsize = -1, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + cwd = self._oTestBoxScript.getPathSpill(), + universal_newlines = True, + close_fds = (False if utils.getHostOs() == 'win' else True), + preexec_fn = (None if utils.getHostOs() in ['win', 'os2'] + else os.setsid)); # pylint: disable=E1101 + except Exception as oXcpt: + self._log('Error creating child process %s: %s' % (asArgs, oXcpt)); + return (False, None); + + oChild.sTestBoxScriptAction = sAction; + + # Start output thread, extending the child object to keep track of it. + oChild.oOutputThread = threading.Thread(target=self._outputThreadProc, args=(oChild, oChild.stdout, sAction)) + oChild.oOutputThread.daemon = True; + oChild.oOutputThread.fPleaseQuit = False; # Our extension. + oChild.oOutputThread.start(); + + return (True, oChild); + + def _monitorChild(self, cSecTimeout, fTryKillCommand = True, oChild = None): + """ + Monitors the child process. If the child executes longer that + cSecTimeout allows, we'll terminate it. + Returns Success indicator and constants.result value. + """ + + if oChild is None: + oChild = self._oChild; + + iProcGroup = oChild.pid; + if utils.getHostOs() in ['win', 'os2'] or iProcGroup <= 0: + iProcGroup = -2; + + # + # Do timeout processing and check the health of the child. + # + sResult = constants.result.PASSED; + seStarted = utils.timestampSecond(); + while True: + # Check status. + iRc = oChild.poll(); + if iRc is not None: + self._log('Child doing "%s" completed with exit code %d' % (oChild.sTestBoxScriptAction, iRc)); + oChild.oOutputThread.join(self.kcSecFinalOutputTimeout); + + if oChild is self._oChild: + self._oChild = None; + + if iRc == constants.rtexitcode.SKIPPED: + return (True, constants.result.SKIPPED); + if iRc != constants.rtexitcode.SUCCESS: + return (False, constants.result.FAILED); + return (True, constants.result.PASSED); + + # Check for abort first, since that has less of a stigma. + if self._shouldTerminate() is True: + sResult = constants.result.ABORTED; + break; + + # Check timeout. + cSecElapsed = utils.timestampSecond() - seStarted; + if cSecElapsed > cSecTimeout: + self._log('Timeout: %u secs (limit %u secs)' % (cSecElapsed, cSecTimeout)); + sResult = constants.result.TIMED_OUT; + break; + + # Wait. + cSecLeft = cSecTimeout - cSecElapsed; + oChild.oOutputThread.join(15 if cSecLeft > 15 else (cSecLeft + 1)); + + # + # If the child is still alive, try use the abort command to stop it + # very gently. This let's the testdriver clean up daemon processes + # and such that our code below won't catch. + # + if fTryKillCommand and oChild.poll() is None: + self._log('Attempting to abort child...'); + (fRc2, oAbortChild) = self._spawnChild('abort'); + if oAbortChild is not None and fRc2 is True: + self._monitorChild(self.kcSecAbortTimeout, False, oAbortChild); + oAbortChild = None; + + # + # If the child is still alive, try the polite way. + # + if oChild.poll() is None: + self._log('Attempting to terminate child doing "%s"...' % (oChild.sTestBoxScriptAction,)); + + if iProcGroup > 0: + try: + os.killpg(iProcGroup, signal.SIGTERM); # pylint: disable=E1101 + except Exception as oXcpt: + self._log('killpg() failed: %s' % (oXcpt,)); + + try: + self._oChild.terminate(); + oChild.oOutputThread.join(self.kcSecTerminateOutputTimeout); + except Exception as oXcpt: + self._log('terminate() failed: %s' % (oXcpt,)); + + # + # If the child doesn't respond to polite, kill it. Always do a killpg + # should there be any processes left in the group. + # + if iProcGroup > 0: + try: + os.killpg(iProcGroup, signal.SIGKILL); # pylint: disable=E1101 + except Exception as oXcpt: + self._log('killpg() failed: %s' % (oXcpt,)); + + if oChild.poll() is None: + self._log('Attemting to kill child doing "%s"...' % (oChild.sTestBoxScriptAction,)); + try: + self._oChild.kill(); + oChild.oOutputThread.join(self.kcSecKillOutputTimeout); + except Exception as oXcpt: + self._log('kill() failed: %s' % (oXcpt,)); + + # + # Give the whole mess a couple of more seconds to respond in case the + # output thread exitted prematurely for some weird reason. + # + if oChild.poll() is None: + time.sleep(2); + time.sleep(2); + time.sleep(2); + + iRc = oChild.poll(); + if iRc is not None: + self._log('Child doing "%s" aborted with exit code %d' % (oChild.sTestBoxScriptAction, iRc)); + else: + self._log('Child doing "%s" is still running, giving up...' % (oChild.sTestBoxScriptAction,)); + ## @todo in this case we should probably try reboot the testbox... + oChild.oOutputThread.fPleaseQuit = True; + + if oChild is self._oChild: + self._oChild = None; + return (False, sResult); + + def _terminateChild(self): + """ + Terminates the child forcefully. + """ + if self._oChild is not None: + pass; + + def _cleanupAfter(self): + """ + Cleans up after a test failure. (On success, cleanup is implicit.) + """ + assert self._oChild is None; + + # + # Tell the script to clean up. + # + if self._sScriptCmdLine: # can be empty if cleanup crashed. + (fRc, self._oChild) = self._spawnChild('cleanup-after'); + if fRc is True: + (fRc, _) = self._monitorChild(self.kcSecCleanupTimeout, False); + self._terminateChild(); + else: + fRc = False; + + # + # Wipe the stuff clean. + # + fRc2 = self._oTestBoxScript.reinitScratch(fnLog = self._log, cRetries = 6); + + return fRc and fRc2; + + + +class TestBoxCleanupTask(TestBoxTestDriverTask): + """ + Special asynchronous task for cleaning up a stale test when starting the + testbox script. It's assumed that the reason for the stale test lies in + it causing a panic, reboot, or similar, so we'll also try collect some + info about recent system crashes and reboots. + """ + + def __init__(self, oTestBoxScript): + # Read the old state, throwing a fit if it's invalid. + sScriptState = oTestBoxScript.getPathState(); + sScriptCmdLine = self._readStateFile(os.path.join(sScriptState, 'script-cmdline.txt')); + sResultId = self._readStateFile(os.path.join(sScriptState, 'result-id.txt')); + try: + idResult = int(sResultId); + if idResult <= 0 or idResult >= 0x7fffffff: + raise Exception(''); + except: + raise Exception('Invalid id value "%s" found in %s' % (sResultId, os.path.join(sScriptState, 'result-id.txt'))); + + sTestBoxId = self._readStateFile(os.path.join(sScriptState, 'testbox-id.txt')); + try: + self.idTestBox = int(sTestBoxId); + if self.idTestBox <= 0 or self.idTestBox >= 0x7fffffff: + raise Exception(''); + except: + raise Exception('Invalid id value "%s" found in %s' % (sTestBoxId, os.path.join(sScriptState, 'testbox-id.txt'))); + self.sTestBoxName = self._readStateFile(os.path.join(sScriptState, 'testbox-name.txt')); + + # Init super. + TestBoxTestDriverTask.__init__(self, oTestBoxScript, self._threadProc, self.kcSecCleanupTimeout, + idResult, sScriptCmdLine); + + @staticmethod + def _readStateFile(sPath): + """ + Reads a state file, returning a string on success and otherwise raising + an exception. + """ + try: + oFile = open(sPath, "rb"); + sStr = oFile.read(); + sStr = sStr.decode('utf-8'); + oFile.close(); + return sStr.strip(); + except Exception as oXcpt: + raise Exception('Failed to read "%s": %s' % (sPath, oXcpt)); + + def _threadProc(self): + """ + Perform the actual clean up on script startup. + """ + + # + # First make sure we won't repeat this exercise should it turn out to + # trigger another reboot/panic/whatever. + # + sScriptCmdLine = os.path.join(self._oTestBoxScript.getPathState(), 'script-cmdline.txt'); + try: + os.remove(sScriptCmdLine); + oFile = open(sScriptCmdLine, 'wb'); + oFile.close(); + except Exception as oXcpt: + self._log('Error truncating "%s": %s' % (sScriptCmdLine, oXcpt)); + + # + # Report the incident. + # + self._log('Seems we rebooted!'); + self._log('script-cmdline="%s"' % (self._sScriptCmdLine)); + self._log('result-id=%d' % (self._idResult)); + self._log('testbox-id=%d' % (self.idTestBox)); + self._log('testbox-name=%s' % (self.sTestBoxName)); + self._logFlush(); + + # System specific info. + sOs = utils.getHostOs(); + if sOs == 'darwin': + self._log('NVRAM Panic Info:\n%s\n' % (self.darwinGetPanicInfo(),)); + + self._logFlush(); + ## @todo Add some special command for reporting this situation so we get something + # useful in the event log. + + # + # Do the cleaning up. + # + self._cleanupAfter(); + + self._reportDone(constants.result.REBOOTED); + return False; + + def darwinGetPanicInfo(self): + """ + Returns a string with the aapl,panic-info content. + """ + # Retriev the info. + try: + sRawInfo = utils.processOutputChecked(['nvram', 'aapl,panic-info']); + except Exception as oXcpt: + return 'exception running nvram: %s' % (oXcpt,); + + # Decode (%xx) and decompact it (7-bit -> 8-bit). + ahDigits = \ + { + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, + '8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15, + }; + sInfo = ''; + off = len('aapl,panic-info') + 1; + iBit = 0; + bLow = 0; + + while off < len(sRawInfo): + # isprint is used to determine whether to %xx or %c it, so we have to + # be a little careful before assuming % sequences are hex bytes. + if sRawInfo[off] == '%' \ + and off + 3 <= len(sRawInfo) \ + and sRawInfo[off + 1] in ahDigits \ + and sRawInfo[off + 2] in ahDigits: + bCur = ahDigits[sRawInfo[off + 1]] * 0x10 + ahDigits[sRawInfo[off + 2]]; + off += 3; + else: + bCur = ord(sRawInfo[off]); + off += 1; + + sInfo += chr(((bCur & (0x7f >> iBit)) << iBit) | bLow); + bLow = bCur >> (7 - iBit); + + if iBit < 6: + iBit += 1; + else: + # Final bit in sequence. + sInfo += chr(bLow); + bLow = 0; + iBit = 0; + + # Expand shorthand. + sInfo = sInfo.replace('@', 'com.apple.'); + sInfo = sInfo.replace('>', 'com.apple.driver.'); + sInfo = sInfo.replace('|', 'com.apple.iokit.'); + sInfo = sInfo.replace('$', 'com.apple.security.'); + sInfo = sInfo.replace('!A', 'Apple'); + sInfo = sInfo.replace('!a', 'Action'); + sInfo = sInfo.replace('!B', 'Bluetooth'); + sInfo = sInfo.replace('!C', 'Controller'); + sInfo = sInfo.replace('!F', 'Family'); + sInfo = sInfo.replace('!I', 'Intel'); + sInfo = sInfo.replace('!U', 'AppleUSB'); + sInfo = sInfo.replace('!P', 'Profile'); + + # Done. + return sInfo + + +class TestBoxExecTask(TestBoxTestDriverTask): + """ + Implementation of a asynchronous EXEC task. + + This uses a thread for doing the actual work, i.e. starting and monitoring + the child process, processing its output, and more. + """ + + def __init__(self, oTestBoxScript, idResult, sScriptZips, sScriptCmdLine, cSecTimeout): + """ + Class instance init + """ + # Init our instance data. + self._sScriptZips = sScriptZips; + + # Init super. + TestBoxTestDriverTask.__init__(self, oTestBoxScript, self._threadProc, cSecTimeout, idResult, sScriptCmdLine); + + @staticmethod + def _writeStateFile(sPath, sContent): + """ + Writes a state file, raising an exception on failure. + """ + try: + oFile = open(sPath, "wb"); + oFile.write(sContent.encode('utf-8')); + oFile.flush(); + try: os.fsync(oFile.fileno()); + except: pass; + oFile.close(); + except Exception as oXcpt: + raise Exception('Failed to write "%s": %s' % (sPath, oXcpt)); + return True; + + def _saveState(self): + """ + Saves the task state on disk so we can launch a TestBoxCleanupTask job + if the test should cause system panic or similar. + + Note! May later be extended to support tests that reboots the host. + """ + sScriptState = self._oTestBoxScript.getPathState(); + try: + self._writeStateFile(os.path.join(sScriptState, 'script-cmdline.txt'), self._sScriptCmdLine); + self._writeStateFile(os.path.join(sScriptState, 'result-id.txt'), str(self._idResult)); + self._writeStateFile(os.path.join(sScriptState, 'testbox-id.txt'), str(self._oTestBoxScript.getTestBoxId())); + self._writeStateFile(os.path.join(sScriptState, 'testbox-name.txt'), self._oTestBoxScript.getTestBoxName()); + except Exception as oXcpt: + self._log('Failed to write state: %s' % (oXcpt,)); + return False; + return True; + + def _downloadAndUnpackScriptZips(self): + """ + Downloads/copies the script ZIPs into TESTBOX_SCRIPT and unzips them to + the same directory. + + Raises no exceptions, returns log + success indicator instead. + """ + sPathScript = self._oTestBoxScript.getPathScripts(); + asArchives = self._sScriptZips.split(','); + for sArchive in asArchives: + sArchive = sArchive.strip(); + if not sArchive: + continue; + + # Figure the destination name (in scripts). + sDstFile = webutils.getFilename(sArchive); + if len(sDstFile) < 1 \ + or re.search('[^a-zA-Z0-9 !#$%&\'()@^_`{}~.-]', sDstFile) is not None: # FAT charset sans 128-255 + '.'. + self._log('Malformed script zip filename: %s' % (sArchive,)); + return False; + sDstFile = os.path.join(sPathScript, sDstFile); + + # Do the work. + if webutils.downloadFile(sArchive, sDstFile, self._oTestBoxScript.getPathBuilds(), self._log, self._log) is not True: + return False; + asFiles = utils.unpackFile(sDstFile, sPathScript, self._log, self._log); + if asFiles is None: + return False; + + # Since zip files doesn't always include mode masks, set the X bit + # of all of them so we can execute binaries and hash-bang scripts. + for sFile in asFiles: + utils.chmodPlusX(sFile); + + return True; + + def _threadProc(self): + """ + Do the work of an EXEC command. + """ + + sResult = constants.result.PASSED; + + # + # Start by preparing the scratch directories. + # + # Note! Failures at this stage are not treated as real errors since + # they may be caused by the previous test and other circumstances + # so we don't want to go fail a build because of this. + # + fRc = self._oTestBoxScript.reinitScratch(self._logInternal); + fNeedCleanUp = fRc; + if fRc is True: + fRc = self._downloadAndUnpackScriptZips(); + testboxcommons.log2('_threadProc: _downloadAndUnpackScriptZips -> %s' % (fRc,)); + if fRc is not True: + sResult = constants.result.BAD_TESTBOX; + + # + # Make sure the script exists. + # + if fRc is True: + sScript = self._assembleArguments('none', fWithInterpreter = False)[0]; + if not os.path.exists(sScript): + self._log('The test driver script "%s" cannot be found.' % (sScript,)); + sDir = sScript; + while len(sDir) > 3: + sDir = os.path.dirname(sDir); + if os.path.exists(sDir): + self._log('First existing parent directory is "%s".' % (sDir,)); + break; + fRc = False; + + if fRc is True: + # + # Start testdriver script. + # + fRc = self._saveState(); + if fRc: + (fRc, self._oChild) = self._spawnChild('all'); + testboxcommons.log2('_threadProc: _spawnChild -> %s, %s' % (fRc, self._oChild)); + if fRc: + (fRc, sResult) = self._monitorChild(self._cSecTimeout); + testboxcommons.log2('_threadProc: _monitorChild -> %s' % (fRc,)); + + # If the run failed, do explicit cleanup. + if fRc is not True: + testboxcommons.log2('_threadProc: explicit cleanups...'); + self._terminateChild(); + self._cleanupAfter(); + fNeedCleanUp = False; + assert self._oChild is None; + + # + # Clean up scratch. + # + if fNeedCleanUp: + if self._oTestBoxScript.reinitScratch(self._logInternal, cRetries = 6) is not True: + self._log('post run reinitScratch failed.'); + fRc = False; + + # + # Report status and everything back to the test manager. + # + if fRc is False and sResult == constants.result.PASSED: + sResult = constants.result.FAILED; + self._reportDone(sResult); + return fRc; + diff --git a/src/VBox/ValidationKit/testboxscript/testboxupgrade.py b/src/VBox/ValidationKit/testboxscript/testboxupgrade.py new file mode 100755 index 00000000..94c4d410 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/testboxupgrade.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# $Id: testboxupgrade.py $ + +""" +TestBox Script - Upgrade from local file ZIP. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + +# Standard python imports. +import os +import shutil +import sys +import subprocess +import threading +import uuid; +import zipfile + +# Validation Kit imports. +import testboxcommons +from testboxscript import TBS_EXITCODE_SYNTAX; +from common import utils; + +# Figure where we are. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__)); +g_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir); + + +def _doUpgradeThreadProc(oStdOut, asBuf): + """Thread procedure for the upgrade test drive.""" + asBuf.append(oStdOut.read()); + return True; + + +def _doUpgradeCheckZip(oZip): + """ + Check that the essential files are there. + Returns list of members on success, None on failure. + """ + asMembers = oZip.namelist(); + if ('testboxscript/testboxscript/testboxscript.py' not in asMembers) \ + or ('testboxscript/testboxscript/testboxscript_real.py' not in asMembers): + testboxcommons.log('Missing one or both testboxscripts (members: %s)' % (asMembers,)); + return None; + + for sMember in asMembers: + if not sMember.startswith('testboxscript/'): + testboxcommons.log('zip file contains member outside testboxscript/: "%s"' % (sMember,)); + return None; + if sMember.find('/../') > 0 or sMember.endswith('/..'): + testboxcommons.log('zip file contains member with escape sequence: "%s"' % (sMember,)); + return None; + + return asMembers; + +def _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers): + """ + Unzips the files into sUpdateDir, does chmod(755) on all files and + checks that there are no symlinks or special files. + Returns True/False. + """ + # + # Extract the files. + # + if os.path.exists(sUpgradeDir): + shutil.rmtree(sUpgradeDir); + for sMember in asMembers: + if sMember.endswith('/'): + os.makedirs(os.path.join(sUpgradeDir, sMember.replace('/', os.path.sep)), 0o775); + else: + oZip.extract(sMember, sUpgradeDir); + + # + # Make all files executable and make sure only owner can write to them. + # While at it, also check that there are only files and directory, no + # symbolic links or special stuff. + # + for sMember in asMembers: + sFull = os.path.join(sUpgradeDir, sMember); + if sMember.endswith('/'): + if not os.path.isdir(sFull): + testboxcommons.log('Not directory: "%s"' % sFull); + return False; + else: + if not os.path.isfile(sFull): + testboxcommons.log('Not regular file: "%s"' % sFull); + return False; + try: + os.chmod(sFull, 0o755); + except Exception as oXcpt: + testboxcommons.log('warning chmod error on %s: %s' % (sFull, oXcpt)); + return True; + +def _doUpgradeTestRun(sUpgradeDir): + """ + Do a testrun of the new script, to make sure it doesn't fail with + to run in any way because of old python, missing import or generally + busted upgrade. + Returns True/False. + """ + asArgs = [os.path.join(sUpgradeDir, 'testboxscript', 'testboxscript', 'testboxscript.py'), '--version' ]; + testboxcommons.log('Testing the new testbox script (%s)...' % (asArgs[0],)); + if sys.executable: + asArgs.insert(0, sys.executable); + oChild = subprocess.Popen(asArgs, shell = False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + + asBuf = [] + oThread = threading.Thread(target=_doUpgradeThreadProc, args=(oChild.stdout, asBuf)); + oThread.daemon = True; + oThread.start(); + oThread.join(30); + + iStatus = oChild.poll(); + if iStatus is None: + testboxcommons.log('Checking the new testboxscript timed out.'); + oChild.terminate(); + oThread.join(5); + return False; + if iStatus is not TBS_EXITCODE_SYNTAX: + testboxcommons.log('The new testboxscript returned %d instead of %d during check.' \ + % (iStatus, TBS_EXITCODE_SYNTAX)); + return False; + + sOutput = ''.join(asBuf); + sOutput = sOutput.strip(); + try: + iNewVersion = int(sOutput); + except: + testboxcommons.log('The new testboxscript returned an unparseable version string: "%s"!' % (sOutput,)); + return False; + testboxcommons.log('New script version: %s' % (iNewVersion,)); + return True; + +def _doUpgradeApply(sUpgradeDir, asMembers): + """ + # Apply the directories and files from the upgrade. + returns True/False/Exception. + """ + + # + # Create directories first since that's least intrusive. + # + for sMember in asMembers: + if sMember[-1] == '/': + sMember = sMember[len('testboxscript/'):]; + if sMember != '': + sFull = os.path.join(g_ksValidationKitDir, sMember); + if not os.path.isdir(sFull): + os.makedirs(sFull, 0o755); + + # + # Move the files into place. + # + fRc = True; + asOldFiles = []; + for sMember in asMembers: + if sMember[-1] != '/': + sSrc = os.path.join(sUpgradeDir, sMember); + sDst = os.path.join(g_ksValidationKitDir, sMember[len('testboxscript/'):]); + + # Move the old file out of the way first. + sDstRm = None; + if os.path.exists(sDst): + testboxcommons.log2('Info: Installing "%s"' % (sDst,)); + sDstRm = '%s-delete-me-%s' % (sDst, uuid.uuid4(),); + try: + os.rename(sDst, sDstRm); + except Exception as oXcpt: + testboxcommons.log('Error: failed to rename (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt)); + try: + shutil.copy(sDst, sDstRm); + except Exception as oXcpt: + testboxcommons.log('Error: failed to copy (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt)); + break; + try: + os.unlink(sDst); + except Exception as oXcpt: + testboxcommons.log('Error: failed to unlink (old) "%s": %s' % (sDst, oXcpt)); + break; + + # Move/copy the new one into place. + testboxcommons.log2('Info: Installing "%s"' % (sDst,)); + try: + os.rename(sSrc, sDst); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to rename (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt)); + try: + shutil.copy(sSrc, sDst); + except: + testboxcommons.log('Error: failed to copy (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt)); + fRc = False; + break; + + # + # Roll back on failure. + # + if fRc is not True: + testboxcommons.log('Attempting to roll back old files...'); + for sDstRm in asOldFiles: + sDst = sDstRm[:sDstRm.rfind('-delete-me')]; + testboxcommons.log2('Info: Rolling back "%s" (%s)' % (sDst, os.path.basename(sDstRm))); + try: + shutil.move(sDstRm, sDst); + except: + testboxcommons.log('Error: failed to rollback "%s" onto "%s": %s' % (sDstRm, sDst, oXcpt)); + return False; + return True; + +def _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers): + """ + Clean up all obsolete files and directories. + Returns True (shouldn't fail or raise any exceptions). + """ + + try: + shutil.rmtree(sUpgradeDir, ignore_errors = True); + except: + pass; + + asKnownFiles = []; + asKnownDirs = []; + for sMember in asMembers: + sMember = sMember[len('testboxscript/'):]; + if sMember == '': + continue; + if sMember[-1] == '/': + asKnownDirs.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember[:-1]))); + else: + asKnownFiles.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember))); + + for sDirPath, asDirs, asFiles in os.walk(g_ksValidationKitDir, topdown=False): + for sDir in asDirs: + sFull = os.path.normpath(os.path.join(sDirPath, sDir)); + if sFull not in asKnownDirs: + testboxcommons.log2('Info: Removing obsolete directory "%s"' % (sFull,)); + try: + os.rmdir(sFull); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to rmdir obsolete dir "%s": %s' % (sFull, oXcpt)); + + for sFile in asFiles: + sFull = os.path.normpath(os.path.join(sDirPath, sFile)); + if sFull not in asKnownFiles: + testboxcommons.log2('Info: Removing obsolete file "%s"' % (sFull,)); + try: + os.unlink(sFull); + except Exception as oXcpt: + testboxcommons.log('Warning: failed to unlink obsolete file "%s": %s' % (sFull, oXcpt)); + return True; + +def upgradeFromZip(sZipFile): + """ + Upgrade the testboxscript install using the specified zip file. + Returns True/False. + """ + + # A little precaution. + if utils.isRunningFromCheckout(): + testboxcommons.log('Use "svn up" to "upgrade" your source tree!'); + return False; + + # + # Prepare. + # + # Note! Don't bother cleaning up files and dirs in the error paths, + # they'll be restricted to the one zip and the one upgrade dir. + # We'll remove them next time we upgrade. + # + oZip = zipfile.ZipFile(sZipFile, 'r'); + asMembers = _doUpgradeCheckZip(oZip); + if asMembers is None: + return False; + + sUpgradeDir = os.path.join(g_ksTestScriptDir, 'upgrade'); + testboxcommons.log('Unzipping "%s" to "%s"...' % (sZipFile, sUpgradeDir)); + if _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers) is not True: + return False; + oZip.close(); + + if _doUpgradeTestRun(sUpgradeDir) is not True: + return False; + + # + # Execute. + # + if _doUpgradeApply(sUpgradeDir, asMembers) is not True: + return False; + _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers); + return True; + + +# For testing purposes. +if __name__ == '__main__': + sys.exit(upgradeFromZip(sys.argv[1])); + diff --git a/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd b/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd new file mode 100644 index 00000000..4cb882ab --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd @@ -0,0 +1,54 @@ +@echo off
+REM $Id: autoexec-testbox.cmd $
+REM REM @file
+REM VirtualBox Validation Kit - testbox script, automatic execution wrapper.
+REM
+
+REM
+REM Copyright (C) 2006-2019 Oracle Corporation
+REM
+REM This file is part of VirtualBox Open Source Edition (OSE), as
+REM available from http://www.virtualbox.org. This file is free software;
+REM you can redistribute it and/or modify it under the terms of the GNU
+REM General Public License (GPL) as published by the Free Software
+REM Foundation, in version 2 as it comes in the "COPYING" file of the
+REM VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+REM hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+REM
+REM The contents of this file may alternatively be used under the terms
+REM of the Common Development and Distribution License Version 1.0
+REM (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+REM VirtualBox OSE distribution, in which case the provisions of the
+REM CDDL are applicable instead of those of the GPL.
+REM
+REM You may elect to license modified versions of this file under the
+REM terms and conditions of either the GPL or the CDDL or both.
+REM
+
+@echo "$Id: autoexec-testbox.cmd $"
+@echo on
+setlocal EnableExtensions
+set exe=python.exe
+for /f %%x in ('tasklist /NH /FI "IMAGENAME eq %exe%"') do if %%x == %exe% goto end
+
+if not exist %SystemRoot%\System32\imdisk.exe goto defaulttest
+
+REM Take presence of imdisk.exe as order to test in ramdisk.
+set RAMDRIVE=D:
+if exist %RAMDRIVE%\TEMP goto skip
+imdisk -a -s 16GB -m %RAMDRIVE% -p "/fs:ntfs /q /y" -o "awe"
+:skip
+
+set VBOX_INSTALL_PATH=%RAMDRIVE%\VBoxInstall
+set TMP=%RAMDRIVE%\TEMP
+set TEMP=%TMP%
+
+mkdir %VBOX_INSTALL_PATH%
+mkdir %TMP%
+
+set TESTBOXSCRIPT_OPTS=--scratch-root=%RAMDRIVE%\testbox
+
+:defaulttest
+%SystemDrive%\Python27\python.exe %SystemDrive%\testboxscript\testboxscript\testboxscript.py --testrsrc-server-type=cifs --builds-server-type=cifs %TESTBOXSCRIPT_OPTS%
+pause
+:end
diff --git a/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py b/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py new file mode 100755 index 00000000..6df2c4d0 --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# $Id: fix_stale_refs.py $ + +""" +This module must be used interactively! +Use with caution as it will delete some values from the regisry! + +It tries to locate client references to products that no longer exist. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +from _winreg import HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS +from _winreg import OpenKey, CloseKey, EnumKey, QueryInfoKey, EnumValue, DeleteValue, QueryValueEx +from distutils.util import strtobool + +def reverse_bytes(hex_string): + """ + This function reverses the order of bytes in the provided string. + Each byte is represented by two characters which are reversed as well. + """ + #print 'reverse_bytes(' + hex_string + ')' + chars = len(hex_string) + if chars > 2: + return reverse_bytes(hex_string[chars/2:]) + reverse_bytes(hex_string[:chars/2]) + else: + return hex_string[1] + hex_string[0] + +def transpose_guid(guid): + """ + Windows Installer uses different way to present GUID string. This function converts GUID + from installer's presentation to more conventional form. + """ + return '{' + reverse_bytes(guid[0:8]) + '-' + reverse_bytes(guid[8:12]) + \ + '-' + reverse_bytes(guid[12:16]) + \ + '-' + reverse_bytes(guid[16:18]) + reverse_bytes(guid[18:20]) + \ + '-' + ''.join([reverse_bytes(guid[i:i+2]) for i in range(20, 32, 2)]) + '}' + +PRODUCTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products' +COMPONENTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components' + +def get_installed_products(): + """ + Enumerate all installed products. + """ + products = {} + hkey_products = OpenKey(HKEY_LOCAL_MACHINE, PRODUCTS_KEY, 0, KEY_ALL_ACCESS) + + try: + product_index = 0 + while True: + product_guid = EnumKey(hkey_products, product_index) + hkey_product_properties = OpenKey(hkey_products, product_guid + r'\InstallProperties', 0, KEY_ALL_ACCESS) + try: + value = QueryValueEx(hkey_product_properties, 'DisplayName')[0] + except WindowsError, exception: + if exception.winerror != 2: + raise + value = '<unknown>' + CloseKey(hkey_product_properties) + products[product_guid] = value + product_index += 1 + except WindowsError, exceptione: + if exceptione.winerror != 259: + print exceptione.strerror + '.', 'error', exceptione.winerror + CloseKey(hkey_products) + + print 'Installed products:' + for product_key in sorted(products.keys()): + print transpose_guid(product_key), '=', products[product_key] + + print + return products + +def get_missing_products(hkey_components): + """ + Detect references to missing products. + """ + products = get_installed_products() + + missing_products = {} + + for component_index in xrange(0, QueryInfoKey(hkey_components)[0]): + component_guid = EnumKey(hkey_components, component_index) + hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS) + clients = [] + for value_index in xrange(0, QueryInfoKey(hkey_component)[1]): + client_guid, client_path = EnumValue(hkey_component, value_index)[:2] + clients.append((client_guid, client_path)) + if not client_guid in products: + if client_guid in missing_products: + missing_products[client_guid].append((component_guid, client_path)) + else: + missing_products[client_guid] = [(component_guid, client_path)] + CloseKey(hkey_component) + return missing_products + +def main(): + """ + Enumerate all installed products, go through all components and check if client refences + point to valid products. Remove references to non-existing products if the user allowed it. + """ + hkey_components = OpenKey(HKEY_LOCAL_MACHINE, COMPONENTS_KEY, 0, KEY_ALL_ACCESS) + + missing_products = get_missing_products(hkey_components) + + print 'Missing products refer the following components:' + for product_guid in sorted(missing_products.keys()): + if product_guid[1:] == '0'*31: + continue + print 'Product', transpose_guid(product_guid) + ':' + for component_guid, component_file in missing_products[product_guid]: + print ' ' + transpose_guid(component_guid), '=', component_file + + print 'Remove all references to product', transpose_guid(product_guid) + '? [y/n]' + if strtobool(raw_input().lower()): + for component_guid, component_file in missing_products[product_guid]: + hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS) + print 'Removing reference in ' + transpose_guid(component_guid), '=', component_file + DeleteValue(hkey_component, product_guid) + CloseKey(hkey_component) + else: + print 'Cancelled removal of product', transpose_guid(product_guid) + + CloseKey(hkey_components) + +if __name__ == "__main__": + main() diff --git a/src/VBox/ValidationKit/testboxscript/win/readme.txt b/src/VBox/ValidationKit/testboxscript/win/readme.txt new file mode 100644 index 00000000..3da82f9d --- /dev/null +++ b/src/VBox/ValidationKit/testboxscript/win/readme.txt @@ -0,0 +1,157 @@ +$Id: readme.txt $ + + +Preparations: + +0. Make sure the computer name (what hostname prints) is the same as the DNS + returns (sans domain) for the host IP. + +1. Install Python 2.7.x from python.org to C:\Python27 or Python 3.y.x to + C:\Python3%y%, where y >= 5. Matching bit count as the host windows version. + +2. Install the win32 extension for python. + +3. Append C:\Python27 or C:\Python3%y% to the system PATH (tail). + +4. Disable UAC. + + Windows 8 / 8.1 / Server 2012: Set the following key to zero: + "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system\EnableLUA" + +5. Disable Automatic updates. (No rebooting during tests, thank you!) + + Ideally we would prevent windows from even checking for updates to avoid + influencing benchmarks and such, however the microsofties aren't keen on it. + So, disable it as much as possible. + + W10: gpedit.msc -> "Administrative Templates" -> "Windows Components" + -> "Windows Update": + - "Configure Automatic Updates": Enable and select "2 - Notify for + download and notiy for install". + - "Allow Automatic Updates immediate installation": Disable. + - "No auto-restart with logged on users for scheduled automatic + updates installations": Enabled. + +6. Go to the group policy editor (gpedit.msc) and change "Computer Configuration" + -> "Windows Settings" -> "Security Settings" -> "Local Policies" + -> "Security Options" -> "Network security: LAN Manager authentication level" + to "Send LM & NTLM- use NTLMv2 session security if negotiated". This fixed + passing the password as an argument to "NET USE" (don't ask why!). + +6b. While in the group policy editor, make sure that "Computer Configuration" + -> "Windows Settings" -> "Security Settings" [ -> "Local Policies" ] + -> "Account Policy" -> "Password must meet complexity requirements" is + disabled so the vbox account can be created later one. + +7. Need to disable the error popups blocking testing. + + Set "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Windows\ErrorMode" + to 2. This immediately disables hard error popups (missing DLLs and such). + + Then there are the sending info to microsoft, debug, dump, look for solution + questions we don't want. Not entirely sure what's required here yet, but + the following stuff might hopefully help (update after testing): + + On Windows XP: + + Go "Control Panel" -> "System Properties" -> "Advanced" + -> "Error Reporting" and check "Disable error reporting" + and uncheck "But notify me when critical erorr occurs". + + On Windows Vista and later: + + In gpedit change the following settings under "Computer Configuration" + -> "Administrative Templates" -> "Windows Components" + -> "Windows Error Reporting": + 1) Enable "Prevent display of the user interface for critical errors". + ... -> "Advanced Error Reporting Settings": + 1) Enable "Configure Report Archive" and set it to "Store All" for + up to 500 (or less) reports. + 2) Disable "Configure Report Queue". + + Run 'serverWerOptin /disable'. + + Then set "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\DontShowUI" + to 1. (Could do all the above from regedit if we wanted...) + +7b. Configure application crash dumps on Vista SP1 and later: + + Set the following values under the key + HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps: + DumpFolder [string] = C:\CrashDumps + DumpCount [dword] = 10 + DumpType [dword] = 1 (minidump) + CustomDumpFlags [dword] = 0 + + mkdir C:\CrashDumps + + See also http://msdn.microsoft.com/en-us/library/windows/desktop/bb787181%28v=vs.85%29.aspx + +7c. Enable verbose driver installation logging (C:\Windows\setupapi.dev.log): + + Create the following value under the key + HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Setup\ + LogLevel [dword] = 0xFF (255) + + If it already exists (typical on W10), just OR 0xff into the existing value. + +8. Install firefox or chrome, download the latest testboxscript*.zip from + the build box. If the testbox is very short on disk space, i.e. less than + 15GB free disk space after installing Windows Updates, install ImDisk 2.0.9 + or later from e.g. http://www.ltr-data.se/opencode.html/ + +9. Create a user named "vbox" with password "password". Must be an + Administrator user! + +10. Configure user "vbox" to log in automatically via "control userpasswords2". + +11. Open up the port ranges 6000-6100 (VRDP) for TCP traffic and 5000-5032 + (NetPerf) for both TCP and UDP traffic in the Windows Firewall. + From the command line (recommended in vista): + for /L %i in (6000,1,6100) do netsh firewall add portopening TCP %i "VRDP %i" + for /L %i in (5000,1,5032) do netsh firewall add portopening TCP %i "NetPerf %i TCP" + for /L %i in (5000,1,5032) do netsh firewall add portopening UDP %i "NetPerf %i UDP" + netsh firewall set icmpsetting type=ALL + +11b. Set a hostname which the test script can resolve to the host's IP address. + +12. Setup time server to "wei01-time.de.oracle.com" and update date/time. + +13. Activate windows. "https://linserv.de.oracle.com/vbox/wiki/MSDN Volume License Keys" + +14. Windows 2012 R2: If you experience mouse pointer problems connecting with rdesktop, + open the mouse pointer settings and disable mouse pointer shadow. + +15. Enable RDP access by opening "System Properties" and selecting "Allow + remote connections to this computer" in the "Remote" tab. Ensure that + "Allow connections only from computers running Remote Desktop with Network + Level Authentication" is not checked or rdesktop can't access it. + + W10: Make old rdesktop connect: + \HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\SecurityLayer + Change DWORD Hex '2' -> '1' + +15b. While you're in "System Properties", in the "Hardware" tab, button + "Driver Signing" tell it to ignore logo testing requirements. + + W10: Doesn't exist any more. + +The install (as user vbox): + +16. Disable loading CONIME. Set "HKEY_CURRENT_USER\Console\LoadConIme" to 0. + +17. Unzip (/ copy) the content of the testboxscript-*.zip to C:\testboxscript. + +18. Copy C:\testboxscript\testboxscript\win\autoexec-testbox.cmd to C:\. + +19. Create a shortcut to C:\autoexec-testbox.cmd and drag it into + "Start" -> "All Programs" -> "Startup". + + W10: Find startup folder by hitting Win+R and entering "shell:startup". + +20. If this is an Intel box and the CPU is capable of Nested Paging, edit C:\autoexec-testbox.cmd + and append '--nested-paging' + + +That's currently it. + |