diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Frontends/VBoxBalloonCtrl | |
parent | Initial commit. (diff) | |
download | virtualbox-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.kmk | 48 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxBalloonCtrl.rc | 61 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxModAPIMonitor.cpp | 666 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxModBallooning.cpp | 797 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdog.cpp | 1217 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogInternal.h | 257 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxBalloonCtrl/VBoxWatchdogUtils.cpp | 278 |
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; +} + |