summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/testboxscript
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/testboxscript')
-rw-r--r--src/VBox/ValidationKit/testboxscript/Makefile.kmk97
-rw-r--r--src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp780
-rw-r--r--src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh190
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/linux/setup-routines.sh172
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh519
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/setup.sh714
-rw-r--r--src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh360
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxcommand.py362
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxcommons.py146
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxconnection.py312
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxscript.py137
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxscript_real.py1073
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxtasks.py944
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/testboxupgrade.py339
-rw-r--r--src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd72
-rwxr-xr-xsrc/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py160
-rw-r--r--src/VBox/ValidationKit/testboxscript/win/readme.txt157
17 files changed, 6534 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..c2d7b22b
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/Makefile.kmk
@@ -0,0 +1,97 @@
+# $Id: Makefile.kmk $
+## @file
+# VirtualBox Validation Kit - TestBox Script.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+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_WINPSDK)_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..97264ebc
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/TestBoxHelper.cpp
@@ -0,0 +1,780 @@
+/* $Id: TestBoxHelper.cpp $ */
+/** @file
+ * VirtualBox Validation Kit - Testbox C Helper Utility.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* 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 ? 0
+ : (uint64_t)((double)cbInterval / ((double)cNsInterval / (double)RT_NS_1SEC));
+
+ 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)((double)cbWritten / ((double)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 (RTX86IsValidStdRange(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 (RTX86IsValidExtRange(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 (RTX86IsValidExtRange(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, 0);
+ 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 (RTX86IsValidStdRange(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 (RTX86IsValidExtRange(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 (RTX86IsValidStdRange(uEax) && uEax >= 1)
+ {
+ uint32_t uEax1 = ASMCpuId_EAX(1);
+ uint32_t uVersion = (RTX86GetCpuFamily(uEax1) << 24)
+ | (RTX86GetCpuModel(uEax1, RTX86IsIntelCpu(uEbx, uEcx, uEdx)) << 8)
+ | RTX86GetCpuStepping(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..ce6d802e
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/darwin/setup-routines.sh
@@ -0,0 +1,190 @@
+# $Id: setup-routines.sh $
+## @file
+# VirtualBox Validation Kit - TestBoxScript Service Setup on Mac OS X (darwin).
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+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/&/&amp;/g' -e 's/</&lt;/g' -e 's/>/&gt;/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. For configure the kernel to keep symbols you might need to:
+ a) For 10.11 (El Capitan) and later boot to the recovery partition and
+ either enabling loading of unsigned kexts:
+ $ csrutil enable --without kext
+ or disable SIP all together:
+ $ csrutil disable
+ b) For 10.15 (Catalina) and later you also need to disable
+ the reboot requirement (also from recovery partition):
+ $ spctl kext-consent disable
+ c) If you are running 10.10 (Yosemite) there is a boot-args option for
+ allowing the loading of unsigned kexts. Run the following and reboot:
+ $ sudo nvram boot-args="kext-dev-mode=1"
+ And then run the following:
+ $ 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..d9404c89
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/linux/setup-routines.sh
@@ -0,0 +1,172 @@
+#!/bin/sh
+# $Id: setup-routines.sh $
+## @file
+# VirtualBox Validation Kit - TestBoxScript Service Setup.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+
+# 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..66892817
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/linux/testboxscript-service.sh
@@ -0,0 +1,519 @@
+#!/bin/sh
+## @file
+# VirtualBox Validation Kit - TestBoxScript service init script.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# 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 or tmux.
+# $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'";
+ if type screen > /dev/null; then
+ su - "${a_USER}" -c "screen -S ${service_name} -d -m ${ARGS}";
+ elif type tmux > /dev/null; then
+ su - "${a_USER}" -c "tmux new-session -AdD -s ${service_name} ${ARGS}";
+ else
+ echo "Need screen or tmux, please install!"
+ exit 1
+ fi
+ 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 [ "${TESTBOXSCRIPT_SPB}" = "yes" ]; then PARAMS="${PARAMS} --spb"; 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..dd51f74a
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/setup.sh
@@ -0,0 +1,714 @@
+#!/usr/bin/env bash
+# $Id: setup.sh $
+## @file
+# VirtualBox Validation Kit - TestBoxScript Service Setup on Unixy platforms.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+
+#
+# !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|arm64|armv4l|armv5tel|armv5tejl)
+ RETVAL='arm'
+ ;;
+ arm64|aarch64)
+ RETVAL='arm64'
+ ;;
+ 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;;
+ "--spb") ;;
+ "--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}" == "--spb" ]; then
+ os_add_args "${!my_opt}"
+ elif [ "${!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"
+TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} BUILDS_PASSWD BUILDS_MOUNTOPT TESTRSRC_PATH TESTRSRC_TYPE TESTRSRC_NAME"
+TESTBOXSCRIPT_CFG_NAMES="${TESTBOXSCRIPT_CFG_NAMES} TESTRSRC_SHARE TESTRSRC_USER TESTRSRC_PASSWD TESTRSRC_MOUNTOPT SPB"
+
+# 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_SPB="--spb"
+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_SPB=""
+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: 155244 $'
+ 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;;
+ --spb) TESTBOXSCRIPT_SPB="yes";;
+ *)
+ 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] == 3 or (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..1560f687
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/solaris/setup-routines.sh
@@ -0,0 +1,360 @@
+# $Id: setup-routines.sh $
+## @file
+# VirtualBox Validation Kit - TestBoxScript Service Setup on Solaris.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+#
+# 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_FMRI="svc:/system/virtualbox/testboxscript"
+MY_SVCCFG="/usr/sbin/svccfg"
+MY_SVCADM="/usr/sbin/svcadm"
+MY_CHGRP="/usr/bin/chgrp"
+MY_TR="/usr/bin/tr"
+MY_TAB=`printf "\t"`
+
+if test "${MY_SOLARIS_VER}" -lt 11; then
+ # solaris 10 service import
+ MY_SVC="/tmp/testboxscript.xml"
+else
+ # use propper manifest directory
+ # /lib/svc/manifest/system for solaris 11 and higher for testboxscript.xml file
+
+ # Since sol 11.4 the solaris testboxscript service
+ # generates Warnings in /var/svc/log/system-manifest-import:default.log
+ # -------- Warning!!
+ # Configuring services...
+ # * Warning!! Importing Zone access service ...FAILED.
+
+ MY_SVC="/lib/svc/manifest/system/testboxscript.xml"
+fi
+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/&quot;/"/g' \
+ -e 's/&lt;/</g' \
+ -e 's/&gt;/>/g' \
+ -e 's/&amp;/&/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}" <<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}" <<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}" <<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
+
+ if test "${MY_SOLARIS_VER}" -lt 11; then
+ # 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}"
+
+ # only for solaris version less than 11
+ rm -f "${MY_SVC}"
+ else
+ "${MY_CHGRP}" "sys" "${MY_SVC}"
+ "${MY_SVCADM}" "restart" "manifest-import"
+
+ # Do not remove the xml file in Solaris versions 11 and higher.
+ # The service will be removed automatically, if the command
+ # svcadm restart manifest-import
+ # will be executed
+
+ fi
+ 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..8554ef8e
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxcommand.py
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+# $Id: testboxcommand.py $
+
+"""
+TestBox Script - Command Processor.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+# 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.
+ with self._oCurTaskLock:
+ self._oCurTask = TestBoxExecTask(self._oTestBoxScript, idResult = idResult, sScriptZips = sScriptZips,
+ sScriptCmdLine = sScriptCmdLine, cSecTimeout = cSecTimeout);
+ 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:
+ 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);
+
+ with self._oCurTaskLock:
+ self._oCurTask = oTask;
+
+ 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. """
+ with self._oCurTaskLock:
+ oCurTask = self._oCurTask;
+ return oCurTask;
+
diff --git a/src/VBox/ValidationKit/testboxscript/testboxcommons.py b/src/VBox/ValidationKit/testboxscript/testboxcommons.py
new file mode 100755
index 00000000..18a1c40a
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxcommons.py
@@ -0,0 +1,146 @@
+# -*- 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-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+# Standard python imports.
+import sys
+import traceback
+
+# Validation Kit imports.
+from common import utils;
+
+#
+# Exceptions.
+#
+
+class TestBoxException(Exception):
+ """
+ Custom exception class
+ """
+ pass; # pylint: disable=unnecessary-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: # pylint: disable=comparison-with-itself
+ 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..ecbcbf1b
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxconnection.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+# $Id: testboxconnection.py $
+
+"""
+TestBox Script - HTTP Connection Handling.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+# 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 = {};
+ # 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=no-member
+ 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=no-member
+ 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 = {};
+ 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..407ec2f7
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxscript.py
@@ -0,0 +1,137 @@
+#!/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-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+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=too-few-public-methods
+ """
+ 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:
+ with open(sPidFile, 'w') as oPidFile:
+ oPidFile.write(str(os.getpid()));
+
+ # 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, # pylint: disable=consider-using-with
+ 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..7a2581ae
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxscript_real.py
@@ -0,0 +1,1073 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# $Id: testboxscript_real.py $
+
+"""
+TestBox Script - main().
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+# Standard python imports.
+import math
+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; # pylint: disable=unnecessary-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=invalid-name
+
+ ## @}
+
+
+ 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=no-member
+ 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: utils.getPresentCpuCount },
+ 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: # pylint: disable=consider-using-dict-items
+ 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: # pylint: disable=consider-using-dict-items
+ 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, os.path.basename(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=no-member
+ 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=no-member
+ + ',gid=' + str(os.getgid()) # pylint: disable=no-member
+ + ',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(); # pylint: disable=consider-using-with
+ oPasswdFile.write(sPassword + '\n');
+ oPasswdFile.flush();
+ utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'smbfs',
+ '-o',
+ 'user=' + sUser
+ + ',uid=' + str(os.getuid()) # pylint: disable=no-member
+ + ',gid=' + str(os.getgid()) # pylint: disable=no-member
+ + ',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', '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 not in ('dunno', '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=import-error
+ oWmi = win32com.client.Dispatch('WbemScripting.SWbemLocator');
+ oWebm = oWmi.ConnectServer('.', 'root\\cimv2');
+ for oItem in oWebm.ExecQuery('SELECT * FROM Win32_ComputerSystemProduct'):
+ if oItem.UUID is not 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=no-member
+ 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=too-few-public-methods
+ """
+ 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: # pylint: disable=consider-using-dict-items
+ 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 = not self._fSignedOn or self._fNeedReSignOn;
+ for sItem in self._ddSignOnParams: # pylint: disable=consider-using-dict-items
+ if self._ddSignOnParams[sItem][self.FN] is None:
+ continue
+
+ sOldValue = self._ddSignOnParams[sItem][self.VALUE]
+ self._ddSignOnParams[sItem][self.VALUE] = self._ddSignOnParams[sItem][self.FN]()
+ if sOldValue != self._ddSignOnParams[sItem][self.VALUE]:
+ fNeedSignOn = True
+ testboxcommons.log('Detected %s parameter change: %s -> %s'
+ % (sItem, sOldValue, self._ddSignOnParams[sItem][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.");
+ def sbp_callback(option, opt_str, value, parser):
+ _, _, _ = opt_str, value, option
+ parser.values.sTestManagerUrl = 'http://10.162.100.8/testmanager/'
+ parser.values.sBuildsServerName = 'vbox-st02.ru.oracle.com'
+ parser.values.sTestRsrcServerName = 'vbox-st02.ru.oracle.com'
+ parser.values.sTestRsrcServerShare = 'scratch/data/testrsrc'
+ parser.add_option("--spb", "--load-sbp-defaults", action="callback", callback=sbp_callback,
+ help="Load defaults for the sbp setup.")
+
+ (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..4d34cbd8
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxtasks.py
@@ -0,0 +1,944 @@
+# -*- coding: utf-8 -*-
+# $Id: testboxtasks.py $
+
+"""
+TestBox Script - Async Tasks.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+# 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(); # pylint: disable=deprecated-method
+ 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;
+
+ with self._oBackLogFlushLock:
+ # Grab the current back log.
+ with self._oBackLogLock:
+ asBackLog = self._asBackLog;
+ self._asBackLog = [];
+ self._cchBackLog = 0;
+ self._secTsBackLogFlush = utils.timestampSecond();
+
+ # 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:
+ with self._oBackLogLock:
+ asBackLog.extend(self._asBackLog);
+ self._asBackLog = asBackLog;
+ # Don't restore _cchBackLog as there is no point in retrying immediately.
+ if oConnection is not None: # Be kind to apache.
+ try: oConnection.close();
+ except: pass;
+ fRc = False;
+
+ 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;
+
+ with self._oBackLogLock:
+ self._asBackLog.append(sFullMsg);
+ cchBackLog = self._cchBackLog + len(sFullMsg) + 1;
+ self._cchBackLog = cchBackLog;
+ secTsBackLogFlush = self._secTsBackLogFlush;
+
+ 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 = utils.getHostOs() != 'win',
+ preexec_fn = (None if utils.getHostOs() in ['win', 'os2']
+ else os.setsid)); # pylint: disable=no-member
+ 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.SUCCESS:
+ return (True, constants.result.PASSED);
+ if iRc == constants.rtexitcode.SKIPPED:
+ return (True, constants.result.SKIPPED);
+ if iRc == constants.rtexitcode.BAD_TESTBOX:
+ return (False, constants.result.BAD_TESTBOX);
+ return (False, constants.result.FAILED);
+
+ # 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=no-member
+ 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=no-member
+ 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:
+ with open(sPath, "rb") as oFile:
+ sStr = oFile.read();
+ sStr = sStr.decode('utf-8');
+ 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);
+ open(sScriptCmdLine, 'wb').close(); # pylint: disable=consider-using-with
+ 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:
+ with open(sPath, "wb") as oFile:
+ oFile.write(sContent.encode('utf-8'));
+ oFile.flush();
+ try: os.fsync(oFile.fileno());
+ except: pass;
+ except Exception as oXcpt:
+ raise Exception('Failed to write "%s": %s' % (sPath, oXcpt));
+ return True;
+
+ @staticmethod
+ def _environTxtContent():
+ """
+ Collects environment variables and values for the environ.txt stat file
+ (for external monitoring tool).
+ """
+ sText = '';
+ for sVar in [ 'TESTBOX_PATH_BUILDS', 'TESTBOX_PATH_RESOURCES', 'TESTBOX_PATH_SCRATCH', 'TESTBOX_PATH_SCRIPTS',
+ 'TESTBOX_PATH_UPLOAD', 'TESTBOX_HAS_HW_VIRT', 'TESTBOX_HAS_NESTED_PAGING', 'TESTBOX_HAS_IOMMU',
+ 'TESTBOX_SCRIPT_REV', 'TESTBOX_CPU_COUNT', 'TESTBOX_MEM_SIZE', 'TESTBOX_SCRATCH_SIZE',
+ 'TESTBOX_WITH_RAW_MODE', 'TESTBOX_WITH_RAW_MODE', 'TESTBOX_MANAGER_URL', 'TESTBOX_UUID',
+ 'TESTBOX_REPORTER', 'TESTBOX_NAME', 'TESTBOX_ID', 'TESTBOX_TEST_SET_ID',
+ 'TESTBOX_TIMEOUT', 'TESTBOX_TIMEOUT_ABS', ]:
+ sValue = os.environ.get(sVar);
+ if sValue:
+ sText += sVar + '=' + sValue + '\n';
+ return sText;
+
+ 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());
+ self._writeStateFile(os.path.join(sScriptState, 'environ.txt'), self._environTxtContent());
+ 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 not sDstFile \
+ 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 unless its a BAD_TESTBOX, since BAD_TESTBOX is
+ # intended for pre-cleanup problems caused by previous test failures. Do a cleanup on
+ # a BAD_TESTBOX could easily trigger an uninstallation error and change status to FAILED.
+ if fRc is not True:
+ if sResult != constants.result.BAD_TESTBOX:
+ 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..765bff3c
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/testboxupgrade.py
@@ -0,0 +1,339 @@
+# -*- coding: utf-8 -*-
+# $Id: testboxupgrade.py $
+
+"""
+TestBox Script - Upgrade from local file ZIP.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+# Standard python imports.
+import os
+import shutil
+import sys
+import subprocess
+import threading
+import time
+import uuid;
+import zipfile
+
+# Validation Kit imports.
+from common import utils;
+import testboxcommons
+from testboxscript import TBS_EXITCODE_SYNTAX;
+
+# 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, # pylint: disable=consider-using-with
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT);
+
+ asBuf = []
+ oThread = threading.Thread(target=_doUpgradeThreadProc, args=(oChild.stdout, asBuf));
+ oThread.daemon = True;
+ oThread.start();
+ oThread.join(30);
+
+ # Give child up to 5 seconds to terminate after producing output.
+ if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
+ oChild.wait(5); # pylint: disable=too-many-function-args
+ else:
+ for _ in range(50):
+ iStatus = oChild.poll();
+ if iStatus is None:
+ break;
+ time.sleep(0.1);
+ 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 = b''.join(asBuf).decode('utf-8');
+ 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'); # No 'with' support in 2.6 class: pylint: disable=consider-using-with
+ 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..3e10fbbb
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/win/autoexec-testbox.cmd
@@ -0,0 +1,72 @@
+@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-2023 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+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), a copy of it is provided in the "COPYING.CDDL" file included
+REM in the VirtualBox 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
+REM SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+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 exist %SystemRoot%\System32\aim_ll.exe (
+ set RAMEXE=aim
+) else if exist %SystemRoot%\System32\imdisk.exe (
+ set RAMEXE=imdisk
+) else goto defaulttest
+
+REM Take presence of imdisk.exe or aim_ll.exe as order to test in ramdisk.
+set RAMDRIVE=D:
+if exist %RAMDRIVE%\TEMP goto skip
+if %RAMEXE% == aim (
+ aim_ll -a -t vm -s 16G -m %RAMDRIVE% -p "/fs:ntfs /q /y"
+) else if %RAMEXE% == imdisk (
+ imdisk -a -s 16GB -m %RAMDRIVE% -p "/fs:ntfs /q /y" -o "awe"
+) else goto defaulttest
+: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..4800c99d
--- /dev/null
+++ b/src/VBox/ValidationKit/testboxscript/win/fix_stale_refs.py
@@ -0,0 +1,160 @@
+# -*- 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-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox 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.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+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 as oXcpt:
+ if oXcpt.winerror != 2:
+ raise
+ value = '<unknown>'
+ CloseKey(hkey_product_properties)
+ products[product_guid] = value
+ product_index += 1
+ except WindowsError as oXcpt:
+ if oXcpt.winerror != 259:
+ print(oXcpt.strerror + '.', 'error', oXcpt.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.
+