VBoxService.cpp revision 30b809b78e39766d5be596d604cea20a12357e04
/* $Id$ */
/** @file
* VBoxService - Guest Additions Service Skeleton.
*/
/*
* Copyright (C) 2007-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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 *
*******************************************************************************/
/** @todo LOG_GROUP*/
#ifndef _MSC_VER
# include <unistd.h>
#endif
#include <errno.h>
#ifndef RT_OS_WINDOWS
# include <signal.h>
# ifdef RT_OS_OS2
# define pthread_sigmask sigprocmask
# endif
#endif
#ifdef RT_OS_FREEBSD
# include <pthread.h>
#endif
#include <package-generated.h>
#include "product-generated.h"
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
#ifdef DEBUG
# include <iprt/memtracker.h>
#endif
#include <iprt/semaphore.h>
#include "VBoxServiceInternal.h"
#ifdef VBOX_WITH_GUEST_CONTROL
# include "VBoxServiceControl.h"
#endif
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** The program name (derived from argv[0]). */
char *g_pszProgName = (char *)"";
/** The current verbosity level. */
int g_cVerbosity = 0;
/** Logging parameters. */
/** @todo Make this configurable later. */
/** Critical section for (debug) logging. */
#ifdef DEBUG
#endif
/** The default service interval (the -i | --interval) option). */
#ifdef RT_OS_WINDOWS
/** Signal shutdown to the Windows service thread. */
static bool volatile g_fWindowsServiceShutdown;
/** Event the Windows service thread waits for shutdown. */
static RTSEMEVENT g_hEvtWindowsService;
#endif
/**
* The details of the services that has been compiled in.
*/
static struct
{
/** Pointer to the service descriptor. */
/** The worker thread. NIL_RTTHREAD if it's the main 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;
/** Whether the service is enabled or not. */
bool fEnabled;
} g_aServices[] =
{
#ifdef VBOXSERVICE_CONTROL
{ &g_Control, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_TIMESYNC
{ &g_TimeSync, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_CLIPBOARD
{ &g_Clipboard, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_VMINFO
{ &g_VMInfo, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_CPUHOTPLUG
{ &g_CpuHotPlug, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_MANAGEMENT
# ifdef VBOX_WITH_MEMBALLOON
{ &g_MemBalloon, NIL_RTTHREAD, false, false, false, false, true },
# endif
{ &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true },
#endif
#if defined(VBOXSERVICE_PAGE_SHARING)
{ &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOX_WITH_SHARED_FOLDERS
{ &g_AutoMount, NIL_RTTHREAD, false, false, false, false, true },
#endif
#ifdef VBOXSERVICE_WITH_DISPLAY
{ &g_Display, NIL_RTTHREAD, false, false, false, false, true },
#endif
};
/* Default call-backs for services which do not need special behaviour. */
/** @copydoc VBOXSERVICE::pfnPreInit */
DECLCALLBACK(int) VBoxServiceDefaultPreInit(void)
{
return VINF_SUCCESS;
}
/** @copydoc VBOXSERVICE::pfnOption */
{
return -1;
}
/** @copydoc VBOXSERVICE::pfnInit */
DECLCALLBACK(int) VBoxServiceDefaultInit(void)
{
return VINF_SUCCESS;
}
/** @copydoc VBOXSERVICE::pfnTerm */
DECLCALLBACK(void) VBoxServiceDefaultTerm(void)
{
return;
}
/**
* Release logger callback.
*
* @return IPRT status code.
* @param pLoggerRelease
* @param enmPhase
* @param pfnLog
*/
static void VBoxServiceLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
{
/* Some introductory information. */
static RTTIMESPEC s_TimeSpec;
char szTmp[256];
if (enmPhase == RTLOGPHASE_BEGIN)
switch (enmPhase)
{
case RTLOGPHASE_BEGIN:
{
"VBoxService %s r%s (verbosity: %d) %s (%s %s) release log\n"
"Log opened %s\n",
/* the package type is interesting for Linux distributions */
char szExecName[RTPATH_MAX];
"Executable: %s\n"
"Process ID: %u\n"
"Package type: %s"
#ifdef VBOX_OSE
" (OSE)"
#endif
"\n",
RTProcSelf(),
break;
}
case RTLOGPHASE_PREROTATE:
break;
case RTLOGPHASE_POSTROTATE:
break;
case RTLOGPHASE_END:
break;
default:
/* nothing */;
}
}
/**
* Creates the default release logger outputting to the specified file.
* Pass NULL for disabled logging.
*
* @return IPRT status code.
* @param pszLogFile Filename for log output. Optional.
*/
int VBoxServiceLogCreate(const char *pszLogFile)
{
/* Create release logger (stdout + file). */
static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
#endif
#ifdef DEBUG
"VBOXSERVICE_LOG",
#else
"VBOXSERVICE_RELEASE_LOG",
#endif
if (RT_SUCCESS(rc))
{
/* register this logger as the release logger */
/* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
}
return rc;
}
void VBoxServiceLogDestroy(void)
{
}
/**
* Displays the program usage message.
*
* @returns 1.
*/
static int vboxServiceUsage(void)
{
RTPrintf("Usage:\n"
" %-12s [-f|--foreground] [-v|--verbose] [-l|--logfile <file>]\n"
" [-i|--interval <seconds>]\n"
" [--disable-<service>] [--enable-<service>]\n"
" [--only-<service>] [-h|-?|--help]\n", g_pszProgName);
#ifdef RT_OS_WINDOWS
RTPrintf(" [-r|--register] [-u|--unregister]\n");
#endif
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
RTPrintf("\n"
"Options:\n"
" -i | --interval The default interval.\n"
" -f | --foreground Don't daemonize the program. For debugging.\n"
" -l | --logfile <file> Enables logging to a file.\n"
" -v | --verbose Increment the verbosity level. For debugging.\n"
" -V | --version Show version information.\n"
" -h | -? | --help Show this message and exit with status 1.\n"
);
#ifdef RT_OS_WINDOWS
RTPrintf(" -r | --register Installs the service.\n"
" -u | --unregister Uninstall service.\n");
#endif
RTPrintf("\n"
"Service-specific options:\n");
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
{
RTPrintf(" --enable-%-14s Enables the %s service. (default)\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
RTPrintf(" --disable-%-13s Disables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
RTPrintf(" --only-%-16s Only enables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
}
RTPrintf("\n"
return 1;
}
/**
* Displays an error message.
*
* @returns RTEXITCODE_FAILURE.
* @param pszFormat The message text.
* @param ... Format arguments.
*/
{
return RTEXITCODE_FAILURE;
}
/**
* Displays a verbose message.
*
* @param iLevel Minimum log level required to display this message.
* @param pszFormat The message text.
* @param ... Format arguments.
*/
{
if (iLevel <= g_cVerbosity)
{
#ifdef DEBUG
if (RT_SUCCESS(rc))
{
#endif
#ifdef DEBUG
}
#endif
}
}
/**
* Reports the current VBoxService status to the host.
*
* This makes sure that the Failed state is sticky.
*
* @return IPRT status code.
* @param enmStatus Status to report to the host.
*/
{
/*
* VBoxGuestFacilityStatus_Failed is sticky.
*/
{
enmStatus, 0 /* Flags */);
if (RT_FAILURE(rc))
{
return rc;
}
}
return VINF_SUCCESS;
}
/**
* Gets a 32-bit value argument.
* @todo Get rid of this and VBoxServiceArgString() as soon as we have RTOpt handling.
*
* @returns 0 on success, non-zero exit code on error.
* @param argc The argument count.
* @param argv The argument vector
* @param psz Where in *pi to start looking for the value argument.
* @param pi Where to find and perhaps update the argument index.
* @param pu32 Where to store the 32-bit value.
* @param u32Min The minimum value.
* @param u32Max The maximum value.
*/
int VBoxServiceArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max)
{
psz++;
if (!*psz)
{
}
char *pszNext;
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The timesync interval of %RU32 seconds is out of range [%RU32..%RU32]\n",
return 0;
}
/** @todo Get rid of this and VBoxServiceArgUInt32() as soon as we have RTOpt handling. */
int VBoxServiceArgString(int argc, char **argv, const char *psz, int *pi, char *pszBuf, size_t cbBuf)
{
psz++;
if (!*psz)
{
}
return 0;
}
/**
* The service thread.
*
* @returns Whatever the worker function returns.
* @param ThreadSelf My thread handle.
* @param pvUser The service index.
*/
{
#ifndef RT_OS_WINDOWS
/*
* Block all signals for this thread. Only the main thread will handle signals.
*/
#endif
return rc;
}
/**
* Lazily calls the pfnPreInit method on each service.
*
* @returns VBox status code, error message displayed.
*/
static RTEXITCODE vboxServiceLazyPreInit(void)
{
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
if (!g_aServices[j].fPreInited)
{
if (RT_FAILURE(rc))
g_aServices[j].fPreInited = true;
}
return RTEXITCODE_SUCCESS;
}
/**
* Count the number of enabled services.
*/
static unsigned vboxServiceCountEnabledServices(void)
{
unsigned cEnabled = 0;
for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
return cEnabled;
}
#ifdef RT_OS_WINDOWS
{
int rc = VINF_SUCCESS;
bool fEventHandled = FALSE;
switch (dwCtrlType)
{
/* User pressed CTRL+C or CTRL+BREAK or an external event was sent
* via GenerateConsoleCtrlEvent(). */
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_C_EVENT:
break;
default:
break;
/** @todo Add other events here. */
}
if (RT_FAILURE(rc))
VBoxServiceError("ControlHandler: Event %ld handled with error rc=%Rrc\n",
dwCtrlType, rc);
return fEventHandled;
}
#endif /* RT_OS_WINDOWS */
/**
* Starts the service.
*
* @returns VBox status code, errors are fully bitched.
*/
int VBoxServiceStartServices(void)
{
int rc;
/*
* Initialize the services.
*/
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
if (g_aServices[j].fEnabled)
{
if (RT_FAILURE(rc))
{
if (rc != VERR_SERVICE_DISABLED)
{
VBoxServiceError("Service '%s' failed to initialize: %Rrc\n",
return rc;
}
g_aServices[j].fEnabled = false;
VBoxServiceVerbose(0, "Service '%s' was disabled because of missing functionality\n",
}
}
/*
* Start the service(s).
*/
rc = VINF_SUCCESS;
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
{
if (!g_aServices[j].fEnabled)
continue;
if (RT_FAILURE(rc))
{
break;
}
g_aServices[j].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? */
if (g_aServices[j].fShutdown)
{
}
}
if (RT_SUCCESS(rc))
else
{
VBoxServiceError("An error occcurred while the services!\n");
}
return rc;
}
/**
* Stops and terminates the services.
*
* This should be called even when VBoxServiceStartServices fails so it can
* clean up anything that we succeeded in starting.
*/
int VBoxServiceStopServices(void)
{
/*
* Signal all the services.
*/
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
/*
* Do the pfnStop callback on all running services.
*/
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
if (g_aServices[j].fStarted)
{
VBoxServiceVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName);
}
/*
* Wait for all the service threads to complete.
*/
int rc = VINF_SUCCESS;
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
{
continue;
{
int rc2 = VINF_SUCCESS;
for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */
{
if (RT_SUCCESS(rc2))
break;
#ifdef RT_OS_WINDOWS
/* Notify SCM that it takes a bit longer ... */
VBoxServiceWinSetStopPendingStatus(i + j*32);
#endif
}
if (RT_FAILURE(rc2))
{
}
}
}
#ifdef RT_OS_WINDOWS
/*
* Wake up and tell the main() thread that we're shutting down (it's
* sleeping in VBoxServiceMainWait).
*/
if (g_hEvtWindowsService != NIL_RTSEMEVENT)
{
}
#endif
VBoxServiceReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed);
return rc;
}
/**
* Block the main thread until the service shuts down.
*/
void VBoxServiceMainWait(void)
{
int rc;
#ifdef RT_OS_WINDOWS
/*
* Wait for the semaphore to be signalled.
*/
while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown))
{
}
#else
/*
* Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking
* all important signals.
*
* sigwait returns when we receive a SIGCHLD. Kind of makes sense since
*/
int iSignal;
do
{
iSignal = -1;
}
# ifdef ERESTART
# endif
);
#endif /* !RT_OS_WINDOWS */
}
{
/*
* Init globals and such.
*/
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
#ifdef DEBUG
#endif
#ifdef VBOXSERVICE_TOOLBOX
/*
* Run toolbox code before all other stuff since these things are simpler
* global mutex restrictions.
*/
return rcExit;
#endif
bool fUserSession = false;
#ifdef VBOX_WITH_GUEST_CONTROL
/*
* Check if we're the specially spawned VBoxService.exe process that
* handles a guest control session.
*/
if ( argc >= 2
fUserSession = true;
#endif
/*
* Connect to the kernel part before daemonizing so we can fail and
* complain if there is some kind of problem. We need to initialize the
* guest lib *before* we do the pre-init just in case one of services needs
* do to some initial stuff with it.
*/
if (fUserSession)
rc = VbglR3InitUser();
else
rc = VbglR3Init();
if (RT_FAILURE(rc))
{
if (rc == VERR_ACCESS_DENIED)
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n",
}
#ifdef RT_OS_WINDOWS
/*
* Check if we're the specially spawned VBoxService.exe process that
* handles page fusion. This saves an extra executable.
*/
if ( argc == 2
return VBoxServicePageSharingInitFork();
#endif
#ifdef VBOX_WITH_GUEST_CONTROL
/*
* Check if we're the specially spawned VBoxService.exe process that
* handles a guest control session.
*/
if (fUserSession)
#endif
/*
* Parse the arguments.
*
* Note! This code predates RTGetOpt, thus the manual parsing.
*/
bool fDaemonize = true;
bool fDaemonized = false;
for (int i = 1; i < argc; i++)
{
if (*psz != '-')
psz++;
/* translate long argument to short */
if (*psz == '-')
{
psz++;
if (MATCHES("foreground"))
psz = "f";
else if (MATCHES("verbose"))
psz = "v";
else if (MATCHES("version"))
psz = "V";
else if (MATCHES("help"))
psz = "h";
else if (MATCHES("interval"))
psz = "i";
#ifdef RT_OS_WINDOWS
else if (MATCHES("register"))
psz = "r";
else if (MATCHES("unregister"))
psz = "u";
#endif
else if (MATCHES("logfile"))
psz = "l";
else if (MATCHES("daemonized"))
{
fDaemonized = true;
continue;
}
else
{
bool fFound = false;
g_aServices[j].fEnabled = true;
g_aServices[j].fEnabled = false;
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
{
if (g_aServices[j].fEnabled)
fFound = true;
}
if (!fFound)
{
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
{
if (fFound)
break;
if (rc != -1)
return rc;
}
}
if (!fFound)
continue;
}
}
/* handle the string of short options. */
do
{
switch (*psz)
{
case 'i':
if (rc)
return rc;
break;
case 'f':
fDaemonize = false;
break;
case 'v':
g_cVerbosity++;
break;
case 'V':
return RTEXITCODE_SUCCESS;
case 'h':
case '?':
return vboxServiceUsage();
#ifdef RT_OS_WINDOWS
case 'r':
return VBoxServiceWinInstall();
case 'u':
return VBoxServiceWinUninstall();
#endif
case 'l':
{
g_szLogFile, sizeof(g_szLogFile));
if (rc)
return rc;
break;
}
default:
{
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
bool fFound = false;
for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
{
if (fFound)
break;
if (rc != -1)
return rc;
}
if (!fFound)
break;
}
}
}
/* Check that at least one service is enabled. */
if (vboxServiceCountEnabledServices() == 0)
if (RT_FAILURE(rc))
/* Call pre-init if we didn't do it already. */
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
#ifdef RT_OS_WINDOWS
/*
* Make sure only one instance of VBoxService runs at a time. Create a
* global mutex for that.
*
* Note! The \\Global\ namespace was introduced with Win2K, thus the
* version check.
* Note! If the mutex exists CreateMutex will open it and set last error to
* ERROR_ALREADY_EXISTS.
*/
else
if (hMutexAppRunning == NULL)
{
if ( dwErr == ERROR_ALREADY_EXISTS
|| dwErr == ERROR_ACCESS_DENIED)
{
return RTEXITCODE_FAILURE;
}
return RTEXITCODE_FAILURE;
}
#else /* !RT_OS_WINDOWS */
/** @todo Add PID file creation here? */
#endif /* !RT_OS_WINDOWS */
VBoxServiceVerbose(0, "%s r%s started. Verbose level = %d\n",
/*
* Daemonize if requested.
*/
if (fDaemonize && !fDaemonized)
{
#ifdef RT_OS_WINDOWS
#else
if (RT_FAILURE(rc))
/* in-child */
#endif
}
#ifdef RT_OS_WINDOWS
else
#endif
{
/*
* Windows: We're running the service as a console application now. Start the
* services, enter the main thread's run loop and stop them again
* when it returns.
*
* POSIX: This is used for both daemons and console runs. Start all services
* and return immediately.
*/
#ifdef RT_OS_WINDOWS
# ifndef RT_OS_NT4
/* Install console control handler. */
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)VBoxServiceConsoleControlHandler, TRUE /* Add handler */))
{
/* Just skip this error, not critical. */
}
# endif /* !RT_OS_NT4 */
#endif /* RT_OS_WINDOWS */
if (RT_SUCCESS(rc))
#ifdef RT_OS_WINDOWS
# ifndef RT_OS_NT4
/* Uninstall console control handler. */
{
/* Just skip this error, not critical. */
}
# endif /* !RT_OS_NT4 */
#else /* !RT_OS_WINDOWS */
/* On Windows - since we're running as a console application - we already stopped all services
* through the console control handler. So only do the stopping of services here on other platforms
#endif /* RT_OS_WINDOWS */
}
#ifdef RT_OS_WINDOWS
/*
* Cleanup mutex.
*/
#endif
VBoxServiceVerbose(0, "Ended.\n");
#ifdef DEBUG
//RTMemTrackerDumpAllToStdOut();
#endif
return rcExit;
}