diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/x11/VBoxClient/main.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-c31cb7e733ae8e33d73383e04db264d4293f0b1a.tar.xz virtualbox-c31cb7e733ae8e33d73383e04db264d4293f0b1a.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/main.cpp')
-rw-r--r-- | src/VBox/Additions/x11/VBoxClient/main.cpp | 885 |
1 files changed, 885 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/main.cpp b/src/VBox/Additions/x11/VBoxClient/main.cpp new file mode 100644 index 00000000..f276223a --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/main.cpp @@ -0,0 +1,885 @@ +/* $Id: main.cpp $ */ +/** @file + * VirtualBox Guest Additions - X11 Client. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/wait.h> +#include <stdlib.h> /* For exit */ +#include <signal.h> +#include <X11/Xlib.h> +#include "product-generated.h" +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/critsect.h> +#include <iprt/errno.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/env.h> +#include <iprt/process.h> +#include <iprt/linux/sysfs.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include "VBoxClient.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +#define VBOXCLIENT_OPT_SERVICES 980 +#define VBOXCLIENT_OPT_CHECKHOSTVERSION VBOXCLIENT_OPT_SERVICES +#define VBOXCLIENT_OPT_CLIPBOARD VBOXCLIENT_OPT_SERVICES + 1 +#define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2 +#define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3 +#define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4 +#define VBOXCLIENT_OPT_VMSVGA_SESSION VBOXCLIENT_OPT_SERVICES + 5 +#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 6 + + +/********************************************************************************************************************************* +* Local structures * +*********************************************************************************************************************************/ +/** + * The global service state. + */ +typedef struct VBCLSERVICESTATE +{ + /** Pointer to the service descriptor. */ + PVBCLSERVICE pDesc; + /** The worker thread. NIL_RTTHREAD if it's the main thread. */ + RTTHREAD Thread; + /** Whether Pre-init was called. */ + bool fPreInited; + /** Shutdown indicator. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the service was started or not. */ + bool fStarted; +} VBCLSERVICESTATE; +/** Pointer to a service state. */ +typedef VBCLSERVICESTATE *PVBCLSERVICESTATE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The global service state. */ +VBCLSERVICESTATE g_Service = { 0 }; + +/** Set by the signal handler when being called. */ +static volatile bool g_fSignalHandlerCalled = false; +/** Critical section for the signal handler. */ +static RTCRITSECT g_csSignalHandler; +/** Flag indicating Whether the service starts in daemonized mode or not. */ +bool g_fDaemonized = false; +/** The name of our pidfile. It is global for the benefit of the cleanup + * routine. */ +static char g_szPidFile[RTPATH_MAX] = ""; +/** The file handle of our pidfile. It is global for the benefit of the + * cleanup routine. */ +static RTFILE g_hPidFile; +/** The name of pidfile for parent (control) process. */ +static char g_szControlPidFile[RTPATH_MAX] = ""; +/** The file handle of parent process pidfile. */ +static RTFILE g_hControlPidFile; + +/** Global critical section held during the clean-up routine (to prevent it + * being called on multiple threads at once) or things which may not happen + * during clean-up (e.g. pausing and resuming the service). + */ +static RTCRITSECT g_critSect; +/** Counter of how often our daemon has been respawned. */ +unsigned g_cRespawn = 0; +/** Logging verbosity level. */ +unsigned g_cVerbosity = 0; +/** Absolute path to log file, if any. */ +static char g_szLogFile[RTPATH_MAX + 128] = ""; +/** Set by the signal handler when SIGUSR1 received. */ +static volatile bool g_fProcessReloadRequested = false; + +/** + * Tries to determine if the session parenting this process is of Xwayland. + * NB: XDG_SESSION_TYPE is a systemd(1) environment variable and is unlikely + * set in non-systemd environments or remote logins. + * Therefore we check the Wayland specific display environment variable first. + */ +bool VBClHasWayland(void) +{ + const char *const pDisplayType = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY); + const char *pSessionType; + + if (pDisplayType != NULL) + return true; + + pSessionType = RTEnvGet(VBCL_ENV_XDG_SESSION_TYPE); + if ((pSessionType != NULL) && (RTStrIStartsWith(pSessionType, "wayland"))) + return true; + + return false; +} + +/** + * Shut down if we get a signal or something. + * + * This is extern so that we can call it from other compilation units. + */ +void VBClShutdown(bool fExit /*=true*/) +{ + /* We never release this, as we end up with a call to exit(3) which is not + * async-safe. Unless we fix this application properly, we should be sure + * never to exit from anywhere except from this method. */ + int rc = RTCritSectEnter(&g_critSect); + if (RT_FAILURE(rc)) + VBClLogFatalError("Failure while acquiring the global critical section, rc=%Rrc\n", rc); + + /* Ask service to stop. */ + if (g_Service.pDesc && + g_Service.pDesc->pfnStop) + { + ASMAtomicWriteBool(&g_Service.fShutdown, true); + g_Service.pDesc->pfnStop(); + + } + + if (g_szPidFile[0] && g_hPidFile) + VbglR3ClosePidFile(g_szPidFile, g_hPidFile); + + VBClLogDestroy(); + + if (fExit) + exit(RTEXITCODE_SUCCESS); +} + +/** + * Xlib error handler for certain errors that we can't avoid. + */ +static int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError) +{ + char errorText[1024]; + + XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText)); + VBClLogError("An X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n", errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial); + return 0; +} + +/** + * Xlib error handler for fatal errors. This often means that the programme is still running + * when X exits. + */ +static int vboxClientXLibIOErrorHandler(Display *pDisplay) +{ + RT_NOREF1(pDisplay); + VBClLogError("A fatal guest X Window error occurred. This may just mean that the Window system was shut down while the client was still running\n"); + VBClShutdown(); + return 0; /* We should never reach this. */ +} + +/** + * A standard signal handler which cleans up and exits. + */ +static void vboxClientSignalHandler(int iSignal) +{ + int rc = RTCritSectEnter(&g_csSignalHandler); + if (RT_SUCCESS(rc)) + { + if (g_fSignalHandlerCalled) + { + RTCritSectLeave(&g_csSignalHandler); + return; + } + + VBClLogVerbose(2, "Received signal %d\n", iSignal); + g_fSignalHandlerCalled = true; + + /* In our internal convention, when VBoxClient process receives SIGUSR1, + * this is a trigger for restarting a process with exec() call. Usually + * happens as a result of Guest Additions update in order to seamlessly + * restart newly installed binaries. */ + if (iSignal == SIGUSR1) + g_fProcessReloadRequested = true; + + /* Leave critical section before stopping the service. */ + RTCritSectLeave(&g_csSignalHandler); + + if ( g_Service.pDesc + && g_Service.pDesc->pfnStop) + { + VBClLogVerbose(2, "Notifying service to stop ...\n"); + + /* Signal the service to stop. */ + ASMAtomicWriteBool(&g_Service.fShutdown, true); + + g_Service.pDesc->pfnStop(); + + VBClLogVerbose(2, "Service notified to stop, waiting on worker thread to stop ...\n"); + } + } +} + +/** + * Reset all standard termination signals to call our signal handler. + */ +static int vboxClientSignalHandlerInstall(void) +{ + struct sigaction sigAction; + sigAction.sa_handler = vboxClientSignalHandler; + sigemptyset(&sigAction.sa_mask); + sigAction.sa_flags = 0; + sigaction(SIGHUP, &sigAction, NULL); + sigaction(SIGINT, &sigAction, NULL); + sigaction(SIGQUIT, &sigAction, NULL); + sigaction(SIGPIPE, &sigAction, NULL); + sigaction(SIGALRM, &sigAction, NULL); + sigaction(SIGTERM, &sigAction, NULL); + sigaction(SIGUSR1, &sigAction, NULL); + sigaction(SIGUSR2, &sigAction, NULL); + + return RTCritSectInit(&g_csSignalHandler); +} + +/** + * Uninstalls a previously installed signal handler. + */ +static int vboxClientSignalHandlerUninstall(void) +{ + signal(SIGTERM, SIG_DFL); +#ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +#endif + + return RTCritSectDelete(&g_csSignalHandler); +} + +/** + * Print out a usage message and exit with success. + */ +static void vboxClientUsage(const char *pcszFileName) +{ + RTPrintf(VBOX_PRODUCT " VBoxClient " + VBOX_VERSION_STRING "\n" + "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + + RTPrintf("Usage: %s " +#ifdef VBOX_WITH_SHARED_CLIPBOARD + "--clipboard|" +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + "--draganddrop|" +#endif +#ifdef VBOX_WITH_GUEST_PROPS + "--checkhostversion|" +#endif +#ifdef VBOX_WITH_SEAMLESS + "--seamless|" +#endif +#ifdef VBOX_WITH_VMSVGA + "--vmsvga|" + "--vmsvga-session" +#endif + "\n[-d|--nodaemon]\n", pcszFileName); + RTPrintf("\n"); + RTPrintf("Options:\n"); +#ifdef VBOX_WITH_SHARED_CLIPBOARD + RTPrintf(" --clipboard starts the shared clipboard service\n"); +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + RTPrintf(" --draganddrop starts the drag and drop service\n"); +#endif +#ifdef VBOX_WITH_GUEST_PROPS + RTPrintf(" --checkhostversion starts the host version notifier service\n"); +#endif +#ifdef VBOX_WITH_SEAMLESS + RTPrintf(" --seamless starts the seamless windows service\n"); +#endif +#ifdef VBOX_WITH_VMSVGA + RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for X11/Wayland guests\n"); +#ifdef RT_OS_LINUX + RTPrintf(" --vmsvga-session starts Desktop Environment specific screen assistant for X11/Wayland guests\n" + " (VMSVGA graphics adapter only)\n"); +#else + RTPrintf(" --vmsvga-session an alias for --vmsvga\n"); +#endif + RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n"); +#endif + RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n"); + RTPrintf(" -d, --nodaemon continues running as a system service\n"); + RTPrintf(" -h, --help shows this help text\n"); + RTPrintf(" -l, --logfile <path> enables logging to a file\n"); + RTPrintf(" -v, --verbose increases logging verbosity level\n"); + RTPrintf(" -V, --version shows version information\n"); + RTPrintf("\n"); +} + +/** + * Complains about seeing more than one service specification. + * + * @returns RTEXITCODE_SYNTAX. + */ +static int vbclSyntaxOnlyOneService(void) +{ + RTMsgError("More than one service specified! Only one, please."); + return RTEXITCODE_SYNTAX; +} + +/** + * The service thread. + * + * @returns Whatever the worker function returns. + * @param ThreadSelf My thread handle. + * @param pvUser The service index. + */ +static DECLCALLBACK(int) vbclThread(RTTHREAD ThreadSelf, void *pvUser) +{ + PVBCLSERVICESTATE pState = (PVBCLSERVICESTATE)pvUser; + AssertPtrReturn(pState, VERR_INVALID_POINTER); + +#ifndef RT_OS_WINDOWS + /* + * Block all signals for this thread. Only the main thread will handle signals. + */ + sigset_t signalMask; + sigfillset(&signalMask); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); +#endif + + AssertPtrReturn(pState->pDesc->pfnWorker, VERR_INVALID_POINTER); + int rc = pState->pDesc->pfnWorker(&pState->fShutdown); + + VBClLogVerbose(2, "Worker loop ended with %Rrc\n", rc); + + ASMAtomicXchgBool(&pState->fShutdown, true); + RTThreadUserSignal(ThreadSelf); + return rc; +} + +/** + * Wait for SIGUSR1 and re-exec. + */ +static void vbclHandleUpdateStarted(char *const argv[]) +{ + /* Context of parent process */ + sigset_t signalMask; + int iSignal; + int rc; + + /* Release reference to guest driver. */ + VbglR3Term(); + + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGUSR1); + rc = pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + + if (rc == 0) + { + LogRel(("%s: waiting for Guest Additions installation to be completed\n", + g_Service.pDesc->pszDesc)); + + /* Wait for SIGUSR1. */ + rc = sigwait(&signalMask, &iSignal); + if (rc == 0) + { + LogRel(("%s: Guest Additions installation to be completed, reloading service\n", + g_Service.pDesc->pszDesc)); + + /* Release pidfile, otherwise new VBoxClient instance won't be able to quire it. */ + VBClShutdown(false); + + rc = RTProcCreate(argv[0], argv, RTENV_DEFAULT, + RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL); + if (RT_SUCCESS(rc)) + LogRel(("%s: service restarted\n", g_Service.pDesc->pszDesc)); + else + LogRel(("%s: cannot replace running image with %s (%s), automatic service reloading has failed\n", + g_Service.pDesc->pszDesc, argv[0], strerror(errno))); + } + else + LogRel(("%s: cannot wait for signal (%s), automatic service reloading has failed\n", + g_Service.pDesc->pszDesc, strerror(errno))); + } + else + LogRel(("%s: failed to setup signal handler, automatic service reloading has failed\n", + g_Service.pDesc->pszDesc)); + + exit(RT_BOOL(rc != 0)); +} + +/** + * Compose pidfile name. + * + * @returns IPRT status code. + * @param szBuf Buffer to store pidfile name into. + * @param cbBuf Size of buffer. + * @param szTemplate Null-terminated string which contains pidfile name. + * @param fParentProcess Whether pidfile path should be composed for + * parent (control) process or for a child (actual + * service) process. + */ +static int vbclGetPidfileName(char *szBuf, size_t cbBuf, const char *szTemplate, + bool fParentProcess) +{ + int rc; + char pszActiveTTY[128]; + size_t cchRead; + + RT_ZERO(pszActiveTTY); + + AssertPtrReturn(szBuf, VERR_INVALID_PARAMETER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(szTemplate, VERR_INVALID_PARAMETER); + + rc = RTPathUserHome(szBuf, cbBuf); + if (RT_FAILURE(rc)) + VBClLogFatalError("%s: getting home directory failed: %Rrc\n", + g_Service.pDesc->pszDesc, rc); + + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szBuf, cbBuf, szTemplate); + +#ifdef RT_OS_LINUX + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */, + &cchRead, "class/tty/tty0/active"); + if (RT_SUCCESS(rc)) + { + RTStrCat(szBuf, cbBuf, "-"); + RTStrCat(szBuf, cbBuf, pszActiveTTY); + } + else + VBClLogInfo("%s: cannot detect currently active tty device, " + "multiple service instances for a single user will not be allowed, rc=%Rrc", + g_Service.pDesc->pszDesc, rc); +#endif /* RT_OS_LINUX */ + + if (RT_SUCCESS(rc)) + RTStrCat(szBuf, cbBuf, fParentProcess ? "-control.pid" : "-service.pid"); + + if (RT_FAILURE(rc)) + VBClLogFatalError("%s: reating PID file path failed: %Rrc\n", + g_Service.pDesc->pszDesc, rc); + + return rc; +} + +/** + * The main loop for the VBoxClient daemon. + */ +int main(int argc, char *argv[]) +{ + /* Note: No VBClLogXXX calls before actually creating the log. */ + + /* Initialize our runtime before all else. */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* A flag which is returned to the parent process when Guest Additions update started. */ + bool fUpdateStarted = false; + + /* This should never be called twice in one process - in fact one Display + * object should probably never be used from multiple threads anyway. */ + if (!XInitThreads()) + return RTMsgErrorExitFailure("Failed to initialize X11 threads\n"); + + /* Get our file name for usage info and hints. */ + const char *pcszFileName = RTPathFilename(argv[0]); + if (!pcszFileName) + pcszFileName = "VBoxClient"; + + /* Parse our option(s). */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--nodaemon", 'd', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--logfile", 'l', RTGETOPT_REQ_STRING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + + /* Services */ +#ifdef VBOX_WITH_GUEST_PROPS + { "--checkhostversion", VBOXCLIENT_OPT_CHECKHOSTVERSION, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD + { "--clipboard", VBOXCLIENT_OPT_CLIPBOARD, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + { "--draganddrop", VBOXCLIENT_OPT_DRAGANDDROP, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_SEAMLESS + { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_VMSVGA + { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING }, + { "--vmsvga-session", VBOXCLIENT_OPT_VMSVGA_SESSION, RTGETOPT_REQ_NOTHING }, + { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING }, +#endif + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to parse command line options, rc=%Rrc\n", rc); + + AssertRC(rc); + + bool fDaemonise = true; + bool fRespawn = true; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'd': + { + fDaemonise = false; + break; + } + + case 'h': + { + vboxClientUsage(pcszFileName); + return RTEXITCODE_SUCCESS; + } + + case 'f': + { + fDaemonise = false; + fRespawn = false; + break; + } + + case 'l': + { + rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Unable to set log file path, rc=%Rrc\n", rc); + break; + } + + case 'n': + { + fRespawn = false; + break; + } + + case 'v': + { + g_cVerbosity++; + break; + } + + case 'V': + { + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; + } + + /* Services */ +#ifdef VBOX_WITH_GUEST_PROPS + case VBOXCLIENT_OPT_CHECKHOSTVERSION: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcHostVersion; + break; + } +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD + case VBOXCLIENT_OPT_CLIPBOARD: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcClipboard; + break; + } +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + case VBOXCLIENT_OPT_DRAGANDDROP: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDragAndDrop; + break; + } +#endif +#ifdef VBOX_WITH_SEAMLESS + case VBOXCLIENT_OPT_SEAMLESS: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcSeamless; + break; + } +#endif +#ifdef VBOX_WITH_VMSVGA + case VBOXCLIENT_OPT_VMSVGA: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDisplaySVGA; + break; + } + + case VBOXCLIENT_OPT_VMSVGA_SESSION: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); +# ifdef RT_OS_LINUX + g_Service.pDesc = &g_SvcDisplaySVGASession; +# else + g_Service.pDesc = &g_SvcDisplaySVGA; +# endif + break; + } + + case VBOXCLIENT_OPT_DISPLAY: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDisplayLegacy; + break; + } +#endif + case VINF_GETOPT_NOT_OPTION: + break; + + case VERR_GETOPT_UNKNOWN_OPTION: + RT_FALL_THROUGH(); + default: + { + if ( g_Service.pDesc + && g_Service.pDesc->pfnOption) + { + rc = g_Service.pDesc->pfnOption(NULL, argc, argv, &GetState.iNext); + } + else /* No service specified yet. */ + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + { + RTMsgError("unrecognized option '%s'", ValueUnion.psz); + RTMsgInfo("Try '%s --help' for more information", pcszFileName); + return RTEXITCODE_SYNTAX; + } + break; + } + + } /* switch */ + } /* while RTGetOpt */ + + if (!g_Service.pDesc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No service specified. Quitting because nothing to do!"); + + /* Initialize VbglR3 before we do anything else with the logger. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc); + + rc = VBClLogCreate(g_szLogFile[0] ? g_szLogFile : ""); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to create release log '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + if (!fDaemonise) + { + /* If the user is running in "no daemon" mode, send critical logging to stdout as well. */ + PRTLOGGER pReleaseLog = RTLogRelGetDefaultInstance(); + if (pReleaseLog) + { + rc = RTLogDestinations(pReleaseLog, "stdout"); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to redivert error output, rc=%Rrc", rc); + } + } + + VBClLogInfo("VBoxClient %s r%s started. Verbose level = %d. Wayland environment detected: %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBClHasWayland() ? "yes" : "no"); + VBClLogInfo("Service: %s\n", g_Service.pDesc->pszDesc); + + rc = RTCritSectInit(&g_critSect); + if (RT_FAILURE(rc)) + VBClLogFatalError("Initializing critical section failed: %Rrc\n", rc); + if (g_Service.pDesc->pszPidFilePathTemplate) + { + /* Get pidfile name for parent (control) process. */ + rc = vbclGetPidfileName(g_szControlPidFile, sizeof(g_szControlPidFile), g_Service.pDesc->pszPidFilePathTemplate, true); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* Get pidfile name for service process. */ + rc = vbclGetPidfileName(g_szPidFile, sizeof(g_szPidFile), g_Service.pDesc->pszPidFilePathTemplate, false); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + } + + if (fDaemonise) + { + rc = VbglR3DaemonizeEx(false /* fNoChDir */, false /* fNoClose */, fRespawn, &g_cRespawn, + true /* fReturnOnUpdate */, &fUpdateStarted, g_szControlPidFile, &g_hControlPidFile); + /* This combination only works in context of parent process. */ + if (RT_SUCCESS(rc) && fUpdateStarted) + vbclHandleUpdateStarted(argv); + } + + if (RT_FAILURE(rc)) + VBClLogFatalError("Daemonizing service failed: %Rrc\n", rc); + + if (g_szPidFile[0]) + { + rc = VbglR3PidFile(g_szPidFile, &g_hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */ + { + VBClLogInfo("%s: service already running, exitting\n", + g_Service.pDesc->pszDesc); + return RTEXITCODE_SUCCESS; + } + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Creating PID file %s failed: %Rrc\n", g_szPidFile, rc); + return RTEXITCODE_FAILURE; + } + } + +#ifndef VBOXCLIENT_WITHOUT_X11 + /* Set an X11 error handler, so that we don't die when we get unavoidable + * errors. */ + XSetErrorHandler(vboxClientXLibErrorHandler); + /* Set an X11 I/O error handler, so that we can shutdown properly on + * fatal errors. */ + XSetIOErrorHandler(vboxClientXLibIOErrorHandler); +#endif + + bool fSignalHandlerInstalled = false; + if (RT_SUCCESS(rc)) + { + rc = vboxClientSignalHandlerInstall(); + if (RT_SUCCESS(rc)) + fSignalHandlerInstalled = true; + } + + if ( RT_SUCCESS(rc) + && g_Service.pDesc->pfnInit) + { + VBClLogInfo("Initializing service ...\n"); + rc = g_Service.pDesc->pfnInit(); + } + + if (RT_SUCCESS(rc)) + { + VBClLogInfo("Creating worker thread ...\n"); + rc = RTThreadCreate(&g_Service.Thread, vbclThread, (void *)&g_Service, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_Service.pDesc->pszName); + if (RT_FAILURE(rc)) + { + VBClLogError("Creating worker thread failed, rc=%Rrc\n", rc); + } + else + { + g_Service.fStarted = true; + + /* Wait for the thread to initialize. */ + /** @todo There is a race between waiting and checking + * the fShutdown flag of a thread here and processing + * the thread's actual worker loop. If the thread decides + * to exit the loop before we skipped the fShutdown check + * below the service will fail to start! */ + /** @todo This presumably means either a one-shot service or that + * something has gone wrong. In the second case treating it as failure + * to start is probably right, so we need a way to signal the first + * rather than leaving the idle thread hanging around. A flag in the + * service description? */ + RTThreadUserWait(g_Service.Thread, RT_MS_1MIN); + if (g_Service.fShutdown) + { + VBClLogError("Service failed to start!\n"); + rc = VERR_GENERAL_FAILURE; + } + else + { + VBClLogInfo("Service started\n"); + + int rcThread; + rc = RTThreadWait(g_Service.Thread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + + if (RT_FAILURE(rc)) + VBClLogError("Waiting on worker thread to stop failed, rc=%Rrc\n", rc); + + if (g_Service.pDesc->pfnTerm) + { + VBClLogInfo("Terminating service\n"); + + int rc2 = g_Service.pDesc->pfnTerm(); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_SUCCESS(rc)) + { + VBClLogInfo("Service terminated\n"); + } + else + VBClLogError("Service failed to terminate, rc=%Rrc\n", rc); + } + } + } + } + + if (RT_FAILURE(rc)) + { + if (rc == VERR_NOT_AVAILABLE) + VBClLogInfo("Service is not availabe, skipping\n"); + else if (rc == VERR_NOT_SUPPORTED) + VBClLogInfo("Service is not supported on this platform, skipping\n"); + else + VBClLogError("Service ended with error %Rrc\n", rc); + } + else + VBClLogVerbose(2, "Service ended\n"); + + if (fSignalHandlerInstalled) + { + int rc2 = vboxClientSignalHandlerUninstall(); + AssertRC(rc2); + } + + VBClShutdown(false /*fExit*/); + + /** @todo r=andy Should we return an appropriate exit code if the service failed to init? + * Must be tested carefully with our init scripts first. */ + return g_fProcessReloadRequested ? VBGLR3EXITCODERELOAD : RTEXITCODE_SUCCESS; +} + |