VBoxWatchdog.cpp revision df03c5ed15c9b5bf6d75fedcdf5057d3ffce8577
/* $Id$ */
/** @file
* VBoxWatchdog.cpp - VirtualBox Watchdog.
*/
/*
* Copyright (C) 2011-2012 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 *
*******************************************************************************/
#ifndef VBOX_ONLY_DOCS
#endif /* !VBOX_ONLY_DOCS */
#include <package-generated.h>
#include <iprt/buildconfig.h>
#include <iprt/critsect.h>
#include <iprt/initterm.h>
#include <iprt/semaphore.h>
#include <algorithm>
#include <string>
#include <signal.h>
#include "VBoxWatchdogInternal.h"
using namespace com;
/** External globals. */
bool g_fDryrun = false;
bool g_fVerbose = false;
# ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
# endif
/** The critical section for the machines map. */
static RTCRITSECT g_csMachines;
/** Set by the signal handler. */
static volatile bool g_fCanceled = false;
/** Logging parameters. */
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
/** Run in background. */
static bool g_fDaemonize = false;
#endif
/**
* The details of the services that has been compiled in.
*/
static struct
{
/** Pointer to the service descriptor. */
/** Whether Pre-init was called. */
bool fPreInited;
/** Whether the module is enabled or not. */
bool fEnabled;
} g_aModules[] =
{
};
enum GETOPTDEF_WATCHDOG
{
GETOPTDEF_WATCHDOG_DRYRUN = 1000
};
/**
* Command line arguments.
*/
static const RTGETOPTDEF g_aOptions[] = {
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
#endif
/** For displayHelp(). */
};
/** Global static objects. */
/* Prototypes. */
static HRESULT watchdogSetup();
static void watchdogShutdown();
#ifdef RT_OS_WINDOWS
/* Required for ATL. */
static CComModule _Module;
#endif
/**
* Handler for global events.
*/
class VirtualBoxEventListener
{
public:
{
}
virtual ~VirtualBoxEventListener()
{
}
{
return S_OK;
}
void uninit()
{
}
{
switch (aType)
{
{
{
if (RT_SUCCESS(rc))
{
? machineAdd(uuid)
: machineRemove(uuid);
if (RT_SUCCESS(rc))
}
}
break;
}
{
{
if (RT_SUCCESS(rc))
{
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
serviceLog("Module '%s' reported an error: %Rrc\n",
/* Keep going. */
}
if (RT_SUCCESS(rc))
}
}
break;
}
{
/* First, notify all modules. */
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
serviceLog("Module '%s' reported an error: %Rrc\n",
/* Keep going. */
}
/* Do global teardown/re-creation stuff. */
if (!fAvailable)
{
serviceLog("VBoxSVC became unavailable\n");
}
else
{
serviceLog("VBoxSVC became available\n");
}
break;
}
default:
/* Not handled event, just skip it. */
break;
}
return S_OK;
}
private:
};
/**
* Signal handler that sets g_fGuestCtrlCanceled.
*
* 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 signalHandler(int iSignal)
{
ASMAtomicWriteBool(&g_fCanceled, true);
if (!g_pEventQ)
{
if (RT_FAILURE(rc))
}
}
/**
* Installs a custom signal handler to get notified
* whenever the user wants to intercept the program.
*/
static void signalHandlerInstall()
{
#ifdef SIGBREAK
#endif
}
/**
* Uninstalls a previously installed signal handler.
*/
static void signalHandlerUninstall()
{
#ifdef SIGBREAK
#endif
}
/**
* Adds a specified machine to the list (map) of handled machines.
* Does not do locking -- needs to be done by caller!
*
* @return IPRT status code.
* @param strUuid UUID of the specified machine.
*/
{
/** @todo Add exception handling! */
do
{
strGroup.asOutParam()));
/*
* Add machine to map.
*/
/*
* Get the machine's VM group(s).
*/
{
serviceLogVerbose(("Machine \"%ls\" is in VM group \"%ls\"\n",
/** @todo Support more than one group! */
/* Add machine to group(s). */
{
}
else
serviceLogVerbose(("Group \"%ls\" now has %ld machine(s)\n",
}
else
serviceLogVerbose(("Machine \"%ls\" has no VM group assigned\n",
/*
* Let all modules know. Typically all modules would register
* their per-machine payload here.
*/
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
serviceLog("OnMachineRegistered: Module '%s' reported an error: %Rrc\n",
/* Keep going. */
}
} while (0);
/** @todo Add std exception handling! */
}
{
int rc = VINF_SUCCESS;
/* Let all modules know. */
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
serviceLog("OnMachineUnregistered: Module '%s' reported an error: %Rrc\n",
/* Keep going. */
}
/* Must log before erasing the iterator because of the UUID ref! */
/* Remove machine from group(s). */
/** @todo Add support for multiple groups! */
{
vecMembers.end(),
strUuid);
serviceLogVerbose(("Group \"%ls\" has %ld machines left\n",
if (!vecMembers.size())
}
#ifndef VBOX_WATCHDOG_GLOBAL_PERFCOL
#endif
/*
* Remove machine from map.
*/
return rc;
}
/**
* Removes a specified machine from the list of handled machines.
* Does not do locking -- needs to be done by caller!
*
* @return IPRT status code.
* @param strUuid UUID of the specified machine.
*/
{
int rc = VINF_SUCCESS;
{
if (RT_FAILURE(rc))
{
serviceLog(("Machine \"%ls\" failed to destroy, rc=%Rc\n"));
if (RT_SUCCESS(rc))
}
}
else
{
rc = VERR_NOT_FOUND;
}
return rc;
}
static void vmListDestroy()
{
serviceLogVerbose(("Destroying VM list ...\n"));
if (RT_SUCCESS(rc))
{
{
}
}
}
static int vmListBuild()
{
serviceLogVerbose(("Building VM list ...\n"));
if (RT_SUCCESS(rc))
{
/*
* Make sure the list is empty.
*/
/*
* Get the list of all _running_ VMs
*/
{
/*
* Iterate through the collection
*/
{
if (machines[i])
{
if (!fAccessible)
{
serviceLogVerbose(("Machine \"%ls\" is inaccessible, skipping\n",
continue;
}
if (RT_FAILURE(rc))
break;
}
}
serviceLogVerbose(("No machines to add found at the moment!\n"));
}
if (RT_SUCCESS(rc))
}
return rc;
}
/**
* Lazily calls the pfnPreInit method on each service.
*
* @returns VBox status code, error message displayed.
*/
static int watchdogLazyPreInit(void)
{
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (!g_aModules[j].fPreInited)
{
if (RT_FAILURE(rc))
{
serviceLog("Module '%s' failed pre-init: %Rrc\n",
return rc;
}
g_aModules[j].fPreInited = true;
}
return VINF_SUCCESS;
}
/**
* Starts all registered modules.
*
* @return IPRT status code.
* @return int
*/
static int watchdogStartModules()
{
int rc = VINF_SUCCESS;
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc))
{
if (rc != VERR_SERVICE_DISABLED)
{
serviceLog("Module '%s' failed to initialize: %Rrc\n",
return rc;
}
g_aModules[j].fEnabled = false;
serviceLog(0, "Module '%s' was disabled because of missing functionality\n",
}
}
return rc;
}
static int watchdogShutdownModules()
{
int rc = VINF_SUCCESS;
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
{
serviceLog("Module '%s' failed to stop: %Rrc\n",
/* Keep original rc. */
if (RT_SUCCESS(rc))
}
/* Keep going. */
}
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
}
return rc;
}
{
do
{
/* Initialize global weak references. */
/*
* Install signal handlers.
*/
#ifdef SIGBREAK
#endif
/*
* Setup the global event listeners:
* - g_pEventSource for machine events
* - g_pEventSourceClient for VBoxClient events (like VBoxSVC handling)
*/
eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged); /* Processed by g_pEventSourceClient. */
CHECK_ERROR_BREAK(g_pEventSource, RegisterListener(g_pVBoxEventListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */));
CHECK_ERROR_BREAK(g_pEventSourceClient, RegisterListener(g_pVBoxEventListener, ComSafeArrayAsInParam(eventTypes), true /* Active listener */));
/*
* Set up modules.
*/
int vrc = watchdogStartModules();
if (RT_FAILURE(vrc))
break;
for (;;)
{
/*
* Do the actual work.
*/
if (RT_SUCCESS(vrc))
{
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
if (g_aModules[j].fEnabled)
{
if (RT_FAILURE(rc2))
serviceLog("Module '%s' reported an error: %Rrc\n",
/* Keep going. */
}
if (RT_SUCCESS(vrc))
}
/*
* Process pending events, then wait for new ones. Note, this
* processes NULL events signalling event loop termination.
*/
if (g_fCanceled)
{
serviceLog("Signal caught, exiting ...\n");
break;
}
}
#ifdef SIGBREAK
#endif
/* VirtualBox callback unregistration. */
if (g_pVBoxEventListener)
{
if (!g_pEventSource.isNull())
}
if (RT_FAILURE(vrc))
} while (0);
}
void serviceLog(const char *pszFormat, ...)
{
}
static void displayHeader()
{
"All rights reserved.\n\n");
}
/**
* Displays the help.
*
* @param pszImage Name of program name (image).
*/
static void displayHelp(const char *pszImage)
{
"Usage:\n"
" %s [-v|--verbose] [-h|-?|--help] [-P|--pidfile]\n"
" [-F|--logfile=<file>] [-R|--logrotate=<num>] [-S|--logsize=<bytes>]\n"
" [-I|--loginterval=<seconds>]\n", pszImage);
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
"Options:\n");
for (unsigned i = 0;
i < RT_ELEMENTS(g_aOptions);
++i)
{
{
str += ", -";
}
str += ":";
const char *pcszDescr = "";
switch (g_aOptions[i].iShort)
{
pcszDescr = "Dryrun mode -- do not perform any actions.";
break;
case 'h':
pcszDescr = "Print this help message and exit.";
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
pcszDescr = "Run in background (daemon mode).";
break;
#endif
case 'P':
pcszDescr = "Name of the PID file which is created when the daemon was started.";
break;
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;
}
}
for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
{
}
/** @todo Change VBOXBALLOONCTRL_RELEASE_LOG to WATCHDOG*. */
RTStrmPrintf(g_pStdErr, "\nUse environment variable VBOXBALLOONCTRL_RELEASE_LOG for logging options.\n");
}
/**
* Creates all global COM objects.
*
* @return HRESULT
*/
static HRESULT watchdogSetup()
{
serviceLogVerbose(("Setting up ...\n"));
{
}
else
{
}
do
{
/*
* Setup metrics.
*/
#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
#endif
if (RT_FAILURE(vrc))
{
break;
}
/*
* Build up initial VM list.
*/
vrc = vmListBuild();
if (RT_FAILURE(vrc))
{
break;
}
} while (0);
return rc;
}
static void watchdogShutdown()
{
serviceLogVerbose(("Shutting down ...\n"));
#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
#endif
}
{
/*
* Before we do anything, init the runtime without loading
* the support driver.
*/
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
/*
* Parse the global options
*/
int c;
const char *pszLogFile = NULL;
const char *pszPidFile = NULL;
{
switch (c)
{
g_fDryrun = true;
break;
case 'h':
displayHelp(argv[0]);
return 0;
case 'v':
g_fVerbose = true;
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
g_fDaemonize = true;
break;
#endif
case 'V':
return 0;
case 'P':
break;
case 'F':
break;
case 'R':
break;
case 'S':
break;
case 'I':
break;
default:
{
bool fFound = false;
/** @todo Add "--disable-<module>" etc. here! */
if (!fFound)
{
rc = watchdogLazyPreInit();
if (RT_FAILURE(rc))
return RTEXITCODE_FAILURE;
{
if (fFound)
break;
if (rc != -1)
return rc;
}
}
if (!fFound)
return RTGetOptPrintError(c, &ValueUnion);
continue;
}
}
}
/** @todo Add "--quiet/-q" option to not show the header. */
/* create release logger, to stdout */
"all", "VBOXBALLOONCTRL_RELEASE_LOG",
if (RT_FAILURE(rc))
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
if (g_fDaemonize)
{
/* prepare release logging */
char szLogFile[RTPATH_MAX];
if (!pszLogFile || !*pszLogFile)
{
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
}
if (RT_FAILURE(rc))
/* create release logger, to file */
"all", "VBOXBALLOONCTRL_RELEASE_LOG",
if (RT_FAILURE(rc))
}
#endif
#ifndef VBOX_ONLY_DOCS
/*
* Initialize COM.
*/
using namespace com;
{
{
RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
}
else
return RTEXITCODE_FAILURE;
}
if (g_fDryrun)
serviceLog("Running in dryrun mode\n");
hrc = watchdogSetup();
return RTEXITCODE_FAILURE;
return rcExit;
#else /* VBOX_ONLY_DOCS */
return RTEXITCODE_SUCCESS;
#endif /* VBOX_ONLY_DOCS */
}