diff options
Diffstat (limited to 'src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp')
-rw-r--r-- | src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp new file mode 100644 index 00000000..94f846db --- /dev/null +++ b/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp @@ -0,0 +1,579 @@ +/* $Id: VBoxAutostart-posix.cpp $ */ +/** @file + * VBoxAutostart - VirtualBox Autostart service. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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 <iprt/env.h> + +#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; +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. */ + +/** Verbosity level. */ +unsigned g_cVerbosity = 0; + +/** 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_cVerbosity) + return; + + LogRel(("%s", pszMsg)); +} + +/** + * Shows the help. + * + * @param pszImage Name of program name (image). + */ +static void showHelp(const char *pszImage) +{ + AssertPtrReturnVoid(pszImage); + + autostartSvcShowHeader(); + + RTStrmPrintf(g_pStdErr, + "Usage: %s [-v|--verbose] [-h|-?|--help]\n" + " [-V|--version]\n" + " [-F|--logfile=<file>] [-R|--logrotate=<num>]\n" + " [-S|--logsize=<bytes>] [-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++) + { + const char *pcszDescr; + switch (g_aOptions[i].iShort) + { + case 'h': + pcszDescr = "Prints 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; + + case 'V': + pcszDescr = "Shows the service version."; + break; + + default: + AssertFailedBreakStmt(pcszDescr = ""); + } + + if (g_aOptions[i].iShort < 1000) + RTStrmPrintf(g_pStdErr, + " %s, -%c\n" + " %s\n", g_aOptions[i].pszLong, g_aOptions[i].iShort, pcszDescr); + else + RTStrmPrintf(g_pStdErr, + " %s\n" + " %s\n", g_aOptions[i].pszLong, pcszDescr); + } + + RTStrmPrintf(g_pStdErr, + "\n" + "Use 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': + showHelp(argv[0]); + return RTEXITCODE_SUCCESS; + + case 'v': + g_cVerbosity++; + break; + +#ifdef VBOXAUTOSTART_DAEMONIZE + case 'b': + g_fDaemonize = true; + break; +#endif + case 'V': + autostartSvcShowVersion(false); + return RTEXITCODE_SUCCESS; + + 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) + { + showHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Either --start or --stop must be present"); + } + else if (fStart && fStop) + { + showHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "--start or --stop are mutually exclusive"); + } + + if (!pszConfigFile) + { + showHelp(argv[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "--config <config file> is missing"); + } + + if (!fQuiet) + autostartSvcShowHeader(); + + PCFGAST pCfgAst = NULL; + char *pszUser = NULL; + PCFGAST pCfgAstUser = NULL; + PCFGAST pCfgAstPolicy = NULL; + PCFGAST pCfgAstUserHome = 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'"); + } + pCfgAstUserHome = autostartConfigAstGetByName(pCfgAstUser, "VBOX_USER_HOME"); + if ( pCfgAstUserHome + && pCfgAstUserHome->enmType == CFGASTNODETYPE_KEYVALUE) + { + rc = RTEnvSet("VBOX_USER_HOME", pCfgAstUserHome->u.KeyValue.aszValue); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "'VBOX_USER_HOME' could not be set for this user"); + } + } + 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; + + if (fStart) + rc = autostartStartMain(pCfgAstUser); + else + { + Assert(fStop); + rc = autostartStopMain(pCfgAstUser); + } + + autostartConfigAstDestroy(pCfgAst); + NativeEventQueue::getMainEventQueue()->processEventQueue(0); + autostartShutdown(); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + |