diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Frontends/VBoxAutostart | |
parent | Initial commit. (diff) | |
download | virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.tar.xz virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Frontends/VBoxAutostart')
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/Makefile.kmk | 44 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp | 562 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostart-win.cpp | 1265 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostart.h | 323 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostartCfg.cpp | 748 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostartStart.cpp | 176 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostartStop.cpp | 234 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostartUtils.cpp | 296 |
8 files changed, 3648 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxAutostart/Makefile.kmk b/src/VBox/Frontends/VBoxAutostart/Makefile.kmk new file mode 100644 index 00000000..310f42de --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/Makefile.kmk @@ -0,0 +1,44 @@ +# $Id: Makefile.kmk $ +## @file +# VBoxAutostart - VM autostart service. +# + +# +# Copyright (C) 2012-2020 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +ifeq ($(KBUILD_TARGET),win) + PROGRAMS += VBoxAutostartSvc + VBoxAutostartSvc_TEMPLATE = VBOXMAINCLIENTEXE + VBoxAutostartSvc_INCS = ../Common + VBoxAutostartSvc_SOURCES = \ + VBoxAutostartCfg.cpp \ + VBoxAutostartStart.cpp \ + VBoxAutostartStop.cpp \ + VBoxAutostartUtils.cpp \ + VBoxAutostart-win.cpp \ + ../Common/PasswordInput.cpp + VBoxAutostartSvc_LIBS.win += Secur32.lib +else + PROGRAMS += VBoxAutostart + VBoxAutostart_TEMPLATE = VBOXMAINCLIENTEXE + VBoxAutostart_SOURCES = \ + VBoxAutostartCfg.cpp \ + VBoxAutostartStart.cpp \ + VBoxAutostartStop.cpp \ + VBoxAutostartUtils.cpp \ + VBoxAutostart-posix.cpp +endif + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp new file mode 100644 index 00000000..60f7b39f --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp @@ -0,0 +1,562 @@ +/* $Id: VBoxAutostart-posix.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/listeners.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/errcore.h> +#include <VBox/log.h> +#include <VBox/version.h> + +#include <package-generated.h> + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/critsect.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> + +#include <algorithm> +#include <list> +#include <string> +#include <signal.h> + +#include "VBoxAutostart.h" + +using namespace com; + +#if defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) || defined(RT_OS_DARWIN) +# define VBOXAUTOSTART_DAEMONIZE +#endif + +ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL; +bool g_fVerbose = false; +ComPtr<IVirtualBox> g_pVirtualBox = NULL; +ComPtr<ISession> g_pSession = NULL; + +/** Logging parameters. */ +static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */ +static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */ +static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */ + +/** Run in background. */ +static bool g_fDaemonize = false; + +/** + * Command line arguments. + */ +static const RTGETOPTDEF g_aOptions[] = { +#ifdef VBOXAUTOSTART_DAEMONIZE + { "--background", 'b', RTGETOPT_REQ_NOTHING }, +#endif + /** For displayHelp(). */ + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--start", 's', RTGETOPT_REQ_NOTHING }, + { "--stop", 'd', RTGETOPT_REQ_NOTHING }, + { "--config", 'c', RTGETOPT_REQ_STRING }, + { "--logfile", 'F', RTGETOPT_REQ_STRING }, + { "--logrotate", 'R', RTGETOPT_REQ_UINT32 }, + { "--logsize", 'S', RTGETOPT_REQ_UINT64 }, + { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }, + { "--quiet", 'Q', RTGETOPT_REQ_NOTHING } +}; + +/** Set by the signal handler. */ +static volatile bool g_fCanceled = false; + + +/** + * Signal handler that sets g_fCanceled. + * + * This can be executed on any thread in the process, on Windows it may even be + * a thread dedicated to delivering this signal. Do not doing anything + * unnecessary here. + */ +static void showProgressSignalHandler(int iSignal) +{ + NOREF(iSignal); + ASMAtomicWriteBool(&g_fCanceled, true); +} + +/** + * Print out progress on the console. + * + * This runs the main event queue every now and then to prevent piling up + * unhandled things (which doesn't cause real problems, just makes things + * react a little slower than in the ideal case). + */ +DECLHIDDEN(HRESULT) showProgress(ComPtr<IProgress> progress) +{ + using namespace com; + + BOOL fCompleted = FALSE; + ULONG ulCurrentPercent = 0; + ULONG ulLastPercent = 0; + + Bstr bstrOperationDescription; + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + + ULONG cOperations = 1; + HRESULT hrc = progress->COMGETTER(OperationCount)(&cOperations); + if (FAILED(hrc)) + { + RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc); + RTStrmFlush(g_pStdErr); + return hrc; + } + + /* + * Note: Outputting the progress info to stderr (g_pStdErr) is intentional + * to not get intermixed with other (raw) stdout data which might get + * written in the meanwhile. + */ + RTStrmPrintf(g_pStdErr, "0%%..."); + RTStrmFlush(g_pStdErr); + + /* setup signal handling if cancelable */ + bool fCanceledAlready = false; + BOOL fCancelable; + hrc = progress->COMGETTER(Cancelable)(&fCancelable); + if (FAILED(hrc)) + fCancelable = FALSE; + if (fCancelable) + { + signal(SIGINT, showProgressSignalHandler); +#ifdef SIGBREAK + signal(SIGBREAK, showProgressSignalHandler); +#endif + } + + hrc = progress->COMGETTER(Completed(&fCompleted)); + while (SUCCEEDED(hrc)) + { + progress->COMGETTER(Percent(&ulCurrentPercent)); + + /* did we cross a 10% mark? */ + if (ulCurrentPercent / 10 > ulLastPercent / 10) + { + /* make sure to also print out missed steps */ + for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10) + { + if (curVal < 100) + { + RTStrmPrintf(g_pStdErr, "%u%%...", curVal); + RTStrmFlush(g_pStdErr); + } + } + ulLastPercent = (ulCurrentPercent / 10) * 10; + } + + if (fCompleted) + break; + + /* process async cancelation */ + if (g_fCanceled && !fCanceledAlready) + { + hrc = progress->Cancel(); + if (SUCCEEDED(hrc)) + fCanceledAlready = true; + else + g_fCanceled = false; + } + + /* make sure the loop is not too tight */ + progress->WaitForCompletion(100); + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + hrc = progress->COMGETTER(Completed(&fCompleted)); + } + + /* undo signal handling */ + if (fCancelable) + { + signal(SIGINT, SIG_DFL); +#ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +#endif + } + + /* complete the line. */ + LONG iRc = E_FAIL; + hrc = progress->COMGETTER(ResultCode)(&iRc); + if (SUCCEEDED(hrc)) + { + if (SUCCEEDED(iRc)) + RTStrmPrintf(g_pStdErr, "100%%\n"); + else if (g_fCanceled) + RTStrmPrintf(g_pStdErr, "CANCELED\n"); + else + { + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Progress state: %Rhrc\n", iRc); + } + hrc = iRc; + } + else + { + RTStrmPrintf(g_pStdErr, "\n"); + RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc); + } + RTStrmFlush(g_pStdErr); + return hrc; +} + +DECLHIDDEN(void) autostartSvcOsLogStr(const char *pszMsg, AUTOSTARTLOGTYPE enmLogType) +{ + if ( enmLogType == AUTOSTARTLOGTYPE_VERBOSE + && !g_fVerbose) + return; + + LogRel(("%s", pszMsg)); +} + +static void displayHeader() +{ + RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " Autostart " VBOX_VERSION_STRING "\n" + "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" + "All rights reserved.\n\n"); +} + +/** + * Displays the help. + * + * @param pszImage Name of program name (image). + */ +static void displayHelp(const char *pszImage) +{ + AssertPtrReturnVoid(pszImage); + + displayHeader(); + + RTStrmPrintf(g_pStdErr, + "Usage:\n" + " %s [-v|--verbose] [-h|-?|--help]\n" + " [-F|--logfile=<file>] [-R|--logrotate=<num>] [-S|--logsize=<bytes>]\n" + " [-I|--loginterval=<seconds>]\n" + " [-c|--config=<config file>]\n", pszImage); + + RTStrmPrintf(g_pStdErr, "\n" + "Options:\n"); + + for (unsigned i = 0; + i < RT_ELEMENTS(g_aOptions); + ++i) + { + std::string str(g_aOptions[i].pszLong); + if (g_aOptions[i].iShort < 1000) /* Don't show short options which are defined by an ID! */ + { + str += ", -"; + str += g_aOptions[i].iShort; + } + str += ":"; + + const char *pcszDescr = ""; + + switch (g_aOptions[i].iShort) + { + case 'h': + pcszDescr = "Print this help message and exit."; + break; + +#ifdef VBOXAUTOSTART_DAEMONIZE + case 'b': + pcszDescr = "Run in background (daemon mode)."; + break; +#endif + + case 'F': + pcszDescr = "Name of file to write log to (no file)."; + break; + + case 'R': + pcszDescr = "Number of log files (0 disables log rotation)."; + break; + + case 'S': + pcszDescr = "Maximum size of a log file to trigger rotation (bytes)."; + break; + + case 'I': + pcszDescr = "Maximum time interval to trigger log rotation (seconds)."; + break; + + case 'c': + pcszDescr = "Name of the configuration file for the global overrides."; + break; + } + + RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr); + } + + RTStrmPrintf(g_pStdErr, "\nUse environment variable VBOXAUTOSTART_RELEASE_LOG for logging options.\n"); +} + +int main(int argc, char *argv[]) +{ + /* + * Before we do anything, init the runtime without loading + * the support driver. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Parse the global options + */ + int c; + const char *pszLogFile = NULL; + const char *pszConfigFile = NULL; + bool fQuiet = false; + bool fStart = false; + bool fStop = false; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, + g_aOptions, RT_ELEMENTS(g_aOptions), 1 /* First */, 0 /*fFlags*/); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'h': + displayHelp(argv[0]); + return 0; + + case 'v': + g_fVerbose = true; + break; + +#ifdef VBOXAUTOSTART_DAEMONIZE + case 'b': + g_fDaemonize = true; + break; +#endif + case 'V': + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return 0; + + case 'F': + pszLogFile = ValueUnion.psz; + break; + + case 'R': + g_cHistory = ValueUnion.u32; + break; + + case 'S': + g_uHistoryFileSize = ValueUnion.u64; + break; + + case 'I': + g_uHistoryFileTime = ValueUnion.u32; + break; + + case 'Q': + fQuiet = true; + break; + + case 'c': + pszConfigFile = ValueUnion.psz; + break; + + case 's': + fStart = true; + break; + + case 'd': + fStop = true; + break; + + default: + return RTGetOptPrintError(c, &ValueUnion); + } + } + + if (!fStart && !fStop) + { + displayHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Either --start or --stop must be present"); + } + else if (fStart && fStop) + { + displayHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "--start or --stop are mutually exclusive"); + } + + if (!pszConfigFile) + { + displayHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "--config <config file> is missing"); + } + + if (!fQuiet) + displayHeader(); + + PCFGAST pCfgAst = NULL; + char *pszUser = NULL; + PCFGAST pCfgAstUser = NULL; + PCFGAST pCfgAstPolicy = NULL; + bool fAllow = false; + + rc = autostartParseConfig(pszConfigFile, &pCfgAst); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + rc = RTProcQueryUsernameA(RTProcSelf(), &pszUser); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to query username of the process"); + + pCfgAstUser = autostartConfigAstGetByName(pCfgAst, pszUser); + pCfgAstPolicy = autostartConfigAstGetByName(pCfgAst, "default_policy"); + + /* Check default policy. */ + if (pCfgAstPolicy) + { + if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE + && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow") + || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "deny"))) + { + if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow")) + fAllow = true; + } + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "'default_policy' must be either 'allow' or 'deny'"); + } + + if ( pCfgAstUser + && pCfgAstUser->enmType == CFGASTNODETYPE_COMPOUND) + { + pCfgAstPolicy = autostartConfigAstGetByName(pCfgAstUser, "allow"); + if (pCfgAstPolicy) + { + if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE + && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true") + || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "false"))) + { + if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true")) + fAllow = true; + else + fAllow = false; + } + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "'allow' must be either 'true' or 'false'"); + } + } + else if (pCfgAstUser) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid config, user is not a compound node"); + + if (!fAllow) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "User is not allowed to autostart VMs"); + + RTStrFree(pszUser); + + /* Don't start if the VirtualBox settings directory does not exist. */ + char szUserHomeDir[RTPATH_MAX]; + rc = com::GetVBoxUserHomeDirectory(szUserHomeDir, sizeof(szUserHomeDir), false /* fCreateDir */); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory: %Rrc", rc); + else if (!RTDirExists(szUserHomeDir)) + return RTEXITCODE_SUCCESS; + + /* create release logger, to stdout */ + RTERRINFOSTATIC ErrInfo; + rc = com::VBoxLogRelCreate("Autostart", g_fDaemonize ? NULL : pszLogFile, + RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG, + "all", "VBOXAUTOSTART_RELEASE_LOG", + RTLOGDEST_STDOUT, UINT32_MAX /* cMaxEntriesPerGroup */, + g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc); + +#ifdef VBOXAUTOSTART_DAEMONIZE + if (g_fDaemonize) + { + /* prepare release logging */ + char szLogFile[RTPATH_MAX]; + + if (!pszLogFile || !*pszLogFile) + { + rc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", rc); + rc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxautostart.log"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", rc); + pszLogFile = szLogFile; + } + + rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc); + /* create release logger, to file */ + rc = com::VBoxLogRelCreate("Autostart", pszLogFile, + RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG, + "all", "VBOXAUTOSTART_RELEASE_LOG", + RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */, + g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc); + } +#endif + + /* Set up COM */ + rc = autostartSetup(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + RTEXITCODE rcExit; + if (fStart) + rcExit = autostartStartMain(pCfgAstUser); + else + { + Assert(fStop); + rcExit = autostartStopMain(pCfgAstUser); + } + + autostartConfigAstDestroy(pCfgAst); + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + + autostartShutdown(); + return rcExit; +} + diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-win.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-win.cpp new file mode 100644 index 00000000..10c50a6f --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-win.cpp @@ -0,0 +1,1265 @@ +/* $Id: VBoxAutostart-win.cpp $ */ +/** @file + * VirtualBox Autostart Service - Windows Specific Code. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <tchar.h> + +#define SECURITY_WIN32 +#include <Security.h> + +#include <VBox/com/array.h> +#include <VBox/com/com.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> +#include <VBox/com/Guid.h> +#include <VBox/com/listeners.h> +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/string.h> +#include <VBox/com/VirtualBox.h> + +#include <VBox/log.h> +#include <VBox/version.h> + +#include <iprt/env.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "VBoxAutostart.h" +#include "PasswordInput.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The service name. */ +#define AUTOSTART_SERVICE_NAME "VBoxAutostartSvc" +/** The service display name. */ +#define AUTOSTART_SERVICE_DISPLAY_NAME "VirtualBox Autostart Service" + +ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL; +bool g_fVerbose = false; +ComPtr<IVirtualBox> g_pVirtualBox = NULL; +ComPtr<ISession> g_pSession = NULL; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The service control handler handle. */ +static SERVICE_STATUS_HANDLE g_hSupSvcWinCtrlHandler = NULL; +/** The service status. */ +static uint32_t volatile g_u32SupSvcWinStatus = SERVICE_STOPPED; +/** The semaphore the main service thread is waiting on in autostartSvcWinServiceMain. */ +static RTSEMEVENTMULTI g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; +/** The service name is used for send to service main. */ +static com::Bstr g_bstrServiceName; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static SC_HANDLE autostartSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess); + +static int autostartGetProcessDomainUser(com::Utf8Str &aUser) +{ + int rc = VERR_NOT_SUPPORTED; + + RTUTF16 wszUsername[1024] = { 0 }; + ULONG cwcUsername = RT_ELEMENTS(wszUsername); + char *pszUser = NULL; + if (!GetUserNameExW(NameSamCompatible, &wszUsername[0], &cwcUsername)) + return RTErrConvertFromWin32(GetLastError()); + rc = RTUtf16ToUtf8(wszUsername, &pszUser); + aUser = pszUser; + aUser.toLower(); + RTStrFree(pszUser); + return rc; +} + +static int autostartGetLocalDomain(com::Utf8Str &aDomain) +{ + RTUTF16 pwszDomain[MAX_COMPUTERNAME_LENGTH + 1] = { 0 }; + uint32_t cwcDomainSize = MAX_COMPUTERNAME_LENGTH + 1; + if (!GetComputerNameW(pwszDomain, (LPDWORD)&cwcDomainSize)) + return RTErrConvertFromWin32(GetLastError()); + char *pszDomain = NULL; + int rc = RTUtf16ToUtf8(pwszDomain, &pszDomain); + aDomain = pszDomain; + aDomain.toLower(); + RTStrFree(pszDomain); + return rc; +} + +static int autostartGetDomainAndUser(const com::Utf8Str &aDomainAndUser, com::Utf8Str &aDomain, com::Utf8Str &aUser) +{ + size_t offDelim = aDomainAndUser.find("\\"); + if (offDelim != aDomainAndUser.npos) + { + // if only domain is specified + if (aDomainAndUser.length() - offDelim == 1) + return VERR_INVALID_PARAMETER; + + if (offDelim == 1 && aDomainAndUser[0] == '.') + { + int rc = autostartGetLocalDomain(aDomain); + aUser = aDomainAndUser.substr(offDelim + 1); + return rc; + } + aDomain = aDomainAndUser.substr(0, offDelim); + aUser = aDomainAndUser.substr(offDelim + 1); + aDomain.toLower(); + aUser.toLower(); + return VINF_SUCCESS; + } + + offDelim = aDomainAndUser.find("@"); + if (offDelim != aDomainAndUser.npos) + { + // if only domain is specified + if (offDelim == 0) + return VERR_INVALID_PARAMETER; + + // with '@' but without domain + if (aDomainAndUser.length() - offDelim == 1) + { + int rc = autostartGetLocalDomain(aDomain); + aUser = aDomainAndUser.substr(0, offDelim); + return rc; + } + aDomain = aDomainAndUser.substr(offDelim + 1); + aUser = aDomainAndUser.substr(0, offDelim); + aDomain.toLower(); + aUser.toLower(); + return VINF_SUCCESS; + } + + // only user is specified + int rc = autostartGetLocalDomain(aDomain); + aUser = aDomainAndUser; + aDomain.toLower(); + aUser.toLower(); + return rc; +} + +/** Common helper for formatting the service name. */ +static void autostartFormatServiceName(const com::Utf8Str &aDomain, const com::Utf8Str &aUser, com::Utf8Str &aServiceName) +{ + aServiceName.printf("%s%s%s", AUTOSTART_SERVICE_NAME, aDomain.c_str(), aUser.c_str()); +} + +/** Used by the delete service operation. */ +static int autostartGetServiceName(const com::Utf8Str &aDomainAndUser, com::Utf8Str &aServiceName) +{ + com::Utf8Str sDomain; + com::Utf8Str sUser; + int rc = autostartGetDomainAndUser(aDomainAndUser, sDomain, sUser); + if (RT_FAILURE(rc)) + return rc; + autostartFormatServiceName(sDomain, sUser, aServiceName); + return VINF_SUCCESS; +} + +/** + * Print out progress on the console. + * + * This runs the main event queue every now and then to prevent piling up + * unhandled things (which doesn't cause real problems, just makes things + * react a little slower than in the ideal case). + */ +DECLHIDDEN(HRESULT) showProgress(ComPtr<IProgress> progress) +{ + using namespace com; + + BOOL fCompleted = FALSE; + ULONG uCurrentPercent = 0; + Bstr bstrOperationDescription; + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + + ULONG cOperations = 1; + HRESULT hrc = progress->COMGETTER(OperationCount)(&cOperations); + if (FAILED(hrc)) + return hrc; + + /* setup signal handling if cancelable */ + bool fCanceledAlready = false; + BOOL fCancelable; + hrc = progress->COMGETTER(Cancelable)(&fCancelable); + if (FAILED(hrc)) + fCancelable = FALSE; + + hrc = progress->COMGETTER(Completed(&fCompleted)); + while (SUCCEEDED(hrc)) + { + progress->COMGETTER(Percent(&uCurrentPercent)); + + if (fCompleted) + break; + + /* process async cancelation */ + if (!fCanceledAlready) + { + hrc = progress->Cancel(); + if (SUCCEEDED(hrc)) + fCanceledAlready = true; + } + + /* make sure the loop is not too tight */ + progress->WaitForCompletion(100); + + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + hrc = progress->COMGETTER(Completed(&fCompleted)); + } + + /* complete the line. */ + LONG iRc = E_FAIL; + hrc = progress->COMGETTER(ResultCode)(&iRc); + if (SUCCEEDED(hrc)) + { + hrc = iRc; + } + + return hrc; +} + +DECLHIDDEN(void) autostartSvcOsLogStr(const char *pszMsg, AUTOSTARTLOGTYPE enmLogType) +{ + HANDLE hEventLog = RegisterEventSourceA(NULL /* local computer */, "VBoxAutostartSvc"); + AssertReturnVoid(hEventLog != NULL); + WORD wType = 0; + const char *apsz[2]; + apsz[0] = "VBoxAutostartSvc"; + apsz[1] = pszMsg; + + switch (enmLogType) + { + case AUTOSTARTLOGTYPE_INFO: + wType = 0; + break; + case AUTOSTARTLOGTYPE_ERROR: + wType = EVENTLOG_ERROR_TYPE; + break; + case AUTOSTARTLOGTYPE_WARNING: + wType = EVENTLOG_WARNING_TYPE; + break; + case AUTOSTARTLOGTYPE_VERBOSE: + if (!g_fVerbose) + return; + wType = EVENTLOG_INFORMATION_TYPE; + break; + default: + AssertMsgFailed(("Invalid log type %d\n", enmLogType)); + } + + BOOL fRc = ReportEventA(hEventLog, /* hEventLog */ + wType, /* wType */ + 0, /* wCategory */ + 0 /** @todo mc */, /* dwEventID */ + NULL, /* lpUserSid */ + RT_ELEMENTS(apsz), /* wNumStrings */ + 0, /* dwDataSize */ + apsz, /* lpStrings */ + NULL); /* lpRawData */ + AssertMsg(fRc, ("%u\n", GetLastError())); NOREF(fRc); + DeregisterEventSource(hEventLog); +} + +/** + * Opens the service control manager. + * + * When this fails, an error message will be displayed. + * + * @returns Valid handle on success. + * NULL on failure, will display an error message. + * + * @param pszAction The action which is requesting access to SCM. + * @param dwAccess The desired access. + */ +static SC_HANDLE autostartSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess) +{ + SC_HANDLE hSCM = OpenSCManager(NULL /* lpMachineName*/, NULL /* lpDatabaseName */, dwAccess); + if (hSCM == NULL) + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_ACCESS_DENIED: + autostartSvcDisplayError("%s - OpenSCManager failure: access denied\n", pszAction); + break; + default: + autostartSvcDisplayError("%s - OpenSCManager failure: %d\n", pszAction, err); + break; + } + } + return hSCM; +} + + +/** + * Opens the service. + * + * Last error is preserved on failure and set to 0 on success. + * + * @returns Valid service handle on success. + * NULL on failure, will display an error message unless it's ignored. + * + * @param pszAction The action which is requesting access to the service. + * @param dwSCMAccess The service control manager access. + * @param dwSVCAccess The desired service access. + * @param cIgnoredErrors The number of ignored errors. + * @param ... Errors codes that should not cause a message to be displayed. + */ +static SC_HANDLE autostartSvcWinOpenService(const PRTUTF16 pwszServiceName, const char *pszAction, DWORD dwSCMAccess, DWORD dwSVCAccess, + unsigned cIgnoredErrors, ...) +{ + SC_HANDLE hSCM = autostartSvcWinOpenSCManager(pszAction, dwSCMAccess); + if (!hSCM) + return NULL; + + SC_HANDLE hSvc = OpenServiceW(hSCM, pwszServiceName, dwSVCAccess); + if (hSvc) + { + CloseServiceHandle(hSCM); + SetLastError(0); + } + else + { + DWORD err = GetLastError(); + bool fIgnored = false; + va_list va; + va_start(va, cIgnoredErrors); + while (!fIgnored && cIgnoredErrors-- > 0) + fIgnored = (DWORD)va_arg(va, int) == err; + va_end(va); + if (!fIgnored) + { + switch (err) + { + case ERROR_ACCESS_DENIED: + autostartSvcDisplayError("%s - OpenService failure: access denied\n", pszAction); + break; + case ERROR_SERVICE_DOES_NOT_EXIST: + autostartSvcDisplayError("%s - OpenService failure: The service %ls does not exist. Reinstall it.\n", + pszAction, pwszServiceName); + break; + default: + autostartSvcDisplayError("%s - OpenService failure: %d\n", pszAction, err); + break; + } + } + + CloseServiceHandle(hSCM); + SetLastError(err); + } + return hSvc; +} + +static RTEXITCODE autostartSvcWinInterrogate(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"interrogate\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinStop(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"stop\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinContinue(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"continue\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinPause(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"pause\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinStart(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"start\" action is not implemented.\n"); + return RTEXITCODE_SUCCESS; +} + + +static RTEXITCODE autostartSvcWinQueryDescription(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"qdescription\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinQueryConfig(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"qconfig\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +static RTEXITCODE autostartSvcWinDisable(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"disable\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + +static RTEXITCODE autostartSvcWinEnable(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTPrintf("VBoxAutostartSvc: The \"enable\" action is not implemented.\n"); + return RTEXITCODE_FAILURE; +} + + +/** + * Handle the 'delete' action. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int autostartSvcWinDelete(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + const char *pszUser = NULL; + static const RTGETOPTDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--user", 'u', RTGETOPT_REQ_STRING }, + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + { + switch (ch) + { + case 'v': + fVerbose = true; + break; + case 'u': + pszUser = Value.psz; + break; + default: + return autostartSvcDisplayGetOptError("delete", ch, &Value); + } + } + + if (!pszUser) + return autostartSvcDisplayError("delete - DeleteService failed, user name required.\n"); + + com::Utf8Str sServiceName; + int vrc = autostartGetServiceName(pszUser, sServiceName); + if (RT_FAILURE(vrc)) + return autostartSvcDisplayError("delete - DeleteService failed, service name for user %s can not be constructed.\n", + pszUser); + /* + * Create the service. + */ + RTEXITCODE rc = RTEXITCODE_FAILURE; + SC_HANDLE hSvc = autostartSvcWinOpenService(com::Bstr(sServiceName).raw(), "delete", SERVICE_CHANGE_CONFIG, DELETE, + 1, ERROR_SERVICE_DOES_NOT_EXIST); + if (hSvc) + { + if (DeleteService(hSvc)) + { + RTPrintf("Successfully deleted the %s service.\n", sServiceName.c_str()); + rc = RTEXITCODE_SUCCESS; + } + else + autostartSvcDisplayError("delete - DeleteService failed, err=%d.\n", GetLastError()); + CloseServiceHandle(hSvc); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + + if (fVerbose) + RTPrintf("The service %s was not installed, nothing to be done.", sServiceName.c_str()); + else + RTPrintf("Successfully deleted the %s service.\n", sServiceName.c_str()); + rc = RTEXITCODE_SUCCESS; + } + return rc; +} + + +/** + * Handle the 'create' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static RTEXITCODE autostartSvcWinCreate(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + const char *pszUser = NULL; + com::Utf8Str strPwd; + const char *pszPwdFile = NULL; + static const RTGETOPTDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--user", 'u', RTGETOPT_REQ_STRING }, + { "--password-file", 'p', RTGETOPT_REQ_STRING } + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + { + switch (ch) + { + case 'v': + fVerbose = true; + break; + case 'u': + pszUser = Value.psz; + break; + case 'p': + pszPwdFile = Value.psz; + break; + default: + return autostartSvcDisplayGetOptError("create", ch, &Value); + } + } + + if (!pszUser) + return autostartSvcDisplayError("Username is missing"); + + if (pszPwdFile) + { + /* Get password from file. */ + RTEXITCODE rcExit = readPasswordFile(pszPwdFile, &strPwd); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + else + { + /* Get password from console. */ + RTEXITCODE rcExit = readPasswordFromConsole(&strPwd, "Enter password:"); + if (rcExit == RTEXITCODE_FAILURE) + return rcExit; + } + + if (strPwd.isEmpty()) + return autostartSvcDisplayError("Password is missing"); + + com::Utf8Str sDomain; + com::Utf8Str sUserTmp; + int vrc = autostartGetDomainAndUser(pszUser, sDomain, sUserTmp); + if (RT_FAILURE(vrc)) + return autostartSvcDisplayError("create - CreateService failed, failed to get domain and user from string %s (%d).\n", + pszUser, vrc); + com::Utf8StrFmt sUserFullName("%s\\%s", sDomain.c_str(), sUserTmp.c_str()); + com::Utf8StrFmt sDisplayName("%s %s@%s", AUTOSTART_SERVICE_DISPLAY_NAME, sUserTmp.c_str(), sDomain.c_str()); + com::Utf8Str sServiceName; + autostartFormatServiceName(sDomain, sUserTmp, sServiceName); + + /* + * Create the service. + */ + RTEXITCODE rc = RTEXITCODE_FAILURE; + SC_HANDLE hSCM = autostartSvcWinOpenSCManager("create", SC_MANAGER_CREATE_SERVICE); /*SC_MANAGER_ALL_ACCESS*/ + if (hSCM) + { + char szExecPath[RTPATH_MAX]; + if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath))) + { + if (fVerbose) + RTPrintf("Creating the %s service, binary \"%s\"...\n", + sServiceName.c_str(), szExecPath); /* yea, the binary name isn't UTF-8, but wtf. */ + + /* + * Add service name as command line parameter for the service + */ + com::Utf8StrFmt sCmdLine("\"%s\" --service=%s", szExecPath, sServiceName.c_str()); + com::Bstr bstrServiceName(sServiceName); + com::Bstr bstrDisplayName(sDisplayName); + com::Bstr bstrCmdLine(sCmdLine); + com::Bstr bstrUserFullName(sUserFullName); + com::Bstr bstrPwd(strPwd); + + SC_HANDLE hSvc = CreateServiceW(hSCM, /* hSCManager */ + bstrServiceName.raw(), /* lpServiceName */ + bstrDisplayName.raw(), /* lpDisplayName */ + SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG, /* dwDesiredAccess */ + SERVICE_WIN32_OWN_PROCESS, /* dwServiceType ( | SERVICE_INTERACTIVE_PROCESS? ) */ + SERVICE_AUTO_START, /* dwStartType */ + SERVICE_ERROR_NORMAL, /* dwErrorControl */ + bstrCmdLine.raw(), /* lpBinaryPathName */ + NULL, /* lpLoadOrderGroup */ + NULL, /* lpdwTagId */ + NULL, /* lpDependencies */ + bstrUserFullName.raw(), /* lpServiceStartName (NULL => LocalSystem) */ + bstrPwd.raw()); /* lpPassword */ + if (hSvc) + { + RTPrintf("Successfully created the %s service.\n", sServiceName.c_str()); + /** @todo Set the service description or it'll look weird in the vista service manager. + * Anything else that should be configured? Start access or something? */ + rc = RTEXITCODE_SUCCESS; + CloseServiceHandle(hSvc); + } + else + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_SERVICE_EXISTS: + autostartSvcDisplayError("create - The service already exists.\n"); + break; + default: + autostartSvcDisplayError("create - CreateService failed, err=%d.\n", GetLastError()); + break; + } + } + CloseServiceHandle(hSvc); + } + else + autostartSvcDisplayError("create - Failed to obtain the executable path: %d\n", GetLastError()); + } + return rc; +} + + +/** + * Sets the service status, just a SetServiceStatus Wrapper. + * + * @returns See SetServiceStatus. + * @param dwStatus The current status. + * @param iWaitHint The wait hint, if < 0 then supply a default. + * @param dwExitCode The service exit code. + */ +static bool autostartSvcWinSetServiceStatus(DWORD dwStatus, int iWaitHint, DWORD dwExitCode) +{ + SERVICE_STATUS SvcStatus; + SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + SvcStatus.dwWin32ExitCode = dwExitCode; + SvcStatus.dwServiceSpecificExitCode = 0; + SvcStatus.dwWaitHint = iWaitHint >= 0 ? iWaitHint : 3000; + SvcStatus.dwCurrentState = dwStatus; + LogFlow(("autostartSvcWinSetServiceStatus: %d -> %d\n", g_u32SupSvcWinStatus, dwStatus)); + g_u32SupSvcWinStatus = dwStatus; + switch (dwStatus) + { + case SERVICE_START_PENDING: + SvcStatus.dwControlsAccepted = 0; + break; + default: + SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + break; + } + + static DWORD dwCheckPoint = 0; + switch (dwStatus) + { + case SERVICE_RUNNING: + case SERVICE_STOPPED: + SvcStatus.dwCheckPoint = 0; + default: + SvcStatus.dwCheckPoint = ++dwCheckPoint; + break; + } + return SetServiceStatus(g_hSupSvcWinCtrlHandler, &SvcStatus) != FALSE; +} + + +/** + * Service control handler (extended). + * + * @returns Windows status (see HandlerEx). + * @retval NO_ERROR if handled. + * @retval ERROR_CALL_NOT_IMPLEMENTED if not handled. + * + * @param dwControl The control code. + * @param dwEventType Event type. (specific to the control?) + * @param pvEventData Event data, specific to the event. + * @param pvContext The context pointer registered with the handler. + * Currently not used. + */ +static DWORD WINAPI autostartSvcWinServiceCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID pvEventData, LPVOID pvContext) +{ + LogFlow(("autostartSvcWinServiceCtrlHandlerEx: dwControl=%#x dwEventType=%#x pvEventData=%p\n", + dwControl, dwEventType, pvEventData)); + + switch (dwControl) + { + /* + * Interrogate the service about it's current status. + * MSDN says that this should just return NO_ERROR and does + * not need to set the status again. + */ + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + /* + * Request to stop the service. + */ + case SERVICE_CONTROL_STOP: + { + /* + * Check if the real services can be stopped and then tell them to stop. + */ + autostartSvcWinSetServiceStatus(SERVICE_STOP_PENDING, 3000, NO_ERROR); + /* + * Notify the main thread that we're done, it will wait for the + * VMs to stop, and set the windows service status to SERVICE_STOPPED + * and return. + */ + int rc = RTSemEventMultiSignal(g_hSupSvcWinEvent); + if (RT_FAILURE(rc)) + autostartSvcLogError("SERVICE_CONTROL_STOP: RTSemEventMultiSignal failed, %Rrc\n", rc); + + return NO_ERROR; + } + + case SERVICE_CONTROL_PAUSE: + case SERVICE_CONTROL_CONTINUE: + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_PARAMCHANGE: + case SERVICE_CONTROL_NETBINDADD: + case SERVICE_CONTROL_NETBINDREMOVE: + case SERVICE_CONTROL_NETBINDENABLE: + case SERVICE_CONTROL_NETBINDDISABLE: + case SERVICE_CONTROL_DEVICEEVENT: + case SERVICE_CONTROL_HARDWAREPROFILECHANGE: + case SERVICE_CONTROL_POWEREVENT: + case SERVICE_CONTROL_SESSIONCHANGE: +#ifdef SERVICE_CONTROL_PRESHUTDOWN /* vista */ + case SERVICE_CONTROL_PRESHUTDOWN: +#endif + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } + + NOREF(dwEventType); + NOREF(pvEventData); + NOREF(pvContext); + /* not reached */ +} + +static RTEXITCODE autostartStartVMs() +{ + int rc = autostartSetup(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + const char *pszConfigFile = RTEnvGet("VBOXAUTOSTART_CONFIG"); + if (!pszConfigFile) + return autostartSvcLogError("Starting VMs failed. VBOXAUTOSTART_CONFIG environment variable is not defined.\n"); + bool fAllow = false; + + PCFGAST pCfgAst = NULL; + rc = autostartParseConfig(pszConfigFile, &pCfgAst); + if (RT_FAILURE(rc)) + return autostartSvcLogError("Starting VMs failed. Failed to parse the config file. Check the access permissions and file structure.\n"); + + PCFGAST pCfgAstPolicy = autostartConfigAstGetByName(pCfgAst, "default_policy"); + /* Check default policy. */ + if (pCfgAstPolicy) + { + if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE + && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow") + || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "deny"))) + { + if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow")) + fAllow = true; + } + else + { + autostartConfigAstDestroy(pCfgAst); + return autostartSvcLogError("'default_policy' must be either 'allow' or 'deny'.\n"); + } + } + + com::Utf8Str sUser; + rc = autostartGetProcessDomainUser(sUser); + if (RT_FAILURE(rc)) + { + autostartConfigAstDestroy(pCfgAst); + return autostartSvcLogError("Failed to query username of the process (%Rrc).\n", rc); + } + + PCFGAST pCfgAstUser = NULL; + for (unsigned i = 0; i < pCfgAst->u.Compound.cAstNodes; i++) + { + PCFGAST pNode = pCfgAst->u.Compound.apAstNodes[i]; + com::Utf8Str sDomain; + com::Utf8Str sUserTmp; + int rc = autostartGetDomainAndUser(pNode->pszKey, sDomain, sUserTmp); + if (RT_FAILURE(rc)) + continue; + com::Utf8StrFmt sDomainUser("%s\\%s", sDomain.c_str(), sUserTmp.c_str()); + if (sDomainUser == sUser) + { + pCfgAstUser = pNode; + break; + } + } + + if ( pCfgAstUser + && pCfgAstUser->enmType == CFGASTNODETYPE_COMPOUND) + { + pCfgAstPolicy = autostartConfigAstGetByName(pCfgAstUser, "allow"); + if (pCfgAstPolicy) + { + if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE + && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true") + || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "false"))) + fAllow = RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true") == 0; + else + { + autostartConfigAstDestroy(pCfgAst); + return autostartSvcLogError("'allow' must be either 'true' or 'false'.\n"); + } + } + } + else if (pCfgAstUser) + { + autostartConfigAstDestroy(pCfgAst); + return autostartSvcLogError("Invalid config, user is not a compound node.\n"); + } + + if (!fAllow) + { + autostartConfigAstDestroy(pCfgAst); + return autostartSvcLogError("User is not allowed to autostart VMs.\n"); + } + + RTEXITCODE ec = autostartStartMain(pCfgAstUser); + autostartConfigAstDestroy(pCfgAst); + if (ec != RTEXITCODE_SUCCESS) + autostartSvcLogError("Starting VMs failed\n"); + + return ec; +} + +/** + * Windows Service Main. + * + * This is invoked when the service is started and should not return until + * the service has been stopped. + * + * @param cArgs Argument count. + * @param papwszArgs Argument vector. + */ +static VOID WINAPI autostartSvcWinServiceMain(DWORD cArgs, LPWSTR *papwszArgs) +{ + RT_NOREF(papwszArgs); + LogFlowFuncEnter(); + + /* + * Register the control handler function for the service and report to SCM. + */ + Assert(g_u32SupSvcWinStatus == SERVICE_STOPPED); + g_hSupSvcWinCtrlHandler = RegisterServiceCtrlHandlerExW(g_bstrServiceName.raw(), autostartSvcWinServiceCtrlHandlerEx, NULL); + if (g_hSupSvcWinCtrlHandler) + { + DWORD err = ERROR_GEN_FAILURE; + if (autostartSvcWinSetServiceStatus(SERVICE_START_PENDING, 3000, NO_ERROR)) + { + if (cArgs == 1) + { + /* + * Create the event semaphore we'll be waiting on and + * then instantiate the actual services. + */ + int rc = RTSemEventMultiCreate(&g_hSupSvcWinEvent); + if (RT_SUCCESS(rc)) + { + /* + * Update the status and enter the work loop. + */ + if (autostartSvcWinSetServiceStatus(SERVICE_RUNNING, 0, 0)) + { + LogFlow(("autostartSvcWinServiceMain: calling autostartStartVMs\n")); + RTEXITCODE ec = autostartStartVMs(); + if (ec == RTEXITCODE_SUCCESS) + { + LogFlow(("autostartSvcWinServiceMain: done string VMs\n")); + err = NO_ERROR; + rc = RTSemEventMultiWait(g_hSupSvcWinEvent, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + LogFlow(("autostartSvcWinServiceMain: woke up\n")); + /** @todo Autostop part. */ + err = NO_ERROR; + } + else + autostartSvcLogError("RTSemEventWait failed, rc=%Rrc", rc); + } + + autostartShutdown(); + } + else + { + err = GetLastError(); + autostartSvcLogError("SetServiceStatus failed, err=%u", err); + } + + RTSemEventMultiDestroy(g_hSupSvcWinEvent); + g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; + } + else + autostartSvcLogError("RTSemEventMultiCreate failed, rc=%Rrc", rc); + } + else + autostartSvcLogTooManyArgsError("main", cArgs, NULL, 0); + } + else + { + err = GetLastError(); + autostartSvcLogError("SetServiceStatus failed, err=%u", err); + } + autostartSvcWinSetServiceStatus(SERVICE_STOPPED, 0, err); + } + else + autostartSvcLogError("RegisterServiceCtrlHandlerEx failed, err=%u", GetLastError()); + + LogFlowFuncLeave(); +} + + +/** + * Handle the 'create' action. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int autostartSvcWinRunIt(int argc, char **argv) +{ + LogFlowFuncEnter(); + + /* + * Initialize release logging. + */ + /** @todo release logging of the system-wide service. */ + + /* + * Parse the arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--service", 's', RTGETOPT_REQ_STRING }, + }; + + const char *pszServiceName = NULL; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + { + switch (ch) + { + case 's': + pszServiceName = Value.psz; + try + { + g_bstrServiceName = com::Bstr(Value.psz); + } + catch (...) + { + autostartSvcLogError("runit failed, service name is not valid utf-8 string or out of memory"); + return RTEXITCODE_FAILURE; + } + break; + default: + return autostartSvcDisplayGetOptError("runit", ch, &Value); + } + } + + if (!pszServiceName) + { + autostartSvcLogError("runit failed, service name is missing"); + return RTEXITCODE_FAILURE; + } + + /* + * Register the service with the service control manager + * and start dispatching requests from it (all done by the API). + */ + SERVICE_TABLE_ENTRYW const s_aServiceStartTable[] = + { + { g_bstrServiceName.raw(), autostartSvcWinServiceMain }, + { NULL, NULL} + }; + if (StartServiceCtrlDispatcherW(&s_aServiceStartTable[0])) + { + LogFlowFuncLeave(); + return RTEXITCODE_SUCCESS; /* told to quit, so quit. */ + } + + DWORD err = GetLastError(); + switch (err) + { + case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: + autostartSvcWinServiceMain(0, NULL);//autostartSvcDisplayError("Cannot run a service from the command line. Use the 'start' action to start it the right way.\n"); + break; + default: + autostartSvcLogError("StartServiceCtrlDispatcher failed, err=%u", err); + break; + } + return RTEXITCODE_FAILURE; +} + + +/** + * Show the version info. + * + * @returns RTEXITCODE_SUCCESS. + */ +static RTEXITCODE autostartSvcWinShowVersion(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fBrief = false; + static const RTGETOPTDEF s_aOptions[] = + { + { "--brief", 'b', RTGETOPT_REQ_NOTHING } + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'b': fBrief = true; break; + default: return autostartSvcDisplayGetOptError("version", ch, &Value); + } + + /* + * Do the printing. + */ + if (fBrief) + RTPrintf("%s\n", VBOX_VERSION_STRING); + else + RTPrintf("VirtualBox Autostart Service Version %s\n" + "(C) 2012 Oracle Corporation\n" + "All rights reserved.\n", + VBOX_VERSION_STRING); + return RTEXITCODE_SUCCESS; +} + + +/** + * Show the usage help screen. + * + * @returns RTEXITCODE_SUCCESS. + */ +static RTEXITCODE autostartSvcWinShowHelp(void) +{ + RTPrintf("VirtualBox Autostart Service Version %s\n" + "(C) 2012 Oracle Corporation\n" + "All rights reserved.\n" + "\n", + VBOX_VERSION_STRING); + RTPrintf("Usage:\n" + "\n" + "VBoxAutostartSvc\n" + " Runs the service.\n" + "VBoxAutostartSvc <version|-v|--version> [-brief]\n" + " Displays the version.\n" + "VBoxAutostartSvc <help|-?|-h|--help> [...]\n" + " Displays this help screen.\n" + "\n" + "VBoxAutostartSvc <install|/RegServer|/i>\n" + " Installs the service.\n" + "VBoxAutostartSvc <uninstall|delete|/UnregServer|/u>\n" + " Uninstalls the service.\n" + ); + return RTEXITCODE_SUCCESS; +} + + +/** + * VBoxAutostart main(), Windows edition. + * + * + * @returns 0 on success. + * + * @param argc Number of arguments in argv. + * @param argv Argument vector. + */ +int main(int argc, char **argv) +{ + /* + * Initialize the IPRT first of all. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + { + autostartSvcLogError("RTR3InitExe failed with rc=%Rrc", rc); + return RTEXITCODE_FAILURE; + } + + RTThreadSleep(10 * 1000); + + /* + * Parse the initial arguments to determine the desired action. + */ + enum + { + kAutoSvcAction_RunIt, + + kAutoSvcAction_Create, + kAutoSvcAction_Delete, + + kAutoSvcAction_Enable, + kAutoSvcAction_Disable, + kAutoSvcAction_QueryConfig, + kAutoSvcAction_QueryDescription, + + kAutoSvcAction_Start, + kAutoSvcAction_Pause, + kAutoSvcAction_Continue, + kAutoSvcAction_Stop, + kAutoSvcAction_Interrogate, + + kAutoSvcAction_End + } enmAction = kAutoSvcAction_RunIt; + int iArg = 1; + if (argc > 1) + { + if ( !stricmp(argv[iArg], "/RegServer") + || !stricmp(argv[iArg], "install") + || !stricmp(argv[iArg], "/i")) + enmAction = kAutoSvcAction_Create; + else if ( !stricmp(argv[iArg], "/UnregServer") + || !stricmp(argv[iArg], "/u") + || !stricmp(argv[iArg], "uninstall") + || !stricmp(argv[iArg], "delete")) + enmAction = kAutoSvcAction_Delete; + + else if (!stricmp(argv[iArg], "enable")) + enmAction = kAutoSvcAction_Enable; + else if (!stricmp(argv[iArg], "disable")) + enmAction = kAutoSvcAction_Disable; + else if (!stricmp(argv[iArg], "qconfig")) + enmAction = kAutoSvcAction_QueryConfig; + else if (!stricmp(argv[iArg], "qdescription")) + enmAction = kAutoSvcAction_QueryDescription; + + else if ( !stricmp(argv[iArg], "start") + || !stricmp(argv[iArg], "/t")) + enmAction = kAutoSvcAction_Start; + else if (!stricmp(argv[iArg], "pause")) + enmAction = kAutoSvcAction_Start; + else if (!stricmp(argv[iArg], "continue")) + enmAction = kAutoSvcAction_Continue; + else if (!stricmp(argv[iArg], "stop")) + enmAction = kAutoSvcAction_Stop; + else if (!stricmp(argv[iArg], "interrogate")) + enmAction = kAutoSvcAction_Interrogate; + else if ( !stricmp(argv[iArg], "help") + || !stricmp(argv[iArg], "?") + || !stricmp(argv[iArg], "/?") + || !stricmp(argv[iArg], "-?") + || !stricmp(argv[iArg], "/h") + || !stricmp(argv[iArg], "-h") + || !stricmp(argv[iArg], "/help") + || !stricmp(argv[iArg], "-help") + || !stricmp(argv[iArg], "--help")) + return autostartSvcWinShowHelp(); + else if ( !stricmp(argv[iArg], "version") + || !stricmp(argv[iArg], "/v") + || !stricmp(argv[iArg], "-v") + || !stricmp(argv[iArg], "/version") + || !stricmp(argv[iArg], "-version") + || !stricmp(argv[iArg], "--version")) + return autostartSvcWinShowVersion(argc - iArg - 1, argv + iArg + 1); + else + iArg--; + iArg++; + } + + /* + * Dispatch it. + */ + switch (enmAction) + { + case kAutoSvcAction_RunIt: + return autostartSvcWinRunIt(argc - iArg, argv + iArg); + + case kAutoSvcAction_Create: + return autostartSvcWinCreate(argc - iArg, argv + iArg); + case kAutoSvcAction_Delete: + return autostartSvcWinDelete(argc - iArg, argv + iArg); + + case kAutoSvcAction_Enable: + return autostartSvcWinEnable(argc - iArg, argv + iArg); + case kAutoSvcAction_Disable: + return autostartSvcWinDisable(argc - iArg, argv + iArg); + case kAutoSvcAction_QueryConfig: + return autostartSvcWinQueryConfig(argc - iArg, argv + iArg); + case kAutoSvcAction_QueryDescription: + return autostartSvcWinQueryDescription(argc - iArg, argv + iArg); + + case kAutoSvcAction_Start: + return autostartSvcWinStart(argc - iArg, argv + iArg); + case kAutoSvcAction_Pause: + return autostartSvcWinPause(argc - iArg, argv + iArg); + case kAutoSvcAction_Continue: + return autostartSvcWinContinue(argc - iArg, argv + iArg); + case kAutoSvcAction_Stop: + return autostartSvcWinStop(argc - iArg, argv + iArg); + case kAutoSvcAction_Interrogate: + return autostartSvcWinInterrogate(argc - iArg, argv + iArg); + + default: + AssertMsgFailed(("enmAction=%d\n", enmAction)); + return RTEXITCODE_FAILURE; + } +} + diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostart.h b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart.h new file mode 100644 index 00000000..1c040a1c --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart.h @@ -0,0 +1,323 @@ +/* $Id: VBoxAutostart.h $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxAutostart_VBoxAutostart_h +#define VBOX_INCLUDED_SRC_VBoxAutostart_VBoxAutostart_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <iprt/getopt.h> +#include <iprt/types.h> + +#include <VBox/cdefs.h> +#include <VBox/types.h> + +#include <VBox/com/com.h> +#include <VBox/com/VirtualBox.h> + +/******************************************************************************* +* Constants And Macros, Structures and Typedefs * +*******************************************************************************/ + +/** + * Config AST node types. + */ +typedef enum CFGASTNODETYPE +{ + /** Invalid. */ + CFGASTNODETYPE_INVALID = 0, + /** Key/Value pair. */ + CFGASTNODETYPE_KEYVALUE, + /** Compound type. */ + CFGASTNODETYPE_COMPOUND, + /** List type. */ + CFGASTNODETYPE_LIST, + /** 32bit hack. */ + CFGASTNODETYPE_32BIT_HACK = 0x7fffffff +} CFGASTNODETYPE; +/** Pointer to a config AST node type. */ +typedef CFGASTNODETYPE *PCFGASTNODETYPE; +/** Pointer to a const config AST node type. */ +typedef const CFGASTNODETYPE *PCCFGASTNODETYPE; + +/** + * Config AST. + */ +typedef struct CFGAST +{ + /** AST node type. */ + CFGASTNODETYPE enmType; + /** Key or scope id. */ + char *pszKey; + /** Type dependent data. */ + union + { + /** Key value pair. */ + struct + { + /** Number of characters in the value - excluding terminator. */ + size_t cchValue; + /** Value string - variable in size. */ + char aszValue[1]; + } KeyValue; + /** Compound type. */ + struct + { + /** Number of AST node entries in the array. */ + unsigned cAstNodes; + /** AST node array - variable in size. */ + struct CFGAST *apAstNodes[1]; + } Compound; + /** List type. */ + struct + { + /** Number of entries in the list. */ + unsigned cListEntries; + /** Array of list entries - variable in size. */ + char *apszEntries[1]; + } List; + } u; +} CFGAST, *PCFGAST; + +/** Flag whether we are in verbose logging mode. */ +extern bool g_fVerbose; +/** Handle to the VirtualBox interface. */ +extern ComPtr<IVirtualBox> g_pVirtualBox; +/** Handle to the session interface. */ +extern ComPtr<ISession> g_pSession; +/** handle to the VirtualBox interface. */ +extern ComPtr<IVirtualBoxClient> g_pVirtualBoxClient; +/** + * System log type. + */ +typedef enum AUTOSTARTLOGTYPE +{ + /** Invalid log type. */ + AUTOSTARTLOGTYPE_INVALID = 0, + /** Log info message. */ + AUTOSTARTLOGTYPE_INFO, + /** Log error message. */ + AUTOSTARTLOGTYPE_ERROR, + /** Log warning message. */ + AUTOSTARTLOGTYPE_WARNING, + /** Log verbose message, only if verbose mode is activated. */ + AUTOSTARTLOGTYPE_VERBOSE, + /** Famous 32bit hack. */ + AUTOSTARTLOGTYPE_32BIT_HACK = 0x7fffffff +} AUTOSTARTLOGTYPE; + +/** + * Log messages to the system and release log. + * + * @returns nothing. + * @param pszMsg Message to log. + * @param enmLogType Log type to use. + */ +DECLHIDDEN(void) autostartSvcOsLogStr(const char *pszMsg, AUTOSTARTLOGTYPE enmLogType); + +/** + * Print out progress on the console. + * + * This runs the main event queue every now and then to prevent piling up + * unhandled things (which doesn't cause real problems, just makes things + * react a little slower than in the ideal case). + */ +DECLHIDDEN(HRESULT) showProgress(ComPtr<IProgress> progress); + +/** + * Converts the machine state to a human readable string. + * + * @returns Pointer to the human readable state. + * @param enmMachineState Machine state to convert. + * @param fShort Flag whether to return a short form. + */ +DECLHIDDEN(const char *) machineStateToName(MachineState_T enmMachineState, bool fShort); + +/** + * Parse the given configuration file and return the interesting config parameters. + * + * @returns VBox status code. + * @param pszFilename The config file to parse. + * @param ppCfgAst Where to store the pointer to the root AST node on success. + */ +DECLHIDDEN(int) autostartParseConfig(const char *pszFilename, PCFGAST *ppCfgAst); + +/** + * Destroys the config AST and frees all resources. + * + * @returns nothing. + * @param pCfgAst The config AST. + */ +DECLHIDDEN(void) autostartConfigAstDestroy(PCFGAST pCfgAst); + +/** + * Return the config AST node with the given name or NULL if it doesn't exist. + * + * @returns Matching config AST node for the given name or NULL if not found. + * @param pCfgAst The config ASt to search. + * @param pszName The name to search for. + */ +DECLHIDDEN(PCFGAST) autostartConfigAstGetByName(PCFGAST pCfgAst, const char *pszName); + +/** + * Main routine for the autostart daemon. + * + * @returns exit status code. + * @param pCfgAst Config AST for the startup part of the autostart daemon. + */ +DECLHIDDEN(RTEXITCODE) autostartStartMain(PCFGAST pCfgAst); + +/** + * Main routine for the autostart daemon when stopping virtual machines + * during system shutdown. + * + * @returns exit status code. + * @param pCfgAst Config AST for the shutdown part of the autostart daemon. + */ +DECLHIDDEN(RTEXITCODE) autostartStopMain(PCFGAST pCfgAst); + +/** + * Logs a verbose message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogVerboseV(const char *pszFormat, va_list va); + +/** + * Logs a verbose message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogVerbose(const char *pszFormat, ...); + +/** + * Logs a warning message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogWarningV(const char *pszFormat, va_list va); + +/** + * Logs a warning message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogWarning(const char *pszFormat, ...); + +/** + * Logs a info message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogInfoV(const char *pszFormat, va_list va); + +/** + * Logs a info message to the appropriate system log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + */ +DECLHIDDEN(void) autostartSvcLogInfo(const char *pszFormat, ...); + +/** + * Logs the message to the appropriate system log. + * + * In debug builds this will also put it in the debug log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + * + * @todo This should later be replaced by the release logger and callback destination(s). + */ +DECLHIDDEN(RTEXITCODE) autostartSvcLogErrorV(const char *pszFormat, va_list va); + +/** + * Logs the error message to the appropriate system log. + * + * In debug builds this will also put it in the debug log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + * + * @todo This should later be replaced by the release logger and callback destination(s). + */ +DECLHIDDEN(RTEXITCODE) autostartSvcLogError(const char *pszFormat, ...); + +/** + * Deals with RTGetOpt failure, bitching in the system log. + * + * @returns 1 + * @param pszAction The action name. + * @param rc The RTGetOpt return value. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + * @param pValue The value returned by RTGetOpt. + */ +DECLHIDDEN(RTEXITCODE) autostartSvcLogGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTGETOPTUNION pValue); + +/** + * Bitch about too many arguments (after RTGetOpt stops) in the system log. + * + * @returns 1 + * @param pszAction The action name. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + */ +DECLHIDDEN(RTEXITCODE) autostartSvcLogTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg); + +/** + * Prints an error message to the screen. + * + * @param pszFormat The message format string. + * @param va Format arguments. + */ +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayErrorV(const char *pszFormat, va_list va); + +/** + * Prints an error message to the screen. + * + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayError(const char *pszFormat, ...); + +/** + * Deals with RTGetOpt failure, i.e. an syntax error. + * + * @returns RTEXITCODE_SYNTAX + * @param pszAction The action name. + * @param rc The RTGetOpt return value. + * @param pValue The value returned by RTGetOpt. + */ +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayGetOptError(const char *pszAction, int rc, PCRTGETOPTUNION pValue); + +DECLHIDDEN(int) autostartSetup(); + +DECLHIDDEN(void) autostartShutdown(); + +#endif /* !VBOX_INCLUDED_SRC_VBoxAutostart_VBoxAutostart_h */ diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostartCfg.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartCfg.cpp new file mode 100644 index 00000000..9938d10f --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartCfg.cpp @@ -0,0 +1,748 @@ +/* $Id: VBoxAutostartCfg.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service, configuration parser. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "VBoxAutostart.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Token type. + */ +typedef enum CFGTOKENTYPE +{ + /** Invalid token type. */ + CFGTOKENTYPE_INVALID = 0, + /** Identifier. */ + CFGTOKENTYPE_ID, + /** Comma. */ + CFGTOKENTYPE_COMMA, + /** Equal sign. */ + CFGTOKENTYPE_EQUAL, + /** Open curly brackets. */ + CFGTOKENTYPE_CURLY_OPEN, + /** Closing curly brackets. */ + CFGTOKENTYPE_CURLY_CLOSING, + /** End of file. */ + CFGTOKENTYPE_EOF, + /** 32bit hack. */ + CFGTOKENTYPE_32BIT_HACK = 0x7fffffff +} CFGTOKENTYPE; +/** Pointer to a token type. */ +typedef CFGTOKENTYPE *PCFGTOKENTYPE; +/** Pointer to a const token type. */ +typedef const CFGTOKENTYPE *PCCFGTOKENTYPE; + +/** + * A token. + */ +typedef struct CFGTOKEN +{ + /** Type of the token. */ + CFGTOKENTYPE enmType; + /** Line number of the token. */ + unsigned iLine; + /** Starting character of the token in the stream. */ + size_t cchStart; + /** Type dependen token data. */ + union + { + /** Data for the ID type. */ + struct + { + /** Size of the id in characters, excluding the \0 terminator. */ + size_t cchToken; + /** Token data, variable size (given by cchToken member). */ + char achToken[1]; + } Id; + } u; +} CFGTOKEN; +/** Pointer to a token. */ +typedef CFGTOKEN *PCFGTOKEN; +/** Pointer to a const token. */ +typedef const CFGTOKEN *PCCFGTOKEN; + +/** + * Tokenizer instance data for the config data. + */ +typedef struct CFGTOKENIZER +{ + /** Config file handle. */ + PRTSTREAM hStrmConfig; + /** String buffer for the current line we are operating in. */ + char *pszLine; + /** Size of the string buffer. */ + size_t cbLine; + /** Current position in the line. */ + char *pszLineCurr; + /** Current line in the config file. */ + unsigned iLine; + /** Current character of the line. */ + size_t cchCurr; + /** Flag whether the end of the config stream is reached. */ + bool fEof; + /** Pointer to the next token in the stream (used to peek). */ + PCFGTOKEN pTokenNext; +} CFGTOKENIZER, *PCFGTOKENIZER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Free a config token. + * + * @returns nothing. + * @param pCfgTokenizer The config tokenizer. + * @param pToken The token to free. + */ +static void autostartConfigTokenFree(PCFGTOKENIZER pCfgTokenizer, PCFGTOKEN pToken) +{ + NOREF(pCfgTokenizer); + RTMemFree(pToken); +} + +/** + * Reads the next line from the config stream. + * + * @returns VBox status code. + * @param pCfgTokenizer The config tokenizer. + */ +static int autostartConfigTokenizerReadNextLine(PCFGTOKENIZER pCfgTokenizer) +{ + int rc = VINF_SUCCESS; + + if (pCfgTokenizer->fEof) + return VERR_EOF; + + do + { + rc = RTStrmGetLine(pCfgTokenizer->hStrmConfig, pCfgTokenizer->pszLine, + pCfgTokenizer->cbLine); + if (rc == VERR_BUFFER_OVERFLOW) + { + char *pszTmp; + + pCfgTokenizer->cbLine += 128; + pszTmp = (char *)RTMemRealloc(pCfgTokenizer->pszLine, pCfgTokenizer->cbLine); + if (pszTmp) + pCfgTokenizer->pszLine = pszTmp; + else + rc = VERR_NO_MEMORY; + } + } while (rc == VERR_BUFFER_OVERFLOW); + + if ( RT_SUCCESS(rc) + || rc == VERR_EOF) + { + pCfgTokenizer->iLine++; + pCfgTokenizer->cchCurr = 1; + pCfgTokenizer->pszLineCurr = pCfgTokenizer->pszLine; + if (rc == VERR_EOF) + pCfgTokenizer->fEof = true; + } + + return rc; +} + +/** + * Get the next token from the config stream and create a token structure. + * + * @returns VBox status code. + * @param pCfgTokenizer The config tokenizer data. + * @param pCfgTokenUse Allocated token structure to use or NULL to allocate + * a new one. It will bee freed if an error is encountered. + * @param ppCfgToken Where to store the pointer to the next token on success. + */ +static int autostartConfigTokenizerCreateToken(PCFGTOKENIZER pCfgTokenizer, + PCFGTOKEN pCfgTokenUse, PCFGTOKEN *ppCfgToken) +{ + const char *pszToken = NULL; + size_t cchToken = 1; + size_t cchAdvance = 0; + CFGTOKENTYPE enmType = CFGTOKENTYPE_INVALID; + int rc = VINF_SUCCESS; + + for (;;) + { + pszToken = pCfgTokenizer->pszLineCurr; + + /* Skip all spaces. */ + while (RT_C_IS_BLANK(*pszToken)) + { + pszToken++; + cchAdvance++; + } + + /* Check if we have to read a new line. */ + if ( *pszToken == '\0' + || *pszToken == '#') + { + rc = autostartConfigTokenizerReadNextLine(pCfgTokenizer); + if (rc == VERR_EOF) + { + enmType = CFGTOKENTYPE_EOF; + rc = VINF_SUCCESS; + break; + } + else if (RT_FAILURE(rc)) + break; + /* start from the beginning. */ + cchAdvance = 0; + } + else if (*pszToken == '=') + { + enmType = CFGTOKENTYPE_EQUAL; + break; + } + else if (*pszToken == ',') + { + enmType = CFGTOKENTYPE_COMMA; + break; + } + else if (*pszToken == '{') + { + enmType = CFGTOKENTYPE_CURLY_OPEN; + break; + } + else if (*pszToken == '}') + { + enmType = CFGTOKENTYPE_CURLY_CLOSING; + break; + } + else + { + const char *pszTmp = pszToken; + cchToken = 0; + enmType = CFGTOKENTYPE_ID; + + /* Get the complete token. */ + while ( RT_C_IS_ALNUM(*pszTmp) + || *pszTmp == '_' + || *pszTmp == '.') + { + pszTmp++; + cchToken++; + } + break; + } + } + + Assert(RT_FAILURE(rc) || enmType != CFGTOKENTYPE_INVALID); + + if (RT_SUCCESS(rc)) + { + /* Free the given token if it is an ID or the current one is an ID token. */ + if ( pCfgTokenUse + && ( pCfgTokenUse->enmType == CFGTOKENTYPE_ID + || enmType == CFGTOKENTYPE_ID)) + { + autostartConfigTokenFree(pCfgTokenizer, pCfgTokenUse); + pCfgTokenUse = NULL; + } + + if (!pCfgTokenUse) + { + size_t cbToken = sizeof(CFGTOKEN); + if (enmType == CFGTOKENTYPE_ID) + cbToken += (cchToken + 1) * sizeof(char); + + pCfgTokenUse = (PCFGTOKEN)RTMemAllocZ(cbToken); + if (!pCfgTokenUse) + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* Copy token data. */ + pCfgTokenUse->enmType = enmType; + pCfgTokenUse->cchStart = pCfgTokenizer->cchCurr; + pCfgTokenUse->iLine = pCfgTokenizer->iLine; + if (enmType == CFGTOKENTYPE_ID) + { + pCfgTokenUse->u.Id.cchToken = cchToken; + memcpy(pCfgTokenUse->u.Id.achToken, pszToken, cchToken); + } + } + else if (pCfgTokenUse) + autostartConfigTokenFree(pCfgTokenizer, pCfgTokenUse); + + if (RT_SUCCESS(rc)) + { + /* Set new position in config stream. */ + pCfgTokenizer->pszLineCurr += cchToken + cchAdvance; + pCfgTokenizer->cchCurr += cchToken + cchAdvance; + *ppCfgToken = pCfgTokenUse; + } + } + + return rc; +} + +/** + * Destroys the given config tokenizer. + * + * @returns nothing. + * @param pCfgTokenizer The config tokenizer to destroy. + */ +static void autostartConfigTokenizerDestroy(PCFGTOKENIZER pCfgTokenizer) +{ + if (pCfgTokenizer->pszLine) + RTMemFree(pCfgTokenizer->pszLine); + if (pCfgTokenizer->hStrmConfig) + RTStrmClose(pCfgTokenizer->hStrmConfig); + if (pCfgTokenizer->pTokenNext) + RTMemFree(pCfgTokenizer->pTokenNext); + RTMemFree(pCfgTokenizer); +} + +/** + * Creates the config tokenizer from the given filename. + * + * @returns VBox status code. + * @param pszFilename Config filename. + * @param ppCfgTokenizer Where to store the pointer to the config tokenizer on + * success. + */ +static int autostartConfigTokenizerCreate(const char *pszFilename, PCFGTOKENIZER *ppCfgTokenizer) +{ + int rc = VINF_SUCCESS; + PCFGTOKENIZER pCfgTokenizer = (PCFGTOKENIZER)RTMemAllocZ(sizeof(CFGTOKENIZER)); + + if (pCfgTokenizer) + { + pCfgTokenizer->iLine = 0; + pCfgTokenizer->cbLine = 128; + pCfgTokenizer->pszLine = (char *)RTMemAllocZ(pCfgTokenizer->cbLine); + if (pCfgTokenizer->pszLine) + { + rc = RTStrmOpen(pszFilename, "r", &pCfgTokenizer->hStrmConfig); + if (RT_SUCCESS(rc)) + { + rc = autostartConfigTokenizerReadNextLine(pCfgTokenizer); + if (RT_SUCCESS(rc)) + rc = autostartConfigTokenizerCreateToken(pCfgTokenizer, NULL, + &pCfgTokenizer->pTokenNext); + } + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + *ppCfgTokenizer = pCfgTokenizer; + else if ( RT_FAILURE(rc) + && pCfgTokenizer) + autostartConfigTokenizerDestroy(pCfgTokenizer); + + return rc; +} + +/** + * Return the next token from the config stream. + * + * @returns VBox status code. + * @param pCfgTokenizer The config tokenizer. + * @param ppCfgToken Where to store the next token. + */ +static int autostartConfigTokenizerGetNextToken(PCFGTOKENIZER pCfgTokenizer, + PCFGTOKEN *ppCfgToken) +{ + *ppCfgToken = pCfgTokenizer->pTokenNext; + return autostartConfigTokenizerCreateToken(pCfgTokenizer, NULL, &pCfgTokenizer->pTokenNext); +} + +/** + * Returns a stringified version of the token type. + * + * @returns Stringified version of the token type. + * @param enmType Token type. + */ +static const char *autostartConfigTokenTypeToStr(CFGTOKENTYPE enmType) +{ + switch (enmType) + { + case CFGTOKENTYPE_COMMA: + return ","; + case CFGTOKENTYPE_EQUAL: + return "="; + case CFGTOKENTYPE_CURLY_OPEN: + return "{"; + case CFGTOKENTYPE_CURLY_CLOSING: + return "}"; + case CFGTOKENTYPE_EOF: + return "<EOF>"; + case CFGTOKENTYPE_ID: + return "<Identifier>"; + default: + AssertFailed(); + return "<Invalid>"; + } + /* not reached */ +} + +/** + * Returns a stringified version of the token. + * + * @returns Stringified version of the token type. + * @param pToken Token. + */ +static const char *autostartConfigTokenToString(PCFGTOKEN pToken) +{ + if (pToken->enmType == CFGTOKENTYPE_ID) + return pToken->u.Id.achToken; + else + return autostartConfigTokenTypeToStr(pToken->enmType); +} + +/** + * Returns the length of the token in characters (without zero terminator). + * + * @returns Token length. + * @param pToken Token. + */ +static size_t autostartConfigTokenGetLength(PCFGTOKEN pToken) +{ + switch (pToken->enmType) + { + case CFGTOKENTYPE_COMMA: + case CFGTOKENTYPE_EQUAL: + case CFGTOKENTYPE_CURLY_OPEN: + case CFGTOKENTYPE_CURLY_CLOSING: + return 1; + case CFGTOKENTYPE_EOF: + return 0; + case CFGTOKENTYPE_ID: + return strlen(pToken->u.Id.achToken); + default: + AssertFailed(); + return 0; + } + /* not reached */ +} + +/** + * Log unexpected token error. + * + * @returns nothing. + * @param pToken The token which caused the error. + * @param pszExpected String of the token which was expected. + */ +static void autostartConfigTokenizerMsgUnexpectedToken(PCFGTOKEN pToken, const char *pszExpected) +{ + autostartSvcLogError("Unexpected token '%s' at %d:%d.%d, expected '%s'", + autostartConfigTokenToString(pToken), + pToken->iLine, pToken->cchStart, + pToken->cchStart + autostartConfigTokenGetLength(pToken) - 1, pszExpected); +} + +/** + * Verfies a token and consumes it. + * + * @returns VBox status code. + * @param pCfgTokenizer The config tokenizer. + * @param pszTokenCheck The token to check for. + */ +static int autostartConfigTokenizerCheckAndConsume(PCFGTOKENIZER pCfgTokenizer, CFGTOKENTYPE enmType) +{ + int rc = VINF_SUCCESS; + PCFGTOKEN pCfgToken = NULL; + + rc = autostartConfigTokenizerGetNextToken(pCfgTokenizer, &pCfgToken); + if (RT_SUCCESS(rc)) + { + if (pCfgToken->enmType != enmType) + { + autostartConfigTokenizerMsgUnexpectedToken(pCfgToken, autostartConfigTokenTypeToStr(enmType)); + rc = VERR_INVALID_PARAMETER; + } + + autostartConfigTokenFree(pCfgTokenizer, pCfgToken); + } + return rc; +} + +/** + * Consumes the next token in the stream. + * + * @returns VBox status code. + * @param pCfgTokenizer Tokenizer instance data. + */ +static int autostartConfigTokenizerConsume(PCFGTOKENIZER pCfgTokenizer) +{ + int rc = VINF_SUCCESS; + PCFGTOKEN pCfgToken = NULL; + + rc = autostartConfigTokenizerGetNextToken(pCfgTokenizer, &pCfgToken); + if (RT_SUCCESS(rc)) + autostartConfigTokenFree(pCfgTokenizer, pCfgToken); + + return rc; +} + +/** + * Returns the start of the next token without consuming it. + * + * @returns The next token without consuming it. + * @param pCfgTokenizer Tokenizer instance data. + */ +DECLINLINE(PCFGTOKEN) autostartConfigTokenizerPeek(PCFGTOKENIZER pCfgTokenizer) +{ + return pCfgTokenizer->pTokenNext; +} + +/** + * Check whether the next token is equal to the given one. + * + * @returns true if the next token in the stream is equal to the given one + * false otherwise. + * @param pszToken The token to check for. + */ +DECLINLINE(bool) autostartConfigTokenizerPeekIsEqual(PCFGTOKENIZER pCfgTokenizer, CFGTOKENTYPE enmType) +{ + PCFGTOKEN pToken = autostartConfigTokenizerPeek(pCfgTokenizer); + return pToken->enmType == enmType; +} + +/** + * Parse a key value node and returns the AST. + * + * @returns VBox status code. + * @param pCfgTokenizer The tokenizer for the config stream. + * @param pszKey The key for the pair. + * @param ppCfgAst Where to store the resulting AST on success. + */ +static int autostartConfigParseValue(PCFGTOKENIZER pCfgTokenizer, const char *pszKey, + PCFGAST *ppCfgAst) +{ + int rc = VINF_SUCCESS; + PCFGTOKEN pToken = NULL; + + rc = autostartConfigTokenizerGetNextToken(pCfgTokenizer, &pToken); + if ( RT_SUCCESS(rc) + && pToken->enmType == CFGTOKENTYPE_ID) + { + PCFGAST pCfgAst = NULL; + + pCfgAst = (PCFGAST)RTMemAllocZ(RT_UOFFSETOF_DYN(CFGAST, u.KeyValue.aszValue[pToken->u.Id.cchToken + 1])); + if (!pCfgAst) + return VERR_NO_MEMORY; + + pCfgAst->enmType = CFGASTNODETYPE_KEYVALUE; + pCfgAst->pszKey = RTStrDup(pszKey); + if (!pCfgAst->pszKey) + { + RTMemFree(pCfgAst); + return VERR_NO_MEMORY; + } + + memcpy(pCfgAst->u.KeyValue.aszValue, pToken->u.Id.achToken, pToken->u.Id.cchToken); + pCfgAst->u.KeyValue.cchValue = pToken->u.Id.cchToken; + *ppCfgAst = pCfgAst; + } + else + { + autostartConfigTokenizerMsgUnexpectedToken(pToken, "non reserved token"); + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Parses a compound node constructing the AST and returning it on success. + * + * @returns VBox status code. + * @param pCfgTokenizer The tokenizer for the config stream. + * @param pszScopeId The scope ID of the compound node. + * @param ppCfgAst Where to store the resulting AST on success. + */ +static int autostartConfigParseCompoundNode(PCFGTOKENIZER pCfgTokenizer, const char *pszScopeId, + PCFGAST *ppCfgAst) +{ + unsigned cAstNodesMax = 10; + PCFGAST pCfgAst = (PCFGAST)RTMemAllocZ(RT_UOFFSETOF_DYN(CFGAST, u.Compound.apAstNodes[cAstNodesMax])); + if (!pCfgAst) + return VERR_NO_MEMORY; + + pCfgAst->enmType = CFGASTNODETYPE_COMPOUND; + pCfgAst->u.Compound.cAstNodes = 0; + pCfgAst->pszKey = RTStrDup(pszScopeId); + if (!pCfgAst->pszKey) + { + RTMemFree(pCfgAst); + return VERR_NO_MEMORY; + } + + int rc = VINF_SUCCESS; + do + { + PCFGTOKEN pToken = NULL; + PCFGAST pAstNode = NULL; + + if ( autostartConfigTokenizerPeekIsEqual(pCfgTokenizer, CFGTOKENTYPE_CURLY_CLOSING) + || autostartConfigTokenizerPeekIsEqual(pCfgTokenizer, CFGTOKENTYPE_EOF)) + break; + + rc = autostartConfigTokenizerGetNextToken(pCfgTokenizer, &pToken); + if ( RT_SUCCESS(rc) + && pToken->enmType == CFGTOKENTYPE_ID) + { + /* Next must be a = token in all cases at this place. */ + rc = autostartConfigTokenizerCheckAndConsume(pCfgTokenizer, CFGTOKENTYPE_EQUAL); + if (RT_SUCCESS(rc)) + { + /* Check whether this is a compound node. */ + if (autostartConfigTokenizerPeekIsEqual(pCfgTokenizer, CFGTOKENTYPE_CURLY_OPEN)) + { + rc = autostartConfigTokenizerConsume(pCfgTokenizer); + if (RT_SUCCESS(rc)) + rc = autostartConfigParseCompoundNode(pCfgTokenizer, pToken->u.Id.achToken, + &pAstNode); + + if (RT_SUCCESS(rc)) + rc = autostartConfigTokenizerCheckAndConsume(pCfgTokenizer, CFGTOKENTYPE_CURLY_CLOSING); + } + else + rc = autostartConfigParseValue(pCfgTokenizer, pToken->u.Id.achToken, + &pAstNode); + } + } + else if (RT_SUCCESS(rc)) + { + autostartConfigTokenizerMsgUnexpectedToken(pToken, "non reserved token"); + rc = VERR_INVALID_PARAMETER; + } + + /* Add to the current compound node. */ + if (RT_SUCCESS(rc)) + { + if (pCfgAst->u.Compound.cAstNodes >= cAstNodesMax) + { + cAstNodesMax += 10; + + PCFGAST pCfgAstNew = (PCFGAST)RTMemRealloc(pCfgAst, RT_UOFFSETOF_DYN(CFGAST, u.Compound.apAstNodes[cAstNodesMax])); + if (!pCfgAstNew) + rc = VERR_NO_MEMORY; + else + pCfgAst = pCfgAstNew; + } + + if (RT_SUCCESS(rc)) + { + pCfgAst->u.Compound.apAstNodes[pCfgAst->u.Compound.cAstNodes] = pAstNode; + pCfgAst->u.Compound.cAstNodes++; + } + } + + autostartConfigTokenFree(pCfgTokenizer, pToken); + + } while (RT_SUCCESS(rc)); + + if (RT_SUCCESS(rc)) + *ppCfgAst = pCfgAst; + else + autostartConfigAstDestroy(pCfgAst); + + return rc; +} + +DECLHIDDEN(int) autostartParseConfig(const char *pszFilename, PCFGAST *ppCfgAst) +{ + PCFGTOKENIZER pCfgTokenizer = NULL; + int rc = VINF_SUCCESS; + PCFGAST pCfgAst = NULL; + + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(ppCfgAst, VERR_INVALID_POINTER); + + rc = autostartConfigTokenizerCreate(pszFilename, &pCfgTokenizer); + if (RT_SUCCESS(rc)) + { + rc = autostartConfigParseCompoundNode(pCfgTokenizer, "", &pCfgAst); + if (RT_SUCCESS(rc)) + rc = autostartConfigTokenizerCheckAndConsume(pCfgTokenizer, CFGTOKENTYPE_EOF); + } + + if (pCfgTokenizer) + autostartConfigTokenizerDestroy(pCfgTokenizer); + + if (RT_SUCCESS(rc)) + *ppCfgAst = pCfgAst; + + return rc; +} + +DECLHIDDEN(void) autostartConfigAstDestroy(PCFGAST pCfgAst) +{ + AssertPtrReturnVoid(pCfgAst); + + switch (pCfgAst->enmType) + { + case CFGASTNODETYPE_KEYVALUE: + { + RTMemFree(pCfgAst); + break; + } + case CFGASTNODETYPE_COMPOUND: + { + for (unsigned i = 0; i < pCfgAst->u.Compound.cAstNodes; i++) + autostartConfigAstDestroy(pCfgAst->u.Compound.apAstNodes[i]); + RTMemFree(pCfgAst); + break; + } + case CFGASTNODETYPE_LIST: + default: + AssertMsgFailed(("Invalid AST node type %d\n", pCfgAst->enmType)); + } +} + +DECLHIDDEN(PCFGAST) autostartConfigAstGetByName(PCFGAST pCfgAst, const char *pszName) +{ + if (!pCfgAst) + return NULL; + + AssertReturn(pCfgAst->enmType == CFGASTNODETYPE_COMPOUND, NULL); + + for (unsigned i = 0; i < pCfgAst->u.Compound.cAstNodes; i++) + { + PCFGAST pNode = pCfgAst->u.Compound.apAstNodes[i]; + + if (!RTStrCmp(pNode->pszKey, pszName)) + return pNode; + } + + return NULL; +} + diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStart.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStart.cpp new file mode 100644 index 00000000..a683aa0e --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStart.cpp @@ -0,0 +1,176 @@ +/* $Id: VBoxAutostartStart.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service, start machines during system boot. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <iprt/errcore.h> +#include <iprt/log.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/thread.h> + +#include <algorithm> +#include <list> + +#include "VBoxAutostart.h" + +using namespace com; + +/** + * VM list entry. + */ +typedef struct AUTOSTARTVM +{ + /** ID of the VM to start. */ + Bstr strId; + /** Startup delay of the VM. */ + ULONG uStartupDelay; +} AUTOSTARTVM; + +static DECLCALLBACK(bool) autostartVMCmp(const AUTOSTARTVM &vm1, const AUTOSTARTVM &vm2) +{ + return vm1.uStartupDelay <= vm2.uStartupDelay; +} + +DECLHIDDEN(RTEXITCODE) autostartStartMain(PCFGAST pCfgAst) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + int vrc = VINF_SUCCESS; + std::list<AUTOSTARTVM> listVM; + uint32_t uStartupDelay = 0; + + pCfgAst = autostartConfigAstGetByName(pCfgAst, "startup_delay"); + if (pCfgAst) + { + if (pCfgAst->enmType == CFGASTNODETYPE_KEYVALUE) + { + vrc = RTStrToUInt32Full(pCfgAst->u.KeyValue.aszValue, 10, &uStartupDelay); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "'startup_delay' must be an unsigned number"); + } + } + + if (uStartupDelay) + { + autostartSvcLogVerbose("Delay starting for %d seconds ...\n", uStartupDelay); + vrc = RTThreadSleep(uStartupDelay * 1000); + } + + if (vrc == VERR_INTERRUPTED) + return RTEXITCODE_SUCCESS; + + /* + * Build a list of all VMs we need to autostart first, apply the overrides + * from the configuration and start the VMs afterwards. + */ + com::SafeIfaceArray<IMachine> machines; + HRESULT rc = g_pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)); + if (SUCCEEDED(rc)) + { + /* + * Iterate through the collection + */ + for (size_t i = 0; i < machines.size(); ++i) + { + if (machines[i]) + { + BOOL fAccessible; + CHECK_ERROR_BREAK(machines[i], COMGETTER(Accessible)(&fAccessible)); + if (!fAccessible) + continue; + + BOOL fAutostart; + CHECK_ERROR_BREAK(machines[i], COMGETTER(AutostartEnabled)(&fAutostart)); + if (fAutostart) + { + AUTOSTARTVM autostartVM; + + CHECK_ERROR_BREAK(machines[i], COMGETTER(Id)(autostartVM.strId.asOutParam())); + CHECK_ERROR_BREAK(machines[i], COMGETTER(AutostartDelay)(&autostartVM.uStartupDelay)); + + listVM.push_back(autostartVM); + } + } + } + + if ( SUCCEEDED(rc) + && !listVM.empty()) + { + ULONG uDelayCurr = 0; + + /* Sort by startup delay and apply base override. */ + listVM.sort(autostartVMCmp); + + std::list<AUTOSTARTVM>::iterator it; + for (it = listVM.begin(); it != listVM.end(); ++it) + { + ComPtr<IMachine> machine; + ComPtr<IProgress> progress; + + if ((*it).uStartupDelay > uDelayCurr) + { + autostartSvcLogVerbose("Delay starting of the next VMs for %d seconds ...\n", + (*it).uStartupDelay - uDelayCurr); + RTThreadSleep(((*it).uStartupDelay - uDelayCurr) * 1000); + uDelayCurr = (*it).uStartupDelay; + } + + CHECK_ERROR_BREAK(g_pVirtualBox, FindMachine((*it).strId.raw(), + machine.asOutParam())); + + CHECK_ERROR_BREAK(machine, LaunchVMProcess(g_pSession, Bstr("headless").raw(), + ComSafeArrayNullInParam(), progress.asOutParam())); + if (SUCCEEDED(rc) && !progress.isNull()) + { + autostartSvcLogVerbose("Waiting for VM \"%ls\" to power on...\n", (*it).strId.raw()); + CHECK_ERROR(progress, WaitForCompletion(-1)); + if (SUCCEEDED(rc)) + { + BOOL completed = true; + CHECK_ERROR(progress, COMGETTER(Completed)(&completed)); + if (SUCCEEDED(rc)) + { + ASSERT(completed); + + LONG iRc; + CHECK_ERROR(progress, COMGETTER(ResultCode)(&iRc)); + if (SUCCEEDED(rc)) + { + if (FAILED(iRc)) + { + ProgressErrorInfo info(progress); + com::GluePrintErrorInfo(info); + } + else + autostartSvcLogVerbose("VM \"%ls\" has been successfully started.\n", (*it).strId.raw()); + } + } + } + } + g_pSession->UnlockMachine(); + } + } + } + + return rcExit; +} + diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStop.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStop.cpp new file mode 100644 index 00000000..ba194f90 --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartStop.cpp @@ -0,0 +1,234 @@ +/* $Id: VBoxAutostartStop.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service, stop machines during system shutdown. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <iprt/thread.h> +#include <iprt/stream.h> +#include <iprt/log.h> +#include <iprt/assert.h> +#include <iprt/message.h> + +#include <algorithm> +#include <list> +#include <string> + +#include "VBoxAutostart.h" + +using namespace com; + +/** + * VM list entry. + */ +typedef struct AUTOSTOPVM +{ + /** ID of the VM to start. */ + Bstr strId; + /** Action to do with the VM. */ + AutostopType_T enmAutostopType; +} AUTOSTOPVM; + +static HRESULT autostartSaveVMState(ComPtr<IConsole> &console) +{ + HRESULT rc = S_OK; + ComPtr<IMachine> machine; + ComPtr<IProgress> progress; + + do + { + /* first pause so we don't trigger a live save which needs more time/resources */ + bool fPaused = false; + rc = console->Pause(); + if (FAILED(rc)) + { + bool fError = true; + if (rc == VBOX_E_INVALID_VM_STATE) + { + /* check if we are already paused */ + MachineState_T machineState; + CHECK_ERROR_BREAK(console, COMGETTER(State)(&machineState)); + /* the error code was lost by the previous instruction */ + rc = VBOX_E_INVALID_VM_STATE; + if (machineState != MachineState_Paused) + { + RTMsgError("Machine in invalid state %d -- %s\n", + machineState, machineStateToName(machineState, false)); + } + else + { + fError = false; + fPaused = true; + } + } + if (fError) + break; + } + + CHECK_ERROR(console, COMGETTER(Machine)(machine.asOutParam())); + CHECK_ERROR(machine, SaveState(progress.asOutParam())); + if (FAILED(rc)) + { + if (!fPaused) + console->Resume(); + break; + } + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR(progress, ("Failed to save machine state")); + if (FAILED(rc)) + { + if (!fPaused) + console->Resume(); + } + } while (0); + + return rc; +} + +DECLHIDDEN(RTEXITCODE) autostartStopMain(PCFGAST pCfgAst) +{ + RT_NOREF(pCfgAst); + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + std::list<AUTOSTOPVM> listVM; + + /* + * Build a list of all VMs we need to autostop first, apply the overrides + * from the configuration and start the VMs afterwards. + */ + com::SafeIfaceArray<IMachine> machines; + HRESULT rc = g_pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines)); + if (SUCCEEDED(rc)) + { + /* + * Iterate through the collection and construct a list of machines + * we have to check. + */ + for (size_t i = 0; i < machines.size(); ++i) + { + if (machines[i]) + { + BOOL fAccessible; + CHECK_ERROR_BREAK(machines[i], COMGETTER(Accessible)(&fAccessible)); + if (!fAccessible) + continue; + + AutostopType_T enmAutostopType; + CHECK_ERROR_BREAK(machines[i], COMGETTER(AutostopType)(&enmAutostopType)); + if (enmAutostopType != AutostopType_Disabled) + { + AUTOSTOPVM autostopVM; + + CHECK_ERROR_BREAK(machines[i], COMGETTER(Id)(autostopVM.strId.asOutParam())); + autostopVM.enmAutostopType = enmAutostopType; + + listVM.push_back(autostopVM); + } + } + } + + if ( SUCCEEDED(rc) + && !listVM.empty()) + { + std::list<AUTOSTOPVM>::iterator it; + for (it = listVM.begin(); it != listVM.end(); ++it) + { + MachineState_T enmMachineState; + ComPtr<IMachine> machine; + + CHECK_ERROR_BREAK(g_pVirtualBox, FindMachine((*it).strId.raw(), + machine.asOutParam())); + + CHECK_ERROR_BREAK(machine, COMGETTER(State)(&enmMachineState)); + + /* Wait until the VM changes from a transient state back. */ + while ( enmMachineState >= MachineState_FirstTransient + && enmMachineState <= MachineState_LastTransient) + { + RTThreadSleep(1000); + CHECK_ERROR_BREAK(machine, COMGETTER(State)(&enmMachineState)); + } + + /* Only power off running machines. */ + if ( enmMachineState == MachineState_Running + || enmMachineState == MachineState_Paused) + { + ComPtr<IConsole> console; + ComPtr<IProgress> progress; + + /* open a session for the VM */ + CHECK_ERROR_BREAK(machine, LockMachine(g_pSession, LockType_Shared)); + + /* get the associated console */ + CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam())); + + switch ((*it).enmAutostopType) + { + case AutostopType_SaveState: + { + rc = autostartSaveVMState(console); + break; + } + case AutostopType_PowerOff: + { + CHECK_ERROR_BREAK(console, PowerDown(progress.asOutParam())); + + rc = showProgress(progress); + CHECK_PROGRESS_ERROR(progress, ("Failed to power off machine")); + break; + } + case AutostopType_AcpiShutdown: + { + BOOL fGuestEnteredACPI = false; + CHECK_ERROR_BREAK(console, GetGuestEnteredACPIMode(&fGuestEnteredACPI)); + if (fGuestEnteredACPI && enmMachineState == MachineState_Running) + { + CHECK_ERROR_BREAK(console, PowerButton()); + + autostartSvcLogInfo("Waiting for VM \"%ls\" to power off...\n", (*it).strId.raw()); + + do + { + RTThreadSleep(1000); + CHECK_ERROR_BREAK(machine, COMGETTER(State)(&enmMachineState)); + } while (enmMachineState == MachineState_Running); + } + else + { + /* Use save state instead and log this to the console. */ + autostartSvcLogWarning("The guest of VM \"%ls\" does not support ACPI shutdown or is currently paused, saving state...\n", + (*it).strId.raw()); + rc = autostartSaveVMState(console); + } + break; + } + default: + autostartSvcLogWarning("Unknown autostop type for VM \"%ls\"\n", (*it).strId.raw()); + } + g_pSession->UnlockMachine(); + } + } + } + } + + return rcExit; +} + diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostartUtils.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartUtils.cpp new file mode 100644 index 00000000..3bbd8584 --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostartUtils.cpp @@ -0,0 +1,296 @@ +/* $Id: VBoxAutostartUtils.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service, start machines during system boot. + * Utils used by the windows and posix frontends. + */ + +/* + * Copyright (C) 2012-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/err.h> + +#include <iprt/message.h> +#include <iprt/thread.h> +#include <iprt/stream.h> +#include <iprt/log.h> +#include <iprt/path.h> + +#include <algorithm> +#include <list> +#include <string> + +#include "VBoxAutostart.h" + +using namespace com; + +DECLHIDDEN(const char *) machineStateToName(MachineState_T machineState, bool fShort) +{ + switch (machineState) + { + case MachineState_PoweredOff: + return fShort ? "poweroff" : "powered off"; + case MachineState_Saved: + return "saved"; + case MachineState_Teleported: + return "teleported"; + case MachineState_Aborted: + return "aborted"; + case MachineState_Running: + return "running"; + case MachineState_Paused: + return "paused"; + case MachineState_Stuck: + return fShort ? "gurumeditation" : "guru meditation"; + case MachineState_Teleporting: + return "teleporting"; + case MachineState_LiveSnapshotting: + return fShort ? "livesnapshotting" : "live snapshotting"; + case MachineState_Starting: + return "starting"; + case MachineState_Stopping: + return "stopping"; + case MachineState_Saving: + return "saving"; + case MachineState_Restoring: + return "restoring"; + case MachineState_TeleportingPausedVM: + return fShort ? "teleportingpausedvm" : "teleporting paused vm"; + case MachineState_TeleportingIn: + return fShort ? "teleportingin" : "teleporting (incoming)"; + case MachineState_DeletingSnapshotOnline: + return fShort ? "deletingsnapshotlive" : "deleting snapshot live"; + case MachineState_DeletingSnapshotPaused: + return fShort ? "deletingsnapshotlivepaused" : "deleting snapshot live paused"; + case MachineState_OnlineSnapshotting: + return fShort ? "onlinesnapshotting" : "online snapshotting"; + case MachineState_RestoringSnapshot: + return fShort ? "restoringsnapshot" : "restoring snapshot"; + case MachineState_DeletingSnapshot: + return fShort ? "deletingsnapshot" : "deleting snapshot"; + case MachineState_SettingUp: + return fShort ? "settingup" : "setting up"; + case MachineState_Snapshotting: + return "snapshotting"; + default: + break; + } + return "unknown"; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcLogErrorV(const char *pszFormat, va_list va) +{ + if (*pszFormat) + { + char *pszMsg = NULL; + if (RTStrAPrintfV(&pszMsg, pszFormat, va) != -1) + { + autostartSvcOsLogStr(pszMsg, AUTOSTARTLOGTYPE_ERROR); + RTStrFree(pszMsg); + } + else + autostartSvcOsLogStr(pszFormat, AUTOSTARTLOGTYPE_ERROR); + } + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcLogError(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + autostartSvcLogErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(void) autostartSvcLogVerboseV(const char *pszFormat, va_list va) +{ + if (*pszFormat) + { + char *pszMsg = NULL; + if (RTStrAPrintfV(&pszMsg, pszFormat, va) != -1) + { + autostartSvcOsLogStr(pszMsg, AUTOSTARTLOGTYPE_VERBOSE); + RTStrFree(pszMsg); + } + else + autostartSvcOsLogStr(pszFormat, AUTOSTARTLOGTYPE_VERBOSE); + } +} + +DECLHIDDEN(void) autostartSvcLogVerbose(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + autostartSvcLogVerboseV(pszFormat, va); + va_end(va); +} + +DECLHIDDEN(void) autostartSvcLogWarningV(const char *pszFormat, va_list va) +{ + if (*pszFormat) + { + char *pszMsg = NULL; + if (RTStrAPrintfV(&pszMsg, pszFormat, va) != -1) + { + autostartSvcOsLogStr(pszMsg, AUTOSTARTLOGTYPE_WARNING); + RTStrFree(pszMsg); + } + else + autostartSvcOsLogStr(pszFormat, AUTOSTARTLOGTYPE_WARNING); + } +} + +DECLHIDDEN(void) autostartSvcLogInfo(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + autostartSvcLogInfoV(pszFormat, va); + va_end(va); +} + +DECLHIDDEN(void) autostartSvcLogInfoV(const char *pszFormat, va_list va) +{ + if (*pszFormat) + { + char *pszMsg = NULL; + if (RTStrAPrintfV(&pszMsg, pszFormat, va) != -1) + { + autostartSvcOsLogStr(pszMsg, AUTOSTARTLOGTYPE_INFO); + RTStrFree(pszMsg); + } + else + autostartSvcOsLogStr(pszFormat, AUTOSTARTLOGTYPE_INFO); + } +} + +DECLHIDDEN(void) autostartSvcLogWarning(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + autostartSvcLogWarningV(pszFormat, va); + va_end(va); +} + +DECLHIDDEN(RTEXITCODE) autostartSvcLogGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTGETOPTUNION pValue) +{ + NOREF(pValue); + autostartSvcLogError("%s - RTGetOpt failure, %Rrc (%d): %s", + pszAction, rc, rc, iArg < argc ? argv[iArg] : "<null>"); + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcLogTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg) +{ + Assert(iArg < argc); + autostartSvcLogError("%s - Too many arguments: %s", pszAction, argv[iArg]); + for ( ; iArg < argc; iArg++) + LogRel(("arg#%i: %s\n", iArg, argv[iArg])); + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayErrorV(const char *pszFormat, va_list va) +{ + RTStrmPrintf(g_pStdErr, "VBoxSupSvc error: "); + RTStrmPrintfV(g_pStdErr, pszFormat, va); + Log(("autostartSvcDisplayErrorV: %s", pszFormat)); /** @todo format it! */ + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayError(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + autostartSvcDisplayErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + +DECLHIDDEN(RTEXITCODE) autostartSvcDisplayGetOptError(const char *pszAction, int rc, PCRTGETOPTUNION pValue) +{ + char szMsg[4096]; + RTGetOptFormatError(szMsg, sizeof(szMsg), rc, pValue); + autostartSvcDisplayError("%s - %s", pszAction, szMsg); + return RTEXITCODE_SYNTAX; +} + +DECLHIDDEN(int) autostartSetup() +{ + autostartSvcOsLogStr("Setting up ...\n", AUTOSTARTLOGTYPE_VERBOSE); + + /* + * Initialize COM. + */ + using namespace com; + HRESULT hrc = com::Initialize(); +# ifdef VBOX_WITH_XPCOM + if (hrc == NS_ERROR_FILE_ACCESS_DENIED) + { + char szHome[RTPATH_MAX] = ""; + com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome)); + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome); + } +# endif + if (FAILED(hrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM (%Rhrc)!", hrc); + + hrc = g_pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (FAILED(hrc)) + { + RTMsgError("Failed to create the VirtualBoxClient object (%Rhrc)!", hrc); + com::ErrorInfo info; + if (!info.isFullAvailable() && !info.isBasicAvailable()) + { + com::GluePrintRCMessage(hrc); + RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start."); + } + else + com::GluePrintErrorInfo(info); + return RTEXITCODE_FAILURE; + } + + /* + * Setup VirtualBox + session interfaces. + */ + HRESULT rc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam()); + if (SUCCEEDED(rc)) + { + rc = g_pSession.createInprocObject(CLSID_Session); + if (FAILED(rc)) + RTMsgError("Failed to create a session object (rc=%Rhrc)!", rc); + } + else + RTMsgError("Failed to get VirtualBox object (rc=%Rhrc)!", rc); + + if (FAILED(rc)) + return VERR_COM_OBJECT_NOT_FOUND; + + return VINF_SUCCESS; +} + +DECLHIDDEN(void) autostartShutdown() +{ + autostartSvcOsLogStr("Shutting down ...\n", AUTOSTARTLOGTYPE_VERBOSE); + + g_pSession.setNull(); + g_pVirtualBox.setNull(); + g_pVirtualBoxClient.setNull(); + com::Shutdown(); +} + |