summaryrefslogtreecommitdiffstats
path: root/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp')
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp1199
1 files changed, 1199 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp
new file mode 100644
index 00000000..30c8f910
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp
@@ -0,0 +1,1199 @@
+/* $Id: VBoxWatchdog.cpp $ */
+/** @file
+ * VBoxWatchdog.cpp - VirtualBox Watchdog.
+ */
+
+/*
+ * Copyright (C) 2011-2019 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 *
+*********************************************************************************************************************************/
+#ifndef VBOX_ONLY_DOCS
+# 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>
+#endif /* !VBOX_ONLY_DOCS */
+
+#include <VBox/err.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 <algorithm>
+#include <string>
+#include <signal.h>
+
+#include "VBoxWatchdogInternal.h"
+
+using namespace com;
+
+/** External globals. */
+bool g_fDryrun = false;
+bool g_fVerbose = false;
+ComPtr<IVirtualBox> g_pVirtualBox = NULL;
+ComPtr<ISession> g_pSession = NULL;
+mapVM g_mapVM;
+mapGroup g_mapGroup;
+# ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ComPtr<IPerformanceCollector> g_pPerfCollector = NULL;
+# 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. */
+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;
+
+/**
+ * The details of the services that has been compiled in.
+ */
+typedef struct VBOXWATCHDOGMOD
+{
+ /** Pointer to the service descriptor. */
+ PCVBOXMODULE pDesc;
+ /** Whether Pre-init was called. */
+ bool fPreInited;
+ /** Whether the module is enabled or not. */
+ bool fEnabled;
+} VBOXWATCHDOGMOD, *PVBOXWATCHDOGMOD;
+
+static VBOXWATCHDOGMOD g_aModules[] =
+{
+ { &g_ModBallooning, false /* Pre-inited */, true /* Enabled */ },
+ { &g_ModAPIMonitor, false /* Pre-inited */, true /* Enabled */ }
+};
+
+enum GETOPTDEF_WATCHDOG
+{
+ GETOPTDEF_WATCHDOG_DISABLE_MODULE = 1000,
+ GETOPTDEF_WATCHDOG_DRYRUN
+};
+
+/**
+ * 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)
+ { "--background", 'b', RTGETOPT_REQ_NOTHING },
+#endif
+ /** For displayHelp(). */
+ { "--disable-<module>", GETOPTDEF_WATCHDOG_DISABLE_MODULE, RTGETOPT_REQ_NOTHING },
+ { "--dryrun", GETOPTDEF_WATCHDOG_DRYRUN, RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--pidfile", 'P', RTGETOPT_REQ_STRING },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }
+};
+
+/** Global static objects. */
+static ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
+static ComPtr<IEventSource> g_pEventSource = NULL;
+static ComPtr<IEventSource> g_pEventSourceClient = NULL;
+static ComPtr<IEventListener> g_pVBoxEventListener = NULL;
+static NativeEventQueue *g_pEventQ = NULL;
+
+/* Prototypes. */
+static int machineAdd(const Bstr &strUuid);
+static int machineRemove(const Bstr &strUuid);
+static int watchdogSetup();
+static void watchdogShutdown();
+
+
+/**
+ * Handler for global events.
+ */
+class VirtualBoxEventListener
+{
+ public:
+ VirtualBoxEventListener()
+ {
+ }
+
+ virtual ~VirtualBoxEventListener()
+ {
+ }
+
+ HRESULT init()
+ {
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnMachineRegistered:
+ {
+ ComPtr<IMachineRegisteredEvent> pEvent = aEvent;
+ Assert(pEvent);
+
+ Bstr uuid;
+ BOOL fRegistered;
+ HRESULT hr = pEvent->COMGETTER(Registered)(&fRegistered);
+ if (SUCCEEDED(hr))
+ hr = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
+
+ if (SUCCEEDED(hr))
+ {
+ int rc = RTCritSectEnter(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ {
+ rc = fRegistered
+ ? machineAdd(uuid)
+ : machineRemove(uuid);
+ int rc2 = RTCritSectLeave(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ AssertRC(rc);
+ }
+ }
+ break;
+ }
+
+ case VBoxEventType_OnMachineStateChanged:
+ {
+ ComPtr<IMachineStateChangedEvent> pEvent = aEvent;
+ Assert(pEvent);
+
+ MachineState_T machineState;
+ Bstr uuid;
+
+ HRESULT hr = pEvent->COMGETTER(State)(&machineState);
+ if (SUCCEEDED(hr))
+ hr = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
+
+ if (SUCCEEDED(hr))
+ {
+ int rc = RTCritSectEnter(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ int rc2 = g_aModules[j].pDesc->pfnOnMachineStateChanged(uuid,
+ machineState);
+ if (RT_FAILURE(rc2))
+ serviceLog("Module '%s' reported an error: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc);
+ /* Keep going. */
+ }
+
+ int rc2 = RTCritSectLeave(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ AssertRC(rc);
+ }
+ }
+ break;
+ }
+
+ case VBoxEventType_OnVBoxSVCAvailabilityChanged:
+ {
+ ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
+ Assert(pVSACEv);
+ BOOL fAvailable = FALSE;
+ pVSACEv->COMGETTER(Available)(&fAvailable);
+
+ /* First, notify all modules. */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ int rc2 = g_aModules[j].pDesc->pfnOnServiceStateChanged(RT_BOOL(fAvailable));
+ if (RT_FAILURE(rc2))
+ serviceLog("Module '%s' reported an error: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc2);
+ /* Keep going. */
+ }
+
+ /* Do global teardown/re-creation stuff. */
+ if (!fAvailable)
+ {
+ serviceLog("VBoxSVC became unavailable\n");
+ watchdogShutdown();
+ }
+ else
+ {
+ serviceLog("VBoxSVC became available\n");
+ int rc2 = watchdogSetup();
+ if (RT_FAILURE(rc2))
+ serviceLog("Unable to re-set up watchdog (rc=%Rrc)!\n", rc2);
+ }
+
+ break;
+ }
+
+ default:
+ /* Not handled event, just skip it. */
+ break;
+ }
+
+ return S_OK;
+ }
+
+ private:
+};
+typedef ListenerImpl<VirtualBoxEventListener> VirtualBoxEventListenerImpl;
+VBOX_LISTENER_DECLARE(VirtualBoxEventListenerImpl)
+
+/**
+ * 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)
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fCanceled, true);
+
+ if (g_pEventQ)
+ {
+ int rc = g_pEventQ->interruptEventQueueProcessing();
+ if (RT_FAILURE(rc))
+ serviceLog("Error: interruptEventQueueProcessing failed with rc=%Rrc\n", rc);
+ }
+}
+
+#if 0 /** @todo signal handler installer / uninstallers are unused. */
+/**
+ * Installs a custom signal handler to get notified
+ * whenever the user wants to intercept the program.
+ */
+static void signalHandlerInstall()
+{
+ signal(SIGINT, signalHandler);
+#ifdef SIGBREAK
+ signal(SIGBREAK, signalHandler);
+#endif
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static void signalHandlerUninstall()
+{
+ signal(SIGINT, SIG_DFL);
+#ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+#endif
+}
+#endif /* unused */
+
+/**
+ * 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.
+ */
+static int machineAdd(const Bstr &strUuid)
+{
+ HRESULT rc;
+
+ /** @todo Add exception handling! */
+
+ do
+ {
+ ComPtr <IMachine> machine;
+ CHECK_ERROR_BREAK(g_pVirtualBox, FindMachine(strUuid.raw(), machine.asOutParam()));
+ Assert(!machine.isNull());
+
+ /*
+ * Get groups for this machine.
+ */
+ com::SafeArray<BSTR> groups;
+ CHECK_ERROR_BREAK(machine, COMGETTER(Groups)(ComSafeArrayAsOutParam(groups)));
+ Utf8Str strGroups;
+ for (size_t i = 0; i < groups.size(); i++)
+ {
+ if (i != 0)
+ strGroups.append(",");
+ strGroups.append(Utf8Str(groups[i]));
+ }
+
+ /*
+ * Add machine to map.
+ */
+ VBOXWATCHDOG_MACHINE m;
+ m.machine = machine;
+ CHECK_ERROR_BREAK(machine, COMGETTER(Name)(m.strName.asOutParam()));
+
+ int rc2 = groupAdd(m.groups, strGroups.c_str(), 0 /* Flags */);
+ AssertRC(rc2);
+
+ Assert(g_mapVM.find(strUuid) == g_mapVM.end());
+ g_mapVM.insert(std::make_pair(strUuid, m));
+ serviceLogVerbose(("Added machine \"%ls\"\n", strUuid.raw()));
+
+ /*
+ * Get the machine's VM group(s).
+ */
+ mapGroupsIterConst itGroup = m.groups.begin();
+ while (itGroup != m.groups.end())
+ {
+ serviceLogVerbose(("Machine \"%ls\" is in VM group \"%s\"\n",
+ strUuid.raw(), itGroup->first.c_str()));
+
+ /* Add machine to group(s). */
+ mapGroupIter itGroups = g_mapGroup.find(itGroup->first);
+ if (itGroups == g_mapGroup.end())
+ {
+ vecGroupMembers vecMembers;
+ vecMembers.push_back(strUuid);
+ g_mapGroup.insert(std::make_pair(itGroup->first, vecMembers));
+
+ itGroups = g_mapGroup.find(itGroup->first);
+ Assert(itGroups != g_mapGroup.end());
+ }
+ else
+ itGroups->second.push_back(strUuid);
+ serviceLogVerbose(("Group \"%s\" has now %ld machine(s)\n",
+ itGroup->first.c_str(), itGroups->second.size()));
+ ++itGroup;
+ }
+
+ /*
+ * 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)
+ {
+ rc2 = g_aModules[j].pDesc->pfnOnMachineRegistered(strUuid);
+ if (RT_FAILURE(rc2))
+ serviceLog("OnMachineRegistered: Module '%s' reported an error: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc);
+ /* Keep going. */
+ }
+
+ } while (0);
+
+ /** @todo Add std exception handling! */
+
+ return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR; /** @todo Find a better error! */
+}
+
+static int machineDestroy(const Bstr &strUuid)
+{
+ AssertReturn(!strUuid.isEmpty(), VERR_INVALID_PARAMETER);
+ int rc = VINF_SUCCESS;
+
+ /* Let all modules know. */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ int rc2 = g_aModules[j].pDesc->pfnOnMachineUnregistered(strUuid);
+ if (RT_FAILURE(rc2))
+ serviceLog("OnMachineUnregistered: Module '%s' reported an error: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc);
+ /* Keep going. */
+ }
+
+ /* Must log before erasing the iterator because of the UUID ref! */
+ serviceLogVerbose(("Removing machine \"%ls\"\n", strUuid.raw()));
+
+ try
+ {
+ mapVMIter itVM = g_mapVM.find(strUuid);
+ Assert(itVM != g_mapVM.end());
+
+ /* Remove machine from group(s). */
+ mapGroupsIterConst itGroups = itVM->second.groups.begin();
+ while (itGroups != itVM->second.groups.end())
+ {
+ mapGroupIter itGroup = g_mapGroup.find(itGroups->first);
+ Assert(itGroup != g_mapGroup.end());
+
+ vecGroupMembers vecMembers = itGroup->second;
+ vecGroupMembersIter itMember = std::find(vecMembers.begin(),
+ vecMembers.end(),
+ strUuid);
+ Assert(itMember != vecMembers.end());
+ vecMembers.erase(itMember);
+
+ serviceLogVerbose(("Group \"%s\" has %ld machines left\n",
+ itGroup->first.c_str(), vecMembers.size()));
+ if (!vecMembers.size())
+ {
+ serviceLogVerbose(("Deleting group \"%s\"\n", itGroup->first.c_str()));
+ g_mapGroup.erase(itGroup);
+ }
+
+ ++itGroups;
+ }
+
+#ifndef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ itVM->second.collector.setNull();
+#endif
+ itVM->second.machine.setNull();
+
+ /*
+ * Remove machine from map.
+ */
+ g_mapVM.erase(itVM);
+ }
+ catch (...)
+ {
+ AssertFailed();
+ }
+
+ 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.
+ */
+static int machineRemove(const Bstr &strUuid)
+{
+ AssertReturn(!strUuid.isEmpty(), VERR_INVALID_PARAMETER);
+ int rc = VINF_SUCCESS;
+
+ mapVMIter it = g_mapVM.find(strUuid);
+ if (it != g_mapVM.end())
+ {
+ int rc2 = machineDestroy(strUuid);
+ if (RT_FAILURE(rc))
+ {
+ serviceLog(("Machine \"%ls\" failed to destroy, rc=%Rc\n"));
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ serviceLogVerbose(("Warning: Removing not added machine \"%ls\"\n", strUuid.raw()));
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+static void vmListDestroy()
+{
+ serviceLogVerbose(("Destroying VM list ...\n"));
+
+ int rc = RTCritSectEnter(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ {
+ mapVMIter it = g_mapVM.begin();
+ while (it != g_mapVM.end())
+ {
+ machineDestroy(it->first);
+ it = g_mapVM.begin();
+ }
+
+ g_mapVM.clear();
+
+ rc = RTCritSectLeave(&g_csMachines);
+ }
+ AssertRC(rc);
+}
+
+static int vmListBuild()
+{
+ serviceLogVerbose(("Building VM list ...\n"));
+
+ int rc = RTCritSectEnter(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Make sure the list is empty.
+ */
+ g_mapVM.clear();
+
+ /*
+ * Get the list of all _running_ VMs
+ */
+ com::SafeIfaceArray<IMachine> machines;
+ HRESULT hrc = g_pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines));
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Iterate through the collection
+ */
+ for (size_t i = 0; i < machines.size(); ++i)
+ {
+ if (machines[i])
+ {
+ Bstr strUUID;
+ CHECK_ERROR_BREAK(machines[i], COMGETTER(Id)(strUUID.asOutParam()));
+
+ BOOL fAccessible;
+ CHECK_ERROR_BREAK(machines[i], COMGETTER(Accessible)(&fAccessible));
+ if (!fAccessible)
+ {
+ serviceLogVerbose(("Machine \"%ls\" is inaccessible, skipping\n",
+ strUUID.raw()));
+ continue;
+ }
+
+ rc = machineAdd(strUUID);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ if (!machines.size())
+ serviceLogVerbose(("No machines to add found at the moment!\n"));
+ }
+
+ int rc2 = RTCritSectLeave(&g_csMachines);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ 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)
+ {
+ int rc = g_aModules[j].pDesc->pfnPreInit();
+ if (RT_FAILURE(rc))
+ {
+ serviceLog("Module '%s' failed pre-init: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc);
+ 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++)
+ {
+ const PVBOXWATCHDOGMOD pMod = &g_aModules[j];
+ if (pMod->fEnabled)
+ {
+ int rc2 = pMod->pDesc->pfnInit();
+ if (RT_FAILURE(rc2))
+ {
+ if (rc2 != VERR_SERVICE_DISABLED)
+ {
+ serviceLog("Module '%s' failed to initialize: %Rrc\n", pMod->pDesc->pszName, rc2);
+ return rc;
+ }
+ pMod->fEnabled = false;
+ serviceLog("Module '%s' was disabled because of missing functionality\n", pMod->pDesc->pszName);
+
+ }
+ }
+ else
+ serviceLog("Module '%s' disabled, skipping ...\n", pMod->pDesc->pszName);
+ }
+
+ return rc;
+}
+
+static int watchdogShutdownModules()
+{
+ int rc = VINF_SUCCESS;
+
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ int rc2 = g_aModules[j].pDesc->pfnStop();
+ if (RT_FAILURE(rc2))
+ {
+ serviceLog("Module '%s' failed to stop: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc);
+ /* Keep original rc. */
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ /* Keep going. */
+ }
+
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ g_aModules[j].pDesc->pfnTerm();
+ }
+
+ return rc;
+}
+
+static RTEXITCODE watchdogMain(/*HandlerArg *a */)
+{
+ HRESULT rc = S_OK;
+
+ do
+ {
+ /* Initialize global weak references. */
+ g_pEventQ = com::NativeEventQueue::getMainEventQueue();
+
+ /*
+ * Install signal handlers.
+ */
+ signal(SIGINT, signalHandler);
+ #ifdef SIGBREAK
+ signal(SIGBREAK, signalHandler);
+ #endif
+
+ /*
+ * Setup the global event listeners:
+ * - g_pEventSource for machine events
+ * - g_pEventSourceClient for VBoxClient events (like VBoxSVC handling)
+ */
+ CHECK_ERROR_BREAK(g_pVirtualBox, COMGETTER(EventSource)(g_pEventSource.asOutParam()));
+ CHECK_ERROR_BREAK(g_pVirtualBoxClient, COMGETTER(EventSource)(g_pEventSourceClient.asOutParam()));
+
+ ComObjPtr<VirtualBoxEventListenerImpl> vboxListenerImpl;
+ vboxListenerImpl.createObject();
+ vboxListenerImpl->init(new VirtualBoxEventListener());
+
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnMachineRegistered);
+ eventTypes.push_back(VBoxEventType_OnMachineStateChanged);
+ eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged); /* Processed by g_pEventSourceClient. */
+
+ g_pVBoxEventListener = vboxListenerImpl;
+ 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.
+ */
+
+ vrc = RTCritSectEnter(&g_csMachines);
+ if (RT_SUCCESS(vrc))
+ {
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ if (g_aModules[j].fEnabled)
+ {
+ int rc2 = g_aModules[j].pDesc->pfnMain();
+ if (RT_FAILURE(rc2))
+ serviceLog("Module '%s' reported an error: %Rrc\n",
+ g_aModules[j].pDesc->pszName, rc2);
+ /* Keep going. */
+ }
+
+ int rc2 = RTCritSectLeave(&g_csMachines);
+ if (RT_SUCCESS(vrc))
+ vrc = rc2;
+ AssertRC(vrc);
+ }
+
+ /*
+ * Process pending events, then wait for new ones. Note, this
+ * processes NULL events signalling event loop termination.
+ */
+ g_pEventQ->processEventQueue(50);
+
+ if (g_fCanceled)
+ {
+ serviceLog("Signal caught, exiting ...\n");
+ break;
+ }
+ }
+
+ signal(SIGINT, SIG_DFL);
+ #ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+ #endif
+
+ /* VirtualBox callback unregistration. */
+ if (g_pVBoxEventListener)
+ {
+ if (!g_pEventSource.isNull())
+ CHECK_ERROR(g_pEventSource, UnregisterListener(g_pVBoxEventListener));
+ g_pVBoxEventListener.setNull();
+ }
+
+ g_pEventSource.setNull();
+ g_pEventSourceClient.setNull();
+
+ vrc = watchdogShutdownModules();
+ AssertRC(vrc);
+
+ if (RT_FAILURE(vrc))
+ rc = VBOX_E_IPRT_ERROR;
+
+ } while (0);
+
+ return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+void serviceLog(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ LogRel(("%s", psz));
+
+ RTStrFree(psz);
+}
+
+static void displayHeader()
+{
+ RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " Watchdog " 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] [-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++)
+ if (g_aModules[j].pDesc->pszUsage)
+ RTStrmPrintf(g_pStdErr, "%s", g_aModules[j].pDesc->pszUsage);
+
+ 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 GETOPTDEF_WATCHDOG_DISABLE_MODULE:
+ pcszDescr = "Disables a module. See module list for built-in modules.";
+ break;
+
+ case GETOPTDEF_WATCHDOG_DRYRUN:
+ 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;
+ }
+
+ RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
+ }
+
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ {
+ if (g_aModules[j].pDesc->pszOptions)
+ RTStrmPrintf(g_pStdErr, "%s", g_aModules[j].pDesc->pszOptions);
+ }
+
+ /** @todo Change VBOXBALLOONCTRL_RELEASE_LOG to WATCHDOG*. */
+ RTStrmPrintf(g_pStdErr, "\nUse environment variable VBOXBALLOONCTRL_RELEASE_LOG for logging options.\n");
+
+ RTStrmPrintf(g_pStdErr, "\nValid module names are: ");
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aModules); j++)
+ {
+ if (j > 0)
+ RTStrmPrintf(g_pStdErr, ", ");
+ RTStrmPrintf(g_pStdErr, "%s", g_aModules[j].pDesc->pszName);
+ }
+ RTStrmPrintf(g_pStdErr, "\n\n");
+}
+
+/**
+ * Creates all global COM objects.
+ *
+ * @return HRESULT
+ */
+static int watchdogSetup()
+{
+ serviceLogVerbose(("Setting up ...\n"));
+
+ /*
+ * 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;
+
+ /*
+ * Setup metrics.
+ */
+#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ CHECK_ERROR_RET(g_pVirtualBox,
+ COMGETTER(PerformanceCollector)(g_pPerfCollector.asOutParam()), VERR_COM_UNEXPECTED);
+#endif
+
+ int vrc = RTCritSectInit(&g_csMachines);
+ if (RT_SUCCESS(vrc))
+ {
+
+ /*
+ * Build up initial VM list.
+ */
+ vrc = vmListBuild();
+ }
+
+ return vrc;
+}
+
+static void watchdogShutdown()
+{
+ serviceLogVerbose(("Shutting down ...\n"));
+
+ vmListDestroy();
+
+ int rc = RTCritSectDelete(&g_csMachines);
+ AssertRC(rc);
+
+#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ g_pPerfCollector.setNull();
+#endif
+
+ g_pSession.setNull();
+ g_pVirtualBox.setNull();
+}
+
+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);
+#ifdef RT_OS_WINDOWS
+ ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
+#endif
+
+ /*
+ * Parse the global options
+ */
+ int c;
+ const char *pszLogFile = NULL;
+ const char *pszPidFile = NULL;
+ 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 GETOPTDEF_WATCHDOG_DRYRUN:
+ 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':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return 0;
+
+ case 'P':
+ pszPidFile = ValueUnion.psz;
+ break;
+
+ 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;
+
+ default:
+ {
+ bool fFound = false;
+
+ char szModDisable[64];
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aModules); j++)
+ {
+ if (!RTStrPrintf(szModDisable, sizeof(szModDisable), "--disable-%s", g_aModules[j].pDesc->pszName))
+ continue;
+
+ if (!RTStrICmp(szModDisable, ValueUnion.psz))
+ {
+ g_aModules[j].fEnabled = false;
+ fFound = true;
+ }
+ }
+
+ if (!fFound)
+ {
+ rc = watchdogLazyPreInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aModules); j++)
+ {
+ if (!g_aModules[j].fEnabled)
+ continue;
+
+ int iArgCnt = argc - GetState.iNext + 1;
+ int iArgIndex = GetState.iNext - 1;
+ int iConsumed = 0;
+ rc = g_aModules[j].pDesc->pfnOption(iArgCnt,
+ &argv[iArgIndex],
+ &iConsumed);
+ fFound = rc == 0;
+ if (fFound)
+ {
+ GetState.iNext += iConsumed;
+ break;
+ }
+ if (rc != -1)
+ return rc;
+ }
+ }
+ if (!fFound)
+ return RTGetOptPrintError(c, &ValueUnion);
+ continue;
+ }
+ }
+ }
+
+ /** @todo Add "--quiet/-q" option to not show the header. */
+ displayHeader();
+
+ /* create release logger, to stdout */
+ RTERRINFOSTATIC ErrInfo;
+ rc = com::VBoxLogRelCreate("Watchdog", g_fDaemonize ? NULL : pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXBALLOONCTRL_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);
+
+#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)
+ {
+ 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), "vboxballoonctrl.log");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", rc);
+ pszLogFile = szLogFile;
+ }
+
+ rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc);
+ /* create release logger, to file */
+ rc = com::VBoxLogRelCreate("Watchdog", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXBALLOONCTRL_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
+
+#ifndef VBOX_ONLY_DOCS
+ /*
+ * 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;
+ }
+
+ if (g_fDryrun)
+ serviceLog("Running in dryrun mode\n");
+
+ rc = watchdogSetup();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ //HandlerArg handlerArg = { argc, argv };
+ RTEXITCODE rcExit = watchdogMain(/*&handlerArg*/);
+
+ NativeEventQueue::getMainEventQueue()->processEventQueue(0);
+
+ watchdogShutdown();
+
+ g_pVirtualBoxClient.setNull();
+
+ com::Shutdown();
+
+ return rcExit;
+#else /* VBOX_ONLY_DOCS */
+ return RTEXITCODE_SUCCESS;
+#endif /* VBOX_ONLY_DOCS */
+}
+