summaryrefslogtreecommitdiffstats
path: root/src/VBox/Frontends/VBoxBalloonCtrl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Frontends/VBoxBalloonCtrl
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.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/Frontends/VBoxBalloonCtrl')
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/Makefile.kmk48
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxBalloonCtrl.rc61
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp666
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp797
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp1217
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogInternal.h257
-rw-r--r--src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogUtils.cpp278
7 files changed, 3324 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/Makefile.kmk b/src/VBox/Frontends/VBoxBalloonCtrl/Makefile.kmk
new file mode 100644
index 00000000..45ce1342
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/Makefile.kmk
@@ -0,0 +1,48 @@
+# $Id: Makefile.kmk $
+## @file
+# VBoxBalloonCtrl - Memory balloon control.
+#
+
+#
+# Copyright (C) 2011-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
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+PROGRAMS += VBoxBalloonCtrl
+VBoxBalloonCtrl_TEMPLATE = VBoxMainClientExe
+VBoxBalloonCtrl_DEFS = VBOX_WATCHDOG_GLOBAL_PERFCOL
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxBalloonCtrl_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxBalloonCtrl_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxBalloonCtrl_DEFS.win = _WIN32_WINNT=0x0500
+VBoxBalloonCtrl_SOURCES = \
+ VBoxWatchdog.cpp \
+ VBoxWatchdogUtils.cpp \
+ VBoxModAPIMonitor.cpp \
+ VBoxModBallooning.cpp
+VBoxBalloonCtrl_SOURCES.win = \
+ VBoxBalloonCtrl.rc
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxBalloonCtrl.rc b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxBalloonCtrl.rc
new file mode 100644
index 00000000..cc09ad6a
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxBalloonCtrl.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxBalloonCtrl.rc $ */
+/** @file
+ * VBoxBalloonCtrl - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-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
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Balloon Control Tool\0"
+ VALUE "InternalName", "VBoxBalloonCtrl\0"
+ VALUE "OriginalFilename", "VBoxBalloonCtrl.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp
new file mode 100644
index 00000000..a39e6761
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp
@@ -0,0 +1,666 @@
+/* $Id: VBoxModAPIMonitor.cpp $ */
+/** @file
+ * VBoxModAPIMonitor - API monitor module for detecting host isolation.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#ifndef VBOX_ONLY_DOCS
+# include <iprt/message.h>
+# include <VBox/com/errorprint.h>
+#endif /* !VBOX_ONLY_DOCS */
+
+#include "VBoxWatchdogInternal.h"
+
+using namespace com;
+
+#define VBOX_MOD_APIMON_NAME "apimon"
+
+/**
+ * The module's RTGetOpt-IDs for the command line.
+ */
+enum GETOPTDEF_APIMON
+{
+ GETOPTDEF_APIMON_GROUPS = 3000,
+ GETOPTDEF_APIMON_ISLN_RESPONSE,
+ GETOPTDEF_APIMON_ISLN_TIMEOUT,
+ GETOPTDEF_APIMON_RESP_TIMEOUT
+};
+
+/**
+ * The module's command line arguments.
+ */
+static const RTGETOPTDEF g_aAPIMonitorOpts[] = {
+ { "--apimon-groups", GETOPTDEF_APIMON_GROUPS, RTGETOPT_REQ_STRING },
+ { "--apimon-isln-response", GETOPTDEF_APIMON_ISLN_RESPONSE, RTGETOPT_REQ_STRING },
+ { "--apimon-isln-timeout", GETOPTDEF_APIMON_ISLN_TIMEOUT, RTGETOPT_REQ_UINT32 },
+ { "--apimon-resp-timeout", GETOPTDEF_APIMON_RESP_TIMEOUT, RTGETOPT_REQ_UINT32 }
+};
+
+enum APIMON_RESPONSE
+{
+ /** Unknown / unhandled response. */
+ APIMON_RESPONSE_NONE = 0,
+ /** Pauses the VM execution. */
+ APIMON_RESPONSE_PAUSE = 10,
+ /** Does a hard power off. */
+ APIMON_RESPONSE_POWEROFF = 200,
+ /** Tries to save the current machine state. */
+ APIMON_RESPONSE_SAVE = 250,
+ /** Tries to shut down all running VMs in
+ * a gentle manner. */
+ APIMON_RESPONSE_SHUTDOWN = 300
+};
+
+/** The VM group(s) the API monitor handles. If none, all VMs get handled. */
+static mapGroups g_vecAPIMonGroups; /** @todo Move this into module payload! */
+static APIMON_RESPONSE g_enmAPIMonIslnResp = APIMON_RESPONSE_NONE;
+static uint32_t g_cMsAPIMonIslnTimeout = 0;
+static Bstr g_strAPIMonIslnLastBeat;
+static uint32_t g_cMsAPIMonResponseTimeout = 0;
+static uint64_t g_uAPIMonIslnLastBeatMS = 0;
+
+static int apimonResponseToEnum(const char *pszResponse, APIMON_RESPONSE *pResp)
+{
+ AssertPtrReturn(pszResponse, VERR_INVALID_POINTER);
+ AssertPtrReturn(pResp, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ if (!RTStrICmp(pszResponse, "none"))
+ {
+ *pResp = APIMON_RESPONSE_NONE;
+ }
+ else if (!RTStrICmp(pszResponse, "pause"))
+ {
+ *pResp = APIMON_RESPONSE_PAUSE;
+ }
+ else if ( !RTStrICmp(pszResponse, "poweroff")
+ || !RTStrICmp(pszResponse, "powerdown"))
+ {
+ *pResp = APIMON_RESPONSE_POWEROFF;
+ }
+ else if (!RTStrICmp(pszResponse, "save"))
+ {
+ *pResp = APIMON_RESPONSE_SAVE;
+ }
+ else if ( !RTStrICmp(pszResponse, "shutdown")
+ || !RTStrICmp(pszResponse, "shutoff"))
+ {
+ *pResp = APIMON_RESPONSE_SHUTDOWN;
+ }
+ else
+ {
+ *pResp = APIMON_RESPONSE_NONE;
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+static const char* apimonResponseToStr(APIMON_RESPONSE enmResp)
+{
+ if (APIMON_RESPONSE_NONE == enmResp)
+ return "none";
+ else if (APIMON_RESPONSE_PAUSE == enmResp)
+ return "pausing";
+ else if (APIMON_RESPONSE_POWEROFF == enmResp)
+ return "powering off";
+ else if (APIMON_RESPONSE_SAVE == enmResp)
+ return "saving state";
+ else if (APIMON_RESPONSE_SHUTDOWN == enmResp)
+ return "shutting down";
+
+ return "unknown";
+}
+
+/* Copied from VBoxManageInfo.cpp. */
+static const char *apimonMachineStateToName(MachineState_T machineState, bool fShort)
+{
+ switch (machineState)
+ {
+ case MachineState_PoweredOff:
+ return fShort ? "poweroff" : "powered off";
+ case MachineState_Saved:
+ return "saved";
+ case MachineState_Teleported:
+ return "teleported";
+ case MachineState_Aborted:
+ return "aborted";
+ case MachineState_AbortedSaved:
+ return "aborted-saved";
+ case MachineState_Running:
+ return "running";
+ case MachineState_Paused:
+ return "paused";
+ case MachineState_Stuck:
+ return fShort ? "gurumeditation" : "guru meditation";
+ case MachineState_LiveSnapshotting:
+ return fShort ? "livesnapshotting" : "live snapshotting";
+ case MachineState_Teleporting:
+ return "teleporting";
+ case MachineState_Starting:
+ return "starting";
+ case MachineState_Stopping:
+ return "stopping";
+ case MachineState_Saving:
+ return "saving";
+ case MachineState_Restoring:
+ return "restoring";
+ case MachineState_TeleportingPausedVM:
+ return fShort ? "teleportingpausedvm" : "teleporting paused vm";
+ case MachineState_TeleportingIn:
+ return fShort ? "teleportingin" : "teleporting (incoming)";
+ case MachineState_RestoringSnapshot:
+ return fShort ? "restoringsnapshot" : "restoring snapshot";
+ case MachineState_DeletingSnapshot:
+ return fShort ? "deletingsnapshot" : "deleting snapshot";
+ case MachineState_DeletingSnapshotOnline:
+ return fShort ? "deletingsnapshotlive" : "deleting snapshot live";
+ case MachineState_DeletingSnapshotPaused:
+ return fShort ? "deletingsnapshotlivepaused" : "deleting snapshot live paused";
+ case MachineState_SettingUp:
+ return fShort ? "settingup" : "setting up";
+ default:
+ break;
+ }
+ return "unknown";
+}
+
+static int apimonMachineControl(const Bstr &strUuid, PVBOXWATCHDOG_MACHINE pMachine,
+ APIMON_RESPONSE enmResp, uint32_t cMsTimeout)
+{
+ /** @todo Add other commands (with enmResp) here. */
+ AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
+
+ serviceLogVerbose(("apimon: Triggering \"%s\" (%RU32ms timeout) for machine \"%ls\"\n",
+ apimonResponseToStr(enmResp), cMsTimeout, strUuid.raw()));
+
+ if ( enmResp == APIMON_RESPONSE_NONE
+ || g_fDryrun)
+ return VINF_SUCCESS; /* Nothing to do. */
+
+ HRESULT hrc;
+ ComPtr <IMachine> machine;
+ CHECK_ERROR_RET(g_pVirtualBox, FindMachine(strUuid.raw(),
+ machine.asOutParam()), VERR_NOT_FOUND);
+ do
+ {
+ /* Query the machine's state to avoid unnecessary IPC. */
+ MachineState_T machineState;
+ CHECK_ERROR_BREAK(machine, COMGETTER(State)(&machineState));
+
+ if ( machineState == MachineState_Running
+ || machineState == MachineState_Paused)
+ {
+ /* Open a session for the VM. */
+ CHECK_ERROR_BREAK(machine, LockMachine(g_pSession, LockType_Shared));
+
+ do
+ {
+ /* Get the associated console. */
+ ComPtr<IConsole> console;
+ CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam()));
+ /* Get the associated session machine. */
+ ComPtr<IMachine> sessionMachine;
+ CHECK_ERROR_BREAK(g_pSession, COMGETTER(Machine)(sessionMachine.asOutParam()));
+
+ ComPtr<IProgress> progress;
+
+ switch (enmResp)
+ {
+ case APIMON_RESPONSE_PAUSE:
+ if (machineState != MachineState_Paused)
+ {
+ serviceLogVerbose(("apimon: Pausing machine \"%ls\" ...\n",
+ strUuid.raw()));
+ CHECK_ERROR_BREAK(console, Pause());
+ }
+ break;
+
+ case APIMON_RESPONSE_POWEROFF:
+ serviceLogVerbose(("apimon: Powering off machine \"%ls\" ...\n",
+ strUuid.raw()));
+ CHECK_ERROR_BREAK(console, PowerDown(progress.asOutParam()));
+ progress->WaitForCompletion(cMsTimeout);
+ CHECK_PROGRESS_ERROR(progress, ("Failed to power off machine \"%ls\"",
+ strUuid.raw()));
+ break;
+
+ case APIMON_RESPONSE_SAVE:
+ {
+ serviceLogVerbose(("apimon: Saving state of machine \"%ls\" ...\n",
+ strUuid.raw()));
+
+ /* First pause so we don't trigger a live save which needs more time/resources. */
+ bool fPaused = false;
+ hrc = console->Pause();
+ if (FAILED(hrc))
+ {
+ bool fError = true;
+ if (hrc == VBOX_E_INVALID_VM_STATE)
+ {
+ /* Check if we are already paused. */
+ CHECK_ERROR_BREAK(console, COMGETTER(State)(&machineState));
+ /* The error code was lost by the previous instruction. */
+ hrc = VBOX_E_INVALID_VM_STATE;
+ if (machineState != MachineState_Paused)
+ {
+ serviceLog("apimon: Machine \"%ls\" in invalid state %d -- %s\n",
+ strUuid.raw(), machineState, apimonMachineStateToName(machineState, false));
+ }
+ else
+ {
+ fError = false;
+ fPaused = true;
+ }
+ }
+ if (fError)
+ break;
+ }
+
+ CHECK_ERROR(sessionMachine, SaveState(progress.asOutParam()));
+ if (SUCCEEDED(hrc))
+ {
+ progress->WaitForCompletion(cMsTimeout);
+ CHECK_PROGRESS_ERROR(progress, ("Failed to save machine state of machine \"%ls\"",
+ strUuid.raw()));
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ serviceLogVerbose(("apimon: State of machine \"%ls\" saved, powering off ...\n", strUuid.raw()));
+ CHECK_ERROR_BREAK(console, PowerButton());
+ }
+ else
+ serviceLogVerbose(("apimon: Saving state of machine \"%ls\" failed\n", strUuid.raw()));
+
+ break;
+ }
+
+ case APIMON_RESPONSE_SHUTDOWN:
+ serviceLogVerbose(("apimon: Shutting down machine \"%ls\" ...\n", strUuid.raw()));
+ CHECK_ERROR_BREAK(console, PowerButton());
+ break;
+
+ default:
+ AssertMsgFailed(("Response %d not implemented", enmResp));
+ break;
+ }
+ } while (0);
+
+ /* Unlock the machine again. */
+ g_pSession->UnlockMachine();
+ }
+ else
+ serviceLogVerbose(("apimon: Warning: Could not trigger \"%s\" (%d) for machine \"%ls\"; in state \"%s\" (%d) currently\n",
+ apimonResponseToStr(enmResp), enmResp, strUuid.raw(),
+ apimonMachineStateToName(machineState, false), machineState));
+ } while (0);
+
+
+
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR;
+}
+
+static bool apimonHandleVM(const PVBOXWATCHDOG_MACHINE pMachine)
+{
+ bool fHandleVM = false;
+
+ try
+ {
+ mapGroupsIterConst itVMGroup = pMachine->groups.begin();
+ while ( itVMGroup != pMachine->groups.end()
+ && !fHandleVM)
+ {
+ mapGroupsIterConst itInGroup = g_vecAPIMonGroups.find(itVMGroup->first);
+ if (itInGroup != g_vecAPIMonGroups.end())
+ fHandleVM = true;
+
+ ++itVMGroup;
+ }
+ }
+ catch (...)
+ {
+ AssertFailed();
+ }
+
+ return fHandleVM;
+}
+
+static int apimonTrigger(APIMON_RESPONSE enmResp)
+{
+ int rc = VINF_SUCCESS;
+
+ bool fAllGroups = g_vecAPIMonGroups.empty();
+ mapVMIter it = g_mapVM.begin();
+
+ if (it == g_mapVM.end())
+ {
+ serviceLog("apimon: No machines in list, skipping ...\n");
+ return rc;
+ }
+
+ while (it != g_mapVM.end())
+ {
+ bool fHandleVM = fAllGroups;
+ try
+ {
+ if (!fHandleVM)
+ fHandleVM = apimonHandleVM(&it->second);
+
+ if (fHandleVM)
+ {
+ int rc2 = apimonMachineControl(it->first /* Uuid */,
+ &it->second /* Machine */, enmResp, g_cMsAPIMonResponseTimeout);
+ if (RT_FAILURE(rc2))
+ serviceLog("apimon: Controlling machine \"%ls\" (response \"%s\") failed with rc=%Rrc",
+ it->first.raw(), apimonResponseToStr(enmResp), rc);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2; /* Store original error. */
+ /* Keep going. */
+ }
+ }
+ catch (...)
+ {
+ AssertFailed();
+ }
+
+ ++it;
+ }
+
+ return rc;
+}
+
+/* Callbacks. */
+static DECLCALLBACK(int) VBoxModAPIMonitorPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorOption(int argc, char *argv[], int *piConsumed)
+{
+ if (!argc) /* Take a shortcut. */
+ return -1;
+
+ AssertPtrReturn(argv, VERR_INVALID_POINTER);
+ AssertPtrReturn(piConsumed, VERR_INVALID_POINTER);
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv,
+ g_aAPIMonitorOpts, RT_ELEMENTS(g_aAPIMonitorOpts),
+ 0 /* First */, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = 0; /* Set default parsing result to valid. */
+
+ int c;
+ RTGETOPTUNION ValueUnion;
+ while ((c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case GETOPTDEF_APIMON_GROUPS:
+ {
+ rc = groupAdd(g_vecAPIMonGroups, ValueUnion.psz, 0 /* Flags */);
+ if (RT_FAILURE(rc))
+ rc = -1; /* Option unknown. */
+ break;
+ }
+
+ case GETOPTDEF_APIMON_ISLN_RESPONSE:
+ rc = apimonResponseToEnum(ValueUnion.psz, &g_enmAPIMonIslnResp);
+ if (RT_FAILURE(rc))
+ rc = -1; /* Option unknown. */
+ break;
+
+ case GETOPTDEF_APIMON_ISLN_TIMEOUT:
+ g_cMsAPIMonIslnTimeout = ValueUnion.u32;
+ if (g_cMsAPIMonIslnTimeout < 1000) /* Don't allow timeouts < 1s. */
+ g_cMsAPIMonIslnTimeout = 1000;
+ break;
+
+ case GETOPTDEF_APIMON_RESP_TIMEOUT:
+ g_cMsAPIMonResponseTimeout = ValueUnion.u32;
+ if (g_cMsAPIMonResponseTimeout < 5000) /* Don't allow timeouts < 5s. */
+ g_cMsAPIMonResponseTimeout = 5000;
+ break;
+
+ default:
+ rc = -1; /* We don't handle this option, skip. */
+ break;
+ }
+
+ /* At the moment we only process one option at a time. */
+ break;
+ }
+
+ *piConsumed += GetState.iNext - 1;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorInit(void)
+{
+ HRESULT hrc = S_OK;
+
+ do
+ {
+ Bstr strValue;
+
+ /* VM groups to watch for. */
+ if (g_vecAPIMonGroups.empty()) /* Not set by command line? */
+ {
+ CHECK_ERROR_BREAK(g_pVirtualBox, GetExtraData(Bstr("VBoxInternal2/Watchdog/APIMonitor/Groups").raw(),
+ strValue.asOutParam()));
+ if (!strValue.isEmpty())
+ {
+ int rc2 = groupAdd(g_vecAPIMonGroups, Utf8Str(strValue).c_str(), 0 /* Flags */);
+ if (RT_FAILURE(rc2))
+ serviceLog("apimon: Warning: API monitor groups string invalid (%ls)\n", strValue.raw());
+ }
+ }
+
+ if (!g_cMsAPIMonIslnTimeout)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/APIMonitor/IsolationTimeoutMS", NULL /* Per-machine */,
+ &g_cMsAPIMonIslnTimeout, 30 * 1000 /* Default is 30 seconds timeout. */);
+ g_cMsAPIMonIslnTimeout = RT_MIN(1000, g_cMsAPIMonIslnTimeout);
+
+ if (g_enmAPIMonIslnResp == APIMON_RESPONSE_NONE) /* Not set by command line? */
+ {
+ Utf8Str strResp;
+ int rc2 = cfgGetValueStr(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/APIMonitor/IsolationResponse", NULL /* Per-machine */,
+ strResp, "" /* Default value. */);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = apimonResponseToEnum(strResp.c_str(), &g_enmAPIMonIslnResp);
+ if (RT_FAILURE(rc2))
+ serviceLog("apimon: Warning: API monitor response string invalid (%ls), defaulting to no action\n",
+ strValue.raw());
+ }
+ }
+
+ if (!g_cMsAPIMonResponseTimeout)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/APIMonitor/ResponseTimeoutMS", NULL /* Per-machine */,
+ &g_cMsAPIMonResponseTimeout, 30 * 1000 /* Default is 30 seconds timeout. */);
+ g_cMsAPIMonResponseTimeout = RT_MIN(5000, g_cMsAPIMonResponseTimeout);
+
+#ifdef DEBUG
+ /* Groups. */
+ serviceLogVerbose(("apimon: Handling %u groups:", g_vecAPIMonGroups.size()));
+ mapGroupsIterConst itGroups = g_vecAPIMonGroups.begin();
+ while (itGroups != g_vecAPIMonGroups.end())
+ {
+ serviceLogVerbose((" %s", itGroups->first.c_str()));
+ ++itGroups;
+ }
+ serviceLogVerbose(("\n"));
+#endif
+
+ } while (0);
+
+ if (SUCCEEDED(hrc))
+ {
+ g_uAPIMonIslnLastBeatMS = 0;
+ }
+
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_COM_IPRT_ERROR; /** @todo Find a better rc! */
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorMain(void)
+{
+ static uint64_t uLastRun = 0;
+ uint64_t uNow = RTTimeProgramMilliTS();
+ uint64_t uDelta = uNow - uLastRun;
+ if (uDelta < 1000) /* Only check every second (or later). */
+ return VINF_SUCCESS;
+ uLastRun = uNow;
+
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+
+#ifdef DEBUG
+ serviceLogVerbose(("apimon: Checking for API heartbeat (%RU64ms) ...\n",
+ g_cMsAPIMonIslnTimeout));
+#endif
+
+ do
+ {
+ Bstr strHeartbeat;
+ CHECK_ERROR_BREAK(g_pVirtualBox, GetExtraData(Bstr("Watchdog/APIMonitor/Heartbeat").raw(),
+ strHeartbeat.asOutParam()));
+ if ( SUCCEEDED(hrc)
+ && !strHeartbeat.isEmpty()
+ && g_strAPIMonIslnLastBeat.compare(strHeartbeat, Bstr::CaseSensitive))
+ {
+ serviceLogVerbose(("apimon: API heartbeat received, resetting timeout\n"));
+
+ g_uAPIMonIslnLastBeatMS = 0;
+ g_strAPIMonIslnLastBeat = strHeartbeat;
+ }
+ else
+ {
+ g_uAPIMonIslnLastBeatMS += uDelta;
+ if (g_uAPIMonIslnLastBeatMS > g_cMsAPIMonIslnTimeout)
+ {
+ serviceLogVerbose(("apimon: No API heartbeat within time received (%RU64ms)\n",
+ g_cMsAPIMonIslnTimeout));
+
+ vrc = apimonTrigger(g_enmAPIMonIslnResp);
+ g_uAPIMonIslnLastBeatMS = 0;
+ }
+ }
+ } while (0);
+
+ if (FAILED(hrc))
+ vrc = VERR_COM_IPRT_ERROR;
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorStop(void)
+{
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(void) VBoxModAPIMonitorTerm(void)
+{
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineRegistered(const Bstr &strUuid)
+{
+ RT_NOREF(strUuid);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineUnregistered(const Bstr &strUuid)
+{
+ RT_NOREF(strUuid);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorOnMachineStateChanged(const Bstr &strUuid, MachineState_T enmState)
+{
+ RT_NOREF(strUuid, enmState);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModAPIMonitorOnServiceStateChanged(bool fAvailable)
+{
+ if (!fAvailable)
+ {
+ serviceLog(("apimon: VBoxSVC became unavailable, triggering action\n"));
+ return apimonTrigger(g_enmAPIMonIslnResp);
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * The 'apimonitor' module description.
+ */
+VBOXMODULE g_ModAPIMonitor =
+{
+ /* pszName. */
+ VBOX_MOD_APIMON_NAME,
+ /* pszDescription. */
+ "API monitor for host isolation detection",
+ /* pszDepends. */
+ NULL,
+ /* uPriority. */
+ 0 /* Not used */,
+ /* pszUsage. */
+ " [--apimon-groups=<string[,stringN]>]\n"
+ " [--apimon-isln-response=<cmd>] [--apimon-isln-timeout=<ms>]\n"
+ " [--apimon-resp-timeout=<ms>]",
+ /* pszOptions. */
+ " --apimon-groups=<string[,...]>\n"
+ " Sets the VM groups for monitoring (all), comma-separated list.\n"
+ " --apimon-isln-response=<cmd>\n"
+ " Sets the isolation response to one of: none, pause, poweroff,\n"
+ " save, or shutdown. Default: none\n"
+ " --apimon-isln-timeout=<ms>\n"
+ " Sets the isolation timeout in ms (30s).\n"
+ " --apimon-resp-timeout=<ms>\n"
+ " Sets the response timeout in ms (30s).\n",
+ /* methods. */
+ VBoxModAPIMonitorPreInit,
+ VBoxModAPIMonitorOption,
+ VBoxModAPIMonitorInit,
+ VBoxModAPIMonitorMain,
+ VBoxModAPIMonitorStop,
+ VBoxModAPIMonitorTerm,
+ /* callbacks. */
+ VBoxModAPIMonitorOnMachineRegistered,
+ VBoxModAPIMonitorOnMachineUnregistered,
+ VBoxModAPIMonitorOnMachineStateChanged,
+ VBoxModAPIMonitorOnServiceStateChanged
+};
+
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp
new file mode 100644
index 00000000..17d8010a
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp
@@ -0,0 +1,797 @@
+/* $Id: VBoxModBallooning.cpp $ */
+/** @file
+ * VBoxModBallooning - Module for handling the automatic ballooning of VMs.
+ */
+
+/*
+ * Copyright (C) 2011-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 *
+*********************************************************************************************************************************/
+#ifndef VBOX_ONLY_DOCS
+# include <VBox/com/errorprint.h>
+#endif /* !VBOX_ONLY_DOCS */
+
+#include "VBoxWatchdogInternal.h"
+#include <iprt/system.h>
+
+using namespace com;
+
+#define VBOX_MOD_BALLOONING_NAME "balloon"
+
+
+/*********************************************************************************************************************************
+* Local Structures *
+*********************************************************************************************************************************/
+
+/**
+ * The module's RTGetOpt-IDs for the command line.
+ */
+enum GETOPTDEF_BALLOONCTRL
+{
+ GETOPTDEF_BALLOONCTRL_BALLOONINC = 2000,
+ GETOPTDEF_BALLOONCTRL_BALLOONDEC,
+ GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT,
+ GETOPTDEF_BALLOONCTRL_BALLOONMAX,
+ GETOPTDEF_BALLOONCTRL_BALLOONSAFETY,
+ GETOPTDEF_BALLOONCTRL_TIMEOUTMS,
+ GETOPTDEF_BALLOONCTRL_GROUPS
+};
+
+/**
+ * The module's command line arguments.
+ */
+static const RTGETOPTDEF g_aBalloonOpts[] = {
+ { "--balloon-dec", GETOPTDEF_BALLOONCTRL_BALLOONDEC, RTGETOPT_REQ_UINT32 },
+ { "--balloon-groups", GETOPTDEF_BALLOONCTRL_GROUPS, RTGETOPT_REQ_STRING },
+ { "--balloon-inc", GETOPTDEF_BALLOONCTRL_BALLOONINC, RTGETOPT_REQ_UINT32 },
+ { "--balloon-interval", GETOPTDEF_BALLOONCTRL_TIMEOUTMS, RTGETOPT_REQ_UINT32 },
+ { "--balloon-lower-limit", GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT, RTGETOPT_REQ_UINT32 },
+ { "--balloon-max", GETOPTDEF_BALLOONCTRL_BALLOONMAX, RTGETOPT_REQ_UINT32 },
+ { "--balloon-safety-margin", GETOPTDEF_BALLOONCTRL_BALLOONSAFETY, RTGETOPT_REQ_UINT32 }
+};
+
+/** The ballooning module's payload. */
+typedef struct VBOXWATCHDOG_BALLOONCTRL_PAYLOAD
+{
+ /** Last (most recent) ballooning size reported by the guest. */
+ uint32_t cMbBalloonCurLast;
+ /** Last (most recent) ballooning request received. */
+ uint32_t cMbBalloonReqLast;
+} VBOXWATCHDOG_BALLOONCTRL_PAYLOAD, *PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD;
+
+
+/*********************************************************************************************************************************
+* Globals *
+*********************************************************************************************************************************/
+
+static uint32_t g_cMsMemoryBalloonTimeout = 30 * 1000;
+static uint32_t g_cMbMemoryBalloonIncrement = 256;
+static uint32_t g_cMbMemoryBalloonDecrement = 128;
+/** Command line: Global balloon limit (in MB) for all VMs. Default is 0, which means
+ * no global limit is set. See balloonGetMaxSize() for more information. */
+static uint32_t g_cMbMemoryBalloonMax = 0;
+static uint32_t g_cMbMemoryBalloonLowerLimit = 128;
+static uint32_t g_cbMemoryBalloonSafety = 1024;
+
+
+/*********************************************************************************************************************************
+* Local Function Prototypes *
+*********************************************************************************************************************************/
+static int balloonSetSize(PVBOXWATCHDOG_MACHINE pMachine, uint32_t cMbBalloonCur);
+
+/**
+ * Retrieves the current delta value
+ *
+ * @return Delta (MB) of the balloon to be deflated (<0) or inflated (>0).
+ * @param pMachine Pointer to the machine's internal structure.
+ * @param uGuestMemFree The guest's current free memory (MB).
+ * @param cMbBalloonOld The balloon's current (old) size (MB).
+ * @param uBalloonNew The balloon's new size (MB).
+ * @param uBalloonMax The maximum ballooning size (MB) it can inflate to.
+ */
+static int32_t balloonGetDelta(PVBOXWATCHDOG_MACHINE pMachine, uint32_t cMbGuestMemFree,
+ uint32_t cMbBalloonOld, uint32_t cMbBalloonNew, uint32_t cMbBalloonMax)
+{
+ serviceLogVerbose(("[%ls] cMbGuestMemFree=%RU32, cMbBalloonOld=%RU32, cMbBalloonNew=%RU32, cMbBalloonMax=%RU32\n",
+ pMachine->strName.raw(), cMbGuestMemFree, cMbBalloonOld, cMbBalloonNew, cMbBalloonMax));
+
+ /* Make sure that the requested new ballooning size does not
+ * exceed the maximum ballooning size (if set). */
+ if ( cMbBalloonMax
+ && cMbBalloonNew > cMbBalloonMax)
+ cMbBalloonNew = cMbBalloonMax;
+
+ int32_t cMbBalloonDelta = 0;
+ if (cMbGuestMemFree < g_cMbMemoryBalloonLowerLimit)
+ {
+ /* Guest is running low on memory, we need to
+ * deflate the balloon. */
+ cMbBalloonDelta = g_cMbMemoryBalloonDecrement * -1;
+
+ /* Ensure that the delta will not return a negative
+ * balloon size. */
+ if ((int32_t)cMbBalloonOld + cMbBalloonDelta < 0)
+ cMbBalloonDelta = 0;
+ }
+ else if (cMbBalloonNew > cMbBalloonOld) /* Inflate. */
+ {
+ /* We want to inflate the balloon if we have room. */
+ uint32_t cMbIncrement = g_cMbMemoryBalloonIncrement;
+ while ( cMbIncrement >= 16
+ && cMbGuestMemFree - cMbIncrement < g_cMbMemoryBalloonLowerLimit)
+ cMbIncrement /= 2;
+
+ if ((cMbGuestMemFree - cMbIncrement) > g_cMbMemoryBalloonLowerLimit)
+ cMbBalloonDelta = (int32_t)cMbIncrement;
+
+ /* Make sure we're still within bounds. */
+ Assert(cMbBalloonDelta >= 0);
+ if (cMbBalloonOld + cMbBalloonDelta > cMbBalloonNew)
+ cMbBalloonDelta = RT_MIN(g_cMbMemoryBalloonIncrement, cMbBalloonNew - cMbBalloonOld);
+ }
+ else if (cMbBalloonNew < cMbBalloonOld) /* Deflate. */
+ {
+ cMbBalloonDelta = RT_MIN(g_cMbMemoryBalloonDecrement, cMbBalloonOld - cMbBalloonNew) * -1;
+ }
+
+ /* Limit the ballooning to the available host memory, leaving some free.
+ * If anything fails clamp the delta to 0. */
+ if (cMbBalloonDelta < 0)
+ {
+ uint64_t cbSafety = (uint64_t)g_cbMemoryBalloonSafety * _1M;
+ uint64_t cbHostRamAvail = 0;
+ int vrc = RTSystemQueryAvailableRam(&cbHostRamAvail);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbHostRamAvail < cbSafety)
+ cMbBalloonDelta = 0;
+ else if ((uint64_t)(-cMbBalloonDelta) > (cbHostRamAvail - cbSafety) / _1M)
+ cMbBalloonDelta = -(int32_t)((cbHostRamAvail - cbSafety) / _1M);
+ }
+ else
+ cMbBalloonDelta = 0;
+ }
+
+ return cMbBalloonDelta;
+}
+
+/**
+ * Determines the maximum balloon size to set for the specified machine.
+ *
+ * @return Maximum ballooning size (in MB), 0 if no maximum set.
+ * @param pMachine Machine to determine maximum ballooning size for.
+ */
+static uint32_t balloonGetMaxSize(PVBOXWATCHDOG_MACHINE pMachine)
+{
+ /*
+ * Is a maximum ballooning size set? Make sure we're within bounds.
+ *
+ * The maximum balloning size can be set
+ * - via global extra-data ("VBoxInternal/Guest/BalloonSizeMax")
+ * - via command line ("--balloon-max")
+ *
+ * Precedence from top to bottom.
+ */
+ uint32_t cMbBalloonMax = 0;
+ char szSource[64];
+
+ Bstr strValue;
+ HRESULT hrc = g_pVirtualBox->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
+ strValue.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && strValue.isNotEmpty())
+ {
+ cMbBalloonMax = Utf8Str(strValue).toUInt32();
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "global extra-data");
+ }
+
+ if (strValue.isEmpty())
+ {
+ Assert(cMbBalloonMax == 0);
+
+ cMbBalloonMax = g_cMbMemoryBalloonMax;
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "command line");
+ }
+
+ serviceLogVerbose(("[%ls] Maximum balloning size is (%s): %RU32MB\n", pMachine->strName.raw(), szSource, cMbBalloonMax));
+ return cMbBalloonMax;
+}
+
+/**
+ * Determines the current (set) balloon size of the specified machine.
+ *
+ * @return IPRT status code.
+ * @param pMachine Machine to determine maximum ballooning size for.
+ * @param pcMbBalloonCur Where to store the current (set) balloon
+ * size (in MB) on success.
+ */
+static int balloonGetCurrentSize(PVBOXWATCHDOG_MACHINE pMachine, uint32_t *pcMbBalloonCur)
+{
+ LONG cKbBalloonCur;
+ int vrc = getMetric(pMachine, L"Guest/RAM/Usage/Balloon", &cKbBalloonCur);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pcMbBalloonCur)
+ *pcMbBalloonCur = (uint32_t)(cKbBalloonCur / 1024);
+ }
+
+ return vrc;
+}
+
+/**
+ * Determines the requested balloon size to set for the specified machine.
+ *
+ * @return Requested ballooning size (in MB), 0 if ballooning should be disabled.
+ * @param pMachine Machine to determine maximum ballooning size for.
+ */
+static uint32_t balloonGetRequestedSize(PVBOXWATCHDOG_MACHINE pMachine)
+{
+ const ComPtr<IMachine> &rptrMachine = pMachine->machine;
+
+ /*
+ * The maximum balloning size can be set
+ * - via per-VM extra-data ("VBoxInternal2/Watchdog/BalloonCtrl/BalloonSizeMax")
+ * - via per-VM extra-data (legacy) ("VBoxInternal/Guest/BalloonSizeMax")
+ *
+ * Precedence from top to bottom.
+ */
+ uint32_t cMbBalloonReq = 0;
+ char szSource[64];
+
+ Bstr strValue;
+ HRESULT hrc = rptrMachine->GetExtraData(Bstr("VBoxInternal2/Watchdog/BalloonCtrl/BalloonSizeMax").raw(),
+ strValue.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && strValue.isNotEmpty())
+ {
+ cMbBalloonReq = Utf8Str(strValue).toUInt32();
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "per-VM extra-data");
+ }
+ else
+ {
+ hrc = rptrMachine->GetExtraData(Bstr("VBoxInternal/Guest/BalloonSizeMax").raw(),
+ strValue.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && strValue.isNotEmpty())
+ {
+ cMbBalloonReq = Utf8Str(strValue).toUInt32();
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "per-VM extra-data (legacy)");
+ }
+ }
+
+ if ( FAILED(hrc)
+ || strValue.isEmpty())
+ {
+ cMbBalloonReq = 0;
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "none (disabled)");
+ }
+
+ serviceLogVerbose(("[%ls] Requested balloning size is (%s): %RU32MB\n", pMachine->strName.raw(), szSource, cMbBalloonReq));
+ return cMbBalloonReq;
+}
+
+/**
+ * Determines whether ballooning for the specified machine is enabled or not.
+ * This can be specified on a per-VM basis or as a globally set value for all VMs.
+ *
+ * @return bool Whether ballooning is enabled or not.
+ * @param pMachine Machine to determine enable status for.
+ */
+static bool balloonIsEnabled(PVBOXWATCHDOG_MACHINE pMachine)
+{
+ const ComPtr<IMachine> &rptrMachine = pMachine->machine;
+
+ bool fEnabled = true; /* By default ballooning is enabled. */
+ char szSource[64];
+
+ Bstr strValue;
+ HRESULT hrc = g_pVirtualBox->GetExtraData(Bstr("VBoxInternal/Guest/BalloonEnabled").raw(),
+ strValue.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && strValue.isNotEmpty())
+ {
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "global extra-data");
+ }
+ else
+ {
+ hrc = rptrMachine->GetExtraData(Bstr("VBoxInternal2/Watchdog/BalloonCtrl/BalloonEnabled").raw(),
+ strValue.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ if (g_fVerbose)
+ RTStrPrintf(szSource, sizeof(szSource), "per-VM extra-data");
+ }
+ }
+
+ if (strValue.isNotEmpty())
+ {
+ fEnabled = RT_BOOL(Utf8Str(strValue).toUInt32());
+ serviceLogVerbose(("[%ls] Ballooning is forced to %s (%s)\n",
+ pMachine->strName.raw(), fEnabled ? "enabled" : "disabled", szSource));
+ }
+
+ return fEnabled;
+}
+
+/**
+ * Indicates whether ballooning on the specified machine state is
+ * possible -- this only is true if the machine is up and running.
+ *
+ * @return bool Flag indicating whether the VM is running or not.
+ * @param enmState The VM's machine state to judge whether it's running or not.
+ */
+static bool balloonIsPossible(MachineState_T enmState)
+{
+ switch (enmState)
+ {
+ case MachineState_Running:
+#if 0
+ /* Not required for ballooning. */
+ case MachineState_Teleporting:
+ case MachineState_LiveSnapshotting:
+ case MachineState_Paused:
+ case MachineState_TeleportingPausedVM:
+#endif
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+int balloonMachineSetup(const Bstr& strUuid)
+{
+ int vrc = VINF_SUCCESS;
+
+ do
+ {
+ PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
+ AssertPtrBreakStmt(pMachine, vrc=VERR_INVALID_PARAMETER);
+
+ ComPtr<IMachine> m = pMachine->machine;
+
+ /*
+ * Setup metrics required for ballooning.
+ */
+ com::SafeArray<BSTR> metricNames(1);
+ com::SafeIfaceArray<IUnknown> metricObjects(1);
+ com::SafeIfaceArray<IPerformanceMetric> metricAffected;
+
+ Bstr strMetricNames(L"Guest/RAM/Usage");
+ strMetricNames.cloneTo(&metricNames[0]);
+
+ HRESULT hrc = m.queryInterfaceTo(&metricObjects[0]);
+
+#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ CHECK_ERROR_BREAK(g_pPerfCollector, SetupMetrics(ComSafeArrayAsInParam(metricNames),
+ ComSafeArrayAsInParam(metricObjects),
+ 5 /* 5 seconds */,
+ 1 /* One sample is enough */,
+ ComSafeArrayAsOutParam(metricAffected)));
+#else
+ ComPtr<IPerformanceCollector> coll = pMachine->collector;
+
+ CHECK_ERROR_BREAK(g_pVirtualBox, COMGETTER(PerformanceCollector)(coll.asOutParam()));
+ CHECK_ERROR_BREAK(coll, SetupMetrics(ComSafeArrayAsInParam(metricNames),
+ ComSafeArrayAsInParam(metricObjects),
+ 5 /* 5 seconds */,
+ 1 /* One sample is enough */,
+ ComSafeArrayAsOutParam(metricAffected)));
+#endif
+ if (FAILED(hrc))
+ vrc = VERR_COM_IPRT_ERROR; /** @todo Find better rc! */
+
+ } while (0);
+
+ return vrc;
+}
+
+/**
+ * Does the actual ballooning and assumes the machine is
+ * capable and ready for ballooning.
+ *
+ * @return IPRT status code.
+ * @param pMachine Pointer to the machine's internal structure.
+ */
+static int balloonMachineUpdate(PVBOXWATCHDOG_MACHINE pMachine)
+{
+ AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
+
+ /*
+ * Get metrics collected at this point.
+ */
+ LONG cKbGuestMemFree;
+ uint32_t cMbBalloonCur = 0;
+
+ int vrc = getMetric(pMachine, L"Guest/RAM/Usage/Free", &cKbGuestMemFree);
+ if (RT_SUCCESS(vrc))
+ vrc = balloonGetCurrentSize(pMachine, &cMbBalloonCur);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* If guest statistics are not up and running yet, skip this iteration and try next time. */
+ if (cKbGuestMemFree <= 0)
+ {
+#ifdef DEBUG
+ serviceLogVerbose(("[%ls] No metrics available yet!\n", pMachine->strName.raw()));
+#endif
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cMbGuestMemFree = (ULONG)cKbGuestMemFree / 1024;
+
+ PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD pData;
+ pData = (PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD)payloadFrom(pMachine, VBOX_MOD_BALLOONING_NAME);
+ AssertPtr(pData);
+
+ /* Determine if ballooning is enabled or disabled. */
+ bool fEnabled = balloonIsEnabled(pMachine);
+
+ /* Determine the current set maximum balloon size. */
+ uint32_t cMbBalloonMax = balloonGetMaxSize(pMachine);
+
+ /* Determine the requested balloon size. */
+ uint32_t cMbBalloonReq = balloonGetRequestedSize(pMachine);
+
+ serviceLogVerbose(("[%ls] Free RAM (MB): %RI32, Ballooning: Current=%RU32MB, Requested=%RU32MB, Maximum=%RU32MB\n",
+ pMachine->strName.raw(), cMbGuestMemFree, cMbBalloonCur, cMbBalloonReq, cMbBalloonMax));
+
+ if ( cMbBalloonMax
+ && cMbBalloonReq > cMbBalloonMax)
+ {
+ if (pData->cMbBalloonReqLast != cMbBalloonReq)
+ serviceLog("[%ls] Warning: Requested ballooning size (%RU32MB) exceeds set maximum ballooning size (%RU32MB), limiting ...\n",
+ pMachine->strName.raw(), cMbBalloonReq, cMbBalloonMax);
+ }
+
+ /* Calculate current balloon delta. */
+ int32_t cMbBalloonDelta = balloonGetDelta(pMachine, cMbGuestMemFree, cMbBalloonCur, cMbBalloonReq, cMbBalloonMax);
+#ifdef DEBUG
+ serviceLogVerbose(("[%ls] cMbBalloonDelta=%RI32\n", pMachine->strName.raw(), cMbBalloonDelta));
+#endif
+ if (cMbBalloonDelta) /* Only do ballooning if there's really smth. to change ... */
+ {
+ cMbBalloonCur = cMbBalloonCur + cMbBalloonDelta;
+
+ if (fEnabled)
+ {
+ serviceLog("[%ls] %s balloon by %RU32MB to %RU32MB ...\n",
+ pMachine->strName.raw(), cMbBalloonDelta > 0 ? "Inflating" : "Deflating", RT_ABS(cMbBalloonDelta), cMbBalloonCur);
+ vrc = balloonSetSize(pMachine, cMbBalloonCur);
+ }
+ else
+ serviceLogVerbose(("[%ls] Requested %s balloon by %RU32MB to %RU32MB, but ballooning is disabled\n",
+ pMachine->strName.raw(), cMbBalloonDelta > 0 ? "inflating" : "deflating",
+ RT_ABS(cMbBalloonDelta), cMbBalloonCur));
+ }
+
+ if (cMbBalloonCur != pData->cMbBalloonCurLast)
+ {
+ /* If ballooning is disabled, always bolt down the ballooning size to 0. */
+ if (!fEnabled)
+ {
+ serviceLogVerbose(("[%ls] Ballooning is disabled, forcing to 0\n", pMachine->strName.raw()));
+ int vrc2 = balloonSetSize(pMachine, 0);
+ if (RT_FAILURE(vrc2))
+ serviceLog("[%ls] Error disabling ballooning, rc=%Rrc\n", pMachine->strName.raw(), vrc2);
+ }
+ }
+
+ pData->cMbBalloonCurLast = cMbBalloonCur;
+ pData->cMbBalloonReqLast = cMbBalloonReq;
+ }
+ else
+ serviceLog("[%ls] Error retrieving metrics, rc=%Rrc\n", pMachine->strName.raw(), vrc);
+
+ return vrc;
+}
+
+static int balloonSetSize(PVBOXWATCHDOG_MACHINE pMachine, uint32_t cMbBalloonCur)
+{
+ int vrc = VINF_SUCCESS;
+
+ serviceLogVerbose(("[%ls] Setting balloon size to %RU32MB ...\n", pMachine->strName.raw(), cMbBalloonCur));
+
+ if (g_fDryrun)
+ return VINF_SUCCESS;
+
+ /* Open a session for the VM. */
+ HRESULT hrc;
+ CHECK_ERROR_RET(pMachine->machine, LockMachine(g_pSession, LockType_Shared), VERR_ACCESS_DENIED);
+
+ do
+ {
+ /* Get the associated console. */
+ ComPtr<IConsole> console;
+ CHECK_ERROR_BREAK(g_pSession, COMGETTER(Console)(console.asOutParam()));
+
+ ComPtr <IGuest> guest;
+ hrc = console->COMGETTER(Guest)(guest.asOutParam());
+ if (SUCCEEDED(hrc))
+ CHECK_ERROR_BREAK(guest, COMSETTER(MemoryBalloonSize)((LONG)cMbBalloonCur));
+ else
+ serviceLog("Error: Unable to set new balloon size %RU32 for machine '%ls', rc=%Rhrc\n",
+ cMbBalloonCur, pMachine->strName.raw(), hrc);
+ if (FAILED(hrc))
+ vrc = VERR_COM_IPRT_ERROR;
+
+ } while (0);
+
+
+ /* Unlock the machine again. */
+ CHECK_ERROR_RET(g_pSession, UnlockMachine(), VERR_ACCESS_DENIED);
+
+ return vrc;
+}
+
+/* Callbacks. */
+static DECLCALLBACK(int) VBoxModBallooningPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningOption(int argc, char *argv[], int *piConsumed)
+{
+ if (!argc) /* Take a shortcut. */
+ return -1;
+
+ AssertPtrReturn(argv, VERR_INVALID_POINTER);
+ AssertPtrReturn(piConsumed, VERR_INVALID_POINTER);
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv,
+ g_aBalloonOpts, RT_ELEMENTS(g_aBalloonOpts),
+ 0 /* First */, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = 0; /* Set default parsing result to valid. */
+
+ int c;
+ RTGETOPTUNION ValueUnion;
+ while ((c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case GETOPTDEF_BALLOONCTRL_BALLOONDEC:
+ g_cMbMemoryBalloonDecrement = ValueUnion.u32;
+ break;
+
+ case GETOPTDEF_BALLOONCTRL_BALLOONINC:
+ g_cMbMemoryBalloonIncrement = ValueUnion.u32;
+ break;
+
+ case GETOPTDEF_BALLOONCTRL_GROUPS:
+ /** @todo Add ballooning groups cmd line arg. */
+ break;
+
+ case GETOPTDEF_BALLOONCTRL_BALLOONLOWERLIMIT:
+ g_cMbMemoryBalloonLowerLimit = ValueUnion.u32;
+ break;
+
+ case GETOPTDEF_BALLOONCTRL_BALLOONMAX:
+ g_cMbMemoryBalloonMax = ValueUnion.u32;
+ break;
+
+ case GETOPTDEF_BALLOONCTRL_BALLOONSAFETY:
+ g_cbMemoryBalloonSafety = ValueUnion.u32;
+ break;
+
+ /** @todo This option is a common module option! Put
+ * this into a utility function! */
+ case GETOPTDEF_BALLOONCTRL_TIMEOUTMS:
+ g_cMsMemoryBalloonTimeout = ValueUnion.u32;
+ if (g_cMsMemoryBalloonTimeout < 500)
+ g_cMsMemoryBalloonTimeout = 500;
+ break;
+
+ default:
+ rc = -1; /* We don't handle this option, skip. */
+ break;
+ }
+
+ /* At the moment we only process one option at a time. */
+ break;
+ }
+
+ *piConsumed += GetState.iNext - 1;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningInit(void)
+{
+ if (!g_cMsMemoryBalloonTimeout)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/BalloonCtrl/TimeoutMS", NULL /* Per-machine */,
+ &g_cMsMemoryBalloonTimeout, 30 * 1000 /* Default is 30 seconds timeout. */);
+
+ if (!g_cMbMemoryBalloonIncrement)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/BalloonCtrl/BalloonIncrementMB", NULL /* Per-machine */,
+ &g_cMbMemoryBalloonIncrement, 256);
+
+ if (!g_cMbMemoryBalloonDecrement)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/BalloonCtrl/BalloonDecrementMB", NULL /* Per-machine */,
+ &g_cMbMemoryBalloonDecrement, 128);
+
+ if (!g_cMbMemoryBalloonLowerLimit)
+ cfgGetValueU32(g_pVirtualBox, NULL /* Machine */,
+ "VBoxInternal2/Watchdog/BalloonCtrl/BalloonLowerLimitMB", NULL /* Per-machine */,
+ &g_cMbMemoryBalloonLowerLimit, 128);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningMain(void)
+{
+ static uint64_t s_msLast = UINT64_MAX;
+ if (s_msLast == UINT64_MAX)
+ s_msLast = RTTimeMilliTS();
+ else
+ {
+ uint64_t msDelta = RTTimeMilliTS() - s_msLast;
+ if (msDelta <= g_cMsMemoryBalloonTimeout)
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ /** @todo Provide API for enumerating/working w/ machines inside a module! */
+ mapVMIter it = g_mapVM.begin();
+ while (it != g_mapVM.end())
+ {
+ MachineState_T state = getMachineState(&it->second);
+
+ /* Our actual ballooning criteria. */
+ if (balloonIsPossible(state))
+ {
+ rc = balloonMachineUpdate(&it->second);
+ AssertRC(rc);
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ ++it;
+ }
+
+ s_msLast = RTTimeMilliTS();
+ return rc;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningStop(void)
+{
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(void) VBoxModBallooningTerm(void)
+{
+}
+
+static DECLCALLBACK(int) VBoxModBallooningOnMachineRegistered(const Bstr &strUuid)
+{
+ PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
+ AssertPtrReturn(pMachine, VERR_INVALID_PARAMETER);
+
+ PVBOXWATCHDOG_BALLOONCTRL_PAYLOAD pData;
+ int rc = payloadAlloc(pMachine, VBOX_MOD_BALLOONING_NAME,
+ sizeof(VBOXWATCHDOG_BALLOONCTRL_PAYLOAD), (void**)&pData);
+ if (RT_SUCCESS(rc))
+ rc = balloonMachineUpdate(pMachine);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningOnMachineUnregistered(const Bstr &strUuid)
+{
+ PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
+ AssertPtrReturn(pMachine, VERR_INVALID_PARAMETER);
+
+ payloadFree(pMachine, VBOX_MOD_BALLOONING_NAME);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) VBoxModBallooningOnMachineStateChanged(const Bstr &strUuid,
+ MachineState_T enmState)
+{
+ RT_NOREF(enmState);
+
+ PVBOXWATCHDOG_MACHINE pMachine = getMachine(strUuid);
+ /* Note: The machine state will change to "setting up" when machine gets deleted,
+ * so pMachine might be NULL here. */
+ if (!pMachine)
+ return VINF_SUCCESS;
+
+ return balloonMachineUpdate(pMachine);
+}
+
+static DECLCALLBACK(int) VBoxModBallooningOnServiceStateChanged(bool fAvailable)
+{
+ RT_NOREF(fAvailable);
+ return VINF_SUCCESS;
+}
+
+/**
+ * The 'balloonctrl' module description.
+ */
+VBOXMODULE g_ModBallooning =
+{
+ /* pszName. */
+ VBOX_MOD_BALLOONING_NAME,
+ /* pszDescription. */
+ "Memory Ballooning Control",
+ /* pszDepends. */
+ NULL,
+ /* uPriority. */
+ 0 /* Not used */,
+ /* pszUsage. */
+ " [--balloon-dec=<MB>] [--balloon-groups=<string>]\n"
+ " [--balloon-inc=<MB>] [--balloon-interval=<ms>]\n"
+ " [--balloon-lower-limit=<MB>] [--balloon-max=<MB>]\n"
+ " [--balloon-safety-margin=<MB]\n",
+ /* pszOptions. */
+ " --balloon-dec=<MB>\n"
+ " Sets the ballooning decrement in MB (128 MB).\n"
+ " --balloon-groups=<string>\n"
+ " Sets the VM groups for ballooning (all).\n"
+ " --balloon-inc=<MB>\n"
+ " Sets the ballooning increment in MB (256 MB).\n"
+ " --balloon-interval=<ms>\n"
+ " Sets the check interval in ms (30 seconds).\n"
+ " --balloon-lower-limit=<MB>\n"
+ " Sets the ballooning lower limit in MB (64 MB).\n"
+ " --balloon-max=<MB>\n"
+ " Sets the balloon maximum limit in MB (0 MB).\n"
+ " Specifying \"0\" means disabled ballooning.\n"
+#if 1
+ /* (Legacy) note. */
+ " Set \"VBoxInternal/Guest/BalloonSizeMax\" for a per-VM\n"
+ " maximum ballooning size.\n"
+#endif
+ " --balloon-safety-margin=<MB>\n"
+ " Free memory when deflating a balloon in MB (1024 MB).\n"
+ ,
+ /* methods. */
+ VBoxModBallooningPreInit,
+ VBoxModBallooningOption,
+ VBoxModBallooningInit,
+ VBoxModBallooningMain,
+ VBoxModBallooningStop,
+ VBoxModBallooningTerm,
+ /* callbacks. */
+ VBoxModBallooningOnMachineRegistered,
+ VBoxModBallooningOnMachineUnregistered,
+ VBoxModBallooningOnMachineStateChanged,
+ VBoxModBallooningOnServiceStateChanged
+};
+
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp
new file mode 100644
index 00000000..cefd7f7f
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp
@@ -0,0 +1,1217 @@
+/* $Id: VBoxWatchdog.cpp $ */
+/** @file
+ * VBoxWatchdog.cpp - VirtualBox Watchdog.
+ */
+
+/*
+ * Copyright (C) 2011-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 *
+*********************************************************************************************************************************/
+#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 <signal.h>
+
+#include "VBoxWatchdogInternal.h"
+
+using namespace com;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * 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;
+
+enum GETOPTDEF_WATCHDOG
+{
+ GETOPTDEF_WATCHDOG_DISABLE_MODULE = 1000,
+ GETOPTDEF_WATCHDOG_DRYRUN
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** 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;
+
+static VBOXWATCHDOGMOD g_aModules[] =
+{
+ { &g_ModBallooning, false /* Pre-inited */, true /* Enabled */ },
+ { &g_ModAPIMonitor, false /* Pre-inited */, true /* Enabled */ }
+};
+
+/**
+ * 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;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+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 hrc = pEvent->COMGETTER(Registered)(&fRegistered);
+ if (SUCCEEDED(hrc))
+ hrc = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ 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 hrc = pEvent->COMGETTER(State)(&machineState);
+ if (SUCCEEDED(hrc))
+ hrc = pEvent->COMGETTER(MachineId)(uuid.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ 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) RT_NOTHROW_DEF
+{
+ 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 hrc;
+
+ /** @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, hrc);
+ /* Keep going. */
+ }
+
+ } while (0);
+
+ /** @todo Add std exception handling! */
+
+ return SUCCEEDED(hrc) ? 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 hrc = 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))
+ hrc = VBOX_E_IPRT_ERROR;
+
+ } while (0);
+
+ return SUCCEEDED(hrc) ? 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"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\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: %s [-v|--verbose] [-h|-?|--help] [-P|--pidfile]\n"
+ " [-F|--logfile=<file>] [-R|--logrotate=<num>] \n"
+ " [-S|--logsize=<bytes>] [-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++)
+ {
+ 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;
+ 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);
+ }
+
+ 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 hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = g_pSession.createInprocObject(CLSID_Session);
+ if (FAILED(hrc))
+ RTMsgError("Failed to create a session object (rc=%Rhrc)!", hrc);
+ }
+ else
+ RTMsgError("Failed to get VirtualBox object (rc=%Rhrc)!", hrc);
+
+ if (FAILED(hrc))
+ 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 */
+}
+
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogInternal.h b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogInternal.h
new file mode 100644
index 00000000..9bac8e55
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogInternal.h
@@ -0,0 +1,257 @@
+/* $Id: VBoxWatchdogInternal.h $ */
+/** @file
+ * VBoxWatchdog - VirtualBox Watchdog Service.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_VBoxBalloonCtrl_VBoxWatchdogInternal_h
+#define VBOX_INCLUDED_SRC_VBoxBalloonCtrl_VBoxWatchdogInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifndef VBOX_ONLY_DOCS
+# include <iprt/getopt.h>
+# include <iprt/time.h>
+
+# include <VBox/err.h>
+# 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/VirtualBox.h>
+#endif /* !VBOX_ONLY_DOCS */
+
+#include <map>
+#include <vector>
+
+using namespace com;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// definitions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/** Command handler argument. */
+struct HandlerArg
+{
+ int argc;
+ char **argv;
+};
+
+/**
+ * A module's payload for a machine entry.
+ * The payload data is not (yet) thread safe -- so only
+ * use this in one module at a time only!
+ */
+typedef struct VBOXWATCHDOG_MODULE_PAYLOAD
+{
+ /** Pointer to allocated payload. Can be NULL if
+ * a module doesn't have an own payload. */
+ void *pvData;
+ /** Size of payload (in bytes). */
+ size_t cbData;
+ /** @todo Add mutex for locking + getPayloadLocked(). */
+} VBOXWATCHDOG_MODULE_PAYLOAD, *PVBOXWATCHDOG_MODULE_PAYLOAD;
+
+/**
+ * Map containing a module's individual payload -- the module itself
+ * is responsible for allocating/handling/destroying this payload.
+ * Primary key is the module name.
+ */
+typedef std::map<const char*, VBOXWATCHDOG_MODULE_PAYLOAD> mapPayload;
+typedef std::map<const char*, VBOXWATCHDOG_MODULE_PAYLOAD>::iterator mapPayloadIter;
+typedef std::map<const char*, VBOXWATCHDOG_MODULE_PAYLOAD>::const_iterator mapPayloadIterConst;
+
+/** Group list (plus additional per-group flags, not used yet) for one VM.
+ * Primary key is the group name, secondary specify flags (if any). */
+typedef std::map<Utf8Str, uint32_t> mapGroups;
+typedef std::map<Utf8Str, uint32_t>::iterator mapGroupsIter;
+typedef std::map<Utf8Str, uint32_t>::const_iterator mapGroupsIterConst;
+
+/** A machine's internal entry.
+ * Primary key is the machine's UUID. */
+typedef struct VBOXWATCHDOG_MACHINE
+{
+ ComPtr<IMachine> machine;
+ /** The machine's name. For logging. */
+ Bstr strName;
+#ifndef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ ComPtr<IPerformanceCollector> collector;
+#endif
+ /** The group(s) this machine belongs to. */
+ mapGroups groups;
+ /** Map containing the individual module payloads. */
+ mapPayload payload;
+} VBOXWATCHDOG_MACHINE, *PVBOXWATCHDOG_MACHINE;
+typedef std::map<Bstr, VBOXWATCHDOG_MACHINE> mapVM;
+typedef std::map<Bstr, VBOXWATCHDOG_MACHINE>::iterator mapVMIter;
+typedef std::map<Bstr, VBOXWATCHDOG_MACHINE>::const_iterator mapVMIterConst;
+
+/** Members of a VM group; currently only represented by the machine's UUID.
+ * Primary key is the machine's UUID. */
+typedef std::vector<Bstr> vecGroupMembers;
+typedef std::vector<Bstr>::iterator vecGroupMembersIter;
+typedef std::vector<Bstr>::const_iterator vecGroupMembersIterConst;
+
+/** A VM group. Can contain none, one or more group members.
+ * Primary key is the group's name. */
+typedef std::map<Utf8Str, vecGroupMembers> mapGroup;
+typedef std::map<Utf8Str, vecGroupMembers>::iterator mapGroupIter;
+typedef std::map<Utf8Str, vecGroupMembers>::const_iterator mapGroupIterConst;
+
+/**
+ * A module descriptor.
+ */
+typedef struct
+{
+ /** The short module name. */
+ const char *pszName;
+ /** The longer module name. */
+ const char *pszDescription;
+ /** A comma-separated list of modules this module
+ * depends on. Might be NULL if no dependencies. */
+ const char *pszDepends;
+ /** Priority (lower is higher, 0 is invalid) of
+ * module execution. */
+ uint32_t uPriority;
+ /** The usage options stuff for the --help screen. */
+ const char *pszUsage;
+ /** The option descriptions for the --help screen. */
+ const char *pszOptions;
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnPreInit,(void));
+
+ /**
+ * Tries to parse the given command line options.
+ *
+ * @returns 0 if we parsed, -1 if it didn't and anything else means exit.
+ * @param argc Argument count.
+ * @param argv Arguments.
+ * @param piConsumed How many parameters this callback consumed from the
+ * remaining arguments passed in.
+ */
+ DECLCALLBACKMEMBER(int, pfnOption,(int argc, char *argv[], int *piConsumed));
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit,(void));
+
+ /** Called from the watchdog's main function. Non-blocking.
+ *
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnMain,(void));
+
+ /**
+ * Stop the module.
+ */
+ DECLCALLBACKMEMBER(int, pfnStop,(void));
+
+ /**
+ * Does termination cleanups.
+ *
+ * @remarks This may be called even if pfnInit hasn't been called!
+ */
+ DECLCALLBACKMEMBER(void, pfnTerm,(void));
+
+ /** @name Callbacks.
+ * @{
+ */
+
+ /**
+ *
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnOnMachineRegistered,(const Bstr &strUuid));
+
+ /**
+ *
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnOnMachineUnregistered,(const Bstr &strUuid));
+
+ /**
+ *
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnOnMachineStateChanged,(const Bstr &strUuid, MachineState_T enmState));
+
+ /**
+ *
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnOnServiceStateChanged,(bool fAvailable));
+
+ /** @} */
+} VBOXMODULE;
+/** Pointer to a VBOXMODULE. */
+typedef VBOXMODULE *PVBOXMODULE;
+/** Pointer to a const VBOXMODULE. */
+typedef VBOXMODULE const *PCVBOXMODULE;
+
+RT_C_DECLS_BEGIN
+
+extern bool g_fDryrun;
+extern bool g_fVerbose;
+extern ComPtr<IVirtualBox> g_pVirtualBox;
+extern ComPtr<ISession> g_pSession;
+extern mapVM g_mapVM;
+extern mapGroup g_mapGroup;
+# ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+extern ComPtr<IPerformanceCollector> g_pPerfCollector;
+# endif
+
+extern VBOXMODULE g_ModBallooning;
+extern VBOXMODULE g_ModAPIMonitor;
+
+extern void serviceLog(const char *pszFormat, ...);
+#define serviceLogVerbose(a) if (g_fVerbose) { serviceLog a; }
+
+int groupAdd(mapGroups &groups, const char *pszGroupsToAdd, uint32_t fFlags);
+
+extern int getMetric(PVBOXWATCHDOG_MACHINE pMachine, const Bstr& strName, LONG *pulData);
+void* payloadFrom(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule);
+int payloadAlloc(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule, size_t cbSize, void **ppszPayload);
+void payloadFree(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule);
+
+PVBOXWATCHDOG_MACHINE getMachine(const Bstr& strUuid);
+MachineState_T getMachineState(const PVBOXWATCHDOG_MACHINE pMachine);
+
+int cfgGetValueStr(const ComPtr<IVirtualBox> &rptrVBox, const ComPtr<IMachine> &rptrMachine,
+ const char *pszGlobal, const char *pszVM, Utf8Str &strValue, Utf8Str strDefault);
+int cfgGetValueU32(const ComPtr<IVirtualBox> &rptrVBox, const ComPtr<IMachine> &rptrMachine,
+ const char *pszGlobal, const char *pszVM, uint32_t *puValue, uint32_t uDefault);
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_VBoxBalloonCtrl_VBoxWatchdogInternal_h */
+
diff --git a/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogUtils.cpp b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogUtils.cpp
new file mode 100644
index 00000000..a556343c
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogUtils.cpp
@@ -0,0 +1,278 @@
+/* $Id: */
+/** @file
+ * VBoxWatchdogUtils - Misc. utility functions for modules.
+ */
+
+/*
+ * Copyright (C) 2011-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/array.h>
+#include "VBoxWatchdogInternal.h"
+
+#include <iprt/sanitized/sstream>
+#include <algorithm>
+
+
+/**
+ * Adds a group / a set of groups to the specified map.
+ * If a group in the group map exists there will be no action.
+ *
+ * @return IPRT status code.
+ * @param groups Map to add group(s) to.
+ * @param pszGroupsToAdd Comma-separated string of one or more groups to add.
+ * @param fFlags Flags to set to the groups added.
+ */
+int groupAdd(mapGroups &groups, const char *pszGroupsToAdd, uint32_t fFlags)
+{
+ AssertPtrReturn(pszGroupsToAdd, VERR_INVALID_POINTER);
+
+ try
+ {
+ std::istringstream strGroups(pszGroupsToAdd);
+ for(std::string strToken; getline(strGroups, strToken, ','); )
+ {
+ strToken.erase(remove_if(strToken.begin(), strToken.end(), isspace), strToken.end());
+
+ Utf8Str strTokenUtf8(strToken.c_str());
+ mapGroupsIterConst it = groups.find(strTokenUtf8);
+
+ if (it == groups.end())
+ groups.insert(std::make_pair(strTokenUtf8, fFlags));
+ }
+ }
+ catch (...)
+ {
+ AssertFailed();
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves a metric from a specified machine.
+ *
+ * @return IPRT status code.
+ * @param pMachine Pointer to the machine's internal structure.
+ * @param strName Name of metric to retrieve.
+ * @param pulData Pointer to value to retrieve the actual metric value.
+ */
+int getMetric(PVBOXWATCHDOG_MACHINE pMachine, const Bstr& strName, LONG *pulData)
+{
+ AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
+ AssertPtrReturn(pulData, VERR_INVALID_POINTER);
+
+ /* Input. */
+ com::SafeArray<BSTR> metricNames(1);
+ com::SafeIfaceArray<IUnknown> metricObjects(1);
+ pMachine->machine.queryInterfaceTo(&metricObjects[0]);
+
+ /* Output. */
+ com::SafeArray<BSTR> retNames;
+ com::SafeIfaceArray<IUnknown> retObjects;
+ com::SafeArray<BSTR> retUnits;
+ com::SafeArray<ULONG> retScales;
+ com::SafeArray<ULONG> retSequenceNumbers;
+ com::SafeArray<ULONG> retIndices;
+ com::SafeArray<ULONG> retLengths;
+ com::SafeArray<LONG> retData;
+
+ /* Query current memory free. */
+ strName.cloneTo(&metricNames[0]);
+#ifdef VBOX_WATCHDOG_GLOBAL_PERFCOL
+ Assert(!g_pPerfCollector.isNull());
+ HRESULT hrc = g_pPerfCollector->QueryMetricsData(
+#else
+ Assert(!pMachine->collector.isNull());
+ HRESULT hrc = pMachine->collector->QueryMetricsData(
+#endif
+ ComSafeArrayAsInParam(metricNames),
+ ComSafeArrayAsInParam(metricObjects),
+ ComSafeArrayAsOutParam(retNames),
+ ComSafeArrayAsOutParam(retObjects),
+ ComSafeArrayAsOutParam(retUnits),
+ ComSafeArrayAsOutParam(retScales),
+ ComSafeArrayAsOutParam(retSequenceNumbers),
+ ComSafeArrayAsOutParam(retIndices),
+ ComSafeArrayAsOutParam(retLengths),
+ ComSafeArrayAsOutParam(retData));
+#if 0
+ /* Useful for metrics debugging. */
+ for (unsigned j = 0; j < retNames.size(); j++)
+ {
+ Bstr metricUnit(retUnits[j]);
+ Bstr metricName(retNames[j]);
+ RTPrintf("%-20ls ", metricName.raw());
+ const char *separator = "";
+ for (unsigned k = 0; k < retLengths[j]; k++)
+ {
+ if (retScales[j] == 1)
+ RTPrintf("%s%d %ls", separator, retData[retIndices[j] + k], metricUnit.raw());
+ else
+ RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[j] + k] / retScales[j],
+ (retData[retIndices[j] + k] * 100 / retScales[j]) % 100, metricUnit.raw());
+ separator = ", ";
+ }
+ RTPrintf("\n");
+ }
+#endif
+
+ if (SUCCEEDED(hrc))
+ *pulData = retData.size() ? retData[retIndices[0]] : 0;
+
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : VINF_NOT_SUPPORTED;
+}
+
+/**
+ * Returns the payload of a machine.
+ *
+ * @return void* Pointer to payload data. Mutable!
+ * @param pMachine Machine to get payload for.
+ * @param pszModule Module name to get payload from.
+ */
+void* payloadFrom(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule)
+{
+ AssertPtrReturn(pMachine, NULL);
+ AssertPtrReturn(pszModule, NULL);
+ mapPayloadIter it = pMachine->payload.find(pszModule);
+ if (it == pMachine->payload.end())
+ return NULL;
+ Assert(it->second.cbData);
+ return it->second.pvData;
+}
+
+int payloadAlloc(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule,
+ size_t cbSize, void **ppszPayload)
+{
+ AssertPtrReturn(pMachine, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszModule, VERR_INVALID_POINTER);
+ AssertReturn(cbSize, VERR_INVALID_PARAMETER);
+
+ void *pvData = RTMemAllocZ(cbSize);
+ AssertPtrReturn(pvData, VERR_NO_MEMORY);
+
+ mapPayloadIter it = pMachine->payload.find(pszModule);
+ AssertReturn(it == pMachine->payload.end(), VERR_INVALID_PARAMETER);
+
+ VBOXWATCHDOG_MODULE_PAYLOAD p;
+ p.pvData = pvData;
+ p.cbData = cbSize;
+
+ if (ppszPayload)
+ *ppszPayload = p.pvData;
+
+ pMachine->payload.insert(std::make_pair(pszModule, p));
+
+ return VINF_SUCCESS;
+}
+
+void payloadFree(PVBOXWATCHDOG_MACHINE pMachine, const char *pszModule)
+{
+ AssertPtrReturnVoid(pMachine);
+ AssertPtrReturnVoid(pszModule);
+
+ mapPayloadIter it = pMachine->payload.find(pszModule);
+ if (it != pMachine->payload.end())
+ {
+ RTMemFree(it->second.pvData);
+ pMachine->payload.erase(it);
+ }
+}
+
+PVBOXWATCHDOG_MACHINE getMachine(const Bstr& strUuid)
+{
+ mapVMIter it = g_mapVM.find(strUuid);
+ if (it != g_mapVM.end())
+ return &it->second;
+ return NULL;
+}
+
+MachineState_T getMachineState(const PVBOXWATCHDOG_MACHINE pMachine)
+{
+ AssertPtrReturn(pMachine, MachineState_Null);
+ MachineState_T machineState;
+ Assert(!pMachine->machine.isNull());
+ HRESULT rc = pMachine->machine->COMGETTER(State)(&machineState);
+ if (SUCCEEDED(rc))
+ return machineState;
+ return MachineState_Null;
+}
+
+int cfgGetValueStr(const ComPtr<IVirtualBox> &rptrVBox, const ComPtr<IMachine> &rptrMachine,
+ const char *pszGlobal, const char *pszVM, Utf8Str &strValue, Utf8Str strDefault)
+{
+ AssertReturn(!rptrVBox.isNull(), VERR_INVALID_POINTER);
+
+
+ /* Try per-VM approach. */
+ Bstr strTemp;
+ HRESULT hr;
+ if (!rptrMachine.isNull())
+ {
+ AssertPtr(pszVM);
+ hr = rptrMachine->GetExtraData(Bstr(pszVM).raw(),
+ strTemp.asOutParam());
+ if ( SUCCEEDED(hr)
+ && !strTemp.isEmpty())
+ {
+ strValue = Utf8Str(strTemp);
+ }
+ }
+
+ if (strValue.isEmpty()) /* Not set by per-VM value? */
+ {
+ AssertPtr(pszGlobal);
+
+ /* Try global approach. */
+ hr = rptrVBox->GetExtraData(Bstr(pszGlobal).raw(),
+ strTemp.asOutParam());
+ if ( SUCCEEDED(hr)
+ && !strTemp.isEmpty())
+ {
+ strValue = Utf8Str(strTemp);
+ }
+ }
+
+ if (strValue.isEmpty())
+ {
+ strValue = strDefault;
+ return VERR_NOT_FOUND;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int cfgGetValueU32(const ComPtr<IVirtualBox> &rptrVBox, const ComPtr<IMachine> &rptrMachine,
+ const char *pszGlobal, const char *pszVM, uint32_t *puValue, uint32_t uDefault)
+{
+ Utf8Str strValue;
+ int rc = cfgGetValueStr(rptrVBox, rptrMachine, pszGlobal, pszVM, strValue, "" /* Default */);
+ if (RT_SUCCESS(rc))
+ *puValue = strValue.toUInt32();
+ else
+ *puValue = uDefault;
+ return rc;
+}
+