diff options
Diffstat (limited to 'src/VBox/Main/src-client')
62 files changed, 77834 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp new file mode 100644 index 00000000..65b593bd --- /dev/null +++ b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp @@ -0,0 +1,231 @@ +/* $Id: AdditionsFacilityImpl.cpp $ */ +/** @file + * VirtualBox Main - Additions facility class. + */ + +/* + * Copyright (C) 2014-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_ADDITIONSFACILITY +#include "LoggingNew.h" + +#include "AdditionsFacilityImpl.h" +#include "Global.h" + +#include "AutoCaller.h" + + +/** + * @note We ASSUME that unknown is the first entry! + */ +/* static */ +const AdditionsFacility::FacilityInfo AdditionsFacility::s_aFacilityInfo[8] = +{ + { "Unknown", AdditionsFacilityType_None, AdditionsFacilityClass_None }, + { "VirtualBox Base Driver", AdditionsFacilityType_VBoxGuestDriver, AdditionsFacilityClass_Driver }, + { "Auto Logon", AdditionsFacilityType_AutoLogon, AdditionsFacilityClass_Feature }, + { "VirtualBox System Service", AdditionsFacilityType_VBoxService, AdditionsFacilityClass_Service }, + { "VirtualBox Desktop Integration", AdditionsFacilityType_VBoxTrayClient, AdditionsFacilityClass_Program }, + { "Seamless Mode", AdditionsFacilityType_Seamless, AdditionsFacilityClass_Feature }, + { "Graphics Mode", AdditionsFacilityType_Graphics, AdditionsFacilityClass_Feature }, + { "Guest Monitor Attach", AdditionsFacilityType_MonitorAttach, AdditionsFacilityClass_Feature }, +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(AdditionsFacility) + +HRESULT AdditionsFacility::FinalConstruct() +{ + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void AdditionsFacility::FinalRelease() +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +HRESULT AdditionsFacility::init(Guest *a_pParent, AdditionsFacilityType_T a_enmFacility, AdditionsFacilityStatus_T a_enmStatus, + uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + RT_NOREF(a_pParent); /** @todo r=bird: For locking perhaps? */ + LogFlowThisFunc(("a_pParent=%p\n", a_pParent)); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + /* Initialize the data: */ + mData.mType = a_enmFacility; + mData.mStatus = a_enmStatus; + mData.mTimestamp = *a_pTimeSpecTS; + mData.mfFlags = a_fFlags; + mData.midxInfo = 0; + for (size_t i = 0; i < RT_ELEMENTS(s_aFacilityInfo); ++i) + if (s_aFacilityInfo[i].mType == a_enmFacility) + { + mData.midxInfo = i; + break; + } + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance. + * + * Called from FinalRelease(). + */ +void AdditionsFacility::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; +} + +HRESULT AdditionsFacility::getClassType(AdditionsFacilityClass_T *aClassType) +{ + LogFlowThisFuncEnter(); + + /* midxInfo is static, so no need to lock anything. */ + size_t idxInfo = mData.midxInfo; + AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0); + *aClassType = s_aFacilityInfo[idxInfo].mClass; + return S_OK; +} + +HRESULT AdditionsFacility::getName(com::Utf8Str &aName) +{ + LogFlowThisFuncEnter(); + + /* midxInfo is static, so no need to lock anything. */ + size_t idxInfo = mData.midxInfo; + AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0); + int vrc = aName.assignNoThrow(s_aFacilityInfo[idxInfo].mName); + return RT_SUCCESS(vrc) ? S_OK : E_OUTOFMEMORY; +} + +HRESULT AdditionsFacility::getLastUpdated(LONG64 *aLastUpdated) +{ + LogFlowThisFuncEnter(); + + /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aLastUpdated = RTTimeSpecGetMilli(&mData.mTimestamp); + return S_OK; +} + +HRESULT AdditionsFacility::getStatus(AdditionsFacilityStatus_T *aStatus) +{ + LogFlowThisFuncEnter(); + + /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aStatus = mData.mStatus; + return S_OK; +} + +HRESULT AdditionsFacility::getType(AdditionsFacilityType_T *aType) +{ + LogFlowThisFuncEnter(); + + /* mType is static, so no need to lock anything. */ + *aType = mData.mType; + return S_OK; +} + +#if 0 /* unused */ + +AdditionsFacilityType_T AdditionsFacility::i_getType() const +{ + return mData.mType; +} + +AdditionsFacilityClass_T AdditionsFacility::i_getClass() const +{ + size_t idxInfo = mData.midxInfo; + AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0); + return s_aFacilityInfo[idxInfo].mClass; +} + +const char *AdditionsFacility::i_getName() const +{ + size_t idxInfo = mData.midxInfo; + AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0); + return s_aFacilityInfo[idxInfo].mName; +} + +#endif /* unused */ + +/** + * @note Caller should read lock the Guest object. + */ +LONG64 AdditionsFacility::i_getLastUpdated() const +{ + return RTTimeSpecGetMilli(&mData.mTimestamp); +} + +/** + * @note Caller should read lock the Guest object. + */ +AdditionsFacilityStatus_T AdditionsFacility::i_getStatus() const +{ + return mData.mStatus; +} + +/** + * Method used by IGuest::facilityUpdate to make updates. + * + * @returns change indicator. + * + * @todo r=bird: Locking here isn't quite sane. While updating is serialized + * by the caller holding down the Guest object lock, this code doesn't + * serialize with this object. So, the read locking done in the getter + * methods is utterly pointless. OTOH, the getter methods only get + * single values, so there isn't really much to be worried about here, + * especially with 32-bit hosts no longer being supported. + */ +bool AdditionsFacility::i_update(AdditionsFacilityStatus_T a_enmStatus, uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + bool const fChanged = mData.mStatus != a_enmStatus; + + mData.mTimestamp = *a_pTimeSpecTS; + mData.mStatus = a_enmStatus; + mData.mfFlags = a_fFlags; + + return fChanged; +} + diff --git a/src/VBox/Main/src-client/AudioDriver.cpp b/src/VBox/Main/src-client/AudioDriver.cpp new file mode 100644 index 00000000..c4876114 --- /dev/null +++ b/src/VBox/Main/src-client/AudioDriver.cpp @@ -0,0 +1,337 @@ +/* $Id: AudioDriver.cpp $ */ +/** @file + * VirtualBox audio base class for Main audio drivers. + */ + +/* + * Copyright (C) 2018-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include "LoggingNew.h" + +#include <VBox/log.h> +#include <VBox/vmm/cfgm.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmapi.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/vmmr3vtable.h> + +#include "AudioDriver.h" +#include "ConsoleImpl.h" + +AudioDriver::AudioDriver(Console *pConsole) + : mpConsole(pConsole) + , mfAttached(false) +{ +} + + +AudioDriver::~AudioDriver(void) +{ +} + + +AudioDriver &AudioDriver::operator=(AudioDriver const &a_rThat) RT_NOEXCEPT +{ + mpConsole = a_rThat.mpConsole; + mCfg = a_rThat.mCfg; + mfAttached = a_rThat.mfAttached; + + return *this; +} + + +/** + * Initializes the audio driver with a certain (device) configuration. + * + * @returns VBox status code. + * @param pCfg Audio driver configuration to use. + */ +int AudioDriver::InitializeConfig(AudioDriverCfg *pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + /* Sanity. */ + AssertReturn(pCfg->strDev.isNotEmpty(), VERR_INVALID_PARAMETER); + AssertReturn(pCfg->uLUN != UINT8_MAX, VERR_INVALID_PARAMETER); + AssertReturn(pCfg->strName.isNotEmpty(), VERR_INVALID_PARAMETER); + + /* Apply configuration. */ + mCfg = *pCfg; + + return VINF_SUCCESS; +} + + +/** + * Attaches the driver via EMT, if configured. + * + * @returns VBox status code. + * @param pUVM The user mode VM handle for talking to EMT. + * @param pVMM The VMM ring-3 vtable. + * @param pAutoLock The callers auto lock instance. Can be NULL if not + * locked. + */ +int AudioDriver::doAttachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock) +{ + if (!isConfigured()) + return VINF_SUCCESS; + + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)attachDriverOnEmt, 1, this); + if (vrc == VERR_TIMEOUT) + { + /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */ + if (pAutoLock) + pAutoLock->release(); + + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + + if (pAutoLock) + pAutoLock->acquire(); + } + + AssertRC(vrc); + pVMM->pfnVMR3ReqFree(pReq); + + return vrc; +} + + +/** + * Configures the audio driver (to CFGM) and attaches it to the audio chain. + * Does nothing if the audio driver already is attached. + * + * @returns VBox status code. + * @param pThis Audio driver to detach. + */ +/* static */ +DECLCALLBACK(int) AudioDriver::attachDriverOnEmt(AudioDriver *pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole); + Assert(ptrVM.isOk()); + + if (pThis->mfAttached) /* Already attached? Bail out. */ + { + LogFunc(("%s: Already attached\n", pThis->mCfg.strName.c_str())); + return VINF_SUCCESS; + } + + AudioDriverCfg *pCfg = &pThis->mCfg; + + LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n", + pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN)); + + /* Detach the driver chain from the audio device first. */ + int vrc = ptrVM.vtable()->pfnPDMR3DeviceDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, 0 /* fFlags */); + if (RT_SUCCESS(vrc)) + { + vrc = pThis->configure(pCfg->uLUN, true /* Attach */); + if (RT_SUCCESS(vrc)) + vrc = ptrVM.vtable()->pfnPDMR3DriverAttach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, + 0 /* fFlags */, NULL /* ppBase */); + } + + if (RT_SUCCESS(vrc)) + { + pThis->mfAttached = true; + LogRel2(("%s: Driver attached (LUN #%u)\n", pCfg->strName.c_str(), pCfg->uLUN)); + } + else + LogRel(("%s: Failed to attach audio driver, rc=%Rrc\n", pCfg->strName.c_str(), vrc)); + + LogFunc(("Returning %Rrc\n", vrc)); + return vrc; +} + + +/** + * Detatches the driver via EMT, if configured. + * + * @returns VBox status code. + * @param pUVM The user mode VM handle for talking to EMT. + * @param pVMM The VMM ring-3 vtable. + * @param pAutoLock The callers auto lock instance. Can be NULL if not + * locked. + */ +int AudioDriver::doDetachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock) +{ + if (!isConfigured()) + return VINF_SUCCESS; + + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)detachDriverOnEmt, 1, this); + if (vrc == VERR_TIMEOUT) + { + /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */ + if (pAutoLock) + pAutoLock->release(); + + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + + if (pAutoLock) + pAutoLock->acquire(); + } + + AssertRC(vrc); + pVMM->pfnVMR3ReqFree(pReq); + + return vrc; +} + + +/** + * Detaches an already attached audio driver from the audio chain. + * Does nothing if the audio driver already is detached or not attached. + * + * @returns VBox status code. + * @param pThis Audio driver to detach. + */ +/* static */ +DECLCALLBACK(int) AudioDriver::detachDriverOnEmt(AudioDriver *pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + if (!pThis->mfAttached) /* Not attached? Bail out. */ + { + LogFunc(("%s: Not attached\n", pThis->mCfg.strName.c_str())); + return VINF_SUCCESS; + } + + Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole); + Assert(ptrVM.isOk()); + + AudioDriverCfg *pCfg = &pThis->mCfg; + + Assert(pCfg->uLUN != UINT8_MAX); + + LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n", + pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN)); + + /* Destroy the entire driver chain for the specified LUN. + * + * Start with the "AUDIO" driver, as this driver serves as the audio connector between + * the device emulation and the select backend(s). */ + int vrc = ptrVM.vtable()->pfnPDMR3DriverDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, + "AUDIO", 0 /* iOccurrence */, 0 /* fFlags */); + if (RT_SUCCESS(vrc)) + vrc = pThis->configure(pCfg->uLUN, false /* Detach */);/** @todo r=bird: Illogical and from what I can tell pointless! */ + + if (RT_SUCCESS(vrc)) + { + pThis->mfAttached = false; + LogRel2(("%s: Driver detached\n", pCfg->strName.c_str())); + } + else + LogRel(("%s: Failed to detach audio driver, vrc=%Rrc\n", pCfg->strName.c_str(), vrc)); + + LogFunc(("Returning %Rrc\n", vrc)); + return vrc; +} + +/** + * Configures the audio driver via CFGM. + * + * @returns VBox status code. + * @param uLUN LUN to attach driver to. + * @param fAttach Whether to attach or detach the driver configuration to CFGM. + * + * @thread EMT + */ +int AudioDriver::configure(unsigned uLUN, bool fAttach) +{ + Console::SafeVMPtrQuiet ptrVM(mpConsole); + AssertReturn(ptrVM.isOk(), VERR_INVALID_STATE); + + PCFGMNODE pRoot = ptrVM.vtable()->pfnCFGMR3GetRootU(ptrVM.rawUVM()); + AssertPtr(pRoot); + PCFGMNODE pDev0 = ptrVM.vtable()->pfnCFGMR3GetChildF(pRoot, "Devices/%s/%u/", mCfg.strDev.c_str(), mCfg.uInst); + + if (!pDev0) /* No audio device configured? Bail out. */ + { + LogRel2(("%s: No audio device configured, skipping to attach driver\n", mCfg.strName.c_str())); + return VINF_SUCCESS; + } + + int vrc = VINF_SUCCESS; + + PCFGMNODE pDevLun = ptrVM.vtable()->pfnCFGMR3GetChildF(pDev0, "LUN#%u/", uLUN); + + if (fAttach) + { + do /* break "loop" */ + { + AssertMsgBreakStmt(pDevLun, ("%s: Device LUN #%u not found\n", mCfg.strName.c_str(), uLUN), vrc = VERR_NOT_FOUND); + + LogRel2(("%s: Configuring audio driver (to LUN #%u)\n", mCfg.strName.c_str(), uLUN)); + + ptrVM.vtable()->pfnCFGMR3RemoveNode(pDevLun); /* Remove LUN completely first. */ + + /* Insert new LUN configuration and build up the new driver chain. */ + vrc = ptrVM.vtable()->pfnCFGMR3InsertNodeF(pDev0, &pDevLun, "LUN#%u/", uLUN); AssertRCBreak(vrc); + vrc = ptrVM.vtable()->pfnCFGMR3InsertString(pDevLun, "Driver", "AUDIO"); AssertRCBreak(vrc); + + PCFGMNODE pLunCfg; + vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "Config", &pLunCfg); AssertRCBreak(vrc); + + vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pLunCfg, "DriverName", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc); + vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "InputEnabled", mCfg.fEnabledIn); AssertRCBreak(vrc); + vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "OutputEnabled", mCfg.fEnabledOut); AssertRCBreak(vrc); + + PCFGMNODE pAttachedDriver; + vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "AttachedDriver", &pAttachedDriver); AssertRCBreak(vrc); + vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pAttachedDriver, "Driver", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc); + PCFGMNODE pAttachedDriverCfg; + vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pAttachedDriver, "Config", &pAttachedDriverCfg); AssertRCBreak(vrc); + + /* Call the (virtual) method for driver-specific configuration. */ + vrc = configureDriver(pAttachedDriverCfg, ptrVM.vtable()); AssertRCBreak(vrc); + + } while (0); + } + else /* Detach */ + { + LogRel2(("%s: Unconfiguring audio driver\n", mCfg.strName.c_str())); + } + + if (RT_SUCCESS(vrc)) + { +#ifdef LOG_ENABLED + LogFunc(("%s: fAttach=%RTbool\n", mCfg.strName.c_str(), fAttach)); + ptrVM.vtable()->pfnCFGMR3Dump(pDevLun); +#endif + } + else + LogRel(("%s: %s audio driver failed with vrc=%Rrc\n", mCfg.strName.c_str(), fAttach ? "Configuring" : "Unconfiguring", vrc)); + + LogFunc(("Returning %Rrc\n", vrc)); + return vrc; +} + diff --git a/src/VBox/Main/src-client/BusAssignmentManager.cpp b/src/VBox/Main/src-client/BusAssignmentManager.cpp new file mode 100644 index 00000000..c21f2f53 --- /dev/null +++ b/src/VBox/Main/src-client/BusAssignmentManager.cpp @@ -0,0 +1,711 @@ +/* $Id: BusAssignmentManager.cpp $ */ +/** @file + * VirtualBox bus slots assignment manager + */ + +/* + * Copyright (C) 2010-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN +#include "LoggingNew.h" + +#include "BusAssignmentManager.h" + +#include <iprt/asm.h> +#include <iprt/string.h> + +#include <VBox/vmm/cfgm.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/com/array.h> + +#include <map> +#include <vector> +#include <algorithm> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +struct DeviceAssignmentRule +{ + const char *pszName; + int iBus; + int iDevice; + int iFn; + int iPriority; +}; + +struct DeviceAliasRule +{ + const char *pszDevName; + const char *pszDevAlias; +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* Those rules define PCI slots assignment */ +/** @note + * The EFI takes assumptions about PCI slot assignments which are different + * from the following tables in certain cases, for example the IDE device + * is assumed to be 00:01.1! */ + +/* Device Bus Device Function Priority */ + +/* Generic rules */ +static const DeviceAssignmentRule g_aGenericRules[] = +{ + /* VGA controller */ + {"vga", 0, 2, 0, 0}, + + /* VMM device */ + {"VMMDev", 0, 4, 0, 0}, + + /* Audio controllers */ + {"ichac97", 0, 5, 0, 0}, + {"hda", 0, 5, 0, 0}, + + /* Storage controllers */ + {"buslogic", 0, 21, 0, 1}, + {"lsilogicsas", 0, 22, 0, 1}, + {"nvme", 0, 14, 0, 1}, + {"virtio-scsi", 0, 15, 0, 1}, + + /* USB controllers */ + {"usb-ohci", 0, 6, 0, 0}, + {"usb-ehci", 0, 11, 0, 0}, + {"usb-xhci", 0, 12, 0, 0}, + + /* ACPI controller */ +#if 0 + // It really should be this for 440FX chipset (part of PIIX4 actually) + {"acpi", 0, 1, 3, 0}, +#else + {"acpi", 0, 7, 0, 0}, +#endif + + /* Network controllers */ + /* the first network card gets the PCI ID 3, the next 3 gets 8..10, + * next 4 get 16..19. In "VMWare compatibility" mode the IDs 3 and 17 + * swap places, i.e. the first card goes to ID 17=0x11. */ + {"nic", 0, 3, 0, 1}, + {"nic", 0, 8, 0, 1}, + {"nic", 0, 9, 0, 1}, + {"nic", 0, 10, 0, 1}, + {"nic", 0, 16, 0, 1}, + {"nic", 0, 17, 0, 1}, + {"nic", 0, 18, 0, 1}, + {"nic", 0, 19, 0, 1}, + + /* ISA/LPC controller */ + {"lpc", 0, 31, 0, 0}, + + { NULL, -1, -1, -1, 0} +}; + +/* PIIX3 chipset rules */ +static const DeviceAssignmentRule g_aPiix3Rules[] = +{ + {"piix3ide", 0, 1, 1, 0}, + {"ahci", 0, 13, 0, 1}, + {"lsilogic", 0, 20, 0, 1}, + {"pcibridge", 0, 24, 0, 0}, + {"pcibridge", 0, 25, 0, 0}, + { NULL, -1, -1, -1, 0} +}; + + +/* ICH9 chipset rules */ +static const DeviceAssignmentRule g_aIch9Rules[] = +{ + /* Host Controller */ + {"i82801", 0, 30, 0, 0}, + + /* Those are functions of LPC at 00:1e:00 */ + /** + * Please note, that for devices being functions, like we do here, device 0 + * must be multifunction, i.e. have header type 0x80. Our LPC device is. + * Alternative approach is to assign separate slot to each device. + */ + {"piix3ide", 0, 31, 1, 2}, + {"ahci", 0, 31, 2, 2}, + {"smbus", 0, 31, 3, 2}, + {"usb-ohci", 0, 31, 4, 2}, + {"usb-ehci", 0, 31, 5, 2}, + {"thermal", 0, 31, 6, 2}, + + /* to make sure rule never used before rules assigning devices on it */ + {"ich9pcibridge", 0, 24, 0, 10}, + {"ich9pcibridge", 0, 25, 0, 10}, + {"ich9pcibridge", 2, 24, 0, 9}, /* Bridges must be instantiated depth */ + {"ich9pcibridge", 2, 25, 0, 9}, /* first (assumption in PDM and other */ + {"ich9pcibridge", 4, 24, 0, 8}, /* places), so make sure that nested */ + {"ich9pcibridge", 4, 25, 0, 8}, /* bridges are added to the last bridge */ + {"ich9pcibridge", 6, 24, 0, 7}, /* only, avoiding the need to re-sort */ + {"ich9pcibridge", 6, 25, 0, 7}, /* everything before starting the VM. */ + {"ich9pcibridge", 8, 24, 0, 6}, + {"ich9pcibridge", 8, 25, 0, 6}, + {"ich9pcibridge", 10, 24, 0, 5}, + {"ich9pcibridge", 10, 25, 0, 5}, + + /* Storage controllers */ + {"ahci", 1, 0, 0, 0}, + {"ahci", 1, 1, 0, 0}, + {"ahci", 1, 2, 0, 0}, + {"ahci", 1, 3, 0, 0}, + {"ahci", 1, 4, 0, 0}, + {"ahci", 1, 5, 0, 0}, + {"ahci", 1, 6, 0, 0}, + {"lsilogic", 1, 7, 0, 0}, + {"lsilogic", 1, 8, 0, 0}, + {"lsilogic", 1, 9, 0, 0}, + {"lsilogic", 1, 10, 0, 0}, + {"lsilogic", 1, 11, 0, 0}, + {"lsilogic", 1, 12, 0, 0}, + {"lsilogic", 1, 13, 0, 0}, + {"buslogic", 1, 14, 0, 0}, + {"buslogic", 1, 15, 0, 0}, + {"buslogic", 1, 16, 0, 0}, + {"buslogic", 1, 17, 0, 0}, + {"buslogic", 1, 18, 0, 0}, + {"buslogic", 1, 19, 0, 0}, + {"buslogic", 1, 20, 0, 0}, + {"lsilogicsas", 1, 21, 0, 0}, + {"lsilogicsas", 1, 26, 0, 0}, + {"lsilogicsas", 1, 27, 0, 0}, + {"lsilogicsas", 1, 28, 0, 0}, + {"lsilogicsas", 1, 29, 0, 0}, + {"lsilogicsas", 1, 30, 0, 0}, + {"lsilogicsas", 1, 31, 0, 0}, + + /* NICs */ + {"nic", 2, 0, 0, 0}, + {"nic", 2, 1, 0, 0}, + {"nic", 2, 2, 0, 0}, + {"nic", 2, 3, 0, 0}, + {"nic", 2, 4, 0, 0}, + {"nic", 2, 5, 0, 0}, + {"nic", 2, 6, 0, 0}, + {"nic", 2, 7, 0, 0}, + {"nic", 2, 8, 0, 0}, + {"nic", 2, 9, 0, 0}, + {"nic", 2, 10, 0, 0}, + {"nic", 2, 11, 0, 0}, + {"nic", 2, 12, 0, 0}, + {"nic", 2, 13, 0, 0}, + {"nic", 2, 14, 0, 0}, + {"nic", 2, 15, 0, 0}, + {"nic", 2, 16, 0, 0}, + {"nic", 2, 17, 0, 0}, + {"nic", 2, 18, 0, 0}, + {"nic", 2, 19, 0, 0}, + {"nic", 2, 20, 0, 0}, + {"nic", 2, 21, 0, 0}, + {"nic", 2, 26, 0, 0}, + {"nic", 2, 27, 0, 0}, + {"nic", 2, 28, 0, 0}, + {"nic", 2, 29, 0, 0}, + {"nic", 2, 30, 0, 0}, + {"nic", 2, 31, 0, 0}, + + /* Storage controller #2 (NVMe, virtio-scsi) */ + {"nvme", 3, 0, 0, 0}, + {"nvme", 3, 1, 0, 0}, + {"nvme", 3, 2, 0, 0}, + {"nvme", 3, 3, 0, 0}, + {"nvme", 3, 4, 0, 0}, + {"nvme", 3, 5, 0, 0}, + {"nvme", 3, 6, 0, 0}, + {"virtio-scsi", 3, 7, 0, 0}, + {"virtio-scsi", 3, 8, 0, 0}, + {"virtio-scsi", 3, 9, 0, 0}, + {"virtio-scsi", 3, 10, 0, 0}, + {"virtio-scsi", 3, 11, 0, 0}, + {"virtio-scsi", 3, 12, 0, 0}, + {"virtio-scsi", 3, 13, 0, 0}, + + { NULL, -1, -1, -1, 0} +}; + + +#ifdef VBOX_WITH_IOMMU_AMD +/* + * AMD IOMMU and LSI Logic controller rules. + * + * Since the PCI slot (BDF=00:20.0) of the LSI Logic controller + * conflicts with the SB I/O APIC, we assign the LSI Logic controller + * to device number 23 when the VM is configured for an AMD IOMMU. + */ +static const DeviceAssignmentRule g_aIch9IommuAmdRules[] = +{ + /* AMD IOMMU. */ + {"iommu-amd", 0, 0, 0, 0}, + /* AMD IOMMU: Reserved for southbridge I/O APIC. */ + {"sb-ioapic", 0, 20, 0, 0}, + + /* Storage controller */ + {"lsilogic", 0, 23, 0, 1}, + { NULL, -1, -1, -1, 0} +}; +#endif + +#ifdef VBOX_WITH_IOMMU_INTEL +/* + * Intel IOMMU. + * The VT-d misc, address remapping, system management device is + * located at BDF 0:5:0 on real hardware but we use 0:1:0 since that + * slot isn't used for anything else. + * + * While we could place the I/O APIC anywhere, we keep it consistent + * with the AMD IOMMU and we assign the LSI Logic controller to + * device number 23 (and I/O APIC at device 20). + */ +static const DeviceAssignmentRule g_aIch9IommuIntelRules[] = +{ + /* Intel IOMMU. */ + {"iommu-intel", 0, 1, 0, 0}, + /* Intel IOMMU: Reserved for I/O APIC. */ + {"sb-ioapic", 0, 20, 0, 0}, + + /* Storage controller */ + {"lsilogic", 0, 23, 0, 1}, + { NULL, -1, -1, -1, 0} +}; +#endif + +/* LSI Logic Controller. */ +static const DeviceAssignmentRule g_aIch9LsiRules[] = +{ + /* Storage controller */ + {"lsilogic", 0, 20, 0, 1}, + { NULL, -1, -1, -1, 0} +}; + +/* Aliasing rules */ +static const DeviceAliasRule g_aDeviceAliases[] = +{ + {"e1000", "nic"}, + {"pcnet", "nic"}, + {"virtio-net", "nic"}, + {"ahci", "storage"}, + {"lsilogic", "storage"}, + {"buslogic", "storage"}, + {"lsilogicsas", "storage"}, + {"nvme", "storage"}, + {"virtio-scsi", "storage"} +}; + + + +/** + * Bus assignment manage state data. + * @internal + */ +struct BusAssignmentManager::State +{ + struct PCIDeviceRecord + { + char szDevName[32]; + PCIBusAddress HostAddress; + + PCIDeviceRecord(const char *pszName, PCIBusAddress aHostAddress) + { + RTStrCopy(this->szDevName, sizeof(szDevName), pszName); + this->HostAddress = aHostAddress; + } + + PCIDeviceRecord(const char *pszName) + { + RTStrCopy(this->szDevName, sizeof(szDevName), pszName); + } + + bool operator<(const PCIDeviceRecord &a) const + { + return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) < 0; + } + + bool operator==(const PCIDeviceRecord &a) const + { + return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) == 0; + } + }; + + typedef std::map<PCIBusAddress,PCIDeviceRecord> PCIMap; + typedef std::vector<PCIBusAddress> PCIAddrList; + typedef std::vector<const DeviceAssignmentRule *> PCIRulesList; + typedef std::map<PCIDeviceRecord,PCIAddrList> ReversePCIMap; + + volatile int32_t cRefCnt; + ChipsetType_T mChipsetType; + const char * mpszBridgeName; + IommuType_T mIommuType; + PCIMap mPCIMap; + ReversePCIMap mReversePCIMap; + PCVMMR3VTABLE mpVMM; + + State() + : cRefCnt(1), mChipsetType(ChipsetType_Null), mpszBridgeName("unknownbridge"), mpVMM(NULL) + {} + ~State() + {} + + HRESULT init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType); + + HRESULT record(const char *pszName, PCIBusAddress& GuestAddress, PCIBusAddress HostAddress); + HRESULT autoAssign(const char *pszName, PCIBusAddress& Address); + bool checkAvailable(PCIBusAddress& Address); + bool findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address); + + const char *findAlias(const char *pszName); + void addMatchingRules(const char *pszName, PCIRulesList& aList); + void listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached); +}; + + +HRESULT BusAssignmentManager::State::init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType) +{ + mpVMM = pVMM; + + if (iommuType != IommuType_None) + { +#if defined(VBOX_WITH_IOMMU_AMD) && defined(VBOX_WITH_IOMMU_INTEL) + Assert(iommuType == IommuType_AMD || iommuType == IommuType_Intel); +#elif defined(VBOX_WITH_IOMMU_AMD) + Assert(iommuType == IommuType_AMD); +#elif defined(VBOX_WITH_IOMMU_INTEL) + Assert(iommuType == IommuType_Intel); +#endif + } + + mChipsetType = chipsetType; + mIommuType = iommuType; + switch (chipsetType) + { + case ChipsetType_PIIX3: + mpszBridgeName = "pcibridge"; + break; + case ChipsetType_ICH9: + mpszBridgeName = "ich9pcibridge"; + break; + default: + mpszBridgeName = "unknownbridge"; + AssertFailed(); + break; + } + return S_OK; +} + +HRESULT BusAssignmentManager::State::record(const char *pszName, PCIBusAddress& Address, PCIBusAddress HostAddress) +{ + PCIDeviceRecord devRec(pszName, HostAddress); + + /* Remember address -> device mapping */ + mPCIMap.insert(PCIMap::value_type(Address, devRec)); + + ReversePCIMap::iterator it = mReversePCIMap.find(devRec); + if (it == mReversePCIMap.end()) + { + mReversePCIMap.insert(ReversePCIMap::value_type(devRec, PCIAddrList())); + it = mReversePCIMap.find(devRec); + } + + /* Remember device name -> addresses mapping */ + it->second.push_back(Address); + + return S_OK; +} + +bool BusAssignmentManager::State::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address) +{ + PCIDeviceRecord devRec(pszDevName); + + ReversePCIMap::iterator it = mReversePCIMap.find(devRec); + if (it == mReversePCIMap.end()) + return false; + + if (iInstance >= (int)it->second.size()) + return false; + + Address = it->second[iInstance]; + return true; +} + +void BusAssignmentManager::State::addMatchingRules(const char *pszName, PCIRulesList& aList) +{ + size_t iRuleset, iRule; + const DeviceAssignmentRule *aArrays[3] = {g_aGenericRules, NULL, NULL}; + + switch (mChipsetType) + { + case ChipsetType_PIIX3: + aArrays[1] = g_aPiix3Rules; + break; + case ChipsetType_ICH9: + { + aArrays[1] = g_aIch9Rules; +#ifdef VBOX_WITH_IOMMU_AMD + if (mIommuType == IommuType_AMD) + aArrays[2] = g_aIch9IommuAmdRules; + else +#endif +#ifdef VBOX_WITH_IOMMU_INTEL + if (mIommuType == IommuType_Intel) + aArrays[2] = g_aIch9IommuIntelRules; + else +#endif + { + aArrays[2] = g_aIch9LsiRules; + } + break; + } + default: + AssertFailed(); + break; + } + + for (iRuleset = 0; iRuleset < RT_ELEMENTS(aArrays); iRuleset++) + { + if (aArrays[iRuleset] == NULL) + continue; + + for (iRule = 0; aArrays[iRuleset][iRule].pszName != NULL; iRule++) + { + if (RTStrCmp(pszName, aArrays[iRuleset][iRule].pszName) == 0) + aList.push_back(&aArrays[iRuleset][iRule]); + } + } +} + +const char *BusAssignmentManager::State::findAlias(const char *pszDev) +{ + for (size_t iAlias = 0; iAlias < RT_ELEMENTS(g_aDeviceAliases); iAlias++) + { + if (strcmp(pszDev, g_aDeviceAliases[iAlias].pszDevName) == 0) + return g_aDeviceAliases[iAlias].pszDevAlias; + } + return NULL; +} + +static bool RuleComparator(const DeviceAssignmentRule *r1, const DeviceAssignmentRule *r2) +{ + return (r1->iPriority > r2->iPriority); +} + +HRESULT BusAssignmentManager::State::autoAssign(const char *pszName, PCIBusAddress& Address) +{ + PCIRulesList matchingRules; + + addMatchingRules(pszName, matchingRules); + const char *pszAlias = findAlias(pszName); + if (pszAlias) + addMatchingRules(pszAlias, matchingRules); + + AssertMsg(matchingRules.size() > 0, ("No rule for %s(%s)\n", pszName, pszAlias)); + + stable_sort(matchingRules.begin(), matchingRules.end(), RuleComparator); + + for (size_t iRule = 0; iRule < matchingRules.size(); iRule++) + { + const DeviceAssignmentRule *rule = matchingRules[iRule]; + + Address.miBus = rule->iBus; + Address.miDevice = rule->iDevice; + Address.miFn = rule->iFn; + + if (checkAvailable(Address)) + return S_OK; + } + AssertLogRelMsgFailed(("BusAssignmentManager: All possible candidate positions for %s exhausted\n", pszName)); + + return E_INVALIDARG; +} + +bool BusAssignmentManager::State::checkAvailable(PCIBusAddress& Address) +{ + PCIMap::const_iterator it = mPCIMap.find(Address); + + return (it == mPCIMap.end()); +} + +void BusAssignmentManager::State::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached) +{ + aAttached.resize(mPCIMap.size()); + + size_t i = 0; + PCIDeviceInfo dev; + for (PCIMap::const_iterator it = mPCIMap.begin(); it != mPCIMap.end(); ++it, ++i) + { + dev.strDeviceName = it->second.szDevName; + dev.guestAddress = it->first; + dev.hostAddress = it->second.HostAddress; + aAttached[i] = dev; + } +} + +BusAssignmentManager::BusAssignmentManager() + : pState(NULL) +{ + pState = new State(); + Assert(pState); +} + +BusAssignmentManager::~BusAssignmentManager() +{ + if (pState) + { + delete pState; + pState = NULL; + } +} + +BusAssignmentManager *BusAssignmentManager::createInstance(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType) +{ + BusAssignmentManager *pInstance = new BusAssignmentManager(); + pInstance->pState->init(pVMM, chipsetType, iommuType); + Assert(pInstance); + return pInstance; +} + +void BusAssignmentManager::AddRef() +{ + ASMAtomicIncS32(&pState->cRefCnt); +} + +void BusAssignmentManager::Release() +{ + if (ASMAtomicDecS32(&pState->cRefCnt) == 0) + delete this; +} + +DECLINLINE(HRESULT) InsertConfigInteger(PCVMMR3VTABLE pVMM, PCFGMNODE pCfg, const char *pszName, uint64_t u64) +{ + int vrc = pVMM->pfnCFGMR3InsertInteger(pCfg, pszName, u64); + if (RT_FAILURE(vrc)) + return E_INVALIDARG; + + return S_OK; +} + +DECLINLINE(HRESULT) InsertConfigNode(PCVMMR3VTABLE pVMM, PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild) +{ + int vrc = pVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild); + if (RT_FAILURE(vrc)) + return E_INVALIDARG; + + return S_OK; +} + + +HRESULT BusAssignmentManager::assignPCIDeviceImpl(const char *pszDevName, + PCFGMNODE pCfg, + PCIBusAddress& GuestAddress, + PCIBusAddress HostAddress, + bool fGuestAddressRequired) +{ + HRESULT hrc = S_OK; + + if (!GuestAddress.valid()) + hrc = pState->autoAssign(pszDevName, GuestAddress); + else + { + bool fAvailable = pState->checkAvailable(GuestAddress); + + if (!fAvailable) + { + if (fGuestAddressRequired) + hrc = E_ACCESSDENIED; + else + hrc = pState->autoAssign(pszDevName, GuestAddress); + } + } + + if (FAILED(hrc)) + return hrc; + + Assert(GuestAddress.valid() && pState->checkAvailable(GuestAddress)); + + hrc = pState->record(pszDevName, GuestAddress, HostAddress); + if (FAILED(hrc)) + return hrc; + + PCVMMR3VTABLE const pVMM = pState->mpVMM; + if (pCfg) + { + hrc = InsertConfigInteger(pVMM, pCfg, "PCIBusNo", GuestAddress.miBus); + if (FAILED(hrc)) + return hrc; + hrc = InsertConfigInteger(pVMM, pCfg, "PCIDeviceNo", GuestAddress.miDevice); + if (FAILED(hrc)) + return hrc; + hrc = InsertConfigInteger(pVMM, pCfg, "PCIFunctionNo", GuestAddress.miFn); + if (FAILED(hrc)) + return hrc; + } + + /* Check if the bus is still unknown, i.e. the bridge to it is missing */ + if ( GuestAddress.miBus > 0 + && !hasPCIDevice(pState->mpszBridgeName, GuestAddress.miBus - 1)) + { + PCFGMNODE pDevices = pVMM->pfnCFGMR3GetParent(pVMM->pfnCFGMR3GetParent(pCfg)); + AssertLogRelMsgReturn(pDevices, ("BusAssignmentManager: cannot find base device configuration\n"), E_UNEXPECTED); + PCFGMNODE pBridges = pVMM->pfnCFGMR3GetChild(pDevices, "ich9pcibridge"); + AssertLogRelMsgReturn(pBridges, ("BusAssignmentManager: cannot find bridge configuration base\n"), E_UNEXPECTED); + + /* Device should be on a not yet existing bus, add it automatically */ + for (int iBridge = 0; iBridge <= GuestAddress.miBus - 1; iBridge++) + { + if (!hasPCIDevice(pState->mpszBridgeName, iBridge)) + { + PCIBusAddress BridgeGuestAddress; + hrc = pState->autoAssign(pState->mpszBridgeName, BridgeGuestAddress); + if (FAILED(hrc)) + return hrc; + if (BridgeGuestAddress.miBus > iBridge) + AssertLogRelMsgFailedReturn(("BusAssignmentManager: cannot create bridge for bus %i because the possible parent bus positions are exhausted\n", iBridge + 1), E_UNEXPECTED); + + PCFGMNODE pInst; + InsertConfigNode(pVMM, pBridges, Utf8StrFmt("%d", iBridge).c_str(), &pInst); + InsertConfigInteger(pVMM, pInst, "Trusted", 1); + hrc = assignPCIDevice(pState->mpszBridgeName, pInst); + if (FAILED(hrc)) + return hrc; + } + } + } + + return S_OK; +} + + +bool BusAssignmentManager::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address) +{ + return pState->findPCIAddress(pszDevName, iInstance, Address); +} +void BusAssignmentManager::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached) +{ + pState->listAttachedPCIDevices(aAttached); +} diff --git a/src/VBox/Main/src-client/ClientTokenHolder.cpp b/src/VBox/Main/src-client/ClientTokenHolder.cpp new file mode 100644 index 00000000..7a85c90c --- /dev/null +++ b/src/VBox/Main/src-client/ClientTokenHolder.cpp @@ -0,0 +1,347 @@ +/* $Id: ClientTokenHolder.cpp $ */ +/** @file + * + * VirtualBox API client session token holder (in the client process) + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_SESSION +#include "LoggingNew.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/log.h> +#include <iprt/semaphore.h> +#include <iprt/process.h> + +#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER +# include <errno.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/ipc.h> +# include <sys/sem.h> +#endif + +#include <VBox/com/defs.h> + +#include "ClientTokenHolder.h" +#include "SessionImpl.h" + + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +/** client token holder thread */ +static DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser); +#endif + + +Session::ClientTokenHolder::ClientTokenHolder() +{ + AssertReleaseFailed(); +} + +Session::ClientTokenHolder::~ClientTokenHolder() +{ + /* release the client token */ +#if defined(RT_OS_WINDOWS) + + if (mSem && mThreadSem) + { + /* + * tell the thread holding the token to release it; + * it will close mSem handle + */ + ::SetEvent(mSem); + /* wait for the thread to finish */ + ::WaitForSingleObject(mThreadSem, INFINITE); + ::CloseHandle(mThreadSem); + + mThreadSem = NULL; + mSem = NULL; + mThread = NIL_RTTHREAD; + } + +#elif defined(RT_OS_OS2) + + if (mThread != NIL_RTTHREAD) + { + Assert(mSem != NIL_RTSEMEVENT); + + /* tell the thread holding the token to release it */ + int vrc = RTSemEventSignal(mSem); + AssertRC(vrc == NO_ERROR); + + /* wait for the thread to finish */ + vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT); + Assert(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED); + + mThread = NIL_RTTHREAD; + } + + if (mSem != NIL_RTSEMEVENT) + { + RTSemEventDestroy(mSem); + mSem = NIL_RTSEMEVENT; + } + +#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) + + if (mSem >= 0) + { + ::sembuf sop = { 0, 1, SEM_UNDO }; + ::semop(mSem, &sop, 1); + + mSem = -1; + } + +#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER) + + if (!mToken.isNull()) + { + mToken->Abandon(); + mToken.setNull(); + } + +#else +# error "Port me!" +#endif +} + +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER +Session::ClientTokenHolder::ClientTokenHolder(const Utf8Str &strTokenId) : + mClientTokenId(strTokenId) +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ +Session::ClientTokenHolder::ClientTokenHolder(IToken *aToken) : + mToken(aToken) +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ +{ +#ifdef CTHSEMTYPE + mSem = CTHSEMARG; +#endif +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + mThread = NIL_RTTHREAD; +#endif + +#if defined(RT_OS_WINDOWS) + mThreadSem = CTHTHREADSEMARG; + + /* + * Since there is no guarantee that the constructor and destructor will be + * called in the same thread, we need a separate thread to hold the token. + */ + + mThreadSem = ::CreateEvent(NULL, FALSE, FALSE, NULL); + AssertMsgReturnVoid(mThreadSem, + ("Cannot create an event sem, err=%d", ::GetLastError())); + + void *data[3]; + data[0] = (void*)strTokenId.c_str(); + data[1] = (void*)mThreadSem; + data[2] = 0; /* will get an output from the thread */ + + /* create a thread to hold the token until signalled to release it */ + int vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void*)data, 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder"); + AssertRCReturnVoid(vrc); + + /* wait until thread init is completed */ + DWORD wrc = ::WaitForSingleObject(mThreadSem, INFINITE); + AssertMsg(wrc == WAIT_OBJECT_0, ("Wait failed, err=%d\n", ::GetLastError())); + Assert(data[2]); + + if (wrc == WAIT_OBJECT_0 && data[2]) + { + /* memorize the event sem we should signal in close() */ + mSem = (HANDLE)data[2]; + } + else + { + ::CloseHandle(mThreadSem); + mThreadSem = NULL; + } +#elif defined(RT_OS_OS2) + /* + * Since there is no guarantee that the constructor and destructor will be + * called in the same thread, we need a separate thread to hold the token. + */ + + int vrc = RTSemEventCreate(&mSem); + AssertRCReturnVoid(vrc); + + void *data[3]; + data[0] = (void*)strTokenId.c_str(); + data[1] = (void*)mSem; + data[2] = (void*)false; /* will get the thread result here */ + + /* create a thread to hold the token until signalled to release it */ + vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void *) data, + 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder"); + AssertRCReturnVoid(vrc); + /* wait until thread init is completed */ + vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT); + AssertReturnVoid(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED); + + /* the thread must succeed */ + AssertReturnVoid((bool)data[2]); + +#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) + +# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN + key_t key = RTStrToUInt32(strTokenId.c_str()); + AssertMsgReturnVoid(key != 0, + ("Key value of 0 is not valid for client token")); +# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ + char *pszSemName = NULL; + RTStrUtf8ToCurrentCP(&pszSemName, strTokenId); + key_t key = ::ftok(pszSemName, 'V'); + RTStrFree(pszSemName); +# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ + int s = ::semget(key, 0, 0); + AssertMsgReturnVoid(s >= 0, + ("Cannot open semaphore, errno=%d", errno)); + + /* grab the semaphore */ + ::sembuf sop = { 0, -1, SEM_UNDO }; + int rv = ::semop(s, &sop, 1); + AssertMsgReturnVoid(rv == 0, + ("Cannot grab semaphore, errno=%d", errno)); + mSem = s; + +#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER) + + /* nothing to do */ + +#else +# error "Port me!" +#endif +} + +bool Session::ClientTokenHolder::isReady() +{ +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + return mSem != CTHSEMARG; +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + return !mToken.isNull(); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ +} + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +/** client token holder thread */ +DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + LogFlowFuncEnter(); + + Assert(pvUser); + + void **data = (void **)pvUser; + +# if defined(RT_OS_WINDOWS) + Utf8Str strSessionId = (const char *)data[0]; + HANDLE initDoneSem = (HANDLE)data[1]; + + Bstr bstrSessionId(strSessionId); + HANDLE mutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, bstrSessionId.raw()); + + //AssertMsg(mutex, ("cannot open token, err=%u\n", ::GetLastError())); + AssertLogRelMsg(mutex, ("cannot open token %ls, err=%u\n", bstrSessionId.raw(), ::GetLastError())); + if (mutex) + { + /* grab the token */ + DWORD wrc = ::WaitForSingleObject(mutex, 0); + AssertMsg(wrc == WAIT_OBJECT_0, ("cannot grab token, err=%d\n", wrc)); + if (wrc == WAIT_OBJECT_0) + { + HANDLE finishSem = ::CreateEvent(NULL, FALSE, FALSE, NULL); + AssertMsg(finishSem, ("cannot create event sem, err=%d\n", ::GetLastError())); + if (finishSem) + { + data[2] = (void*)finishSem; + /* signal we're done with init */ + ::SetEvent(initDoneSem); + /* wait until we're signaled to release the token */ + ::WaitForSingleObject(finishSem, INFINITE); + /* release the token */ + LogFlow(("ClientTokenHolderThread(): releasing token...\n")); + BOOL fRc = ::ReleaseMutex(mutex); + AssertMsg(fRc, ("cannot release token, err=%d\n", ::GetLastError())); NOREF(fRc); + ::CloseHandle(mutex); + ::CloseHandle(finishSem); + } + } + } + + /* signal we're done */ + ::SetEvent(initDoneSem); +# elif defined(RT_OS_OS2) + Utf8Str strSessionId = (const char *)data[0]; + RTSEMEVENT finishSem = (RTSEMEVENT)data[1]; + + LogFlowFunc(("strSessionId='%s', finishSem=%p\n", strSessionId.c_str(), finishSem)); + + HMTX mutex = NULLHANDLE; + APIRET arc = ::DosOpenMutexSem((PSZ)strSessionId.c_str(), &mutex); + AssertMsg(arc == NO_ERROR, ("cannot open token, arc=%ld\n", arc)); + + if (arc == NO_ERROR) + { + /* grab the token */ + LogFlowFunc(("grabbing token...\n")); + arc = ::DosRequestMutexSem(mutex, SEM_IMMEDIATE_RETURN); + AssertMsg(arc == NO_ERROR, ("cannot grab token, arc=%ld\n", arc)); + if (arc == NO_ERROR) + { + /* store the answer */ + data[2] = (void*)true; + /* signal we're done */ + int vrc = RTThreadUserSignal(Thread); + AssertRC(vrc); + + /* wait until we're signaled to release the token */ + LogFlowFunc(("waiting for termination signal..\n")); + vrc = RTSemEventWait(finishSem, RT_INDEFINITE_WAIT); + Assert(arc == ERROR_INTERRUPT || ERROR_TIMEOUT); + + /* release the token */ + LogFlowFunc(("releasing token...\n")); + arc = ::DosReleaseMutexSem(mutex); + AssertMsg(arc == NO_ERROR, ("cannot release token, arc=%ld\n", arc)); + } + ::DosCloseMutexSem(mutex); + } + + /* store the answer */ + data[1] = (void*)false; + /* signal we're done */ + int vrc = RTThreadUserSignal(Thread); + AssertRC(vrc); +# else +# error "Port me!" +# endif + + LogFlowFuncLeave(); + + return 0; +} +#endif + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/CloudGateway.cpp b/src/VBox/Main/src-client/CloudGateway.cpp new file mode 100644 index 00000000..60a468b1 --- /dev/null +++ b/src/VBox/Main/src-client/CloudGateway.cpp @@ -0,0 +1,309 @@ +/* $Id: CloudGateway.cpp $ */ +/** @file + * Implementation of local and cloud gateway management. + */ + +/* + * Copyright (C) 2019-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE + +/* Make sure all the stdint.h macros are included - must come first! */ +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS +#endif + +#include "LoggingNew.h" +#include "ApplianceImpl.h" +#include "CloudNetworkImpl.h" +#include "CloudGateway.h" + +#include <iprt/http.h> +#include <iprt/inifile.h> +#include <iprt/net.h> +#include <iprt/path.h> +#include <iprt/vfs.h> +#include <iprt/uri.h> +#ifdef DEBUG +#include <iprt/file.h> +#include <VBox/com/utils.h> +#endif + +#ifdef VBOX_WITH_LIBSSH +/* Prevent inclusion of Winsock2.h */ +#define _WINSOCK2API_ +#include <libssh/libssh.h> +#endif /* VBOX_WITH_LIBSSH */ + + +static HRESULT setMacAddress(const Utf8Str& str, RTMAC& mac) +{ + int rc = RTNetStrToMacAddr(str.c_str(), &mac); + if (RT_FAILURE(rc)) + { + LogRel(("CLOUD-NET: Invalid MAC address '%s'\n", str.c_str())); + return E_INVALIDARG; + } + return S_OK; +} + + +HRESULT GatewayInfo::setCloudMacAddress(const Utf8Str& mac) +{ + return setMacAddress(mac, mCloudMacAddress); +} + + +HRESULT GatewayInfo::setLocalMacAddress(const Utf8Str& mac) +{ + return setMacAddress(mac, mLocalMacAddress); +} + + +class CloudError +{ +public: + CloudError(HRESULT hrc, const Utf8Str& strText) : mHrc(hrc), mText(strText) {}; + HRESULT getRc() { return mHrc; }; + Utf8Str getText() { return mText; }; + +private: + HRESULT mHrc; + Utf8Str mText; +}; + + +static void handleErrors(HRESULT hrc, const char *pszFormat, ...) +{ + if (FAILED(hrc)) + { + va_list va; + va_start(va, pszFormat); + Utf8Str strError(pszFormat, va); + va_end(va); + LogRel(("CLOUD-NET: %s (rc=%x)\n", strError.c_str(), hrc)); + throw CloudError(hrc, strError); + } + +} + + +class CloudClient +{ +public: + CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile); + ~CloudClient() {}; + + void startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway); + void stopCloudGateway(const GatewayInfo& gateway); + +private: + ComPtr<ICloudProviderManager> mManager; + ComPtr<ICloudProvider> mProvider; + ComPtr<ICloudProfile> mProfile; + ComPtr<ICloudClient> mClient; +}; + + +CloudClient::CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile) +{ + HRESULT hrc = virtualBox->COMGETTER(CloudProviderManager)(mManager.asOutParam()); + handleErrors(hrc, "Failed to obtain cloud provider manager object"); + hrc = mManager->GetProviderByShortName(strProvider.raw(), mProvider.asOutParam()); + handleErrors(hrc, "Failed to obtain cloud provider '%ls'", strProvider.raw()); + hrc = mProvider->GetProfileByName(strProfile.raw(), mProfile.asOutParam()); + handleErrors(hrc, "Failed to obtain cloud profile '%ls'", strProfile.raw()); + hrc = mProfile->CreateCloudClient(mClient.asOutParam()); + handleErrors(hrc, "Failed to create cloud client"); +} + + +void CloudClient::startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway) +{ + ComPtr<IProgress> progress; + ComPtr<ICloudNetworkGatewayInfo> gatewayInfo; + HRESULT hrc = mClient->StartCloudNetworkGateway(network, Bstr(gateway.mPublicSshKey).raw(), + gatewayInfo.asOutParam(), progress.asOutParam()); + handleErrors(hrc, "Failed to launch compute instance"); + hrc = progress->WaitForCompletion(-1); + handleErrors(hrc, "Failed to launch compute instance (wait)"); + + Bstr instanceId; + hrc = gatewayInfo->COMGETTER(InstanceId)(instanceId.asOutParam()); + handleErrors(hrc, "Failed to get launched compute instance id"); + gateway.mGatewayInstanceId = instanceId; + + Bstr publicIP; + hrc = gatewayInfo->COMGETTER(PublicIP)(publicIP.asOutParam()); + handleErrors(hrc, "Failed to get cloud gateway public IP address"); + gateway.mCloudPublicIp = publicIP; + + Bstr secondaryPublicIP; + hrc = gatewayInfo->COMGETTER(SecondaryPublicIP)(secondaryPublicIP.asOutParam()); + handleErrors(hrc, "Failed to get cloud gateway secondary public IP address"); + gateway.mCloudSecondaryPublicIp = secondaryPublicIP; + + Bstr macAddress; + hrc = gatewayInfo->COMGETTER(MacAddress)(macAddress.asOutParam()); + handleErrors(hrc, "Failed to get cloud gateway public IP address"); + gateway.setCloudMacAddress(macAddress); +} + + +void CloudClient::stopCloudGateway(const GatewayInfo& gateway) +{ + ComPtr<IProgress> progress; + HRESULT hrc = mClient->TerminateInstance(Bstr(gateway.mGatewayInstanceId).raw(), progress.asOutParam()); + handleErrors(hrc, "Failed to terminate compute instance"); +#if 0 + /* Someday we may want to wait until the cloud gateway has terminated. */ + hrc = progress->WaitForCompletion(-1); + handleErrors(hrc, "Failed to terminate compute instance (wait)"); +#endif +} + + +HRESULT startCloudGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& gateway) +{ + HRESULT hrc = S_OK; + + try { + hrc = network->COMGETTER(Provider)(gateway.mCloudProvider.asOutParam()); + hrc = network->COMGETTER(Profile)(gateway.mCloudProfile.asOutParam()); + CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile); + client.startCloudGateway(network, gateway); + } + catch (CloudError e) + { + hrc = e.getRc(); + } + + return hrc; +} + + +HRESULT stopCloudGateway(ComPtr<IVirtualBox> virtualBox, GatewayInfo& gateway) +{ + if (gateway.mGatewayInstanceId.isEmpty()) + return S_OK; + + LogRel(("CLOUD-NET: Terminating cloud gateway instance '%s'...\n", gateway.mGatewayInstanceId.c_str())); + + HRESULT hrc = S_OK; + try { + CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile); + client.stopCloudGateway(gateway); +#if 0 +# ifdef DEBUG + char szKeyPath[RTPATH_MAX]; + + int rc = GetVBoxUserHomeDirectory(szKeyPath, sizeof(szKeyPath), false /* fCreateDir */); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(szKeyPath, sizeof(szKeyPath), "gateway-key.pem"); + AssertRCReturn(rc, rc); + rc = RTFileDelete(szKeyPath); + if (RT_FAILURE(rc)) + LogRel(("WARNING! Failed to delete private key %s with rc=%d\n", szKeyPath, rc)); + } + else + LogRel(("WARNING! Failed to get VirtualBox user home directory with '%Rrc'\n", rc)); +# endif /* DEBUG */ +#endif + } + catch (CloudError e) + { + hrc = e.getRc(); + LogRel(("CLOUD-NET: Failed to terminate cloud gateway instance (rc=%x).\n", hrc)); + } + gateway.mGatewayInstanceId.setNull(); + return hrc; +} + + +HRESULT generateKeys(GatewayInfo& gateway) +{ +#ifndef VBOX_WITH_LIBSSH + RT_NOREF(gateway); + return E_NOTIMPL; +#else /* VBOX_WITH_LIBSSH */ + ssh_key single_use_key; + int rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &single_use_key); + if (rc != SSH_OK) + { + LogRel(("Failed to generate a key pair. rc = %d\n", rc)); + return E_FAIL; + } + + char *pstrKey = NULL; + rc = ssh_pki_export_privkey_base64(single_use_key, NULL, NULL, NULL, &pstrKey); + if (rc != SSH_OK) + { + LogRel(("Failed to export private key. rc = %d\n", rc)); + return E_FAIL; + } + gateway.mPrivateSshKey = pstrKey; +#if 0 +# ifdef DEBUG + char szConfigPath[RTPATH_MAX]; + + rc = GetVBoxUserHomeDirectory(szConfigPath, sizeof(szConfigPath), false /* fCreateDir */); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(szConfigPath, sizeof(szConfigPath), "gateway-key.pem"); + AssertRCReturn(rc, rc); + rc = ssh_pki_export_privkey_file(single_use_key, NULL, NULL, NULL, szConfigPath); + if (rc != SSH_OK) + { + LogRel(("Failed to export private key to %s with rc=%d\n", szConfigPath, rc)); + return E_FAIL; + } +# ifndef RT_OS_WINDOWS + rc = RTPathSetMode(szConfigPath, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); /* Satisfy ssh client */ + AssertRCReturn(rc, rc); +# endif + } + else + { + LogRel(("Failed to get VirtualBox user home directory with '%Rrc'\n", rc)); + return E_FAIL; + } +# endif /* DEBUG */ +#endif + ssh_string_free_char(pstrKey); + pstrKey = NULL; + rc = ssh_pki_export_pubkey_base64(single_use_key, &pstrKey); + if (rc != SSH_OK) + { + LogRel(("Failed to export public key. rc = %d\n", rc)); + return E_FAIL; + } + gateway.mPublicSshKey = Utf8StrFmt("ssh-rsa %s single-use-key", pstrKey); + ssh_string_free_char(pstrKey); + ssh_key_free(single_use_key); + + return S_OK; +#endif /* VBOX_WITH_LIBSSH */ +} diff --git a/src/VBox/Main/src-client/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp new file mode 100644 index 00000000..dbde726f --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImpl.cpp @@ -0,0 +1,11964 @@ +/* $Id: ConsoleImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation + */ + +/* + * Copyright (C) 2005-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE +#include "LoggingNew.h" + +/** @todo Move the TAP mess back into the driver! */ +#if defined(RT_OS_WINDOWS) +#elif defined(RT_OS_LINUX) +# include <errno.h> +# include <sys/ioctl.h> +# include <sys/poll.h> +# include <sys/fcntl.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <net/if.h> +# include <linux/if_tun.h> +# include <stdio.h> +# include <stdlib.h> +# include <string.h> +#elif defined(RT_OS_FREEBSD) +# include <errno.h> +# include <sys/ioctl.h> +# include <sys/poll.h> +# include <sys/fcntl.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <stdio.h> +# include <stdlib.h> +# include <string.h> +#elif defined(RT_OS_SOLARIS) +# include <iprt/coredumper.h> +#endif + +#include "ConsoleImpl.h" + +#include "Global.h" +#include "VirtualBoxErrorInfoImpl.h" +#include "GuestImpl.h" +#include "KeyboardImpl.h" +#include "MouseImpl.h" +#include "DisplayImpl.h" +#include "MachineDebuggerImpl.h" +#include "USBDeviceImpl.h" +#include "RemoteUSBDeviceImpl.h" +#include "SharedFolderImpl.h" +#ifdef VBOX_WITH_AUDIO_VRDE +# include "DrvAudioVRDE.h" +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING +# include "DrvAudioRec.h" +#endif +#ifdef VBOX_WITH_USB_CARDREADER +# include "UsbCardReader.h" +#endif +#include "ProgressImpl.h" +#include "ConsoleVRDPServer.h" +#include "VMMDev.h" +#ifdef VBOX_WITH_EXTPACK +# include "ExtPackManagerImpl.h" +#endif +#include "BusAssignmentManager.h" +#include "PCIDeviceAttachmentImpl.h" +#include "EmulatedUSBImpl.h" +#include "NvramStoreImpl.h" +#include "StringifyEnums.h" + +#include "VBoxEvents.h" +#include "AutoCaller.h" +#include "ThreadTask.h" + +#ifdef VBOX_WITH_RECORDING +# include "Recording.h" +#endif + +#include "CryptoUtils.h" + +#include <VBox/com/array.h> +#include "VBox/com/ErrorInfo.h" +#include <VBox/com/listeners.h> + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/cpp/utils.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/ldr.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/base64.h> +#include <iprt/memsafer.h> + +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/vmm/vmm.h> +#include <VBox/vmm/pdmapi.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmasynccompletion.h> +#include <VBox/vmm/pdmnetifs.h> +#include <VBox/vmm/pdmstorageifs.h> +#ifdef VBOX_WITH_USB +# include <VBox/vmm/pdmusb.h> +#endif +#ifdef VBOX_WITH_NETSHAPER +# include <VBox/vmm/pdmnetshaper.h> +#endif /* VBOX_WITH_NETSHAPER */ +#include <VBox/vmm/mm.h> +#include <VBox/vmm/ssm.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/vusb.h> + +#include <VBox/VMMDev.h> + +#ifdef VBOX_WITH_SHARED_CLIPBOARD +# include <VBox/HostServices/VBoxClipboardSvc.h> +#endif +#include <VBox/HostServices/DragAndDropSvc.h> +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +# include <VBox/com/array.h> +#endif + +#ifdef VBOX_OPENSSL_FIPS +# include <openssl/crypto.h> +#endif + +#include <set> +#include <algorithm> +#include <memory> // for auto_ptr +#include <vector> +#include <exception>// std::exception + +// VMTask and friends +//////////////////////////////////////////////////////////////////////////////// + +/** + * Task structure for asynchronous VM operations. + * + * Once created, the task structure adds itself as a Console caller. This means: + * + * 1. The user must check for #rc() before using the created structure + * (e.g. passing it as a thread function argument). If #rc() returns a + * failure, the Console object may not be used by the task. + * 2. On successful initialization, the structure keeps the Console caller + * until destruction (to ensure Console remains in the Ready state and won't + * be accidentally uninitialized). Forgetting to delete the created task + * will lead to Console::uninit() stuck waiting for releasing all added + * callers. + * + * If \a aUsesVMPtr parameter is true, the task structure will also add itself + * as a Console::mpUVM caller with the same meaning as above. See + * Console::addVMCaller() for more info. + */ +class VMTask: public ThreadTask +{ +public: + VMTask(Console *aConsole, + Progress *aProgress, + const ComPtr<IProgress> &aServerProgress, + bool aUsesVMPtr) + : ThreadTask("GenericVMTask"), + mConsole(aConsole), + mConsoleCaller(aConsole), + mProgress(aProgress), + mServerProgress(aServerProgress), + mRC(E_FAIL), + mpSafeVMPtr(NULL) + { + AssertReturnVoid(aConsole); + mRC = mConsoleCaller.rc(); + if (FAILED(mRC)) + return; + if (aUsesVMPtr) + { + mpSafeVMPtr = new Console::SafeVMPtr(aConsole); + if (!mpSafeVMPtr->isOk()) + mRC = mpSafeVMPtr->rc(); + } + } + + virtual ~VMTask() + { + releaseVMCaller(); + } + + HRESULT rc() const { return mRC; } + bool isOk() const { return SUCCEEDED(rc()); } + + /** Releases the VM caller before destruction. Not normally necessary. */ + void releaseVMCaller() + { + if (mpSafeVMPtr) + { + delete mpSafeVMPtr; + mpSafeVMPtr = NULL; + } + } + + const ComObjPtr<Console> mConsole; + AutoCaller mConsoleCaller; + const ComObjPtr<Progress> mProgress; + Utf8Str mErrorMsg; + const ComPtr<IProgress> mServerProgress; + +private: + HRESULT mRC; + Console::SafeVMPtr *mpSafeVMPtr; +}; + + +class VMPowerUpTask : public VMTask +{ +public: + VMPowerUpTask(Console *aConsole, + Progress *aProgress) + : VMTask(aConsole, aProgress, NULL /* aServerProgress */, false /* aUsesVMPtr */) + , mpfnConfigConstructor(NULL) + , mStartPaused(false) + , mTeleporterEnabled(FALSE) + , m_pKeyStore(NULL) + { + m_strTaskName = "VMPwrUp"; + } + + PFNCFGMCONSTRUCTOR mpfnConfigConstructor; + Utf8Str mSavedStateFile; + Utf8Str mKeyStore; + Utf8Str mKeyId; + Console::SharedFolderDataMap mSharedFolders; + bool mStartPaused; + BOOL mTeleporterEnabled; + SecretKeyStore *m_pKeyStore; + + /* array of progress objects for hard disk reset operations */ + typedef std::list<ComPtr<IProgress> > ProgressList; + ProgressList hardDiskProgresses; + + void handler() + { + Console::i_powerUpThreadTask(this); + } + +}; + +class VMPowerDownTask : public VMTask +{ +public: + VMPowerDownTask(Console *aConsole, + const ComPtr<IProgress> &aServerProgress) + : VMTask(aConsole, NULL /* aProgress */, aServerProgress, + true /* aUsesVMPtr */) + { + m_strTaskName = "VMPwrDwn"; + } + + void handler() + { + Console::i_powerDownThreadTask(this); + } +}; + +// Handler for global events +//////////////////////////////////////////////////////////////////////////////// +inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType); + +class VmEventListener +{ +public: + VmEventListener() + {} + + + HRESULT init(Console *aConsole) + { + mConsole = aConsole; + return S_OK; + } + + void uninit() + { + } + + virtual ~VmEventListener() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) + { + switch(aType) + { + case VBoxEventType_OnNATRedirect: + { + ComPtr<IMachine> pMachine = mConsole->i_machine(); + ComPtr<INATRedirectEvent> pNREv = aEvent; + HRESULT rc = E_FAIL; + Assert(pNREv); + + Bstr id; + rc = pNREv->COMGETTER(MachineId)(id.asOutParam()); + AssertComRC(rc); + if (id != mConsole->i_getId()) + break; + + /* now we can operate with redirects */ + NATProtocol_T proto = (NATProtocol_T)0; + pNREv->COMGETTER(Proto)(&proto); + BOOL fRemove; + pNREv->COMGETTER(Remove)(&fRemove); + Bstr hostIp; + pNREv->COMGETTER(HostIP)(hostIp.asOutParam()); + LONG hostPort = 0; + pNREv->COMGETTER(HostPort)(&hostPort); + Bstr guestIp; + pNREv->COMGETTER(GuestIP)(guestIp.asOutParam()); + LONG guestPort = 0; + pNREv->COMGETTER(GuestPort)(&guestPort); + ULONG ulSlot; + rc = pNREv->COMGETTER(Slot)(&ulSlot); + AssertComRC(rc); + if (FAILED(rc)) + break; + mConsole->i_onNATRedirectRuleChanged(ulSlot, fRemove, proto, hostIp.raw(), hostPort, guestIp.raw(), guestPort); + break; + } + + case VBoxEventType_OnHostNameResolutionConfigurationChange: + { + mConsole->i_onNATDnsChanged(); + break; + } + + case VBoxEventType_OnHostPCIDevicePlug: + { + // handle if needed + break; + } + + case VBoxEventType_OnExtraDataChanged: + { + ComPtr<IExtraDataChangedEvent> pEDCEv = aEvent; + Bstr strMachineId; + HRESULT hrc = pEDCEv->COMGETTER(MachineId)(strMachineId.asOutParam()); + if (FAILED(hrc)) break; + + Bstr strKey; + hrc = pEDCEv->COMGETTER(Key)(strKey.asOutParam()); + if (FAILED(hrc)) break; + + Bstr strVal; + hrc = pEDCEv->COMGETTER(Value)(strVal.asOutParam()); + if (FAILED(hrc)) break; + + mConsole->i_onExtraDataChange(strMachineId.raw(), strKey.raw(), strVal.raw()); + break; + } + + default: + AssertFailed(); + } + + return S_OK; + } +private: + ComObjPtr<Console> mConsole; +}; + +typedef ListenerImpl<VmEventListener, Console*> VmEventListenerImpl; + + +VBOX_LISTENER_DECLARE(VmEventListenerImpl) + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +Console::Console() + : mSavedStateDataLoaded(false) + , mConsoleVRDPServer(NULL) + , mfVRDEChangeInProcess(false) + , mfVRDEChangePending(false) + , mhModVMM(NIL_RTLDRMOD) + , mpVMM(NULL) + , mpUVM(NULL) + , mVMCallers(0) + , mVMZeroCallersSem(NIL_RTSEMEVENT) + , mVMDestroying(false) + , mVMPoweredOff(false) + , mVMIsAlreadyPoweringOff(false) + , mfSnapshotFolderSizeWarningShown(false) + , mfSnapshotFolderExt4WarningShown(false) + , mfSnapshotFolderDiskTypeShown(false) + , mfVMHasUsbController(false) + , mfTurnResetIntoPowerOff(false) + , mfPowerOffCausedByReset(false) + , mpVmm2UserMethods(NULL) + , m_pVMMDev(NULL) + , mAudioVRDE(NULL) +#ifdef VBOX_WITH_USB_CARDREADER + , mUsbCardReader(NULL) +#endif + , mBusMgr(NULL) + , m_pKeyStore(NULL) + , mpIfSecKey(NULL) + , mpIfSecKeyHlp(NULL) + , mVMStateChangeCallbackDisabled(false) + , mfUseHostClipboard(true) + , mMachineState(MachineState_PoweredOff) + , mhLdrModCrypto(NIL_RTLDRMOD) + , mcRefsCrypto(0) + , mpCryptoIf(NULL) +{ +} + +Console::~Console() +{} + +HRESULT Console::FinalConstruct() +{ + LogFlowThisFunc(("\n")); + + mcLedSets = 0; + RT_ZERO(maLedSets); + + MYVMM2USERMETHODS *pVmm2UserMethods = (MYVMM2USERMETHODS *)RTMemAllocZ(sizeof(*mpVmm2UserMethods) + sizeof(Console *)); + if (!pVmm2UserMethods) + return E_OUTOFMEMORY; + pVmm2UserMethods->u32Magic = VMM2USERMETHODS_MAGIC; + pVmm2UserMethods->u32Version = VMM2USERMETHODS_VERSION; + pVmm2UserMethods->pfnSaveState = Console::i_vmm2User_SaveState; + pVmm2UserMethods->pfnNotifyEmtInit = Console::i_vmm2User_NotifyEmtInit; + pVmm2UserMethods->pfnNotifyEmtTerm = Console::i_vmm2User_NotifyEmtTerm; + pVmm2UserMethods->pfnNotifyPdmtInit = Console::i_vmm2User_NotifyPdmtInit; + pVmm2UserMethods->pfnNotifyPdmtTerm = Console::i_vmm2User_NotifyPdmtTerm; + pVmm2UserMethods->pfnNotifyResetTurnedIntoPowerOff = Console::i_vmm2User_NotifyResetTurnedIntoPowerOff; + pVmm2UserMethods->pfnQueryGenericObject = Console::i_vmm2User_QueryGenericObject; + pVmm2UserMethods->u32EndMagic = VMM2USERMETHODS_MAGIC; + pVmm2UserMethods->pConsole = this; + mpVmm2UserMethods = pVmm2UserMethods; + + MYPDMISECKEY *pIfSecKey = (MYPDMISECKEY *)RTMemAllocZ(sizeof(*mpIfSecKey) + sizeof(Console *)); + if (!pIfSecKey) + return E_OUTOFMEMORY; + pIfSecKey->pfnKeyRetain = Console::i_pdmIfSecKey_KeyRetain; + pIfSecKey->pfnKeyRelease = Console::i_pdmIfSecKey_KeyRelease; + pIfSecKey->pfnPasswordRetain = Console::i_pdmIfSecKey_PasswordRetain; + pIfSecKey->pfnPasswordRelease = Console::i_pdmIfSecKey_PasswordRelease; + pIfSecKey->pConsole = this; + mpIfSecKey = pIfSecKey; + + MYPDMISECKEYHLP *pIfSecKeyHlp = (MYPDMISECKEYHLP *)RTMemAllocZ(sizeof(*mpIfSecKeyHlp) + sizeof(Console *)); + if (!pIfSecKeyHlp) + return E_OUTOFMEMORY; + pIfSecKeyHlp->pfnKeyMissingNotify = Console::i_pdmIfSecKeyHlp_KeyMissingNotify; + pIfSecKeyHlp->pConsole = this; + mpIfSecKeyHlp = pIfSecKeyHlp; + + mRemoteUsbIf.pvUser = this; + mRemoteUsbIf.pfnQueryRemoteUsbBackend = Console::i_usbQueryRemoteUsbBackend; + + return BaseFinalConstruct(); +} + +void Console::FinalRelease() +{ + LogFlowThisFunc(("\n")); + + uninit(); + + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** @todo r=bird: aLockType is always LockType_VM. */ +HRESULT Console::initWithMachine(IMachine *aMachine, IInternalMachineControl *aControl, LockType_T aLockType) +{ + AssertReturn(aMachine && aControl, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + LogFlowThisFuncEnter(); + LogFlowThisFunc(("aMachine=%p, aControl=%p\n", aMachine, aControl)); + + HRESULT rc = E_FAIL; + + unconst(mMachine) = aMachine; + unconst(mControl) = aControl; + + /* Cache essential properties and objects, and create child objects */ + + rc = mMachine->COMGETTER(State)(&mMachineState); + AssertComRCReturnRC(rc); + + rc = mMachine->COMGETTER(Id)(mstrUuid.asOutParam()); + AssertComRCReturnRC(rc); + +#ifdef VBOX_WITH_EXTPACK + unconst(mptrExtPackManager).createObject(); + rc = mptrExtPackManager->initExtPackManager(NULL, VBOXEXTPACKCTX_VM_PROCESS); + AssertComRCReturnRC(rc); +#endif + + // Event source may be needed by other children + unconst(mEventSource).createObject(); + rc = mEventSource->init(); + AssertComRCReturnRC(rc); + + mcAudioRefs = 0; + mcVRDPClients = 0; + mu32SingleRDPClientId = 0; + mcGuestCredentialsProvided = false; + + /* Now the VM specific parts */ + /** @todo r=bird: aLockType is always LockType_VM. */ + if (aLockType == LockType_VM) + { + /* Load the VMM. We won't continue without it being successfully loaded here. */ + rc = i_loadVMM(); + AssertComRCReturnRC(rc); + + rc = mMachine->COMGETTER(VRDEServer)(unconst(mVRDEServer).asOutParam()); + AssertComRCReturnRC(rc); + + unconst(mGuest).createObject(); + rc = mGuest->init(this); + AssertComRCReturnRC(rc); + + ULONG cCpus = 1; + rc = mMachine->COMGETTER(CPUCount)(&cCpus); + mGuest->i_setCpuCount(cCpus); + + unconst(mKeyboard).createObject(); + rc = mKeyboard->init(this); + AssertComRCReturnRC(rc); + + unconst(mMouse).createObject(); + rc = mMouse->init(this); + AssertComRCReturnRC(rc); + + unconst(mDisplay).createObject(); + rc = mDisplay->init(this); + AssertComRCReturnRC(rc); + + unconst(mVRDEServerInfo).createObject(); + rc = mVRDEServerInfo->init(this); + AssertComRCReturnRC(rc); + + unconst(mEmulatedUSB).createObject(); + rc = mEmulatedUSB->init(this); + AssertComRCReturnRC(rc); + + /* Init the NVRAM store. */ + ComPtr<INvramStore> pNvramStore; + rc = aMachine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam()); + AssertComRCReturnRC(rc); + + Bstr strNonVolatilePath; + pNvramStore->COMGETTER(NonVolatileStorageFile)(strNonVolatilePath.asOutParam()); + + unconst(mptrNvramStore).createObject(); + rc = mptrNvramStore->init(this, strNonVolatilePath); + AssertComRCReturnRC(rc); + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + Bstr bstrNvramKeyId; + Bstr bstrNvramKeyStore; + rc = pNvramStore->COMGETTER(KeyId)(bstrNvramKeyId.asOutParam()); + AssertComRCReturnRC(rc); + rc = pNvramStore->COMGETTER(KeyStore)(bstrNvramKeyStore.asOutParam()); + AssertComRCReturnRC(rc); + const Utf8Str strNvramKeyId(bstrNvramKeyId); + const Utf8Str strNvramKeyStore(bstrNvramKeyStore); + mptrNvramStore->i_updateEncryptionSettings(strNvramKeyId, strNvramKeyStore); +#endif + + /* Grab global and machine shared folder lists */ + + rc = i_fetchSharedFolders(true /* aGlobal */); + AssertComRCReturnRC(rc); + rc = i_fetchSharedFolders(false /* aGlobal */); + AssertComRCReturnRC(rc); + + /* Create other child objects */ + + unconst(mConsoleVRDPServer) = new ConsoleVRDPServer(this); + AssertReturn(mConsoleVRDPServer, E_FAIL); + + /* Figure out size of meAttachmentType vector */ + ComPtr<IVirtualBox> pVirtualBox; + rc = aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + AssertComRC(rc); + ComPtr<ISystemProperties> pSystemProperties; + if (pVirtualBox) + pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam()); + ChipsetType_T chipsetType = ChipsetType_PIIX3; + aMachine->COMGETTER(ChipsetType)(&chipsetType); + ULONG maxNetworkAdapters = 0; + if (pSystemProperties) + pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); + meAttachmentType.resize(maxNetworkAdapters); + for (ULONG slot = 0; slot < maxNetworkAdapters; ++slot) + meAttachmentType[slot] = NetworkAttachmentType_Null; + +#ifdef VBOX_WITH_AUDIO_VRDE + unconst(mAudioVRDE) = new AudioVRDE(this); + AssertReturn(mAudioVRDE, E_FAIL); +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING + unconst(mRecording.mAudioRec) = new AudioVideoRec(this); + AssertReturn(mRecording.mAudioRec, E_FAIL); +#endif + +#ifdef VBOX_WITH_USB_CARDREADER + unconst(mUsbCardReader) = new UsbCardReader(this); + AssertReturn(mUsbCardReader, E_FAIL); +#endif + + m_cDisksPwProvided = 0; + m_cDisksEncrypted = 0; + + unconst(m_pKeyStore) = new SecretKeyStore(true /* fKeyBufNonPageable */); + AssertReturn(m_pKeyStore, E_FAIL); + + /* VirtualBox events registration. */ + { + ComPtr<IEventSource> pES; + rc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam()); + AssertComRC(rc); + ComObjPtr<VmEventListenerImpl> aVmListener; + aVmListener.createObject(); + aVmListener->init(new VmEventListener(), this); + mVmListener = aVmListener; + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnNATRedirect); + eventTypes.push_back(VBoxEventType_OnHostNameResolutionConfigurationChange); + eventTypes.push_back(VBoxEventType_OnHostPCIDevicePlug); + eventTypes.push_back(VBoxEventType_OnExtraDataChanged); + rc = pES->RegisterListener(aVmListener, ComSafeArrayAsInParam(eventTypes), true); + AssertComRC(rc); + } + } + + /* Confirm a successful initialization when it's the case */ + autoInitSpan.setSucceeded(); + +#ifdef VBOX_WITH_EXTPACK + /* Let the extension packs have a go at things (hold no locks). */ + if (SUCCEEDED(rc)) + mptrExtPackManager->i_callAllConsoleReadyHooks(this); +#endif + + LogFlowThisFuncLeave(); + + return S_OK; +} + +/** + * Uninitializes the Console object. + */ +void Console::uninit() +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + { + LogFlowThisFunc(("Already uninitialized.\n")); + LogFlowThisFuncLeave(); + return; + } + + LogFlowThisFunc(("initFailed()=%d\n", autoUninitSpan.initFailed())); + if (mVmListener) + { + ComPtr<IEventSource> pES; + ComPtr<IVirtualBox> pVirtualBox; + HRESULT rc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + AssertComRC(rc); + if (SUCCEEDED(rc) && !pVirtualBox.isNull()) + { + rc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam()); + AssertComRC(rc); + if (!pES.isNull()) + { + rc = pES->UnregisterListener(mVmListener); + AssertComRC(rc); + } + } + mVmListener.setNull(); + } + + /* power down the VM if necessary */ + if (mpUVM) + { + i_powerDown(); + Assert(mpUVM == NULL); + } + + if (mVMZeroCallersSem != NIL_RTSEMEVENT) + { + RTSemEventDestroy(mVMZeroCallersSem); + mVMZeroCallersSem = NIL_RTSEMEVENT; + } + + if (mpVmm2UserMethods) + { + RTMemFree((void *)mpVmm2UserMethods); + mpVmm2UserMethods = NULL; + } + + if (mpIfSecKey) + { + RTMemFree((void *)mpIfSecKey); + mpIfSecKey = NULL; + } + + if (mpIfSecKeyHlp) + { + RTMemFree((void *)mpIfSecKeyHlp); + mpIfSecKeyHlp = NULL; + } + +#ifdef VBOX_WITH_USB_CARDREADER + if (mUsbCardReader) + { + delete mUsbCardReader; + unconst(mUsbCardReader) = NULL; + } +#endif + +#ifdef VBOX_WITH_AUDIO_VRDE + if (mAudioVRDE) + { + delete mAudioVRDE; + unconst(mAudioVRDE) = NULL; + } +#endif + +#ifdef VBOX_WITH_RECORDING + i_recordingDestroy(); +# ifdef VBOX_WITH_AUDIO_RECORDING + if (mRecording.mAudioRec) + { + delete mRecording.mAudioRec; + unconst(mRecording.mAudioRec) = NULL; + } +# endif +#endif /* VBOX_WITH_RECORDING */ + + // if the VM had a VMMDev with an HGCM thread, then remove that here + if (m_pVMMDev) + { + delete m_pVMMDev; + unconst(m_pVMMDev) = NULL; + } + + if (mBusMgr) + { + mBusMgr->Release(); + mBusMgr = NULL; + } + + if (m_pKeyStore) + { + delete m_pKeyStore; + unconst(m_pKeyStore) = NULL; + } + + m_mapGlobalSharedFolders.clear(); + m_mapMachineSharedFolders.clear(); + m_mapSharedFolders.clear(); // console instances + + mRemoteUSBDevices.clear(); + mUSBDevices.clear(); + + if (mVRDEServerInfo) + { + mVRDEServerInfo->uninit(); + unconst(mVRDEServerInfo).setNull(); + } + + if (mEmulatedUSB) + { + mEmulatedUSB->uninit(); + unconst(mEmulatedUSB).setNull(); + } + + if (mDebugger) + { + mDebugger->uninit(); + unconst(mDebugger).setNull(); + } + + if (mDisplay) + { + mDisplay->uninit(); + unconst(mDisplay).setNull(); + } + + if (mMouse) + { + mMouse->uninit(); + unconst(mMouse).setNull(); + } + + if (mKeyboard) + { + mKeyboard->uninit(); + unconst(mKeyboard).setNull(); + } + + if (mGuest) + { + mGuest->uninit(); + unconst(mGuest).setNull(); + } + + if (mConsoleVRDPServer) + { + delete mConsoleVRDPServer; + unconst(mConsoleVRDPServer) = NULL; + } + + if (mptrNvramStore) + { + mptrNvramStore->uninit(); + unconst(mptrNvramStore).setNull(); + } + + unconst(mVRDEServer).setNull(); + + unconst(mControl).setNull(); + unconst(mMachine).setNull(); + + // we don't perform uninit() as it's possible that some pending event refers to this source + unconst(mEventSource).setNull(); + +#ifdef VBOX_WITH_EXTPACK + unconst(mptrExtPackManager).setNull(); +#endif + + /* Unload the VMM. */ + mpVMM = NULL; + if (mhModVMM != NIL_RTLDRMOD) + { + RTLdrClose(mhModVMM); + mhModVMM = NIL_RTLDRMOD; + } + + /* Release memory held by the LED sets. */ + for (size_t idxSet = 0; idxSet < mcLedSets; idxSet++) + { + RTMemFree(maLedSets[idxSet].papLeds); + RTMemFree(maLedSets[idxSet].paSubTypes); + maLedSets[idxSet].papLeds = NULL; + maLedSets[idxSet].paSubTypes = NULL; + } + mcLedSets = 0; + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + /* Close the release log before unloading the cryptographic module. */ + if (m_fEncryptedLog) + { + PRTLOGGER pLogEnc = RTLogRelSetDefaultInstance(NULL); + int vrc = RTLogDestroy(pLogEnc); + AssertRC(vrc); + } +#endif + + HRESULT rc = i_unloadCryptoIfModule(); + AssertComRC(rc); + + LogFlowThisFuncLeave(); +} + +#ifdef VBOX_WITH_GUEST_PROPS + +/** + * Wrapper for VMMDev::i_guestPropertiesHandleVMReset + */ +HRESULT Console::i_pullGuestProperties(ComSafeArrayOut(BSTR, names), ComSafeArrayOut(BSTR, values), + ComSafeArrayOut(LONG64, timestamps), ComSafeArrayOut(BSTR, flags)) +{ + AssertReturn(mControl.isNotNull(), VERR_INVALID_POINTER); + return mControl->PullGuestProperties(ComSafeArrayOutArg(names), ComSafeArrayOutArg(values), + ComSafeArrayOutArg(timestamps), ComSafeArrayOutArg(flags)); +} + +/** + * Handles guest properties on a VM reset. + * + * We must delete properties that are flagged TRANSRESET. + * + * @todo r=bird: Would be more efficient if we added a request to the HGCM + * service to do this instead of detouring thru VBoxSVC. + * (IMachine::SetGuestProperty ends up in VBoxSVC, which in turns calls + * back into the VM process and the HGCM service.) + */ +void Console::i_guestPropertiesHandleVMReset(void) +{ + std::vector<Utf8Str> names; + std::vector<Utf8Str> values; + std::vector<LONG64> timestamps; + std::vector<Utf8Str> flags; + HRESULT hrc = i_enumerateGuestProperties("*", names, values, timestamps, flags); + if (SUCCEEDED(hrc)) + { + for (size_t i = 0; i < flags.size(); i++) + { + /* Delete all properties which have the flag "TRANSRESET". */ + if (flags[i].contains("TRANSRESET", Utf8Str::CaseInsensitive)) + { + hrc = mMachine->DeleteGuestProperty(Bstr(names[i]).raw()); + if (FAILED(hrc)) + LogRel(("RESET: Could not delete transient property \"%s\", rc=%Rhrc\n", + names[i].c_str(), hrc)); + } + } + } + else + LogRel(("RESET: Unable to enumerate guest properties, rc=%Rhrc\n", hrc)); +} + +bool Console::i_guestPropertiesVRDPEnabled(void) +{ + Bstr value; + HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableGuestPropertiesVRDP").raw(), + value.asOutParam()); + if ( hrc == S_OK + && value == "1") + return true; + return false; +} + +void Console::i_guestPropertiesVRDPUpdateLogon(uint32_t u32ClientId, const char *pszUser, const char *pszDomain) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + char szPropNm[256]; + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId); + Bstr clientName; + mVRDEServerInfo->COMGETTER(ClientName)(clientName.asOutParam()); + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + clientName.raw(), + bstrReadOnlyGuest.raw()); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + Bstr(pszUser).raw(), + bstrReadOnlyGuest.raw()); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + Bstr(pszDomain).raw(), + bstrReadOnlyGuest.raw()); + + char szClientId[64]; + RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId); + mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastConnectedClient").raw(), + Bstr(szClientId).raw(), + bstrReadOnlyGuest.raw()); + + return; +} + +void Console::i_guestPropertiesVRDPUpdateActiveClient(uint32_t u32ClientId) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("%d\n", u32ClientId)); + + Bstr bstrFlags(L"RDONLYGUEST,TRANSIENT"); + + char szClientId[64]; + RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId); + + mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/ActiveClient").raw(), + Bstr(szClientId).raw(), + bstrFlags.raw()); + + return; +} + +void Console::i_guestPropertiesVRDPUpdateNameChange(uint32_t u32ClientId, const char *pszName) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + char szPropNm[256]; + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId); + Bstr clientName(pszName); + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + clientName.raw(), + bstrReadOnlyGuest.raw()); + +} + +void Console::i_guestPropertiesVRDPUpdateIPAddrChange(uint32_t u32ClientId, const char *pszIPAddr) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + char szPropNm[256]; + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/IPAddr", u32ClientId); + Bstr clientIPAddr(pszIPAddr); + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + clientIPAddr.raw(), + bstrReadOnlyGuest.raw()); + +} + +void Console::i_guestPropertiesVRDPUpdateLocationChange(uint32_t u32ClientId, const char *pszLocation) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + char szPropNm[256]; + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Location", u32ClientId); + Bstr clientLocation(pszLocation); + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + clientLocation.raw(), + bstrReadOnlyGuest.raw()); + +} + +void Console::i_guestPropertiesVRDPUpdateOtherInfoChange(uint32_t u32ClientId, const char *pszOtherInfo) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + char szPropNm[256]; + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/OtherInfo", u32ClientId); + Bstr clientOtherInfo(pszOtherInfo); + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + clientOtherInfo.raw(), + bstrReadOnlyGuest.raw()); + +} + +void Console::i_guestPropertiesVRDPUpdateClientAttach(uint32_t u32ClientId, bool fAttached) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + char szPropNm[256]; + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId); + + Bstr bstrValue = fAttached? "1": "0"; + + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), + bstrValue.raw(), + bstrReadOnlyGuest.raw()); +} + +void Console::i_guestPropertiesVRDPUpdateDisconnect(uint32_t u32ClientId) +{ + if (!i_guestPropertiesVRDPEnabled()) + return; + + LogFlowFunc(("\n")); + + Bstr bstrReadOnlyGuest(L"RDONLYGUEST"); + + char szPropNm[256]; + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL, + bstrReadOnlyGuest.raw()); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL, + bstrReadOnlyGuest.raw()); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL, + bstrReadOnlyGuest.raw()); + + RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId); + mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL, + bstrReadOnlyGuest.raw()); + + char szClientId[64]; + RTStrPrintf(szClientId, sizeof(szClientId), "%d", u32ClientId); + mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastDisconnectedClient").raw(), + Bstr(szClientId).raw(), + bstrReadOnlyGuest.raw()); + + return; +} + +#endif /* VBOX_WITH_GUEST_PROPS */ + +#ifdef VBOX_WITH_EXTPACK +/** + * Used by VRDEServer and others to talke to the extension pack manager. + * + * @returns The extension pack manager. + */ +ExtPackManager *Console::i_getExtPackManager() +{ + return mptrExtPackManager; +} +#endif + + +int Console::i_VRDPClientLogon(uint32_t u32ClientId, const char *pszUser, const char *pszPassword, const char *pszDomain) +{ + LogFlowFuncEnter(); + LogFlowFunc(("%d, %s, %s, %s\n", u32ClientId, pszUser, pszPassword, pszDomain)); + + AutoCaller autoCaller(this); + if (!autoCaller.isOk()) + { + /* Console has been already uninitialized, deny request */ + LogRel(("AUTH: Access denied (Console uninitialized).\n")); + LogFlowFuncLeave(); + return VERR_ACCESS_DENIED; + } + + Guid uuid = Guid(i_getId()); + + AuthType_T authType = AuthType_Null; + HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType); + AssertComRCReturn(hrc, VERR_ACCESS_DENIED); + + ULONG authTimeout = 0; + hrc = mVRDEServer->COMGETTER(AuthTimeout)(&authTimeout); + AssertComRCReturn(hrc, VERR_ACCESS_DENIED); + + AuthResult result = AuthResultAccessDenied; + AuthGuestJudgement guestJudgement = AuthGuestNotAsked; + + LogFlowFunc(("Auth type %d\n", authType)); + + LogRel(("AUTH: User: [%s]. Domain: [%s]. Authentication type: [%s]\n", + pszUser, pszDomain, + authType == AuthType_Null? + "Null": + (authType == AuthType_External? + "External": + (authType == AuthType_Guest? + "Guest": + "INVALID" + ) + ) + )); + + switch (authType) + { + case AuthType_Null: + { + result = AuthResultAccessGranted; + break; + } + + case AuthType_External: + { + /* Call the external library. */ + result = mConsoleVRDPServer->Authenticate(uuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId); + + if (result != AuthResultDelegateToGuest) + { + break; + } + + LogRel(("AUTH: Delegated to guest.\n")); + + LogFlowFunc(("External auth asked for guest judgement\n")); + } + RT_FALL_THRU(); + + case AuthType_Guest: + { + guestJudgement = AuthGuestNotReacted; + + /** @todo r=dj locking required here for m_pVMMDev? */ + PPDMIVMMDEVPORT pDevPort; + if ( m_pVMMDev + && ((pDevPort = m_pVMMDev->getVMMDevPort())) + ) + { + /* Issue the request to guest. Assume that the call does not require EMT. It should not. */ + + /* Ask the guest to judge these credentials. */ + uint32_t u32GuestFlags = VMMDEV_SETCREDENTIALS_JUDGE; + + int rc = pDevPort->pfnSetCredentials(pDevPort, pszUser, pszPassword, pszDomain, u32GuestFlags); + + if (RT_SUCCESS(rc)) + { + /* Wait for guest. */ + rc = m_pVMMDev->WaitCredentialsJudgement(authTimeout, &u32GuestFlags); + + if (RT_SUCCESS(rc)) + { + switch (u32GuestFlags & ( VMMDEV_CREDENTIALS_JUDGE_OK + | VMMDEV_CREDENTIALS_JUDGE_DENY + | VMMDEV_CREDENTIALS_JUDGE_NOJUDGEMENT)) + { + case VMMDEV_CREDENTIALS_JUDGE_DENY: guestJudgement = AuthGuestAccessDenied; break; + case VMMDEV_CREDENTIALS_JUDGE_NOJUDGEMENT: guestJudgement = AuthGuestNoJudgement; break; + case VMMDEV_CREDENTIALS_JUDGE_OK: guestJudgement = AuthGuestAccessGranted; break; + default: + LogFlowFunc(("Invalid guest flags %#08x!!!\n", u32GuestFlags)); break; + } + } + else + { + LogFlowFunc(("Wait for credentials judgement rc = %Rrc!!!\n", rc)); + } + + LogFlowFunc(("Guest judgement %d\n", guestJudgement)); + } + else + { + LogFlowFunc(("Could not set credentials rc = %Rrc!!!\n", rc)); + } + } + + if (authType == AuthType_External) + { + LogRel(("AUTH: Guest judgement %d.\n", guestJudgement)); + LogFlowFunc(("External auth called again with guest judgement = %d\n", guestJudgement)); + result = mConsoleVRDPServer->Authenticate(uuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId); + } + else + { + switch (guestJudgement) + { + case AuthGuestAccessGranted: + result = AuthResultAccessGranted; + break; + default: + result = AuthResultAccessDenied; + break; + } + } + } break; + + default: + AssertFailed(); + } + + LogFlowFunc(("Result = %d\n", result)); + LogFlowFuncLeave(); + + if (result != AuthResultAccessGranted) + { + /* Reject. */ + LogRel(("AUTH: Access denied.\n")); + return VERR_ACCESS_DENIED; + } + + LogRel(("AUTH: Access granted.\n")); + + /* Multiconnection check must be made after authentication, so bad clients would not interfere with a good one. */ + BOOL allowMultiConnection = FALSE; + hrc = mVRDEServer->COMGETTER(AllowMultiConnection)(&allowMultiConnection); + AssertComRCReturn(hrc, VERR_ACCESS_DENIED); + + BOOL reuseSingleConnection = FALSE; + hrc = mVRDEServer->COMGETTER(ReuseSingleConnection)(&reuseSingleConnection); + AssertComRCReturn(hrc, VERR_ACCESS_DENIED); + + LogFlowFunc(("allowMultiConnection %d, reuseSingleConnection = %d, mcVRDPClients = %d, mu32SingleRDPClientId = %d\n", + allowMultiConnection, reuseSingleConnection, mcVRDPClients, mu32SingleRDPClientId)); + + if (allowMultiConnection == FALSE) + { + /* Note: the 'mcVRDPClients' variable is incremented in ClientConnect callback, which is called when the client + * is successfully connected, that is after the ClientLogon callback. Therefore the mcVRDPClients + * value is 0 for first client. + */ + if (mcVRDPClients != 0) + { + Assert(mcVRDPClients == 1); + /* There is a client already. + * If required drop the existing client connection and let the connecting one in. + */ + if (reuseSingleConnection) + { + LogRel(("AUTH: Multiple connections are not enabled. Disconnecting existing client.\n")); + mConsoleVRDPServer->DisconnectClient(mu32SingleRDPClientId, false); + } + else + { + /* Reject. */ + LogRel(("AUTH: Multiple connections are not enabled. Access denied.\n")); + return VERR_ACCESS_DENIED; + } + } + + /* Save the connected client id. From now on it will be necessary to disconnect this one. */ + mu32SingleRDPClientId = u32ClientId; + } + +#ifdef VBOX_WITH_GUEST_PROPS + i_guestPropertiesVRDPUpdateLogon(u32ClientId, pszUser, pszDomain); +#endif /* VBOX_WITH_GUEST_PROPS */ + + /* Check if the successfully verified credentials are to be sent to the guest. */ + BOOL fProvideGuestCredentials = FALSE; + + Bstr value; + hrc = mMachine->GetExtraData(Bstr("VRDP/ProvideGuestCredentials").raw(), + value.asOutParam()); + if (SUCCEEDED(hrc) && value == "1") + { + /* Provide credentials only if there are no logged in users. */ + Utf8Str noLoggedInUsersValue; + LONG64 ul64Timestamp = 0; + Utf8Str flags; + + hrc = i_getGuestProperty("/VirtualBox/GuestInfo/OS/NoLoggedInUsers", + &noLoggedInUsersValue, &ul64Timestamp, &flags); + + if (SUCCEEDED(hrc) && noLoggedInUsersValue != "false") + { + /* And only if there are no connected clients. */ + if (ASMAtomicCmpXchgBool(&mcGuestCredentialsProvided, true, false)) + { + fProvideGuestCredentials = TRUE; + } + } + } + + /** @todo r=dj locking required here for m_pVMMDev? */ + if ( fProvideGuestCredentials + && m_pVMMDev) + { + uint32_t u32GuestFlags = VMMDEV_SETCREDENTIALS_GUESTLOGON; + + PPDMIVMMDEVPORT pDevPort = m_pVMMDev->getVMMDevPort(); + if (pDevPort) + { + int rc = pDevPort->pfnSetCredentials(m_pVMMDev->getVMMDevPort(), + pszUser, pszPassword, pszDomain, u32GuestFlags); + AssertRC(rc); + } + } + + return VINF_SUCCESS; +} + +void Console::i_VRDPClientStatusChange(uint32_t u32ClientId, const char *pszStatus) +{ + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + LogFlowFunc(("%s\n", pszStatus)); + +#ifdef VBOX_WITH_GUEST_PROPS + /* Parse the status string. */ + if (RTStrICmp(pszStatus, "ATTACH") == 0) + { + i_guestPropertiesVRDPUpdateClientAttach(u32ClientId, true); + } + else if (RTStrICmp(pszStatus, "DETACH") == 0) + { + i_guestPropertiesVRDPUpdateClientAttach(u32ClientId, false); + } + else if (RTStrNICmp(pszStatus, "NAME=", strlen("NAME=")) == 0) + { + i_guestPropertiesVRDPUpdateNameChange(u32ClientId, pszStatus + strlen("NAME=")); + } + else if (RTStrNICmp(pszStatus, "CIPA=", strlen("CIPA=")) == 0) + { + i_guestPropertiesVRDPUpdateIPAddrChange(u32ClientId, pszStatus + strlen("CIPA=")); + } + else if (RTStrNICmp(pszStatus, "CLOCATION=", strlen("CLOCATION=")) == 0) + { + i_guestPropertiesVRDPUpdateLocationChange(u32ClientId, pszStatus + strlen("CLOCATION=")); + } + else if (RTStrNICmp(pszStatus, "COINFO=", strlen("COINFO=")) == 0) + { + i_guestPropertiesVRDPUpdateOtherInfoChange(u32ClientId, pszStatus + strlen("COINFO=")); + } +#endif + + LogFlowFuncLeave(); +} + +void Console::i_VRDPClientConnect(uint32_t u32ClientId) +{ + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + uint32_t u32Clients = ASMAtomicIncU32(&mcVRDPClients); + VMMDev *pDev; + PPDMIVMMDEVPORT pPort; + if ( (u32Clients == 1) + && ((pDev = i_getVMMDev())) + && ((pPort = pDev->getVMMDevPort())) + ) + { + pPort->pfnVRDPChange(pPort, + true, + VRDP_EXPERIENCE_LEVEL_FULL); /** @todo configurable */ + } + + NOREF(u32ClientId); + mDisplay->i_VRDPConnectionEvent(true); + +#ifdef VBOX_WITH_GUEST_PROPS + i_guestPropertiesVRDPUpdateActiveClient(u32ClientId); +#endif /* VBOX_WITH_GUEST_PROPS */ + + LogFlowFuncLeave(); + return; +} + +void Console::i_VRDPClientDisconnect(uint32_t u32ClientId, + uint32_t fu32Intercepted) +{ + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AssertReturnVoid(mConsoleVRDPServer); + + uint32_t u32Clients = ASMAtomicDecU32(&mcVRDPClients); + VMMDev *pDev; + PPDMIVMMDEVPORT pPort; + + if ( (u32Clients == 0) + && ((pDev = i_getVMMDev())) + && ((pPort = pDev->getVMMDevPort())) + ) + { + pPort->pfnVRDPChange(pPort, + false, + 0); + } + + mDisplay->i_VRDPConnectionEvent(false); + + if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_USB) + { + mConsoleVRDPServer->USBBackendDelete(u32ClientId); + } + + if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_CLIPBOARD) + { + mConsoleVRDPServer->ClipboardDelete(u32ClientId); + } + +#ifdef VBOX_WITH_AUDIO_VRDE + if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_AUDIO) + { + if (mAudioVRDE) + mAudioVRDE->onVRDEControl(false /* fEnable */, 0 /* uFlags */); + } +#endif + + AuthType_T authType = AuthType_Null; + HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType); + AssertComRC(hrc); + + if (authType == AuthType_External) + mConsoleVRDPServer->AuthDisconnect(i_getId(), u32ClientId); + +#ifdef VBOX_WITH_GUEST_PROPS + i_guestPropertiesVRDPUpdateDisconnect(u32ClientId); + if (u32Clients == 0) + i_guestPropertiesVRDPUpdateActiveClient(0); +#endif /* VBOX_WITH_GUEST_PROPS */ + + if (u32Clients == 0) + mcGuestCredentialsProvided = false; + + LogFlowFuncLeave(); + return; +} + +void Console::i_VRDPInterceptAudio(uint32_t u32ClientId) +{ + RT_NOREF(u32ClientId); + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + LogFlowFunc(("u32ClientId=%RU32\n", u32ClientId)); + +#ifdef VBOX_WITH_AUDIO_VRDE + if (mAudioVRDE) + mAudioVRDE->onVRDEControl(true /* fEnable */, 0 /* uFlags */); +#endif + + LogFlowFuncLeave(); + return; +} + +void Console::i_VRDPInterceptUSB(uint32_t u32ClientId, void **ppvIntercept) +{ + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AssertReturnVoid(mConsoleVRDPServer); + + mConsoleVRDPServer->USBBackendCreate(u32ClientId, ppvIntercept); + + LogFlowFuncLeave(); + return; +} + +void Console::i_VRDPInterceptClipboard(uint32_t u32ClientId) +{ + LogFlowFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AssertReturnVoid(mConsoleVRDPServer); + + mConsoleVRDPServer->ClipboardCreate(u32ClientId); + + LogFlowFuncLeave(); + return; +} + + +//static +const char *Console::sSSMConsoleUnit = "ConsoleData"; +/** The saved state version. */ +#define CONSOLE_SAVED_STATE_VERSION UINT32_C(0x00010002) +/** The saved state version, pre shared folder autoMountPoint. */ +#define CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT UINT32_C(0x00010001) + +inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType) +{ + switch (adapterType) + { + case NetworkAdapterType_Am79C970A: + case NetworkAdapterType_Am79C973: + case NetworkAdapterType_Am79C960: + return "pcnet"; +#ifdef VBOX_WITH_E1000 + case NetworkAdapterType_I82540EM: + case NetworkAdapterType_I82543GC: + case NetworkAdapterType_I82545EM: + return "e1000"; +#endif +#ifdef VBOX_WITH_VIRTIO + case NetworkAdapterType_Virtio: + return "virtio-net"; +#endif + case NetworkAdapterType_NE1000: + case NetworkAdapterType_NE2000: + case NetworkAdapterType_WD8003: + case NetworkAdapterType_WD8013: + case NetworkAdapterType_ELNK2: + return "dp8390"; + case NetworkAdapterType_ELNK1: + return "3c501"; + default: + AssertFailed(); + return "unknown"; + } + /* not reached */ +} + +/** + * Loads various console data stored in the saved state file. + * + * This method does validation of the state file and returns an error info + * when appropriate. + * + * The method does nothing if the machine is not in the Saved file or if + * console data from it has already been loaded. + * + * @note The caller must lock this object for writing. + */ +HRESULT Console::i_loadDataFromSavedState() +{ + if ( ( mMachineState != MachineState_Saved + && mMachineState != MachineState_AbortedSaved) + || mSavedStateDataLoaded) + return S_OK; + + Bstr bstrSavedStateFile; + HRESULT hrc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam()); + if (SUCCEEDED(hrc)) + { + Bstr bstrStateKeyId; + hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam()); + if (SUCCEEDED(hrc)) + { + Bstr bstrStateKeyStore; + hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam()); + if (SUCCEEDED(hrc)) + { + Utf8Str const strSavedStateFile(bstrSavedStateFile); + + PCVMMR3VTABLE pVMM = mpVMM; + AssertPtrReturn(pVMM, E_UNEXPECTED); + + PSSMHANDLE pSSM; + SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore); + + int vrc = ssmStream.open(strSavedStateFile.c_str(), false /*fWrite*/, &pSSM); + if (RT_SUCCESS(vrc)) + { + uint32_t uVersion = 0; + vrc = pVMM->pfnSSMR3Seek(pSSM, sSSMConsoleUnit, 0 /* iInstance */, &uVersion); + /** @todo r=bird: This version check is premature, so the logic here is + * buggered as we won't ignore VERR_SSM_UNIT_NOT_FOUND as seems to be + * intended. Sigh. */ + if (SSM_VERSION_MAJOR(uVersion) == SSM_VERSION_MAJOR(CONSOLE_SAVED_STATE_VERSION)) + { + if (RT_SUCCESS(vrc)) + try + { + vrc = i_loadStateFileExecInternal(pSSM, pVMM, uVersion); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + else if (vrc == VERR_SSM_UNIT_NOT_FOUND) + vrc = VINF_SUCCESS; + } + else + vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + ssmStream.close(); + } + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("The saved state file '%s' is invalid (%Rrc). Delete the saved state and try again"), + strSavedStateFile.c_str(), vrc); + + mSavedStateDataLoaded = true; + } + } + } + + return hrc; +} + +/** + * Callback handler to save various console data to the state file, + * called when the user saves the VM state. + * + * @returns VBox status code. + * @param pSSM SSM handle. + * @param pVMM The VMM ring-3 vtable. + * @param pvUser Pointer to Console + * + * @note Locks the Console object for reading. + */ +/*static*/ DECLCALLBACK(int) +Console::i_saveStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser) +{ + LogFlowFunc(("\n")); + + Console *pThat = static_cast<Console *>(pvUser); + AssertReturn(pThat, VERR_INVALID_POINTER); + + AutoCaller autoCaller(pThat); + AssertComRCReturn(autoCaller.rc(), VERR_INVALID_STATE); + + AutoReadLock alock(pThat COMMA_LOCKVAL_SRC_POS); + + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)pThat->m_mapSharedFolders.size()); + + for (SharedFolderMap::const_iterator it = pThat->m_mapSharedFolders.begin(); + it != pThat->m_mapSharedFolders.end(); + ++it) + { + SharedFolder *pSF = (*it).second; + AutoCaller sfCaller(pSF); + AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS); + + const Utf8Str &name = pSF->i_getName(); + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)name.length() + 1 /* term. 0 */); + pVMM->pfnSSMR3PutStrZ(pSSM, name.c_str()); + + const Utf8Str &hostPath = pSF->i_getHostPath(); + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)hostPath.length() + 1 /* term. 0 */); + pVMM->pfnSSMR3PutStrZ(pSSM, hostPath.c_str()); + + pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isWritable()); + pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isAutoMounted()); + + const Utf8Str &rStrAutoMountPoint = pSF->i_getAutoMountPoint(); + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)rStrAutoMountPoint.length() + 1 /* term. 0 */); + pVMM->pfnSSMR3PutStrZ(pSSM, rStrAutoMountPoint.c_str()); + } + + return VINF_SUCCESS; +} + +/** + * Callback handler to load various console data from the state file. + * + * Called when the VM is being restored from the saved state. + * + * @returns VBox status code. + * @param pSSM SSM handle. + * @param pVMM The VMM ring-3 vtable. + * @param pvUser pointer to Console + * @param uVersion Console unit version. Should match sSSMConsoleVer. + * @param uPass The data pass. + */ +//static +DECLCALLBACK(int) +Console::i_loadStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + LogFlowFunc(("uVersion=%#x uPass=%#x\n", uVersion, uPass)); + Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass); + + if (SSM_VERSION_MAJOR_CHANGED(uVersion, CONSOLE_SAVED_STATE_VERSION)) + return VERR_VERSION_MISMATCH; + + Console *pThat = static_cast<Console *>(pvUser); + AssertReturn(pThat, VERR_INVALID_PARAMETER); + + /* Currently, nothing to do when we've been called from VMR3Load*. */ + return pVMM->pfnSSMR3SkipToEndOfUnit(pSSM); +} + +/** + * Method to load various console data from the state file. + * + * Called from #i_loadDataFromSavedState. + * + * @param pSSM SSM handle. + * @param pVMM The VMM vtable. + * @param u32Version Console unit version. + * Should match sSSMConsoleVer. + * + * @note Locks the Console object for writing. + */ +int Console::i_loadStateFileExecInternal(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t u32Version) +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturn(m_mapSharedFolders.empty(), VERR_INTERNAL_ERROR); + + uint32_t size = 0; + int vrc = pVMM->pfnSSMR3GetU32(pSSM, &size); + AssertRCReturn(vrc, vrc); + + for (uint32_t i = 0; i < size; ++i) + { + Utf8Str strName; + Utf8Str strHostPath; + bool writable = true; + bool autoMount = false; + + uint32_t cbStr = 0; + char *buf = NULL; + + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + buf = new char[cbStr]; + vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr); + AssertRC(vrc); + strName = buf; + delete[] buf; + + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + buf = new char[cbStr]; + vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr); + AssertRC(vrc); + strHostPath = buf; + delete[] buf; + + if (u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT) + pVMM->pfnSSMR3GetBool(pSSM, &writable); + + if ( u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT +#ifndef VBOX_OSE /* This broke saved state when introduced in r63916 (4.0). */ + && pVMM->pfnSSMR3HandleRevision(pSSM) >= 63916 +#endif + ) + pVMM->pfnSSMR3GetBool(pSSM, &autoMount); + + Utf8Str strAutoMountPoint; + if (u32Version >= CONSOLE_SAVED_STATE_VERSION) + { + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + vrc = strAutoMountPoint.reserveNoThrow(cbStr); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnSSMR3GetStrZ(pSSM, strAutoMountPoint.mutableRaw(), cbStr); + AssertRCReturn(vrc, vrc); + strAutoMountPoint.jolt(); + } + + ComObjPtr<SharedFolder> pSharedFolder; + pSharedFolder.createObject(); + HRESULT rc = pSharedFolder->init(this, + strName, + strHostPath, + writable, + autoMount, + strAutoMountPoint, + false /* fFailOnError */); + AssertComRCReturn(rc, VERR_INTERNAL_ERROR); + + m_mapSharedFolders.insert(std::make_pair(strName, pSharedFolder)); + } + + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_GUEST_PROPS + +// static +DECLCALLBACK(int) Console::i_doGuestPropNotification(void *pvExtension, + uint32_t u32Function, + void *pvParms, + uint32_t cbParms) +{ + Assert(u32Function == 0); NOREF(u32Function); + + /* + * No locking, as this is purely a notification which does not make any + * changes to the object state. + */ + PGUESTPROPHOSTCALLBACKDATA pCBData = reinterpret_cast<PGUESTPROPHOSTCALLBACKDATA>(pvParms); + AssertReturn(sizeof(GUESTPROPHOSTCALLBACKDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(pCBData->u32Magic == GUESTPROPHOSTCALLBACKDATA_MAGIC, VERR_INVALID_PARAMETER); + LogFlow(("Console::doGuestPropNotification: pCBData={.pcszName=%s, .pcszValue=%s, .pcszFlags=%s}\n", + pCBData->pcszName, pCBData->pcszValue, pCBData->pcszFlags)); + + int rc; + Bstr name(pCBData->pcszName); + Bstr value(pCBData->pcszValue); + Bstr flags(pCBData->pcszFlags); + BOOL fWasDeleted = !pCBData->pcszValue; + ComObjPtr<Console> pConsole = reinterpret_cast<Console *>(pvExtension); + HRESULT hrc = pConsole->mControl->PushGuestProperty(name.raw(), + value.raw(), + pCBData->u64Timestamp, + flags.raw(), + fWasDeleted); + if (SUCCEEDED(hrc)) + { + ::FireGuestPropertyChangedEvent(pConsole->mEventSource, pConsole->i_getId().raw(), name.raw(), value.raw(), flags.raw(), + fWasDeleted); + rc = VINF_SUCCESS; + } + else + { + LogFlow(("Console::doGuestPropNotification: hrc=%Rhrc pCBData={.pcszName=%s, .pcszValue=%s, .pcszFlags=%s}\n", + hrc, pCBData->pcszName, pCBData->pcszValue, pCBData->pcszFlags)); + rc = Global::vboxStatusCodeFromCOM(hrc); + } + return rc; +} + +HRESULT Console::i_doEnumerateGuestProperties(const Utf8Str &aPatterns, + std::vector<Utf8Str> &aNames, + std::vector<Utf8Str> &aValues, + std::vector<LONG64> &aTimestamps, + std::vector<Utf8Str> &aFlags) +{ + AssertReturn(m_pVMMDev, E_FAIL); + + VBOXHGCMSVCPARM parm[3]; + parm[0].type = VBOX_HGCM_SVC_PARM_PTR; + parm[0].u.pointer.addr = (void*)aPatterns.c_str(); + parm[0].u.pointer.size = (uint32_t)aPatterns.length() + 1; + + /* + * Now things get slightly complicated. Due to a race with the guest adding + * properties, there is no good way to know how much to enlarge a buffer for + * the service to enumerate into. We choose a decent starting size and loop a + * few times, each time retrying with the size suggested by the service plus + * one Kb. + */ + size_t cchBuf = 4096; + Utf8Str Utf8Buf; + int vrc = VERR_BUFFER_OVERFLOW; + for (unsigned i = 0; i < 10 && (VERR_BUFFER_OVERFLOW == vrc); ++i) + { + try + { + Utf8Buf.reserve(cchBuf + 1024); + } + catch(...) + { + return E_OUTOFMEMORY; + } + + parm[1].type = VBOX_HGCM_SVC_PARM_PTR; + parm[1].u.pointer.addr = Utf8Buf.mutableRaw(); + parm[1].u.pointer.size = (uint32_t)cchBuf + 1024; + + parm[2].type = VBOX_HGCM_SVC_PARM_32BIT; + parm[2].u.uint32 = 0; + + vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_ENUM_PROPS, 3, &parm[0]); + Utf8Buf.jolt(); + if (parm[2].type != VBOX_HGCM_SVC_PARM_32BIT) + return setErrorBoth(E_FAIL, vrc, tr("Internal application error")); + cchBuf = parm[2].u.uint32; + } + if (vrc == VERR_BUFFER_OVERFLOW) + return setError(E_UNEXPECTED, tr("Temporary failure due to guest activity, please retry")); + + /* + * Finally we have to unpack the data returned by the service into the safe + * arrays supplied by the caller. We start by counting the number of entries. + */ + const char *pszBuf + = reinterpret_cast<const char *>(parm[1].u.pointer.addr); + unsigned cEntries = 0; + /* The list is terminated by a zero-length string at the end of a set + * of four strings. */ + for (size_t i = 0; strlen(pszBuf + i) != 0; ) + { + /* We are counting sets of four strings. */ + for (unsigned j = 0; j < 4; ++j) + i += strlen(pszBuf + i) + 1; + ++cEntries; + } + + aNames.resize(cEntries); + aValues.resize(cEntries); + aTimestamps.resize(cEntries); + aFlags.resize(cEntries); + + size_t iBuf = 0; + /* Rely on the service to have formated the data correctly. */ + for (unsigned i = 0; i < cEntries; ++i) + { + size_t cchName = strlen(pszBuf + iBuf); + aNames[i] = &pszBuf[iBuf]; + iBuf += cchName + 1; + + size_t cchValue = strlen(pszBuf + iBuf); + aValues[i] = &pszBuf[iBuf]; + iBuf += cchValue + 1; + + size_t cchTimestamp = strlen(pszBuf + iBuf); + aTimestamps[i] = RTStrToUInt64(&pszBuf[iBuf]); + iBuf += cchTimestamp + 1; + + size_t cchFlags = strlen(pszBuf + iBuf); + aFlags[i] = &pszBuf[iBuf]; + iBuf += cchFlags + 1; + } + + return S_OK; +} + +#endif /* VBOX_WITH_GUEST_PROPS */ + + +// IConsole properties +///////////////////////////////////////////////////////////////////////////// +HRESULT Console::getMachine(ComPtr<IMachine> &aMachine) +{ + /* mMachine is constant during life time, no need to lock */ + mMachine.queryInterfaceTo(aMachine.asOutParam()); + + /* callers expect to get a valid reference, better fail than crash them */ + if (mMachine.isNull()) + return E_FAIL; + + return S_OK; +} + +HRESULT Console::getState(MachineState_T *aState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* we return our local state (since it's always the same as on the server) */ + *aState = mMachineState; + + return S_OK; +} + +HRESULT Console::getGuest(ComPtr<IGuest> &aGuest) +{ + /* mGuest is constant during life time, no need to lock */ + mGuest.queryInterfaceTo(aGuest.asOutParam()); + + return S_OK; +} + +HRESULT Console::getKeyboard(ComPtr<IKeyboard> &aKeyboard) +{ + /* mKeyboard is constant during life time, no need to lock */ + mKeyboard.queryInterfaceTo(aKeyboard.asOutParam()); + + return S_OK; +} + +HRESULT Console::getMouse(ComPtr<IMouse> &aMouse) +{ + /* mMouse is constant during life time, no need to lock */ + mMouse.queryInterfaceTo(aMouse.asOutParam()); + + return S_OK; +} + +HRESULT Console::getDisplay(ComPtr<IDisplay> &aDisplay) +{ + /* mDisplay is constant during life time, no need to lock */ + mDisplay.queryInterfaceTo(aDisplay.asOutParam()); + + return S_OK; +} + +HRESULT Console::getDebugger(ComPtr<IMachineDebugger> &aDebugger) +{ + /* we need a write lock because of the lazy mDebugger initialization*/ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* check if we have to create the debugger object */ + if (!mDebugger) + { + unconst(mDebugger).createObject(); + mDebugger->init(this); + } + + mDebugger.queryInterfaceTo(aDebugger.asOutParam()); + + return S_OK; +} + +HRESULT Console::getUSBDevices(std::vector<ComPtr<IUSBDevice> > &aUSBDevices) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + size_t i = 0; + aUSBDevices.resize(mUSBDevices.size()); + for (USBDeviceList::const_iterator it = mUSBDevices.begin(); it != mUSBDevices.end(); ++i, ++it) + (*it).queryInterfaceTo(aUSBDevices[i].asOutParam()); + + return S_OK; +} + + +HRESULT Console::getRemoteUSBDevices(std::vector<ComPtr<IHostUSBDevice> > &aRemoteUSBDevices) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + size_t i = 0; + aRemoteUSBDevices.resize(mRemoteUSBDevices.size()); + for (RemoteUSBDeviceList::const_iterator it = mRemoteUSBDevices.begin(); it != mRemoteUSBDevices.end(); ++i, ++it) + (*it).queryInterfaceTo(aRemoteUSBDevices[i].asOutParam()); + + return S_OK; +} + +HRESULT Console::getVRDEServerInfo(ComPtr<IVRDEServerInfo> &aVRDEServerInfo) +{ + /* mVRDEServerInfo is constant during life time, no need to lock */ + mVRDEServerInfo.queryInterfaceTo(aVRDEServerInfo.asOutParam()); + + return S_OK; +} + +HRESULT Console::getEmulatedUSB(ComPtr<IEmulatedUSB> &aEmulatedUSB) +{ + /* mEmulatedUSB is constant during life time, no need to lock */ + mEmulatedUSB.queryInterfaceTo(aEmulatedUSB.asOutParam()); + + return S_OK; +} + +HRESULT Console::getSharedFolders(std::vector<ComPtr<ISharedFolder> > &aSharedFolders) +{ + /* loadDataFromSavedState() needs a write lock */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Read console data stored in the saved state file (if not yet done) */ + HRESULT rc = i_loadDataFromSavedState(); + if (FAILED(rc)) return rc; + + size_t i = 0; + aSharedFolders.resize(m_mapSharedFolders.size()); + for (SharedFolderMap::const_iterator it = m_mapSharedFolders.begin(); it != m_mapSharedFolders.end(); ++i, ++it) + (it)->second.queryInterfaceTo(aSharedFolders[i].asOutParam()); + + return S_OK; +} + +HRESULT Console::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + // no need to lock - lifetime constant + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + return S_OK; +} + +HRESULT Console::getAttachedPCIDevices(std::vector<ComPtr<IPCIDeviceAttachment> > &aAttachedPCIDevices) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mBusMgr) + { + std::vector<BusAssignmentManager::PCIDeviceInfo> devInfos; + mBusMgr->listAttachedPCIDevices(devInfos); + ComObjPtr<PCIDeviceAttachment> dev; + aAttachedPCIDevices.resize(devInfos.size()); + for (size_t i = 0; i < devInfos.size(); i++) + { + const BusAssignmentManager::PCIDeviceInfo &devInfo = devInfos[i]; + dev.createObject(); + dev->init(NULL, devInfo.strDeviceName, + devInfo.hostAddress.valid() ? devInfo.hostAddress.asLong() : -1, + devInfo.guestAddress.asLong(), + devInfo.hostAddress.valid()); + dev.queryInterfaceTo(aAttachedPCIDevices[i].asOutParam()); + } + } + else + aAttachedPCIDevices.resize(0); + + return S_OK; +} + +HRESULT Console::getUseHostClipboard(BOOL *aUseHostClipboard) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aUseHostClipboard = mfUseHostClipboard; + + return S_OK; +} + +HRESULT Console::setUseHostClipboard(BOOL aUseHostClipboard) +{ + if (mfUseHostClipboard != RT_BOOL(aUseHostClipboard)) + { + mfUseHostClipboard = RT_BOOL(aUseHostClipboard); + LogRel(("Shared Clipboard: %s using host clipboard\n", mfUseHostClipboard ? "Enabled" : "Disabled")); + } + + return S_OK; +} + +// IConsole methods +///////////////////////////////////////////////////////////////////////////// + +HRESULT Console::powerUp(ComPtr<IProgress> &aProgress) +{ + return i_powerUp(aProgress.asOutParam(), false /* aPaused */); +} + +HRESULT Console::powerUpPaused(ComPtr<IProgress> &aProgress) +{ + return i_powerUp(aProgress.asOutParam(), true /* aPaused */); +} + +HRESULT Console::powerDown(ComPtr<IProgress> &aProgress) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + switch (mMachineState) + { + case MachineState_Running: + case MachineState_Paused: + case MachineState_Stuck: + break; + + /* Try cancel the save state. */ + case MachineState_Saving: + if (!mptrCancelableProgress.isNull()) + { + HRESULT hrc = mptrCancelableProgress->Cancel(); + if (SUCCEEDED(hrc)) + break; + } + return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point during a save state")); + + /* Try cancel the teleportation. */ + case MachineState_Teleporting: + case MachineState_TeleportingPausedVM: + if (!mptrCancelableProgress.isNull()) + { + HRESULT hrc = mptrCancelableProgress->Cancel(); + if (SUCCEEDED(hrc)) + break; + } + return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a teleportation")); + + /* Try cancel the online snapshot. */ + case MachineState_OnlineSnapshotting: + if (!mptrCancelableProgress.isNull()) + { + HRESULT hrc = mptrCancelableProgress->Cancel(); + if (SUCCEEDED(hrc)) + break; + } + return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in an online snapshot")); + + /* Try cancel the live snapshot. */ + case MachineState_LiveSnapshotting: + if (!mptrCancelableProgress.isNull()) + { + HRESULT hrc = mptrCancelableProgress->Cancel(); + if (SUCCEEDED(hrc)) + break; + } + return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a live snapshot")); + + /* extra nice error message for a common case */ + case MachineState_Saved: + case MachineState_AbortedSaved: + return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down a saved virtual machine")); + case MachineState_Stopping: + return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is being powered down")); + default: + return setError(VBOX_E_INVALID_VM_STATE, + tr("Invalid machine state: %s (must be Running, Paused or Stuck)"), + Global::stringifyMachineState(mMachineState)); + } + LogFlowThisFunc(("Initiating SHUTDOWN request...\n")); + + /* memorize the current machine state */ + MachineState_T lastMachineState = mMachineState; + +#ifdef VBOX_WITH_GUEST_PROPS + if (mfTurnResetIntoPowerOff) + { + alock.release(); /** @todo r=bird: This code introduces a race condition wrt to the state. This must be done elsewhere! */ + mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw()); + mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(), + Bstr("PowerOff").raw(), Bstr("RDONLYGUEST").raw()); + mMachine->SaveSettings(); + alock.acquire(); + } +#endif + + /* + * Request a progress object from the server (this will set the machine state + * to Stopping on the server to block others from accessing this machine). + */ + ComPtr<IProgress> ptrProgress; + HRESULT hrc = mControl->BeginPoweringDown(ptrProgress.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* Sync the state with the server: */ + i_setMachineStateLocally(MachineState_Stopping); + + /* Create the power down task: */ + VMPowerDownTask *pTask = NULL; + try + { + pTask = new VMPowerDownTask(this, ptrProgress); + if (!pTask->isOk()) + { + hrc = setError(FAILED(pTask->rc()) ? pTask->rc() : E_FAIL, tr("Could not create VMPowerDownTask object\n")); + delete(pTask); + pTask = NULL; + } + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + if (SUCCEEDED(hrc)) + { + ptrProgress.queryInterfaceTo(aProgress.asOutParam()); + LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc)); + return hrc; + } + } + + /* + * Cancel the requested power down procedure. + * This will reset the machine state to the state it had right + * before calling mControl->BeginPoweringDown(). + */ + ErrorInfoKeeper eik; + mControl->EndPoweringDown(eik.getResultCode(), eik.getText().raw()); + i_setMachineStateLocally(lastMachineState); + } + LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc)); + return hrc; +} + +HRESULT Console::reset() +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + /** @todo r=bird: This should be allowed on paused VMs as well. Later. */ + ) + return i_setInvalidMachineStateError(); + + /* protect mpUVM */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + int vrc = ptrVM.vtable()->pfnVMR3Reset(ptrVM.rawUVM()); + + hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not reset the machine (%Rrc)"), vrc); + } + + LogFlowThisFunc(("mMachineState=%d, hrc=%Rhrc\n", mMachineState, hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +/*static*/ DECLCALLBACK(int) Console::i_unplugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu) +{ + LogFlowFunc(("pThis=%p pVM=%p idCpu=%u\n", pThis, pUVM, idCpu)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + int vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, "acpi", 0, idCpu, 0); + Log(("UnplugCpu: rc=%Rrc\n", vrc)); + + return vrc; +} + +HRESULT Console::i_doCPURemove(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + HRESULT rc = S_OK; + + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + AssertReturn(m_pVMMDev, E_FAIL); + PPDMIVMMDEVPORT pVmmDevPort = m_pVMMDev->getVMMDevPort(); + AssertReturn(pVmmDevPort, E_FAIL); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + ) + return i_setInvalidMachineStateError(); + + /* Check if the CPU is present */ + BOOL fCpuAttached; + rc = mMachine->GetCPUStatus(aCpu, &fCpuAttached); + if (FAILED(rc)) + return rc; + if (!fCpuAttached) + return setError(E_FAIL, tr("CPU %d is not attached"), aCpu); + + /* Leave the lock before any EMT/VMMDev call. */ + alock.release(); + bool fLocked = true; + + /* Check if the CPU is unlocked */ + PPDMIBASE pBase; + int vrc = pVMM->pfnPDMR3QueryDeviceLun(pUVM, "acpi", 0, aCpu, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pApicPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + + /* Notify the guest if possible. */ + uint32_t idCpuCore, idCpuPackage; + vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pVmmDevPort->pfnCpuHotUnplug(pVmmDevPort, idCpuCore, idCpuPackage); + if (RT_SUCCESS(vrc)) + { + unsigned cTries = 100; + do + { + /* It will take some time until the event is processed in the guest. Wait... */ + vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER; + if (RT_SUCCESS(vrc) && !fLocked) + break; + + /* Sleep a bit */ + RTThreadSleep(100); + } while (cTries-- > 0); + } + else if (vrc == VERR_VMMDEV_CPU_HOTPLUG_NOT_MONITORED_BY_GUEST) + { + /* Query one time. It is possible that the user ejected the CPU. */ + vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER; + } + } + + /* If the CPU was unlocked we can detach it now. */ + if (RT_SUCCESS(vrc) && !fLocked) + { + /* + * Call worker on EMT #0, that's faster and safer than doing everything + * using VMR3ReqCall. + */ + PVMREQ pReq; + vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_unplugCpu, 4, + this, pUVM, pVMM, (VMCPUID)aCpu); + + if (vrc == VERR_TIMEOUT) + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + pVMM->pfnVMR3ReqFree(pReq); + + if (RT_SUCCESS(vrc)) + { + /* Detach it from the VM */ + vrc = pVMM->pfnVMR3HotUnplugCpu(pUVM, aCpu); + AssertRC(vrc); + } + else + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Hot-Remove failed (rc=%Rrc)"), vrc); + } + else + rc = setErrorBoth(VBOX_E_VM_ERROR, VERR_RESOURCE_BUSY, + tr("Hot-Remove was aborted because the CPU may still be used by the guest"), VERR_RESOURCE_BUSY); + + LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc)); + LogFlowThisFuncLeave(); + return rc; +} + +/*static*/ DECLCALLBACK(int) Console::i_plugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu) +{ + LogFlowFunc(("pThis=%p uCpu=%u\n", pThis, idCpu)); + RT_NOREF(pThis); + + int rc = pVMM->pfnVMR3HotPlugCpu(pUVM, idCpu); + AssertRC(rc); + + PCFGMNODE pInst = pVMM->pfnCFGMR3GetChild(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/acpi/0/"); + AssertRelease(pInst); + /* nuke anything which might have been left behind. */ + pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", idCpu)); + +#define RC_CHECK() do { if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } } while (0) + + PCFGMNODE pLunL0; + PCFGMNODE pCfg; + rc = pVMM->pfnCFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%u", idCpu); RC_CHECK(); + rc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", "ACPICpu"); RC_CHECK(); + rc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pCfg); RC_CHECK(); + + /* + * Attach the driver. + */ + PPDMIBASE pBase; + rc = pVMM->pfnPDMR3DeviceAttach(pUVM, "acpi", 0, idCpu, 0, &pBase); RC_CHECK(); + + Log(("PlugCpu: rc=%Rrc\n", rc)); + + pVMM->pfnCFGMR3Dump(pInst); + +#undef RC_CHECK + + return VINF_SUCCESS; +} + +HRESULT Console::i_doCPUAdd(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + HRESULT rc = S_OK; + + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + /** @todo r=bird: This should be allowed on paused VMs as well. Later. */ + ) + return i_setInvalidMachineStateError(); + + AssertReturn(m_pVMMDev, E_FAIL); + PPDMIVMMDEVPORT pDevPort = m_pVMMDev->getVMMDevPort(); + AssertReturn(pDevPort, E_FAIL); + + /* Check if the CPU is present */ + BOOL fCpuAttached; + rc = mMachine->GetCPUStatus(aCpu, &fCpuAttached); + if (FAILED(rc)) return rc; + + if (fCpuAttached) + return setError(E_FAIL, + tr("CPU %d is already attached"), aCpu); + + /* + * Call worker on EMT #0, that's faster and safer than doing everything + * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait + * here to make requests from under the lock in order to serialize them. + */ + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_plugCpu, 4, + this, pUVM, pVMM, aCpu); + + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + if (vrc == VERR_TIMEOUT) + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + pVMM->pfnVMR3ReqFree(pReq); + + if (RT_SUCCESS(vrc)) + { + /* Notify the guest if possible. */ + uint32_t idCpuCore, idCpuPackage; + vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pDevPort->pfnCpuHotPlug(pDevPort, idCpuCore, idCpuPackage); + /** @todo warning if the guest doesn't support it */ + } + else + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not add CPU to the machine (%Rrc)"), vrc); + + LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc)); + LogFlowThisFuncLeave(); + return rc; +} + +HRESULT Console::pause() +{ + LogFlowThisFuncEnter(); + + HRESULT rc = i_pause(Reason_Unspecified); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +HRESULT Console::resume() +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mMachineState != MachineState_Paused) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot resume the machine as it is not paused (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + + HRESULT rc = i_resume(Reason_Unspecified, alock); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +HRESULT Console::powerButton() +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + ) + return i_setInvalidMachineStateError(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + // no need to release lock, as there are no cross-thread callbacks + + /* get the acpi device interface and press the button. */ + PPDMIBASE pBase = NULL; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + if (pPort) + vrc = pPort->pfnPowerButtonPress(pPort); + else + vrc = VERR_PDM_MISSING_INTERFACE; + } + + hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Controlled power off failed (%Rrc)"), vrc); + } + + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT Console::getPowerButtonHandled(BOOL *aHandled) +{ + LogFlowThisFuncEnter(); + + *aHandled = FALSE; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + ) + return i_setInvalidMachineStateError(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + // no need to release lock, as there are no cross-thread callbacks + + /* get the acpi device interface and check if the button press was handled. */ + PPDMIBASE pBase; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + if (pPort) + { + bool fHandled = false; + vrc = pPort->pfnGetPowerButtonHandled(pPort, &fHandled); + if (RT_SUCCESS(vrc)) + *aHandled = fHandled; + } + else + vrc = VERR_PDM_MISSING_INTERFACE; + } + + hrc = RT_SUCCESS(vrc) ? S_OK + : setErrorBoth(VBOX_E_PDM_ERROR, vrc, + tr("Checking if the ACPI Power Button event was handled by the guest OS failed (%Rrc)"), vrc); + + } + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT Console::getGuestEnteredACPIMode(BOOL *aEntered) +{ + LogFlowThisFuncEnter(); + + *aEntered = FALSE; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting + ) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Invalid machine state %s when checking if the guest entered the ACPI mode"), + Global::stringifyMachineState(mMachineState)); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + // no need to release lock, as there are no cross-thread callbacks + + /* get the acpi device interface and query the information. */ + PPDMIBASE pBase; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + if (pPort) + { + bool fEntered = false; + vrc = pPort->pfnGetGuestEnteredACPIMode(pPort, &fEntered); + if (RT_SUCCESS(vrc)) + *aEntered = fEntered; + } + else + vrc = VERR_PDM_MISSING_INTERFACE; + } + } + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT Console::sleepButton() +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting) + return i_setInvalidMachineStateError(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + // no need to release lock, as there are no cross-thread callbacks + + /* get the acpi device interface and press the sleep button. */ + PPDMIBASE pBase = NULL; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + if (pPort) + vrc = pPort->pfnSleepButtonPress(pPort); + else + vrc = VERR_PDM_MISSING_INTERFACE; + } + + hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending sleep button event failed (%Rrc)"), vrc); + } + + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +/** read the value of a LED. */ +DECLINLINE(uint32_t) readAndClearLed(PPDMLED pLed) +{ + if (!pLed) + return 0; + uint32_t u32 = pLed->Actual.u32 | pLed->Asserted.u32; + pLed->Asserted.u32 = 0; + return u32; +} + +HRESULT Console::getDeviceActivity(const std::vector<DeviceType_T> &aType, std::vector<DeviceActivity_T> &aActivity) +{ + /* + * Note: we don't lock the console object here because + * readAndClearLed() should be thread safe. + */ + + std::vector<bool> aWanted; + std::vector<PDMLEDCORE> aLED; + DeviceType_T maxWanted = (DeviceType_T) 0; + DeviceType_T enmType; + + /* Make a roadmap of which DeviceType_T LED types are wanted */ + for (size_t iType = 0; iType < aType.size(); ++iType) + { + enmType = aType[iType]; + if (enmType > maxWanted) + { + maxWanted = enmType; + aWanted.resize(maxWanted + 1); + } + aWanted[enmType] = true; + } + aLED.resize(maxWanted + 1); + + /* Collect all the LEDs in a single sweep through all drivers' sets */ + for (uint32_t idxSet = 0; idxSet < mcLedSets; ++idxSet) + { + /* Look inside this driver's set of LEDs */ + PLEDSET pLS = &maLedSets[idxSet]; + + /* Multi-type drivers (e.g. SCSI) have a subtype array which must be matched. */ + if (pLS->paSubTypes) + { + for (uint32_t inSet = 0; inSet < pLS->cLeds; ++inSet) + { + enmType = pLS->paSubTypes[inSet]; + if (enmType < maxWanted && aWanted[enmType]) + aLED[enmType].u32 |= readAndClearLed(pLS->papLeds[inSet]); + } + } + /* Single-type drivers (e.g. floppy) have the type in ->enmType */ + else + { + enmType = pLS->enmType; + if (enmType < maxWanted && aWanted[enmType]) + for (uint32_t inSet = 0; inSet < pLS->cLeds; ++inSet) + aLED[enmType].u32 |= readAndClearLed(pLS->papLeds[inSet]); + } + } + + aActivity.resize(aType.size()); + for (size_t iType = 0; iType < aActivity.size(); ++iType) + { + /* Compose the result */ + switch (aLED[aType[iType]].u32 & (PDMLED_READING | PDMLED_WRITING)) + { + case 0: + aActivity[iType] = DeviceActivity_Idle; + break; + case PDMLED_READING: + aActivity[iType] = DeviceActivity_Reading; + break; + case PDMLED_WRITING: + case PDMLED_READING | PDMLED_WRITING: + aActivity[iType] = DeviceActivity_Writing; + break; + } + } + + return S_OK; +} + +HRESULT Console::attachUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename) +{ +#ifdef VBOX_WITH_USB + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Paused) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot attach a USB device to the machine which is not running or paused (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + + /* Get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* Don't proceed unless we have a USB controller. */ + if (mfVMHasUsbController) + { + /* release the lock because the USB Proxy service may call us back + * (via onUSBDeviceAttach()) */ + alock.release(); + + /* Request the device capture */ + hrc = mControl->CaptureUSBDevice(Bstr(aId.toString()).raw(), Bstr(aCaptureFilename).raw()); + } + else + hrc = setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller")); + } + return hrc; + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aId, aCaptureFilename); + return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller")); +#endif /* !VBOX_WITH_USB */ +} + +HRESULT Console::detachUSBDevice(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice) +{ + RT_NOREF(aDevice); +#ifdef VBOX_WITH_USB + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Find it. */ + for (USBDeviceList::iterator it = mUSBDevices.begin(); it != mUSBDevices.end(); ++it) + if ((*it)->i_id() == aId) + { + /* Found it! */ + ComObjPtr<OUSBDevice> pUSBDevice(*it); + + /* Remove the device from the collection, it is re-added below for failures */ + mUSBDevices.erase(it); + + /* + * Inform the USB device and USB proxy about what's cooking. + */ + alock.release(); + HRESULT hrc = mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), false /* aDone */); + if (SUCCEEDED(hrc)) + { + /* Request the PDM to detach the USB device. */ + hrc = i_detachUSBDevice(pUSBDevice); + if (SUCCEEDED(hrc)) + { + /* Request the device release. Even if it fails, the device will + * remain as held by proxy, which is OK for us (the VM process). */ + return mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), true /* aDone */); + } + } + + /* Re-add the device to the collection */ + alock.acquire(); + mUSBDevices.push_back(pUSBDevice); + return hrc; + } + + return setError(E_INVALIDARG, tr("USB device with UUID {%RTuuid} is not attached to this machine"), aId.raw()); + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aId, aDevice); + return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller")); +#endif /* !VBOX_WITH_USB */ +} + + +HRESULT Console::findUSBDeviceByAddress(const com::Utf8Str &aName, ComPtr<IUSBDevice> &aDevice) +{ +#ifdef VBOX_WITH_USB + + aDevice = NULL; + + SafeIfaceArray<IUSBDevice> devsvec; + HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec)); + if (FAILED(rc)) return rc; + + for (size_t i = 0; i < devsvec.size(); ++i) + { + Bstr bstrAddress; + rc = devsvec[i]->COMGETTER(Address)(bstrAddress.asOutParam()); + if (FAILED(rc)) return rc; + if (bstrAddress == aName) + { + ComObjPtr<OUSBDevice> pUSBDevice; + pUSBDevice.createObject(); + pUSBDevice->init(devsvec[i]); + return pUSBDevice.queryInterfaceTo(aDevice.asOutParam()); + } + } + + return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with address '%s'"), aName.c_str()); + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aName, aDevice); + return E_NOTIMPL; +#endif /* !VBOX_WITH_USB */ +} + +HRESULT Console::findUSBDeviceById(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice) +{ +#ifdef VBOX_WITH_USB + + aDevice = NULL; + + SafeIfaceArray<IUSBDevice> devsvec; + HRESULT rc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec)); + if (FAILED(rc)) return rc; + + Utf8Str const strId = aId.toString(); + for (size_t i = 0; i < devsvec.size(); ++i) + { + Bstr id; + rc = devsvec[i]->COMGETTER(Id)(id.asOutParam()); + if (FAILED(rc)) return rc; + if (id == strId) + { + ComObjPtr<OUSBDevice> pUSBDevice; + pUSBDevice.createObject(); + pUSBDevice->init(devsvec[i]); + ComObjPtr<IUSBDevice> iUSBDevice = static_cast <ComObjPtr<IUSBDevice> > (pUSBDevice); + return iUSBDevice.queryInterfaceTo(aDevice.asOutParam()); + } + } + + return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with uuid {%RTuuid}"), aId.raw()); + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aId, aDevice); + return E_NOTIMPL; +#endif /* !VBOX_WITH_USB */ +} + +HRESULT Console::createSharedFolder(const com::Utf8Str &aName, const com::Utf8Str &aHostPath, BOOL aWritable, + BOOL aAutomount, const com::Utf8Str &aAutoMountPoint) +{ + LogFlowThisFunc(("Entering for '%s' -> '%s'\n", aName.c_str(), aHostPath.c_str())); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /// @todo see @todo in AttachUSBDevice() about the Paused state + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot create a transient shared folder on a machine in a saved state (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + if ( mMachineState != MachineState_PoweredOff + && mMachineState != MachineState_Teleported + && mMachineState != MachineState_Aborted + && mMachineState != MachineState_Running + && mMachineState != MachineState_Paused + ) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot create a transient shared folder on the machine while it is changing the state (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + + ComObjPtr<SharedFolder> pSharedFolder; + HRESULT rc = i_findSharedFolder(aName, pSharedFolder, false /* aSetError */); + if (SUCCEEDED(rc)) + return setError(VBOX_E_FILE_ERROR, + tr("Shared folder named '%s' already exists"), + aName.c_str()); + + pSharedFolder.createObject(); + rc = pSharedFolder->init(this, + aName, + aHostPath, + !!aWritable, + !!aAutomount, + aAutoMountPoint, + true /* fFailOnError */); + if (FAILED(rc)) return rc; + + /* If the VM is online and supports shared folders, share this folder + * under the specified name. (Ignore any failure to obtain the VM handle.) */ + SafeVMPtrQuiet ptrVM(this); + if ( ptrVM.isOk() + && m_pVMMDev + && m_pVMMDev->isShFlActive() + ) + { + /* first, remove the machine or the global folder if there is any */ + SharedFolderDataMap::const_iterator it; + if (i_findOtherSharedFolder(aName, it)) + { + rc = i_removeSharedFolder(aName); + if (FAILED(rc)) + return rc; + } + + /* second, create the given folder */ + rc = i_createSharedFolder(aName, SharedFolderData(aHostPath, !!aWritable, !!aAutomount, aAutoMountPoint)); + if (FAILED(rc)) + return rc; + } + + m_mapSharedFolders.insert(std::make_pair(aName, pSharedFolder)); + + /* Notify console callbacks after the folder is added to the list. */ + alock.release(); + ::FireSharedFolderChangedEvent(mEventSource, Scope_Session); + + LogFlowThisFunc(("Leaving for '%s' -> '%s'\n", aName.c_str(), aHostPath.c_str())); + + return rc; +} + +HRESULT Console::removeSharedFolder(const com::Utf8Str &aName) +{ + LogFlowThisFunc(("Entering for '%s'\n", aName.c_str())); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /// @todo see @todo in AttachUSBDevice() about the Paused state + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot remove a transient shared folder from a machine in a saved state (machine state: %s)"), + Global::stringifyMachineState(mMachineState));; + if ( mMachineState != MachineState_PoweredOff + && mMachineState != MachineState_Teleported + && mMachineState != MachineState_Aborted + && mMachineState != MachineState_Running + && mMachineState != MachineState_Paused + ) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot remove a transient shared folder from the machine while it is changing the state (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + + ComObjPtr<SharedFolder> pSharedFolder; + HRESULT rc = i_findSharedFolder(aName, pSharedFolder, true /* aSetError */); + if (FAILED(rc)) return rc; + + /* protect the VM handle (if not NULL) */ + SafeVMPtrQuiet ptrVM(this); + if ( ptrVM.isOk() + && m_pVMMDev + && m_pVMMDev->isShFlActive() + ) + { + /* if the VM is online and supports shared folders, UNshare this folder. */ + + /* first, remove the given folder */ + rc = i_removeSharedFolder(aName); + if (FAILED(rc)) return rc; + + /* first, remove the machine or the global folder if there is any */ + SharedFolderDataMap::const_iterator it; + if (i_findOtherSharedFolder(aName, it)) + { + rc = i_createSharedFolder(aName, it->second); + /* don't check rc here because we need to remove the console + * folder from the collection even on failure */ + } + } + + m_mapSharedFolders.erase(aName); + + /* Notify console callbacks after the folder is removed from the list. */ + alock.release(); + ::FireSharedFolderChangedEvent(mEventSource, Scope_Session); + + LogFlowThisFunc(("Leaving for '%s'\n", aName.c_str())); + + return rc; +} + +HRESULT Console::addEncryptionPassword(const com::Utf8Str &aId, const com::Utf8Str &aPassword, + BOOL aClearOnSuspend) +{ + if ( aId.isEmpty() + || aPassword.isEmpty()) + return setError(E_FAIL, tr("The ID and password must be both valid")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + size_t cbKey = aPassword.length() + 1; /* Include terminator */ + const uint8_t *pbKey = (const uint8_t *)aPassword.c_str(); + + int vrc = m_pKeyStore->addSecretKey(aId, pbKey, cbKey); + if ( RT_SUCCESS(vrc) +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + || vrc == VERR_ALREADY_EXISTS /* Allow setting an existing key for encrypted VMs. */ +#endif + ) + { + unsigned cDisksConfigured = 0; + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + if (mptrNvramStore.isNotNull()) + mptrNvramStore->i_addPassword(aId, aPassword); + + SecretKey *pKey = NULL; + vrc = m_pKeyStore->retainSecretKey(aId, &pKey); + AssertRCReturn(vrc, E_FAIL); + pKey->setRemoveOnSuspend(!!aClearOnSuspend); + pKey->release(); +#endif + + hrc = i_configureEncryptionForDisk(aId, &cDisksConfigured); + if (SUCCEEDED(hrc)) + { +#ifndef VBOX_WITH_FULL_VM_ENCRYPTION + SecretKey *pKey = NULL; +#endif + vrc = m_pKeyStore->retainSecretKey(aId, &pKey); + AssertRCReturn(vrc, E_FAIL); + + pKey->setUsers(cDisksConfigured); +#ifndef VBOX_WITH_FULL_VM_ENCRYPTION + pKey->setRemoveOnSuspend(!!aClearOnSuspend); + m_pKeyStore->releaseSecretKey(aId); +#endif + m_cDisksPwProvided += cDisksConfigured; + + if ( m_cDisksPwProvided == m_cDisksEncrypted + && mMachineState == MachineState_Paused) + { + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + alock.release(); + vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_RECONFIG); + + hrc = RT_SUCCESS(vrc) ? S_OK + : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc); + } + } + } +#ifndef VBOX_WITH_FULL_VM_ENCRYPTION + else if (vrc == VERR_ALREADY_EXISTS) + hrc = setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password with the given ID already exists")); +#endif + else if (vrc == VERR_NO_MEMORY) + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to allocate enough secure memory for the key")); + else + hrc = setErrorBoth(E_FAIL, vrc, tr("Unknown error happened while adding a password (%Rrc)"), vrc); + + return hrc; +} + +HRESULT Console::addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds, const std::vector<com::Utf8Str> &aPasswords, + BOOL aClearOnSuspend) +{ + HRESULT hrc = S_OK; + + if ( aIds.empty() + || aPasswords.empty()) + return setError(E_FAIL, tr("IDs and passwords must not be empty")); + + if (aIds.size() != aPasswords.size()) + return setError(E_FAIL, tr("The number of entries in the id and password arguments must match")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + +#ifndef VBOX_WITH_FULL_VM_ENCRYPTION + /* Check that the IDs do not exist already before changing anything. */ + for (unsigned i = 0; i < aIds.size(); i++) + { + SecretKey *pKey = NULL; + int vrc = m_pKeyStore->retainSecretKey(aIds[i], &pKey); + if (vrc != VERR_NOT_FOUND) + { + AssertPtr(pKey); + if (pKey) + pKey->release(); + return setError(VBOX_E_OBJECT_IN_USE, tr("A password with the given ID already exists")); + } + } +#else + /* + * Passwords for the same ID can be added in different ways because + * of encrypted VMs now. Just add them instead of generating an error. + */ + /** @todo Check that passwords with the same ID match. */ +#endif + + for (unsigned i = 0; i < aIds.size(); i++) + { + hrc = addEncryptionPassword(aIds[i], aPasswords[i], aClearOnSuspend); + if (FAILED(hrc)) + { + /* + * Try to remove already successfully added passwords from the map to not + * change the state of the Console object. + */ + ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */ + for (unsigned ii = 0; ii < i; ii++) + { + i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(aIds[ii]); + removeEncryptionPassword(aIds[ii]); + } + + break; + } + } + + return hrc; +} + +HRESULT Console::removeEncryptionPassword(const com::Utf8Str &aId) +{ + if (aId.isEmpty()) + return setError(E_FAIL, tr("The ID must be valid")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + SecretKey *pKey = NULL; + int vrc = m_pKeyStore->retainSecretKey(aId, &pKey); + if (RT_SUCCESS(vrc)) + { + m_cDisksPwProvided -= pKey->getUsers(); + m_pKeyStore->releaseSecretKey(aId); + vrc = m_pKeyStore->deleteSecretKey(aId); + AssertRCReturn(vrc, E_FAIL); + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + if (mptrNvramStore.isNotNull()) + mptrNvramStore->i_removePassword(aId); +#endif + } + else if (vrc == VERR_NOT_FOUND) + return setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("A password with the ID \"%s\" does not exist"), aId.c_str()); + else + return setErrorBoth(E_FAIL, vrc, tr("Failed to remove password with ID \"%s\" (%Rrc)"), aId.c_str(), vrc); + + return S_OK; +} + +HRESULT Console::clearAllEncryptionPasswords() +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + if (mptrNvramStore.isNotNull()) + mptrNvramStore->i_removeAllPasswords(); +#endif + + int vrc = m_pKeyStore->deleteAllSecretKeys(false /* fSuspend */, false /* fForce */); + if (vrc == VERR_RESOURCE_IN_USE) + return setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password is still in use by the VM")); + else if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Deleting all passwords failed (%Rrc)")); + + m_cDisksPwProvided = 0; + return S_OK; +} + +// Non-interface public methods +///////////////////////////////////////////////////////////////////////////// + +/*static*/ +HRESULT Console::i_setErrorStatic(HRESULT aResultCode, const char *pcsz, ...) +{ + va_list args; + va_start(args, pcsz); + HRESULT rc = setErrorInternalV(aResultCode, + getStaticClassIID(), + getStaticComponentName(), + pcsz, args, + false /* aWarning */, + true /* aLogIt */); + va_end(args); + return rc; +} + +/*static*/ +HRESULT Console::i_setErrorStaticBoth(HRESULT aResultCode, int vrc, const char *pcsz, ...) +{ + va_list args; + va_start(args, pcsz); + HRESULT rc = setErrorInternalV(aResultCode, + getStaticClassIID(), + getStaticComponentName(), + pcsz, args, + false /* aWarning */, + true /* aLogIt */, + vrc); + va_end(args); + return rc; +} + +HRESULT Console::i_setInvalidMachineStateError() +{ + return setError(VBOX_E_INVALID_VM_STATE, + tr("Invalid machine state: %s"), + Global::stringifyMachineState(mMachineState)); +} + + +/** + * Converts to PDM device names. + */ +/* static */ const char *Console::i_storageControllerTypeToStr(StorageControllerType_T enmCtrlType) +{ + switch (enmCtrlType) + { + case StorageControllerType_LsiLogic: + return "lsilogicscsi"; + case StorageControllerType_BusLogic: + return "buslogic"; + case StorageControllerType_LsiLogicSas: + return "lsilogicsas"; + case StorageControllerType_IntelAhci: + return "ahci"; + case StorageControllerType_PIIX3: + case StorageControllerType_PIIX4: + case StorageControllerType_ICH6: + return "piix3ide"; + case StorageControllerType_I82078: + return "i82078"; + case StorageControllerType_USB: + return "Msd"; + case StorageControllerType_NVMe: + return "nvme"; + case StorageControllerType_VirtioSCSI: + return "virtio-scsi"; + default: + return NULL; + } +} + +HRESULT Console::i_storageBusPortDeviceToLun(StorageBus_T enmBus, LONG port, LONG device, unsigned &uLun) +{ + switch (enmBus) + { + case StorageBus_IDE: + case StorageBus_Floppy: + { + AssertMsgReturn(port < 2 && port >= 0, ("%d\n", port), E_INVALIDARG); + AssertMsgReturn(device < 2 && device >= 0, ("%d\n", device), E_INVALIDARG); + uLun = 2 * port + device; + return S_OK; + } + case StorageBus_SATA: + case StorageBus_SCSI: + case StorageBus_SAS: + case StorageBus_PCIe: + case StorageBus_VirtioSCSI: + { + uLun = port; + return S_OK; + } + case StorageBus_USB: + { + /* + * It is always the first lun, the port denotes the device instance + * for the Msd device. + */ + uLun = 0; + return S_OK; + } + default: + uLun = 0; + AssertMsgFailedReturn(("%d\n", enmBus), E_INVALIDARG); + } +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Suspend the VM before we do any medium or network attachment change. + * + * @param pUVM Safe VM handle. + * @param pVMM Safe VMM vtable. + * @param pAlock The automatic lock instance. This is for when we have + * to leave it in order to avoid deadlocks. + * @param pfResume where to store the information if we need to resume + * afterwards. + */ +HRESULT Console::i_suspendBeforeConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock, bool *pfResume) +{ + *pfResume = false; + + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + switch (enmVMState) + { + case VMSTATE_RUNNING: + case VMSTATE_RESETTING: + case VMSTATE_SOFT_RESETTING: + { + LogFlowFunc(("Suspending the VM...\n")); + /* disable the callback to prevent Console-level state change */ + mVMStateChangeCallbackDisabled = true; + if (pAlock) + pAlock->release(); + int vrc = pVMM->pfnVMR3Suspend(pUVM, VMSUSPENDREASON_RECONFIG); + if (pAlock) + pAlock->acquire(); + mVMStateChangeCallbackDisabled = false; + if (RT_FAILURE(vrc)) + return setErrorInternalF(VBOX_E_INVALID_VM_STATE, + COM_IIDOF(IConsole), + getStaticComponentName(), + false /*aWarning*/, + true /*aLogIt*/, + vrc, + tr("Could suspend VM for medium change (%Rrc)"), vrc); + *pfResume = true; + break; + } + case VMSTATE_SUSPENDED: + break; + default: + return setErrorInternalF(VBOX_E_INVALID_VM_STATE, + COM_IIDOF(IConsole), + getStaticComponentName(), + false /*aWarning*/, + true /*aLogIt*/, + 0 /* aResultDetail */, + tr("Invalid state '%s' for changing medium"), + pVMM->pfnVMR3GetStateName(enmVMState)); + } + + return S_OK; +} + +/** + * Resume the VM after we did any medium or network attachment change. + * This is the counterpart to Console::suspendBeforeConfigChange(). + * + * @param pUVM Safe VM handle. + * @param pVMM Safe VMM vtable. + */ +void Console::i_resumeAfterConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + LogFlowFunc(("Resuming the VM...\n")); + + /* disable the callback to prevent Console-level state change */ + mVMStateChangeCallbackDisabled = true; + int rc = pVMM->pfnVMR3Resume(pUVM, VMRESUMEREASON_RECONFIG); + mVMStateChangeCallbackDisabled = false; + AssertRC(rc); + if (RT_FAILURE(rc)) + { + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + if (enmVMState == VMSTATE_SUSPENDED) + { + /* too bad, we failed. try to sync the console state with the VMM state */ + i_vmstateChangeCallback(pUVM, pVMM, VMSTATE_SUSPENDED, enmVMState, this); + } + } +} + +/** + * Process a medium change. + * + * @param aMediumAttachment The medium attachment with the new medium state. + * @param fForce Force medium chance, if it is locked or not. + * @param pUVM Safe VM handle. + * @param pVMM Safe VMM vtable. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_doMediumChange(IMediumAttachment *aMediumAttachment, bool fForce, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* We will need to release the write lock before calling EMT */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + const char *pszDevice = NULL; + + SafeIfaceArray<IStorageController> ctrls; + rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); + AssertComRC(rc); + IMedium *pMedium; + rc = aMediumAttachment->COMGETTER(Medium)(&pMedium); + AssertComRC(rc); + Bstr mediumLocation; + if (pMedium) + { + rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam()); + AssertComRC(rc); + } + + Bstr attCtrlName; + rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam()); + AssertComRC(rc); + ComPtr<IStorageController> pStorageController; + for (size_t i = 0; i < ctrls.size(); ++i) + { + Bstr ctrlName; + rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam()); + AssertComRC(rc); + if (attCtrlName == ctrlName) + { + pStorageController = ctrls[i]; + break; + } + } + if (pStorageController.isNull()) + return setError(E_FAIL, + tr("Could not find storage controller '%ls'"), attCtrlName.raw()); + + StorageControllerType_T enmCtrlType; + rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(rc); + pszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + rc = pStorageController->COMGETTER(Bus)(&enmBus); + AssertComRC(rc); + ULONG uInstance; + rc = pStorageController->COMGETTER(Instance)(&uInstance); + AssertComRC(rc); + BOOL fUseHostIOCache; + rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache); + AssertComRC(rc); + + /* + * Suspend the VM first. The VM must not be running since it might have + * pending I/O to the drive which is being changed. + */ + bool fResume = false; + rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker on EMT #0, that's faster and safer than doing everything + * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait + * here to make requests from under the lock in order to serialize them. + */ + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_changeRemovableMedium, 9, + this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fForce); + + /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + if (vrc == VERR_TIMEOUT) + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + pVMM->pfnVMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM, pVMM); + + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Returns S_OK\n")); + return S_OK; + } + + if (pMedium) + return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc); + return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc); +} + +/** + * Performs the medium change in EMT. + * + * @returns VBox status code. + * + * @param pThis Pointer to the Console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pcszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param enmBus The storage bus type of the controller. + * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O). + * @param aMediumAtt The medium attachment. + * @param fForce Force unmounting. + * + * @thread EMT + * @note The VM must not be running since it might have pending I/O to the drive which is being changed. + */ +DECLCALLBACK(int) Console::i_changeRemovableMedium(Console *pThis, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + const char *pcszDevice, + unsigned uInstance, + StorageBus_T enmBus, + bool fUseHostIOCache, + IMediumAttachment *aMediumAtt, + bool fForce) +{ + LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p, fForce=%d\n", + pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt, fForce)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + AutoCaller autoCaller(pThis); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + /* + * Check the VM for correct state. + */ + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + int rc = pThis->i_configMediumAttachment(pcszDevice, + uInstance, + enmBus, + fUseHostIOCache, + false /* fSetupMerge */, + false /* fBuiltinIOCache */, + false /* fInsertDiskIntegrityDrv. */, + 0 /* uMergeSource */, + 0 /* uMergeTarget */, + aMediumAtt, + pThis->mMachineState, + NULL /* phrc */, + true /* fAttachDetach */, + fForce /* fForceUnmount */, + false /* fHotplug */, + pUVM, + pVMM, + NULL /* paLedDevType */, + NULL /* ppLunL0 */); + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * Attach a new storage device to the VM. + * + * @param aMediumAttachment The medium attachment which is added. + * @param pUVM Safe VM handle. + * @param pVMM Safe VMM vtable. + * @param fSilent Flag whether to notify the guest about the attached device. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_doStorageDeviceAttach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* We will need to release the write lock before calling EMT */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + const char *pszDevice = NULL; + + SafeIfaceArray<IStorageController> ctrls; + rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); + AssertComRC(rc); + IMedium *pMedium; + rc = aMediumAttachment->COMGETTER(Medium)(&pMedium); + AssertComRC(rc); + Bstr mediumLocation; + if (pMedium) + { + rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam()); + AssertComRC(rc); + } + + Bstr attCtrlName; + rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam()); + AssertComRC(rc); + ComPtr<IStorageController> pStorageController; + for (size_t i = 0; i < ctrls.size(); ++i) + { + Bstr ctrlName; + rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam()); + AssertComRC(rc); + if (attCtrlName == ctrlName) + { + pStorageController = ctrls[i]; + break; + } + } + if (pStorageController.isNull()) + return setError(E_FAIL, tr("Could not find storage controller '%ls'"), attCtrlName.raw()); + + StorageControllerType_T enmCtrlType; + rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(rc); + pszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + rc = pStorageController->COMGETTER(Bus)(&enmBus); + AssertComRC(rc); + ULONG uInstance; + rc = pStorageController->COMGETTER(Instance)(&uInstance); + AssertComRC(rc); + BOOL fUseHostIOCache; + rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache); + AssertComRC(rc); + + /* + * Suspend the VM first. The VM must not be running since it might have + * pending I/O to the drive which is being changed. + */ + bool fResume = false; + rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker on EMT #0, that's faster and safer than doing everything + * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait + * here to make requests from under the lock in order to serialize them. + */ + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_attachStorageDevice, 9, + this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fSilent); + + /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + if (vrc == VERR_TIMEOUT) + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + pVMM->pfnVMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM, pVMM); + + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Returns S_OK\n")); + return S_OK; + } + + if (!pMedium) + return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc); + return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc); +} + + +/** + * Performs the storage attach operation in EMT. + * + * @returns VBox status code. + * + * @param pThis Pointer to the Console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pcszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param enmBus The storage bus type of the controller. + * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O). + * @param aMediumAtt The medium attachment. + * @param fSilent Flag whether to inform the guest about the attached device. + * + * @thread EMT + * @note The VM must not be running since it might have pending I/O to the drive which is being changed. + */ +DECLCALLBACK(int) Console::i_attachStorageDevice(Console *pThis, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + const char *pcszDevice, + unsigned uInstance, + StorageBus_T enmBus, + bool fUseHostIOCache, + IMediumAttachment *aMediumAtt, + bool fSilent) +{ + LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p\n", + pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + AutoCaller autoCaller(pThis); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + /* + * Check the VM for correct state. + */ + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + int rc = pThis->i_configMediumAttachment(pcszDevice, + uInstance, + enmBus, + fUseHostIOCache, + false /* fSetupMerge */, + false /* fBuiltinIOCache */, + false /* fInsertDiskIntegrityDrv. */, + 0 /* uMergeSource */, + 0 /* uMergeTarget */, + aMediumAtt, + pThis->mMachineState, + NULL /* phrc */, + true /* fAttachDetach */, + false /* fForceUnmount */, + !fSilent /* fHotplug */, + pUVM, + pVMM, + NULL /* paLedDevType */, + NULL); + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * Attach a new storage device to the VM. + * + * @param aMediumAttachment The medium attachment which is added. + * @param pUVM Safe VM handle. + * @param pVMM Safe VMM vtable. + * @param fSilent Flag whether to notify the guest about the detached device. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_doStorageDeviceDetach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* We will need to release the write lock before calling EMT */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + const char *pszDevice = NULL; + + SafeIfaceArray<IStorageController> ctrls; + rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); + AssertComRC(rc); + IMedium *pMedium; + rc = aMediumAttachment->COMGETTER(Medium)(&pMedium); + AssertComRC(rc); + Bstr mediumLocation; + if (pMedium) + { + rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam()); + AssertComRC(rc); + } + + Bstr attCtrlName; + rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam()); + AssertComRC(rc); + ComPtr<IStorageController> pStorageController; + for (size_t i = 0; i < ctrls.size(); ++i) + { + Bstr ctrlName; + rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam()); + AssertComRC(rc); + if (attCtrlName == ctrlName) + { + pStorageController = ctrls[i]; + break; + } + } + if (pStorageController.isNull()) + return setError(E_FAIL, tr("Could not find storage controller '%ls'"), attCtrlName.raw()); + + StorageControllerType_T enmCtrlType; + rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(rc); + pszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + rc = pStorageController->COMGETTER(Bus)(&enmBus); + AssertComRC(rc); + ULONG uInstance; + rc = pStorageController->COMGETTER(Instance)(&uInstance); + AssertComRC(rc); + + /* + * Suspend the VM first. The VM must not be running since it might have + * pending I/O to the drive which is being changed. + */ + bool fResume = false; + rc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker on EMT #0, that's faster and safer than doing everything + * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait + * here to make requests from under the lock in order to serialize them. + */ + PVMREQ pReq; + int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_detachStorageDevice, 8, + this, pUVM, pVMM, pszDevice, uInstance, enmBus, aMediumAttachment, fSilent); + + /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + if (vrc == VERR_TIMEOUT) + vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + pVMM->pfnVMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM, pVMM); + + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Returns S_OK\n")); + return S_OK; + } + + if (!pMedium) + return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc); + return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc); +} + +/** + * Performs the storage detach operation in EMT. + * + * @returns VBox status code. + * + * @param pThis Pointer to the Console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pcszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param enmBus The storage bus type of the controller. + * @param pMediumAtt Pointer to the medium attachment. + * @param fSilent Flag whether to notify the guest about the detached device. + * + * @thread EMT + * @note The VM must not be running since it might have pending I/O to the drive which is being changed. + */ +DECLCALLBACK(int) Console::i_detachStorageDevice(Console *pThis, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + const char *pcszDevice, + unsigned uInstance, + StorageBus_T enmBus, + IMediumAttachment *pMediumAtt, + bool fSilent) +{ + LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, pMediumAtt=%p\n", + pThis, uInstance, pcszDevice, pcszDevice, enmBus, pMediumAtt)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + AutoCaller autoCaller(pThis); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + /* + * Check the VM for correct state. + */ + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + /* Determine the base path for the device instance. */ + PCFGMNODE pCtlInst; + pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance); + AssertReturn(pCtlInst || enmBus == StorageBus_USB, VERR_INTERNAL_ERROR); + +#define H() AssertMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_GENERAL_FAILURE) + + HRESULT hrc; + int rc = VINF_SUCCESS; + int rcRet = VINF_SUCCESS; + unsigned uLUN; + LONG lDev; + LONG lPort; + DeviceType_T lType; + PCFGMNODE pLunL0 = NULL; + + hrc = pMediumAtt->COMGETTER(Device)(&lDev); H(); + hrc = pMediumAtt->COMGETTER(Port)(&lPort); H(); + hrc = pMediumAtt->COMGETTER(Type)(&lType); H(); + hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H(); + +#undef H + + if (enmBus != StorageBus_USB) + { + /* First check if the LUN really exists. */ + pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + if (pLunL0) + { + uint32_t fFlags = 0; + + if (fSilent) + fFlags |= PDM_TACH_FLAGS_NOT_HOT_PLUG; + + rc = pVMM->pfnPDMR3DeviceDetach(pUVM, pcszDevice, uInstance, uLUN, fFlags); + if (rc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + rc = VINF_SUCCESS; + AssertRCReturn(rc, rc); + pVMM->pfnCFGMR3RemoveNode(pLunL0); + + Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN); + pThis->mapMediumAttachments.erase(devicePath); + + } + else + AssertFailedReturn(VERR_INTERNAL_ERROR); + + pVMM->pfnCFGMR3Dump(pCtlInst); + } +#ifdef VBOX_WITH_USB + else + { + /* Find the correct USB device in the list. */ + USBStorageDeviceList::iterator it; + for (it = pThis->mUSBStorageDevices.begin(); it != pThis->mUSBStorageDevices.end(); ++it) + { + if (it->iPort == lPort) + break; + } + + AssertReturn(it != pThis->mUSBStorageDevices.end(), VERR_INTERNAL_ERROR); + rc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, &it->mUuid); + AssertRCReturn(rc, rc); + pThis->mUSBStorageDevices.erase(it); + } +#endif + + LogFlowFunc(("Returning %Rrc\n", rcRet)); + return rcRet; +} + +/** + * Called by IInternalSessionControl::OnNetworkAdapterChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onNetworkAdapterChange(INetworkAdapter *aNetworkAdapter, BOOL changeAdapter) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger network changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + /* Get the properties we need from the adapter */ + BOOL fCableConnected, fTraceEnabled; + rc = aNetworkAdapter->COMGETTER(CableConnected)(&fCableConnected); + AssertComRC(rc); + if (SUCCEEDED(rc)) + { + rc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fTraceEnabled); + AssertComRC(rc); + if (SUCCEEDED(rc)) + { + ULONG ulInstance; + rc = aNetworkAdapter->COMGETTER(Slot)(&ulInstance); + AssertComRC(rc); + if (SUCCEEDED(rc)) + { + /* + * Find the adapter instance, get the config interface and update + * the link state. + */ + NetworkAdapterType_T adapterType; + rc = aNetworkAdapter->COMGETTER(AdapterType)(&adapterType); + AssertComRC(rc); + const char *pszAdapterName = networkAdapterTypeToName(adapterType); + + // prevent cross-thread deadlocks, don't need the lock any more + alock.release(); + + PPDMIBASE pBase = NULL; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMINETWORKCONFIG pINetCfg; + pINetCfg = PDMIBASE_QUERY_INTERFACE(pBase, PDMINETWORKCONFIG); + if (pINetCfg) + { + Log(("Console::onNetworkAdapterChange: setting link state to %d\n", + fCableConnected)); + vrc = pINetCfg->pfnSetLinkState(pINetCfg, + fCableConnected ? PDMNETWORKLINKSTATE_UP + : PDMNETWORKLINKSTATE_DOWN); + ComAssertRC(vrc); + } + if (RT_SUCCESS(vrc) && changeAdapter) + { + VMSTATE enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM()); + if ( enmVMState == VMSTATE_RUNNING /** @todo LiveMigration: Forbid or deal + correctly with the _LS variants */ + || enmVMState == VMSTATE_SUSPENDED) + { + if (fTraceEnabled && fCableConnected && pINetCfg) + { + vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_DOWN); + ComAssertRC(vrc); + } + + rc = i_doNetworkAdapterChange(ptrVM.rawUVM(), ptrVM.vtable(), pszAdapterName, + ulInstance, 0, aNetworkAdapter); + + if (fTraceEnabled && fCableConnected && pINetCfg) + { + vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_UP); + ComAssertRC(vrc); + } + } + } + } + else if (vrc == VERR_PDM_DEVICE_INSTANCE_NOT_FOUND) + return setErrorBoth(E_FAIL, vrc, tr("The network adapter #%u is not enabled"), ulInstance); + else + ComAssertRC(vrc); + + if (RT_FAILURE(vrc)) + rc = E_FAIL; + + alock.acquire(); + } + } + } + ptrVM.release(); + } + + // definitely don't need the lock any more + alock.release(); + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + ::FireNetworkAdapterChangedEvent(mEventSource, aNetworkAdapter); + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnNATEngineChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onNATRedirectRuleChanged(ULONG ulInstance, BOOL aNatRuleRemove, NATProtocol_T aProto, IN_BSTR aHostIP, + LONG aHostPort, IN_BSTR aGuestIP, LONG aGuestPort) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger NAT engine changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + do + { + ComPtr<INetworkAdapter> pNetworkAdapter; + rc = i_machine()->GetNetworkAdapter(ulInstance, pNetworkAdapter.asOutParam()); + if ( FAILED(rc) + || pNetworkAdapter.isNull()) + break; + + /* + * Find the adapter instance, get the config interface and update + * the link state. + */ + NetworkAdapterType_T adapterType; + rc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType); + if (FAILED(rc)) + { + AssertComRC(rc); + rc = E_FAIL; + break; + } + + const char *pszAdapterName = networkAdapterTypeToName(adapterType); + PPDMIBASE pBase; + int vrc = ptrVM.vtable()->pfnPDMR3QueryLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase); + if (RT_FAILURE(vrc)) + { + /* This may happen if the NAT network adapter is currently not attached. + * This is a valid condition. */ + if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + break; + ComAssertRC(vrc); + rc = E_FAIL; + break; + } + + NetworkAttachmentType_T attachmentType; + rc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType); + if ( FAILED(rc) + || attachmentType != NetworkAttachmentType_NAT) + { + rc = E_FAIL; + break; + } + + /* look down for PDMINETWORKNATCONFIG interface */ + PPDMINETWORKNATCONFIG pNetNatCfg = NULL; + while (pBase) + { + pNetNatCfg = (PPDMINETWORKNATCONFIG)pBase->pfnQueryInterface(pBase, PDMINETWORKNATCONFIG_IID); + if (pNetNatCfg) + break; + /** @todo r=bird: This stinks! */ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pBase); + pBase = pDrvIns->pDownBase; + } + if (!pNetNatCfg) + break; + + bool fUdp = aProto == NATProtocol_UDP; + vrc = pNetNatCfg->pfnRedirectRuleCommand(pNetNatCfg, !!aNatRuleRemove, fUdp, + Utf8Str(aHostIP).c_str(), (uint16_t)aHostPort, Utf8Str(aGuestIP).c_str(), + (uint16_t)aGuestPort); + if (RT_FAILURE(vrc)) + rc = E_FAIL; + } while (0); /* break loop */ + ptrVM.release(); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + + +/* + * IHostNameResolutionConfigurationChangeEvent + * + * Currently this event doesn't carry actual resolver configuration, + * so we have to go back to VBoxSVC and ask... This is not ideal. + */ +HRESULT Console::i_onNATDnsChanged() +{ + HRESULT hrc; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + +#if 0 /* XXX: We don't yet pass this down to pfnNotifyDnsChanged */ + ComPtr<IVirtualBox> pVirtualBox; + hrc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + if (FAILED(hrc)) + return S_OK; + + ComPtr<IHost> pHost; + hrc = pVirtualBox->COMGETTER(Host)(pHost.asOutParam()); + if (FAILED(hrc)) + return S_OK; + + SafeArray<BSTR> aNameServers; + hrc = pHost->COMGETTER(NameServers)(ComSafeArrayAsOutParam(aNameServers)); + if (FAILED(hrc)) + return S_OK; + + const size_t cNameServers = aNameServers.size(); + Log(("DNS change - %zu nameservers\n", cNameServers)); + + for (size_t i = 0; i < cNameServers; ++i) + { + com::Utf8Str strNameServer(aNameServers[i]); + Log(("- nameserver[%zu] = \"%s\"\n", i, strNameServer.c_str())); + } + + com::Bstr domain; + pHost->COMGETTER(DomainName)(domain.asOutParam()); + Log(("domain name = \"%s\"\n", com::Utf8Str(domain).c_str())); +#endif /* 0 */ + + ChipsetType_T enmChipsetType; + hrc = mMachine->COMGETTER(ChipsetType)(&enmChipsetType); + if (!FAILED(hrc)) + { + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + ULONG ulInstanceMax = (ULONG)Global::getMaxNetworkAdapters(enmChipsetType); + + notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "pcnet", ulInstanceMax); + notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "e1000", ulInstanceMax); + notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "virtio-net", ulInstanceMax); + } + } + + return S_OK; +} + + +/* + * This routine walks over all network device instances, checking if + * device instance has DrvNAT attachment and triggering DrvNAT DNS + * change callback. + */ +void Console::notifyNatDnsChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, ULONG ulInstanceMax) +{ + Log(("notifyNatDnsChange: looking for DrvNAT attachment on %s device instances\n", pszDevice)); + for (ULONG ulInstance = 0; ulInstance < ulInstanceMax; ulInstance++) + { + PPDMIBASE pBase; + int rc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pszDevice, ulInstance, 0 /* iLun */, "NAT", &pBase); + if (RT_FAILURE(rc)) + continue; + + Log(("Instance %s#%d has DrvNAT attachment; do actual notify\n", pszDevice, ulInstance)); + if (pBase) + { + PPDMINETWORKNATCONFIG pNetNatCfg = NULL; + pNetNatCfg = (PPDMINETWORKNATCONFIG)pBase->pfnQueryInterface(pBase, PDMINETWORKNATCONFIG_IID); + if (pNetNatCfg && pNetNatCfg->pfnNotifyDnsChanged) + pNetNatCfg->pfnNotifyDnsChanged(pNetNatCfg); + } + } +} + + +VMMDevMouseInterface *Console::i_getVMMDevMouseInterface() +{ + return m_pVMMDev; +} + +DisplayMouseInterface *Console::i_getDisplayMouseInterface() +{ + return mDisplay; +} + +/** + * Parses one key value pair. + * + * @returns VBox status code. + * @param psz Configuration string. + * @param ppszEnd Where to store the pointer to the string following the key value pair. + * @param ppszKey Where to store the key on success. + * @param ppszVal Where to store the value on success. + */ +int Console::i_consoleParseKeyValue(const char *psz, const char **ppszEnd, + char **ppszKey, char **ppszVal) +{ + int rc = VINF_SUCCESS; + const char *pszKeyStart = psz; + const char *pszValStart = NULL; + size_t cchKey = 0; + size_t cchVal = 0; + + while ( *psz != '=' + && *psz) + psz++; + + /* End of string at this point is invalid. */ + if (*psz == '\0') + return VERR_INVALID_PARAMETER; + + cchKey = psz - pszKeyStart; + psz++; /* Skip = character */ + pszValStart = psz; + + while ( *psz != ',' + && *psz != '\n' + && *psz != '\r' + && *psz) + psz++; + + cchVal = psz - pszValStart; + + if (cchKey && cchVal) + { + *ppszKey = RTStrDupN(pszKeyStart, cchKey); + if (*ppszKey) + { + *ppszVal = RTStrDupN(pszValStart, cchVal); + if (!*ppszVal) + { + RTStrFree(*ppszKey); + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + if (RT_SUCCESS(rc)) + *ppszEnd = psz; + + return rc; +} + +/** + * Initializes the secret key interface on all configured attachments. + * + * @returns COM status code. + */ +HRESULT Console::i_initSecretKeyIfOnAllAttachments(void) +{ + HRESULT hrc = S_OK; + SafeIfaceArray<IMediumAttachment> sfaAttachments; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* Get the VM - must be done before the read-locking. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments)); + AssertComRCReturnRC(hrc); + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + m_cDisksPwProvided = 0; +#endif + + /* Find the correct attachment. */ + for (unsigned i = 0; i < sfaAttachments.size(); i++) + { + const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i]; + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + ComPtr<IMedium> pMedium; + ComPtr<IMedium> pBase; + + hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam()); + AssertComRC(hrc); + + bool fKeepSecIf = false; + /* Skip non hard disk attachments. */ + if (pMedium.isNotNull()) + { + /* Get the UUID of the base medium and compare. */ + hrc = pMedium->COMGETTER(Base)(pBase.asOutParam()); + AssertComRC(hrc); + + Bstr bstrKeyId; + hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam()); + if (SUCCEEDED(hrc)) + { + Utf8Str strKeyId(bstrKeyId); + SecretKey *pKey = NULL; + int vrc = m_pKeyStore->retainSecretKey(strKeyId, &pKey); + if (RT_SUCCESS(vrc)) + { + fKeepSecIf = true; + m_pKeyStore->releaseSecretKey(strKeyId); + } + } + } +#endif + + /* + * Query storage controller, port and device + * to identify the correct driver. + */ + ComPtr<IStorageController> pStorageCtrl; + Bstr storageCtrlName; + LONG lPort, lDev; + ULONG ulStorageCtrlInst; + + hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam()); + AssertComRC(hrc); + + hrc = pAtt->COMGETTER(Port)(&lPort); + AssertComRC(hrc); + + hrc = pAtt->COMGETTER(Device)(&lDev); + AssertComRC(hrc); + + hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam()); + AssertComRC(hrc); + + hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst); + AssertComRC(hrc); + + StorageControllerType_T enmCtrlType; + hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(hrc); + const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus); + AssertComRC(hrc); + + unsigned uLUN; + hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); + AssertComRC(hrc); + + PPDMIBASE pIBase = NULL; + PPDMIMEDIA pIMedium = NULL; + int rc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase); + if (RT_SUCCESS(rc)) + { + if (pIBase) + { + pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (pIMedium) + { +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + rc = pIMedium->pfnSetSecKeyIf(pIMedium, fKeepSecIf ? mpIfSecKey : NULL, mpIfSecKeyHlp); + Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED); + if (fKeepSecIf) + m_cDisksPwProvided++; +#else + rc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp); + Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED); +#endif + } + } + } + } + + return hrc; +} + +/** + * Removes the key interfaces from all disk attachments with the given key ID. + * Useful when changing the key store or dropping it. + * + * @returns COM status code. + * @param strId The ID to look for. + */ +HRESULT Console::i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(const Utf8Str &strId) +{ + HRESULT hrc = S_OK; + SafeIfaceArray<IMediumAttachment> sfaAttachments; + + /* Get the VM - must be done before the read-locking. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments)); + AssertComRCReturnRC(hrc); + + /* Find the correct attachment. */ + for (unsigned i = 0; i < sfaAttachments.size(); i++) + { + const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i]; + ComPtr<IMedium> pMedium; + ComPtr<IMedium> pBase; + Bstr bstrKeyId; + + hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam()); + if (FAILED(hrc)) + break; + + /* Skip non hard disk attachments. */ + if (pMedium.isNull()) + continue; + + /* Get the UUID of the base medium and compare. */ + hrc = pMedium->COMGETTER(Base)(pBase.asOutParam()); + if (FAILED(hrc)) + break; + + hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam()); + if (hrc == VBOX_E_OBJECT_NOT_FOUND) + { + hrc = S_OK; + continue; + } + else if (FAILED(hrc)) + break; + + if (strId.equals(Utf8Str(bstrKeyId))) + { + + /* + * Query storage controller, port and device + * to identify the correct driver. + */ + ComPtr<IStorageController> pStorageCtrl; + Bstr storageCtrlName; + LONG lPort, lDev; + ULONG ulStorageCtrlInst; + + hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam()); + AssertComRC(hrc); + + hrc = pAtt->COMGETTER(Port)(&lPort); + AssertComRC(hrc); + + hrc = pAtt->COMGETTER(Device)(&lDev); + AssertComRC(hrc); + + hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam()); + AssertComRC(hrc); + + hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst); + AssertComRC(hrc); + + StorageControllerType_T enmCtrlType; + hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(hrc); + const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus); + AssertComRC(hrc); + + unsigned uLUN; + hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); + AssertComRC(hrc); + + PPDMIBASE pIBase = NULL; + PPDMIMEDIA pIMedium = NULL; + int rc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase); + if (RT_SUCCESS(rc)) + { + if (pIBase) + { + pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (pIMedium) + { + rc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp); + Assert(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED); + } + } + } + } + } + + return hrc; +} + +/** + * Configures the encryption support for the disk which have encryption conigured + * with the configured key. + * + * @returns COM status code. + * @param strId The ID of the password. + * @param pcDisksConfigured Where to store the number of disks configured for the given ID. + */ +HRESULT Console::i_configureEncryptionForDisk(const com::Utf8Str &strId, unsigned *pcDisksConfigured) +{ + unsigned cDisksConfigured = 0; + HRESULT hrc = S_OK; + SafeIfaceArray<IMediumAttachment> sfaAttachments; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* Get the VM - must be done before the read-locking. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments)); + if (FAILED(hrc)) + return hrc; + + /* Find the correct attachment. */ + for (unsigned i = 0; i < sfaAttachments.size(); i++) + { + const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i]; + ComPtr<IMedium> pMedium; + ComPtr<IMedium> pBase; + Bstr bstrKeyId; + + hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam()); + if (FAILED(hrc)) + break; + + /* Skip non hard disk attachments. */ + if (pMedium.isNull()) + continue; + + /* Get the UUID of the base medium and compare. */ + hrc = pMedium->COMGETTER(Base)(pBase.asOutParam()); + if (FAILED(hrc)) + break; + + hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam()); + if (hrc == VBOX_E_OBJECT_NOT_FOUND) + { + hrc = S_OK; + continue; + } + else if (FAILED(hrc)) + break; + + if (strId.equals(Utf8Str(bstrKeyId))) + { + /* + * Found the matching medium, query storage controller, port and device + * to identify the correct driver. + */ + ComPtr<IStorageController> pStorageCtrl; + Bstr storageCtrlName; + LONG lPort, lDev; + ULONG ulStorageCtrlInst; + + hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam()); + if (FAILED(hrc)) + break; + + hrc = pAtt->COMGETTER(Port)(&lPort); + if (FAILED(hrc)) + break; + + hrc = pAtt->COMGETTER(Device)(&lDev); + if (FAILED(hrc)) + break; + + hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam()); + if (FAILED(hrc)) + break; + + hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst); + if (FAILED(hrc)) + break; + + StorageControllerType_T enmCtrlType; + hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(hrc); + const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus); + AssertComRC(hrc); + + unsigned uLUN; + hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); + AssertComRCReturnRC(hrc); + + PPDMIBASE pIBase = NULL; + PPDMIMEDIA pIMedium = NULL; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase); + if (RT_SUCCESS(vrc)) + { + if (pIBase) + { + pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (!pIMedium) + return setError(E_FAIL, tr("could not query medium interface of controller")); + vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp); + if (vrc == VERR_VD_PASSWORD_INCORRECT) + { + hrc = setError(VBOX_E_PASSWORD_INCORRECT, + tr("The provided password for ID \"%s\" is not correct for at least one disk using this ID"), + strId.c_str()); + break; + } + else if (RT_FAILURE(vrc)) + { + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set the encryption key (%Rrc)"), vrc); + break; + } + + if (RT_SUCCESS(vrc)) + cDisksConfigured++; + } + else + return setError(E_FAIL, tr("could not query base interface of controller")); + } + } + } + + if ( SUCCEEDED(hrc) + && pcDisksConfigured) + *pcDisksConfigured = cDisksConfigured; + else if (FAILED(hrc)) + { + /* Clear disk encryption setup on successfully configured attachments. */ + ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */ + i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(strId); + } + + return hrc; +} + +/** + * Parses the encryption configuration for one disk. + * + * @returns COM status code. + * @param psz Pointer to the configuration for the encryption of one disk. + * @param ppszEnd Pointer to the string following encrpytion configuration. + */ +HRESULT Console::i_consoleParseDiskEncryption(const char *psz, const char **ppszEnd) +{ + char *pszUuid = NULL; + char *pszKeyEnc = NULL; + int rc = VINF_SUCCESS; + HRESULT hrc = S_OK; + + while ( *psz + && RT_SUCCESS(rc)) + { + char *pszKey = NULL; + char *pszVal = NULL; + const char *pszEnd = NULL; + + rc = i_consoleParseKeyValue(psz, &pszEnd, &pszKey, &pszVal); + if (RT_SUCCESS(rc)) + { + if (!RTStrCmp(pszKey, "uuid")) + pszUuid = pszVal; + else if (!RTStrCmp(pszKey, "dek")) + pszKeyEnc = pszVal; + else + rc = VERR_INVALID_PARAMETER; + + RTStrFree(pszKey); + + if (*pszEnd == ',') + psz = pszEnd + 1; + else + { + /* + * End of the configuration for the current disk, skip linefeed and + * carriage returns. + */ + while ( *pszEnd == '\n' + || *pszEnd == '\r') + pszEnd++; + + psz = pszEnd; + break; /* Stop parsing */ + } + + } + } + + if ( RT_SUCCESS(rc) + && pszUuid + && pszKeyEnc) + { + ssize_t cbKey = 0; + + /* Decode the key. */ + cbKey = RTBase64DecodedSize(pszKeyEnc, NULL); + if (cbKey != -1) + { + uint8_t *pbKey; + rc = RTMemSaferAllocZEx((void **)&pbKey, cbKey, RTMEMSAFER_F_REQUIRE_NOT_PAGABLE); + if (RT_SUCCESS(rc)) + { + rc = RTBase64Decode(pszKeyEnc, pbKey, cbKey, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = m_pKeyStore->addSecretKey(Utf8Str(pszUuid), pbKey, cbKey); + if (RT_SUCCESS(rc)) + { + hrc = i_configureEncryptionForDisk(Utf8Str(pszUuid), NULL); + if (FAILED(hrc)) + { + /* Delete the key from the map. */ + rc = m_pKeyStore->deleteSecretKey(Utf8Str(pszUuid)); + AssertRC(rc); + } + } + } + else + hrc = setErrorBoth(E_FAIL, rc, tr("Failed to decode the key (%Rrc)"), rc); + + RTMemSaferFree(pbKey, cbKey); + } + else + hrc = setErrorBoth(E_FAIL, rc, tr("Failed to allocate secure memory for the key (%Rrc)"), rc); + } + else + hrc = setError(E_FAIL, + tr("The base64 encoding of the passed key is incorrect")); + } + else if (RT_SUCCESS(rc)) + hrc = setError(E_FAIL, + tr("The encryption configuration is incomplete")); + + if (pszUuid) + RTStrFree(pszUuid); + if (pszKeyEnc) + { + RTMemWipeThoroughly(pszKeyEnc, strlen(pszKeyEnc), 10 /* cMinPasses */); + RTStrFree(pszKeyEnc); + } + + if (ppszEnd) + *ppszEnd = psz; + + return hrc; +} + +HRESULT Console::i_setDiskEncryptionKeys(const Utf8Str &strCfg) +{ + HRESULT hrc = S_OK; + const char *pszCfg = strCfg.c_str(); + + while ( *pszCfg + && SUCCEEDED(hrc)) + { + const char *pszNext = NULL; + hrc = i_consoleParseDiskEncryption(pszCfg, &pszNext); + pszCfg = pszNext; + } + + return hrc; +} + +void Console::i_removeSecretKeysOnSuspend() +{ + /* Remove keys which are supposed to be removed on a suspend. */ + int rc = m_pKeyStore->deleteAllSecretKeys(true /* fSuspend */, true /* fForce */); + AssertRC(rc); NOREF(rc); +} + +/** + * Process a network adaptor change. + * + * @returns COM status code. + * + * @param pUVM The VM handle (caller hold this safely). + * @param pVMM The VMM vtable. + * @param pszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param uLun The PDM LUN number of the drive. + * @param aNetworkAdapter The network adapter whose attachment needs to be changed + */ +HRESULT Console::i_doNetworkAdapterChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, + unsigned uInstance, unsigned uLun, INetworkAdapter *aNetworkAdapter) +{ + LogFlowThisFunc(("pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n", + pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter)); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* + * Suspend the VM first. + */ + bool fResume = false; + HRESULT hr = i_suspendBeforeConfigChange(pUVM, pVMM, NULL, &fResume); + if (FAILED(hr)) + return hr; + + /* + * Call worker in EMT, that's faster and safer than doing everything + * using VM3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait + * here to make requests from under the lock in order to serialize them. + */ + int rc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /*idDstCpu*/, + (PFNRT)i_changeNetworkAttachment, 7, + this, pUVM, pVMM, pszDevice, uInstance, uLun, aNetworkAdapter); + + if (fResume) + i_resumeAfterConfigChange(pUVM, pVMM); + + if (RT_SUCCESS(rc)) + return S_OK; + + return setErrorBoth(E_FAIL, rc, tr("Could not change the network adaptor attachement type (%Rrc)"), rc); +} + + +/** + * Performs the Network Adaptor change in EMT. + * + * @returns VBox status code. + * + * @param pThis Pointer to the Console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param uLun The PDM LUN number of the drive. + * @param aNetworkAdapter The network adapter whose attachment needs to be changed + * + * @thread EMT + * @note Locks the Console object for writing. + * @note The VM must not be running. + */ +DECLCALLBACK(int) Console::i_changeNetworkAttachment(Console *pThis, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + const char *pszDevice, + unsigned uInstance, + unsigned uLun, + INetworkAdapter *aNetworkAdapter) +{ + LogFlowFunc(("pThis=%p pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n", + pThis, pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + AutoCaller autoCaller(pThis); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + ComPtr<IVirtualBox> pVirtualBox; + pThis->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + ComPtr<ISystemProperties> pSystemProperties; + if (pVirtualBox) + pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam()); + ChipsetType_T chipsetType = ChipsetType_PIIX3; + pThis->mMachine->COMGETTER(ChipsetType)(&chipsetType); + ULONG maxNetworkAdapters = 0; + if (pSystemProperties) + pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); + AssertMsg( ( !strcmp(pszDevice, "pcnet") + || !strcmp(pszDevice, "e1000") + || !strcmp(pszDevice, "virtio-net")) + && uLun == 0 + && uInstance < maxNetworkAdapters, + ("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance)); + Log(("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance)); + + /* + * Check the VM for correct state. + */ + PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */ + PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */ + PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%d/", pszDevice, uInstance); + AssertRelease(pInst); + + int rc = pThis->i_configNetwork(pszDevice, uInstance, uLun, aNetworkAdapter, pCfg, pLunL0, pInst, + true /*fAttachDetach*/, false /*fIgnoreConnectFailure*/, pUVM, pVMM); + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * Returns the device name of a given audio adapter. + * + * @returns Device name, or an empty string if no device is configured. + * @param aAudioAdapter Audio adapter to return device name for. + */ +Utf8Str Console::i_getAudioAdapterDeviceName(IAudioAdapter *aAudioAdapter) +{ + Utf8Str strDevice; + + AudioControllerType_T audioController; + HRESULT hrc = aAudioAdapter->COMGETTER(AudioController)(&audioController); + AssertComRC(hrc); + if (SUCCEEDED(hrc)) + { + switch (audioController) + { + case AudioControllerType_HDA: strDevice = "hda"; break; + case AudioControllerType_AC97: strDevice = "ichac97"; break; + case AudioControllerType_SB16: strDevice = "sb16"; break; + default: break; /* None. */ + } + } + + return strDevice; +} + +/** + * Called by IInternalSessionControl::OnAudioAdapterChange(). + */ +HRESULT Console::i_onAudioAdapterChange(IAudioAdapter *aAudioAdapter) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + + /* don't trigger audio changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + BOOL fEnabledIn, fEnabledOut; + hrc = aAudioAdapter->COMGETTER(EnabledIn)(&fEnabledIn); + AssertComRC(hrc); + if (SUCCEEDED(hrc)) + { + hrc = aAudioAdapter->COMGETTER(EnabledOut)(&fEnabledOut); + AssertComRC(hrc); + if (SUCCEEDED(hrc)) + { + int rc = VINF_SUCCESS; + + for (ULONG ulLUN = 0; ulLUN < 16 /** @todo Use a define */; ulLUN++) + { + PPDMIBASE pBase; + int rc2 = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), + i_getAudioAdapterDeviceName(aAudioAdapter).c_str(), + 0 /* iInstance */, ulLUN, "AUDIO", &pBase); + if (RT_FAILURE(rc2)) + continue; + + if (pBase) + { + PPDMIAUDIOCONNECTOR pAudioCon = (PPDMIAUDIOCONNECTOR)pBase->pfnQueryInterface(pBase, + PDMIAUDIOCONNECTOR_IID); + if ( pAudioCon + && pAudioCon->pfnEnable) + { + int rcIn = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_IN, RT_BOOL(fEnabledIn)); + if (RT_FAILURE(rcIn)) + LogRel(("Audio: Failed to %s input of LUN#%RU32, rc=%Rrc\n", + fEnabledIn ? "enable" : "disable", ulLUN, rcIn)); + + if (RT_SUCCESS(rc)) + rc = rcIn; + + int rcOut = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_OUT, RT_BOOL(fEnabledOut)); + if (RT_FAILURE(rcOut)) + LogRel(("Audio: Failed to %s output of LUN#%RU32, rc=%Rrc\n", + fEnabledIn ? "enable" : "disable", ulLUN, rcOut)); + + if (RT_SUCCESS(rc)) + rc = rcOut; + } + } + } + + if (RT_SUCCESS(rc)) + LogRel(("Audio: Status has changed (input is %s, output is %s)\n", + fEnabledIn ? "enabled" : "disabled", fEnabledOut ? "enabled" : "disabled")); + } + } + + ptrVM.release(); + } + + alock.release(); + + /* notify console callbacks on success */ + if (SUCCEEDED(hrc)) + ::FireAudioAdapterChangedEvent(mEventSource, aAudioAdapter); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return S_OK; +} + +/** + * Called by IInternalSessionControl::OnHostAudioDeviceChange(). + */ +HRESULT Console::i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState, + IVirtualBoxErrorInfo *aErrInfo) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + + /** @todo Implement logic here. */ + + alock.release(); + + /* notify console callbacks on success */ + if (SUCCEEDED(hrc)) + ::FireHostAudioDeviceChangedEvent(mEventSource, aDevice, aNew, aState, aErrInfo); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return S_OK; +} + +/** + * Performs the Serial Port attachment change in EMT. + * + * @returns VBox status code. + * + * @param pThis Pointer to the Console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pSerialPort The serial port whose attachment needs to be changed + * + * @thread EMT + * @note Locks the Console object for writing. + * @note The VM must not be running. + */ +DECLCALLBACK(int) Console::i_changeSerialPortAttachment(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, ISerialPort *pSerialPort) +{ + LogFlowFunc(("pThis=%p pUVM=%p pSerialPort=%p\n", pThis, pUVM, pSerialPort)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + AutoCaller autoCaller(pThis); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS); + + /* + * Check the VM for correct state. + */ + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + HRESULT hrc = S_OK; + int rc = VINF_SUCCESS; + ULONG ulSlot; + hrc = pSerialPort->COMGETTER(Slot)(&ulSlot); + if (SUCCEEDED(hrc)) + { + /* Check whether the port mode changed and act accordingly. */ + Assert(ulSlot < 4); + + PortMode_T eHostMode; + hrc = pSerialPort->COMGETTER(HostMode)(&eHostMode); + if (SUCCEEDED(hrc)) + { + PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/serial/%d/", ulSlot); + AssertRelease(pInst); + + /* Remove old driver. */ + if (pThis->m_aeSerialPortMode[ulSlot] != PortMode_Disconnected) + { + rc = pVMM->pfnPDMR3DeviceDetach(pUVM, "serial", ulSlot, 0, 0); + PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pInst, "LUN#0"); + pVMM->pfnCFGMR3RemoveNode(pLunL0); + } + + if (RT_SUCCESS(rc)) + { + BOOL fServer; + Bstr bstrPath; + hrc = pSerialPort->COMGETTER(Server)(&fServer); + if (SUCCEEDED(hrc)) + hrc = pSerialPort->COMGETTER(Path)(bstrPath.asOutParam()); + + /* Configure new driver. */ + if ( SUCCEEDED(hrc) + && eHostMode != PortMode_Disconnected) + { + rc = pThis->i_configSerialPort(pInst, eHostMode, Utf8Str(bstrPath).c_str(), RT_BOOL(fServer)); + if (RT_SUCCESS(rc)) + { + /* + * Attach the driver. + */ + PPDMIBASE pBase; + rc = pVMM->pfnPDMR3DeviceAttach(pUVM, "serial", ulSlot, 0, 0, &pBase); + + pVMM->pfnCFGMR3Dump(pInst); + } + } + } + } + } + + if (RT_SUCCESS(rc) && FAILED(hrc)) + rc = VERR_INTERNAL_ERROR; + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * Called by IInternalSessionControl::OnSerialPortChange(). + */ +HRESULT Console::i_onSerialPortChange(ISerialPort *aSerialPort) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT hrc = S_OK; + + /* don't trigger audio changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + ULONG ulSlot; + BOOL fEnabled = FALSE; + hrc = aSerialPort->COMGETTER(Slot)(&ulSlot); + if (SUCCEEDED(hrc)) + hrc = aSerialPort->COMGETTER(Enabled)(&fEnabled); + if (SUCCEEDED(hrc) && fEnabled) + { + /* Check whether the port mode changed and act accordingly. */ + Assert(ulSlot < 4); + + PortMode_T eHostMode; + hrc = aSerialPort->COMGETTER(HostMode)(&eHostMode); + if (m_aeSerialPortMode[ulSlot] != eHostMode) + { + /* + * Suspend the VM first. + */ + bool fResume = false; + HRESULT hr = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), NULL, &fResume); + if (FAILED(hr)) + return hr; + + /* + * Call worker in EMT, that's faster and safer than doing everything + * using VM3ReqCallWait. + */ + int rc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /*idDstCpu*/, + (PFNRT)i_changeSerialPortAttachment, 4, + this, ptrVM.rawUVM(), ptrVM.vtable(), aSerialPort); + + if (fResume) + i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable()); + if (RT_SUCCESS(rc)) + m_aeSerialPortMode[ulSlot] = eHostMode; + else + hrc = setErrorBoth(E_FAIL, rc, tr("Failed to change the serial port attachment (%Rrc)"), rc); + } + } + } + + if (SUCCEEDED(hrc)) + ::FireSerialPortChangedEvent(mEventSource, aSerialPort); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return hrc; +} + +/** + * Called by IInternalSessionControl::OnParallelPortChange(). + */ +HRESULT Console::i_onParallelPortChange(IParallelPort *aParallelPort) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + ::FireParallelPortChangedEvent(mEventSource, aParallelPort); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return S_OK; +} + +/** + * Called by IInternalSessionControl::OnStorageControllerChange(). + */ +HRESULT Console::i_onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + ::FireStorageControllerChangedEvent(mEventSource, aMachineId.toString(), aControllerName); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return S_OK; +} + +/** + * Called by IInternalSessionControl::OnMediumChange(). + */ +HRESULT Console::i_onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForce) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT rc = S_OK; + + /* don't trigger medium changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + rc = i_doMediumChange(aMediumAttachment, !!aForce, ptrVM.rawUVM(), ptrVM.vtable()); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + ::FireMediumChangedEvent(mEventSource, aMediumAttachment); + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnCPUChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onCPUChange(ULONG aCPU, BOOL aRemove) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT rc = S_OK; + + /* don't trigger CPU changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if (aRemove) + rc = i_doCPURemove(aCPU, ptrVM.rawUVM(), ptrVM.vtable()); + else + rc = i_doCPUAdd(aCPU, ptrVM.rawUVM(), ptrVM.vtable()); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + ::FireCPUChangedEvent(mEventSource, aCPU, aRemove); + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnCpuExecutionCapChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onCPUExecutionCapChange(ULONG aExecutionCap) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger the CPU priority change if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting + ) + { + /* No need to call in the EMT thread. */ + rc = ptrVM.vtable()->pfnVMR3SetCpuExecutionCap(ptrVM.rawUVM(), aExecutionCap); + } + else + rc = i_setInvalidMachineStateError(); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireCPUExecutionCapChangedEvent(mEventSource, aExecutionCap); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnClipboardModeChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onClipboardModeChange(ClipboardMode_T aClipboardMode) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger the clipboard mode change if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting) + { + int vrc = i_changeClipboardMode(aClipboardMode); + if (RT_FAILURE(vrc)) + rc = E_FAIL; /** @todo r=andy Set error info here? */ + } + else + rc = i_setInvalidMachineStateError(); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireClipboardModeChangedEvent(mEventSource, aClipboardMode); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnClipboardFileTransferModeChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onClipboardFileTransferModeChange(bool aEnabled) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger the change if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting) + { + int vrc = i_changeClipboardFileTransferMode(aEnabled); + if (RT_FAILURE(vrc)) + rc = E_FAIL; /** @todo r=andy Set error info here? */ + } + else + rc = i_setInvalidMachineStateError(); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireClipboardFileTransferModeChangedEvent(mEventSource, aEnabled ? TRUE : FALSE); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnDnDModeChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onDnDModeChange(DnDMode_T aDnDMode) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger the drag and drop mode change if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting) + i_changeDnDMode(aDnDMode); + else + rc = i_setInvalidMachineStateError(); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireDnDModeChangedEvent(mEventSource, aDnDMode); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Check the return code of mConsoleVRDPServer->Launch. LogRel() the error reason and + * return an error message appropriate for setError(). + */ +Utf8Str Console::VRDPServerErrorToMsg(int vrc) +{ + Utf8Str errMsg; + if (vrc == VERR_NET_ADDRESS_IN_USE) + { + /* Not fatal if we start the VM, fatal if the VM is already running. */ + Bstr bstr; + mVRDEServer->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam()); + errMsg = Utf8StrFmt(tr("VirtualBox Remote Desktop Extension server can't bind to the port(s): %s"), + Utf8Str(bstr).c_str()); + LogRel(("VRDE: Warning: failed to launch VRDE server (%Rrc): %s\n", vrc, errMsg.c_str())); + } + else if (vrc == VINF_NOT_SUPPORTED) + { + /* This means that the VRDE is not installed. + * Not fatal if we start the VM, fatal if the VM is already running. */ + LogRel(("VRDE: VirtualBox Remote Desktop Extension is not available.\n")); + errMsg = Utf8Str(tr("VirtualBox Remote Desktop Extension is not available")); + } + else if (RT_FAILURE(vrc)) + { + /* Fail if the server is installed but can't start. Always fatal. */ + switch (vrc) + { + case VERR_FILE_NOT_FOUND: + errMsg = Utf8StrFmt(tr("Could not find the VirtualBox Remote Desktop Extension library")); + break; + default: + errMsg = Utf8StrFmt(tr("Failed to launch the Remote Desktop Extension server (%Rrc)"), vrc); + break; + } + LogRel(("VRDE: Failed: (%Rrc): %s\n", vrc, errMsg.c_str())); + } + + return errMsg; +} + +/** + * Called by IInternalSessionControl::OnVRDEServerChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onVRDEServerChange(BOOL aRestart) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger VRDE server changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + /* Serialize. */ + if (mfVRDEChangeInProcess) + mfVRDEChangePending = true; + else + { + do { + mfVRDEChangeInProcess = true; + mfVRDEChangePending = false; + + if ( mVRDEServer + && ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting + || mMachineState == MachineState_Paused + ) + ) + { + BOOL vrdpEnabled = FALSE; + + rc = mVRDEServer->COMGETTER(Enabled)(&vrdpEnabled); + ComAssertComRCRetRC(rc); + + if (aRestart) + { + /* VRDP server may call this Console object back from other threads (VRDP INPUT or OUTPUT). */ + alock.release(); + + if (vrdpEnabled) + { + // If there was no VRDP server started the 'stop' will do nothing. + // However if a server was started and this notification was called, + // we have to restart the server. + mConsoleVRDPServer->Stop(); + + int vrc = mConsoleVRDPServer->Launch(); + if (vrc != VINF_SUCCESS) + { + Utf8Str errMsg = VRDPServerErrorToMsg(vrc); + rc = setErrorBoth(E_FAIL, vrc, errMsg.c_str()); + } + else + { +#ifdef VBOX_WITH_AUDIO_VRDE + mAudioVRDE->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/); +#endif + mConsoleVRDPServer->EnableConnections(); + } + } + else + { + mConsoleVRDPServer->Stop(); +#ifdef VBOX_WITH_AUDIO_VRDE + mAudioVRDE->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/); +#endif + } + + alock.acquire(); + } + } + else + rc = i_setInvalidMachineStateError(); + + mfVRDEChangeInProcess = false; + } while (mfVRDEChangePending && SUCCEEDED(rc)); + } + + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireVRDEServerChangedEvent(mEventSource); + } + + return rc; +} + +void Console::i_onVRDEServerInfoChange() +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireVRDEServerInfoChangedEvent(mEventSource); +} + +HRESULT Console::i_sendACPIMonitorHotPlugEvent() +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mMachineState != MachineState_Running + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_LiveSnapshotting) + return i_setInvalidMachineStateError(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + // no need to release lock, as there are no cross-thread callbacks + + /* get the acpi device interface and press the sleep button. */ + PPDMIBASE pBase; + int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase); + if (RT_SUCCESS(vrc)) + { + Assert(pBase); + PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT); + if (pPort) + vrc = pPort->pfnMonitorHotPlugEvent(pPort); + else + vrc = VERR_PDM_MISSING_INTERFACE; + } + + HRESULT rc = RT_SUCCESS(vrc) ? S_OK + : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending monitor hot-plug event failed (%Rrc)"), vrc); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +#ifdef VBOX_WITH_RECORDING +/** + * Enables or disables recording of a VM. + * + * @returns VBox status code. + * @retval VERR_NO_CHANGE if the recording state has not been changed. + * @param fEnable Whether to enable or disable the recording. + * @param pAutoLock Pointer to auto write lock to use for attaching/detaching required driver(s) at runtime. + */ +int Console::i_recordingEnable(BOOL fEnable, util::AutoWriteLock *pAutoLock) +{ + AssertPtrReturn(pAutoLock, VERR_INVALID_POINTER); + + int vrc = VINF_SUCCESS; + + Display *pDisplay = i_getDisplay(); + if (pDisplay) + { + bool const fIsEnabled = mRecording.mCtx.IsStarted(); + + if (RT_BOOL(fEnable) != fIsEnabled) + { + LogRel(("Recording: %s\n", fEnable ? "Enabling" : "Disabling")); + + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if (fEnable) + { + vrc = i_recordingCreate(); + if (RT_SUCCESS(vrc)) + { +# ifdef VBOX_WITH_AUDIO_RECORDING + /* Attach the video recording audio driver if required. */ + if ( mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio) + && mRecording.mAudioRec) + { + vrc = mRecording.mAudioRec->applyConfiguration(mRecording.mCtx.GetConfig()); + if (RT_SUCCESS(vrc)) + vrc = mRecording.mAudioRec->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock); + + if (RT_FAILURE(vrc)) + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Attaching to audio recording driver failed (%Rrc) -- please consult log file for details"), vrc); + } +# endif + if ( RT_SUCCESS(vrc) + && mRecording.mCtx.IsReady()) /* Any video recording (audio and/or video) feature enabled? */ + { + vrc = pDisplay->i_recordingInvalidate(); + if (RT_SUCCESS(vrc)) + { + vrc = i_recordingStart(pAutoLock); + if (RT_FAILURE(vrc)) + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording start failed (%Rrc) -- please consult log file for details"), vrc); + } + } + } + else + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording initialization failed (%Rrc) -- please consult log file for details"), vrc); + + if (RT_FAILURE(vrc)) + LogRel(("Recording: Failed to enable with %Rrc\n", vrc)); + } + else + { + vrc = i_recordingStop(pAutoLock); + if (RT_SUCCESS(vrc)) + { +# ifdef VBOX_WITH_AUDIO_RECORDING + if (mRecording.mAudioRec) + mRecording.mAudioRec->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock); +# endif + i_recordingDestroy(); + } + else + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording stop failed (%Rrc) -- please consult log file for details"), vrc); + } + } + else + vrc = VERR_VM_INVALID_VM_STATE; + + if (RT_FAILURE(vrc)) + LogRel(("Recording: %s failed with %Rrc\n", fEnable ? "Enabling" : "Disabling", vrc)); + } + else /* Should not happen. */ + { + vrc = VERR_NO_CHANGE; + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording already %s"), fIsEnabled ? tr("enabled") : tr("disabled")); + } + } + + return vrc; +} +#endif /* VBOX_WITH_RECORDING */ + +/** + * Called by IInternalSessionControl::OnRecordingChange(). + */ +HRESULT Console::i_onRecordingChange(BOOL fEnabled) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; +#ifdef VBOX_WITH_RECORDING + /* Don't trigger recording changes if the VM isn't running. */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + LogFlowThisFunc(("fEnabled=%RTbool\n", RT_BOOL(fEnabled))); + + int vrc = i_recordingEnable(fEnabled, &alock); + if (RT_SUCCESS(vrc)) + { + alock.release(); + ::FireRecordingChangedEvent(mEventSource); + } + else /* Error set via ErrorInfo within i_recordingEnable() already. */ + rc = VBOX_E_IPRT_ERROR; + ptrVM.release(); + } +#else + RT_NOREF(fEnabled); +#endif /* VBOX_WITH_RECORDING */ + return rc; +} + +/** + * Called by IInternalSessionControl::OnUSBControllerChange(). + */ +HRESULT Console::i_onUSBControllerChange() +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + ::FireUSBControllerChangedEvent(mEventSource); + + return S_OK; +} + +/** + * Called by IInternalSessionControl::OnSharedFolderChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onSharedFolderChange(BOOL aGlobal) +{ + LogFlowThisFunc(("aGlobal=%RTbool\n", aGlobal)); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = i_fetchSharedFolders(aGlobal); + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireSharedFolderChangedEvent(mEventSource, aGlobal ? Scope_Global : Scope_Machine); + } + + return rc; +} + +/** + * Called by IInternalSessionControl::OnGuestDebugControlChange(). + */ +HRESULT Console::i_onGuestDebugControlChange(IGuestDebugControl *aGuestDebugControl) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT hrc = S_OK; + + /* don't trigger changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + /// @todo + } + + if (SUCCEEDED(hrc)) + ::FireGuestDebugControlChangedEvent(mEventSource, aGuestDebugControl); + + LogFlowThisFunc(("Leaving rc=%#x\n", S_OK)); + return hrc; +} + + +/** + * Called by IInternalSessionControl::OnUSBDeviceAttach() or locally by + * processRemoteUSBDevices() after IInternalMachineControl::RunUSBDeviceFilters() + * returns TRUE for a given remote USB device. + * + * @return S_OK if the device was attached to the VM. + * @return failure if not attached. + * + * @param aDevice The device in question. + * @param aError Error information. + * @param aMaskedIfs The interfaces to hide from the guest. + * @param aCaptureFilename File name where to store the USB traffic. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onUSBDeviceAttach(IUSBDevice *aDevice, IVirtualBoxErrorInfo *aError, ULONG aMaskedIfs, + const Utf8Str &aCaptureFilename) +{ +#ifdef VBOX_WITH_USB + LogFlowThisFunc(("aDevice=%p aError=%p\n", aDevice, aError)); + + AutoCaller autoCaller(this); + ComAssertComRCRetRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Get the VM pointer (we don't need error info, since it's a callback). */ + SafeVMPtrQuiet ptrVM(this); + if (!ptrVM.isOk()) + { + /* The VM may be no more operational when this message arrives + * (e.g. it may be Saving or Stopping or just PoweredOff) -- + * autoVMCaller.rc() will return a failure in this case. */ + LogFlowThisFunc(("Attach request ignored (mMachineState=%d).\n", mMachineState)); + return ptrVM.rc(); + } + + if (aError != NULL) + { + /* notify callbacks about the error */ + alock.release(); + i_onUSBDeviceStateChange(aDevice, true /* aAttached */, aError); + return S_OK; + } + + /* Don't proceed unless there's at least one USB hub. */ + if (!ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM())) + { + LogFlowThisFunc(("Attach request ignored (no USB controller).\n")); + return E_FAIL; + } + + alock.release(); + HRESULT rc = i_attachUSBDevice(aDevice, aMaskedIfs, aCaptureFilename); + if (FAILED(rc)) + { + /* take the current error info */ + com::ErrorInfoKeeper eik; + /* the error must be a VirtualBoxErrorInfo instance */ + ComPtr<IVirtualBoxErrorInfo> pError = eik.takeError(); + Assert(!pError.isNull()); + if (!pError.isNull()) + { + /* notify callbacks about the error */ + i_onUSBDeviceStateChange(aDevice, true /* aAttached */, pError); + } + } + + return rc; + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aDevice, aError, aMaskedIfs, aCaptureFilename); + return E_FAIL; +#endif /* !VBOX_WITH_USB */ +} + +/** + * Called by IInternalSessionControl::OnUSBDeviceDetach() and locally by + * processRemoteUSBDevices(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onUSBDeviceDetach(IN_BSTR aId, + IVirtualBoxErrorInfo *aError) +{ +#ifdef VBOX_WITH_USB + Guid Uuid(aId); + LogFlowThisFunc(("aId={%RTuuid} aError=%p\n", Uuid.raw(), aError)); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Find the device. */ + ComObjPtr<OUSBDevice> pUSBDevice; + USBDeviceList::iterator it = mUSBDevices.begin(); + while (it != mUSBDevices.end()) + { + LogFlowThisFunc(("it={%RTuuid}\n", (*it)->i_id().raw())); + if ((*it)->i_id() == Uuid) + { + pUSBDevice = *it; + break; + } + ++it; + } + + + if (pUSBDevice.isNull()) + { + LogFlowThisFunc(("USB device not found.\n")); + + /* The VM may be no more operational when this message arrives + * (e.g. it may be Saving or Stopping or just PoweredOff). Use + * AutoVMCaller to detect it -- AutoVMCaller::rc() will return a + * failure in this case. */ + + AutoVMCallerQuiet autoVMCaller(this); + if (FAILED(autoVMCaller.rc())) + { + LogFlowThisFunc(("Detach request ignored (mMachineState=%d).\n", + mMachineState)); + return autoVMCaller.rc(); + } + + /* the device must be in the list otherwise */ + AssertFailedReturn(E_FAIL); + } + + if (aError != NULL) + { + /* notify callback about an error */ + alock.release(); + i_onUSBDeviceStateChange(pUSBDevice, false /* aAttached */, aError); + return S_OK; + } + + /* Remove the device from the collection, it is re-added below for failures */ + mUSBDevices.erase(it); + + alock.release(); + HRESULT rc = i_detachUSBDevice(pUSBDevice); + if (FAILED(rc)) + { + /* Re-add the device to the collection */ + alock.acquire(); + mUSBDevices.push_back(pUSBDevice); + alock.release(); + /* take the current error info */ + com::ErrorInfoKeeper eik; + /* the error must be a VirtualBoxErrorInfo instance */ + ComPtr<IVirtualBoxErrorInfo> pError = eik.takeError(); + Assert(!pError.isNull()); + if (!pError.isNull()) + { + /* notify callbacks about the error */ + i_onUSBDeviceStateChange(pUSBDevice, false /* aAttached */, pError); + } + } + + return rc; + +#else /* !VBOX_WITH_USB */ + RT_NOREF(aId, aError); + return E_FAIL; +#endif /* !VBOX_WITH_USB */ +} + +/** + * Called by IInternalSessionControl::OnBandwidthGroupChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + /* don't trigger bandwidth group changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if ( mMachineState == MachineState_Running + || mMachineState == MachineState_Teleporting + || mMachineState == MachineState_LiveSnapshotting + ) + { + /* No need to call in the EMT thread. */ + Bstr bstrName; + rc = aBandwidthGroup->COMGETTER(Name)(bstrName.asOutParam()); + if (SUCCEEDED(rc)) + { + Utf8Str const strName(bstrName); + LONG64 cMax; + rc = aBandwidthGroup->COMGETTER(MaxBytesPerSec)(&cMax); + if (SUCCEEDED(rc)) + { + BandwidthGroupType_T enmType; + rc = aBandwidthGroup->COMGETTER(Type)(&enmType); + if (SUCCEEDED(rc)) + { + int vrc = VINF_SUCCESS; + if (enmType == BandwidthGroupType_Disk) + vrc = ptrVM.vtable()->pfnPDMR3AsyncCompletionBwMgrSetMaxForFile(ptrVM.rawUVM(), strName.c_str(), + (uint32_t)cMax); +#ifdef VBOX_WITH_NETSHAPER + else if (enmType == BandwidthGroupType_Network) + vrc = ptrVM.vtable()->pfnPDMR3NsBwGroupSetLimit(ptrVM.rawUVM(), strName.c_str(), cMax); + else + rc = E_NOTIMPL; +#endif + AssertRC(vrc); + } + } + } + } + else + rc = i_setInvalidMachineStateError(); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + { + alock.release(); + ::FireBandwidthGroupChangedEvent(mEventSource, aBandwidthGroup); + } + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +/** + * Called by IInternalSessionControl::OnStorageDeviceChange(). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_onStorageDeviceChange(IMediumAttachment *aMediumAttachment, BOOL aRemove, BOOL aSilent) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT rc = S_OK; + + /* don't trigger medium changes if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + if (aRemove) + rc = i_doStorageDeviceDetach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent)); + else + rc = i_doStorageDeviceAttach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent)); + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(rc)) + ::FireStorageDeviceChangedEvent(mEventSource, aMediumAttachment, aRemove, aSilent); + + LogFlowThisFunc(("Leaving rc=%#x\n", rc)); + return rc; +} + +HRESULT Console::i_onExtraDataChange(const Bstr &aMachineId, const Bstr &aKey, const Bstr &aVal) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + if (aMachineId != i_getId()) + return S_OK; + + /* don't do anything if the VM isn't running */ + if (aKey == "VBoxInternal2/TurnResetIntoPowerOff") + { + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + mfTurnResetIntoPowerOff = aVal == "1"; + int vrc = ptrVM.vtable()->pfnVMR3SetPowerOffInsteadOfReset(ptrVM.rawUVM(), mfTurnResetIntoPowerOff); + AssertRC(vrc); + + ptrVM.release(); + } + } + + /* notify console callbacks on success */ + ::FireExtraDataChangedEvent(mEventSource, aMachineId.raw(), aKey.raw(), aVal.raw()); + + LogFlowThisFunc(("Leaving S_OK\n")); + return S_OK; +} + +/** + * @note Temporarily locks this object for writing. + */ +HRESULT Console::i_getGuestProperty(const Utf8Str &aName, Utf8Str *aValue, LONG64 *aTimestamp, Utf8Str *aFlags) +{ +#ifndef VBOX_WITH_GUEST_PROPS + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_PROPS */ + if (!RT_VALID_PTR(aValue)) + return E_POINTER; + if (aTimestamp != NULL && !RT_VALID_PTR(aTimestamp)) + return E_POINTER; + if (aFlags != NULL && !RT_VALID_PTR(aFlags)) + return E_POINTER; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* protect mpUVM (if not NULL) */ + SafeVMPtrQuiet ptrVM(this); + if (FAILED(ptrVM.rc())) + return ptrVM.rc(); + + /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by + * ptrVM, so there is no need to hold a lock of this */ + + HRESULT rc = E_UNEXPECTED; + try + { + VBOXHGCMSVCPARM parm[4]; + char szBuffer[GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN]; + + parm[0].type = VBOX_HGCM_SVC_PARM_PTR; + parm[0].u.pointer.addr = (void *)aName.c_str(); + parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */ + + parm[1].type = VBOX_HGCM_SVC_PARM_PTR; + parm[1].u.pointer.addr = szBuffer; + parm[1].u.pointer.size = sizeof(szBuffer); + + parm[2].type = VBOX_HGCM_SVC_PARM_64BIT; + parm[2].u.uint64 = 0; + + parm[3].type = VBOX_HGCM_SVC_PARM_32BIT; + parm[3].u.uint32 = 0; + + int vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_GET_PROP, + 4, &parm[0]); + /* The returned string should never be able to be greater than our buffer */ + AssertLogRel(vrc != VERR_BUFFER_OVERFLOW); + AssertLogRel(RT_FAILURE(vrc) || parm[2].type == VBOX_HGCM_SVC_PARM_64BIT); + if (RT_SUCCESS(vrc)) + { + *aValue = szBuffer; + + if (aTimestamp) + *aTimestamp = parm[2].u.uint64; + + if (aFlags) + *aFlags = &szBuffer[strlen(szBuffer) + 1]; + + rc = S_OK; + } + else if (vrc == VERR_NOT_FOUND) + { + *aValue = ""; + rc = S_OK; + } + else + rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc); + } + catch(std::bad_alloc & /*e*/) + { + rc = E_OUTOFMEMORY; + } + + return rc; +#endif /* VBOX_WITH_GUEST_PROPS */ +} + +/** + * @note Temporarily locks this object for writing. + */ +HRESULT Console::i_setGuestProperty(const Utf8Str &aName, const Utf8Str &aValue, const Utf8Str &aFlags) +{ +#ifndef VBOX_WITH_GUEST_PROPS + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_PROPS */ + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* protect mpUVM (if not NULL) */ + SafeVMPtrQuiet ptrVM(this); + if (FAILED(ptrVM.rc())) + return ptrVM.rc(); + + /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by + * ptrVM, so there is no need to hold a lock of this */ + + VBOXHGCMSVCPARM parm[3]; + + parm[0].type = VBOX_HGCM_SVC_PARM_PTR; + parm[0].u.pointer.addr = (void*)aName.c_str(); + parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */ + + parm[1].type = VBOX_HGCM_SVC_PARM_PTR; + parm[1].u.pointer.addr = (void *)aValue.c_str(); + parm[1].u.pointer.size = (uint32_t)aValue.length() + 1; /* The + 1 is the null terminator */ + + int vrc; + if (aFlags.isEmpty()) + { + vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP_VALUE, 2, &parm[0]); + } + else + { + parm[2].type = VBOX_HGCM_SVC_PARM_PTR; + parm[2].u.pointer.addr = (void*)aFlags.c_str(); + parm[2].u.pointer.size = (uint32_t)aFlags.length() + 1; /* The + 1 is the null terminator */ + + vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP, 3, &parm[0]); + } + + HRESULT hrc = S_OK; + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc); + return hrc; +#endif /* VBOX_WITH_GUEST_PROPS */ +} + +HRESULT Console::i_deleteGuestProperty(const Utf8Str &aName) +{ +#ifndef VBOX_WITH_GUEST_PROPS + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_PROPS */ + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* protect mpUVM (if not NULL) */ + SafeVMPtrQuiet ptrVM(this); + if (FAILED(ptrVM.rc())) + return ptrVM.rc(); + + /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by + * ptrVM, so there is no need to hold a lock of this */ + + VBOXHGCMSVCPARM parm[1]; + parm[0].type = VBOX_HGCM_SVC_PARM_PTR; + parm[0].u.pointer.addr = (void*)aName.c_str(); + parm[0].u.pointer.size = (uint32_t)aName.length() + 1; /* The + 1 is the null terminator */ + + int vrc = m_pVMMDev->hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_DEL_PROP, 1, &parm[0]); + + HRESULT hrc = S_OK; + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc); + return hrc; +#endif /* VBOX_WITH_GUEST_PROPS */ +} + +/** + * @note Temporarily locks this object for writing. + */ +HRESULT Console::i_enumerateGuestProperties(const Utf8Str &aPatterns, + std::vector<Utf8Str> &aNames, + std::vector<Utf8Str> &aValues, + std::vector<LONG64> &aTimestamps, + std::vector<Utf8Str> &aFlags) +{ +#ifndef VBOX_WITH_GUEST_PROPS + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_PROPS */ + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* protect mpUVM (if not NULL) */ + AutoVMCallerWeak autoVMCaller(this); + if (FAILED(autoVMCaller.rc())) + return autoVMCaller.rc(); + + /* Note: validity of mVMMDev which is bound to uninit() is guaranteed by + * autoVMCaller, so there is no need to hold a lock of this */ + + return i_doEnumerateGuestProperties(aPatterns, aNames, aValues, aTimestamps, aFlags); +#endif /* VBOX_WITH_GUEST_PROPS */ +} + + +/* + * Internal: helper function for connecting progress reporting + */ +static DECLCALLBACK(int) onlineMergeMediumProgress(void *pvUser, unsigned uPercentage) +{ + HRESULT rc = S_OK; + IProgress *pProgress = static_cast<IProgress *>(pvUser); + if (pProgress) + { + ComPtr<IInternalProgressControl> pProgressControl(pProgress); + AssertReturn(!!pProgressControl, VERR_INVALID_PARAMETER); + rc = pProgressControl->SetCurrentOperationProgress(uPercentage); + } + return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +/** + * @note Temporarily locks this object for writing. bird: And/or reading? + */ +HRESULT Console::i_onlineMergeMedium(IMediumAttachment *aMediumAttachment, + ULONG aSourceIdx, ULONG aTargetIdx, + IProgress *aProgress) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + HRESULT rc = S_OK; + int vrc = VINF_SUCCESS; + + /* Get the VM - must be done before the read-locking. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* We will need to release the lock before doing the actual merge */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* paranoia - we don't want merges to happen while teleporting etc. */ + switch (mMachineState) + { + case MachineState_DeletingSnapshotOnline: + case MachineState_DeletingSnapshotPaused: + break; + + default: + return i_setInvalidMachineStateError(); + } + + /** @todo AssertComRC -> AssertComRCReturn! Could potentially end up + * using uninitialized variables here. */ + BOOL fBuiltinIOCache; + rc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache); + AssertComRC(rc); + SafeIfaceArray<IStorageController> ctrls; + rc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); + AssertComRC(rc); + LONG lDev; + rc = aMediumAttachment->COMGETTER(Device)(&lDev); + AssertComRC(rc); + LONG lPort; + rc = aMediumAttachment->COMGETTER(Port)(&lPort); + AssertComRC(rc); + IMedium *pMedium; + rc = aMediumAttachment->COMGETTER(Medium)(&pMedium); + AssertComRC(rc); + Bstr mediumLocation; + if (pMedium) + { + rc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam()); + AssertComRC(rc); + } + + Bstr attCtrlName; + rc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam()); + AssertComRC(rc); + ComPtr<IStorageController> pStorageController; + for (size_t i = 0; i < ctrls.size(); ++i) + { + Bstr ctrlName; + rc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam()); + AssertComRC(rc); + if (attCtrlName == ctrlName) + { + pStorageController = ctrls[i]; + break; + } + } + if (pStorageController.isNull()) + return setError(E_FAIL, + tr("Could not find storage controller '%ls'"), + attCtrlName.raw()); + + StorageControllerType_T enmCtrlType; + rc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType); + AssertComRC(rc); + const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType); + + StorageBus_T enmBus; + rc = pStorageController->COMGETTER(Bus)(&enmBus); + AssertComRC(rc); + ULONG uInstance; + rc = pStorageController->COMGETTER(Instance)(&uInstance); + AssertComRC(rc); + BOOL fUseHostIOCache; + rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache); + AssertComRC(rc); + + unsigned uLUN; + rc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); + AssertComRCReturnRC(rc); + + Assert(mMachineState == MachineState_DeletingSnapshotOnline); + + /* Pause the VM, as it might have pending IO on this drive */ + bool fResume = false; + rc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume); + if (FAILED(rc)) + return rc; + + bool fInsertDiskIntegrityDrv = false; + Bstr strDiskIntegrityFlag; + rc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(), + strDiskIntegrityFlag.asOutParam()); + if ( rc == S_OK + && strDiskIntegrityFlag == "1") + fInsertDiskIntegrityDrv = true; + + alock.release(); + vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 15, + this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus, + fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, true /* fSetupMerge */, + aSourceIdx, aTargetIdx, aMediumAttachment, mMachineState, &rc); + /* error handling is after resuming the VM */ + + if (fResume) + i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable()); + + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc); + if (FAILED(rc)) + return rc; + + PPDMIBASE pIBase = NULL; + PPDMIMEDIA pIMedium = NULL; + vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, uInstance, uLUN, "VD", &pIBase); + if (RT_SUCCESS(vrc)) + { + if (pIBase) + { + pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (!pIMedium) + return setError(E_FAIL, tr("could not query medium interface of controller")); + } + else + return setError(E_FAIL, tr("could not query base interface of controller")); + } + + /* Finally trigger the merge. */ + vrc = pIMedium->pfnMerge(pIMedium, onlineMergeMediumProgress, aProgress); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed to perform an online medium merge (%Rrc)"), vrc); + + alock.acquire(); + /* Pause the VM, as it might have pending IO on this drive */ + rc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume); + if (FAILED(rc)) + return rc; + alock.release(); + + /* Update medium chain and state now, so that the VM can continue. */ + rc = mControl->FinishOnlineMergeMedium(); + + vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 15, + this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus, + fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, false /* fSetupMerge */, + 0 /* uMergeSource */, 0 /* uMergeTarget */, aMediumAttachment, mMachineState, &rc); + /* error handling is after resuming the VM */ + + if (fResume) + i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable()); + + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc); + if (FAILED(rc)) + return rc; + + return rc; +} + +HRESULT Console::i_reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments) +{ + HRESULT rc = S_OK; + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + for (size_t i = 0; i < aAttachments.size(); ++i) + { + ComPtr<IStorageController> pStorageController; + Bstr controllerName; + ULONG lInstance; + StorageControllerType_T enmController; + StorageBus_T enmBus; + BOOL fUseHostIOCache; + + /* + * We could pass the objects, but then EMT would have to do lots of + * IPC (to VBoxSVC) which takes a significant amount of time. + * Better query needed values here and pass them. + */ + rc = aAttachments[i]->COMGETTER(Controller)(controllerName.asOutParam()); + if (FAILED(rc)) + throw rc; + + rc = mMachine->GetStorageControllerByName(controllerName.raw(), pStorageController.asOutParam()); + if (FAILED(rc)) + throw rc; + + rc = pStorageController->COMGETTER(ControllerType)(&enmController); + if (FAILED(rc)) + throw rc; + rc = pStorageController->COMGETTER(Instance)(&lInstance); + if (FAILED(rc)) + throw rc; + rc = pStorageController->COMGETTER(Bus)(&enmBus); + if (FAILED(rc)) + throw rc; + rc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache); + if (FAILED(rc)) + throw rc; + + const char *pcszDevice = i_storageControllerTypeToStr(enmController); + + BOOL fBuiltinIOCache; + rc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache); + if (FAILED(rc)) + throw rc; + + bool fInsertDiskIntegrityDrv = false; + Bstr strDiskIntegrityFlag; + rc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(), + strDiskIntegrityFlag.asOutParam()); + if ( rc == S_OK + && strDiskIntegrityFlag == "1") + fInsertDiskIntegrityDrv = true; + + alock.release(); + + IMediumAttachment *pAttachment = aAttachments[i]; + int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 15, + this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, lInstance, enmBus, + fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, + false /* fSetupMerge */, 0 /* uMergeSource */, 0 /* uMergeTarget */, + pAttachment, mMachineState, &rc); + if (RT_FAILURE(vrc)) + throw setErrorBoth(E_FAIL, vrc, "%Rrc", vrc); + if (FAILED(rc)) + throw rc; + + alock.acquire(); + } + + return rc; +} + +HRESULT Console::i_onVMProcessPriorityChange(VMProcPriority_T priority) +{ + HRESULT rc = S_OK; + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + RTPROCPRIORITY enmProcPriority = RTPROCPRIORITY_DEFAULT; + switch (priority) + { + case VMProcPriority_Default: + enmProcPriority = RTPROCPRIORITY_DEFAULT; + break; + case VMProcPriority_Flat: + enmProcPriority = RTPROCPRIORITY_FLAT; + break; + case VMProcPriority_Low: + enmProcPriority = RTPROCPRIORITY_LOW; + break; + case VMProcPriority_Normal: + enmProcPriority = RTPROCPRIORITY_NORMAL; + break; + case VMProcPriority_High: + enmProcPriority = RTPROCPRIORITY_HIGH; + break; + default: + return setError(E_INVALIDARG, tr("Unsupported priority type (%d)"), priority); + } + int vrc = RTProcSetPriority(enmProcPriority); + if (RT_FAILURE(vrc)) + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, + tr("Could not set the priority of the process (%Rrc). Try to set it when VM is not started."), vrc); + + return rc; +} + +/** + * Load an HGCM service. + * + * Main purpose of this method is to allow extension packs to load HGCM + * service modules, which they can't, because the HGCM functionality lives + * in module VBoxC (and ConsoleImpl.cpp is part of it and thus can call it). + * Extension modules must not link directly against VBoxC, (XP)COM is + * handling this. + */ +int Console::i_hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName) +{ + /* Everyone seems to delegate all HGCM calls to VMMDev, so stick to this + * convention. Adds one level of indirection for no obvious reason. */ + AssertPtrReturn(m_pVMMDev, VERR_INVALID_STATE); + return m_pVMMDev->hgcmLoadService(pszServiceLibrary, pszServiceName); +} + +/** + * Merely passes the call to Guest::enableVMMStatistics(). + */ +void Console::i_enableVMMStatistics(BOOL aEnable) +{ + if (mGuest) + mGuest->i_enableVMMStatistics(aEnable); +} + +/** + * Worker for Console::Pause and internal entry point for pausing a VM for + * a specific reason. + */ +HRESULT Console::i_pause(Reason_T aReason) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + switch (mMachineState) + { + case MachineState_Running: + case MachineState_Teleporting: + case MachineState_LiveSnapshotting: + break; + + case MachineState_Paused: + case MachineState_TeleportingPausedVM: + case MachineState_OnlineSnapshotting: + /* Remove any keys which are supposed to be removed on a suspend. */ + if ( aReason == Reason_HostSuspend + || aReason == Reason_HostBatteryLow) + { + i_removeSecretKeysOnSuspend(); + return S_OK; + } + return setError(VBOX_E_INVALID_VM_STATE, tr("Already paused")); + + default: + return i_setInvalidMachineStateError(); + } + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + LogFlowThisFunc(("Sending PAUSE request...\n")); + if (aReason != Reason_Unspecified) + LogRel(("Pausing VM execution, reason '%s'\n", ::stringifyReason(aReason))); + + /** @todo r=klaus make use of aReason */ + VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER; + if (aReason == Reason_HostSuspend) + enmReason = VMSUSPENDREASON_HOST_SUSPEND; + else if (aReason == Reason_HostBatteryLow) + enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW; + + int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason); + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc); + else if ( aReason == Reason_HostSuspend + || aReason == Reason_HostBatteryLow) + { + alock.acquire(); + i_removeSecretKeysOnSuspend(); + } + } + + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +/** + * Worker for Console::Resume and internal entry point for resuming a VM for + * a specific reason. + */ +HRESULT Console::i_resume(Reason_T aReason, AutoWriteLock &alock) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + LogFlowThisFunc(("Sending RESUME request...\n")); + if (aReason != Reason_Unspecified) + LogRel(("Resuming VM execution, reason '%s'\n", ::stringifyReason(aReason))); + + int vrc; + VMSTATE const enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM()); + if (enmVMState == VMSTATE_CREATED) + { +#ifdef VBOX_WITH_EXTPACK + vrc = mptrExtPackManager->i_callAllVmPowerOnHooks(this, ptrVM.vtable()->pfnVMR3GetVM(ptrVM.rawUVM()), ptrVM.vtable()); +#else + vrc = VINF_SUCCESS; +#endif + if (RT_SUCCESS(vrc)) + vrc = ptrVM.vtable()->pfnVMR3PowerOn(ptrVM.rawUVM()); /* (PowerUpPaused) */ + } + else + { + VMRESUMEREASON enmReason; + if (aReason == Reason_HostResume) + { + /* + * Host resume may be called multiple times successively. We don't want to VMR3Resume->vmR3Resume->vmR3TrySetState() + * to assert on us, hence check for the VM state here and bail if it's not in the 'suspended' state. + * See @bugref{3495}. + * + * Also, don't resume the VM through a host-resume unless it was suspended due to a host-suspend. + */ + if (enmVMState != VMSTATE_SUSPENDED) + { + LogRel(("Ignoring VM resume request, VM is currently not suspended (%d)\n", enmVMState)); + return S_OK; + } + VMSUSPENDREASON const enmSuspendReason = ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM()); + if (enmSuspendReason != VMSUSPENDREASON_HOST_SUSPEND) + { + LogRel(("Ignoring VM resume request, VM was not suspended due to host-suspend (%d)\n", enmSuspendReason)); + return S_OK; + } + + enmReason = VMRESUMEREASON_HOST_RESUME; + } + else + { + /* + * Any other reason to resume the VM throws an error when the VM was suspended due to a host suspend. + * See @bugref{7836}. + */ + if ( enmVMState == VMSTATE_SUSPENDED + && ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM()) == VMSUSPENDREASON_HOST_SUSPEND) + return setError(VBOX_E_INVALID_VM_STATE, tr("VM is paused due to host power management")); + + enmReason = aReason == Reason_Snapshot ? VMRESUMEREASON_STATE_SAVED : VMRESUMEREASON_USER; + } + + // for snapshots: no state change callback, VBoxSVC does everything + if (aReason == Reason_Snapshot) + mVMStateChangeCallbackDisabled = true; + + vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), enmReason); + + if (aReason == Reason_Snapshot) + mVMStateChangeCallbackDisabled = false; + } + + HRESULT hrc = RT_SUCCESS(vrc) ? S_OK + : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc); + + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + return hrc; +} + +/** + * Internal entry point for saving state of a VM for a specific reason. This + * method is completely synchronous. + * + * The machine state is already set appropriately. It is only changed when + * saving state actually paused the VM (happens with live snapshots and + * teleportation), and in this case reflects the now paused variant. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_saveState(Reason_T aReason, const ComPtr<IProgress> &aProgress, const ComPtr<ISnapshot> &aSnapshot, + const Utf8Str &aStateFilePath, bool aPauseVM, bool &aLeftPaused) +{ + LogFlowThisFuncEnter(); + aLeftPaused = false; + + AssertReturn(!aProgress.isNull(), E_INVALIDARG); + AssertReturn(!aStateFilePath.isEmpty(), E_INVALIDARG); + Assert(aSnapshot.isNull() || aReason == Reason_Snapshot); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + if ( mMachineState != MachineState_Saving + && mMachineState != MachineState_LiveSnapshotting + && mMachineState != MachineState_OnlineSnapshotting + && mMachineState != MachineState_Teleporting + && mMachineState != MachineState_TeleportingPausedVM) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot save the execution state as the machine is not running or paused (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + bool fContinueAfterwards = mMachineState != MachineState_Saving; + + Bstr strDisableSaveState; + mMachine->GetExtraData(Bstr("VBoxInternal2/DisableSaveState").raw(), strDisableSaveState.asOutParam()); + if (strDisableSaveState == "1") + return setError(VBOX_E_VM_ERROR, + tr("Saving the execution state is disabled for this VM")); + + if (aReason != Reason_Unspecified) + LogRel(("Saving state of VM, reason '%s'\n", ::stringifyReason(aReason))); + + /* ensure the directory for the saved state file exists */ + { + Utf8Str dir = aStateFilePath; + dir.stripFilename(); + if (!RTDirExists(dir.c_str())) + { + int vrc = RTDirCreateFullPath(dir.c_str(), 0700); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Could not create a directory '%s' to save the state to (%Rrc)"), + dir.c_str(), vrc); + } + } + + /* Get the VM handle early, we need it in several places. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + bool fPaused = false; + if (aPauseVM) + { + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER; + if (aReason == Reason_HostSuspend) + enmReason = VMSUSPENDREASON_HOST_SUSPEND; + else if (aReason == Reason_HostBatteryLow) + enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW; + int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason); + alock.acquire(); + + if (RT_SUCCESS(vrc)) + fPaused = true; + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc); + } + + Bstr bstrStateKeyId; + Bstr bstrStateKeyStore; +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + if (SUCCEEDED(hrc)) + { + hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam()); + if (SUCCEEDED(hrc)) + { + hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam()); + if (FAILED(hrc)) + hrc = setError(hrc, tr("Could not get key store for state file(%Rhrc (0x%08X))"), hrc, hrc); + } + else + hrc = setError(hrc, tr("Could not get key id for state file(%Rhrc (0x%08X))"), hrc, hrc); + } +#endif + + if (SUCCEEDED(hrc)) + { + LogFlowFunc(("Saving the state to '%s'...\n", aStateFilePath.c_str())); + + mpVmm2UserMethods->pISnapshot = aSnapshot; + mptrCancelableProgress = aProgress; + + SsmStream ssmStream(this, ptrVM.vtable(), m_pKeyStore, bstrStateKeyId, bstrStateKeyStore); + int vrc = ssmStream.create(aStateFilePath.c_str()); + if (RT_SUCCESS(vrc)) + { + PCSSMSTRMOPS pStreamOps = NULL; + void *pvStreamOpsUser = NULL; + vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser); + if (RT_SUCCESS(vrc)) + { + alock.release(); + + vrc = ptrVM.vtable()->pfnVMR3Save(ptrVM.rawUVM(), + NULL /*pszFilename*/, + pStreamOps, + pvStreamOpsUser, + fContinueAfterwards, + Console::i_stateProgressCallback, + static_cast<IProgress *>(aProgress), + &aLeftPaused); + + alock.acquire(); + } + + ssmStream.close(); + if (RT_FAILURE(vrc)) + { + int vrc2 = RTFileDelete(aStateFilePath.c_str()); + AssertRC(vrc2); + } + } + + mpVmm2UserMethods->pISnapshot = NULL; + mptrCancelableProgress.setNull(); + if (RT_SUCCESS(vrc)) + { + Assert(fContinueAfterwards || !aLeftPaused); + + if (!fContinueAfterwards) + { + /* + * The machine has been successfully saved, so power it down + * (vmstateChangeCallback() will set state to Saved on success). + * Note: we release the VM caller, otherwise it will deadlock. + */ + ptrVM.release(); + alock.release(); + autoCaller.release(); + + HRESULT rc = i_powerDown(); + AssertComRC(rc); + + autoCaller.add(); + alock.acquire(); + } + else if (fPaused) + aLeftPaused = true; + } + else + { + if (fPaused) + { + alock.release(); + ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_STATE_RESTORED); + alock.acquire(); + } + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to save the machine state to '%s' (%Rrc)"), + aStateFilePath.c_str(), vrc); + } + } + } + + LogFlowFuncLeave(); + return S_OK; +} + +/** + * Internal entry point for cancelling a VM save state. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_cancelSaveState() +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Get the VM handle. */ + SafeVMPtr ptrVM(this); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + ptrVM.vtable()->pfnSSMR3Cancel(ptrVM.rawUVM()); + + LogFlowFuncLeave(); + return hrc; +} + +#ifdef VBOX_WITH_AUDIO_RECORDING +/** + * Sends audio (frame) data to the recording routines. + * + * @returns HRESULT + * @param pvData Audio data to send. + * @param cbData Size (in bytes) of audio data to send. + * @param uTimestampMs Timestamp (in ms) of audio data. + */ +HRESULT Console::i_recordingSendAudio(const void *pvData, size_t cbData, uint64_t uTimestampMs) +{ + if ( mRecording.mCtx.IsStarted() + && mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio)) + return mRecording.mCtx.SendAudioFrame(pvData, cbData, uTimestampMs); + + return S_OK; +} +#endif /* VBOX_WITH_AUDIO_RECORDING */ + +#ifdef VBOX_WITH_RECORDING + +int Console::i_recordingGetSettings(settings::RecordingSettings &recording) +{ + Assert(mMachine.isNotNull()); + + recording.applyDefaults(); + + ComPtr<IRecordingSettings> pRecordSettings; + HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam()); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + + BOOL fTemp; + hrc = pRecordSettings->COMGETTER(Enabled)(&fTemp); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + recording.common.fEnabled = RT_BOOL(fTemp); + + SafeIfaceArray<IRecordingScreenSettings> paRecScreens; + hrc = pRecordSettings->COMGETTER(Screens)(ComSafeArrayAsOutParam(paRecScreens)); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + + for (unsigned long i = 0; i < (unsigned long)paRecScreens.size(); ++i) + { + settings::RecordingScreenSettings recScreenSettings; + ComPtr<IRecordingScreenSettings> pRecScreenSettings = paRecScreens[i]; + + hrc = pRecScreenSettings->COMGETTER(Enabled)(&fTemp); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + recScreenSettings.fEnabled = RT_BOOL(fTemp); + com::SafeArray<RecordingFeature_T> vecFeatures; + hrc = pRecScreenSettings->COMGETTER(Features)(ComSafeArrayAsOutParam(vecFeatures)); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + /* Make sure to clear map first, as we want to (re-)set enabled features. */ + recScreenSettings.featureMap.clear(); + for (size_t f = 0; f < vecFeatures.size(); ++f) + { + if (vecFeatures[f] == RecordingFeature_Audio) + recScreenSettings.featureMap[RecordingFeature_Audio] = true; + else if (vecFeatures[f] == RecordingFeature_Video) + recScreenSettings.featureMap[RecordingFeature_Video] = true; + } + hrc = pRecScreenSettings->COMGETTER(MaxTime)((ULONG *)&recScreenSettings.ulMaxTimeS); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(MaxFileSize)((ULONG *)&recScreenSettings.File.ulMaxSizeMB); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + Bstr bstrTemp; + hrc = pRecScreenSettings->COMGETTER(Filename)(bstrTemp.asOutParam()); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + recScreenSettings.File.strName = bstrTemp; + hrc = pRecScreenSettings->COMGETTER(Options)(bstrTemp.asOutParam()); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + recScreenSettings.strOptions = bstrTemp; + hrc = pRecScreenSettings->COMGETTER(AudioCodec)(&recScreenSettings.Audio.enmCodec); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(AudioDeadline)(&recScreenSettings.Audio.enmDeadline); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(AudioRateControlMode)(&recScreenSettings.Audio.enmRateCtlMode); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(AudioHz)((ULONG *)&recScreenSettings.Audio.uHz); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(AudioBits)((ULONG *)&recScreenSettings.Audio.cBits); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(AudioChannels)((ULONG *)&recScreenSettings.Audio.cChannels); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoCodec)(&recScreenSettings.Video.enmCodec); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoWidth)((ULONG *)&recScreenSettings.Video.ulWidth); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoHeight)((ULONG *)&recScreenSettings.Video.ulHeight); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoDeadline)(&recScreenSettings.Video.enmDeadline); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoRateControlMode)(&recScreenSettings.Video.enmRateCtlMode); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoScalingMode)(&recScreenSettings.Video.enmScalingMode); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoRate)((ULONG *)&recScreenSettings.Video.ulRate); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecScreenSettings->COMGETTER(VideoFPS)((ULONG *)&recScreenSettings.Video.ulFPS); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + + recording.mapScreens[i] = recScreenSettings; + } + + Assert(recording.mapScreens.size() == paRecScreens.size()); + + return VINF_SUCCESS; +} + +/** + * Creates the recording context. + * + * @returns VBox status code. + */ +int Console::i_recordingCreate(void) +{ + settings::RecordingSettings recordingSettings; + int vrc = i_recordingGetSettings(recordingSettings); + if (RT_SUCCESS(vrc)) + vrc = mRecording.mCtx.Create(this, recordingSettings); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Destroys the recording context. + */ +void Console::i_recordingDestroy(void) +{ + mRecording.mCtx.Destroy(); +} + +/** + * Starts recording. Does nothing if recording is already active. + * + * @returns VBox status code. + */ +int Console::i_recordingStart(util::AutoWriteLock *pAutoLock /* = NULL */) +{ + RT_NOREF(pAutoLock); + + if (mRecording.mCtx.IsStarted()) + return VINF_SUCCESS; + + LogRel(("Recording: Starting ...\n")); + + int vrc = mRecording.mCtx.Start(); + if (RT_SUCCESS(vrc)) + { + for (unsigned uScreen = 0; uScreen < mRecording.mCtx.GetStreamCount(); uScreen++) + mDisplay->i_recordingScreenChanged(uScreen); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Stops recording. Does nothing if recording is not active. + */ +int Console::i_recordingStop(util::AutoWriteLock *pAutoLock /* = NULL */) +{ + if (!mRecording.mCtx.IsStarted()) + return VINF_SUCCESS; + + LogRel(("Recording: Stopping ...\n")); + + int vrc = mRecording.mCtx.Stop(); + if (RT_SUCCESS(vrc)) + { + const size_t cStreams = mRecording.mCtx.GetStreamCount(); + for (unsigned uScreen = 0; uScreen < cStreams; ++uScreen) + mDisplay->i_recordingScreenChanged(uScreen); + + if (pAutoLock) + pAutoLock->release(); + + ComPtr<IRecordingSettings> pRecordSettings; + HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam()); + ComAssertComRC(hrc); + hrc = pRecordSettings->COMSETTER(Enabled)(FALSE); + ComAssertComRC(hrc); + + if (pAutoLock) + pAutoLock->acquire(); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +#endif /* VBOX_WITH_RECORDING */ + +/** + * Gets called by Session::UpdateMachineState() + * (IInternalSessionControl::updateMachineState()). + * + * Must be called only in certain cases (see the implementation). + * + * @note Locks this object for writing. + */ +HRESULT Console::i_updateMachineState(MachineState_T aMachineState) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturn( mMachineState == MachineState_Saving + || mMachineState == MachineState_OnlineSnapshotting + || mMachineState == MachineState_LiveSnapshotting + || mMachineState == MachineState_DeletingSnapshotOnline + || mMachineState == MachineState_DeletingSnapshotPaused + || aMachineState == MachineState_Saving + || aMachineState == MachineState_OnlineSnapshotting + || aMachineState == MachineState_LiveSnapshotting + || aMachineState == MachineState_DeletingSnapshotOnline + || aMachineState == MachineState_DeletingSnapshotPaused + , E_FAIL); + + return i_setMachineStateLocally(aMachineState); +} + +/** + * Gets called by Session::COMGETTER(NominalState)() + * (IInternalSessionControl::getNominalState()). + * + * @note Locks this object for reading. + */ +HRESULT Console::i_getNominalState(MachineState_T &aNominalState) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + /* Get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + MachineState_T enmMachineState = MachineState_Null; + VMSTATE enmVMState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM()); + switch (enmVMState) + { + case VMSTATE_CREATING: + case VMSTATE_CREATED: + case VMSTATE_POWERING_ON: + enmMachineState = MachineState_Starting; + break; + case VMSTATE_LOADING: + enmMachineState = MachineState_Restoring; + break; + case VMSTATE_RESUMING: + case VMSTATE_SUSPENDING: + case VMSTATE_SUSPENDING_LS: + case VMSTATE_SUSPENDING_EXT_LS: + case VMSTATE_SUSPENDED: + case VMSTATE_SUSPENDED_LS: + case VMSTATE_SUSPENDED_EXT_LS: + enmMachineState = MachineState_Paused; + break; + case VMSTATE_RUNNING: + case VMSTATE_RUNNING_LS: + case VMSTATE_RESETTING: + case VMSTATE_RESETTING_LS: + case VMSTATE_SOFT_RESETTING: + case VMSTATE_SOFT_RESETTING_LS: + case VMSTATE_DEBUGGING: + case VMSTATE_DEBUGGING_LS: + enmMachineState = MachineState_Running; + break; + case VMSTATE_SAVING: + enmMachineState = MachineState_Saving; + break; + case VMSTATE_POWERING_OFF: + case VMSTATE_POWERING_OFF_LS: + case VMSTATE_DESTROYING: + enmMachineState = MachineState_Stopping; + break; + case VMSTATE_OFF: + case VMSTATE_OFF_LS: + case VMSTATE_FATAL_ERROR: + case VMSTATE_FATAL_ERROR_LS: + case VMSTATE_LOAD_FAILURE: + case VMSTATE_TERMINATED: + enmMachineState = MachineState_PoweredOff; + break; + case VMSTATE_GURU_MEDITATION: + case VMSTATE_GURU_MEDITATION_LS: + enmMachineState = MachineState_Stuck; + break; + default: + AssertMsgFailed(("%s\n", ptrVM.vtable()->pfnVMR3GetStateName(enmVMState))); + enmMachineState = MachineState_PoweredOff; + } + aNominalState = enmMachineState; + + LogFlowFuncLeave(); + return S_OK; +} + +void Console::i_onMousePointerShapeChange(bool fVisible, bool fAlpha, + uint32_t xHot, uint32_t yHot, + uint32_t width, uint32_t height, + const uint8_t *pu8Shape, + uint32_t cbShape) +{ +#if 0 + LogFlowThisFuncEnter(); + LogFlowThisFunc(("fVisible=%d, fAlpha=%d, xHot = %d, yHot = %d, width=%d, height=%d, shape=%p\n", + fVisible, fAlpha, xHot, yHot, width, height, pShape)); +#endif + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + if (!mMouse.isNull()) + mMouse->updateMousePointerShape(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape); + + com::SafeArray<BYTE> shape(cbShape); + if (pu8Shape) + memcpy(shape.raw(), pu8Shape, cbShape); + ::FireMousePointerShapeChangedEvent(mEventSource, fVisible, fAlpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape)); + +#if 0 + LogFlowThisFuncLeave(); +#endif +} + +void Console::i_onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative, + BOOL supportsTouchScreen, BOOL supportsTouchPad, BOOL needsHostCursor) +{ + LogFlowThisFunc(("supportsAbsolute=%d supportsRelative=%d supportsTouchScreen=%d supportsTouchPad=%d needsHostCursor=%d\n", + supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor)); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireMouseCapabilityChangedEvent(mEventSource, supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor); +} + +void Console::i_onStateChange(MachineState_T machineState) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + ::FireStateChangedEvent(mEventSource, machineState); +} + +void Console::i_onAdditionsStateChange() +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireAdditionsStateChangedEvent(mEventSource); +} + +/** + * @remarks This notification only is for reporting an incompatible + * Guest Additions interface, *not* the Guest Additions version! + * + * The user will be notified inside the guest if new Guest + * Additions are available (via VBoxTray/VBoxClient). + */ +void Console::i_onAdditionsOutdated() +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + /** @todo implement this */ +} + +void Console::i_onKeyboardLedsChange(bool fNumLock, bool fCapsLock, bool fScrollLock) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireKeyboardLedsChangedEvent(mEventSource, fNumLock, fCapsLock, fScrollLock); +} + +void Console::i_onUSBDeviceStateChange(IUSBDevice *aDevice, bool aAttached, + IVirtualBoxErrorInfo *aError) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireUSBDeviceStateChangedEvent(mEventSource, aDevice, aAttached, aError); +} + +void Console::i_onRuntimeError(BOOL aFatal, IN_BSTR aErrorID, IN_BSTR aMessage) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + ::FireRuntimeErrorEvent(mEventSource, aFatal, aErrorID, aMessage); +} + +HRESULT Console::i_onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId) +{ + AssertReturn(aCanShow, E_POINTER); + AssertReturn(aWinId, E_POINTER); + + *aCanShow = FALSE; + *aWinId = 0; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + ComPtr<IEvent> ptrEvent; + if (aCheck) + { + *aCanShow = TRUE; + HRESULT hrc = ::CreateCanShowWindowEvent(ptrEvent.asOutParam(), mEventSource); + if (SUCCEEDED(hrc)) + { + VBoxEventDesc EvtDesc(ptrEvent, mEventSource); + BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */ + //Assert(fDelivered); + if (fDelivered) + { + // bit clumsy + ComPtr<ICanShowWindowEvent> ptrCanShowEvent = ptrEvent; + if (ptrCanShowEvent) + { + BOOL fVetoed = FALSE; + BOOL fApproved = FALSE; + ptrCanShowEvent->IsVetoed(&fVetoed); + ptrCanShowEvent->IsApproved(&fApproved); + *aCanShow = fApproved || !fVetoed; + } + else + AssertFailed(); + } + } + } + else + { + HRESULT hrc = ::CreateShowWindowEvent(ptrEvent.asOutParam(), mEventSource, 0); + if (SUCCEEDED(hrc)) + { + VBoxEventDesc EvtDesc(ptrEvent, mEventSource); + BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */ + //Assert(fDelivered); + if (fDelivered) + { + ComPtr<IShowWindowEvent> ptrShowEvent = ptrEvent; + if (ptrShowEvent) + { + LONG64 idWindow = 0; + ptrShowEvent->COMGETTER(WinId)(&idWindow); + if (idWindow != 0 && *aWinId == 0) + *aWinId = idWindow; + } + else + AssertFailed(); + } + } + } + + return S_OK; +} + +// private methods +//////////////////////////////////////////////////////////////////////////////// + +/** + * Loads the VMM if needed. + * + * @returns COM status. + * @remarks Caller must write lock the console object. + */ +HRESULT Console::i_loadVMM(void) RT_NOEXCEPT +{ + if ( mhModVMM == NIL_RTLDRMOD + || mpVMM == NULL) + { + Assert(!mpVMM); + + HRESULT hrc; + RTERRINFOSTATIC ErrInfo; + RTLDRMOD hModVMM = NIL_RTLDRMOD; + int vrc = SUPR3HardenedLdrLoadAppPriv("VBoxVMM", &hModVMM, RTLDRLOAD_FLAGS_LOCAL, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(vrc)) + { + PFNVMMGETVTABLE pfnGetVTable = NULL; + vrc = RTLdrGetSymbol(hModVMM, VMMR3VTABLE_GETTER_NAME, (void **)&pfnGetVTable); + if (pfnGetVTable) + { + PCVMMR3VTABLE pVMM = pfnGetVTable(); + if (pVMM) + { + if (VMMR3VTABLE_IS_COMPATIBLE(pVMM->uMagicVersion)) + { + if (pVMM->uMagicVersion == pVMM->uMagicVersionEnd) + { + mhModVMM = hModVMM; + mpVMM = pVMM; + LogFunc(("mhLdrVMM=%p phVMM=%p uMagicVersion=%#RX64\n", hModVMM, pVMM, pVMM->uMagicVersion)); + return S_OK; + } + + hrc = setErrorVrc(vrc, "Bogus VMM vtable: uMagicVersion=%#RX64 uMagicVersionEnd=%#RX64", + pVMM->uMagicVersion, pVMM->uMagicVersionEnd); + } + else + hrc = setErrorVrc(vrc, "Incompatible of bogus VMM version magic: %#RX64", pVMM->uMagicVersion); + } + else + hrc = setErrorVrc(vrc, "pfnGetVTable return NULL!"); + } + else + hrc = setErrorVrc(vrc, "Failed to locate symbol '%s' in VBoxVMM: %Rrc", VMMR3VTABLE_GETTER_NAME, vrc); + RTLdrClose(hModVMM); + } + else + hrc = setErrorVrc(vrc, "Failed to load VBoxVMM: %#RTeic", &ErrInfo.Core); + return hrc; + } + + return S_OK; +} + +/** + * Increases the usage counter of the mpUVM pointer. + * + * Guarantees that VMR3Destroy() will not be called on it at least until + * releaseVMCaller() is called. + * + * If this method returns a failure, the caller is not allowed to use mpUVM and + * may return the failed result code to the upper level. This method sets the + * extended error info on failure if \a aQuiet is false. + * + * Setting \a aQuiet to true is useful for methods that don't want to return + * the failed result code to the caller when this method fails (e.g. need to + * silently check for the mpUVM availability). + * + * When mpUVM is NULL but \a aAllowNullVM is true, a corresponding error will be + * returned instead of asserting. Having it false is intended as a sanity check + * for methods that have checked mMachineState and expect mpUVM *NOT* to be + * NULL. + * + * @param aQuiet true to suppress setting error info + * @param aAllowNullVM true to accept mpUVM being NULL and return a failure + * (otherwise this method will assert if mpUVM is NULL) + * + * @note Locks this object for writing. + */ +HRESULT Console::i_addVMCaller(bool aQuiet /* = false */, + bool aAllowNullVM /* = false */) +{ + RT_NOREF(aAllowNullVM); + AutoCaller autoCaller(this); + /** @todo Fix race during console/VM reference destruction, refer @bugref{6318} + * comment 25. */ + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mVMDestroying) + { + /* powerDown() is waiting for all callers to finish */ + return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED, tr("The virtual machine is being powered down")); + } + + if (mpUVM == NULL) + { + Assert(aAllowNullVM == true); + + /* The machine is not powered up */ + return aQuiet ? E_ACCESSDENIED : setError(E_ACCESSDENIED, tr("The virtual machine is not powered up")); + } + + ++mVMCallers; + + return S_OK; +} + +/** + * Decreases the usage counter of the mpUVM pointer. + * + * Must always complete the addVMCaller() call after the mpUVM pointer is no + * more necessary. + * + * @note Locks this object for writing. + */ +void Console::i_releaseVMCaller() +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturnVoid(mpUVM != NULL); + + Assert(mVMCallers > 0); + --mVMCallers; + + if (mVMCallers == 0 && mVMDestroying) + { + /* inform powerDown() there are no more callers */ + RTSemEventSignal(mVMZeroCallersSem); + } +} + + +/** + * Helper for SafeVMPtrBase. + */ +HRESULT Console::i_safeVMPtrRetainer(PUVM *a_ppUVM, PCVMMR3VTABLE *a_ppVMM, bool a_Quiet) RT_NOEXCEPT +{ + *a_ppUVM = NULL; + *a_ppVMM = NULL; + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Repeat the checks done by addVMCaller. + */ + if (mVMDestroying) /* powerDown() is waiting for all callers to finish */ + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("The virtual machine is being powered down")); + PUVM const pUVM = mpUVM; + if (!pUVM) + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("The virtual machine is powered off")); + PCVMMR3VTABLE const pVMM = mpVMM; + if (!pVMM) + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("No VMM loaded!")); + + /* + * Retain a reference to the user mode VM handle and get the global handle. + */ + uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM); + if (cRefs == UINT32_MAX) + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("The virtual machine is powered off")); + + /* done */ + *a_ppUVM = pUVM; + *a_ppVMM = pVMM; + return S_OK; +} + +void Console::i_safeVMPtrReleaser(PUVM *a_ppUVM) +{ + PUVM const pUVM = *a_ppUVM; + *a_ppUVM = NULL; + if (pUVM) + { + PCVMMR3VTABLE const pVMM = mpVMM; + if (pVMM) + pVMM->pfnVMR3ReleaseUVM(pUVM); + } +} + + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedOpen(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename, uint32_t fFlags) +{ + RT_NOREF(pIf); + Console *pConsole = static_cast<Console *>(pvUser); + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + + int vrc = RTVfsFileOpenNormal(pszFilename, fFlags, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + PCVBOXCRYPTOIF pCryptoIf = NULL; + vrc = pConsole->i_retainCryptoIf(&pCryptoIf); + if (RT_SUCCESS(vrc)) + { + SecretKey *pKey = NULL; + + vrc = pConsole->m_pKeyStore->retainSecretKey(pConsole->m_strLogKeyId, &pKey); + if (RT_SUCCESS(vrc)) + { + const char *pszPassword = (const char *)pKey->getKeyBuffer(); + + vrc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pConsole->m_strLogKeyStore.c_str(), pszPassword, + &pConsole->m_hVfsFileLog); + pKey->release(); + } + + /* On success we keep the reference to keep the cryptographic module loaded. */ + if (RT_FAILURE(vrc)) + pConsole->i_releaseCryptoIf(pCryptoIf); + } + + /* Always do this because the encrypted log has retained a reference to the underlying file. */ + RTVfsFileRelease(hVfsFile); + if (RT_FAILURE(vrc)) + RTFileDelete(pszFilename); + } + + return vrc; +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedClose(PCRTLOGOUTPUTIF pIf, void *pvUser) +{ + RT_NOREF(pIf); + Console *pConsole = static_cast<Console *>(pvUser); + + RTVfsFileRelease(pConsole->m_hVfsFileLog); + pConsole->m_hVfsFileLog = NIL_RTVFSFILE; + return VINF_SUCCESS; +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedDelete(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename) +{ + RT_NOREF(pIf, pvUser); + return RTFileDelete(pszFilename); +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedRename(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilenameOld, + const char *pszFilenameNew, uint32_t fFlags) +{ + RT_NOREF(pIf, pvUser); + return RTFileRename(pszFilenameOld, pszFilenameNew, fFlags); +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedQuerySize(PCRTLOGOUTPUTIF pIf, void *pvUser, uint64_t *pcbSize) +{ + RT_NOREF(pIf); + Console *pConsole = static_cast<Console *>(pvUser); + + return RTVfsFileQuerySize(pConsole->m_hVfsFileLog, pcbSize); +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedWrite(PCRTLOGOUTPUTIF pIf, void *pvUser, const void *pvBuf, + size_t cbWrite, size_t *pcbWritten) +{ + RT_NOREF(pIf); + Console *pConsole = static_cast<Console *>(pvUser); + + return RTVfsFileWrite(pConsole->m_hVfsFileLog, pvBuf, cbWrite, pcbWritten); +} + + +/*static*/ +DECLCALLBACK(int) Console::i_logEncryptedFlush(PCRTLOGOUTPUTIF pIf, void *pvUser) +{ + RT_NOREF(pIf); + Console *pConsole = static_cast<Console *>(pvUser); + + return RTVfsFileFlush(pConsole->m_hVfsFileLog); +} +#endif + + +/** + * Initialize the release logging facility. In case something + * goes wrong, there will be no release logging. Maybe in the future + * we can add some logic to use different file names in this case. + * Note that the logic must be in sync with Machine::DeleteSettings(). + */ +HRESULT Console::i_consoleInitReleaseLog(const ComPtr<IMachine> aMachine) +{ + Bstr bstrLogFolder; + HRESULT hrc = aMachine->COMGETTER(LogFolder)(bstrLogFolder.asOutParam()); + if (FAILED(hrc)) + return hrc; + Utf8Str strLogDir = bstrLogFolder; + + /* make sure the Logs folder exists */ + Assert(strLogDir.length()); + if (!RTDirExists(strLogDir.c_str())) + RTDirCreateFullPath(strLogDir.c_str(), 0700); + + Utf8StrFmt logFile("%s%cVBox.log", strLogDir.c_str(), RTPATH_DELIMITER); + Utf8StrFmt pngFile("%s%cVBox.png", strLogDir.c_str(), RTPATH_DELIMITER); + + /* + * Age the old log files. + * Rename .(n-1) to .(n), .(n-2) to .(n-1), ..., and the last log file to .1 + * Overwrite target files in case they exist. + */ + ComPtr<IVirtualBox> pVirtualBox; + aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + ComPtr<ISystemProperties> pSystemProperties; + pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam()); + ULONG cHistoryFiles = 3; + pSystemProperties->COMGETTER(LogHistoryCount)(&cHistoryFiles); + if (cHistoryFiles) + { + for (int i = cHistoryFiles - 1; i >= 0; i--) + { + Utf8Str *files[] = { &logFile, &pngFile }; + Utf8Str oldName, newName; + + for (unsigned int j = 0; j < RT_ELEMENTS(files); ++j) + { + if (i > 0) + oldName.printf("%s.%d", files[j]->c_str(), i); + else + oldName = *files[j]; + newName.printf("%s.%d", files[j]->c_str(), i + 1); + + /* If the old file doesn't exist, delete the new file (if it + * exists) to provide correct rotation even if the sequence is + * broken */ + if (RTFileRename(oldName.c_str(), newName.c_str(), RTFILEMOVE_FLAGS_REPLACE) == VERR_FILE_NOT_FOUND) + RTFileDelete(newName.c_str()); + } + } + } + + Bstr bstrLogKeyId; + Bstr bstrLogKeyStore; + PCRTLOGOUTPUTIF pLogOutputIf = NULL; + void *pvLogOutputUser = NULL; + int vrc = VINF_SUCCESS; +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + hrc = aMachine->COMGETTER(LogKeyId)(bstrLogKeyId.asOutParam()); + if (SUCCEEDED(hrc)) + { + hrc = aMachine->COMGETTER(LogKeyStore)(bstrLogKeyStore.asOutParam()); + if ( SUCCEEDED(hrc) + && bstrLogKeyId.isNotEmpty() + && bstrLogKeyStore.isNotEmpty()) + { + m_LogOutputIf.pfnOpen = Console::i_logEncryptedOpen; + m_LogOutputIf.pfnClose = Console::i_logEncryptedClose; + m_LogOutputIf.pfnDelete = Console::i_logEncryptedDelete; + m_LogOutputIf.pfnRename = Console::i_logEncryptedRename; + m_LogOutputIf.pfnQuerySize = Console::i_logEncryptedQuerySize; + m_LogOutputIf.pfnWrite = Console::i_logEncryptedWrite; + m_LogOutputIf.pfnFlush = Console::i_logEncryptedFlush; + + m_strLogKeyId = Utf8Str(bstrLogKeyId); + m_strLogKeyStore = Utf8Str(bstrLogKeyStore); + + pLogOutputIf = &m_LogOutputIf; + pvLogOutputUser = this; + m_fEncryptedLog = true; + } + } + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set encryption for release log (%Rrc)"), vrc); + else +#endif + { + RTERRINFOSTATIC ErrInfo; + vrc = com::VBoxLogRelCreateEx("VM", logFile.c_str(), + RTLOGFLAGS_PREFIX_TIME_PROG | RTLOGFLAGS_RESTRICT_GROUPS, + "all all.restrict -default.restrict", + "VBOX_RELEASE_LOG", RTLOGDEST_FILE, + 32768 /* cMaxEntriesPerGroup */, + 0 /* cHistory */, 0 /* uHistoryFileTime */, + 0 /* uHistoryFileSize */, + pLogOutputIf, pvLogOutputUser, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to open release log (%s, %Rrc)"), ErrInfo.Core.pszMsg, vrc); + } + + /* If we've made any directory changes, flush the directory to increase + the likelihood that the log file will be usable after a system panic. + + Tip: Try 'export VBOX_RELEASE_LOG_FLAGS=flush' if the last bits of the log + is missing. Just don't have too high hopes for this to help. */ + if (SUCCEEDED(hrc) || cHistoryFiles) + RTDirFlush(strLogDir.c_str()); + + return hrc; +} + +/** + * Common worker for PowerUp and PowerUpPaused. + * + * @returns COM status code. + * + * @param aProgress Where to return the progress object. + * @param aPaused true if PowerUpPaused called. + */ +HRESULT Console::i_powerUp(IProgress **aProgress, bool aPaused) +{ + LogFlowThisFuncEnter(); + + CheckComArgOutPointerValid(aProgress); + + AutoCaller autoCaller(this); + HRESULT rc = autoCaller.rc(); + if (FAILED(rc)) return rc; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + + if (Global::IsOnlineOrTransient(mMachineState)) + return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is already running or busy (machine state: %s)"), + Global::stringifyMachineState(mMachineState)); + + + /* Set up release logging as early as possible after the check if + * there is already a running VM which we shouldn't disturb. */ + rc = i_consoleInitReleaseLog(mMachine); + if (FAILED(rc)) + return rc; + +#ifdef VBOX_OPENSSL_FIPS + LogRel(("crypto: FIPS mode %s\n", FIPS_mode() ? "enabled" : "FAILED")); +#endif + + /* test and clear the TeleporterEnabled property */ + BOOL fTeleporterEnabled; + rc = mMachine->COMGETTER(TeleporterEnabled)(&fTeleporterEnabled); + if (FAILED(rc)) + return rc; + +#if 0 /** @todo we should save it afterwards, but that isn't necessarily a good idea. Find a better place for this (VBoxSVC). */ + if (fTeleporterEnabled) + { + rc = mMachine->COMSETTER(TeleporterEnabled)(FALSE); + if (FAILED(rc)) + return rc; + } +#endif + + PCVMMR3VTABLE const pVMM = mpVMM; + AssertPtrReturn(pVMM, E_UNEXPECTED); + + ComObjPtr<Progress> pPowerupProgress; + bool fBeganPoweringUp = false; + + LONG cOperations = 1; + LONG ulTotalOperationsWeight = 1; + VMPowerUpTask *task = NULL; + + try + { + /* Create a progress object to track progress of this operation. Must + * be done as early as possible (together with BeginPowerUp()) as this + * is vital for communicating as much as possible early powerup + * failure information to the API caller */ + pPowerupProgress.createObject(); + Bstr progressDesc; + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + progressDesc = tr("Restoring virtual machine"); + else if (fTeleporterEnabled) + progressDesc = tr("Teleporting virtual machine"); + else + progressDesc = tr("Starting virtual machine"); + + /* + * Saved VMs will have to prove that their saved states seem kosher. + */ + Utf8Str strSavedStateFile; + Bstr bstrStateKeyId; + Bstr bstrStateKeyStore; + + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + { + Bstr bstrSavedStateFile; + rc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam()); + if (FAILED(rc)) + throw rc; + strSavedStateFile = bstrSavedStateFile; + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + rc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam()); + if (FAILED(rc)) + throw rc; + rc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam()); + if (FAILED(rc)) + throw rc; +#endif + + ComAssertRet(bstrSavedStateFile.isNotEmpty(), E_FAIL); + SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore); + int vrc = ssmStream.open(strSavedStateFile.c_str()); + if (RT_SUCCESS(vrc)) + { + PCSSMSTRMOPS pStreamOps; + void *pvStreamOpsUser; + + vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser); + if (RT_SUCCESS(vrc)) + vrc = pVMM->pfnSSMR3ValidateFile(NULL /*pszFilename*/, pStreamOps, pvStreamOpsUser, + false /* fChecksumIt */); + } + + if (RT_FAILURE(vrc)) + { + Utf8Str errMsg; + switch (vrc) + { + case VERR_FILE_NOT_FOUND: + errMsg.printf(tr("VM failed to start because the saved state file '%s' does not exist."), + strSavedStateFile.c_str()); + break; + default: + errMsg.printf(tr("VM failed to start because the saved state file '%s' is invalid (%Rrc). " + "Delete the saved state prior to starting the VM."), strSavedStateFile.c_str(), vrc); + break; + } + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, errMsg.c_str()); + } + + } + + /* Read console data, including console shared folders, stored in the + * saved state file (if not yet done). + */ + rc = i_loadDataFromSavedState(); + if (FAILED(rc)) + throw rc; + + /* Check all types of shared folders and compose a single list */ + SharedFolderDataMap sharedFolders; + { + /* first, insert global folders */ + for (SharedFolderDataMap::const_iterator it = m_mapGlobalSharedFolders.begin(); + it != m_mapGlobalSharedFolders.end(); + ++it) + { + const SharedFolderData &d = it->second; + sharedFolders[it->first] = d; + } + + /* second, insert machine folders */ + for (SharedFolderDataMap::const_iterator it = m_mapMachineSharedFolders.begin(); + it != m_mapMachineSharedFolders.end(); + ++it) + { + const SharedFolderData &d = it->second; + sharedFolders[it->first] = d; + } + + /* third, insert console folders */ + for (SharedFolderMap::const_iterator it = m_mapSharedFolders.begin(); + it != m_mapSharedFolders.end(); + ++it) + { + SharedFolder *pSF = it->second; + AutoCaller sfCaller(pSF); + AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS); + sharedFolders[it->first] = SharedFolderData(pSF->i_getHostPath(), + pSF->i_isWritable(), + pSF->i_isAutoMounted(), + pSF->i_getAutoMountPoint()); + } + } + + + /* Setup task object and thread to carry out the operation + * asynchronously */ + try { task = new VMPowerUpTask(this, pPowerupProgress); } + catch (std::bad_alloc &) { throw rc = E_OUTOFMEMORY; } + if (!task->isOk()) + throw task->rc(); + + task->mpfnConfigConstructor = i_configConstructor; + task->mSharedFolders = sharedFolders; + task->mStartPaused = aPaused; + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + try { task->mSavedStateFile = strSavedStateFile; } + catch (std::bad_alloc &) { throw rc = E_OUTOFMEMORY; } + task->mTeleporterEnabled = fTeleporterEnabled; + + /* Reset differencing hard disks for which autoReset is true, + * but only if the machine has no snapshots OR the current snapshot + * is an OFFLINE snapshot; otherwise we would reset the current + * differencing image of an ONLINE snapshot which contains the disk + * state of the machine while it was previously running, but without + * the corresponding machine state, which is equivalent to powering + * off a running machine and not good idea + */ + ComPtr<ISnapshot> pCurrentSnapshot; + rc = mMachine->COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()); + if (FAILED(rc)) + throw rc; + + BOOL fCurrentSnapshotIsOnline = false; + if (pCurrentSnapshot) + { + rc = pCurrentSnapshot->COMGETTER(Online)(&fCurrentSnapshotIsOnline); + if (FAILED(rc)) + throw rc; + } + + if (strSavedStateFile.isEmpty() && !fCurrentSnapshotIsOnline) + { + LogFlowThisFunc(("Looking for immutable images to reset\n")); + + com::SafeIfaceArray<IMediumAttachment> atts; + rc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts)); + if (FAILED(rc)) + throw rc; + + for (size_t i = 0; + i < atts.size(); + ++i) + { + DeviceType_T devType; + rc = atts[i]->COMGETTER(Type)(&devType); + /** @todo later applies to floppies as well */ + if (devType == DeviceType_HardDisk) + { + ComPtr<IMedium> pMedium; + rc = atts[i]->COMGETTER(Medium)(pMedium.asOutParam()); + if (FAILED(rc)) + throw rc; + + /* needs autoreset? */ + BOOL autoReset = FALSE; + rc = pMedium->COMGETTER(AutoReset)(&autoReset); + if (FAILED(rc)) + throw rc; + + if (autoReset) + { + ComPtr<IProgress> pResetProgress; + rc = pMedium->Reset(pResetProgress.asOutParam()); + if (FAILED(rc)) + throw rc; + + /* save for later use on the powerup thread */ + task->hardDiskProgresses.push_back(pResetProgress); + } + } + } + } + else + LogFlowThisFunc(("Machine has a current snapshot which is online, skipping immutable images reset\n")); + + /* setup task object and thread to carry out the operation + * asynchronously */ + +#ifdef VBOX_WITH_EXTPACK + mptrExtPackManager->i_dumpAllToReleaseLog(); +#endif + +#ifdef RT_OS_SOLARIS + /* setup host core dumper for the VM */ + Bstr value; + HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpEnabled").raw(), value.asOutParam()); + if (SUCCEEDED(hrc) && value == "1") + { + Bstr coreDumpDir, coreDumpReplaceSys, coreDumpLive; + mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpDir").raw(), coreDumpDir.asOutParam()); + mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpReplaceSystemDump").raw(), coreDumpReplaceSys.asOutParam()); + mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpLive").raw(), coreDumpLive.asOutParam()); + + uint32_t fCoreFlags = 0; + if ( coreDumpReplaceSys.isEmpty() == false + && Utf8Str(coreDumpReplaceSys).toUInt32() == 1) + fCoreFlags |= RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP; + + if ( coreDumpLive.isEmpty() == false + && Utf8Str(coreDumpLive).toUInt32() == 1) + fCoreFlags |= RTCOREDUMPER_FLAGS_LIVE_CORE; + + Utf8Str strDumpDir(coreDumpDir); + const char *pszDumpDir = strDumpDir.c_str(); + if ( pszDumpDir + && *pszDumpDir == '\0') + pszDumpDir = NULL; + + int vrc; + if ( pszDumpDir + && !RTDirExists(pszDumpDir)) + { + /* + * Try create the directory. + */ + vrc = RTDirCreateFullPath(pszDumpDir, 0700); + if (RT_FAILURE(vrc)) + throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper. Couldn't create dump directory '%s' (%Rrc)"), + pszDumpDir, vrc); + } + + vrc = RTCoreDumperSetup(pszDumpDir, fCoreFlags); + if (RT_FAILURE(vrc)) + throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper (%Rrc)"), vrc); + LogRel(("CoreDumper setup successful. pszDumpDir=%s fFlags=%#x\n", pszDumpDir ? pszDumpDir : ".", fCoreFlags)); + } +#endif + + + // If there is immutable drive the process that. + VMPowerUpTask::ProgressList progresses(task->hardDiskProgresses); + if (aProgress && !progresses.empty()) + { + for (VMPowerUpTask::ProgressList::const_iterator it = progresses.begin(); it != progresses.end(); ++it) + { + ++cOperations; + ulTotalOperationsWeight += 1; + } + rc = pPowerupProgress->init(static_cast<IConsole *>(this), + progressDesc.raw(), + TRUE, // Cancelable + cOperations, + ulTotalOperationsWeight, + tr("Starting Hard Disk operations"), + 1); + AssertComRCReturnRC(rc); + } + else if ( mMachineState == MachineState_Saved + || mMachineState == MachineState_AbortedSaved + || !fTeleporterEnabled) + rc = pPowerupProgress->init(static_cast<IConsole *>(this), + progressDesc.raw(), + FALSE /* aCancelable */); + else if (fTeleporterEnabled) + rc = pPowerupProgress->init(static_cast<IConsole *>(this), + progressDesc.raw(), + TRUE /* aCancelable */, + 3 /* cOperations */, + 10 /* ulTotalOperationsWeight */, + tr("Teleporting virtual machine"), + 1 /* ulFirstOperationWeight */); + + if (FAILED(rc)) + throw rc; + + /* Tell VBoxSVC and Machine about the progress object so they can + combine/proxy it to any openRemoteSession caller. */ + LogFlowThisFunc(("Calling BeginPowerUp...\n")); + rc = mControl->BeginPowerUp(pPowerupProgress); + if (FAILED(rc)) + { + LogFlowThisFunc(("BeginPowerUp failed\n")); + throw rc; + } + fBeganPoweringUp = true; + + LogFlowThisFunc(("Checking if canceled...\n")); + BOOL fCanceled; + rc = pPowerupProgress->COMGETTER(Canceled)(&fCanceled); + if (FAILED(rc)) + throw rc; + + if (fCanceled) + { + LogFlowThisFunc(("Canceled in BeginPowerUp\n")); + throw setError(E_FAIL, tr("Powerup was canceled")); + } + LogFlowThisFunc(("Not canceled yet.\n")); + + /** @todo this code prevents starting a VM with unavailable bridged + * networking interface. The only benefit is a slightly better error + * message, which should be moved to the driver code. This is the + * only reason why I left the code in for now. The driver allows + * unavailable bridged networking interfaces in certain circumstances, + * and this is sabotaged by this check. The VM will initially have no + * network connectivity, but the user can fix this at runtime. */ +#if 0 + /* the network cards will undergo a quick consistency check */ + for (ULONG slot = 0; + slot < maxNetworkAdapters; + ++slot) + { + ComPtr<INetworkAdapter> pNetworkAdapter; + mMachine->GetNetworkAdapter(slot, pNetworkAdapter.asOutParam()); + BOOL enabled = FALSE; + pNetworkAdapter->COMGETTER(Enabled)(&enabled); + if (!enabled) + continue; + + NetworkAttachmentType_T netattach; + pNetworkAdapter->COMGETTER(AttachmentType)(&netattach); + switch (netattach) + { + case NetworkAttachmentType_Bridged: + { + /* a valid host interface must have been set */ + Bstr hostif; + pNetworkAdapter->COMGETTER(HostInterface)(hostif.asOutParam()); + if (hostif.isEmpty()) + { + throw setError(VBOX_E_HOST_ERROR, + tr("VM cannot start because host interface networking requires a host interface name to be set")); + } + ComPtr<IVirtualBox> pVirtualBox; + mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + ComPtr<IHost> pHost; + pVirtualBox->COMGETTER(Host)(pHost.asOutParam()); + ComPtr<IHostNetworkInterface> pHostInterface; + if (!SUCCEEDED(pHost->FindHostNetworkInterfaceByName(hostif.raw(), pHostInterface.asOutParam()))) + throw setError(VBOX_E_HOST_ERROR, + tr("VM cannot start because the host interface '%ls' does not exist"), hostif.raw()); + break; + } + default: + break; + } + } +#endif // 0 + + + /* setup task object and thread to carry out the operation + * asynchronously */ + if (aProgress) + { + rc = pPowerupProgress.queryInterfaceTo(aProgress); + AssertComRCReturnRC(rc); + } + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + task->mKeyStore = Utf8Str(bstrStateKeyStore); + task->mKeyId = Utf8Str(bstrStateKeyId); + task->m_pKeyStore = m_pKeyStore; +#endif + + rc = task->createThread(); + task = NULL; + if (FAILED(rc)) + throw rc; + + /* finally, set the state: no right to fail in this method afterwards + * since we've already started the thread and it is now responsible for + * any error reporting and appropriate state change! */ + if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved) + i_setMachineState(MachineState_Restoring); + else if (fTeleporterEnabled) + i_setMachineState(MachineState_TeleportingIn); + else + i_setMachineState(MachineState_Starting); + } + catch (HRESULT aRC) + { + rc = aRC; + } + + if (FAILED(rc) && fBeganPoweringUp) + { + + /* The progress object will fetch the current error info */ + if (!pPowerupProgress.isNull()) + pPowerupProgress->i_notifyComplete(rc); + + /* Save the error info across the IPC below. Can't be done before the + * progress notification above, as saving the error info deletes it + * from the current context, and thus the progress object wouldn't be + * updated correctly. */ + ErrorInfoKeeper eik; + + /* signal end of operation */ + mControl->EndPowerUp(rc); + } + + if (task) + { + ErrorInfoKeeper eik; + delete task; + } + + LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc)); + LogFlowThisFuncLeave(); + return rc; +} + +/** + * Internal power off worker routine. + * + * This method may be called only at certain places with the following meaning + * as shown below: + * + * - if the machine state is either Running or Paused, a normal + * Console-initiated powerdown takes place (e.g. PowerDown()); + * - if the machine state is Saving, saveStateThread() has successfully done its + * job; + * - if the machine state is Starting or Restoring, powerUpThread() has failed + * to start/load the VM; + * - if the machine state is Stopping, the VM has powered itself off (i.e. not + * as a result of the powerDown() call). + * + * Calling it in situations other than the above will cause unexpected behavior. + * + * Note that this method should be the only one that destroys mpUVM and sets it + * to NULL. + * + * @param aProgress Progress object to run (may be NULL). + * + * @note Locks this object for writing. + * + * @note Never call this method from a thread that called addVMCaller() or + * instantiated an AutoVMCaller object; first call releaseVMCaller() or + * release(). Otherwise it will deadlock. + */ +HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + ComPtr<IInternalProgressControl> pProgressControl(aProgress); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Total # of steps for the progress object. Must correspond to the + * number of "advance percent count" comments in this method! */ + enum { StepCount = 7 }; + /* current step */ + ULONG step = 0; + + HRESULT rc = S_OK; + int vrc = VINF_SUCCESS; + + /* sanity */ + Assert(mVMDestroying == false); + + PCVMMR3VTABLE const pVMM = mpVMM; + AssertPtrReturn(pVMM, E_UNEXPECTED); + PUVM pUVM = mpUVM; + AssertPtrReturn(pUVM, E_UNEXPECTED); + + uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM); + Assert(cRefs != UINT32_MAX); NOREF(cRefs); + + AssertMsg( mMachineState == MachineState_Running + || mMachineState == MachineState_Paused + || mMachineState == MachineState_Stuck + || mMachineState == MachineState_Starting + || mMachineState == MachineState_Stopping + || mMachineState == MachineState_Saving + || mMachineState == MachineState_Restoring + || mMachineState == MachineState_TeleportingPausedVM + || mMachineState == MachineState_TeleportingIn + , ("Invalid machine state: %s\n", ::stringifyMachineState(mMachineState))); + + LogRel(("Console::powerDown(): A request to power off the VM has been issued (mMachineState=%s, InUninit=%d)\n", + ::stringifyMachineState(mMachineState), getObjectState().getState() == ObjectState::InUninit)); + + /* Check if we need to power off the VM. In case of mVMPoweredOff=true, the + * VM has already powered itself off in vmstateChangeCallback() and is just + * notifying Console about that. In case of Starting or Restoring, + * powerUpThread() is calling us on failure, so the VM is already off at + * that point. */ + if ( !mVMPoweredOff + && ( mMachineState == MachineState_Starting + || mMachineState == MachineState_Restoring + || mMachineState == MachineState_TeleportingIn) + ) + mVMPoweredOff = true; + + /* + * Go to Stopping state if not already there. + * + * Note that we don't go from Saving/Restoring to Stopping because + * vmstateChangeCallback() needs it to set the state to Saved on + * VMSTATE_TERMINATED. In terms of protecting from inappropriate operations + * while leaving the lock below, Saving or Restoring should be fine too. + * Ditto for TeleportingPausedVM -> Teleported. + */ + if ( mMachineState != MachineState_Saving + && mMachineState != MachineState_Restoring + && mMachineState != MachineState_Stopping + && mMachineState != MachineState_TeleportingIn + && mMachineState != MachineState_TeleportingPausedVM + ) + i_setMachineState(MachineState_Stopping); + + /* ---------------------------------------------------------------------- + * DONE with necessary state changes, perform the power down actions (it's + * safe to release the object lock now if needed) + * ---------------------------------------------------------------------- */ + + if (mDisplay) + { + alock.release(); + + mDisplay->i_notifyPowerDown(); + + alock.acquire(); + } + + /* Stop the VRDP server to prevent new clients connection while VM is being + * powered off. */ + if (mConsoleVRDPServer) + { + LogFlowThisFunc(("Stopping VRDP server...\n")); + + /* Leave the lock since EMT could call us back as addVMCaller() */ + alock.release(); + + mConsoleVRDPServer->Stop(); + + alock.acquire(); + } + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + + + /* ---------------------------------------------------------------------- + * Now, wait for all mpUVM callers to finish their work if there are still + * some on other threads. NO methods that need mpUVM (or initiate other calls + * that need it) may be called after this point + * ---------------------------------------------------------------------- */ + + /* go to the destroying state to prevent from adding new callers */ + mVMDestroying = true; + + if (mVMCallers > 0) + { + /* lazy creation */ + if (mVMZeroCallersSem == NIL_RTSEMEVENT) + RTSemEventCreate(&mVMZeroCallersSem); + + LogFlowThisFunc(("Waiting for mpUVM callers (%d) to drop to zero...\n", mVMCallers)); + + alock.release(); + + RTSemEventWait(mVMZeroCallersSem, RT_INDEFINITE_WAIT); + + alock.acquire(); + } + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + + vrc = VINF_SUCCESS; + + /* + * Power off the VM if not already done that. + * Leave the lock since EMT will call vmstateChangeCallback. + * + * Note that VMR3PowerOff() may fail here (invalid VMSTATE) if the + * VM-(guest-)initiated power off happened in parallel a ms before this + * call. So far, we let this error pop up on the user's side. + */ + if (!mVMPoweredOff) + { + LogFlowThisFunc(("Powering off the VM...\n")); + alock.release(); + vrc = pVMM->pfnVMR3PowerOff(pUVM); +#ifdef VBOX_WITH_EXTPACK + mptrExtPackManager->i_callAllVmPowerOffHooks(this, pVMM->pfnVMR3GetVM(pUVM), pVMM); +#endif + alock.acquire(); + } + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + +#ifdef VBOX_WITH_HGCM + /* Shutdown HGCM services before destroying the VM. */ + if (m_pVMMDev) + { + LogFlowThisFunc(("Shutdown HGCM...\n")); + + /* Leave the lock since EMT might wait for it and will call us back as addVMCaller() */ + alock.release(); + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** @todo Deregister area callbacks? */ +# endif +# ifdef VBOX_WITH_DRAG_AND_DROP + if (m_hHgcmSvcExtDragAndDrop) + { + HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtDragAndDrop); + m_hHgcmSvcExtDragAndDrop = NULL; + } +# endif + + m_pVMMDev->hgcmShutdown(); + + alock.acquire(); + } + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + +#endif /* VBOX_WITH_HGCM */ + + LogFlowThisFunc(("Ready for VM destruction.\n")); + + /* If we are called from Console::uninit(), then try to destroy the VM even + * on failure (this will most likely fail too, but what to do?..) */ + if (RT_SUCCESS(vrc) || getObjectState().getState() == ObjectState::InUninit) + { + /* If the machine has a USB controller, release all USB devices + * (symmetric to the code in captureUSBDevices()) */ + if (mfVMHasUsbController) + { + alock.release(); + i_detachAllUSBDevices(false /* aDone */); + alock.acquire(); + } + + /* Now we've got to destroy the VM as well. (mpUVM is not valid beyond + * this point). We release the lock before calling VMR3Destroy() because + * it will result into calling destructors of drivers associated with + * Console children which may in turn try to lock Console (e.g. by + * instantiating SafeVMPtr to access mpUVM). It's safe here because + * mVMDestroying is set which should prevent any activity. */ + + /* Set mpUVM to NULL early just in case if some old code is not using + * addVMCaller()/releaseVMCaller(). (We have our own ref on pUVM.) */ + pVMM->pfnVMR3ReleaseUVM(mpUVM); + mpUVM = NULL; + + LogFlowThisFunc(("Destroying the VM...\n")); + + alock.release(); + + vrc = pVMM->pfnVMR3Destroy(pUVM); + + /* take the lock again */ + alock.acquire(); + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Machine has been destroyed (mMachineState=%d)\n", + mMachineState)); + /* Note: the Console-level machine state change happens on the + * VMSTATE_TERMINATE state change in vmstateChangeCallback(). If + * powerDown() is called from EMT (i.e. from vmstateChangeCallback() + * on receiving VM-initiated VMSTATE_OFF), VMSTATE_TERMINATE hasn't + * occurred yet. This is okay, because mMachineState is already + * Stopping in this case, so any other attempt to call PowerDown() + * will be rejected. */ + } + else + { + /* bad bad bad, but what to do? (Give Console our UVM ref.) */ + mpUVM = pUVM; + pUVM = NULL; + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not destroy the machine. (Error: %Rrc)"), vrc); + } + + /* Complete the detaching of the USB devices. */ + if (mfVMHasUsbController) + { + alock.release(); + i_detachAllUSBDevices(true /* aDone */); + alock.acquire(); + } + + /* advance percent count */ + if (pProgressControl) + pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount); + } + else + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not power off the machine. (Error: %Rrc)"), vrc); + + /* + * Finished with the destruction. + * + * Note that if something impossible happened and we've failed to destroy + * the VM, mVMDestroying will remain true and mMachineState will be + * something like Stopping, so most Console methods will return an error + * to the caller. + */ + if (pUVM != NULL) + pVMM->pfnVMR3ReleaseUVM(pUVM); + else + mVMDestroying = false; + + LogFlowThisFuncLeave(); + return rc; +} + +/** + * @note Locks this object for writing. + */ +HRESULT Console::i_setMachineState(MachineState_T aMachineState, bool aUpdateServer /* = true */) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + if (mMachineState != aMachineState) + { + LogThisFunc(("machineState=%s -> %s aUpdateServer=%RTbool\n", + ::stringifyMachineState(mMachineState), ::stringifyMachineState(aMachineState), aUpdateServer)); + LogRel(("Console: Machine state changed to '%s'\n", ::stringifyMachineState(aMachineState))); + mMachineState = aMachineState; + + /// @todo (dmik) + // possibly, we need to redo onStateChange() using the dedicated + // Event thread, like it is done in VirtualBox. This will make it + // much safer (no deadlocks possible if someone tries to use the + // console from the callback), however, listeners will lose the + // ability to synchronously react to state changes (is it really + // necessary??) + LogFlowThisFunc(("Doing onStateChange()...\n")); + i_onStateChange(aMachineState); + LogFlowThisFunc(("Done onStateChange()\n")); + + if (aUpdateServer) + { + /* Server notification MUST be done from under the lock; otherwise + * the machine state here and on the server might go out of sync + * which can lead to various unexpected results (like the machine + * state being >= MachineState_Running on the server, while the + * session state is already SessionState_Unlocked at the same time + * there). + * + * Cross-lock conditions should be carefully watched out: calling + * UpdateState we will require Machine and SessionMachine locks + * (remember that here we're holding the Console lock here, and also + * all locks that have been acquire by the thread before calling + * this method). + */ + LogFlowThisFunc(("Doing mControl->UpdateState()...\n")); + rc = mControl->UpdateState(aMachineState); + LogFlowThisFunc(("mControl->UpdateState()=%Rhrc\n", rc)); + } + } + + return rc; +} + +/** + * Searches for a shared folder with the given logical name + * in the collection of shared folders. + * + * @param strName logical name of the shared folder + * @param aSharedFolder where to return the found object + * @param aSetError whether to set the error info if the folder is + * not found + * @return + * S_OK when found or E_INVALIDARG when not found + * + * @note The caller must lock this object for writing. + */ +HRESULT Console::i_findSharedFolder(const Utf8Str &strName, ComObjPtr<SharedFolder> &aSharedFolder, bool aSetError /* = false */) +{ + /* sanity check */ + AssertReturn(isWriteLockOnCurrentThread(), E_FAIL); + + SharedFolderMap::const_iterator it = m_mapSharedFolders.find(strName); + if (it != m_mapSharedFolders.end()) + { + aSharedFolder = it->second; + return S_OK; + } + + if (aSetError) + setError(VBOX_E_FILE_ERROR, tr("Could not find a shared folder named '%s'."), strName.c_str()); + return VBOX_E_FILE_ERROR; +} + +/** + * Fetches the list of global or machine shared folders from the server. + * + * @param aGlobal true to fetch global folders. + * + * @note The caller must lock this object for writing. + */ +HRESULT Console::i_fetchSharedFolders(BOOL aGlobal) +{ + /* sanity check */ + AssertReturn( getObjectState().getState() == ObjectState::InInit + || isWriteLockOnCurrentThread(), E_FAIL); + + LogFlowThisFunc(("Entering\n")); + + /* Check if we're online and keep it that way. */ + SafeVMPtrQuiet ptrVM(this); + AutoVMCallerQuietWeak autoVMCaller(this); + bool const online = ptrVM.isOk() + && m_pVMMDev + && m_pVMMDev->isShFlActive(); + + HRESULT rc = S_OK; + + try + { + if (aGlobal) + { + /// @todo grab & process global folders when they are done + } + else + { + SharedFolderDataMap oldFolders; + if (online) + oldFolders = m_mapMachineSharedFolders; + + m_mapMachineSharedFolders.clear(); + + SafeIfaceArray<ISharedFolder> folders; + rc = mMachine->COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(folders)); + if (FAILED(rc)) throw rc; + + for (size_t i = 0; i < folders.size(); ++i) + { + ComPtr<ISharedFolder> pSharedFolder = folders[i]; + + Bstr bstr; + rc = pSharedFolder->COMGETTER(Name)(bstr.asOutParam()); + if (FAILED(rc)) throw rc; + Utf8Str strName(bstr); + + rc = pSharedFolder->COMGETTER(HostPath)(bstr.asOutParam()); + if (FAILED(rc)) throw rc; + Utf8Str strHostPath(bstr); + + BOOL writable; + rc = pSharedFolder->COMGETTER(Writable)(&writable); + if (FAILED(rc)) throw rc; + + BOOL autoMount; + rc = pSharedFolder->COMGETTER(AutoMount)(&autoMount); + if (FAILED(rc)) throw rc; + + rc = pSharedFolder->COMGETTER(AutoMountPoint)(bstr.asOutParam()); + if (FAILED(rc)) throw rc; + Utf8Str strAutoMountPoint(bstr); + + m_mapMachineSharedFolders.insert(std::make_pair(strName, + SharedFolderData(strHostPath, !!writable, + !!autoMount, strAutoMountPoint))); + + /* send changes to HGCM if the VM is running */ + if (online) + { + SharedFolderDataMap::iterator it = oldFolders.find(strName); + if ( it == oldFolders.end() + || it->second.m_strHostPath != strHostPath) + { + /* a new machine folder is added or + * the existing machine folder is changed */ + if (m_mapSharedFolders.find(strName) != m_mapSharedFolders.end()) + ; /* the console folder exists, nothing to do */ + else + { + /* remove the old machine folder (when changed) + * or the global folder if any (when new) */ + if ( it != oldFolders.end() + || m_mapGlobalSharedFolders.find(strName) != m_mapGlobalSharedFolders.end() + ) + { + rc = i_removeSharedFolder(strName); + if (FAILED(rc)) throw rc; + } + + /* create the new machine folder */ + rc = i_createSharedFolder(strName, + SharedFolderData(strHostPath, !!writable, !!autoMount, strAutoMountPoint)); + if (FAILED(rc)) throw rc; + } + } + /* forget the processed (or identical) folder */ + if (it != oldFolders.end()) + oldFolders.erase(it); + } + } + + /* process outdated (removed) folders */ + if (online) + { + for (SharedFolderDataMap::const_iterator it = oldFolders.begin(); + it != oldFolders.end(); ++it) + { + if (m_mapSharedFolders.find(it->first) != m_mapSharedFolders.end()) + ; /* the console folder exists, nothing to do */ + else + { + /* remove the outdated machine folder */ + rc = i_removeSharedFolder(it->first); + if (FAILED(rc)) throw rc; + + /* create the global folder if there is any */ + SharedFolderDataMap::const_iterator git = + m_mapGlobalSharedFolders.find(it->first); + if (git != m_mapGlobalSharedFolders.end()) + { + rc = i_createSharedFolder(git->first, git->second); + if (FAILED(rc)) throw rc; + } + } + } + } + } + } + catch (HRESULT rc2) + { + rc = rc2; + if (online) + i_atVMRuntimeErrorCallbackF(0, "BrokenSharedFolder", N_("Broken shared folder!")); + } + + LogFlowThisFunc(("Leaving\n")); + + return rc; +} + +/** + * Searches for a shared folder with the given name in the list of machine + * shared folders and then in the list of the global shared folders. + * + * @param strName Name of the folder to search for. + * @param aIt Where to store the pointer to the found folder. + * @return @c true if the folder was found and @c false otherwise. + * + * @note The caller must lock this object for reading. + */ +bool Console::i_findOtherSharedFolder(const Utf8Str &strName, + SharedFolderDataMap::const_iterator &aIt) +{ + /* sanity check */ + AssertReturn(isWriteLockOnCurrentThread(), false); + + /* first, search machine folders */ + aIt = m_mapMachineSharedFolders.find(strName); + if (aIt != m_mapMachineSharedFolders.end()) + return true; + + /* second, search machine folders */ + aIt = m_mapGlobalSharedFolders.find(strName); + if (aIt != m_mapGlobalSharedFolders.end()) + return true; + + return false; +} + +/** + * Calls the HGCM service to add a shared folder definition. + * + * @param strName Shared folder name. + * @param aData Shared folder data. + * + * @note Must be called from under AutoVMCaller and when mpUVM != NULL! + * @note Doesn't lock anything. + */ +HRESULT Console::i_createSharedFolder(const Utf8Str &strName, const SharedFolderData &aData) +{ + Log(("Adding shared folder '%s' -> '%s'\n", strName.c_str(), aData.m_strHostPath.c_str())); + + /* + * Sanity checks + */ + ComAssertRet(strName.isNotEmpty(), E_FAIL); + ComAssertRet(aData.m_strHostPath.isNotEmpty(), E_FAIL); + + AssertReturn(mpUVM, E_FAIL); + AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL); + + /* + * Find out whether we should allow symbolic link creation. + */ + Bstr bstrValue; + HRESULT hrc = mMachine->GetExtraData(BstrFmt("VBoxInternal2/SharedFoldersEnableSymlinksCreate/%s", strName.c_str()).raw(), + bstrValue.asOutParam()); + bool fSymlinksCreate = hrc == S_OK && bstrValue == "1"; + + /* + * Check whether the path is valid and exists. + */ + char szAbsHostPath[RTPATH_MAX]; + int vrc = RTPathAbs(aData.m_strHostPath.c_str(), szAbsHostPath, sizeof(szAbsHostPath)); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), aData.m_strHostPath.c_str(), vrc); + + /* Check whether the path is full (absolute). ASSUMING a RTPATH_MAX of ~4K + this also checks that the length is within bounds of a SHFLSTRING. */ + if (RTPathCompare(aData.m_strHostPath.c_str(), szAbsHostPath) != 0) + return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), aData.m_strHostPath.c_str()); + + bool const fMissing = !RTPathExists(szAbsHostPath); + + /* + * Check the other two string lengths before converting them all to SHFLSTRINGS. + */ + if (strName.length() >= _2K) + return setError(E_INVALIDARG, tr("Shared folder name is too long: %zu bytes", "", strName.length()), strName.length()); + if (aData.m_strAutoMountPoint.length() >= RTPATH_MAX) + return setError(E_INVALIDARG, tr("Shared folder mount point too long: %zu bytes", "", + (int)aData.m_strAutoMountPoint.length()), + aData.m_strAutoMountPoint.length()); + + PSHFLSTRING pHostPath = ShflStringDupUtf8AsUtf16(aData.m_strHostPath.c_str()); + PSHFLSTRING pName = ShflStringDupUtf8AsUtf16(strName.c_str()); + PSHFLSTRING pAutoMountPoint = ShflStringDupUtf8AsUtf16(aData.m_strAutoMountPoint.c_str()); + if (pHostPath && pName && pAutoMountPoint) + { + /* + * Make a SHFL_FN_ADD_MAPPING call to tell the service about folder. + */ + VBOXHGCMSVCPARM aParams[SHFL_CPARMS_ADD_MAPPING]; + SHFLSTRING_TO_HGMC_PARAM(&aParams[0], pHostPath); + SHFLSTRING_TO_HGMC_PARAM(&aParams[1], pName); + HGCMSvcSetU32(&aParams[2], + (aData.m_fWritable ? SHFL_ADD_MAPPING_F_WRITABLE : 0) + | (aData.m_fAutoMount ? SHFL_ADD_MAPPING_F_AUTOMOUNT : 0) + | (fSymlinksCreate ? SHFL_ADD_MAPPING_F_CREATE_SYMLINKS : 0) + | (fMissing ? SHFL_ADD_MAPPING_F_MISSING : 0)); + SHFLSTRING_TO_HGMC_PARAM(&aParams[3], pAutoMountPoint); + AssertCompile(SHFL_CPARMS_ADD_MAPPING == 4); + + vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_ADD_MAPPING, SHFL_CPARMS_ADD_MAPPING, aParams); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(E_FAIL, vrc, tr("Could not create a shared folder '%s' mapped to '%s' (%Rrc)"), + strName.c_str(), aData.m_strHostPath.c_str(), vrc); + + else if (fMissing) + hrc = setError(E_INVALIDARG, tr("Shared folder path '%s' does not exist on the host"), aData.m_strHostPath.c_str()); + else + hrc = S_OK; + } + else + hrc = E_OUTOFMEMORY; + RTMemFree(pAutoMountPoint); + RTMemFree(pName); + RTMemFree(pHostPath); + return hrc; +} + +/** + * Calls the HGCM service to remove the shared folder definition. + * + * @param strName Shared folder name. + * + * @note Must be called from under AutoVMCaller and when mpUVM != NULL! + * @note Doesn't lock anything. + */ +HRESULT Console::i_removeSharedFolder(const Utf8Str &strName) +{ + ComAssertRet(strName.isNotEmpty(), E_FAIL); + + /* sanity checks */ + AssertReturn(mpUVM, E_FAIL); + AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL); + + VBOXHGCMSVCPARM parms; + SHFLSTRING *pMapName; + size_t cbString; + + Log(("Removing shared folder '%s'\n", strName.c_str())); + + Bstr bstrName(strName); + cbString = (bstrName.length() + 1) * sizeof(RTUTF16); + if (cbString >= UINT16_MAX) + return setError(E_INVALIDARG, tr("The name is too long")); + pMapName = (SHFLSTRING *) RTMemAllocZ(SHFLSTRING_HEADER_SIZE + cbString); + Assert(pMapName); + memcpy(pMapName->String.ucs2, bstrName.raw(), cbString); + + pMapName->u16Size = (uint16_t)cbString; + pMapName->u16Length = (uint16_t)(cbString - sizeof(RTUTF16)); + + parms.type = VBOX_HGCM_SVC_PARM_PTR; + parms.u.pointer.addr = pMapName; + parms.u.pointer.size = ShflStringSizeOfBuffer(pMapName); + + int vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_REMOVE_MAPPING, 1, &parms); + RTMemFree(pMapName); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Could not remove the shared folder '%s' (%Rrc)"), strName.c_str(), vrc); + + return S_OK; +} + +/** + * Retains a reference to the default cryptographic interface. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the VM is not configured for encryption. + * @param ppCryptoIf Where to store the pointer to the cryptographic interface on success. + * + * @note Locks this object for writing. + */ +int Console::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf) +{ + AssertReturn(ppCryptoIf != NULL, VERR_INVALID_PARAMETER); + + int vrc = VINF_SUCCESS; + if (mhLdrModCrypto == NIL_RTLDRMOD) + { +#ifdef VBOX_WITH_EXTPACK + /* + * Check that a crypto extension pack name is set and resolve it into a + * library path. + */ + HRESULT hrc = S_OK; + Bstr bstrExtPack; + + ComPtr<IVirtualBox> pVirtualBox; + mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + ComPtr<ISystemProperties> pSystemProperties; + if (pVirtualBox) + pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam()); + if (pSystemProperties) + pSystemProperties->COMGETTER(DefaultCryptoExtPack)(bstrExtPack.asOutParam()); + if (FAILED(hrc)) + return hrc; + + Utf8Str strExtPack(bstrExtPack); + if (strExtPack.isEmpty()) + { + setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Ńo extension pack providing a cryptographic support module could be found")); + return VERR_NOT_FOUND; + } + + Utf8Str strCryptoLibrary; + vrc = mptrExtPackManager->i_getCryptoLibraryPathForExtPack(&strExtPack, &strCryptoLibrary); + if (RT_SUCCESS(vrc)) + { + RTERRINFOSTATIC ErrInfo; + vrc = SUPR3HardenedLdrLoadPlugIn(strCryptoLibrary.c_str(), &mhLdrModCrypto, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(vrc)) + { + /* Resolve the entry point and query the pointer to the cryptographic interface. */ + PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL; + vrc = RTLdrGetSymbol(mhLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry); + if (RT_SUCCESS(vrc)) + { + vrc = pfnCryptoEntry(&mpCryptoIf); + if (RT_FAILURE(vrc)) + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Failed to query the interface callback table from the cryptographic support module '%s' from extension pack '%s'"), + strCryptoLibrary.c_str(), strExtPack.c_str()); + } + else + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Failed to resolve the entry point for the cryptographic support module '%s' from extension pack '%s'"), + strCryptoLibrary.c_str(), strExtPack.c_str()); + + if (RT_FAILURE(vrc)) + { + RTLdrClose(mhLdrModCrypto); + mhLdrModCrypto = NIL_RTLDRMOD; + } + } + else + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Couldn't load the cryptographic support module '%s' from extension pack '%s' (error: '%s')"), + strCryptoLibrary.c_str(), strExtPack.c_str(), ErrInfo.Core.pszMsg); + } + else + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Couldn't resolve the library path of the crpytographic support module for extension pack '%s'"), + strExtPack.c_str()); +#else + setError(VBOX_E_NOT_SUPPORTED, + tr("The cryptographic support module is not supported in this build because extension packs are not supported")); + vrc = VERR_NOT_SUPPORTED; +#endif + } + + if (RT_SUCCESS(vrc)) + { + ASMAtomicIncU32(&mcRefsCrypto); + *ppCryptoIf = mpCryptoIf; + } + + return vrc; +} + +/** + * Releases the reference of the given cryptographic interface. + * + * @returns VBox status code. + * @param pCryptoIf Pointer to the cryptographic interface to release. + * + * @note Locks this object for writing. + */ +int Console::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf) +{ + AssertReturn(pCryptoIf == mpCryptoIf, VERR_INVALID_PARAMETER); + + ASMAtomicDecU32(&mcRefsCrypto); + return VINF_SUCCESS; +} + +/** + * Tries to unload any loaded cryptographic support module if it is not in use currently. + * + * @returns COM status code. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_unloadCryptoIfModule(void) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS); + + if (mcRefsCrypto) + return setError(E_ACCESSDENIED, + tr("The cryptographic support module is in use and can't be unloaded")); + + if (mhLdrModCrypto != NIL_RTLDRMOD) + { + int vrc = RTLdrClose(mhLdrModCrypto); + AssertRC(vrc); + mhLdrModCrypto = NIL_RTLDRMOD; + } + + return S_OK; +} + +/** @callback_method_impl{FNVMATSTATE} + * + * @note Locks the Console object for writing. + * @remarks The @a pUVM parameter can be NULL in one case where powerUpThread() + * calls after the VM was destroyed. + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmstateChangeCallback(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState, VMSTATE enmOldState, void *pvUser) +{ + LogFlowFunc(("Changing state from %s to %s (pUVM=%p)\n", + pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState), pUVM)); + RT_NOREF(pVMM); + + Console *that = static_cast<Console *>(pvUser); + AssertReturnVoid(that); + + AutoCaller autoCaller(that); + + /* Note that we must let this method proceed even if Console::uninit() has + * been already called. In such case this VMSTATE change is a result of: + * 1) powerDown() called from uninit() itself, or + * 2) VM-(guest-)initiated power off. */ + AssertReturnVoid( autoCaller.isOk() + || that->getObjectState().getState() == ObjectState::InUninit); + + switch (enmState) + { + /* + * The VM has terminated + */ + case VMSTATE_OFF: + { +#ifdef VBOX_WITH_GUEST_PROPS + if (that->mfTurnResetIntoPowerOff) + { + Bstr strPowerOffReason; + + if (that->mfPowerOffCausedByReset) + strPowerOffReason = Bstr("Reset"); + else + strPowerOffReason = Bstr("PowerOff"); + + that->mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw()); + that->mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(), + strPowerOffReason.raw(), Bstr("RDONLYGUEST").raw()); + that->mMachine->SaveSettings(); + } +#endif + + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + return; + + /* Do we still think that it is running? It may happen if this is a + * VM-(guest-)initiated shutdown/poweroff. + */ + if ( that->mMachineState != MachineState_Stopping + && that->mMachineState != MachineState_Saving + && that->mMachineState != MachineState_Restoring + && that->mMachineState != MachineState_TeleportingIn + && that->mMachineState != MachineState_TeleportingPausedVM + && !that->mVMIsAlreadyPoweringOff + ) + { + LogFlowFunc(("VM has powered itself off but Console still thinks it is running. Notifying.\n")); + + /* + * Prevent powerDown() from calling VMR3PowerOff() again if this was called from + * the power off state change. + * When called from the Reset state make sure to call VMR3PowerOff() first. + */ + Assert(that->mVMPoweredOff == false); + that->mVMPoweredOff = true; + + /* + * request a progress object from the server + * (this will set the machine state to Stopping on the server + * to block others from accessing this machine) + */ + ComPtr<IProgress> pProgress; + HRESULT rc = that->mControl->BeginPoweringDown(pProgress.asOutParam()); + AssertComRC(rc); + + /* sync the state with the server */ + that->i_setMachineStateLocally(MachineState_Stopping); + + /* + * Setup task object and thread to carry out the operation + * asynchronously (if we call powerDown() right here but there + * is one or more mpUVM callers (added with addVMCaller()) we'll + * deadlock). + */ + VMPowerDownTask *pTask = NULL; + try + { + pTask = new VMPowerDownTask(that, pProgress); + } + catch (std::bad_alloc &) + { + LogRelFunc(("E_OUTOFMEMORY creating VMPowerDownTask")); + rc = E_OUTOFMEMORY; + break; + } + + /* + * If creating a task failed, this can currently mean one of + * two: either Console::uninit() has been called just a ms + * before (so a powerDown() call is already on the way), or + * powerDown() itself is being already executed. Just do + * nothing. + */ + if (pTask->isOk()) + { + rc = pTask->createThread(); + pTask = NULL; + if (FAILED(rc)) + LogRelFunc(("Problem with creating thread for VMPowerDownTask.\n")); + } + else + { + LogFlowFunc(("Console is already being uninitialized. (%Rhrc)\n", pTask->rc())); + delete pTask; + pTask = NULL; + rc = E_FAIL; + } + } + break; + } + + /* The VM has been completely destroyed. + * + * Note: This state change can happen at two points: + * 1) At the end of VMR3Destroy() if it was not called from EMT. + * 2) At the end of vmR3EmulationThread if VMR3Destroy() was + * called by EMT. + */ + case VMSTATE_TERMINATED: + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + break; + +#ifdef VBOX_WITH_CLOUD_NET + /* + * We stop cloud gateway here because we may have failed to connect to it, + * configure it, or establish a tunnel. We definitely do not want an orphaned + * instance running in the cloud. + */ + if (!that->mGateway.mGatewayInstanceId.isEmpty()) + { + ComPtr<IVirtualBox> pVirtualBox; + HRESULT rc = that->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + AssertComRC(rc); + if (SUCCEEDED(rc) && !pVirtualBox.isNull()) + stopCloudGateway(pVirtualBox, that->mGateway); + } +#endif /* VBOX_WITH_CLOUD_NET */ + /* Terminate host interface networking. If pUVM is NULL, we've been + * manually called from powerUpThread() either before calling + * VMR3Create() or after VMR3Create() failed, so no need to touch + * networking. + */ + if (pUVM) + that->i_powerDownHostInterfaces(); + + /* From now on the machine is officially powered down or remains in + * the Saved state. + */ + switch (that->mMachineState) + { + default: + AssertFailed(); + RT_FALL_THRU(); + case MachineState_Stopping: + /* successfully powered down */ + that->i_setMachineState(MachineState_PoweredOff); + break; + case MachineState_Saving: + /* successfully saved */ + that->i_setMachineState(MachineState_Saved); + break; + case MachineState_Starting: + /* failed to start, but be patient: set back to PoweredOff + * (for similarity with the below) */ + that->i_setMachineState(MachineState_PoweredOff); + break; + case MachineState_Restoring: + /* failed to load the saved state file, but be patient: set + * to AbortedSaved (to preserve the saved state file) */ + that->i_setMachineState(MachineState_AbortedSaved); + break; + case MachineState_TeleportingIn: + /* Teleportation failed or was canceled. Back to powered off. */ + that->i_setMachineState(MachineState_PoweredOff); + break; + case MachineState_TeleportingPausedVM: + /* Successfully teleported the VM. */ + that->i_setMachineState(MachineState_Teleported); + break; + } + break; + } + + case VMSTATE_RESETTING: + /** @todo shouldn't VMSTATE_RESETTING_LS be here? */ + { +#ifdef VBOX_WITH_GUEST_PROPS + /* Do not take any read/write locks here! */ + that->i_guestPropertiesHandleVMReset(); +#endif + break; + } + + case VMSTATE_SOFT_RESETTING: + case VMSTATE_SOFT_RESETTING_LS: + /* Shouldn't do anything here! */ + break; + + case VMSTATE_SUSPENDED: + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + break; + + switch (that->mMachineState) + { + case MachineState_Teleporting: + that->i_setMachineState(MachineState_TeleportingPausedVM); + break; + + case MachineState_LiveSnapshotting: + that->i_setMachineState(MachineState_OnlineSnapshotting); + break; + + case MachineState_TeleportingPausedVM: + case MachineState_Saving: + case MachineState_Restoring: + case MachineState_Stopping: + case MachineState_TeleportingIn: + case MachineState_OnlineSnapshotting: + /* The worker thread handles the transition. */ + break; + + case MachineState_Running: + that->i_setMachineState(MachineState_Paused); + break; + + case MachineState_Paused: + /* Nothing to do. */ + break; + + default: + AssertMsgFailed(("%s\n", ::stringifyMachineState(that->mMachineState))); + } + break; + } + + case VMSTATE_SUSPENDED_LS: + case VMSTATE_SUSPENDED_EXT_LS: + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + if (that->mVMStateChangeCallbackDisabled) + break; + switch (that->mMachineState) + { + case MachineState_Teleporting: + that->i_setMachineState(MachineState_TeleportingPausedVM); + break; + + case MachineState_LiveSnapshotting: + that->i_setMachineState(MachineState_OnlineSnapshotting); + break; + + case MachineState_TeleportingPausedVM: + case MachineState_Saving: + /* ignore */ + break; + + default: + AssertMsgFailed(("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState), + pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) )); + that->i_setMachineState(MachineState_Paused); + break; + } + break; + } + + case VMSTATE_RUNNING: + { + if ( enmOldState == VMSTATE_POWERING_ON + || enmOldState == VMSTATE_RESUMING) + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + break; + + Assert( ( ( that->mMachineState == MachineState_Starting + || that->mMachineState == MachineState_Paused) + && enmOldState == VMSTATE_POWERING_ON) + || ( ( that->mMachineState == MachineState_Restoring + || that->mMachineState == MachineState_TeleportingIn + || that->mMachineState == MachineState_Paused + || that->mMachineState == MachineState_Saving + ) + && enmOldState == VMSTATE_RESUMING)); + + that->i_setMachineState(MachineState_Running); + } + + break; + } + + case VMSTATE_RUNNING_LS: + AssertMsg( that->mMachineState == MachineState_LiveSnapshotting + || that->mMachineState == MachineState_Teleporting, + ("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState), + pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) )); + break; + + case VMSTATE_FATAL_ERROR: + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + break; + + /* Fatal errors are only for running VMs. */ + Assert(Global::IsOnline(that->mMachineState)); + + /* Note! 'Pause' is used here in want of something better. There + * are currently only two places where fatal errors might be + * raised, so it is not worth adding a new externally + * visible state for this yet. */ + that->i_setMachineState(MachineState_Paused); + break; + } + + case VMSTATE_GURU_MEDITATION: + { + AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS); + + if (that->mVMStateChangeCallbackDisabled) + break; + + /* Guru are only for running VMs */ + Assert(Global::IsOnline(that->mMachineState)); + + that->i_setMachineState(MachineState_Stuck); + break; + } + + case VMSTATE_CREATED: + { + /* + * We have to set the secret key helper interface for the VD drivers to + * get notified about missing keys. + */ + that->i_initSecretKeyIfOnAllAttachments(); + break; + } + + default: /* shut up gcc */ + break; + } +} + +/** + * Changes the clipboard mode. + * + * @returns VBox status code. + * @param aClipboardMode new clipboard mode. + */ +int Console::i_changeClipboardMode(ClipboardMode_T aClipboardMode) +{ +#ifdef VBOX_WITH_SHARED_CLIPBOARD + VMMDev *pVMMDev = m_pVMMDev; + AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER); + + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_32BIT; + + switch (aClipboardMode) + { + default: + case ClipboardMode_Disabled: + LogRel(("Shared Clipboard: Mode: Off\n")); + parm.u.uint32 = VBOX_SHCL_MODE_OFF; + break; + case ClipboardMode_GuestToHost: + LogRel(("Shared Clipboard: Mode: Guest to Host\n")); + parm.u.uint32 = VBOX_SHCL_MODE_GUEST_TO_HOST; + break; + case ClipboardMode_HostToGuest: + LogRel(("Shared Clipboard: Mode: Host to Guest\n")); + parm.u.uint32 = VBOX_SHCL_MODE_HOST_TO_GUEST; + break; + case ClipboardMode_Bidirectional: + LogRel(("Shared Clipboard: Mode: Bidirectional\n")); + parm.u.uint32 = VBOX_SHCL_MODE_BIDIRECTIONAL; + break; + } + + int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_MODE, 1, &parm); + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Error changing mode: %Rrc\n", vrc)); + + return vrc; +#else + RT_NOREF(aClipboardMode); + return VERR_NOT_IMPLEMENTED; +#endif +} + +/** + * Changes the clipboard file transfer mode. + * + * @returns VBox status code. + * @param aEnabled Whether clipboard file transfers are enabled or not. + */ +int Console::i_changeClipboardFileTransferMode(bool aEnabled) +{ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + VMMDev *pVMMDev = m_pVMMDev; + AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER); + + VBOXHGCMSVCPARM parm; + RT_ZERO(parm); + + parm.type = VBOX_HGCM_SVC_PARM_32BIT; + parm.u.uint32 = aEnabled ? VBOX_SHCL_TRANSFER_MODE_ENABLED : VBOX_SHCL_TRANSFER_MODE_DISABLED; + + int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1 /* cParms */, &parm); + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Error changing file transfer mode: %Rrc\n", vrc)); + + return vrc; +#else + RT_NOREF(aEnabled); + return VERR_NOT_IMPLEMENTED; +#endif +} + +/** + * Changes the drag and drop mode. + * + * @param aDnDMode new drag and drop mode. + */ +int Console::i_changeDnDMode(DnDMode_T aDnDMode) +{ + VMMDev *pVMMDev = m_pVMMDev; + AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER); + + VBOXHGCMSVCPARM parm; + RT_ZERO(parm); + parm.type = VBOX_HGCM_SVC_PARM_32BIT; + + switch (aDnDMode) + { + default: + case DnDMode_Disabled: + LogRel(("Drag and drop mode: Off\n")); + parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_OFF; + break; + case DnDMode_GuestToHost: + LogRel(("Drag and drop mode: Guest to Host\n")); + parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST; + break; + case DnDMode_HostToGuest: + LogRel(("Drag and drop mode: Host to Guest\n")); + parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST; + break; + case DnDMode_Bidirectional: + LogRel(("Drag and drop mode: Bidirectional\n")); + parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL; + break; + } + + int rc = pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", DragAndDropSvc::HOST_DND_FN_SET_MODE, 1 /* cParms */, &parm); + if (RT_FAILURE(rc)) + LogRel(("Error changing drag and drop mode: %Rrc\n", rc)); + + return rc; +} + +#ifdef VBOX_WITH_USB +/** + * @interface_method_impl{REMOTEUSBIF,pfnQueryRemoteUsbBackend} + */ +/*static*/ DECLCALLBACK(PREMOTEUSBCALLBACK) +Console::i_usbQueryRemoteUsbBackend(void *pvUser, PCRTUUID pUuid, uint32_t idClient) +{ + Console *pConsole = (Console *)pvUser; + + AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS); + + Guid const uuid(*pUuid); + return (PREMOTEUSBCALLBACK)pConsole->i_consoleVRDPServer()->USBBackendRequestPointer(idClient, &uuid); +} + + +/** + * Sends a request to VMM to attach the given host device. + * After this method succeeds, the attached device will appear in the + * mUSBDevices collection. + * + * @param aHostDevice device to attach + * + * @note Synchronously calls EMT. + */ +HRESULT Console::i_attachUSBDevice(IUSBDevice *aHostDevice, ULONG aMaskedIfs, const Utf8Str &aCaptureFilename) +{ + AssertReturn(aHostDevice, E_FAIL); + AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL); + + HRESULT hrc; + + /* + * Get the address and the Uuid, and call the pfnCreateProxyDevice roothub + * method in EMT (using usbAttachCallback()). + */ + Bstr bstrAddress; + hrc = aHostDevice->COMGETTER(Address)(bstrAddress.asOutParam()); + ComAssertComRCRetRC(hrc); + Utf8Str const Address(bstrAddress); + + Bstr id; + hrc = aHostDevice->COMGETTER(Id)(id.asOutParam()); + ComAssertComRCRetRC(hrc); + Guid const uuid(id); + + BOOL fRemote = FALSE; + hrc = aHostDevice->COMGETTER(Remote)(&fRemote); + ComAssertComRCRetRC(hrc); + + Bstr bstrBackend; + hrc = aHostDevice->COMGETTER(Backend)(bstrBackend.asOutParam()); + ComAssertComRCRetRC(hrc); + Utf8Str const strBackend(bstrBackend); + + /* Get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + LogFlowThisFunc(("Proxying USB device '%s' {%RTuuid}...\n", Address.c_str(), uuid.raw())); + + PCFGMNODE pRemoteCfg = NULL; + if (fRemote) + { + RemoteUSBDevice *pRemoteUSBDevice = static_cast<RemoteUSBDevice *>(aHostDevice); + + pRemoteCfg = mpVMM->pfnCFGMR3CreateTree(ptrVM.rawUVM()); + if (pRemoteCfg) + { + int vrc = mpVMM->pfnCFGMR3InsertInteger(pRemoteCfg, "ClientId", pRemoteUSBDevice->clientId()); + if (RT_FAILURE(vrc)) + { + mpVMM->pfnCFGMR3DestroyTree(pRemoteCfg); + return setErrorBoth(E_FAIL, vrc, tr("Failed to create configuration for USB device.")); + } + } + else + return setErrorBoth(E_OUTOFMEMORY, VERR_NO_MEMORY, tr("Failed to allocate config tree for USB device.")); + } + + USBConnectionSpeed_T enmSpeed; + hrc = aHostDevice->COMGETTER(Speed)(&enmSpeed); + AssertComRCReturnRC(hrc); + + int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)i_usbAttachCallback, 11, + this, ptrVM.rawUVM(), ptrVM.vtable(), aHostDevice, uuid.raw(), + strBackend.c_str(), Address.c_str(), pRemoteCfg, enmSpeed, aMaskedIfs, + aCaptureFilename.isEmpty() ? NULL : aCaptureFilename.c_str()); + if (RT_SUCCESS(vrc)) + { + /* Create a OUSBDevice and add it to the device list */ + ComObjPtr<OUSBDevice> pUSBDevice; + pUSBDevice.createObject(); + hrc = pUSBDevice->init(aHostDevice); + AssertComRC(hrc); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mUSBDevices.push_back(pUSBDevice); + LogFlowFunc(("Attached device {%RTuuid}\n", pUSBDevice->i_id().raw())); + + /* notify callbacks */ + alock.release(); + i_onUSBDeviceStateChange(pUSBDevice, true /* aAttached */, NULL); + } + else + { + Log1WarningThisFunc(("Failed to create proxy device for '%s' {%RTuuid} (%Rrc)\n", Address.c_str(), uuid.raw(), vrc)); + switch (vrc) + { + case VERR_VUSB_NO_PORTS: + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to attach the USB device. (No available ports on the USB controller).")); + break; + case VERR_VUSB_USBFS_PERMISSION: + hrc = setErrorBoth(E_FAIL, vrc, tr("Not permitted to open the USB device, check usbfs options")); + break; + default: + hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to create a proxy device for the USB device. (Error: %Rrc)"), vrc); + break; + } + } + + return hrc; +} + +/** + * USB device attach callback used by AttachUSBDevice(). + * Note that AttachUSBDevice() doesn't return until this callback is executed, + * so we don't use AutoCaller and don't care about reference counters of + * interface pointers passed in. + * + * @thread EMT + * @note Locks the console object for writing. + */ +//static +DECLCALLBACK(int) +Console::i_usbAttachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, IUSBDevice *aHostDevice, PCRTUUID aUuid, + const char *pszBackend, const char *aAddress, PCFGMNODE pRemoteCfg, USBConnectionSpeed_T aEnmSpeed, + ULONG aMaskedIfs, const char *pszCaptureFilename) +{ + RT_NOREF(aHostDevice); + LogFlowFuncEnter(); + LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid)); + + AssertReturn(that && aUuid, VERR_INVALID_PARAMETER); + AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE); + + VUSBSPEED enmSpeed = VUSB_SPEED_UNKNOWN; + switch (aEnmSpeed) + { + case USBConnectionSpeed_Low: enmSpeed = VUSB_SPEED_LOW; break; + case USBConnectionSpeed_Full: enmSpeed = VUSB_SPEED_FULL; break; + case USBConnectionSpeed_High: enmSpeed = VUSB_SPEED_HIGH; break; + case USBConnectionSpeed_Super: enmSpeed = VUSB_SPEED_SUPER; break; + case USBConnectionSpeed_SuperPlus: enmSpeed = VUSB_SPEED_SUPERPLUS; break; + default: AssertFailed(); break; + } + + int vrc = pVMM->pfnPDMR3UsbCreateProxyDevice(pUVM, aUuid, pszBackend, aAddress, pRemoteCfg, + enmSpeed, aMaskedIfs, pszCaptureFilename); + LogFlowFunc(("vrc=%Rrc\n", vrc)); + LogFlowFuncLeave(); + return vrc; +} + +/** + * Sends a request to VMM to detach the given host device. After this method + * succeeds, the detached device will disappear from the mUSBDevices + * collection. + * + * @param aHostDevice device to attach + * + * @note Synchronously calls EMT. + */ +HRESULT Console::i_detachUSBDevice(const ComObjPtr<OUSBDevice> &aHostDevice) +{ + AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL); + + /* Get the VM handle. */ + SafeVMPtr ptrVM(this); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* if the device is attached, then there must at least one USB hub. */ + AssertReturn(ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM()), E_FAIL); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("Detaching USB proxy device {%RTuuid}...\n", aHostDevice->i_id().raw())); + + /* + * If this was a remote device, release the backend pointer. + * The pointer was requested in usbAttachCallback. + */ + BOOL fRemote = FALSE; + + HRESULT hrc2 = aHostDevice->COMGETTER(Remote)(&fRemote); + if (FAILED(hrc2)) + i_setErrorStatic(hrc2, "GetRemote() failed"); + + PCRTUUID pUuid = aHostDevice->i_id().raw(); + if (fRemote) + { + Guid guid(*pUuid); + i_consoleVRDPServer()->USBBackendReleasePointer(&guid); + } + + alock.release(); + int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)i_usbDetachCallback, 4, + this, ptrVM.rawUVM(), ptrVM.vtable(), pUuid); + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("Detached device {%RTuuid}\n", pUuid)); + + /* notify callbacks */ + i_onUSBDeviceStateChange(aHostDevice, false /* aAttached */, NULL); + } + + ComAssertRCRet(vrc, E_FAIL); + + return S_OK; +} + +/** + * USB device detach callback used by DetachUSBDevice(). + * + * Note that DetachUSBDevice() doesn't return until this callback is executed, + * so we don't use AutoCaller and don't care about reference counters of + * interface pointers passed in. + * + * @thread EMT + */ +//static +DECLCALLBACK(int) +Console::i_usbDetachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, PCRTUUID aUuid) +{ + LogFlowFuncEnter(); + LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid)); + + AssertReturn(that && aUuid, VERR_INVALID_PARAMETER); + AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE); + + int vrc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, aUuid); + + LogFlowFunc(("vrc=%Rrc\n", vrc)); + LogFlowFuncLeave(); + return vrc; +} +#endif /* VBOX_WITH_USB */ + +/* Note: FreeBSD needs this whether netflt is used or not. */ +#if ((defined(RT_OS_LINUX) && !defined(VBOX_WITH_NETFLT)) || defined(RT_OS_FREEBSD)) + +/** + * Helper function to handle host interface device creation and attachment. + * + * @param networkAdapter the network adapter which attachment should be reset + * @return COM status code + * + * @note The caller must lock this object for writing. + * + * @todo Move this back into the driver! + */ +HRESULT Console::i_attachToTapInterface(INetworkAdapter *networkAdapter) +{ + LogFlowThisFunc(("\n")); + /* sanity check */ + AssertReturn(isWriteLockOnCurrentThread(), E_FAIL); + +# ifdef VBOX_STRICT + /* paranoia */ + NetworkAttachmentType_T attachment; + networkAdapter->COMGETTER(AttachmentType)(&attachment); + Assert(attachment == NetworkAttachmentType_Bridged); +# endif /* VBOX_STRICT */ + + HRESULT rc = S_OK; + + ULONG slot = 0; + rc = networkAdapter->COMGETTER(Slot)(&slot); + AssertComRC(rc); + +# ifdef RT_OS_LINUX + /* + * Allocate a host interface device + */ + int vrc = RTFileOpen(&maTapFD[slot], "/dev/net/tun", + RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_INHERIT); + if (RT_SUCCESS(vrc)) + { + /* + * Set/obtain the tap interface. + */ + struct ifreq IfReq; + RT_ZERO(IfReq); + /* The name of the TAP interface we are using */ + Bstr tapDeviceName; + rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam()); + if (FAILED(rc)) + tapDeviceName.setNull(); /* Is this necessary? */ + if (tapDeviceName.isEmpty()) + { + LogRel(("No TAP device name was supplied.\n")); + rc = setError(E_FAIL, tr("No TAP device name was supplied for the host networking interface")); + } + + if (SUCCEEDED(rc)) + { + /* If we are using a static TAP device then try to open it. */ + Utf8Str str(tapDeviceName); + RTStrCopy(IfReq.ifr_name, sizeof(IfReq.ifr_name), str.c_str()); /** @todo bitch about names which are too long... */ + IfReq.ifr_flags = IFF_TAP | IFF_NO_PI; + vrc = ioctl(RTFileToNative(maTapFD[slot]), TUNSETIFF, &IfReq); + if (vrc != 0) + { + LogRel(("Failed to open the host network interface %ls\n", tapDeviceName.raw())); + rc = setErrorBoth(E_FAIL, vrc, tr("Failed to open the host network interface %ls"), tapDeviceName.raw()); + } + } + if (SUCCEEDED(rc)) + { + /* + * Make it pollable. + */ + if (fcntl(RTFileToNative(maTapFD[slot]), F_SETFL, O_NONBLOCK) != -1) + { + Log(("i_attachToTapInterface: %RTfile %ls\n", maTapFD[slot], tapDeviceName.raw())); + /* + * Here is the right place to communicate the TAP file descriptor and + * the host interface name to the server if/when it becomes really + * necessary. + */ + maTAPDeviceName[slot] = tapDeviceName; + vrc = VINF_SUCCESS; + } + else + { + int iErr = errno; + + LogRel(("Configuration error: Failed to configure /dev/net/tun non blocking. Error: %s\n", strerror(iErr))); + vrc = VERR_HOSTIF_BLOCKING; + rc = setErrorBoth(E_FAIL, vrc, tr("could not set up the host networking device for non blocking access: %s"), + strerror(errno)); + } + } + } + else + { + LogRel(("Configuration error: Failed to open /dev/net/tun rc=%Rrc\n", vrc)); + switch (vrc) + { + case VERR_ACCESS_DENIED: + /* will be handled by our caller */ + rc = E_ACCESSDENIED; + break; + default: + rc = setErrorBoth(E_FAIL, vrc, tr("Could not set up the host networking device: %Rrc"), vrc); + break; + } + } + +# elif defined(RT_OS_FREEBSD) + /* + * Set/obtain the tap interface. + */ + /* The name of the TAP interface we are using */ + Bstr tapDeviceName; + rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam()); + if (FAILED(rc)) + tapDeviceName.setNull(); /* Is this necessary? */ + if (tapDeviceName.isEmpty()) + { + LogRel(("No TAP device name was supplied.\n")); + rc = setError(E_FAIL, tr("No TAP device name was supplied for the host networking interface")); + } + char szTapdev[1024] = "/dev/"; + /* If we are using a static TAP device then try to open it. */ + Utf8Str str(tapDeviceName); + if (str.length() + strlen(szTapdev) <= sizeof(szTapdev)) + strcat(szTapdev, str.c_str()); + else + memcpy(szTapdev + strlen(szTapdev), str.c_str(), + sizeof(szTapdev) - strlen(szTapdev) - 1); /** @todo bitch about names which are too long... */ + int vrc = RTFileOpen(&maTapFD[slot], szTapdev, + RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_INHERIT | RTFILE_O_NON_BLOCK); + + if (RT_SUCCESS(vrc)) + maTAPDeviceName[slot] = tapDeviceName; + else + { + switch (vrc) + { + case VERR_ACCESS_DENIED: + /* will be handled by our caller */ + rc = E_ACCESSDENIED; + break; + default: + rc = setErrorBoth(E_FAIL, vrc, tr("Failed to open the host network interface %ls"), tapDeviceName.raw()); + break; + } + } +# else +# error "huh?" +# endif + /* in case of failure, cleanup. */ + if (RT_FAILURE(vrc) && SUCCEEDED(rc)) + { + LogRel(("General failure attaching to host interface\n")); + rc = setErrorBoth(E_FAIL, vrc, tr("General failure attaching to host interface")); + } + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + return rc; +} + + +/** + * Helper function to handle detachment from a host interface + * + * @param networkAdapter the network adapter which attachment should be reset + * @return COM status code + * + * @note The caller must lock this object for writing. + * + * @todo Move this back into the driver! + */ +HRESULT Console::i_detachFromTapInterface(INetworkAdapter *networkAdapter) +{ + /* sanity check */ + LogFlowThisFunc(("\n")); + AssertReturn(isWriteLockOnCurrentThread(), E_FAIL); + + HRESULT rc = S_OK; +# ifdef VBOX_STRICT + /* paranoia */ + NetworkAttachmentType_T attachment; + networkAdapter->COMGETTER(AttachmentType)(&attachment); + Assert(attachment == NetworkAttachmentType_Bridged); +# endif /* VBOX_STRICT */ + + ULONG slot = 0; + rc = networkAdapter->COMGETTER(Slot)(&slot); + AssertComRC(rc); + + /* is there an open TAP device? */ + if (maTapFD[slot] != NIL_RTFILE) + { + /* + * Close the file handle. + */ + Bstr tapDeviceName, tapTerminateApplication; + bool isStatic = true; + rc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam()); + if (FAILED(rc) || tapDeviceName.isEmpty()) + { + /* If the name is empty, this is a dynamic TAP device, so close it now, + so that the termination script can remove the interface. Otherwise we still + need the FD to pass to the termination script. */ + isStatic = false; + int rcVBox = RTFileClose(maTapFD[slot]); + AssertRC(rcVBox); + maTapFD[slot] = NIL_RTFILE; + } + if (isStatic) + { + /* If we are using a static TAP device, we close it now, after having called the + termination script. */ + int rcVBox = RTFileClose(maTapFD[slot]); + AssertRC(rcVBox); + } + /* the TAP device name and handle are no longer valid */ + maTapFD[slot] = NIL_RTFILE; + maTAPDeviceName[slot] = ""; + } + LogFlowThisFunc(("returning %d\n", rc)); + return rc; +} + +#endif /* (RT_OS_LINUX || RT_OS_FREEBSD) && !VBOX_WITH_NETFLT */ + +/** + * Called at power down to terminate host interface networking. + * + * @note The caller must lock this object for writing. + */ +HRESULT Console::i_powerDownHostInterfaces() +{ + LogFlowThisFunc(("\n")); + + /* sanity check */ + AssertReturn(isWriteLockOnCurrentThread(), E_FAIL); + + /* + * host interface termination handling + */ + HRESULT rc = S_OK; + ComPtr<IVirtualBox> pVirtualBox; + mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam()); + ComPtr<ISystemProperties> pSystemProperties; + if (pVirtualBox) + pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam()); + ChipsetType_T chipsetType = ChipsetType_PIIX3; + mMachine->COMGETTER(ChipsetType)(&chipsetType); + ULONG maxNetworkAdapters = 0; + if (pSystemProperties) + pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); + + for (ULONG slot = 0; slot < maxNetworkAdapters; slot++) + { + ComPtr<INetworkAdapter> pNetworkAdapter; + rc = mMachine->GetNetworkAdapter(slot, pNetworkAdapter.asOutParam()); + if (FAILED(rc)) break; + + BOOL enabled = FALSE; + pNetworkAdapter->COMGETTER(Enabled)(&enabled); + if (!enabled) + continue; + + NetworkAttachmentType_T attachment; + pNetworkAdapter->COMGETTER(AttachmentType)(&attachment); + if (attachment == NetworkAttachmentType_Bridged) + { +#if ((defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)) && !defined(VBOX_WITH_NETFLT)) + HRESULT rc2 = i_detachFromTapInterface(pNetworkAdapter); + if (FAILED(rc2) && SUCCEEDED(rc)) + rc = rc2; +#endif /* (RT_OS_LINUX || RT_OS_FREEBSD) && !VBOX_WITH_NETFLT */ + } + } + + return rc; +} + + +/** + * Process callback handler for VMR3LoadFromFile, VMR3LoadFromStream, VMR3Save + * and VMR3Teleport. + * + * @param pUVM The user mode VM handle. + * @param uPercent Completion percentage (0-100). + * @param pvUser Pointer to an IProgress instance. + * @return VINF_SUCCESS. + */ +/*static*/ +DECLCALLBACK(int) Console::i_stateProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser) +{ + IProgress *pProgress = static_cast<IProgress *>(pvUser); + + /* update the progress object */ + if (pProgress) + { + ComPtr<IInternalProgressControl> pProgressControl(pProgress); + AssertReturn(!!pProgressControl, VERR_INVALID_PARAMETER); + pProgressControl->SetCurrentOperationProgress(uPercent); + } + + NOREF(pUVM); + return VINF_SUCCESS; +} + +/** + * @copydoc FNVMATERROR + * + * @remarks Might be some tiny serialization concerns with access to the string + * object here... + */ +/*static*/ DECLCALLBACK(void) +Console::i_genericVMSetErrorCallback(PUVM pUVM, void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list args) +{ + RT_SRC_POS_NOREF(); + Utf8Str *pErrorText = (Utf8Str *)pvUser; + AssertPtr(pErrorText); + + /* We ignore RT_SRC_POS_DECL arguments to avoid confusion of end-users. */ + va_list va2; + va_copy(va2, args); + + /* Append to any the existing error message. */ + try + { + if (pErrorText->length()) + pErrorText->appendPrintf(".\n%N (%Rrc)", pszFormat, &va2, rc, rc); + else + pErrorText->printf("%N (%Rrc)", pszFormat, &va2, rc, rc); + } + catch (std::bad_alloc &) + { + } + + va_end(va2); + + NOREF(pUVM); +} + +/** + * VM runtime error callback function (FNVMATRUNTIMEERROR). + * + * See VMSetRuntimeError for the detailed description of parameters. + * + * @param pUVM The user mode VM handle. Ignored, so passing NULL + * is fine. + * @param pvUser The user argument, pointer to the Console instance. + * @param fFlags The action flags. See VMSETRTERR_FLAGS_*. + * @param pszErrorId Error ID string. + * @param pszFormat Error message format string. + * @param va Error message arguments. + * @thread EMT. + */ +/* static */ DECLCALLBACK(void) +Console::i_atVMRuntimeErrorCallback(PUVM pUVM, void *pvUser, uint32_t fFlags, + const char *pszErrorId, const char *pszFormat, va_list va) +{ + bool const fFatal = !!(fFlags & VMSETRTERR_FLAGS_FATAL); + LogFlowFuncEnter(); + + Console *that = static_cast<Console *>(pvUser); + AssertReturnVoid(that); + + Utf8Str message(pszFormat, va); + + LogRel(("Console: VM runtime error: fatal=%RTbool, errorID=%s message=\"%s\"\n", fFatal, pszErrorId, message.c_str())); + try + { + that->i_onRuntimeError(BOOL(fFatal), Bstr(pszErrorId).raw(), Bstr(message).raw()); + } + catch (std::bad_alloc &) + { + } + LogFlowFuncLeave(); NOREF(pUVM); +} + +/** + * Captures USB devices that match filters of the VM. + * Called at VM startup. + * + * @param pUVM The VM handle. + */ +HRESULT Console::i_captureUSBDevices(PUVM pUVM) +{ + RT_NOREF(pUVM); + LogFlowThisFunc(("\n")); + + /* sanity check */ + AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* If the machine has a USB controller, ask the USB proxy service to + * capture devices */ + if (mfVMHasUsbController) + { + /* release the lock before calling Host in VBoxSVC since Host may call + * us back from under its lock (e.g. onUSBDeviceAttach()) which would + * produce an inter-process dead-lock otherwise. */ + alock.release(); + + HRESULT hrc = mControl->AutoCaptureUSBDevices(); + ComAssertComRCRetRC(hrc); + } + + return S_OK; +} + + +/** + * Detach all USB device which are attached to the VM for the + * purpose of clean up and such like. + */ +void Console::i_detachAllUSBDevices(bool aDone) +{ + LogFlowThisFunc(("aDone=%RTbool\n", aDone)); + + /* sanity check */ + AssertReturnVoid(!isWriteLockOnCurrentThread()); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mUSBDevices.clear(); + + /* release the lock before calling Host in VBoxSVC since Host may call + * us back from under its lock (e.g. onUSBDeviceAttach()) which would + * produce an inter-process dead-lock otherwise. */ + alock.release(); + + mControl->DetachAllUSBDevices(aDone); +} + +/** + * @note Locks this object for writing. + */ +void Console::i_processRemoteUSBDevices(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevList, uint32_t cbDevList, bool fDescExt) +{ + LogFlowThisFuncEnter(); + LogFlowThisFunc(("u32ClientId = %d, pDevList=%p, cbDevList = %d, fDescExt = %d\n", + u32ClientId, pDevList, cbDevList, fDescExt)); + + AutoCaller autoCaller(this); + if (!autoCaller.isOk()) + { + /* Console has been already uninitialized, deny request */ + AssertMsgFailed(("Console is already uninitialized\n")); + LogFlowThisFunc(("Console is already uninitialized\n")); + LogFlowThisFuncLeave(); + return; + } + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Mark all existing remote USB devices as dirty. + */ + for (RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin(); + it != mRemoteUSBDevices.end(); + ++it) + { + (*it)->dirty(true); + } + + /* + * Process the pDevList and add devices those are not already in the mRemoteUSBDevices list. + */ + /** @todo (sunlover) REMOTE_USB Strict validation of the pDevList. */ + VRDEUSBDEVICEDESC *e = pDevList; + + /* The cbDevList condition must be checked first, because the function can + * receive pDevList = NULL and cbDevList = 0 on client disconnect. + */ + while (cbDevList >= 2 && e->oNext) + { + /* Sanitize incoming strings in case they aren't valid UTF-8. */ + if (e->oManufacturer) + RTStrPurgeEncoding((char *)e + e->oManufacturer); + if (e->oProduct) + RTStrPurgeEncoding((char *)e + e->oProduct); + if (e->oSerialNumber) + RTStrPurgeEncoding((char *)e + e->oSerialNumber); + + LogFlowThisFunc(("vendor %04x, product %04x, name = %s\n", + e->idVendor, e->idProduct, e->oProduct ? (char *)e + e->oProduct : "")); + + bool fNewDevice = true; + + for (RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin(); + it != mRemoteUSBDevices.end(); + ++it) + { + if ( (*it)->devId() == e->id + && (*it)->clientId() == u32ClientId) + { + /* The device is already in the list. */ + (*it)->dirty(false); + fNewDevice = false; + break; + } + } + + if (fNewDevice) + { + LogRel(("Remote USB: ++++ Vendor %04X. Product %04X. Name = [%s].\n", + e->idVendor, e->idProduct, e->oProduct? (char *)e + e->oProduct: "")); + + /* Create the device object and add the new device to list. */ + ComObjPtr<RemoteUSBDevice> pUSBDevice; + pUSBDevice.createObject(); + pUSBDevice->init(u32ClientId, e, fDescExt); + + mRemoteUSBDevices.push_back(pUSBDevice); + + /* Check if the device is ok for current USB filters. */ + BOOL fMatched = FALSE; + ULONG fMaskedIfs = 0; + HRESULT hrc = mControl->RunUSBDeviceFilters(pUSBDevice, &fMatched, &fMaskedIfs); + + AssertComRC(hrc); + + LogFlowThisFunc(("USB filters return %d %#x\n", fMatched, fMaskedIfs)); + + if (fMatched) + { + alock.release(); + hrc = i_onUSBDeviceAttach(pUSBDevice, NULL, fMaskedIfs, Utf8Str()); + alock.acquire(); + + /// @todo (r=dmik) warning reporting subsystem + + if (hrc == S_OK) + { + LogFlowThisFunc(("Device attached\n")); + pUSBDevice->captured(true); + } + } + } + + if (cbDevList < e->oNext) + { + Log1WarningThisFunc(("cbDevList %d > oNext %d\n", cbDevList, e->oNext)); + break; + } + + cbDevList -= e->oNext; + + e = (VRDEUSBDEVICEDESC *)((uint8_t *)e + e->oNext); + } + + /* + * Remove dirty devices, that is those which are not reported by the server anymore. + */ + for (;;) + { + ComObjPtr<RemoteUSBDevice> pUSBDevice; + + RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin(); + while (it != mRemoteUSBDevices.end()) + { + if ((*it)->dirty()) + { + pUSBDevice = *it; + break; + } + + ++it; + } + + if (!pUSBDevice) + { + break; + } + + USHORT vendorId = 0; + pUSBDevice->COMGETTER(VendorId)(&vendorId); + + USHORT productId = 0; + pUSBDevice->COMGETTER(ProductId)(&productId); + + Bstr product; + pUSBDevice->COMGETTER(Product)(product.asOutParam()); + + LogRel(("Remote USB: ---- Vendor %04x. Product %04x. Name = [%ls].\n", vendorId, productId, product.raw())); + + /* Detach the device from VM. */ + if (pUSBDevice->captured()) + { + Bstr uuid; + pUSBDevice->COMGETTER(Id)(uuid.asOutParam()); + alock.release(); + i_onUSBDeviceDetach(uuid.raw(), NULL); + alock.acquire(); + } + + /* And remove it from the list. */ + mRemoteUSBDevices.erase(it); + } + + LogFlowThisFuncLeave(); +} + + +/** + * Worker called by VMPowerUpTask::handler to start the VM (also from saved + * state) and track progress. + * + * @param pTask The power up task. + * + * @note Locks the Console object for writing. + */ +/*static*/ +void Console::i_powerUpThreadTask(VMPowerUpTask *pTask) +{ + LogFlowFuncEnter(); + + AssertReturnVoid(pTask); + AssertReturnVoid(!pTask->mConsole.isNull()); + AssertReturnVoid(!pTask->mProgress.isNull()); + + VirtualBoxBase::initializeComForThread(); + + HRESULT rc = S_OK; + int vrc = VINF_SUCCESS; + + /* Set up a build identifier so that it can be seen from core dumps what + * exact build was used to produce the core. */ + static char s_szBuildID[48]; + RTStrPrintf(s_szBuildID, sizeof(s_szBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s", + "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D"); + + ComObjPtr<Console> pConsole = pTask->mConsole; + + /* Note: no need to use AutoCaller because VMPowerUpTask does that */ + + /* The lock is also used as a signal from the task initiator (which + * releases it only after RTThreadCreate()) that we can start the job */ + AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS); + + /* sanity */ + Assert(pConsole->mpUVM == NULL); + + try + { + // Create the VMM device object, which starts the HGCM thread; do this only + // once for the console, for the pathological case that the same console + // object is used to power up a VM twice. + if (!pConsole->m_pVMMDev) + { + pConsole->m_pVMMDev = new VMMDev(pConsole); + AssertReturnVoid(pConsole->m_pVMMDev); + } + + /* wait for auto reset ops to complete so that we can successfully lock + * the attached hard disks by calling LockMedia() below */ + for (VMPowerUpTask::ProgressList::const_iterator + it = pTask->hardDiskProgresses.begin(); + it != pTask->hardDiskProgresses.end(); ++it) + { + HRESULT rc2 = (*it)->WaitForCompletion(-1); + AssertComRC(rc2); + + rc = pTask->mProgress->SetNextOperation(BstrFmt(tr("Disk Image Reset Operation - Immutable Image")).raw(), 1); + AssertComRCReturnVoid(rc); + } + + /* + * Lock attached media. This method will also check their accessibility. + * If we're a teleporter, we'll have to postpone this action so we can + * migrate between local processes. + * + * Note! The media will be unlocked automatically by + * SessionMachine::i_setMachineState() when the VM is powered down. + */ + if (!pTask->mTeleporterEnabled) + { + rc = pConsole->mControl->LockMedia(); + if (FAILED(rc)) throw rc; + } + + /* Create the VRDP server. In case of headless operation, this will + * also create the framebuffer, required at VM creation. + */ + ConsoleVRDPServer *server = pConsole->i_consoleVRDPServer(); + Assert(server); + + /* Does VRDP server call Console from the other thread? + * Not sure (and can change), so release the lock just in case. + */ + alock.release(); + vrc = server->Launch(); + alock.acquire(); + + if (vrc != VINF_SUCCESS) + { + Utf8Str errMsg = pConsole->VRDPServerErrorToMsg(vrc); + if ( RT_FAILURE(vrc) + && vrc != VERR_NET_ADDRESS_IN_USE) /* not fatal */ + throw i_setErrorStaticBoth(E_FAIL, vrc, errMsg.c_str()); + } + + ComPtr<IMachine> pMachine = pConsole->i_machine(); + ULONG cCpus = 1; + pMachine->COMGETTER(CPUCount)(&cCpus); + + VMProcPriority_T enmVMPriority = VMProcPriority_Default; + pMachine->COMGETTER(VMProcessPriority)(&enmVMPriority); + + /* + * Create the VM + * + * Note! Release the lock since EMT will call Console. It's safe because + * mMachineState is either Starting or Restoring state here. + */ + alock.release(); + + if (enmVMPriority != VMProcPriority_Default) + pConsole->i_onVMProcessPriorityChange(enmVMPriority); + + PCVMMR3VTABLE pVMM = pConsole->mpVMM; + PVM pVM = NULL; + vrc = pVMM->pfnVMR3Create(cCpus, + pConsole->mpVmm2UserMethods, + Console::i_genericVMSetErrorCallback, + &pTask->mErrorMsg, + pTask->mpfnConfigConstructor, + static_cast<Console *>(pConsole), + &pVM, NULL); + alock.acquire(); + if (RT_SUCCESS(vrc)) + { + do /* break "loop" */ + { + /* + * Register our load/save state file handlers + */ + vrc = pVMM->pfnSSMR3RegisterExternal(pConsole->mpUVM, sSSMConsoleUnit, 0 /*iInstance*/, + CONSOLE_SAVED_STATE_VERSION, 0 /* cbGuess */, + NULL, NULL, NULL, + NULL, i_saveStateFileExec, NULL, + NULL, i_loadStateFileExec, NULL, + static_cast<Console *>(pConsole)); + AssertRCBreak(vrc); + + vrc = static_cast<Console *>(pConsole)->i_getDisplay()->i_registerSSM(pConsole->mpUVM); + AssertRC(vrc); + if (RT_FAILURE(vrc)) + break; + + /* + * Synchronize debugger settings + */ + MachineDebugger *machineDebugger = pConsole->i_getMachineDebugger(); + if (machineDebugger) + machineDebugger->i_flushQueuedSettings(); + + /* + * Shared Folders + */ + if (pConsole->m_pVMMDev->isShFlActive()) + { + /* Does the code below call Console from the other thread? + * Not sure, so release the lock just in case. */ + alock.release(); + + for (SharedFolderDataMap::const_iterator it = pTask->mSharedFolders.begin(); + it != pTask->mSharedFolders.end(); + ++it) + { + const SharedFolderData &d = it->second; + rc = pConsole->i_createSharedFolder(it->first, d); + if (FAILED(rc)) + { + ErrorInfoKeeper eik; + pConsole->i_atVMRuntimeErrorCallbackF(0, "BrokenSharedFolder", + N_("The shared folder '%s' could not be set up: %ls.\n" + "The shared folder setup will not be complete. It is recommended to power down the virtual " + "machine and fix the shared folder settings while the machine is not running"), + it->first.c_str(), eik.getText().raw()); + } + } + if (FAILED(rc)) + rc = S_OK; // do not fail with broken shared folders + + /* acquire the lock again */ + alock.acquire(); + } + +#ifdef VBOX_WITH_AUDIO_VRDE + /* + * Attach the VRDE audio driver. + */ + if (pConsole->i_getVRDEServer()) + { + BOOL fVRDEEnabled = FALSE; + rc = pConsole->i_getVRDEServer()->COMGETTER(Enabled)(&fVRDEEnabled); + AssertComRCBreak(rc, RT_NOTHING); + + if ( fVRDEEnabled + && pConsole->mAudioVRDE) + pConsole->mAudioVRDE->doAttachDriverViaEmt(pConsole->mpUVM, pVMM, &alock); + } +#endif + + /* + * Enable client connections to the VRDP server. + */ + pConsole->i_consoleVRDPServer()->EnableConnections(); + +#ifdef VBOX_WITH_RECORDING + /* + * Enable recording if configured. + */ + BOOL fRecordingEnabled = FALSE; + { + ComPtr<IRecordingSettings> ptrRecordingSettings; + rc = pConsole->mMachine->COMGETTER(RecordingSettings)(ptrRecordingSettings.asOutParam()); + AssertComRCBreak(rc, RT_NOTHING); + + rc = ptrRecordingSettings->COMGETTER(Enabled)(&fRecordingEnabled); + AssertComRCBreak(rc, RT_NOTHING); + } + if (fRecordingEnabled) + { + vrc = pConsole->i_recordingEnable(fRecordingEnabled, &alock); + if (RT_SUCCESS(vrc)) + ::FireRecordingChangedEvent(pConsole->mEventSource); + else + { + LogRel(("Recording: Failed with %Rrc on VM power up\n", vrc)); + vrc = VINF_SUCCESS; /* do not fail with broken recording */ + } + } +#endif + + /* release the lock before a lengthy operation */ + alock.release(); + + /* + * Capture USB devices. + */ + rc = pConsole->i_captureUSBDevices(pConsole->mpUVM); + if (FAILED(rc)) + { + alock.acquire(); + break; + } + + /* + * Load saved state? + */ + if (pTask->mSavedStateFile.length()) + { + LogFlowFunc(("Restoring saved state from '%s'...\n", pTask->mSavedStateFile.c_str())); + +#ifdef VBOX_WITH_FULL_VM_ENCRYPTION + SsmStream ssmStream(pConsole, pVMM, pTask->m_pKeyStore, pTask->mKeyId, pTask->mKeyStore); + + vrc = ssmStream.open(pTask->mSavedStateFile.c_str()); + if (RT_SUCCESS(vrc)) + { + PCSSMSTRMOPS pStreamOps; + void *pvStreamOpsUser; + + vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser); + if (RT_SUCCESS(vrc)) + vrc = pVMM->pfnVMR3LoadFromStream(pConsole->mpUVM, + pStreamOps, pvStreamOpsUser, + Console::i_stateProgressCallback, + static_cast<IProgress *>(pTask->mProgress), + false /*fTeleporting*/); + } +#else + vrc = pVMM->pfnVMR3LoadFromFile(pConsole->mpUVM, + pTask->mSavedStateFile.c_str(), + Console::i_stateProgressCallback, + static_cast<IProgress *>(pTask->mProgress)); +#endif + if (RT_SUCCESS(vrc)) + { + if (pTask->mStartPaused) + /* done */ + pConsole->i_setMachineState(MachineState_Paused); + else + { + /* Start/Resume the VM execution */ +#ifdef VBOX_WITH_EXTPACK + vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM); +#endif + if (RT_SUCCESS(vrc)) + vrc = pVMM->pfnVMR3Resume(pConsole->mpUVM, VMRESUMEREASON_STATE_RESTORED); + AssertLogRelRC(vrc); + } + } + + /* Power off in case we failed loading or resuming the VM */ + if (RT_FAILURE(vrc)) + { + int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2); +#ifdef VBOX_WITH_EXTPACK + pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM); +#endif + } + } + else if (pTask->mTeleporterEnabled) + { + /* -> ConsoleImplTeleporter.cpp */ + bool fPowerOffOnFailure; + rc = pConsole->i_teleporterTrg(pConsole->mpUVM, pConsole->mpVMM, pMachine, &pTask->mErrorMsg, + pTask->mStartPaused, pTask->mProgress, &fPowerOffOnFailure); + if (FAILED(rc) && fPowerOffOnFailure) + { + ErrorInfoKeeper eik; + int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2); +#ifdef VBOX_WITH_EXTPACK + pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM); +#endif + } + } + else if (pTask->mStartPaused) + /* done */ + pConsole->i_setMachineState(MachineState_Paused); + else + { + /* Power on the VM (i.e. start executing) */ +#ifdef VBOX_WITH_EXTPACK + vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM); +#endif + if (RT_SUCCESS(vrc)) + vrc = pVMM->pfnVMR3PowerOn(pConsole->mpUVM); + AssertLogRelRC(vrc); + } + + /* acquire the lock again */ + alock.acquire(); + } + while (0); + + /* On failure, destroy the VM */ + if (FAILED(rc) || RT_FAILURE(vrc)) + { + /* preserve existing error info */ + ErrorInfoKeeper eik; + + /* powerDown() will call VMR3Destroy() and do all necessary + * cleanup (VRDP, USB devices) */ + alock.release(); + HRESULT rc2 = pConsole->i_powerDown(); + alock.acquire(); + AssertComRC(rc2); + } + else + { + /* + * Deregister the VMSetError callback. This is necessary as the + * pfnVMAtError() function passed to VMR3Create() is supposed to + * be sticky but our error callback isn't. + */ + alock.release(); + pVMM->pfnVMR3AtErrorDeregister(pConsole->mpUVM, Console::i_genericVMSetErrorCallback, &pTask->mErrorMsg); + /** @todo register another VMSetError callback? */ + alock.acquire(); + } + } + else + { + /* + * If VMR3Create() failed it has released the VM memory. + */ + if (pConsole->m_pVMMDev) + { + alock.release(); /* just to be on the safe side... */ + pConsole->m_pVMMDev->hgcmShutdown(true /*fUvmIsInvalid*/); + alock.acquire(); + } + pVMM->pfnVMR3ReleaseUVM(pConsole->mpUVM); + pConsole->mpUVM = NULL; + } + + if (SUCCEEDED(rc) && RT_FAILURE(vrc)) + { + /* If VMR3Create() or one of the other calls in this function fail, + * an appropriate error message has been set in pTask->mErrorMsg. + * However since that happens via a callback, the rc status code in + * this function is not updated. + */ + if (!pTask->mErrorMsg.length()) + { + /* If the error message is not set but we've got a failure, + * convert the VBox status code into a meaningful error message. + * This becomes unused once all the sources of errors set the + * appropriate error message themselves. + */ + AssertMsgFailed(("Missing error message during powerup for status code %Rrc\n", vrc)); + pTask->mErrorMsg = Utf8StrFmt(tr("Failed to start VM execution (%Rrc)"), vrc); + } + + /* Set the error message as the COM error. + * Progress::notifyComplete() will pick it up later. */ + throw i_setErrorStaticBoth(E_FAIL, vrc, pTask->mErrorMsg.c_str()); + } + } + catch (HRESULT aRC) { rc = aRC; } + + if ( pConsole->mMachineState == MachineState_Starting + || pConsole->mMachineState == MachineState_Restoring + || pConsole->mMachineState == MachineState_TeleportingIn + ) + { + /* We are still in the Starting/Restoring state. This means one of: + * + * 1) we failed before VMR3Create() was called; + * 2) VMR3Create() failed. + * + * In both cases, there is no need to call powerDown(), but we still + * need to go back to the PoweredOff/Saved state. Reuse + * vmstateChangeCallback() for that purpose. + */ + + /* preserve existing error info */ + ErrorInfoKeeper eik; + + Assert(pConsole->mpUVM == NULL); + i_vmstateChangeCallback(NULL, pConsole->mpVMM, VMSTATE_TERMINATED, VMSTATE_CREATING, pConsole); + } + + /* + * Evaluate the final result. Note that the appropriate mMachineState value + * is already set by vmstateChangeCallback() in all cases. + */ + + /* release the lock, don't need it any more */ + alock.release(); + + if (SUCCEEDED(rc)) + { + /* Notify the progress object of the success */ + pTask->mProgress->i_notifyComplete(S_OK); + } + else + { + /* The progress object will fetch the current error info */ + pTask->mProgress->i_notifyComplete(rc); + LogRel(("Power up failed (vrc=%Rrc, rc=%Rhrc (%#08X))\n", vrc, rc, rc)); + } + + /* Notify VBoxSVC and any waiting openRemoteSession progress object. */ + pConsole->mControl->EndPowerUp(rc); + +#if defined(RT_OS_WINDOWS) + /* uninitialize COM */ + CoUninitialize(); +#endif + + LogFlowFuncLeave(); +} + + +/** + * Reconfigures a medium attachment (part of taking or deleting an online snapshot). + * + * @param pThis Reference to the console object. + * @param pUVM The VM handle. + * @param pVMM The VMM vtable. + * @param pcszDevice The name of the controller type. + * @param uInstance The instance of the controller. + * @param enmBus The storage bus type of the controller. + * @param fUseHostIOCache Use the host I/O cache (disable async I/O). + * @param fBuiltinIOCache Use the builtin I/O cache. + * @param fInsertDiskIntegrityDrv Flag whether to insert the disk integrity driver into the chain + * for additionalk debugging aids. + * @param fSetupMerge Whether to set up a medium merge + * @param uMergeSource Merge source image index + * @param uMergeTarget Merge target image index + * @param aMediumAtt The medium attachment. + * @param aMachineState The current machine state. + * @param phrc Where to store com error - only valid if we return VERR_GENERAL_FAILURE. + * @return VBox status code. + */ +/* static */ +DECLCALLBACK(int) Console::i_reconfigureMediumAttachment(Console *pThis, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + const char *pcszDevice, + unsigned uInstance, + StorageBus_T enmBus, + bool fUseHostIOCache, + bool fBuiltinIOCache, + bool fInsertDiskIntegrityDrv, + bool fSetupMerge, + unsigned uMergeSource, + unsigned uMergeTarget, + IMediumAttachment *aMediumAtt, + MachineState_T aMachineState, + HRESULT *phrc) +{ + LogFlowFunc(("pUVM=%p aMediumAtt=%p phrc=%p\n", pUVM, aMediumAtt, phrc)); + + HRESULT hrc; + Bstr bstr; + *phrc = S_OK; +#define H() do { if (FAILED(hrc)) { AssertMsgFailed(("hrc=%Rhrc (%#x)\n", hrc, hrc)); *phrc = hrc; return VERR_GENERAL_FAILURE; } } while (0) + + /* Ignore attachments other than hard disks, since at the moment they are + * not subject to snapshotting in general. */ + DeviceType_T lType; + hrc = aMediumAtt->COMGETTER(Type)(&lType); H(); + if (lType != DeviceType_HardDisk) + return VINF_SUCCESS; + + /* Update the device instance configuration. */ + int rc = pThis->i_configMediumAttachment(pcszDevice, + uInstance, + enmBus, + fUseHostIOCache, + fBuiltinIOCache, + fInsertDiskIntegrityDrv, + fSetupMerge, + uMergeSource, + uMergeTarget, + aMediumAtt, + aMachineState, + phrc, + true /* fAttachDetach */, + false /* fForceUnmount */, + false /* fHotplug */, + pUVM, + pVMM, + NULL /* paLedDevType */, + NULL /* ppLunL0)*/); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("rc=%Rrc\n", rc)); + return rc; + } + +#undef H + + LogFlowFunc(("Returns success\n")); + return VINF_SUCCESS; +} + +/** + * Thread for powering down the Console. + * + * @param pTask The power down task. + * + * @note Locks the Console object for writing. + */ +/*static*/ +void Console::i_powerDownThreadTask(VMPowerDownTask *pTask) +{ + int rc = VINF_SUCCESS; /* only used in assertion */ + LogFlowFuncEnter(); + try + { + if (pTask->isOk() == false) + rc = VERR_GENERAL_FAILURE; + + const ComObjPtr<Console> &that = pTask->mConsole; + + /* Note: no need to use AutoCaller to protect Console because VMTask does + * that */ + + /* wait until the method tat started us returns */ + AutoWriteLock thatLock(that COMMA_LOCKVAL_SRC_POS); + + /* release VM caller to avoid the powerDown() deadlock */ + pTask->releaseVMCaller(); + + thatLock.release(); + + that->i_powerDown(pTask->mServerProgress); + + /* complete the operation */ + that->mControl->EndPoweringDown(S_OK, Bstr().raw()); + + } + catch (const std::exception &e) + { + AssertMsgFailed(("Exception %s was caught, rc=%Rrc\n", e.what(), rc)); + NOREF(e); NOREF(rc); + } + + LogFlowFuncLeave(); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnSaveState} + */ +/*static*/ DECLCALLBACK(int) +Console::i_vmm2User_SaveState(PCVMM2USERMETHODS pThis, PUVM pUVM) +{ + Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole; + NOREF(pUVM); + + /* + * For now, just call SaveState. We should probably try notify the GUI so + * it can pop up a progress object and stuff. The progress object created + * by the call isn't returned to anyone and thus gets updated without + * anyone noticing it. + */ + ComPtr<IProgress> pProgress; + HRESULT hrc = pConsole->mMachine->SaveState(pProgress.asOutParam()); + return SUCCEEDED(hrc) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtInit} + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmm2User_NotifyEmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu) +{ + NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu); + VirtualBoxBase::initializeComForThread(); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtTerm} + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmm2User_NotifyEmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu) +{ + NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu); + VirtualBoxBase::uninitializeComForThread(); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtInit} + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmm2User_NotifyPdmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM) +{ + NOREF(pThis); NOREF(pUVM); + VirtualBoxBase::initializeComForThread(); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtTerm} + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmm2User_NotifyPdmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM) +{ + NOREF(pThis); NOREF(pUVM); + VirtualBoxBase::uninitializeComForThread(); +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnNotifyResetTurnedIntoPowerOff} + */ +/*static*/ DECLCALLBACK(void) +Console::i_vmm2User_NotifyResetTurnedIntoPowerOff(PCVMM2USERMETHODS pThis, PUVM pUVM) +{ + Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole; + NOREF(pUVM); + + pConsole->mfPowerOffCausedByReset = true; +} + +/** + * Internal function to get LED set off of Console instance + * + * @returns pointer to PDMLED object + * + * @param iLedSet Index of LED set to fetch + */ +PPDMLED * +Console::i_getLedSet(uint32_t iLedSet) +{ + AssertReturn(iLedSet < RT_ELEMENTS(maLedSets), NULL); + return maLedSets[iLedSet].papLeds; +} + +/** + * @interface_method_impl{VMM2USERMETHODS,pfnQueryGenericObject} + */ +/*static*/ DECLCALLBACK(void *) +Console::i_vmm2User_QueryGenericObject(PCVMM2USERMETHODS pThis, PUVM pUVM, PCRTUUID pUuid) +{ + Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole; + NOREF(pUVM); + + /* To simplify comparison we copy the UUID into a com::Guid object. */ + com::Guid const UuidCopy(*pUuid); + + if (UuidCopy == COM_IIDOF(IConsole)) + { + IConsole *pIConsole = static_cast<IConsole *>(pConsole); + return pIConsole; + } + + if (UuidCopy == COM_IIDOF(IMachine)) + { + IMachine *pIMachine = pConsole->mMachine; + return pIMachine; + } + + if (UuidCopy == COM_IIDOF(IKeyboard)) + { + IKeyboard *pIKeyboard = pConsole->mKeyboard; + return pIKeyboard; + } + + if (UuidCopy == COM_IIDOF(IMouse)) + { + IMouse *pIMouse = pConsole->mMouse; + return pIMouse; + } + + if (UuidCopy == COM_IIDOF(IDisplay)) + { + IDisplay *pIDisplay = pConsole->mDisplay; + return pIDisplay; + } + + if (UuidCopy == COM_IIDOF(INvramStore)) + { + NvramStore *pNvramStore = static_cast<NvramStore *>(pConsole->mptrNvramStore); + return pNvramStore; + } + + if (UuidCopy == VMMDEV_OID) + return pConsole->m_pVMMDev; + + if (UuidCopy == USBCARDREADER_OID) + return pConsole->mUsbCardReader; + + if (UuidCopy == COM_IIDOF(ISnapshot)) + return ((MYVMM2USERMETHODS *)pThis)->pISnapshot; + + if (UuidCopy == REMOTEUSBIF_OID) + return &pConsole->mRemoteUsbIf; + + if (UuidCopy == EMULATEDUSBIF_OID) + return pConsole->mEmulatedUSB->i_getEmulatedUsbIf(); + + return NULL; +} + + +/** + * @interface_method_impl{PDMISECKEY,pfnKeyRetain} + */ +/*static*/ DECLCALLBACK(int) +Console::i_pdmIfSecKey_KeyRetain(PPDMISECKEY pInterface, const char *pszId, const uint8_t **ppbKey, size_t *pcbKey) +{ + Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole; + + AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS); + SecretKey *pKey = NULL; + + int rc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey); + if (RT_SUCCESS(rc)) + { + *ppbKey = (const uint8_t *)pKey->getKeyBuffer(); + *pcbKey = pKey->getKeySize(); + } + + return rc; +} + +/** + * @interface_method_impl{PDMISECKEY,pfnKeyRelease} + */ +/*static*/ DECLCALLBACK(int) +Console::i_pdmIfSecKey_KeyRelease(PPDMISECKEY pInterface, const char *pszId) +{ + Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole; + + AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS); + return pConsole->m_pKeyStore->releaseSecretKey(Utf8Str(pszId)); +} + +/** + * @interface_method_impl{PDMISECKEY,pfnPasswordRetain} + */ +/*static*/ DECLCALLBACK(int) +Console::i_pdmIfSecKey_PasswordRetain(PPDMISECKEY pInterface, const char *pszId, const char **ppszPassword) +{ + Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole; + + AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS); + SecretKey *pKey = NULL; + + int rc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey); + if (RT_SUCCESS(rc)) + *ppszPassword = (const char *)pKey->getKeyBuffer(); + + return rc; +} + +/** + * @interface_method_impl{PDMISECKEY,pfnPasswordRelease} + */ +/*static*/ DECLCALLBACK(int) +Console::i_pdmIfSecKey_PasswordRelease(PPDMISECKEY pInterface, const char *pszId) +{ + Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole; + + AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS); + return pConsole->m_pKeyStore->releaseSecretKey(Utf8Str(pszId)); +} + +/** + * @interface_method_impl{PDMISECKEYHLP,pfnKeyMissingNotify} + */ +/*static*/ DECLCALLBACK(int) +Console::i_pdmIfSecKeyHlp_KeyMissingNotify(PPDMISECKEYHLP pInterface) +{ + Console *pConsole = ((MYPDMISECKEYHLP *)pInterface)->pConsole; + + /* Set guest property only, the VM is paused in the media driver calling us. */ + pConsole->mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/DekMissing").raw()); + pConsole->mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/DekMissing").raw(), + Bstr("1").raw(), Bstr("RDONLYGUEST").raw()); + pConsole->mMachine->SaveSettings(); + + return VINF_SUCCESS; +} + + + +/** + * The Main status driver instance data. + */ +typedef struct DRVMAINSTATUS +{ + /** The LED connectors. */ + PDMILEDCONNECTORS ILedConnectors; + /** Pointer to the LED ports interface above us. */ + PPDMILEDPORTS pLedPorts; + /** Pointer to the array of LED pointers. */ + PPDMLED *papLeds; + /** The unit number corresponding to the first entry in the LED array. */ + uint32_t iFirstLUN; + /** The unit number corresponding to the last entry in the LED array. + * (The size of the LED array is iLastLUN - iFirstLUN + 1.) */ + uint32_t iLastLUN; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** The Media Notify interface. */ + PDMIMEDIANOTIFY IMediaNotify; + /** Set if there potentially are medium attachments. */ + bool fHasMediumAttachments; + /** Device name+instance for mapping */ + char *pszDeviceInstance; + /** Pointer to the Console object, for driver triggered activities. */ + Console *pConsole; +} DRVMAINSTATUS, *PDRVMAINSTATUS; + + +/** + * Notification about a unit which have been changed. + * + * The driver must discard any pointers to data owned by + * the unit and requery it. + * + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param iLUN The unit number. + */ +DECLCALLBACK(void) Console::i_drvStatus_UnitChanged(PPDMILEDCONNECTORS pInterface, unsigned iLUN) +{ + PDRVMAINSTATUS pThis = RT_FROM_MEMBER(pInterface, DRVMAINSTATUS, ILedConnectors); + if (iLUN >= pThis->iFirstLUN && iLUN <= pThis->iLastLUN) + { + PPDMLED pLed; + int rc = pThis->pLedPorts->pfnQueryStatusLed(pThis->pLedPorts, iLUN, &pLed); + /* + * pLed now points directly to the per-unit struct PDMLED field + * inside the target device struct owned by the hardware driver. + */ + if (RT_FAILURE(rc)) + pLed = NULL; + ASMAtomicWritePtr(&pThis->papLeds[iLUN - pThis->iFirstLUN], pLed); + /* + * papLeds[] points to the struct PDMLED of each of this driver's + * units. The entries are initialized here, called out of a loop + * in Console::i_drvStatus_Construct(), which previously called + * Console::i_attachStatusDriver() to allocate the array itself. + * + * The arrays (and thus individual LEDs) are eventually read out + * by Console::getDeviceActivity(), which is itself called from + * src/VBox/Frontends/VirtualBox/src/runtime/UIIndicatorsPool.cpp + */ + Log(("drvStatus_UnitChanged: iLUN=%d pLed=%p\n", iLUN, pLed)); + } +} + + +/** + * Notification about a medium eject. + * + * @returns VBox status code. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param uLUN The unit number. + */ +DECLCALLBACK(int) Console::i_drvStatus_MediumEjected(PPDMIMEDIANOTIFY pInterface, unsigned uLUN) +{ + PDRVMAINSTATUS pThis = RT_FROM_MEMBER(pInterface, DRVMAINSTATUS, IMediaNotify); + LogFunc(("uLUN=%d\n", uLUN)); + if (pThis->fHasMediumAttachments) + { + Console * const pConsole = pThis->pConsole; + AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS); + + ComPtr<IMediumAttachment> pMediumAtt; + Utf8Str devicePath = Utf8StrFmt("%s/LUN#%u", pThis->pszDeviceInstance, uLUN); + Console::MediumAttachmentMap::const_iterator end = pConsole->mapMediumAttachments.end(); + Console::MediumAttachmentMap::const_iterator it = pConsole->mapMediumAttachments.find(devicePath); + if (it != end) + pMediumAtt = it->second; + Assert(!pMediumAtt.isNull()); + if (!pMediumAtt.isNull()) + { + IMedium *pMedium = NULL; + HRESULT rc = pMediumAtt->COMGETTER(Medium)(&pMedium); + AssertComRC(rc); + if (SUCCEEDED(rc) && pMedium) + { + BOOL fHostDrive = FALSE; + rc = pMedium->COMGETTER(HostDrive)(&fHostDrive); + AssertComRC(rc); + if (!fHostDrive) + { + alock.release(); + + ComPtr<IMediumAttachment> pNewMediumAtt; + rc = pThis->pConsole->mControl->EjectMedium(pMediumAtt, pNewMediumAtt.asOutParam()); + if (SUCCEEDED(rc)) + { + pThis->pConsole->mMachine->SaveSettings(); + ::FireMediumChangedEvent(pThis->pConsole->mEventSource, pNewMediumAtt); + } + + alock.acquire(); + if (pNewMediumAtt != pMediumAtt) + { + pConsole->mapMediumAttachments.erase(devicePath); + pConsole->mapMediumAttachments.insert(std::make_pair(devicePath, pNewMediumAtt)); + } + } + } + } + } + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) Console::i_drvStatus_QueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDCONNECTORS, &pThis->ILedConnectors); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIANOTIFY, &pThis->IMediaNotify); + return NULL; +} + + +/** + * Destruct a status driver instance. + * + * @returns VBox status code. + * @param pDrvIns The driver instance data. + */ +DECLCALLBACK(void) Console::i_drvStatus_Destruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS); + LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + if (pThis->papLeds) + { + unsigned iLed = pThis->iLastLUN - pThis->iFirstLUN + 1; + while (iLed-- > 0) + ASMAtomicWriteNullPtr(&pThis->papLeds[iLed]); + } +} + + +/** + * Construct a status driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +DECLCALLBACK(int) Console::i_drvStatus_Construct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS); + LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Initialize data. + */ + com::Guid ConsoleUuid(COM_IIDOF(IConsole)); + IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw()); + AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3); + Console *pConsole = static_cast<Console *>(pIConsole); + AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3); + + pDrvIns->IBase.pfnQueryInterface = Console::i_drvStatus_QueryInterface; + pThis->ILedConnectors.pfnUnitChanged = Console::i_drvStatus_UnitChanged; + pThis->IMediaNotify.pfnEjected = Console::i_drvStatus_MediumEjected; + pThis->pDrvIns = pDrvIns; + pThis->pConsole = pConsole; + pThis->fHasMediumAttachments = false; + pThis->papLeds = NULL; + pThis->pszDeviceInstance = NULL; + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, + "DeviceInstance|" + "iLedSet|" + "HasMediumAttachments|" + "First|" + "Last", + ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Read config. + */ + PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3; + + uint32_t iLedSet; + int rc = pHlp->pfnCFGMQueryU32(pCfg, "iLedSet", &iLedSet); + AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"iLedSet\" value! rc=%Rrc\n", rc), rc); + pThis->papLeds = pConsole->i_getLedSet(iLedSet); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "HasMediumAttachments", &pThis->fHasMediumAttachments, false); + AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"HasMediumAttachments\" value! rc=%Rrc\n", rc), rc); + + if (pThis->fHasMediumAttachments) + { + rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "DeviceInstance", &pThis->pszDeviceInstance); + AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"DeviceInstance\" value! rc=%Rrc\n", rc), rc); + } + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "First", &pThis->iFirstLUN, 0); + AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"First\" value! rc=%Rrc\n", rc), rc); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "Last", &pThis->iLastLUN, 0); + AssertLogRelMsgRCReturn(rc, ("Configuration error: Failed to query the \"Last\" value! rc=%Rrc\n", rc), rc); + + AssertLogRelMsgReturn(pThis->iFirstLUN <= pThis->iLastLUN, + ("Configuration error: Invalid unit range %u-%u\n", pThis->iFirstLUN, pThis->iLastLUN), + VERR_INVALID_PARAMETER); + + /* + * Get the ILedPorts interface of the above driver/device and + * query the LEDs we want. + */ + pThis->pLedPorts = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS); + AssertMsgReturn(pThis->pLedPorts, ("Configuration error: No led ports interface above!\n"), + VERR_PDM_MISSING_INTERFACE_ABOVE); + + for (unsigned i = pThis->iFirstLUN; i <= pThis->iLastLUN; ++i) + Console::i_drvStatus_UnitChanged(&pThis->ILedConnectors, i); + + return VINF_SUCCESS; +} + + +/** + * Console status driver (LED) registration record. + */ +const PDMDRVREG Console::DrvStatusReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "MainStatus", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main status driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_STATUS, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINSTATUS), + /* pfnConstruct */ + Console::i_drvStatus_Construct, + /* pfnDestruct */ + Console::i_drvStatus_Destruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + + + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/ConsoleImpl2.cpp b/src/VBox/Main/src-client/ConsoleImpl2.cpp new file mode 100644 index 00000000..71e1bda2 --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImpl2.cpp @@ -0,0 +1,6915 @@ +/* $Id: ConsoleImpl2.cpp $ */ +/** @file + * VBox Console COM Class implementation - VM Configuration Bits. + * + * @remark We've split out the code that the 64-bit VC++ v8 compiler finds + * problematic to optimize so we can disable optimizations and later, + * perhaps, find a real solution for it (like rewriting the code and + * to stop resemble a tonne of spaghetti). + */ + +/* + * Copyright (C) 2006-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE +#include "LoggingNew.h" + +// VBoxNetCfg-win.h needs winsock2.h and thus MUST be included before any other +// header file includes Windows.h. +#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT) +# include <VBox/VBoxNetCfg-win.h> +#endif + +#include "ConsoleImpl.h" +#include "DisplayImpl.h" +#include "NvramStoreImpl.h" +#ifdef VBOX_WITH_DRAG_AND_DROP +# include "GuestImpl.h" +# include "GuestDnDPrivate.h" +#endif +#include "VMMDev.h" +#include "Global.h" +#ifdef VBOX_WITH_PCI_PASSTHROUGH +# include "PCIRawDevImpl.h" +#endif + +// generated header +#include "SchemaDefs.h" + +#include "AutoCaller.h" + +#include <iprt/base64.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/cpp/exception.h> +#if 0 /* enable to play with lots of memory. */ +# include <iprt/env.h> +#endif +#include <iprt/stream.h> + +#include <iprt/http.h> +#include <iprt/socket.h> +#include <iprt/uri.h> + +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/settings.h> /* For MachineConfigFile::getHostDefaultAudioDriver(). */ +#include <VBox/vmm/pdmapi.h> /* For PDMR3DriverAttach/PDMR3DriverDetach. */ +#include <VBox/vmm/pdmusb.h> /* For PDMR3UsbCreateEmulatedDevice. */ +#include <VBox/vmm/pdmdev.h> /* For PDMAPICMODE enum. */ +#include <VBox/vmm/pdmstorageifs.h> +#include <VBox/vmm/gcm.h> +#include <VBox/version.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD +# include <VBox/HostServices/VBoxClipboardSvc.h> +#endif +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +# include <VBox/com/defs.h> +# include <VBox/com/array.h> +# include <vector> +#endif /* VBOX_WITH_GUEST_PROPS */ +#include <VBox/intnet.h> + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/array.h> + +#ifdef VBOX_WITH_NETFLT +# if defined(RT_OS_SOLARIS) +# include <zone.h> +# elif defined(RT_OS_LINUX) +# include <unistd.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <linux/types.h> +# include <linux/if.h> +# elif defined(RT_OS_FREEBSD) +# include <unistd.h> +# include <sys/types.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <net/if.h> +# include <net80211/ieee80211_ioctl.h> +# endif +# if defined(RT_OS_WINDOWS) +# include <iprt/win/ntddndis.h> +# include <devguid.h> +# else +# include <HostNetworkInterfaceImpl.h> +# include <netif.h> +# include <stdlib.h> +# endif +#endif /* VBOX_WITH_NETFLT */ + +#ifdef VBOX_WITH_AUDIO_VRDE +# include "DrvAudioVRDE.h" +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING +# include "DrvAudioRec.h" +#endif +#include "NetworkServiceRunner.h" +#include "BusAssignmentManager.h" +#ifdef VBOX_WITH_EXTPACK +# include "ExtPackManagerImpl.h" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue); + + +/* Darwin compile kludge */ +#undef PVM + +/* Comment out the following line to remove VMWare compatibility hack. */ +#define VMWARE_NET_IN_SLOT_11 + +/** + * Translate IDE StorageControllerType_T to string representation. + */ +static const char* controllerString(StorageControllerType_T enmType) +{ + switch (enmType) + { + case StorageControllerType_PIIX3: + return "PIIX3"; + case StorageControllerType_PIIX4: + return "PIIX4"; + case StorageControllerType_ICH6: + return "ICH6"; + default: + return "Unknown"; + } +} + +/** + * Simple class for storing network boot information. + */ +struct BootNic +{ + ULONG mInstance; + PCIBusAddress mPCIAddress; + + ULONG mBootPrio; + bool operator < (const BootNic &rhs) const + { + ULONG lval = mBootPrio - 1; /* 0 will wrap around and get the lowest priority. */ + ULONG rval = rhs.mBootPrio - 1; + return lval < rval; /* Zero compares as highest number (lowest prio). */ + } +}; + +#ifndef VBOX_WITH_EFI_IN_DD2 +static int findEfiRom(IVirtualBox* vbox, FirmwareType_T aFirmwareType, Utf8Str *pEfiRomFile) +{ + Bstr aFilePath, empty; + BOOL fPresent = FALSE; + HRESULT hrc = vbox->CheckFirmwarePresent(aFirmwareType, empty.raw(), + empty.asOutParam(), aFilePath.asOutParam(), &fPresent); + AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc)); + + if (!fPresent) + { + LogRel(("Failed to find an EFI ROM file.\n")); + return VERR_FILE_NOT_FOUND; + } + + *pEfiRomFile = Utf8Str(aFilePath); + + return VINF_SUCCESS; +} +#endif + +/** + * @throws HRESULT on extra data retrival error. + */ +static int getSmcDeviceKey(IVirtualBox *pVirtualBox, IMachine *pMachine, Utf8Str *pStrKey, bool *pfGetKeyFromRealSMC) +{ + *pfGetKeyFromRealSMC = false; + + /* + * The extra data takes precedence (if non-zero). + */ + GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/SmcDeviceKey", pStrKey); + if (pStrKey->isNotEmpty()) + return VINF_SUCCESS; + +#ifdef RT_OS_DARWIN + + /* + * Work done in EFI/DevSmc + */ + *pfGetKeyFromRealSMC = true; + int vrc = VINF_SUCCESS; + +#else + /* + * Is it apple hardware in bootcamp? + */ + /** @todo implement + test RTSYSDMISTR_MANUFACTURER on all hosts. + * Currently falling back on the product name. */ + char szManufacturer[256]; + szManufacturer[0] = '\0'; + RTSystemQueryDmiString(RTSYSDMISTR_MANUFACTURER, szManufacturer, sizeof(szManufacturer)); + if (szManufacturer[0] != '\0') + { + if ( !strcmp(szManufacturer, "Apple Computer, Inc.") + || !strcmp(szManufacturer, "Apple Inc.") + ) + *pfGetKeyFromRealSMC = true; + } + else + { + char szProdName[256]; + szProdName[0] = '\0'; + RTSystemQueryDmiString(RTSYSDMISTR_PRODUCT_NAME, szProdName, sizeof(szProdName)); + if ( ( !strncmp(szProdName, RT_STR_TUPLE("Mac")) + || !strncmp(szProdName, RT_STR_TUPLE("iMac")) + || !strncmp(szProdName, RT_STR_TUPLE("Xserve")) + ) + && !strchr(szProdName, ' ') /* no spaces */ + && RT_C_IS_DIGIT(szProdName[strlen(szProdName) - 1]) /* version number */ + ) + *pfGetKeyFromRealSMC = true; + } + + int vrc = VINF_SUCCESS; +#endif + + return vrc; +} + + +/* + * VC++ 8 / amd64 has some serious trouble with the next functions. + * As a temporary measure, we'll drop global optimizations. + */ +#if defined(_MSC_VER) && defined(RT_ARCH_AMD64) +# if _MSC_VER >= RT_MSC_VER_VC80 && _MSC_VER < RT_MSC_VER_VC100 +# pragma optimize("g", off) +# endif +#endif + +class ConfigError : public RTCError +{ +public: + + ConfigError(const char *pcszFunction, + int vrc, + const char *pcszName) + : RTCError(Utf8StrFmt(Console::tr("%s failed: rc=%Rrc, pcszName=%s"), pcszFunction, vrc, pcszName)), + m_vrc(vrc) + { + AssertMsgFailed(("%s\n", what())); // in strict mode, hit a breakpoint here + } + + int m_vrc; +}; + + +/** + * Helper that calls CFGMR3InsertString and throws an RTCError if that + * fails (C-string variant). + * @param pNode See CFGMR3InsertStringN. + * @param pcszName See CFGMR3InsertStringN. + * @param pcszValue The string value. + */ +void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const char *pcszValue) +{ + int vrc = mpVMM->pfnCFGMR3InsertString(pNode, pcszName, pcszValue); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertString", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertString and throws an RTCError if that + * fails (Utf8Str variant). + * @param pNode See CFGMR3InsertStringN. + * @param pcszName See CFGMR3InsertStringN. + * @param rStrValue The string value. + */ +void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue) +{ + int vrc = mpVMM->pfnCFGMR3InsertStringN(pNode, pcszName, rStrValue.c_str(), rStrValue.length()); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertStringLengthKnown", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertString and throws an RTCError if that + * fails (Bstr variant). + * + * @param pNode See CFGMR3InsertStringN. + * @param pcszName See CFGMR3InsertStringN. + * @param rBstrValue The string value. + */ +void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Bstr &rBstrValue) +{ + InsertConfigString(pNode, pcszName, Utf8Str(rBstrValue)); +} + +/** + * Helper that calls CFGMR3InsertPassword and throws an RTCError if that + * fails (Utf8Str variant). + * @param pNode See CFGMR3InsertPasswordN. + * @param pcszName See CFGMR3InsertPasswordN. + * @param rStrValue The string value. + */ +void Console::InsertConfigPassword(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue) +{ + int vrc = mpVMM->pfnCFGMR3InsertPasswordN(pNode, pcszName, rStrValue.c_str(), rStrValue.length()); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertPasswordLengthKnown", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertBytes and throws an RTCError if that fails. + * + * @param pNode See CFGMR3InsertBytes. + * @param pcszName See CFGMR3InsertBytes. + * @param pvBytes See CFGMR3InsertBytes. + * @param cbBytes See CFGMR3InsertBytes. + */ +void Console::InsertConfigBytes(PCFGMNODE pNode, const char *pcszName, const void *pvBytes, size_t cbBytes) +{ + int vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pcszName, pvBytes, cbBytes); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertBytes", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertInteger and throws an RTCError if that + * fails. + * + * @param pNode See CFGMR3InsertInteger. + * @param pcszName See CFGMR3InsertInteger. + * @param u64Integer See CFGMR3InsertInteger. + */ +void Console::InsertConfigInteger(PCFGMNODE pNode, const char *pcszName, uint64_t u64Integer) +{ + int vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pcszName, u64Integer); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertInteger", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertNode and throws an RTCError if that fails. + * + * @param pNode See CFGMR3InsertNode. + * @param pcszName See CFGMR3InsertNode. + * @param ppChild See CFGMR3InsertNode. + */ +void Console::InsertConfigNode(PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild) +{ + int vrc = mpVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertNode", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3InsertNodeF and throws an RTCError if that fails. + * + * @param pNode See CFGMR3InsertNodeF. + * @param ppChild See CFGMR3InsertNodeF. + * @param pszNameFormat Name format string, see CFGMR3InsertNodeF. + * @param ... Format arguments. + */ +void Console::InsertConfigNodeF(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, ...) +{ + va_list va; + va_start(va, pszNameFormat); + int vrc = mpVMM->pfnCFGMR3InsertNodeF(pNode, ppChild, "%N", pszNameFormat, &va); + va_end(va); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertNodeF", vrc, pszNameFormat); +} + +/** + * Helper that calls CFGMR3RemoveValue and throws an RTCError if that fails. + * + * @param pNode See CFGMR3RemoveValue. + * @param pcszName See CFGMR3RemoveValue. + */ +void Console::RemoveConfigValue(PCFGMNODE pNode, const char *pcszName) +{ + int vrc = mpVMM->pfnCFGMR3RemoveValue(pNode, pcszName); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3RemoveValue", vrc, pcszName); +} + +/** + * Gets an extra data value, consulting both machine and global extra data. + * + * @throws HRESULT on failure + * @returns pStrValue for the callers convenience. + * @param pVirtualBox Pointer to the IVirtualBox interface. + * @param pMachine Pointer to the IMachine interface. + * @param pszName The value to get. + * @param pStrValue Where to return it's value (empty string if not + * found). + */ +static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue) +{ + pStrValue->setNull(); + + Bstr bstrName(pszName); + Bstr bstrValue; + HRESULT hrc = pMachine->GetExtraData(bstrName.raw(), bstrValue.asOutParam()); + if (FAILED(hrc)) + throw hrc; + if (bstrValue.isEmpty()) + { + hrc = pVirtualBox->GetExtraData(bstrName.raw(), bstrValue.asOutParam()); + if (FAILED(hrc)) + throw hrc; + } + + if (bstrValue.isNotEmpty()) + *pStrValue = bstrValue; + return pStrValue; +} + + +/** Helper that finds out the next HBA port used + */ +static LONG GetNextUsedPort(LONG aPortUsed[30], LONG lBaseVal, uint32_t u32Size) +{ + LONG lNextPortUsed = 30; + for (size_t j = 0; j < u32Size; ++j) + { + if ( aPortUsed[j] > lBaseVal + && aPortUsed[j] <= lNextPortUsed) + lNextPortUsed = aPortUsed[j]; + } + return lNextPortUsed; +} + +#define MAX_BIOS_LUN_COUNT 4 + +int Console::SetBiosDiskInfo(ComPtr<IMachine> pMachine, PCFGMNODE pCfg, PCFGMNODE pBiosCfg, + Bstr controllerName, const char * const s_apszBiosConfig[4]) +{ + RT_NOREF(pCfg); + HRESULT hrc; +#define MAX_DEVICES 30 +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + + LONG lPortLUN[MAX_BIOS_LUN_COUNT]; + LONG lPortUsed[MAX_DEVICES]; + uint32_t u32HDCount = 0; + + /* init to max value */ + lPortLUN[0] = MAX_DEVICES; + + com::SafeIfaceArray<IMediumAttachment> atts; + hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(), + ComSafeArrayAsOutParam(atts)); H(); + size_t uNumAttachments = atts.size(); + if (uNumAttachments > MAX_DEVICES) + { + LogRel(("Number of Attachments > Max=%d.\n", uNumAttachments)); + uNumAttachments = MAX_DEVICES; + } + + /* Find the relevant ports/IDs, i.e the ones to which a HD is attached. */ + for (size_t j = 0; j < uNumAttachments; ++j) + { + IMediumAttachment *pMediumAtt = atts[j]; + LONG lPortNum = 0; + hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H(); + if (SUCCEEDED(hrc)) + { + DeviceType_T lType; + hrc = pMediumAtt->COMGETTER(Type)(&lType); H(); + if (SUCCEEDED(hrc) && lType == DeviceType_HardDisk) + { + /* find min port number used for HD */ + if (lPortNum < lPortLUN[0]) + lPortLUN[0] = lPortNum; + lPortUsed[u32HDCount++] = lPortNum; + LogFlowFunc(("HD port Count=%d\n", u32HDCount)); + } + } + } + + + /* Pick only the top 4 used HD Ports as CMOS doesn't have space + * to save details for all 30 ports + */ + uint32_t u32MaxPortCount = MAX_BIOS_LUN_COUNT; + if (u32HDCount < MAX_BIOS_LUN_COUNT) + u32MaxPortCount = u32HDCount; + for (size_t j = 1; j < u32MaxPortCount; j++) + lPortLUN[j] = GetNextUsedPort(lPortUsed, lPortLUN[j-1], u32HDCount); + if (pBiosCfg) + { + for (size_t j = 0; j < u32MaxPortCount; j++) + { + InsertConfigInteger(pBiosCfg, s_apszBiosConfig[j], lPortLUN[j]); + LogFlowFunc(("Top %d HBA ports = %s, %d\n", j, s_apszBiosConfig[j], lPortLUN[j])); + } + } + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_PCI_PASSTHROUGH +HRESULT Console::i_attachRawPCIDevices(PUVM pUVM, BusAssignmentManager *pBusMgr, PCFGMNODE pDevices) +{ +# ifndef VBOX_WITH_EXTPACK + RT_NOREF(pUVM); +# endif + HRESULT hrc = S_OK; + PCFGMNODE pInst, pCfg, pLunL0, pLunL1; + + SafeIfaceArray<IPCIDeviceAttachment> assignments; + ComPtr<IMachine> aMachine = i_machine(); + + hrc = aMachine->COMGETTER(PCIDeviceAssignments)(ComSafeArrayAsOutParam(assignments)); + if ( hrc != S_OK + || assignments.size() < 1) + return hrc; + + /* + * PCI passthrough is only available if the proper ExtPack is installed. + * + * Note. Configuring PCI passthrough here and providing messages about + * the missing extpack isn't exactly clean, but it is a necessary evil + * to patch over legacy compatability issues introduced by the new + * distribution model. + */ +# ifdef VBOX_WITH_EXTPACK + static const char *s_pszPCIRawExtPackName = "Oracle VM VirtualBox Extension Pack"; + if (!mptrExtPackManager->i_isExtPackUsable(s_pszPCIRawExtPackName)) + /* Always fatal! */ + return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Implementation of the PCI passthrough framework not found!\n" + "The VM cannot be started. To fix this problem, either " + "install the '%s' or disable PCI passthrough via VBoxManage"), + s_pszPCIRawExtPackName); +# endif + + /* Now actually add devices */ + PCFGMNODE pPCIDevs = NULL; + + if (assignments.size() > 0) + { + InsertConfigNode(pDevices, "pciraw", &pPCIDevs); + + PCFGMNODE pRoot = CFGMR3GetParent(pDevices); Assert(pRoot); + + /* Tell PGM to tell GPCIRaw about guest mappings. */ + CFGMR3InsertNode(pRoot, "PGM", NULL); + InsertConfigInteger(CFGMR3GetChild(pRoot, "PGM"), "PciPassThrough", 1); + + /* + * Currently, using IOMMU needed for PCI passthrough + * requires RAM preallocation. + */ + /** @todo check if we can lift this requirement */ + CFGMR3RemoveValue(pRoot, "RamPreAlloc"); + InsertConfigInteger(pRoot, "RamPreAlloc", 1); + } + + for (size_t iDev = 0; iDev < assignments.size(); iDev++) + { + PCIBusAddress HostPCIAddress, GuestPCIAddress; + ComPtr<IPCIDeviceAttachment> assignment = assignments[iDev]; + LONG host, guest; + Bstr aDevName; + + hrc = assignment->COMGETTER(HostAddress)(&host); H(); + hrc = assignment->COMGETTER(GuestAddress)(&guest); H(); + hrc = assignment->COMGETTER(Name)(aDevName.asOutParam()); H(); + + InsertConfigNode(pPCIDevs, Utf8StrFmt("%d", iDev).c_str(), &pInst); + InsertConfigInteger(pInst, "Trusted", 1); + + HostPCIAddress.fromLong(host); + Assert(HostPCIAddress.valid()); + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigString(pCfg, "DeviceName", aDevName); + + InsertConfigInteger(pCfg, "DetachHostDriver", 1); + InsertConfigInteger(pCfg, "HostPCIBusNo", HostPCIAddress.miBus); + InsertConfigInteger(pCfg, "HostPCIDeviceNo", HostPCIAddress.miDevice); + InsertConfigInteger(pCfg, "HostPCIFunctionNo", HostPCIAddress.miFn); + + GuestPCIAddress.fromLong(guest); + Assert(GuestPCIAddress.valid()); + hrc = pBusMgr->assignHostPCIDevice("pciraw", pInst, HostPCIAddress, GuestPCIAddress, true); + if (hrc != S_OK) + return hrc; + + InsertConfigInteger(pCfg, "GuestPCIBusNo", GuestPCIAddress.miBus); + InsertConfigInteger(pCfg, "GuestPCIDeviceNo", GuestPCIAddress.miDevice); + InsertConfigInteger(pCfg, "GuestPCIFunctionNo", GuestPCIAddress.miFn); + + /* the driver */ + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "pciraw"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + + /* the Main driver */ + InsertConfigString(pLunL1, "Driver", "MainPciRaw"); + InsertConfigNode(pLunL1, "Config", &pCfg); + PCIRawDev* pMainDev = new PCIRawDev(this); + InsertConfigInteger(pCfg, "Object", (uintptr_t)pMainDev); + } + + return hrc; +} +#endif + + +/** + * Allocate a set of LEDs. + * + * This grabs a maLedSets entry and populates it with @a cLeds. + * + * @returns Index into maLedSets. + * @param cLeds The number of LEDs in the set. + * @param enmType The device type. + * @param ppaSubTypes When not NULL, subtypes for each LED and return the + * array pointer here. + */ +uint32_t Console::i_allocateDriverLeds(uint32_t cLeds, DeviceType_T enmType, DeviceType_T **ppaSubTypes) +{ + Assert(cLeds > 0); + Assert(cLeds < 1024); /* Adjust if any driver supports >=1024 units! */ + + /* Grab a LED set entry before we start allocating anything so the destructor can do the cleanups. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Caller should have this already. Need protect mcLedSets check and update. */ + AssertStmt(mcLedSets < RT_ELEMENTS(maLedSets), + throw ConfigError("AllocateDriverPapLeds", VERR_OUT_OF_RANGE, "Too many LED sets")); + uint32_t const idxLedSet = mcLedSets++; + PLEDSET pLS = &maLedSets[idxLedSet]; + pLS->papLeds = (PPDMLED *)RTMemAllocZ(sizeof(PPDMLED) * cLeds); + AssertStmt(pLS->papLeds, throw E_OUTOFMEMORY); + pLS->cLeds = cLeds; + pLS->enmType = enmType; + pLS->paSubTypes = NULL; + + if (ppaSubTypes) + { + *ppaSubTypes = pLS->paSubTypes = (DeviceType_T *)RTMemAlloc(sizeof(DeviceType_T) * cLeds); + AssertStmt(pLS->paSubTypes, throw E_OUTOFMEMORY); + for (size_t idxSub = 0; idxSub < cLeds; ++idxSub) + pLS->paSubTypes[idxSub] = DeviceType_Null; + } + + LogRel2(("mcLedSets = %d, RT_ELEMENTS(maLedSets) = %d\n", mcLedSets, RT_ELEMENTS(maLedSets))); + return idxLedSet; +} + + +/** @todo r=bird: Drop uFirst as it's always zero? Then s/uLast/cLeds/g. */ +void Console::i_attachStatusDriver(PCFGMNODE pCtlInst, DeviceType_T enmType, + uint32_t uFirst, uint32_t uLast, + DeviceType_T **ppaSubTypes, + Console::MediumAttachmentMap *pmapMediumAttachments, + const char *pcszDevice, unsigned uInstance) +{ + Assert(uFirst <= uLast); + PCFGMNODE pLunL0; + InsertConfigNode(pCtlInst, "LUN#999", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MainStatus"); + PCFGMNODE pCfg; + InsertConfigNode(pLunL0, "Config", &pCfg); + uint32_t const iLedSet = i_allocateDriverLeds(uLast - uFirst + 1, enmType, ppaSubTypes); + InsertConfigInteger(pCfg, "iLedSet", iLedSet); + + InsertConfigInteger(pCfg, "HasMediumAttachments", pmapMediumAttachments != NULL); + if (pmapMediumAttachments) + { + AssertPtr(pcszDevice); + Utf8StrFmt deviceInstance("%s/%u", pcszDevice, uInstance); + InsertConfigString(pCfg, "DeviceInstance", deviceInstance.c_str()); + } + InsertConfigInteger(pCfg, "First", uFirst); + InsertConfigInteger(pCfg, "Last", uLast); +} + + +/** + * Construct the VM configuration tree (CFGM). + * + * This is a callback for VMR3Create() call. It is called from CFGMR3Init() in + * the emulation thread (EMT). Any per thread COM/XPCOM initialization is done + * here. + * + * @returns VBox status code. + * @param pUVM The user mode VM handle. + * @param pVM The cross context VM handle. + * @param pVMM The VMM ring-3 vtable. + * @param pvConsole Pointer to the VMPowerUpTask object. + * + * @note Locks the Console object for writing. + */ +/*static*/ DECLCALLBACK(int) +Console::i_configConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvConsole) +{ + LogFlowFuncEnter(); + + AssertReturn(pvConsole, VERR_INVALID_POINTER); + ComObjPtr<Console> pConsole = static_cast<Console *>(pvConsole); + + AutoCaller autoCaller(pConsole); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + /* lock the console because we widely use internal fields and methods */ + AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS); + + /* + * Set the VM handle and do the rest of the job in an worker method so we + * can easily reset the VM handle on failure. + */ + pConsole->mpUVM = pUVM; + pVMM->pfnVMR3RetainUVM(pUVM); + int vrc; + try + { + vrc = pConsole->i_configConstructorInner(pUVM, pVM, pVMM, &alock); + } + catch (...) + { + vrc = VERR_UNEXPECTED_EXCEPTION; + } + if (RT_FAILURE(vrc)) + { + pConsole->mpUVM = NULL; + pVMM->pfnVMR3ReleaseUVM(pUVM); + } + + return vrc; +} + + +/** + * Worker for configConstructor. + * + * @return VBox status code. + * @param pUVM The user mode VM handle. + * @param pVM The cross context VM handle. + * @param pVMM The VMM vtable. + * @param pAlock The automatic lock instance. This is for when we have + * to leave it in order to avoid deadlocks (ext packs and + * more). + */ +int Console::i_configConstructorInner(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock) +{ + RT_NOREF(pVM /* when everything is disabled */); + VMMDev *pVMMDev = m_pVMMDev; Assert(pVMMDev); + ComPtr<IMachine> pMachine = i_machine(); + + int vrc; + HRESULT hrc; + Utf8Str strTmp; + Bstr bstr; + +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + + /* + * Get necessary objects and frequently used parameters. + */ + ComPtr<IVirtualBox> virtualBox; + hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H(); + + ComPtr<IHost> host; + hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H(); + + ComPtr<ISystemProperties> systemProperties; + hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam()); H(); + + ComPtr<IBIOSSettings> biosSettings; + hrc = pMachine->COMGETTER(BIOSSettings)(biosSettings.asOutParam()); H(); + + ComPtr<INvramStore> nvramStore; + hrc = pMachine->COMGETTER(NonVolatileStore)(nvramStore.asOutParam()); H(); + + hrc = pMachine->COMGETTER(HardwareUUID)(bstr.asOutParam()); H(); + RTUUID HardwareUuid; + vrc = RTUuidFromUtf16(&HardwareUuid, bstr.raw()); + AssertRCReturn(vrc, vrc); + + ULONG cRamMBs; + hrc = pMachine->COMGETTER(MemorySize)(&cRamMBs); H(); +#if 0 /* enable to play with lots of memory. */ + if (RTEnvExist("VBOX_RAM_SIZE")) + cRamMBs = RTStrToUInt64(RTEnvGet("VBOX_RAM_SIZE")); +#endif + uint64_t const cbRam = cRamMBs * (uint64_t)_1M; + uint32_t cbRamHole = MM_RAM_HOLE_SIZE_DEFAULT; + uint64_t uMcfgBase = 0; + uint32_t cbMcfgLength = 0; + + ParavirtProvider_T enmParavirtProvider; + hrc = pMachine->GetEffectiveParavirtProvider(&enmParavirtProvider); H(); + + Bstr strParavirtDebug; + hrc = pMachine->COMGETTER(ParavirtDebug)(strParavirtDebug.asOutParam()); H(); + + BOOL fIOAPIC; + uint32_t uIoApicPciAddress = NIL_PCIBDF; + hrc = biosSettings->COMGETTER(IOAPICEnabled)(&fIOAPIC); H(); + + ChipsetType_T chipsetType; + hrc = pMachine->COMGETTER(ChipsetType)(&chipsetType); H(); + if (chipsetType == ChipsetType_ICH9) + { + /* We'd better have 0x10000000 region, to cover 256 buses but this put + * too much load on hypervisor heap. Linux 4.8 currently complains with + * ``acpi PNP0A03:00: [Firmware Info]: MMCONFIG for domain 0000 [bus 00-3f] + * only partially covers this bridge'' */ + cbMcfgLength = 0x4000000; //0x10000000; + cbRamHole += cbMcfgLength; + uMcfgBase = _4G - cbRamHole; + } + + /* Get the CPU profile name. */ + Bstr bstrCpuProfile; + hrc = pMachine->COMGETTER(CPUProfile)(bstrCpuProfile.asOutParam()); H(); + + /* Check if long mode is enabled. */ + BOOL fIsGuest64Bit; + hrc = pMachine->GetCPUProperty(CPUPropertyType_LongMode, &fIsGuest64Bit); H(); + + /* + * Figure out the IOMMU config. + */ +#if defined(VBOX_WITH_IOMMU_AMD) || defined(VBOX_WITH_IOMMU_INTEL) + IommuType_T enmIommuType; + hrc = pMachine->COMGETTER(IommuType)(&enmIommuType); H(); + + /* Resolve 'automatic' type to an Intel or AMD IOMMU based on the host CPU. */ + if (enmIommuType == IommuType_Automatic) + { + if ( bstrCpuProfile.startsWith("AMD") + || bstrCpuProfile.startsWith("Quad-Core AMD") + || bstrCpuProfile.startsWith("Hygon")) + enmIommuType = IommuType_AMD; + else if (bstrCpuProfile.startsWith("Intel")) + { + if ( bstrCpuProfile.equals("Intel 8086") + || bstrCpuProfile.equals("Intel 80186") + || bstrCpuProfile.equals("Intel 80286") + || bstrCpuProfile.equals("Intel 80386") + || bstrCpuProfile.equals("Intel 80486")) + enmIommuType = IommuType_None; + else + enmIommuType = IommuType_Intel; + } +# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + else if (ASMIsAmdCpu()) + enmIommuType = IommuType_AMD; + else if (ASMIsIntelCpu()) + enmIommuType = IommuType_Intel; +# endif + else + { + /** @todo Should we handle other CPUs like Shanghai, VIA etc. here? */ + LogRel(("WARNING! Unrecognized CPU type, IOMMU disabled.\n")); + enmIommuType = IommuType_None; + } + } + + if (enmIommuType == IommuType_AMD) + { +# ifdef VBOX_WITH_IOMMU_AMD + /* + * Reserve the specific PCI address of the "SB I/O APIC" when using + * an AMD IOMMU. Required by Linux guests, see @bugref{9654#c23}. + */ + uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC; +# else + LogRel(("WARNING! AMD IOMMU not supported, IOMMU disabled.\n")); + enmIommuType = IommuType_None; +# endif + } + + if (enmIommuType == IommuType_Intel) + { +# ifdef VBOX_WITH_IOMMU_INTEL + /* + * Reserve a unique PCI address for the I/O APIC when using + * an Intel IOMMU. For convenience we use the same address as + * we do on AMD, see @bugref{9967#c13}. + */ + uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC; +# else + LogRel(("WARNING! Intel IOMMU not supported, IOMMU disabled.\n")); + enmIommuType = IommuType_None; +# endif + } + + if ( enmIommuType == IommuType_AMD + || enmIommuType == IommuType_Intel) + { + if (chipsetType != ChipsetType_ICH9) + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("IOMMU uses MSIs which requires the ICH9 chipset implementation.")); + if (!fIOAPIC) + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("IOMMU requires an I/O APIC for remapping interrupts.")); + } +#else + IommuType_T const enmIommuType = IommuType_None; +#endif + + /* Instantiate the bus assignment manager. */ + Assert(enmIommuType != IommuType_Automatic); + BusAssignmentManager *pBusMgr = mBusMgr = BusAssignmentManager::createInstance(pVMM, chipsetType, enmIommuType); + + ULONG cCpus = 1; + hrc = pMachine->COMGETTER(CPUCount)(&cCpus); H(); + + ULONG ulCpuExecutionCap = 100; + hrc = pMachine->COMGETTER(CPUExecutionCap)(&ulCpuExecutionCap); H(); + + Bstr osTypeId; + hrc = pMachine->COMGETTER(OSTypeId)(osTypeId.asOutParam()); H(); + LogRel(("Guest OS type: '%s'\n", Utf8Str(osTypeId).c_str())); + + APICMode_T apicMode; + hrc = biosSettings->COMGETTER(APICMode)(&apicMode); H(); + uint32_t uFwAPIC; + switch (apicMode) + { + case APICMode_Disabled: + uFwAPIC = 0; + break; + case APICMode_APIC: + uFwAPIC = 1; + break; + case APICMode_X2APIC: + uFwAPIC = 2; + break; + default: + AssertMsgFailed(("Invalid APICMode=%d\n", apicMode)); + uFwAPIC = 1; + break; + } + + ComPtr<IGuestOSType> pGuestOSType; + virtualBox->GetGuestOSType(osTypeId.raw(), pGuestOSType.asOutParam()); + + BOOL fOsXGuest = FALSE; + BOOL fWinGuest = FALSE; + BOOL fOs2Guest = FALSE; + BOOL fW9xGuest = FALSE; + BOOL fDosGuest = FALSE; + if (!pGuestOSType.isNull()) + { + Bstr guestTypeFamilyId; + hrc = pGuestOSType->COMGETTER(FamilyId)(guestTypeFamilyId.asOutParam()); H(); + fOsXGuest = guestTypeFamilyId == Bstr("MacOS"); + fWinGuest = guestTypeFamilyId == Bstr("Windows"); + fOs2Guest = osTypeId.startsWith("OS2"); + fW9xGuest = osTypeId.startsWith("Windows9"); /* Does not include Windows Me. */ + fDosGuest = osTypeId.startsWith("DOS") || osTypeId.startsWith("Windows31"); + } + + ULONG maxNetworkAdapters; + hrc = systemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); H(); + + /* + * Get root node first. + * This is the only node in the tree. + */ + PCFGMNODE pRoot = pVMM->pfnCFGMR3GetRootU(pUVM); + Assert(pRoot); + + // InsertConfigString throws + try + { + + /* + * Set the root (and VMM) level values. + */ + hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H(); + InsertConfigString(pRoot, "Name", bstr); + InsertConfigBytes(pRoot, "UUID", &HardwareUuid, sizeof(HardwareUuid)); + InsertConfigInteger(pRoot, "RamSize", cbRam); + InsertConfigInteger(pRoot, "RamHoleSize", cbRamHole); + InsertConfigInteger(pRoot, "NumCPUs", cCpus); + InsertConfigInteger(pRoot, "CpuExecutionCap", ulCpuExecutionCap); + InsertConfigInteger(pRoot, "TimerMillies", 10); + + BOOL fPageFusion = FALSE; + hrc = pMachine->COMGETTER(PageFusionEnabled)(&fPageFusion); H(); + InsertConfigInteger(pRoot, "PageFusionAllowed", fPageFusion); /* boolean */ + + /* Not necessary, but makes sure this setting ends up in the release log. */ + ULONG ulBalloonSize = 0; + hrc = pMachine->COMGETTER(MemoryBalloonSize)(&ulBalloonSize); H(); + InsertConfigInteger(pRoot, "MemBalloonSize", ulBalloonSize); + + /* + * EM values (before CPUM as it may need to set IemExecutesAll). + */ + PCFGMNODE pEM; + InsertConfigNode(pRoot, "EM", &pEM); + + /* Triple fault behavior. */ + BOOL fTripleFaultReset = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_TripleFaultReset, &fTripleFaultReset); H(); + InsertConfigInteger(pEM, "TripleFaultReset", fTripleFaultReset); + + /* + * CPUM values. + */ + PCFGMNODE pCPUM; + InsertConfigNode(pRoot, "CPUM", &pCPUM); + PCFGMNODE pIsaExts; + InsertConfigNode(pCPUM, "IsaExts", &pIsaExts); + + /* Host CPUID leaf overrides. */ + for (uint32_t iOrdinal = 0; iOrdinal < _4K; iOrdinal++) + { + ULONG uLeaf, uSubLeaf, uEax, uEbx, uEcx, uEdx; + hrc = pMachine->GetCPUIDLeafByOrdinal(iOrdinal, &uLeaf, &uSubLeaf, &uEax, &uEbx, &uEcx, &uEdx); + if (hrc == E_INVALIDARG) + break; + H(); + PCFGMNODE pLeaf; + InsertConfigNode(pCPUM, Utf8StrFmt("HostCPUID/%RX32", uLeaf).c_str(), &pLeaf); + /** @todo Figure out how to tell the VMM about uSubLeaf */ + InsertConfigInteger(pLeaf, "eax", uEax); + InsertConfigInteger(pLeaf, "ebx", uEbx); + InsertConfigInteger(pLeaf, "ecx", uEcx); + InsertConfigInteger(pLeaf, "edx", uEdx); + } + + /* We must limit CPUID count for Windows NT 4, as otherwise it stops + with error 0x3e (MULTIPROCESSOR_CONFIGURATION_NOT_SUPPORTED). */ + if (osTypeId == "WindowsNT4") + { + LogRel(("Limiting CPUID leaf count for NT4 guests\n")); + InsertConfigInteger(pCPUM, "NT4LeafLimit", true); + } + + if (fOsXGuest) + { + /* Expose extended MWAIT features to Mac OS X guests. */ + LogRel(("Using MWAIT extensions\n")); + InsertConfigInteger(pIsaExts, "MWaitExtensions", true); + + /* Fake the CPU family/model so the guest works. This is partly + because older mac releases really doesn't work on newer cpus, + and partly because mac os x expects more from systems with newer + cpus (MSRs, power features, whatever). */ + uint32_t uMaxIntelFamilyModelStep = UINT32_MAX; + if ( osTypeId == "MacOS" + || osTypeId == "MacOS_64") + uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482. */ + else if ( osTypeId == "MacOS106" + || osTypeId == "MacOS106_64") + uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ + else if ( osTypeId == "MacOS107" + || osTypeId == "MacOS107_64") + uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out + what is required here. */ + else if ( osTypeId == "MacOS108" + || osTypeId == "MacOS108_64") + uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out + what is required here. */ + else if ( osTypeId == "MacOS109" + || osTypeId == "MacOS109_64") + uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure + out what is required here. */ + if (uMaxIntelFamilyModelStep != UINT32_MAX) + InsertConfigInteger(pCPUM, "MaxIntelFamilyModelStep", uMaxIntelFamilyModelStep); + } + + /* CPU Portability level, */ + ULONG uCpuIdPortabilityLevel = 0; + hrc = pMachine->COMGETTER(CPUIDPortabilityLevel)(&uCpuIdPortabilityLevel); H(); + InsertConfigInteger(pCPUM, "PortableCpuIdLevel", uCpuIdPortabilityLevel); + + /* Physical Address Extension (PAE) */ + BOOL fEnablePAE = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_PAE, &fEnablePAE); H(); + fEnablePAE |= fIsGuest64Bit; + InsertConfigInteger(pRoot, "EnablePAE", fEnablePAE); + + /* 64-bit guests (long mode) */ + InsertConfigInteger(pCPUM, "Enable64bit", fIsGuest64Bit); + + /* APIC/X2APIC configuration */ + BOOL fEnableAPIC = true; + BOOL fEnableX2APIC = true; + hrc = pMachine->GetCPUProperty(CPUPropertyType_APIC, &fEnableAPIC); H(); + hrc = pMachine->GetCPUProperty(CPUPropertyType_X2APIC, &fEnableX2APIC); H(); + if (fEnableX2APIC) + Assert(fEnableAPIC); + + /* CPUM profile name. */ + InsertConfigString(pCPUM, "GuestCpuName", bstrCpuProfile); + + /* + * Temporary(?) hack to make sure we emulate the ancient 16-bit CPUs + * correctly. There are way too many #UDs we'll miss using VT-x, + * raw-mode or qemu for the 186 and 286, while we'll get undefined opcodes + * dead wrong on 8086 (see http://www.os2museum.com/wp/undocumented-8086-opcodes/). + */ + if ( bstrCpuProfile.equals("Intel 80386") /* just for now */ + || bstrCpuProfile.equals("Intel 80286") + || bstrCpuProfile.equals("Intel 80186") + || bstrCpuProfile.equals("Nec V20") + || bstrCpuProfile.equals("Intel 8086") ) + { + InsertConfigInteger(pEM, "IemExecutesAll", true); + if (!bstrCpuProfile.equals("Intel 80386")) + { + fEnableAPIC = false; + fIOAPIC = false; + } + fEnableX2APIC = false; + } + + /* Adjust firmware APIC handling to stay within the VCPU limits. */ + if (uFwAPIC == 2 && !fEnableX2APIC) + { + if (fEnableAPIC) + uFwAPIC = 1; + else + uFwAPIC = 0; + LogRel(("Limiting the firmware APIC level from x2APIC to %s\n", fEnableAPIC ? "APIC" : "Disabled")); + } + else if (uFwAPIC == 1 && !fEnableAPIC) + { + uFwAPIC = 0; + LogRel(("Limiting the firmware APIC level from APIC to Disabled\n")); + } + + /* Speculation Control. */ + BOOL fSpecCtrl = FALSE; + hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrl, &fSpecCtrl); H(); + InsertConfigInteger(pCPUM, "SpecCtrl", fSpecCtrl); + + /* Nested VT-x / AMD-V. */ + BOOL fNestedHWVirt = FALSE; + hrc = pMachine->GetCPUProperty(CPUPropertyType_HWVirt, &fNestedHWVirt); H(); + InsertConfigInteger(pCPUM, "NestedHWVirt", fNestedHWVirt ? true : false); + + /* + * Hardware virtualization extensions. + */ + /* Sanitize valid/useful APIC combinations, see @bugref{8868}. */ + if (!fEnableAPIC) + { + if (fIsGuest64Bit) + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Cannot disable the APIC for a 64-bit guest.")); + if (cCpus > 1) + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Cannot disable the APIC for an SMP guest.")); + if (fIOAPIC) + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Cannot disable the APIC when the I/O APIC is present.")); + } + + BOOL fHMEnabled; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Enabled, &fHMEnabled); H(); + if (cCpus > 1 && !fHMEnabled) + { + LogRel(("Forced fHMEnabled to TRUE by SMP guest.\n")); + fHMEnabled = TRUE; + } + + BOOL fHMForced; + fHMEnabled = fHMForced = TRUE; + LogRel(("fHMForced=true - No raw-mode support in this build!\n")); + if (!fHMForced) /* No need to query if already forced above. */ + { + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Force, &fHMForced); H(); + if (fHMForced) + LogRel(("fHMForced=true - HWVirtExPropertyType_Force\n")); + } + InsertConfigInteger(pRoot, "HMEnabled", fHMEnabled); + + /* /HM/xyz */ + PCFGMNODE pHM; + InsertConfigNode(pRoot, "HM", &pHM); + InsertConfigInteger(pHM, "HMForced", fHMForced); + if (fHMEnabled) + { + /* Indicate whether 64-bit guests are supported or not. */ + InsertConfigInteger(pHM, "64bitEnabled", fIsGuest64Bit); + + /** @todo Not exactly pretty to check strings; VBOXOSTYPE would be better, + but that requires quite a bit of API change in Main. */ + if ( fIOAPIC + && ( osTypeId == "WindowsNT4" + || osTypeId == "Windows2000" + || osTypeId == "WindowsXP" + || osTypeId == "Windows2003")) + { + /* Only allow TPR patching for NT, Win2k, XP and Windows Server 2003. (32 bits mode) + * We may want to consider adding more guest OSes (Solaris) later on. + */ + InsertConfigInteger(pHM, "TPRPatchingEnabled", 1); + } + } + + /* HWVirtEx exclusive mode */ + BOOL fHMExclusive = true; + hrc = systemProperties->COMGETTER(ExclusiveHwVirt)(&fHMExclusive); H(); + InsertConfigInteger(pHM, "Exclusive", fHMExclusive); + + /* Nested paging (VT-x/AMD-V) */ + BOOL fEnableNestedPaging = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_NestedPaging, &fEnableNestedPaging); H(); + InsertConfigInteger(pHM, "EnableNestedPaging", fEnableNestedPaging); + + /* Large pages; requires nested paging */ + BOOL fEnableLargePages = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_LargePages, &fEnableLargePages); H(); + InsertConfigInteger(pHM, "EnableLargePages", fEnableLargePages); + + /* VPID (VT-x) */ + BOOL fEnableVPID = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_VPID, &fEnableVPID); H(); + InsertConfigInteger(pHM, "EnableVPID", fEnableVPID); + + /* Unrestricted execution aka UX (VT-x) */ + BOOL fEnableUX = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UnrestrictedExecution, &fEnableUX); H(); + InsertConfigInteger(pHM, "EnableUX", fEnableUX); + + /* Virtualized VMSAVE/VMLOAD (AMD-V) */ + BOOL fVirtVmsaveVmload = true; + hrc = host->GetProcessorFeature(ProcessorFeature_VirtVmsaveVmload, &fVirtVmsaveVmload); H(); + InsertConfigInteger(pHM, "SvmVirtVmsaveVmload", fVirtVmsaveVmload); + + /* Indirect branch prediction boundraries. */ + BOOL fIBPBOnVMExit = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMExit, &fIBPBOnVMExit); H(); + InsertConfigInteger(pHM, "IBPBOnVMExit", fIBPBOnVMExit); + + BOOL fIBPBOnVMEntry = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMEntry, &fIBPBOnVMEntry); H(); + InsertConfigInteger(pHM, "IBPBOnVMEntry", fIBPBOnVMEntry); + + BOOL fSpecCtrlByHost = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrlByHost, &fSpecCtrlByHost); H(); + InsertConfigInteger(pHM, "SpecCtrlByHost", fSpecCtrlByHost); + + BOOL fL1DFlushOnSched = true; + hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnEMTScheduling, &fL1DFlushOnSched); H(); + InsertConfigInteger(pHM, "L1DFlushOnSched", fL1DFlushOnSched); + + BOOL fL1DFlushOnVMEntry = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnVMEntry, &fL1DFlushOnVMEntry); H(); + InsertConfigInteger(pHM, "L1DFlushOnVMEntry", fL1DFlushOnVMEntry); + + BOOL fMDSClearOnSched = true; + hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnEMTScheduling, &fMDSClearOnSched); H(); + InsertConfigInteger(pHM, "MDSClearOnSched", fMDSClearOnSched); + + BOOL fMDSClearOnVMEntry = false; + hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnVMEntry, &fMDSClearOnVMEntry); H(); + InsertConfigInteger(pHM, "MDSClearOnVMEntry", fMDSClearOnVMEntry); + + /* Reset overwrite. */ + mfTurnResetIntoPowerOff = GetExtraDataBoth(virtualBox, pMachine, + "VBoxInternal2/TurnResetIntoPowerOff", &strTmp)->equals("1"); + if (mfTurnResetIntoPowerOff) + InsertConfigInteger(pRoot, "PowerOffInsteadOfReset", 1); + + /* Use NEM rather than HM. */ + BOOL fUseNativeApi = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UseNativeApi, &fUseNativeApi); H(); + InsertConfigInteger(pHM, "UseNEMInstead", fUseNativeApi); + + /* Enable workaround for missing TLB flush for OS/2 guests, see ticketref:20625. */ + if (osTypeId.startsWith("OS2")) + InsertConfigInteger(pHM, "MissingOS2TlbFlushWorkaround", 1); + + /* + * NEM + */ + PCFGMNODE pNEM; + InsertConfigNode(pRoot, "NEM", &pNEM); + InsertConfigInteger(pNEM, "Allow64BitGuests", fIsGuest64Bit); + + /* + * Paravirt. provider. + */ + PCFGMNODE pParavirtNode; + InsertConfigNode(pRoot, "GIM", &pParavirtNode); + const char *pcszParavirtProvider; + bool fGimDeviceNeeded = true; + switch (enmParavirtProvider) + { + case ParavirtProvider_None: + pcszParavirtProvider = "None"; + fGimDeviceNeeded = false; + break; + + case ParavirtProvider_Minimal: + pcszParavirtProvider = "Minimal"; + break; + + case ParavirtProvider_HyperV: + pcszParavirtProvider = "HyperV"; + break; + + case ParavirtProvider_KVM: + pcszParavirtProvider = "KVM"; + break; + + default: + AssertMsgFailed(("Invalid enmParavirtProvider=%d\n", enmParavirtProvider)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Invalid paravirt. provider '%d'"), + enmParavirtProvider); + } + InsertConfigString(pParavirtNode, "Provider", pcszParavirtProvider); + + /* + * Parse paravirt. debug options. + */ + bool fGimDebug = false; + com::Utf8Str strGimDebugAddress = "127.0.0.1"; + uint32_t uGimDebugPort = 50000; + if (strParavirtDebug.isNotEmpty()) + { + /* Hyper-V debug options. */ + if (enmParavirtProvider == ParavirtProvider_HyperV) + { + bool fGimHvDebug = false; + com::Utf8Str strGimHvVendor; + bool fGimHvVsIf = false; + bool fGimHvHypercallIf = false; + + size_t uPos = 0; + com::Utf8Str strDebugOptions = strParavirtDebug; + com::Utf8Str strKey; + com::Utf8Str strVal; + while ((uPos = strDebugOptions.parseKeyValue(strKey, strVal, uPos)) != com::Utf8Str::npos) + { + if (strKey == "enabled") + { + if (strVal.toUInt32() == 1) + { + /* Apply defaults. + The defaults are documented in the user manual, + changes need to be reflected accordingly. */ + fGimHvDebug = true; + strGimHvVendor = "Microsoft Hv"; + fGimHvVsIf = true; + fGimHvHypercallIf = false; + } + /* else: ignore, i.e. don't assert below with 'enabled=0'. */ + } + else if (strKey == "address") + strGimDebugAddress = strVal; + else if (strKey == "port") + uGimDebugPort = strVal.toUInt32(); + else if (strKey == "vendor") + strGimHvVendor = strVal; + else if (strKey == "vsinterface") + fGimHvVsIf = RT_BOOL(strVal.toUInt32()); + else if (strKey == "hypercallinterface") + fGimHvHypercallIf = RT_BOOL(strVal.toUInt32()); + else + { + AssertMsgFailed(("Unrecognized Hyper-V debug option '%s'\n", strKey.c_str())); + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Unrecognized Hyper-V debug option '%s' in '%s'"), strKey.c_str(), + strDebugOptions.c_str()); + } + } + + /* Update HyperV CFGM node with active debug options. */ + if (fGimHvDebug) + { + PCFGMNODE pHvNode; + InsertConfigNode(pParavirtNode, "HyperV", &pHvNode); + InsertConfigString(pHvNode, "VendorID", strGimHvVendor); + InsertConfigInteger(pHvNode, "VSInterface", fGimHvVsIf ? 1 : 0); + InsertConfigInteger(pHvNode, "HypercallDebugInterface", fGimHvHypercallIf ? 1 : 0); + fGimDebug = true; + } + } + } + + /* + * Guest Compatibility Manager. + */ + PCFGMNODE pGcmNode; + uint32_t u32FixerSet = 0; + InsertConfigNode(pRoot, "GCM", &pGcmNode); + /* OS/2 and Win9x guests can run DOS apps so they get + * the DOS specific fixes as well. + */ + if (fOs2Guest) + u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_OS2; + else if (fW9xGuest) + u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_WIN9X; + else if (fDosGuest) + u32FixerSet = GCMFIXER_DBZ_DOS; + InsertConfigInteger(pGcmNode, "FixerSet", u32FixerSet); + + + /* + * MM values. + */ + PCFGMNODE pMM; + InsertConfigNode(pRoot, "MM", &pMM); + InsertConfigInteger(pMM, "CanUseLargerHeap", chipsetType == ChipsetType_ICH9); + + /* + * PDM config. + * Load drivers in VBoxC.[so|dll] + */ + PCFGMNODE pPDM; + PCFGMNODE pNode; + PCFGMNODE pMod; + InsertConfigNode(pRoot, "PDM", &pPDM); + InsertConfigNode(pPDM, "Devices", &pNode); + InsertConfigNode(pPDM, "Drivers", &pNode); + InsertConfigNode(pNode, "VBoxC", &pMod); +#ifdef VBOX_WITH_XPCOM + // VBoxC is located in the components subdirectory + char szPathVBoxC[RTPATH_MAX]; + vrc = RTPathAppPrivateArch(szPathVBoxC, RTPATH_MAX - sizeof("/components/VBoxC")); AssertRC(vrc); + strcat(szPathVBoxC, "/components/VBoxC"); + InsertConfigString(pMod, "Path", szPathVBoxC); +#else + InsertConfigString(pMod, "Path", "VBoxC"); +#endif + + + /* + * Block cache settings. + */ + PCFGMNODE pPDMBlkCache; + InsertConfigNode(pPDM, "BlkCache", &pPDMBlkCache); + + /* I/O cache size */ + ULONG ioCacheSize = 5; + hrc = pMachine->COMGETTER(IOCacheSize)(&ioCacheSize); H(); + InsertConfigInteger(pPDMBlkCache, "CacheSize", ioCacheSize * _1M); + + /* + * Bandwidth groups. + */ + ComPtr<IBandwidthControl> bwCtrl; + + hrc = pMachine->COMGETTER(BandwidthControl)(bwCtrl.asOutParam()); H(); + + com::SafeIfaceArray<IBandwidthGroup> bwGroups; + hrc = bwCtrl->GetAllBandwidthGroups(ComSafeArrayAsOutParam(bwGroups)); H(); + + PCFGMNODE pAc; + InsertConfigNode(pPDM, "AsyncCompletion", &pAc); + PCFGMNODE pAcFile; + InsertConfigNode(pAc, "File", &pAcFile); + PCFGMNODE pAcFileBwGroups; + InsertConfigNode(pAcFile, "BwGroups", &pAcFileBwGroups); +#ifdef VBOX_WITH_NETSHAPER + PCFGMNODE pNetworkShaper; + InsertConfigNode(pPDM, "NetworkShaper", &pNetworkShaper); + PCFGMNODE pNetworkBwGroups; + InsertConfigNode(pNetworkShaper, "BwGroups", &pNetworkBwGroups); +#endif /* VBOX_WITH_NETSHAPER */ + + for (size_t i = 0; i < bwGroups.size(); i++) + { + Bstr strName; + hrc = bwGroups[i]->COMGETTER(Name)(strName.asOutParam()); H(); + if (strName.isEmpty()) + return pVMM->pfnVMR3SetError(pUVM, VERR_CFGM_NO_NODE, RT_SRC_POS, N_("No bandwidth group name specified")); + + BandwidthGroupType_T enmType = BandwidthGroupType_Null; + hrc = bwGroups[i]->COMGETTER(Type)(&enmType); H(); + LONG64 cMaxBytesPerSec = 0; + hrc = bwGroups[i]->COMGETTER(MaxBytesPerSec)(&cMaxBytesPerSec); H(); + + if (enmType == BandwidthGroupType_Disk) + { + PCFGMNODE pBwGroup; + InsertConfigNode(pAcFileBwGroups, Utf8Str(strName).c_str(), &pBwGroup); + InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec); + InsertConfigInteger(pBwGroup, "Start", cMaxBytesPerSec); + InsertConfigInteger(pBwGroup, "Step", 0); + } +#ifdef VBOX_WITH_NETSHAPER + else if (enmType == BandwidthGroupType_Network) + { + /* Network bandwidth groups. */ + PCFGMNODE pBwGroup; + InsertConfigNode(pNetworkBwGroups, Utf8Str(strName).c_str(), &pBwGroup); + InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec); + } +#endif /* VBOX_WITH_NETSHAPER */ + } + + /* + * Devices + */ + PCFGMNODE pDevices = NULL; /* /Devices */ + PCFGMNODE pDev = NULL; /* /Devices/Dev/ */ + PCFGMNODE pInst = NULL; /* /Devices/Dev/0/ */ + PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */ + PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */ + PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */ + PCFGMNODE pBiosCfg = NULL; /* /Devices/pcbios/0/Config/ */ + PCFGMNODE pNetBootCfg = NULL; /* /Devices/pcbios/0/Config/NetBoot/ */ + + InsertConfigNode(pRoot, "Devices", &pDevices); + + /* + * GIM Device + */ + if (fGimDeviceNeeded) + { + InsertConfigNode(pDevices, "GIMDev", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + //InsertConfigNode(pInst, "Config", &pCfg); + + if (fGimDebug) + { + InsertConfigNode(pInst, "LUN#998", &pLunL0); + InsertConfigString(pLunL0, "Driver", "UDP"); + InsertConfigNode(pLunL0, "Config", &pLunL1); + InsertConfigString(pLunL1, "ServerAddress", strGimDebugAddress); + InsertConfigInteger(pLunL1, "ServerPort", uGimDebugPort); + } + } + + /* + * PC Arch. + */ + InsertConfigNode(pDevices, "pcarch", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + + /* + * The time offset + */ + LONG64 timeOffset; + hrc = biosSettings->COMGETTER(TimeOffset)(&timeOffset); H(); + PCFGMNODE pTMNode; + InsertConfigNode(pRoot, "TM", &pTMNode); + InsertConfigInteger(pTMNode, "UTCOffset", timeOffset * 1000000); + + /* + * DMA + */ + InsertConfigNode(pDevices, "8237A", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + + /* + * PCI buses. + */ + uint32_t uIocPCIAddress, uHbcPCIAddress; + switch (chipsetType) + { + default: + AssertFailed(); + RT_FALL_THRU(); + case ChipsetType_PIIX3: + /* Create the base for adding bridges on demand */ + InsertConfigNode(pDevices, "pcibridge", NULL); + + InsertConfigNode(pDevices, "pci", &pDev); + uHbcPCIAddress = (0x0 << 16) | 0; + uIocPCIAddress = (0x1 << 16) | 0; // ISA controller + break; + case ChipsetType_ICH9: + /* Create the base for adding bridges on demand */ + InsertConfigNode(pDevices, "ich9pcibridge", NULL); + + InsertConfigNode(pDevices, "ich9pci", &pDev); + uHbcPCIAddress = (0x1e << 16) | 0; + uIocPCIAddress = (0x1f << 16) | 0; // LPC controller + break; + } + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC); + if (chipsetType == ChipsetType_ICH9) + { + /* Provide MCFG info */ + InsertConfigInteger(pCfg, "McfgBase", uMcfgBase); + InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength); + +#ifdef VBOX_WITH_PCI_PASSTHROUGH + /* Add PCI passthrough devices */ + hrc = i_attachRawPCIDevices(pUVM, pBusMgr, pDevices); H(); +#endif + + if (enmIommuType == IommuType_AMD) + { + /* AMD IOMMU. */ + InsertConfigNode(pDevices, "iommu-amd", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + hrc = pBusMgr->assignPCIDevice("iommu-amd", pInst); H(); + + /* The AMD IOMMU device needs to know which PCI slot it's in, see @bugref{9654#c104}. */ + { + PCIBusAddress Address; + if (pBusMgr->findPCIAddress("iommu-amd", 0, Address)) + { + uint32_t const u32IommuAddress = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "PCIAddress", u32IommuAddress); + } + else + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Failed to find PCI address of the assigned IOMMU device!")); + } + + PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress); + hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H(); + } + else if (enmIommuType == IommuType_Intel) + { + /* Intel IOMMU. */ + InsertConfigNode(pDevices, "iommu-intel", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + hrc = pBusMgr->assignPCIDevice("iommu-intel", pInst); H(); + + PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress); + hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H(); + } + } + + /* + * Enable the following devices: HPET, SMC and LPC on MacOS X guests or on ICH9 chipset + */ + + /* + * High Precision Event Timer (HPET) + */ + BOOL fHPETEnabled; + /* Other guests may wish to use HPET too, but MacOS X not functional without it */ + hrc = pMachine->COMGETTER(HPETEnabled)(&fHPETEnabled); H(); + /* so always enable HPET in extended profile */ + fHPETEnabled |= fOsXGuest; + /* HPET is always present on ICH9 */ + fHPETEnabled |= (chipsetType == ChipsetType_ICH9); + if (fHPETEnabled) + { + InsertConfigNode(pDevices, "hpet", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "ICH9", (chipsetType == ChipsetType_ICH9) ? 1 : 0); /* boolean */ + } + + /* + * System Management Controller (SMC) + */ + BOOL fSmcEnabled; + fSmcEnabled = fOsXGuest; + if (fSmcEnabled) + { + InsertConfigNode(pDevices, "smc", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + + bool fGetKeyFromRealSMC; + Utf8Str strKey; + vrc = getSmcDeviceKey(virtualBox, pMachine, &strKey, &fGetKeyFromRealSMC); + AssertRCReturn(vrc, vrc); + + if (!fGetKeyFromRealSMC) + InsertConfigString(pCfg, "DeviceKey", strKey); + InsertConfigInteger(pCfg, "GetKeyFromRealSMC", fGetKeyFromRealSMC); + } + + /* + * Low Pin Count (LPC) bus + */ + BOOL fLpcEnabled; + /** @todo implement appropriate getter */ + fLpcEnabled = fOsXGuest || (chipsetType == ChipsetType_ICH9); + if (fLpcEnabled) + { + InsertConfigNode(pDevices, "lpc", &pDev); + InsertConfigNode(pDev, "0", &pInst); + hrc = pBusMgr->assignPCIDevice("lpc", pInst); H(); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + } + + BOOL fShowRtc; + fShowRtc = fOsXGuest || (chipsetType == ChipsetType_ICH9); + + /* + * PS/2 keyboard & mouse. + */ + InsertConfigNode(pDevices, "pckbd", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + + KeyboardHIDType_T aKbdHID; + hrc = pMachine->COMGETTER(KeyboardHIDType)(&aKbdHID); H(); + if (aKbdHID != KeyboardHIDType_None) + { + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "KeyboardQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 64); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainKeyboard"); + } + + PointingHIDType_T aPointingHID; + hrc = pMachine->COMGETTER(PointingHIDType)(&aPointingHID); H(); + if (aPointingHID != PointingHIDType_None) + { + InsertConfigNode(pInst, "LUN#1", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MouseQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 128); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainMouse"); + } + + /* + * i8254 Programmable Interval Timer And Dummy Speaker + */ + InsertConfigNode(pDevices, "i8254", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); +#ifdef DEBUG + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ +#endif + + /* + * i8259 Programmable Interrupt Controller. + */ + InsertConfigNode(pDevices, "i8259", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + + /* + * Advanced Programmable Interrupt Controller. + * SMP: Each CPU has a LAPIC, but we have a single device representing all LAPICs states, + * thus only single insert + */ + if (fEnableAPIC) + { + InsertConfigNode(pDevices, "apic", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC); + PDMAPICMODE enmAPICMode = PDMAPICMODE_APIC; + if (fEnableX2APIC) + enmAPICMode = PDMAPICMODE_X2APIC; + else if (!fEnableAPIC) + enmAPICMode = PDMAPICMODE_NONE; + InsertConfigInteger(pCfg, "Mode", enmAPICMode); + InsertConfigInteger(pCfg, "NumCPUs", cCpus); + + if (fIOAPIC) + { + /* + * I/O Advanced Programmable Interrupt Controller. + */ + InsertConfigNode(pDevices, "ioapic", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "NumCPUs", cCpus); + if (enmIommuType == IommuType_AMD) + InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress); + else if (enmIommuType == IommuType_Intel) + { + InsertConfigString(pCfg, "ChipType", "DMAR"); + InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress); + } + } + } + + /* + * RTC MC146818. + */ + InsertConfigNode(pDevices, "mc146818", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + BOOL fRTCUseUTC; + hrc = pMachine->COMGETTER(RTCUseUTC)(&fRTCUseUTC); H(); + InsertConfigInteger(pCfg, "UseUTC", fRTCUseUTC ? 1 : 0); + + /* + * VGA. + */ + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + hrc = pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); H(); + GraphicsControllerType_T enmGraphicsController; + hrc = pGraphicsAdapter->COMGETTER(GraphicsControllerType)(&enmGraphicsController); H(); + switch (enmGraphicsController) + { + case GraphicsControllerType_Null: + break; +#ifdef VBOX_WITH_VMSVGA + case GraphicsControllerType_VMSVGA: + InsertConfigInteger(pHM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */ + InsertConfigInteger(pNEM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */ + RT_FALL_THROUGH(); + case GraphicsControllerType_VBoxSVGA: +#endif + case GraphicsControllerType_VBoxVGA: + vrc = i_configGraphicsController(pDevices, enmGraphicsController, pBusMgr, pMachine, pGraphicsAdapter, biosSettings, + RT_BOOL(fHMEnabled)); + if (FAILED(vrc)) + return vrc; + break; + default: + AssertMsgFailed(("Invalid graphicsController=%d\n", enmGraphicsController)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Invalid graphics controller type '%d'"), enmGraphicsController); + } + + /* + * Firmware. + */ + FirmwareType_T eFwType = FirmwareType_BIOS; + hrc = pMachine->COMGETTER(FirmwareType)(&eFwType); H(); + +#ifdef VBOX_WITH_EFI + BOOL fEfiEnabled = (eFwType >= FirmwareType_EFI) && (eFwType <= FirmwareType_EFIDUAL); +#else + BOOL fEfiEnabled = false; +#endif + if (!fEfiEnabled) + { + /* + * PC Bios. + */ + InsertConfigNode(pDevices, "pcbios", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pBiosCfg); + InsertConfigInteger(pBiosCfg, "NumCPUs", cCpus); + InsertConfigString(pBiosCfg, "HardDiskDevice", "piix3ide"); + InsertConfigString(pBiosCfg, "FloppyDevice", "i82078"); + InsertConfigInteger(pBiosCfg, "IOAPIC", fIOAPIC); + InsertConfigInteger(pBiosCfg, "APIC", uFwAPIC); + BOOL fPXEDebug; + hrc = biosSettings->COMGETTER(PXEDebugEnabled)(&fPXEDebug); H(); + InsertConfigInteger(pBiosCfg, "PXEDebug", fPXEDebug); + InsertConfigBytes(pBiosCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid)); + BOOL fUuidLe; + hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H(); + InsertConfigInteger(pBiosCfg, "UuidLe", fUuidLe); + InsertConfigNode(pBiosCfg, "NetBoot", &pNetBootCfg); + InsertConfigInteger(pBiosCfg, "McfgBase", uMcfgBase); + InsertConfigInteger(pBiosCfg, "McfgLength", cbMcfgLength); + + AssertMsgReturn(SchemaDefs::MaxBootPosition <= 9, ("Too many boot devices %d\n", SchemaDefs::MaxBootPosition), + VERR_INVALID_PARAMETER); + + for (ULONG pos = 1; pos <= SchemaDefs::MaxBootPosition; ++pos) + { + DeviceType_T enmBootDevice; + hrc = pMachine->GetBootOrder(pos, &enmBootDevice); H(); + + char szParamName[] = "BootDeviceX"; + szParamName[sizeof(szParamName) - 2] = (char)(pos - 1 + '0'); + + const char *pszBootDevice; + switch (enmBootDevice) + { + case DeviceType_Null: + pszBootDevice = "NONE"; + break; + case DeviceType_HardDisk: + pszBootDevice = "IDE"; + break; + case DeviceType_DVD: + pszBootDevice = "DVD"; + break; + case DeviceType_Floppy: + pszBootDevice = "FLOPPY"; + break; + case DeviceType_Network: + pszBootDevice = "LAN"; + break; + default: + AssertMsgFailed(("Invalid enmBootDevice=%d\n", enmBootDevice)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Invalid boot device '%d'"), enmBootDevice); + } + InsertConfigString(pBiosCfg, szParamName, pszBootDevice); + } + + /** @todo @bugref{7145}: We might want to enable this by default for new VMs. For now, + * this is required for Windows 2012 guests. */ + if (osTypeId == "Windows2012_64") + InsertConfigInteger(pBiosCfg, "DmiExposeMemoryTable", 1); /* boolean */ + } + else + { + /* Autodetect firmware type, basing on guest type */ + if (eFwType == FirmwareType_EFI) + eFwType = fIsGuest64Bit ? FirmwareType_EFI64 : FirmwareType_EFI32; + bool const f64BitEntry = eFwType == FirmwareType_EFI64; + + Assert(eFwType == FirmwareType_EFI64 || eFwType == FirmwareType_EFI32 || eFwType == FirmwareType_EFIDUAL); +#ifdef VBOX_WITH_EFI_IN_DD2 + const char *pszEfiRomFile = eFwType == FirmwareType_EFIDUAL ? "VBoxEFIDual.fd" + : eFwType == FirmwareType_EFI32 ? "VBoxEFI32.fd" + : "VBoxEFI64.fd"; +#else + Utf8Str efiRomFile; + vrc = findEfiRom(virtualBox, eFwType, &efiRomFile); + AssertRCReturn(vrc, vrc); + const char *pszEfiRomFile = efiRomFile.c_str(); +#endif + + /* Get boot args */ + Utf8Str bootArgs; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiBootArgs", &bootArgs); + + /* Get device props */ + Utf8Str deviceProps; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiDeviceProps", &deviceProps); + + /* Get NVRAM file name */ + Utf8Str strNvram = mptrNvramStore->i_getNonVolatileStorageFile(); + + BOOL fUuidLe; + hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H(); + + /* Get graphics mode settings */ + uint32_t u32GraphicsMode = UINT32_MAX; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsMode", &strTmp); + if (strTmp.isEmpty()) + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGopMode", &strTmp); + if (!strTmp.isEmpty()) + u32GraphicsMode = strTmp.toUInt32(); + + /* Get graphics resolution settings, with some sanity checking */ + Utf8Str strResolution; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsResolution", &strResolution); + if (!strResolution.isEmpty()) + { + size_t pos = strResolution.find("x"); + if (pos != strResolution.npos) + { + Utf8Str strH, strV; + strH.assignEx(strResolution, 0, pos); + strV.assignEx(strResolution, pos+1, strResolution.length()-pos-1); + uint32_t u32H = strH.toUInt32(); + uint32_t u32V = strV.toUInt32(); + if (u32H == 0 || u32V == 0) + strResolution.setNull(); + } + else + strResolution.setNull(); + } + else + { + uint32_t u32H = 0; + uint32_t u32V = 0; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiHorizontalResolution", &strTmp); + if (strTmp.isEmpty()) + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaHorizontalResolution", &strTmp); + if (!strTmp.isEmpty()) + u32H = strTmp.toUInt32(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiVerticalResolution", &strTmp); + if (strTmp.isEmpty()) + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaVerticalResolution", &strTmp); + if (!strTmp.isEmpty()) + u32V = strTmp.toUInt32(); + if (u32H != 0 && u32V != 0) + strResolution = Utf8StrFmt("%ux%u", u32H, u32V); + } + + /* + * EFI subtree. + */ + InsertConfigNode(pDevices, "efi", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "NumCPUs", cCpus); + InsertConfigInteger(pCfg, "McfgBase", uMcfgBase); + InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength); + InsertConfigString(pCfg, "EfiRom", pszEfiRomFile); + InsertConfigString(pCfg, "BootArgs", bootArgs); + InsertConfigString(pCfg, "DeviceProps", deviceProps); + InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC); + InsertConfigInteger(pCfg, "APIC", uFwAPIC); + InsertConfigBytes(pCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid)); + InsertConfigInteger(pCfg, "UuidLe", fUuidLe); + InsertConfigInteger(pCfg, "64BitEntry", f64BitEntry); /* boolean */ + InsertConfigString(pCfg, "NvramFile", strNvram); + if (u32GraphicsMode != UINT32_MAX) + InsertConfigInteger(pCfg, "GraphicsMode", u32GraphicsMode); + if (!strResolution.isEmpty()) + InsertConfigString(pCfg, "GraphicsResolution", strResolution); + + /* For OS X guests we'll force passing host's DMI info to the guest */ + if (fOsXGuest) + { + InsertConfigInteger(pCfg, "DmiUseHostInfo", 1); + InsertConfigInteger(pCfg, "DmiExposeMemoryTable", 1); + } + + /* Attach the NVRAM storage driver. */ + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "NvramStore"); + } + + /* + * The USB Controllers. + */ + com::SafeIfaceArray<IUSBController> usbCtrls; + hrc = pMachine->COMGETTER(USBControllers)(ComSafeArrayAsOutParam(usbCtrls)); + bool fOhciPresent = false; /**< Flag whether at least one OHCI controller is present. */ + bool fXhciPresent = false; /**< Flag whether at least one XHCI controller is present. */ + + if (SUCCEEDED(hrc)) + { + for (size_t i = 0; i < usbCtrls.size(); ++i) + { + USBControllerType_T enmCtrlType; + vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H(); + if (enmCtrlType == USBControllerType_OHCI) + { + fOhciPresent = true; + break; + } + else if (enmCtrlType == USBControllerType_XHCI) + { + fXhciPresent = true; + break; + } + } + } + else if (hrc != E_NOTIMPL) + { + H(); + } + + /* + * Currently EHCI is only enabled when an OHCI or XHCI controller is present as well. + */ + if (fOhciPresent || fXhciPresent) + mfVMHasUsbController = true; + + PCFGMNODE pUsbDevices = NULL; /**< Required for USB storage controller later. */ + if (mfVMHasUsbController) + { + for (size_t i = 0; i < usbCtrls.size(); ++i) + { + USBControllerType_T enmCtrlType; + vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H(); + + if (enmCtrlType == USBControllerType_OHCI) + { + InsertConfigNode(pDevices, "usb-ohci", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice("usb-ohci", pInst); H(); + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "VUSBRootHub"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, DeviceType_USB, 0, 0, NULL, NULL, NULL, 0); + } +#ifdef VBOX_WITH_EHCI + else if (enmCtrlType == USBControllerType_EHCI) + { + InsertConfigNode(pDevices, "usb-ehci", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice("usb-ehci", pInst); H(); + + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "VUSBRootHub"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, DeviceType_USB, 0, 0, NULL, NULL, NULL, 0); + } +#endif + else if (enmCtrlType == USBControllerType_XHCI) + { + InsertConfigNode(pDevices, "usb-xhci", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice("usb-xhci", pInst); H(); + + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "VUSBRootHub"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + InsertConfigNode(pInst, "LUN#1", &pLunL1); + InsertConfigString(pLunL1, "Driver", "VUSBRootHub"); + InsertConfigNode(pLunL1, "Config", &pCfg); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, DeviceType_USB, 0, 1, NULL, NULL, NULL, 0); + } + } /* for every USB controller. */ + + + /* + * Virtual USB Devices. + */ + InsertConfigNode(pRoot, "USB", &pUsbDevices); + +#ifdef VBOX_WITH_USB + { + /* + * Global USB options, currently unused as we'll apply the 2.0 -> 1.1 morphing + * on a per device level now. + */ + InsertConfigNode(pUsbDevices, "USBProxy", &pCfg); + InsertConfigNode(pCfg, "GlobalConfig", &pCfg); + // This globally enables the 2.0 -> 1.1 device morphing of proxied devices to keep windows quiet. + //InsertConfigInteger(pCfg, "Force11Device", true); + // The following breaks stuff, but it makes MSDs work in vista. (I include it here so + // that it's documented somewhere.) Users needing it can use: + // VBoxManage setextradata "myvm" "VBoxInternal/USB/USBProxy/GlobalConfig/Force11PacketSize" 1 + //InsertConfigInteger(pCfg, "Force11PacketSize", true); + } +#endif + +#ifdef VBOX_WITH_USB_CARDREADER + BOOL aEmulatedUSBCardReaderEnabled = FALSE; + hrc = pMachine->COMGETTER(EmulatedUSBCardReaderEnabled)(&aEmulatedUSBCardReaderEnabled); H(); + if (aEmulatedUSBCardReaderEnabled) + { + InsertConfigNode(pUsbDevices, "CardReader", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + InsertConfigNode(pInst, "LUN#0", &pLunL0); +# ifdef VBOX_WITH_USB_CARDREADER_TEST + InsertConfigString(pLunL0, "Driver", "DrvDirectCardReader"); + InsertConfigNode(pLunL0, "Config", &pCfg); +# else + InsertConfigString(pLunL0, "Driver", "UsbCardReader"); + InsertConfigNode(pLunL0, "Config", &pCfg); +# endif + } +#endif + + /* Virtual USB Mouse/Tablet */ + if ( aPointingHID == PointingHIDType_USBMouse + || aPointingHID == PointingHIDType_USBTablet + || aPointingHID == PointingHIDType_USBMultiTouch + || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad) + { + InsertConfigNode(pUsbDevices, "HidMouse", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + if (aPointingHID == PointingHIDType_USBMouse) + InsertConfigString(pCfg, "Mode", "relative"); + else + InsertConfigString(pCfg, "Mode", "absolute"); + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MouseQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 128); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainMouse"); + } + if ( aPointingHID == PointingHIDType_USBMultiTouch + || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad) + { + InsertConfigNode(pDev, "1", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + InsertConfigString(pCfg, "Mode", "multitouch"); + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MouseQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 128); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainMouse"); + } + if (aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad) + { + InsertConfigNode(pDev, "2", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + InsertConfigString(pCfg, "Mode", "touchpad"); + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MouseQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 128); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainMouse"); + } + + /* Virtual USB Keyboard */ + if (aKbdHID == KeyboardHIDType_USBKeyboard) + { + InsertConfigNode(pUsbDevices, "HidKeyboard", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "KeyboardQueue"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "QueueSize", 64); + + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "MainKeyboard"); + } + } + + /* + * Storage controllers. + */ + com::SafeIfaceArray<IStorageController> ctrls; + PCFGMNODE aCtrlNodes[StorageControllerType_VirtioSCSI + 1] = {}; + hrc = pMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); H(); + + bool fFdcEnabled = false; + for (size_t i = 0; i < ctrls.size(); ++i) + { + DeviceType_T *paLedDevType = NULL; + + StorageControllerType_T enmCtrlType; + hrc = ctrls[i]->COMGETTER(ControllerType)(&enmCtrlType); H(); + AssertRelease((unsigned)enmCtrlType < RT_ELEMENTS(aCtrlNodes) + || enmCtrlType == StorageControllerType_USB); + + StorageBus_T enmBus; + hrc = ctrls[i]->COMGETTER(Bus)(&enmBus); H(); + + Bstr controllerName; + hrc = ctrls[i]->COMGETTER(Name)(controllerName.asOutParam()); H(); + + ULONG ulInstance = 999; + hrc = ctrls[i]->COMGETTER(Instance)(&ulInstance); H(); + + BOOL fUseHostIOCache; + hrc = ctrls[i]->COMGETTER(UseHostIOCache)(&fUseHostIOCache); H(); + + BOOL fBootable; + hrc = ctrls[i]->COMGETTER(Bootable)(&fBootable); H(); + + PCFGMNODE pCtlInst = NULL; + const char *pszCtrlDev = i_storageControllerTypeToStr(enmCtrlType); + if (enmCtrlType != StorageControllerType_USB) + { + /* /Devices/<ctrldev>/ */ + pDev = aCtrlNodes[enmCtrlType]; + if (!pDev) + { + InsertConfigNode(pDevices, pszCtrlDev, &pDev); + aCtrlNodes[enmCtrlType] = pDev; /* IDE variants are handled in the switch */ + } + + /* /Devices/<ctrldev>/<instance>/ */ + InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pCtlInst); + + /* Device config: /Devices/<ctrldev>/<instance>/<values> & /ditto/Config/<values> */ + InsertConfigInteger(pCtlInst, "Trusted", 1); + InsertConfigNode(pCtlInst, "Config", &pCfg); + } + + static const char * const apszBiosConfigScsi[MAX_BIOS_LUN_COUNT] = + { "ScsiLUN1", "ScsiLUN2", "ScsiLUN3", "ScsiLUN4" }; + + static const char * const apszBiosConfigSata[MAX_BIOS_LUN_COUNT] = + { "SataLUN1", "SataLUN2", "SataLUN3", "SataLUN4" }; + + switch (enmCtrlType) + { + case StorageControllerType_LsiLogic: + { + hrc = pBusMgr->assignPCIDevice("lsilogic", pCtlInst); H(); + + InsertConfigInteger(pCfg, "Bootable", fBootable); + + /* BIOS configuration values, first SCSI controller only. */ + if ( !pBusMgr->hasPCIDevice("lsilogic", 1) + && !pBusMgr->hasPCIDevice("buslogic", 0) + && !pBusMgr->hasPCIDevice("lsilogicsas", 0) + && pBiosCfg) + { + InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicscsi"); + hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H(); + } + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 15, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_BusLogic: + { + hrc = pBusMgr->assignPCIDevice("buslogic", pCtlInst); H(); + + InsertConfigInteger(pCfg, "Bootable", fBootable); + + /* BIOS configuration values, first SCSI controller only. */ + if ( !pBusMgr->hasPCIDevice("lsilogic", 0) + && !pBusMgr->hasPCIDevice("buslogic", 1) + && !pBusMgr->hasPCIDevice("lsilogicsas", 0) + && pBiosCfg) + { + InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "buslogic"); + hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H(); + } + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 15, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_IntelAhci: + { + hrc = pBusMgr->assignPCIDevice("ahci", pCtlInst); H(); + + ULONG cPorts = 0; + hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H(); + InsertConfigInteger(pCfg, "PortCount", cPorts); + InsertConfigInteger(pCfg, "Bootable", fBootable); + + com::SafeIfaceArray<IMediumAttachment> atts; + hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(), + ComSafeArrayAsOutParam(atts)); H(); + + /* Configure the hotpluggable flag for the port. */ + for (unsigned idxAtt = 0; idxAtt < atts.size(); ++idxAtt) + { + IMediumAttachment *pMediumAtt = atts[idxAtt]; + + LONG lPortNum = 0; + hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H(); + + BOOL fHotPluggable = FALSE; + hrc = pMediumAtt->COMGETTER(HotPluggable)(&fHotPluggable); H(); + if (SUCCEEDED(hrc)) + { + PCFGMNODE pPortCfg; + char szName[24]; + RTStrPrintf(szName, sizeof(szName), "Port%d", lPortNum); + + InsertConfigNode(pCfg, szName, &pPortCfg); + InsertConfigInteger(pPortCfg, "Hotpluggable", fHotPluggable ? 1 : 0); + } + } + + /* BIOS configuration values, first AHCI controller only. */ + if ( !pBusMgr->hasPCIDevice("ahci", 1) + && pBiosCfg) + { + InsertConfigString(pBiosCfg, "SataHardDiskDevice", "ahci"); + hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigSata); H(); + } + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_PIIX3: + case StorageControllerType_PIIX4: + case StorageControllerType_ICH6: + { + /* + * IDE (update this when the main interface changes) + */ + hrc = pBusMgr->assignPCIDevice("piix3ide", pCtlInst); H(); + InsertConfigString(pCfg, "Type", controllerString(enmCtrlType)); + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 3, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + + /* IDE flavors */ + aCtrlNodes[StorageControllerType_PIIX3] = pDev; + aCtrlNodes[StorageControllerType_PIIX4] = pDev; + aCtrlNodes[StorageControllerType_ICH6] = pDev; + break; + } + + case StorageControllerType_I82078: + { + /* + * i82078 Floppy drive controller + */ + fFdcEnabled = true; + InsertConfigInteger(pCfg, "IRQ", 6); + InsertConfigInteger(pCfg, "DMA", 2); + InsertConfigInteger(pCfg, "MemMapped", 0 ); + InsertConfigInteger(pCfg, "IOBase", 0x3f0); + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_Floppy, 0, 1, NULL, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_LsiLogicSas: + { + hrc = pBusMgr->assignPCIDevice("lsilogicsas", pCtlInst); H(); + + InsertConfigString(pCfg, "ControllerType", "SAS1068"); + InsertConfigInteger(pCfg, "Bootable", fBootable); + + /* BIOS configuration values, first SCSI controller only. */ + if ( !pBusMgr->hasPCIDevice("lsilogic", 0) + && !pBusMgr->hasPCIDevice("buslogic", 0) + && !pBusMgr->hasPCIDevice("lsilogicsas", 1) + && pBiosCfg) + { + InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicsas"); + hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H(); + } + + ULONG cPorts = 0; + hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H(); + InsertConfigInteger(pCfg, "NumPorts", cPorts); + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 7, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_USB: + { + if (pUsbDevices) + { + /* + * USB MSDs are handled a bit different as the device instance + * doesn't match the storage controller instance but the port. + */ + InsertConfigNode(pUsbDevices, "Msd", &pDev); + pCtlInst = pDev; + } + else + return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("There is no USB controller enabled but there\n" + "is at least one USB storage device configured for this VM.\n" + "To fix this problem either enable the USB controller or remove\n" + "the storage device from the VM")); + break; + } + + case StorageControllerType_NVMe: + { + hrc = pBusMgr->assignPCIDevice("nvme", pCtlInst); H(); + + ULONG cPorts = 0; + hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H(); + InsertConfigInteger(pCfg, "NamespacesMax", cPorts); + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, NULL, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + case StorageControllerType_VirtioSCSI: + { + hrc = pBusMgr->assignPCIDevice("virtio-scsi", pCtlInst); H(); + + ULONG cPorts = 0; + hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H(); + InsertConfigInteger(pCfg, "NumTargets", cPorts); + InsertConfigInteger(pCfg, "Bootable", fBootable); + + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, cPorts - 1, &paLedDevType, + &mapMediumAttachments, pszCtrlDev, ulInstance); + break; + } + + default: + AssertLogRelMsgFailedReturn(("invalid storage controller type: %d\n", enmCtrlType), VERR_MAIN_CONFIG_CONSTRUCTOR_IPE); + } + + /* Attach the media to the storage controllers. */ + com::SafeIfaceArray<IMediumAttachment> atts; + hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(), + ComSafeArrayAsOutParam(atts)); H(); + + /* Builtin I/O cache - per device setting. */ + BOOL fBuiltinIOCache = true; + hrc = pMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache); H(); + + bool fInsertDiskIntegrityDrv = false; + Bstr strDiskIntegrityFlag; + hrc = pMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(), + strDiskIntegrityFlag.asOutParam()); + if ( hrc == S_OK + && strDiskIntegrityFlag == "1") + fInsertDiskIntegrityDrv = true; + + for (size_t j = 0; j < atts.size(); ++j) + { + IMediumAttachment *pMediumAtt = atts[j]; + vrc = i_configMediumAttachment(pszCtrlDev, + ulInstance, + enmBus, + !!fUseHostIOCache, + enmCtrlType == StorageControllerType_NVMe ? false : !!fBuiltinIOCache, + fInsertDiskIntegrityDrv, + false /* fSetupMerge */, + 0 /* uMergeSource */, + 0 /* uMergeTarget */, + pMediumAtt, + mMachineState, + NULL /* phrc */, + false /* fAttachDetach */, + false /* fForceUnmount */, + false /* fHotplug */, + pUVM, + pVMM, + paLedDevType, + NULL /* ppLunL0 */); + if (RT_FAILURE(vrc)) + return vrc; + } + H(); + } + H(); + + /* + * Network adapters + */ +#ifdef VMWARE_NET_IN_SLOT_11 + bool fSwapSlots3and11 = false; +#endif + PCFGMNODE pDevPCNet = NULL; /* PCNet-type devices */ + InsertConfigNode(pDevices, "pcnet", &pDevPCNet); +#ifdef VBOX_WITH_E1000 + PCFGMNODE pDevE1000 = NULL; /* E1000-type devices */ + InsertConfigNode(pDevices, "e1000", &pDevE1000); +#endif +#ifdef VBOX_WITH_VIRTIO + PCFGMNODE pDevVirtioNet = NULL; /* Virtio network devices */ + InsertConfigNode(pDevices, "virtio-net", &pDevVirtioNet); +#endif /* VBOX_WITH_VIRTIO */ + PCFGMNODE pDevDP8390 = NULL; /* DP8390-type devices */ + InsertConfigNode(pDevices, "dp8390", &pDevDP8390); + PCFGMNODE pDev3C501 = NULL; /* EtherLink-type devices */ + InsertConfigNode(pDevices, "3c501", &pDev3C501); + + std::list<BootNic> llBootNics; + for (ULONG uInstance = 0; uInstance < maxNetworkAdapters; ++uInstance) + { + ComPtr<INetworkAdapter> networkAdapter; + hrc = pMachine->GetNetworkAdapter(uInstance, networkAdapter.asOutParam()); H(); + BOOL fEnabledNetAdapter = FALSE; + hrc = networkAdapter->COMGETTER(Enabled)(&fEnabledNetAdapter); H(); + if (!fEnabledNetAdapter) + continue; + + /* + * The virtual hardware type. Create appropriate device first. + */ + const char *pszAdapterName = "pcnet"; + NetworkAdapterType_T adapterType; + hrc = networkAdapter->COMGETTER(AdapterType)(&adapterType); H(); + switch (adapterType) + { + case NetworkAdapterType_Am79C970A: + case NetworkAdapterType_Am79C973: + case NetworkAdapterType_Am79C960: + pDev = pDevPCNet; + break; +#ifdef VBOX_WITH_E1000 + case NetworkAdapterType_I82540EM: + case NetworkAdapterType_I82543GC: + case NetworkAdapterType_I82545EM: + pDev = pDevE1000; + pszAdapterName = "e1000"; + break; +#endif +#ifdef VBOX_WITH_VIRTIO + case NetworkAdapterType_Virtio: + pDev = pDevVirtioNet; + pszAdapterName = "virtio-net"; + break; +#endif /* VBOX_WITH_VIRTIO */ + case NetworkAdapterType_NE1000: + case NetworkAdapterType_NE2000: + case NetworkAdapterType_WD8003: + case NetworkAdapterType_WD8013: + case NetworkAdapterType_ELNK2: + pDev = pDevDP8390; + break; + case NetworkAdapterType_ELNK1: + pDev = pDev3C501; + break; + default: + AssertMsgFailed(("Invalid network adapter type '%d' for slot '%d'", adapterType, uInstance)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Invalid network adapter type '%d' for slot '%d'"), adapterType, uInstance); + } + + InsertConfigNode(pDev, Utf8StrFmt("%u", uInstance).c_str(), &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + /* the first network card gets the PCI ID 3, the next 3 gets 8..10, + * next 4 get 16..19. */ + int iPCIDeviceNo; + switch (uInstance) + { + case 0: + iPCIDeviceNo = 3; + break; + case 1: case 2: case 3: + iPCIDeviceNo = uInstance - 1 + 8; + break; + case 4: case 5: case 6: case 7: + iPCIDeviceNo = uInstance - 4 + 16; + break; + default: + /* auto assignment */ + iPCIDeviceNo = -1; + break; + } +#ifdef VMWARE_NET_IN_SLOT_11 + /* + * Dirty hack for PCI slot compatibility with VMWare, + * it assigns slot 0x11 to the first network controller. + */ + if (iPCIDeviceNo == 3 && adapterType == NetworkAdapterType_I82545EM) + { + iPCIDeviceNo = 0x11; + fSwapSlots3and11 = true; + } + else if (iPCIDeviceNo == 0x11 && fSwapSlots3and11) + iPCIDeviceNo = 3; +#endif + PCIBusAddress PCIAddr = PCIBusAddress(0, iPCIDeviceNo, 0); + hrc = pBusMgr->assignPCIDevice(pszAdapterName, pInst, PCIAddr); H(); + + InsertConfigNode(pInst, "Config", &pCfg); +#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE /* not safe here yet. */ /** @todo Make PCNet ring-0 safe on 32-bit mac kernels! */ + if (pDev == pDevPCNet) + InsertConfigInteger(pCfg, "R0Enabled", false); +#endif + /* + * Collect information needed for network booting and add it to the list. + */ + BootNic nic; + + nic.mInstance = uInstance; + /* Could be updated by reference, if auto assigned */ + nic.mPCIAddress = PCIAddr; + + hrc = networkAdapter->COMGETTER(BootPriority)(&nic.mBootPrio); H(); + + llBootNics.push_back(nic); + + /* + * The virtual hardware type. PCNet supports three types, E1000 three, + * but VirtIO only one. + */ + switch (adapterType) + { + case NetworkAdapterType_Am79C970A: + InsertConfigString(pCfg, "ChipType", "Am79C970A"); + break; + case NetworkAdapterType_Am79C973: + InsertConfigString(pCfg, "ChipType", "Am79C973"); + break; + case NetworkAdapterType_Am79C960: + InsertConfigString(pCfg, "ChipType", "Am79C960"); + break; + case NetworkAdapterType_I82540EM: + InsertConfigInteger(pCfg, "AdapterType", 0); + break; + case NetworkAdapterType_I82543GC: + InsertConfigInteger(pCfg, "AdapterType", 1); + break; + case NetworkAdapterType_I82545EM: + InsertConfigInteger(pCfg, "AdapterType", 2); + break; + case NetworkAdapterType_Virtio: + break; + case NetworkAdapterType_NE1000: + InsertConfigString(pCfg, "DeviceType", "NE1000"); + break; + case NetworkAdapterType_NE2000: + InsertConfigString(pCfg, "DeviceType", "NE2000"); + break; + case NetworkAdapterType_WD8003: + InsertConfigString(pCfg, "DeviceType", "WD8003"); + break; + case NetworkAdapterType_WD8013: + InsertConfigString(pCfg, "DeviceType", "WD8013"); + break; + case NetworkAdapterType_ELNK2: + InsertConfigString(pCfg, "DeviceType", "3C503"); + break; + case NetworkAdapterType_ELNK1: + break; + case NetworkAdapterType_Null: AssertFailedBreak(); /* (compiler warnings) */ +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case NetworkAdapterType_32BitHack: AssertFailedBreak(); /* (compiler warnings) */ +#endif + } + + /* + * Get the MAC address and convert it to binary representation + */ + Bstr macAddr; + hrc = networkAdapter->COMGETTER(MACAddress)(macAddr.asOutParam()); H(); + Assert(!macAddr.isEmpty()); + Utf8Str macAddrUtf8 = macAddr; +#ifdef VBOX_WITH_CLOUD_NET + NetworkAttachmentType_T eAttachmentType; + hrc = networkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H(); + if (eAttachmentType == NetworkAttachmentType_Cloud) + { + mGateway.setLocalMacAddress(macAddrUtf8); + /* We'll insert cloud MAC later, when it becomes known. */ + } + else + { +#endif + char *macStr = (char*)macAddrUtf8.c_str(); + Assert(strlen(macStr) == 12); + RTMAC Mac; + RT_ZERO(Mac); + char *pMac = (char*)&Mac; + for (uint32_t i = 0; i < 6; ++i) + { + int c1 = *macStr++ - '0'; + if (c1 > 9) + c1 -= 7; + int c2 = *macStr++ - '0'; + if (c2 > 9) + c2 -= 7; + *pMac++ = (char)(((c1 & 0x0f) << 4) | (c2 & 0x0f)); + } + InsertConfigBytes(pCfg, "MAC", &Mac, sizeof(Mac)); +#ifdef VBOX_WITH_CLOUD_NET + } +#endif + /* + * Check if the cable is supposed to be unplugged + */ + BOOL fCableConnected; + hrc = networkAdapter->COMGETTER(CableConnected)(&fCableConnected); H(); + InsertConfigInteger(pCfg, "CableConnected", fCableConnected ? 1 : 0); + + /* + * Line speed to report from custom drivers + */ + ULONG ulLineSpeed; + hrc = networkAdapter->COMGETTER(LineSpeed)(&ulLineSpeed); H(); + InsertConfigInteger(pCfg, "LineSpeed", ulLineSpeed); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, DeviceType_Network, 0, 0, NULL, NULL, NULL, 0); + + /* + * Configure the network card now + */ + bool fIgnoreConnectFailure = mMachineState == MachineState_Restoring; + vrc = i_configNetwork(pszAdapterName, + uInstance, + 0, + networkAdapter, + pCfg, + pLunL0, + pInst, + false /*fAttachDetach*/, + fIgnoreConnectFailure, + pUVM, + pVMM); + if (RT_FAILURE(vrc)) + return vrc; + } + + /* + * Build network boot information and transfer it to the BIOS. + */ + if (pNetBootCfg && !llBootNics.empty()) /* NetBoot node doesn't exist for EFI! */ + { + llBootNics.sort(); /* Sort the list by boot priority. */ + + char achBootIdx[] = "0"; + unsigned uBootIdx = 0; + + for (std::list<BootNic>::iterator it = llBootNics.begin(); it != llBootNics.end(); ++it) + { + /* A NIC with priority 0 is only used if it's first in the list. */ + if (it->mBootPrio == 0 && uBootIdx != 0) + break; + + PCFGMNODE pNetBtDevCfg; + achBootIdx[0] = (char)('0' + uBootIdx++); /* Boot device order. */ + InsertConfigNode(pNetBootCfg, achBootIdx, &pNetBtDevCfg); + InsertConfigInteger(pNetBtDevCfg, "NIC", it->mInstance); + InsertConfigInteger(pNetBtDevCfg, "PCIBusNo", it->mPCIAddress.miBus); + InsertConfigInteger(pNetBtDevCfg, "PCIDeviceNo", it->mPCIAddress.miDevice); + InsertConfigInteger(pNetBtDevCfg, "PCIFunctionNo", it->mPCIAddress.miFn); + } + } + + /* + * Serial (UART) Ports + */ + /* serial enabled mask to be passed to dev ACPI */ + uint16_t auSerialIoPortBase[SchemaDefs::SerialPortCount] = {0}; + uint8_t auSerialIrq[SchemaDefs::SerialPortCount] = {0}; + InsertConfigNode(pDevices, "serial", &pDev); + for (ULONG ulInstance = 0; ulInstance < SchemaDefs::SerialPortCount; ++ulInstance) + { + ComPtr<ISerialPort> serialPort; + hrc = pMachine->GetSerialPort(ulInstance, serialPort.asOutParam()); H(); + BOOL fEnabledSerPort = FALSE; + if (serialPort) + { + hrc = serialPort->COMGETTER(Enabled)(&fEnabledSerPort); H(); + } + if (!fEnabledSerPort) + { + m_aeSerialPortMode[ulInstance] = PortMode_Disconnected; + continue; + } + + InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + + ULONG ulIRQ; + hrc = serialPort->COMGETTER(IRQ)(&ulIRQ); H(); + InsertConfigInteger(pCfg, "IRQ", ulIRQ); + auSerialIrq[ulInstance] = (uint8_t)ulIRQ; + + ULONG ulIOBase; + hrc = serialPort->COMGETTER(IOBase)(&ulIOBase); H(); + InsertConfigInteger(pCfg, "IOBase", ulIOBase); + auSerialIoPortBase[ulInstance] = (uint16_t)ulIOBase; + + BOOL fServer; + hrc = serialPort->COMGETTER(Server)(&fServer); H(); + hrc = serialPort->COMGETTER(Path)(bstr.asOutParam()); H(); + UartType_T eUartType; + const char *pszUartType; + hrc = serialPort->COMGETTER(UartType)(&eUartType); H(); + switch (eUartType) + { + case UartType_U16450: pszUartType = "16450"; break; + case UartType_U16750: pszUartType = "16750"; break; + default: AssertFailed(); RT_FALL_THRU(); + case UartType_U16550A: pszUartType = "16550A"; break; + } + InsertConfigString(pCfg, "UartType", pszUartType); + + PortMode_T eHostMode; + hrc = serialPort->COMGETTER(HostMode)(&eHostMode); H(); + + m_aeSerialPortMode[ulInstance] = eHostMode; + if (eHostMode != PortMode_Disconnected) + { + vrc = i_configSerialPort(pInst, eHostMode, Utf8Str(bstr).c_str(), RT_BOOL(fServer)); + if (RT_FAILURE(vrc)) + return vrc; + } + } + + /* + * Parallel (LPT) Ports + */ + /* parallel enabled mask to be passed to dev ACPI */ + uint16_t auParallelIoPortBase[SchemaDefs::ParallelPortCount] = {0}; + uint8_t auParallelIrq[SchemaDefs::ParallelPortCount] = {0}; + InsertConfigNode(pDevices, "parallel", &pDev); + for (ULONG ulInstance = 0; ulInstance < SchemaDefs::ParallelPortCount; ++ulInstance) + { + ComPtr<IParallelPort> parallelPort; + hrc = pMachine->GetParallelPort(ulInstance, parallelPort.asOutParam()); H(); + BOOL fEnabledParPort = FALSE; + if (parallelPort) + { + hrc = parallelPort->COMGETTER(Enabled)(&fEnabledParPort); H(); + } + if (!fEnabledParPort) + continue; + + InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + + ULONG ulIRQ; + hrc = parallelPort->COMGETTER(IRQ)(&ulIRQ); H(); + InsertConfigInteger(pCfg, "IRQ", ulIRQ); + auParallelIrq[ulInstance] = (uint8_t)ulIRQ; + ULONG ulIOBase; + hrc = parallelPort->COMGETTER(IOBase)(&ulIOBase); H(); + InsertConfigInteger(pCfg, "IOBase", ulIOBase); + auParallelIoPortBase[ulInstance] = (uint16_t)ulIOBase; + + hrc = parallelPort->COMGETTER(Path)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + { + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "HostParallel"); + InsertConfigNode(pLunL0, "Config", &pLunL1); + InsertConfigString(pLunL1, "DevicePath", bstr); + } + } + + /* + * VMM Device + */ + InsertConfigNode(pDevices, "VMMDev", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice("VMMDev", pInst); H(); + + Bstr hwVersion; + hrc = pMachine->COMGETTER(HardwareVersion)(hwVersion.asOutParam()); H(); + if (hwVersion.compare(Bstr("1").raw()) == 0) /* <= 2.0.x */ + InsertConfigInteger(pCfg, "HeapEnabled", 0); + Bstr snapshotFolder; + hrc = pMachine->COMGETTER(SnapshotFolder)(snapshotFolder.asOutParam()); H(); + InsertConfigString(pCfg, "GuestCoreDumpDir", snapshotFolder); + + /* the VMM device's Main driver */ + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "HGCM"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, DeviceType_SharedFolder, 0, 0, NULL, NULL, NULL, 0); + + /* + * Audio configuration. + */ + + /* + * AC'97 ICH / SoundBlaster16 audio / Intel HD Audio. + */ + ComPtr<IAudioSettings> audioSettings; + hrc = pMachine->COMGETTER(AudioSettings)(audioSettings.asOutParam()); H(); + + BOOL fAudioEnabled = FALSE; + ComPtr<IAudioAdapter> audioAdapter; + hrc = audioSettings->COMGETTER(Adapter)(audioAdapter.asOutParam()); H(); + if (audioAdapter) + { + hrc = audioAdapter->COMGETTER(Enabled)(&fAudioEnabled); H(); + } + + if (fAudioEnabled) + { + AudioControllerType_T enmAudioController; + hrc = audioAdapter->COMGETTER(AudioController)(&enmAudioController); H(); + AudioCodecType_T enmAudioCodec; + hrc = audioAdapter->COMGETTER(AudioCodec)(&enmAudioCodec); H(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/TimerHz", &strTmp); + const uint64_t uTimerHz = strTmp.toUInt64(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeInMs", &strTmp); + const uint64_t uBufSizeInMs = strTmp.toUInt64(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeOutMs", &strTmp); + const uint64_t uBufSizeOutMs = strTmp.toUInt64(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp); + const bool fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1"); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Level", &strTmp); + const uint32_t uDebugLevel = strTmp.toUInt32(); + + Utf8Str strDebugPathOut; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut); + +#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/VaKit/Enabled", &strTmp); /* Deprecated; do not use! */ + if (strTmp.isEmpty()) + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/ValKit/Enabled", &strTmp); + /* Whether the Validation Kit audio backend runs as the primary backend. + * Can also be used with VBox release builds. */ + const bool fValKitEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1"); +#endif + /** @todo Implement an audio device class, similar to the audio backend class, to construct the common stuff + * without duplicating (more) code. */ + + const char *pszAudioDevice; + switch (enmAudioController) + { + case AudioControllerType_AC97: + { + /* ICH AC'97. */ + pszAudioDevice = "ichac97"; + + InsertConfigNode(pDevices, pszAudioDevice, &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H(); + InsertConfigNode(pInst, "Config", &pCfg); + switch (enmAudioCodec) + { + case AudioCodecType_STAC9700: + InsertConfigString(pCfg, "Codec", "STAC9700"); + break; + case AudioCodecType_AD1980: + InsertConfigString(pCfg, "Codec", "AD1980"); + break; + default: AssertFailedBreak(); + } + if (uTimerHz) + InsertConfigInteger(pCfg, "TimerHz", uTimerHz); + if (uBufSizeInMs) + InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs); + if (uBufSizeOutMs) + InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs); + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + if (strDebugPathOut.isNotEmpty()) + InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut); + break; + } + case AudioControllerType_SB16: + { + /* Legacy SoundBlaster16. */ + pszAudioDevice = "sb16"; + + InsertConfigNode(pDevices, pszAudioDevice, &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "IRQ", 5); + InsertConfigInteger(pCfg, "DMA", 1); + InsertConfigInteger(pCfg, "DMA16", 5); + InsertConfigInteger(pCfg, "Port", 0x220); + InsertConfigInteger(pCfg, "Version", 0x0405); + if (uTimerHz) + InsertConfigInteger(pCfg, "TimerHz", uTimerHz); + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + if (strDebugPathOut.isNotEmpty()) + InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut); + break; + } + case AudioControllerType_HDA: + { + /* Intel HD Audio. */ + pszAudioDevice = "hda"; + + InsertConfigNode(pDevices, pszAudioDevice, &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H(); + InsertConfigNode(pInst, "Config", &pCfg); + if (uBufSizeInMs) + InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs); + if (uBufSizeOutMs) + InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs); + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + if (strDebugPathOut.isNotEmpty()) + InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut); + + /* macOS guests uses a different HDA variant to make 10.14+ (or maybe 10.13?) recognize the device. */ + if (fOsXGuest) + InsertConfigString(pCfg, "DeviceName", "Intel Sunrise Point"); + break; + } + default: + pszAudioDevice = "oops"; + AssertFailedBreak(); + } + + PCFGMNODE pCfgAudioAdapter = NULL; + InsertConfigNode(pInst, "AudioConfig", &pCfgAudioAdapter); + SafeArray<BSTR> audioProps; + hrc = audioAdapter->COMGETTER(PropertiesList)(ComSafeArrayAsOutParam(audioProps)); H(); + + std::list<Utf8Str> audioPropertyNamesList; + for (size_t i = 0; i < audioProps.size(); ++i) + { + Bstr bstrValue; + audioPropertyNamesList.push_back(Utf8Str(audioProps[i])); + hrc = audioAdapter->GetProperty(audioProps[i], bstrValue.asOutParam()); + Utf8Str strKey(audioProps[i]); + InsertConfigString(pCfgAudioAdapter, strKey.c_str(), bstrValue); + } + + /* + * The audio driver. + */ + const char *pszAudioDriver = NULL; +#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + if (fValKitEnabled) + { + pszAudioDriver = "ValidationKitAudio"; + LogRel(("Audio: ValidationKit driver active\n")); + } +#endif + /* If nothing else was selected before, ask the API. */ + if (pszAudioDriver == NULL) + { + AudioDriverType_T enmAudioDriver; + hrc = audioAdapter->COMGETTER(AudioDriver)(&enmAudioDriver); H(); + + /* The "Default" audio driver needs special treatment, as we need to figure out which driver to use + * by default on the current platform. */ + bool const fUseDefaultDrv = enmAudioDriver == AudioDriverType_Default; + + AudioDriverType_T const enmDefaultAudioDriver = settings::MachineConfigFile::getHostDefaultAudioDriver(); + + if (fUseDefaultDrv) + { + enmAudioDriver = enmDefaultAudioDriver; + if (enmAudioDriver == AudioDriverType_Null) + LogRel(("Audio: Warning: No default driver detected for current platform -- defaulting to Null audio backend\n")); + } + + switch (enmAudioDriver) + { + case AudioDriverType_Default: /* Can't happen, but handle it anyway. */ + RT_FALL_THROUGH(); + case AudioDriverType_Null: + pszAudioDriver = "NullAudio"; + break; +#ifdef RT_OS_WINDOWS +# ifdef VBOX_WITH_WINMM + case AudioDriverType_WinMM: +# error "Port WinMM audio backend!" /** @todo Still needed? */ + break; +# endif + case AudioDriverType_DirectSound: + /* Use the Windows Audio Session (WAS) API rather than Direct Sound on Windows + versions we've tested it on (currently W7+). Since Vista, Direct Sound has + been emulated on top of WAS according to the docs, so better use WAS directly. + + Set extradata value "VBoxInternal2/Audio/WindowsDrv" "dsound" to no use WasAPI. + + Keep this hack for backwards compatibility (introduced < 7.0). + */ + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/WindowsDrv", &strTmp); H(); + if ( enmDefaultAudioDriver == AudioDriverType_WAS + && ( strTmp.isEmpty() + || strTmp.equalsIgnoreCase("was") + || strTmp.equalsIgnoreCase("wasapi")) ) + { + /* Nothing to do here, fall through to WAS driver. */ + } + else + { + pszAudioDriver = "DSoundAudio"; + break; + } + RT_FALL_THROUGH(); + case AudioDriverType_WAS: + if (enmDefaultAudioDriver == AudioDriverType_WAS) /* WAS supported? */ + pszAudioDriver = "HostAudioWas"; + else if (enmDefaultAudioDriver == AudioDriverType_DirectSound) + { + LogRel(("Audio: Warning: Windows Audio Session (WAS) not supported, defaulting to DirectSound backend\n")); + pszAudioDriver = "DSoundAudio"; + } + break; +#endif /* RT_OS_WINDOWS */ +#ifdef RT_OS_SOLARIS + case AudioDriverType_SolAudio: + /* Should not happen, as the Solaris Audio backend is not around anymore. + * Remove this sometime later. */ + LogRel(("Audio: Warning: Solaris Audio is deprecated, please switch to OSS!\n")); + LogRel(("Audio: Automatically setting host audio backend to OSS\n")); + + /* Manually set backend to OSS for now. */ + pszAudioDriver = "OSSAudio"; + break; +#endif +#ifdef VBOX_WITH_AUDIO_OSS + case AudioDriverType_OSS: + pszAudioDriver = "OSSAudio"; + break; +#endif +#ifdef VBOX_WITH_AUDIO_ALSA + case AudioDriverType_ALSA: + pszAudioDriver = "ALSAAudio"; + break; +#endif +#ifdef VBOX_WITH_AUDIO_PULSE + case AudioDriverType_Pulse: + pszAudioDriver = "PulseAudio"; + break; +#endif +#ifdef RT_OS_DARWIN + case AudioDriverType_CoreAudio: + pszAudioDriver = "CoreAudio"; + break; +#endif + default: + pszAudioDriver = "oops"; + AssertFailedBreak(); + } + + if (fUseDefaultDrv) + LogRel(("Audio: Detected default audio driver type is '%s'\n", pszAudioDriver)); + } + + BOOL fAudioEnabledIn = FALSE; + hrc = audioAdapter->COMGETTER(EnabledIn)(&fAudioEnabledIn); H(); + BOOL fAudioEnabledOut = FALSE; + hrc = audioAdapter->COMGETTER(EnabledOut)(&fAudioEnabledOut); H(); + + unsigned idxAudioLun = 0; + + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun); + i_configAudioDriver(virtualBox, pMachine, pLunL0, pszAudioDriver, !!fAudioEnabledIn, !!fAudioEnabledOut); + idxAudioLun++; + +#ifdef VBOX_WITH_AUDIO_VRDE + /* Insert dummy audio driver to have the LUN configured. */ + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun); + InsertConfigString(pLunL0, "Driver", "AUDIO"); + AudioDriverCfg DrvCfgVRDE(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVRDE", + !!fAudioEnabledIn, !!fAudioEnabledOut); + vrc = mAudioVRDE->InitializeConfig(&DrvCfgVRDE); + AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "mAudioVRDE->InitializeConfig failed")); + idxAudioLun++; +#endif + +#ifdef VBOX_WITH_AUDIO_RECORDING + /* Insert dummy audio driver to have the LUN configured. */ + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun); + InsertConfigString(pLunL0, "Driver", "AUDIO"); + AudioDriverCfg DrvCfgVideoRec(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVideoRec", + false /*a_fEnabledIn*/, true /*a_fEnabledOut*/); + vrc = mRecording.mAudioRec->InitializeConfig(&DrvCfgVideoRec); + AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "Recording.mAudioRec->InitializeConfig failed")); + idxAudioLun++; +#endif + + if (fDebugEnabled) + { +#ifdef VBOX_WITH_AUDIO_DEBUG +# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + /* + * When both, ValidationKit and Debug mode (for audio) are enabled, + * skip configuring the Debug audio driver, as both modes can + * mess with the audio data and would lead to side effects. + * + * The ValidationKit audio driver has precedence over the Debug audio driver. + * + * This also can (and will) be used in VBox release builds. + */ + if (fValKitEnabled) + { + LogRel(("Audio: Warning: ValidationKit running and Debug mode enabled -- disabling Debug driver\n")); + } + else /* Debug mode active -- run both (nice for catching errors / doing development). */ + { + /* + * The ValidationKit backend. + */ + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun); + i_configAudioDriver(virtualBox, pMachine, pLunL0, "ValidationKitAudio", + !!fAudioEnabledIn, !!fAudioEnabledOut); + idxAudioLun++; +# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */ + /* + * The Debug audio backend. + */ + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun); + i_configAudioDriver(virtualBox, pMachine, pLunL0, "DebugAudio", + !!fAudioEnabledIn, !!fAudioEnabledOut); + idxAudioLun++; +# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + } +# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */ +#endif /* VBOX_WITH_AUDIO_DEBUG */ + + /* + * Tweak the logging groups. + */ + Utf8Str strGroups("drv_audio.e.l.l2.l3.f" + " audio_mixer.e.l.l2.l3.f" + " dev_hda_codec.e.l.l2.l3.f" + " dev_hda.e.l.l2.l3.f" + " dev_ac97.e.l.l2.l3.f" + " dev_sb16.e.l.l2.l3.f"); + + LogRel(("Audio: Debug level set to %RU32\n", uDebugLevel)); + + switch (uDebugLevel) + { + case 0: + strGroups += " drv_host_audio.e.l.l2.l3.f"; + break; + case 1: + RT_FALL_THROUGH(); + case 2: + RT_FALL_THROUGH(); + case 3: + strGroups += " drv_host_audio.e.l.l2.l3.f+audio_test.e.l.l2.l3.f"; + break; + case 4: + RT_FALL_THROUGH(); + default: + strGroups += " drv_host_audio.e.l.l2.l3.l4.f+audio_test.e.l.l2.l3.l4.f"; + break; + } + + vrc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), strGroups.c_str()); + if (RT_FAILURE(vrc)) + LogRel(("Audio: Setting debug logging failed, vrc=%Rrc\n", vrc)); + } + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD + /* + * Shared Clipboard. + */ + { + ClipboardMode_T enmClipboardMode = ClipboardMode_Disabled; + hrc = pMachine->COMGETTER(ClipboardMode)(&enmClipboardMode); H(); +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + BOOL fFileTransfersEnabled; + hrc = pMachine->COMGETTER(ClipboardFileTransfersEnabled)(&fFileTransfersEnabled); H(); +#endif + + /* Load the service */ + vrc = pVMMDev->hgcmLoadService("VBoxSharedClipboard", "VBoxSharedClipboard"); + if (RT_SUCCESS(vrc)) + { + LogRel(("Shared Clipboard: Service loaded\n")); + + /* Set initial clipboard mode. */ + vrc = i_changeClipboardMode(enmClipboardMode); + AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial clipboard mode (%d): vrc=%Rrc\n", + enmClipboardMode, vrc)); + + /* Setup the service. */ + VBOXHGCMSVCPARM parm; + HGCMSvcSetU32(&parm, !i_useHostClipboard()); + vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &parm); + AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial headless mode (%RTbool): vrc=%Rrc\n", + !i_useHostClipboard(), vrc)); + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + vrc = i_changeClipboardFileTransferMode(RT_BOOL(fFileTransfersEnabled)); + AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial file transfers mode (%u): vrc=%Rrc\n", + fFileTransfersEnabled, vrc)); + + /** @todo Register area callbacks? (See also deregistration todo in Console::i_powerDown.) */ +# endif + } + else + LogRel(("Shared Clipboard: Not available, vrc=%Rrc\n", vrc)); + vrc = VINF_SUCCESS; /* None of the potential failures above are fatal. */ + } +#endif /* VBOX_WITH_SHARED_CLIPBOARD */ + + /* + * HGCM HostChannel. + */ + { + Bstr value; + hrc = pMachine->GetExtraData(Bstr("HGCM/HostChannel").raw(), + value.asOutParam()); + + if ( hrc == S_OK + && value == "1") + { + vrc = pVMMDev->hgcmLoadService("VBoxHostChannel", "VBoxHostChannel"); + if (RT_FAILURE(vrc)) + { + LogRel(("VBoxHostChannel is not available, vrc=%Rrc\n", vrc)); + /* That is not a fatal failure. */ + vrc = VINF_SUCCESS; + } + } + } + +#ifdef VBOX_WITH_DRAG_AND_DROP + /* + * Drag and Drop. + */ + { + DnDMode_T enmMode = DnDMode_Disabled; + hrc = pMachine->COMGETTER(DnDMode)(&enmMode); H(); + + /* Load the service */ + vrc = pVMMDev->hgcmLoadService("VBoxDragAndDropSvc", "VBoxDragAndDropSvc"); + if (RT_FAILURE(vrc)) + { + LogRel(("Drag and drop service is not available, vrc=%Rrc\n", vrc)); + /* That is not a fatal failure. */ + vrc = VINF_SUCCESS; + } + else + { + vrc = HGCMHostRegisterServiceExtension(&m_hHgcmSvcExtDragAndDrop, "VBoxDragAndDropSvc", + &GuestDnD::notifyDnDDispatcher, + GuestDnDInst()); + if (RT_FAILURE(vrc)) + Log(("Cannot register VBoxDragAndDropSvc extension, vrc=%Rrc\n", vrc)); + else + { + LogRel(("Drag and drop service loaded\n")); + vrc = i_changeDnDMode(enmMode); + } + } + } +#endif /* VBOX_WITH_DRAG_AND_DROP */ + +#if defined(VBOX_WITH_TPM) + /* + * Configure the Trusted Platform Module. + */ + ComObjPtr<ITrustedPlatformModule> ptrTpm; + TpmType_T enmTpmType = TpmType_None; + + hrc = pMachine->COMGETTER(TrustedPlatformModule)(ptrTpm.asOutParam()); H(); + hrc = ptrTpm->COMGETTER(Type)(&enmTpmType); H(); + if (enmTpmType != TpmType_None) + { + InsertConfigNode(pDevices, "tpm", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + InsertConfigNode(pInst, "LUN#0", &pLunL0); + + switch (enmTpmType) + { + case TpmType_v1_2: + case TpmType_v2_0: + { + InsertConfigString(pLunL0, "Driver", "TpmEmuTpms"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "TpmVersion", enmTpmType == TpmType_v1_2 ? 1 : 2); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "NvramStore"); + break; + } + case TpmType_Host: + { +#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS) + InsertConfigString(pLunL0, "Driver", "TpmHost"); + InsertConfigNode(pLunL0, "Config", &pCfg); +#endif + break; + } + case TpmType_Swtpm: + { + Bstr location; + hrc = ptrTpm->COMGETTER(Location)(location.asOutParam()); H(); + + InsertConfigString(pLunL0, "Driver", "TpmEmu"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "Location", location); + break; + } + default: + AssertFailedBreak(); + } + } +#endif + + /* + * ACPI + */ + BOOL fACPI; + hrc = biosSettings->COMGETTER(ACPIEnabled)(&fACPI); H(); + if (fACPI) + { + /* Always show the CPU leafs when we have multiple VCPUs or when the IO-APIC is enabled. + * The Windows SMP kernel needs a CPU leaf or else its idle loop will burn cpu cycles; the + * intelppm driver refuses to register an idle state handler. + * Always show CPU leafs for OS X guests. */ + BOOL fShowCpu = fOsXGuest; + if (cCpus > 1 || fIOAPIC) + fShowCpu = true; + + BOOL fCpuHotPlug; + hrc = pMachine->COMGETTER(CPUHotPlugEnabled)(&fCpuHotPlug); H(); + + InsertConfigNode(pDevices, "acpi", &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + InsertConfigNode(pInst, "Config", &pCfg); + hrc = pBusMgr->assignPCIDevice("acpi", pInst); H(); + + InsertConfigInteger(pCfg, "NumCPUs", cCpus); + + InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC); + InsertConfigInteger(pCfg, "FdcEnabled", fFdcEnabled); + InsertConfigInteger(pCfg, "HpetEnabled", fHPETEnabled); + InsertConfigInteger(pCfg, "SmcEnabled", fSmcEnabled); + InsertConfigInteger(pCfg, "ShowRtc", fShowRtc); + if (fOsXGuest && !llBootNics.empty()) + { + BootNic aNic = llBootNics.front(); + uint32_t u32NicPCIAddr = (aNic.mPCIAddress.miDevice << 16) | aNic.mPCIAddress.miFn; + InsertConfigInteger(pCfg, "NicPciAddress", u32NicPCIAddr); + } + if (fOsXGuest && fAudioEnabled) + { + PCIBusAddress Address; + if (pBusMgr->findPCIAddress("hda", 0, Address)) + { + uint32_t u32AudioPCIAddr = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "AudioPciAddress", u32AudioPCIAddr); + } + } + if (fOsXGuest) + { + PCIBusAddress Address; + if (pBusMgr->findPCIAddress("nvme", 0, Address)) + { + uint32_t u32NvmePCIAddr = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "NvmePciAddress", u32NvmePCIAddr); + } + } + if (enmIommuType == IommuType_AMD) + { + PCIBusAddress Address; + if (pBusMgr->findPCIAddress("iommu-amd", 0, Address)) + { + uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "IommuAmdEnabled", true); + InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress); + if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address)) + { + uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress); + } + else + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("AMD IOMMU is enabled, but the I/O APIC is not assigned a PCI address!")); + } + } + else if (enmIommuType == IommuType_Intel) + { + PCIBusAddress Address; + if (pBusMgr->findPCIAddress("iommu-intel", 0, Address)) + { + uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "IommuIntelEnabled", true); + InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress); + if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address)) + { + uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn; + InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress); + } + else + return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Intel IOMMU is enabled, but the I/O APIC is not assigned a PCI address!")); + } + } + + InsertConfigInteger(pCfg, "IocPciAddress", uIocPCIAddress); + if (chipsetType == ChipsetType_ICH9) + { + InsertConfigInteger(pCfg, "McfgBase", uMcfgBase); + InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength); + /* 64-bit prefetch window root resource: Only for ICH9 and if PAE or Long Mode is enabled (@bugref{5454}). */ + if (fIsGuest64Bit || fEnablePAE) + InsertConfigInteger(pCfg, "PciPref64Enabled", 1); + } + InsertConfigInteger(pCfg, "HostBusPciAddress", uHbcPCIAddress); + InsertConfigInteger(pCfg, "ShowCpu", fShowCpu); + InsertConfigInteger(pCfg, "CpuHotPlug", fCpuHotPlug); + + InsertConfigInteger(pCfg, "Serial0IoPortBase", auSerialIoPortBase[0]); + InsertConfigInteger(pCfg, "Serial0Irq", auSerialIrq[0]); + + InsertConfigInteger(pCfg, "Serial1IoPortBase", auSerialIoPortBase[1]); + InsertConfigInteger(pCfg, "Serial1Irq", auSerialIrq[1]); + + if (auSerialIoPortBase[2]) + { + InsertConfigInteger(pCfg, "Serial2IoPortBase", auSerialIoPortBase[2]); + InsertConfigInteger(pCfg, "Serial2Irq", auSerialIrq[2]); + } + + if (auSerialIoPortBase[3]) + { + InsertConfigInteger(pCfg, "Serial3IoPortBase", auSerialIoPortBase[3]); + InsertConfigInteger(pCfg, "Serial3Irq", auSerialIrq[3]); + } + + InsertConfigInteger(pCfg, "Parallel0IoPortBase", auParallelIoPortBase[0]); + InsertConfigInteger(pCfg, "Parallel0Irq", auParallelIrq[0]); + + InsertConfigInteger(pCfg, "Parallel1IoPortBase", auParallelIoPortBase[1]); + InsertConfigInteger(pCfg, "Parallel1Irq", auParallelIrq[1]); + +#if defined(VBOX_WITH_TPM) + switch (enmTpmType) + { + case TpmType_v1_2: + InsertConfigString(pCfg, "TpmMode", "tis1.2"); + break; + case TpmType_v2_0: + InsertConfigString(pCfg, "TpmMode", "fifo2.0"); + break; + /** @todo Host and swtpm. */ + default: + break; + } +#endif + + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "ACPIHost"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + /* Attach the dummy CPU drivers */ + for (ULONG iCpuCurr = 1; iCpuCurr < cCpus; iCpuCurr++) + { + BOOL fCpuAttached = true; + + if (fCpuHotPlug) + { + hrc = pMachine->GetCPUStatus(iCpuCurr, &fCpuAttached); H(); + } + + if (fCpuAttached) + { + InsertConfigNode(pInst, Utf8StrFmt("LUN#%u", iCpuCurr).c_str(), &pLunL0); + InsertConfigString(pLunL0, "Driver", "ACPICpu"); + InsertConfigNode(pLunL0, "Config", &pCfg); + } + } + } + + /* + * Configure DBGF (Debug(ger) Facility) and DBGC (Debugger Console). + */ + { + PCFGMNODE pDbgf; + InsertConfigNode(pRoot, "DBGF", &pDbgf); + + /* Paths to search for debug info and such things. */ + hrc = pMachine->COMGETTER(SettingsFilePath)(bstr.asOutParam()); H(); + Utf8Str strSettingsPath(bstr); + bstr.setNull(); + strSettingsPath.stripFilename(); + strSettingsPath.append("/"); + + char szHomeDir[RTPATH_MAX + 1]; + int vrc2 = RTPathUserHome(szHomeDir, sizeof(szHomeDir) - 1); + if (RT_FAILURE(vrc2)) + szHomeDir[0] = '\0'; + RTPathEnsureTrailingSeparator(szHomeDir, sizeof(szHomeDir)); + + + Utf8Str strPath; + strPath.append(strSettingsPath).append("debug/;"); + strPath.append(strSettingsPath).append(";"); + strPath.append("cache*").append(strSettingsPath).append("dbgcache/;"); /* handy for symlinking to actual cache */ + strPath.append(szHomeDir); + + InsertConfigString(pDbgf, "Path", strPath.c_str()); + + /* Tracing configuration. */ + BOOL fTracingEnabled; + hrc = pMachine->COMGETTER(TracingEnabled)(&fTracingEnabled); H(); + if (fTracingEnabled) + InsertConfigInteger(pDbgf, "TracingEnabled", 1); + + hrc = pMachine->COMGETTER(TracingConfig)(bstr.asOutParam()); H(); + if (fTracingEnabled) + InsertConfigString(pDbgf, "TracingConfig", bstr); + + BOOL fAllowTracingToAccessVM; + hrc = pMachine->COMGETTER(AllowTracingToAccessVM)(&fAllowTracingToAccessVM); H(); + if (fAllowTracingToAccessVM) + InsertConfigInteger(pPDM, "AllowTracingToAccessVM", 1); + + /* Debugger console config. */ + PCFGMNODE pDbgc; + InsertConfigNode(pRoot, "DBGC", &pDbgc); + + hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H(); + Utf8Str strVBoxHome = bstr; + bstr.setNull(); + if (strVBoxHome.isNotEmpty()) + strVBoxHome.append("/"); + else + { + strVBoxHome = szHomeDir; + strVBoxHome.append("/.vbox"); + } + + Utf8Str strFile(strVBoxHome); + strFile.append("dbgc-history"); + InsertConfigString(pDbgc, "HistoryFile", strFile); + + strFile = strSettingsPath; + strFile.append("dbgc-init"); + InsertConfigString(pDbgc, "LocalInitScript", strFile); + + strFile = strVBoxHome; + strFile.append("dbgc-init"); + InsertConfigString(pDbgc, "GlobalInitScript", strFile); + + /* + * Configure guest debug settings. + */ + ComObjPtr<IGuestDebugControl> ptrGstDbgCtrl; + GuestDebugProvider_T enmGstDbgProvider = GuestDebugProvider_None; + + hrc = pMachine->COMGETTER(GuestDebugControl)(ptrGstDbgCtrl.asOutParam()); H(); + hrc = ptrGstDbgCtrl->COMGETTER(DebugProvider)(&enmGstDbgProvider); H(); + if (enmGstDbgProvider != GuestDebugProvider_None) + { + GuestDebugIoProvider_T enmGstDbgIoProvider = GuestDebugIoProvider_None; + hrc = ptrGstDbgCtrl->COMGETTER(DebugIoProvider)(&enmGstDbgIoProvider); H(); + hrc = ptrGstDbgCtrl->COMGETTER(DebugAddress)(bstr.asOutParam()); H(); + Utf8Str strAddress = bstr; + bstr.setNull(); + + ULONG ulPort = 0; + hrc = ptrGstDbgCtrl->COMGETTER(DebugPort)(&ulPort); H(); + + PCFGMNODE pDbgSettings; + InsertConfigNode(pDbgc, "Dbg", &pDbgSettings); + InsertConfigString(pDbgSettings, "Address", strAddress); + InsertConfigInteger(pDbgSettings, "Port", ulPort); + + switch (enmGstDbgProvider) + { + case GuestDebugProvider_Native: + InsertConfigString(pDbgSettings, "StubType", "Native"); + break; + case GuestDebugProvider_GDB: + InsertConfigString(pDbgSettings, "StubType", "Gdb"); + break; + case GuestDebugProvider_KD: + InsertConfigString(pDbgSettings, "StubType", "Kd"); + break; + default: + AssertFailed(); + break; + } + + switch (enmGstDbgIoProvider) + { + case GuestDebugIoProvider_TCP: + InsertConfigString(pDbgSettings, "Provider", "tcp"); + break; + case GuestDebugIoProvider_UDP: + InsertConfigString(pDbgSettings, "Provider", "udp"); + break; + case GuestDebugIoProvider_IPC: + InsertConfigString(pDbgSettings, "Provider", "ipc"); + break; + default: + AssertFailed(); + break; + } + } + } + } + catch (ConfigError &x) + { + // InsertConfig threw something: + pVMM->pfnVMR3SetError(pUVM, x.m_vrc, RT_SRC_POS, "Caught ConfigError: %Rrc - %s", x.m_vrc, x.what()); + return x.m_vrc; + } + catch (HRESULT hrcXcpt) + { + AssertLogRelMsgFailedReturn(("hrc=%Rhrc\n", hrcXcpt), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR); + } + +#ifdef VBOX_WITH_EXTPACK + /* + * Call the extension pack hooks if everything went well thus far. + */ + if (RT_SUCCESS(vrc)) + { + pAlock->release(); + vrc = mptrExtPackManager->i_callAllVmConfigureVmmHooks(this, pVM, pVMM); + pAlock->acquire(); + } +#endif + + /* + * Apply the CFGM overlay. + */ + if (RT_SUCCESS(vrc)) + vrc = i_configCfgmOverlay(pRoot, virtualBox, pMachine); + + /* + * Dump all extradata API settings tweaks, both global and per VM. + */ + if (RT_SUCCESS(vrc)) + vrc = i_configDumpAPISettingsTweaks(virtualBox, pMachine); + +#undef H + + pAlock->release(); /* Avoid triggering the lock order inversion check. */ + + /* + * Register VM state change handler. + */ + int vrc2 = pVMM->pfnVMR3AtStateRegister(pUVM, Console::i_vmstateChangeCallback, this); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + /* + * Register VM runtime error handler. + */ + vrc2 = pVMM->pfnVMR3AtRuntimeErrorRegister(pUVM, Console::i_atVMRuntimeErrorCallback, this); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + pAlock->acquire(); + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + LogFlowFuncLeave(); + + return vrc; +} + +/** + * Configures an audio driver via CFGM by getting (optional) values from extra data. + * + * @param pVirtualBox Pointer to IVirtualBox instance. + * @param pMachine Pointer to IMachine instance. + * @param pLUN Pointer to CFGM node of LUN (the driver) to configure. + * @param pszDrvName Name of the driver to configure. + * @param fAudioEnabledIn IAudioAdapter::enabledIn value. + * @param fAudioEnabledOut IAudioAdapter::enabledOut value. + * + * @throws ConfigError or HRESULT on if there is trouble. + */ +void Console::i_configAudioDriver(IVirtualBox *pVirtualBox, IMachine *pMachine, PCFGMNODE pLUN, const char *pszDrvName, + bool fAudioEnabledIn, bool fAudioEnabledOut) +{ +#define H() AssertLogRelMsgStmt(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), \ + throw ConfigError(__FUNCTION__, VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR, "line: " RT_XSTR(__LINE__))) + + InsertConfigString(pLUN, "Driver", "AUDIO"); + + PCFGMNODE pCfg; + InsertConfigNode(pLUN, "Config", &pCfg); + InsertConfigString(pCfg, "DriverName", pszDrvName); + InsertConfigInteger(pCfg, "InputEnabled", fAudioEnabledIn); + InsertConfigInteger(pCfg, "OutputEnabled", fAudioEnabledOut); + + Utf8Str strTmp; + GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp); + const uint64_t fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1"); + if (fDebugEnabled) + { + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + + Utf8Str strDebugPathOut; + GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut); + InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut.c_str()); + } + + /* + * PCM input parameters (playback + recording). + * We have host driver specific ones as: VBoxInternal2/Audio/<DrvName>/<Value> + * And global ones for all host drivers: VBoxInternal2/Audio/<Value> + */ + for (unsigned iDir = 0; iDir < 2; iDir++) + { + static const struct + { + const char *pszExtraName; + const char *pszCfgmName; + } s_aToCopy[] = + { /* PCM parameters: */ + { "PCMSampleBit", "PCMSampleBit" }, + { "PCMSampleHz", "PCMSampleHz" }, + { "PCMSampleSigned", "PCMSampleSigned" }, + { "PCMSampleSwapEndian", "PCMSampleSwapEndian" }, + { "PCMSampleChannels", "PCMSampleChannels" }, + /* Buffering stuff: */ + { "PeriodSizeMs", "PeriodSizeMs" }, + { "BufferSizeMs", "BufferSizeMs" }, + { "PreBufferSizeMs", "PreBufferSizeMs" }, + }; + + PCFGMNODE pDirNode = NULL; + const char *pszDir = iDir == 0 ? "In" : "Out"; + for (size_t i = 0; i < RT_ELEMENTS(s_aToCopy); i++) + { + char szExtra[128]; + RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s/%s%s", pszDrvName, s_aToCopy[i].pszExtraName, pszDir); + GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp); /* throws hrc */ + if (strTmp.isEmpty()) + { + RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s%s", s_aToCopy[i].pszExtraName, pszDir); + GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp); + if (strTmp.isEmpty()) + continue; + } + + uint32_t uValue; + int vrc = RTStrToUInt32Full(strTmp.c_str(), 0, &uValue); + if (RT_SUCCESS(vrc)) + { + if (!pDirNode) + InsertConfigNode(pCfg, pszDir, &pDirNode); + InsertConfigInteger(pDirNode, s_aToCopy[i].pszCfgmName, uValue); + } + else + LogRel(("Ignoring malformed 32-bit unsigned integer config value '%s' = '%s': %Rrc\n", szExtra, strTmp.c_str(), vrc)); + } + } + + PCFGMNODE pLunL1; + InsertConfigNode(pLUN, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", pszDrvName); + InsertConfigNode(pLunL1, "Config", &pCfg); + +#ifdef RT_OS_WINDOWS + if (strcmp(pszDrvName, "HostAudioWas") == 0) + { + Bstr bstrTmp; + HRESULT hrc = pMachine->COMGETTER(Id)(bstrTmp.asOutParam()); H(); + InsertConfigString(pCfg, "VmUuid", bstrTmp); + } +#endif + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) + if ( strcmp(pszDrvName, "HostAudioWas") == 0 + || strcmp(pszDrvName, "PulseAudio") == 0) + { + Bstr bstrTmp; + HRESULT hrc = pMachine->COMGETTER(Name)(bstrTmp.asOutParam()); H(); + InsertConfigString(pCfg, "VmName", bstrTmp); + } +#endif + + LogFlowFunc(("szDrivName=%s\n", pszDrvName)); + +#undef H +} + +/** + * Applies the CFGM overlay as specified by VBoxInternal/XXX extra data + * values. + * + * @returns VBox status code. + * @param pRoot The root of the configuration tree. + * @param pVirtualBox Pointer to the IVirtualBox interface. + * @param pMachine Pointer to the IMachine interface. + */ +/* static */ +int Console::i_configCfgmOverlay(PCFGMNODE pRoot, IVirtualBox *pVirtualBox, IMachine *pMachine) +{ + /* + * CFGM overlay handling. + * + * Here we check the extra data entries for CFGM values + * and create the nodes and insert the values on the fly. Existing + * values will be removed and reinserted. CFGM is typed, so by default + * we will guess whether it's a string or an integer (byte arrays are + * not currently supported). It's possible to override this autodetection + * by adding "string:", "integer:" or "bytes:" (future). + * + * We first perform a run on global extra data, then on the machine + * extra data to support global settings with local overrides. + */ + int vrc = VINF_SUCCESS; + bool fFirst = true; + try + { + /** @todo add support for removing nodes and byte blobs. */ + /* + * Get the next key + */ + SafeArray<BSTR> aGlobalExtraDataKeys; + SafeArray<BSTR> aMachineExtraDataKeys; + HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys)); + AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc)); + + // remember the no. of global values so we can call the correct method below + size_t cGlobalValues = aGlobalExtraDataKeys.size(); + + hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys)); + AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc)); + + // build a combined list from global keys... + std::list<Utf8Str> llExtraDataKeys; + + for (size_t i = 0; i < aGlobalExtraDataKeys.size(); ++i) + llExtraDataKeys.push_back(Utf8Str(aGlobalExtraDataKeys[i])); + // ... and machine keys + for (size_t i = 0; i < aMachineExtraDataKeys.size(); ++i) + llExtraDataKeys.push_back(Utf8Str(aMachineExtraDataKeys[i])); + + size_t i2 = 0; + for (std::list<Utf8Str>::const_iterator it = llExtraDataKeys.begin(); + it != llExtraDataKeys.end(); + ++it, ++i2) + { + const Utf8Str &strKey = *it; + + /* + * We only care about keys starting with "VBoxInternal/" (skip "G:" or "M:") + */ + if (!strKey.startsWith("VBoxInternal/")) + continue; + + const char *pszExtraDataKey = strKey.c_str() + sizeof("VBoxInternal/") - 1; + + // get the value + Bstr bstrExtraDataValue; + if (i2 < cGlobalValues) + // this is still one of the global values: + hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam()); + else + hrc = pMachine->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam()); + if (FAILED(hrc)) + LogRel(("Warning: Cannot get extra data key %s, rc = %Rhrc\n", strKey.c_str(), hrc)); + + if (fFirst) + { + fFirst = false; + LogRel(("Extradata overrides:\n")); + } + LogRel((" %s=\"%ls\"%s\n", strKey.c_str(), bstrExtraDataValue.raw(), i2 < cGlobalValues ? " (global)" : "")); + + /* + * The key will be in the format "Node1/Node2/Value" or simply "Value". + * Split the two and get the node, delete the value and create the node + * if necessary. + */ + PCFGMNODE pNode; + const char *pszCFGMValueName = strrchr(pszExtraDataKey, '/'); + if (pszCFGMValueName) + { + /* terminate the node and advance to the value (Utf8Str might not + offically like this but wtf) */ + *(char *)pszCFGMValueName = '\0'; + ++pszCFGMValueName; + + /* does the node already exist? */ + pNode = mpVMM->pfnCFGMR3GetChild(pRoot, pszExtraDataKey); + if (pNode) + mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName); + else + { + /* create the node */ + vrc = mpVMM->pfnCFGMR3InsertNode(pRoot, pszExtraDataKey, &pNode); + if (RT_FAILURE(vrc)) + { + AssertLogRelMsgRC(vrc, ("failed to insert node '%s'\n", pszExtraDataKey)); + continue; + } + Assert(pNode); + } + } + else + { + /* root value (no node path). */ + pNode = pRoot; + pszCFGMValueName = pszExtraDataKey; + pszExtraDataKey--; + mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName); + } + + /* + * Now let's have a look at the value. + * Empty strings means that we should remove the value, which we've + * already done above. + */ + Utf8Str strCFGMValueUtf8(bstrExtraDataValue); + if (strCFGMValueUtf8.isNotEmpty()) + { + uint64_t u64Value; + + /* check for type prefix first. */ + if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("string:"))) + vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str() + sizeof("string:") - 1); + else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("integer:"))) + { + vrc = RTStrToUInt64Full(strCFGMValueUtf8.c_str() + sizeof("integer:") - 1, 0, &u64Value); + if (RT_SUCCESS(vrc)) + vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value); + } + else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("bytes:"))) + { + char const *pszBase64 = strCFGMValueUtf8.c_str() + sizeof("bytes:") - 1; + ssize_t cbValue = RTBase64DecodedSize(pszBase64, NULL); + if (cbValue > 0) + { + void *pvBytes = RTMemTmpAlloc(cbValue); + if (pvBytes) + { + vrc = RTBase64Decode(pszBase64, pvBytes, cbValue, NULL, NULL); + if (RT_SUCCESS(vrc)) + vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, pvBytes, cbValue); + RTMemTmpFree(pvBytes); + } + else + vrc = VERR_NO_TMP_MEMORY; + } + else if (cbValue == 0) + vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, NULL, 0); + else + vrc = VERR_INVALID_BASE64_ENCODING; + } + /* auto detect type. */ + else if (RT_SUCCESS(RTStrToUInt64Full(strCFGMValueUtf8.c_str(), 0, &u64Value))) + vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value); + else + vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str()); + AssertLogRelMsgRCBreak(vrc, ("failed to insert CFGM value '%s' to key '%s'\n", + strCFGMValueUtf8.c_str(), pszExtraDataKey)); + } + } + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + return vrc; +} + +/** + * Dumps the API settings tweaks as specified by VBoxInternal2/XXX extra data + * values. + * + * @returns VBox status code. + * @param pVirtualBox Pointer to the IVirtualBox interface. + * @param pMachine Pointer to the IMachine interface. + */ +/* static */ +int Console::i_configDumpAPISettingsTweaks(IVirtualBox *pVirtualBox, IMachine *pMachine) +{ + { + SafeArray<BSTR> aGlobalExtraDataKeys; + HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys)); + AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc)); + bool hasKey = false; + for (size_t i = 0; i < aGlobalExtraDataKeys.size(); i++) + { + Utf8Str strKey(aGlobalExtraDataKeys[i]); + if (!strKey.startsWith("VBoxInternal2/")) + continue; + + Bstr bstrValue; + hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(), + bstrValue.asOutParam()); + if (FAILED(hrc)) + continue; + if (!hasKey) + LogRel(("Global extradata API settings:\n")); + LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw())); + hasKey = true; + } + } + + { + SafeArray<BSTR> aMachineExtraDataKeys; + HRESULT hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys)); + AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc)); + bool hasKey = false; + for (size_t i = 0; i < aMachineExtraDataKeys.size(); i++) + { + Utf8Str strKey(aMachineExtraDataKeys[i]); + if (!strKey.startsWith("VBoxInternal2/")) + continue; + + Bstr bstrValue; + hrc = pMachine->GetExtraData(Bstr(strKey).raw(), + bstrValue.asOutParam()); + if (FAILED(hrc)) + continue; + if (!hasKey) + LogRel(("Per-VM extradata API settings:\n")); + LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw())); + hasKey = true; + } + } + + return VINF_SUCCESS; +} + +int Console::i_configGraphicsController(PCFGMNODE pDevices, + const GraphicsControllerType_T enmGraphicsController, + BusAssignmentManager *pBusMgr, + const ComPtr<IMachine> &ptrMachine, + const ComPtr<IGraphicsAdapter> &ptrGraphicsAdapter, + const ComPtr<IBIOSSettings> &ptrBiosSettings, + bool fHMEnabled) +{ + // InsertConfig* throws + try + { + PCFGMNODE pDev, pInst, pCfg, pLunL0; + HRESULT hrc; + Bstr bstr; + const char *pcszDevice = "vga"; + +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + InsertConfigNode(pDevices, pcszDevice, &pDev); + InsertConfigNode(pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + + hrc = pBusMgr->assignPCIDevice(pcszDevice, pInst); H(); + InsertConfigNode(pInst, "Config", &pCfg); + ULONG cVRamMBs; + hrc = ptrGraphicsAdapter->COMGETTER(VRAMSize)(&cVRamMBs); H(); + InsertConfigInteger(pCfg, "VRamSize", cVRamMBs * _1M); + ULONG cMonitorCount; + hrc = ptrGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitorCount); H(); + InsertConfigInteger(pCfg, "MonitorCount", cMonitorCount); +#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE + InsertConfigInteger(pCfg, "R0Enabled", fHMEnabled); +#else + NOREF(fHMEnabled); +#endif + BOOL f3DEnabled; + hrc = ptrGraphicsAdapter->COMGETTER(Accelerate3DEnabled)(&f3DEnabled); H(); + InsertConfigInteger(pCfg, "3DEnabled", f3DEnabled); + + i_attachStatusDriver(pInst, DeviceType_Graphics3D, 0, 0, NULL, NULL, NULL, 0); + +#ifdef VBOX_WITH_VMSVGA + if ( enmGraphicsController == GraphicsControllerType_VMSVGA + || enmGraphicsController == GraphicsControllerType_VBoxSVGA) + { + InsertConfigInteger(pCfg, "VMSVGAEnabled", true); + if (enmGraphicsController == GraphicsControllerType_VMSVGA) + { + InsertConfigInteger(pCfg, "VMSVGAPciBarLayout", true); + InsertConfigInteger(pCfg, "VMSVGAPciId", true); + } +# ifdef VBOX_WITH_VMSVGA3D + InsertConfigInteger(pCfg, "VMSVGA3dEnabled", f3DEnabled); +# else + LogRel(("VMSVGA3d not available in this build!\n")); +# endif /* VBOX_WITH_VMSVGA3D */ + } +#else + RT_NOREF(enmGraphicsController); +#endif /* VBOX_WITH_VMSVGA */ + + /* Custom VESA mode list */ + unsigned cModes = 0; + for (unsigned iMode = 1; iMode <= 16; ++iMode) + { + char szExtraDataKey[sizeof("CustomVideoModeXX")]; + RTStrPrintf(szExtraDataKey, sizeof(szExtraDataKey), "CustomVideoMode%u", iMode); + hrc = ptrMachine->GetExtraData(Bstr(szExtraDataKey).raw(), bstr.asOutParam()); H(); + if (bstr.isEmpty()) + break; + InsertConfigString(pCfg, szExtraDataKey, bstr); + ++cModes; + } + InsertConfigInteger(pCfg, "CustomVideoModes", cModes); + + /* VESA height reduction */ + ULONG ulHeightReduction; + IFramebuffer *pFramebuffer = NULL; + hrc = i_getDisplay()->QueryFramebuffer(0, &pFramebuffer); + if (SUCCEEDED(hrc) && pFramebuffer) + { + hrc = pFramebuffer->COMGETTER(HeightReduction)(&ulHeightReduction); H(); + pFramebuffer->Release(); + pFramebuffer = NULL; + } + else + { + /* If framebuffer is not available, there is no height reduction. */ + ulHeightReduction = 0; + } + InsertConfigInteger(pCfg, "HeightReduction", ulHeightReduction); + + /* + * BIOS logo + */ + BOOL fFadeIn; + hrc = ptrBiosSettings->COMGETTER(LogoFadeIn)(&fFadeIn); H(); + InsertConfigInteger(pCfg, "FadeIn", fFadeIn ? 1 : 0); + BOOL fFadeOut; + hrc = ptrBiosSettings->COMGETTER(LogoFadeOut)(&fFadeOut); H(); + InsertConfigInteger(pCfg, "FadeOut", fFadeOut ? 1: 0); + ULONG logoDisplayTime; + hrc = ptrBiosSettings->COMGETTER(LogoDisplayTime)(&logoDisplayTime); H(); + InsertConfigInteger(pCfg, "LogoTime", logoDisplayTime); + Bstr logoImagePath; + hrc = ptrBiosSettings->COMGETTER(LogoImagePath)(logoImagePath.asOutParam()); H(); + InsertConfigString(pCfg, "LogoFile", Utf8Str(!logoImagePath.isEmpty() ? logoImagePath : "") ); + + /* + * Boot menu + */ + BIOSBootMenuMode_T eBootMenuMode; + int iShowBootMenu; + hrc = ptrBiosSettings->COMGETTER(BootMenuMode)(&eBootMenuMode); H(); + switch (eBootMenuMode) + { + case BIOSBootMenuMode_Disabled: iShowBootMenu = 0; break; + case BIOSBootMenuMode_MenuOnly: iShowBootMenu = 1; break; + default: iShowBootMenu = 2; break; + } + InsertConfigInteger(pCfg, "ShowBootMenu", iShowBootMenu); + + /* Attach the display. */ + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MainDisplay"); + InsertConfigNode(pLunL0, "Config", &pCfg); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + +#undef H + + return VINF_SUCCESS; +} + + +/** + * Ellipsis to va_list wrapper for calling setVMRuntimeErrorCallback. + */ +void Console::i_atVMRuntimeErrorCallbackF(uint32_t fFlags, const char *pszErrorId, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + i_atVMRuntimeErrorCallback(NULL, this, fFlags, pszErrorId, pszFormat, va); + va_end(va); +} + +/* XXX introduce RT format specifier */ +static uint64_t formatDiskSize(uint64_t u64Size, const char **pszUnit) +{ + if (u64Size > INT64_C(5000)*_1G) + { + *pszUnit = "TB"; + return u64Size / _1T; + } + else if (u64Size > INT64_C(5000)*_1M) + { + *pszUnit = "GB"; + return u64Size / _1G; + } + else + { + *pszUnit = "MB"; + return u64Size / _1M; + } +} + +/** + * Checks the location of the given medium for known bugs affecting the usage + * of the host I/O cache setting. + * + * @returns VBox status code. + * @param pMedium The medium to check. + * @param pfUseHostIOCache Where to store the suggested host I/O cache setting. + */ +int Console::i_checkMediumLocation(IMedium *pMedium, bool *pfUseHostIOCache) +{ +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + /* + * Some sanity checks. + */ + RT_NOREF(pfUseHostIOCache); + ComPtr<IMediumFormat> pMediumFormat; + HRESULT hrc = pMedium->COMGETTER(MediumFormat)(pMediumFormat.asOutParam()); H(); + ULONG uCaps = 0; + com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap; + hrc = pMediumFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap)); H(); + + for (ULONG j = 0; j < mediumFormatCap.size(); j++) + uCaps |= mediumFormatCap[j]; + + if (uCaps & MediumFormatCapabilities_File) + { + Bstr bstrFile; + hrc = pMedium->COMGETTER(Location)(bstrFile.asOutParam()); H(); + Utf8Str const strFile(bstrFile); + + Bstr bstrSnap; + ComPtr<IMachine> pMachine = i_machine(); + hrc = pMachine->COMGETTER(SnapshotFolder)(bstrSnap.asOutParam()); H(); + Utf8Str const strSnap(bstrSnap); + + RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN; + int vrc2 = RTFsQueryType(strFile.c_str(), &enmFsTypeFile); + AssertMsgRCReturn(vrc2, ("Querying the file type of '%s' failed!\n", strFile.c_str()), vrc2); + + /* Any VM which hasn't created a snapshot or saved the current state of the VM + * won't have a Snapshot folder yet so no need to log anything about the file system + * type of the non-existent directory in such cases. */ + RTFSTYPE enmFsTypeSnap = RTFSTYPE_UNKNOWN; + vrc2 = RTFsQueryType(strSnap.c_str(), &enmFsTypeSnap); + if (RT_SUCCESS(vrc2) && !mfSnapshotFolderDiskTypeShown) + { + LogRel(("File system of '%s' (snapshots) is %s\n", strSnap.c_str(), RTFsTypeName(enmFsTypeSnap))); + mfSnapshotFolderDiskTypeShown = true; + } + LogRel(("File system of '%s' is %s\n", strFile.c_str(), RTFsTypeName(enmFsTypeFile))); + LONG64 i64Size; + hrc = pMedium->COMGETTER(LogicalSize)(&i64Size); H(); +#ifdef RT_OS_WINDOWS + if ( enmFsTypeFile == RTFSTYPE_FAT + && i64Size >= _4G) + { + const char *pszUnit; + uint64_t u64Print = formatDiskSize((uint64_t)i64Size, &pszUnit); + i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected", + N_("The medium '%s' has a logical size of %RU64%s " + "but the file system the medium is located on seems " + "to be FAT(32) which cannot handle files bigger than 4GB.\n" + "We strongly recommend to put all your virtual disk images and " + "the snapshot folder onto an NTFS partition"), + strFile.c_str(), u64Print, pszUnit); + } +#else /* !RT_OS_WINDOWS */ + if ( enmFsTypeFile == RTFSTYPE_FAT + || enmFsTypeFile == RTFSTYPE_EXT + || enmFsTypeFile == RTFSTYPE_EXT2 + || enmFsTypeFile == RTFSTYPE_EXT3 + || enmFsTypeFile == RTFSTYPE_EXT4) + { + RTFILE file; + int vrc = RTFileOpen(&file, strFile.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(vrc)) + { + RTFOFF maxSize; + /* Careful: This function will work only on selected local file systems! */ + vrc = RTFileQueryMaxSizeEx(file, &maxSize); + RTFileClose(file); + if ( RT_SUCCESS(vrc) + && maxSize > 0 + && i64Size > (LONG64)maxSize) + { + const char *pszUnitSiz; + const char *pszUnitMax; + uint64_t u64PrintSiz = formatDiskSize((LONG64)i64Size, &pszUnitSiz); + uint64_t u64PrintMax = formatDiskSize(maxSize, &pszUnitMax); + i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected", /* <= not exact but ... */ + N_("The medium '%s' has a logical size of %RU64%s " + "but the file system the medium is located on can " + "only handle files up to %RU64%s in theory.\n" + "We strongly recommend to put all your virtual disk " + "images and the snapshot folder onto a proper " + "file system (e.g. ext3) with a sufficient size"), + strFile.c_str(), u64PrintSiz, pszUnitSiz, u64PrintMax, pszUnitMax); + } + } + } +#endif /* !RT_OS_WINDOWS */ + + /* + * Snapshot folder: + * Here we test only for a FAT partition as we had to create a dummy file otherwise + */ + if ( enmFsTypeSnap == RTFSTYPE_FAT + && i64Size >= _4G + && !mfSnapshotFolderSizeWarningShown) + { + const char *pszUnit; + uint64_t u64Print = formatDiskSize(i64Size, &pszUnit); + i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected", +#ifdef RT_OS_WINDOWS + N_("The snapshot folder of this VM '%s' seems to be located on " + "a FAT(32) file system. The logical size of the medium '%s' " + "(%RU64%s) is bigger than the maximum file size this file " + "system can handle (4GB).\n" + "We strongly recommend to put all your virtual disk images and " + "the snapshot folder onto an NTFS partition"), +#else + N_("The snapshot folder of this VM '%s' seems to be located on " + "a FAT(32) file system. The logical size of the medium '%s' " + "(%RU64%s) is bigger than the maximum file size this file " + "system can handle (4GB).\n" + "We strongly recommend to put all your virtual disk images and " + "the snapshot folder onto a proper file system (e.g. ext3)"), +#endif + strSnap.c_str(), strFile.c_str(), u64Print, pszUnit); + /* Show this particular warning only once */ + mfSnapshotFolderSizeWarningShown = true; + } + +#ifdef RT_OS_LINUX + /* + * Ext4 bug: Check if the host I/O cache is disabled and the disk image is located + * on an ext4 partition. + * This bug apparently applies to the XFS file system as well. + * Linux 2.6.36 is known to be fixed (tested with 2.6.36-rc4). + */ + + char szOsRelease[128]; + int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOsRelease, sizeof(szOsRelease)); + bool fKernelHasODirectBug = RT_FAILURE(vrc) + || (RTStrVersionCompare(szOsRelease, "2.6.36-rc4") < 0); + + if ( (uCaps & MediumFormatCapabilities_Asynchronous) + && !*pfUseHostIOCache + && fKernelHasODirectBug) + { + if ( enmFsTypeFile == RTFSTYPE_EXT4 + || enmFsTypeFile == RTFSTYPE_XFS) + { + i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected", + N_("The host I/O cache for at least one controller is disabled " + "and the medium '%s' for this VM " + "is located on an %s partition. There is a known Linux " + "kernel bug which can lead to the corruption of the virtual " + "disk image under these conditions.\n" + "Either enable the host I/O cache permanently in the VM " + "settings or put the disk image and the snapshot folder " + "onto a different file system.\n" + "The host I/O cache will now be enabled for this medium"), + strFile.c_str(), enmFsTypeFile == RTFSTYPE_EXT4 ? "ext4" : "xfs"); + *pfUseHostIOCache = true; + } + else if ( ( enmFsTypeSnap == RTFSTYPE_EXT4 + || enmFsTypeSnap == RTFSTYPE_XFS) + && !mfSnapshotFolderExt4WarningShown) + { + i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected", + N_("The host I/O cache for at least one controller is disabled " + "and the snapshot folder for this VM " + "is located on an %s partition. There is a known Linux " + "kernel bug which can lead to the corruption of the virtual " + "disk image under these conditions.\n" + "Either enable the host I/O cache permanently in the VM " + "settings or put the disk image and the snapshot folder " + "onto a different file system.\n" + "The host I/O cache will now be enabled for this medium"), + enmFsTypeSnap == RTFSTYPE_EXT4 ? "ext4" : "xfs"); + *pfUseHostIOCache = true; + mfSnapshotFolderExt4WarningShown = true; + } + } + + /* + * 2.6.18 bug: Check if the host I/O cache is disabled and the host is running + * Linux 2.6.18. See @bugref{8690}. Apparently the same problem as + * documented in https://lkml.org/lkml/2007/2/1/14. We saw such + * kernel oopses on Linux 2.6.18-416.el5. We don't know when this + * was fixed but we _know_ that 2.6.18 EL5 kernels are affected. + */ + bool fKernelAsyncUnreliable = RT_FAILURE(vrc) + || (RTStrVersionCompare(szOsRelease, "2.6.19") < 0); + if ( (uCaps & MediumFormatCapabilities_Asynchronous) + && !*pfUseHostIOCache + && fKernelAsyncUnreliable) + { + i_atVMRuntimeErrorCallbackF(0, "Linux2618TooOld", + N_("The host I/O cache for at least one controller is disabled. " + "There is a known Linux kernel bug which can lead to kernel " + "oopses under heavy load. To our knowledge this bug affects " + "all 2.6.18 kernels.\n" + "Either enable the host I/O cache permanently in the VM " + "settings or switch to a newer host kernel.\n" + "The host I/O cache will now be enabled for this medium")); + *pfUseHostIOCache = true; + } +#endif + } +#undef H + + return VINF_SUCCESS; +} + +/** + * Unmounts the specified medium from the specified device. + * + * @returns VBox status code. + * @param pUVM The usermode VM handle. + * @param pVMM The VMM vtable. + * @param enmBus The storage bus. + * @param enmDevType The device type. + * @param pcszDevice The device emulation. + * @param uInstance Instance of the device. + * @param uLUN The LUN on the device. + * @param fForceUnmount Whether to force unmounting. + */ +int Console::i_unmountMediumFromGuest(PUVM pUVM, PCVMMR3VTABLE pVMM, StorageBus_T enmBus, DeviceType_T enmDevType, + const char *pcszDevice, unsigned uInstance, unsigned uLUN, + bool fForceUnmount) RT_NOEXCEPT +{ + /* Unmount existing media only for floppy and DVD drives. */ + int vrc = VINF_SUCCESS; + PPDMIBASE pBase; + if (enmBus == StorageBus_USB) + vrc = pVMM->pfnPDMR3UsbQueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase); + else if ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI) + || (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD)) + vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase); + else /* IDE or Floppy */ + vrc = pVMM->pfnPDMR3QueryLun(pUVM, pcszDevice, uInstance, uLUN, &pBase); + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_PDM_LUN_NOT_FOUND || vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + vrc = VINF_SUCCESS; + AssertRC(vrc); + } + else + { + PPDMIMOUNT pIMount = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMOUNT); + AssertReturn(pIMount, VERR_INVALID_POINTER); + + /* Unmount the media (but do not eject the medium!) */ + vrc = pIMount->pfnUnmount(pIMount, fForceUnmount, false /*=fEject*/); + if (vrc == VERR_PDM_MEDIA_NOT_MOUNTED) + vrc = VINF_SUCCESS; + /* for example if the medium is locked */ + else if (RT_FAILURE(vrc)) + return vrc; + } + + return vrc; +} + +/** + * Removes the currently attached medium driver form the specified device + * taking care of the controlelr specific configs wrt. to the attached driver chain. + * + * @returns VBox status code. + * @param pCtlInst The controler instance node in the CFGM tree. + * @param pcszDevice The device name. + * @param uInstance The device instance. + * @param uLUN The device LUN. + * @param enmBus The storage bus. + * @param fAttachDetach Flag whether this is a change while the VM is running + * @param fHotplug Flag whether the guest should be notified about the device change. + * @param fForceUnmount Flag whether to force unmounting the medium even if it is locked. + * @param pUVM The usermode VM handle. + * @param pVMM The VMM vtable. + * @param enmDevType The device type. + * @param ppLunL0 Where to store the node to attach the new config to on success. + */ +int Console::i_removeMediumDriverFromVm(PCFGMNODE pCtlInst, + const char *pcszDevice, + unsigned uInstance, + unsigned uLUN, + StorageBus_T enmBus, + bool fAttachDetach, + bool fHotplug, + bool fForceUnmount, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + DeviceType_T enmDevType, + PCFGMNODE *ppLunL0) +{ + int vrc = VINF_SUCCESS; + bool fAddLun = false; + + /* First check if the LUN already exists. */ + PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + AssertReturn(!RT_VALID_PTR(pLunL0) || fAttachDetach, VERR_INTERNAL_ERROR); + + if (pLunL0) + { + /* + * Unmount the currently mounted medium if we don't just hot remove the + * complete device (SATA) and it supports unmounting (DVD). + */ + if ( (enmDevType != DeviceType_HardDisk) + && !fHotplug) + { + vrc = i_unmountMediumFromGuest(pUVM, pVMM, enmBus, enmDevType, pcszDevice, uInstance, uLUN, fForceUnmount); + if (RT_FAILURE(vrc)) + return vrc; + } + + /* + * Don't detach the SCSI driver when unmounting the current medium + * (we are not ripping out the device but only eject the medium). + */ + char *pszDriverDetach = NULL; + if ( !fHotplug + && ( (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD) + || enmBus == StorageBus_SAS + || enmBus == StorageBus_SCSI + || enmBus == StorageBus_VirtioSCSI + || enmBus == StorageBus_USB)) + { + /* Get the current attached driver we have to detach. */ + PCFGMNODE pDrvLun = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u/AttachedDriver/", uLUN); + if (pDrvLun) + { + char szDriver[128]; + RT_ZERO(szDriver); + vrc = pVMM->pfnCFGMR3QueryString(pDrvLun, "Driver", &szDriver[0], sizeof(szDriver)); + if (RT_SUCCESS(vrc)) + pszDriverDetach = RTStrDup(&szDriver[0]); + + pLunL0 = pDrvLun; + } + } + + if (enmBus == StorageBus_USB) + vrc = pVMM->pfnPDMR3UsbDriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach, + 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG); + else + vrc = pVMM->pfnPDMR3DriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach, + 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG); + + if (pszDriverDetach) + { + RTStrFree(pszDriverDetach); + /* Remove the complete node and create new for the new config. */ + pVMM->pfnCFGMR3RemoveNode(pLunL0); + pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + if (pLunL0) + { + try + { + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + } + } + if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + vrc = VINF_SUCCESS; + AssertRCReturn(vrc, vrc); + + /* + * Don't remove the LUN except for IDE/floppy/NVMe (which connects directly to the medium driver + * even for DVD devices) or if there is a hotplug event which rips out the complete device. + */ + if ( fHotplug + || enmBus == StorageBus_IDE + || enmBus == StorageBus_Floppy + || enmBus == StorageBus_PCIe + || (enmBus == StorageBus_SATA && enmDevType != DeviceType_DVD)) + { + fAddLun = true; + pVMM->pfnCFGMR3RemoveNode(pLunL0); + } + } + else + fAddLun = true; + + try + { + if (fAddLun) + InsertConfigNodeF(pCtlInst, &pLunL0, "LUN#%u", uLUN); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + + if (ppLunL0) + *ppLunL0 = pLunL0; + + return vrc; +} + +int Console::i_configMediumAttachment(const char *pcszDevice, + unsigned uInstance, + StorageBus_T enmBus, + bool fUseHostIOCache, + bool fBuiltinIOCache, + bool fInsertDiskIntegrityDrv, + bool fSetupMerge, + unsigned uMergeSource, + unsigned uMergeTarget, + IMediumAttachment *pMediumAtt, + MachineState_T aMachineState, + HRESULT *phrc, + bool fAttachDetach, + bool fForceUnmount, + bool fHotplug, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + DeviceType_T *paLedDevType, + PCFGMNODE *ppLunL0) +{ + // InsertConfig* throws + try + { + int vrc = VINF_SUCCESS; + HRESULT hrc; + Bstr bstr; + PCFGMNODE pCtlInst = NULL; + +// #define RC_CHECK() AssertMsgReturn(RT_SUCCESS(rc), ("rc=%Rrc\n", rc), rc) +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + + LONG lDev; + hrc = pMediumAtt->COMGETTER(Device)(&lDev); H(); + LONG lPort; + hrc = pMediumAtt->COMGETTER(Port)(&lPort); H(); + DeviceType_T lType; + hrc = pMediumAtt->COMGETTER(Type)(&lType); H(); + BOOL fNonRotational; + hrc = pMediumAtt->COMGETTER(NonRotational)(&fNonRotational); H(); + BOOL fDiscard; + hrc = pMediumAtt->COMGETTER(Discard)(&fDiscard); H(); + + if (lType == DeviceType_DVD) + fInsertDiskIntegrityDrv = false; + + unsigned uLUN; + PCFGMNODE pLunL0 = NULL; + hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H(); + + /* Determine the base path for the device instance. */ + if (enmBus != StorageBus_USB) + pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance); + else + { + /* If we hotplug a USB device create a new CFGM tree. */ + if (!fHotplug) + pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "USB/%s/", pcszDevice); + else + pCtlInst = pVMM->pfnCFGMR3CreateTree(pUVM); + } + AssertReturn(pCtlInst, VERR_INTERNAL_ERROR); + + if (enmBus == StorageBus_USB) + { + PCFGMNODE pCfg = NULL; + + /* Create correct instance. */ + if (!fHotplug) + { + if (!fAttachDetach) + InsertConfigNodeF(pCtlInst, &pCtlInst, "%d", lPort); + else + pCtlInst = pVMM->pfnCFGMR3GetChildF(pCtlInst, "%d/", lPort); + } + + if (!fAttachDetach) + InsertConfigNode(pCtlInst, "Config", &pCfg); + + uInstance = lPort; /* Overwrite uInstance with the correct one. */ + + if (!fHotplug && !fAttachDetach) + { + char aszUuid[RTUUID_STR_LENGTH + 1]; + USBStorageDevice UsbMsd = USBStorageDevice(); + + memset(aszUuid, 0, sizeof(aszUuid)); + vrc = RTUuidCreate(&UsbMsd.mUuid); + AssertRCReturn(vrc, vrc); + vrc = RTUuidToStr(&UsbMsd.mUuid, aszUuid, sizeof(aszUuid)); + AssertRCReturn(vrc, vrc); + + UsbMsd.iPort = uInstance; + + InsertConfigString(pCtlInst, "UUID", aszUuid); + mUSBStorageDevices.push_back(UsbMsd); + + /** @todo No LED after hotplugging. */ + /* Attach the status driver */ + i_attachStatusDriver(pCtlInst, DeviceType_HardDisk, 0, 7, &paLedDevType, + &mapMediumAttachments, pcszDevice, 0); + } + } + + vrc = i_removeMediumDriverFromVm(pCtlInst, pcszDevice, uInstance, uLUN, enmBus, fAttachDetach, + fHotplug, fForceUnmount, pUVM, pVMM, lType, &pLunL0); + if (RT_FAILURE(vrc)) + return vrc; + if (ppLunL0) + *ppLunL0 = pLunL0; + + Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN); + mapMediumAttachments[devicePath] = pMediumAtt; + + ComPtr<IMedium> ptrMedium; + hrc = pMediumAtt->COMGETTER(Medium)(ptrMedium.asOutParam()); H(); + + /* + * 1. Only check this for hard disk images. + * 2. Only check during VM creation and not later, especially not during + * taking an online snapshot! + */ + if ( lType == DeviceType_HardDisk + && ( aMachineState == MachineState_Starting + || aMachineState == MachineState_Restoring)) + { + vrc = i_checkMediumLocation(ptrMedium, &fUseHostIOCache); + if (RT_FAILURE(vrc)) + return vrc; + } + + BOOL fPassthrough = FALSE; + if (ptrMedium.isNotNull()) + { + BOOL fHostDrive; + hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H(); + if ( ( lType == DeviceType_DVD + || lType == DeviceType_Floppy) + && !fHostDrive) + { + /* + * Informative logging. + */ + Bstr bstrFile; + hrc = ptrMedium->COMGETTER(Location)(bstrFile.asOutParam()); H(); + Utf8Str strFile(bstrFile); + RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN; + (void)RTFsQueryType(strFile.c_str(), &enmFsTypeFile); + LogRel(("File system of '%s' (%s) is %s\n", + strFile.c_str(), lType == DeviceType_DVD ? "DVD" : "Floppy", RTFsTypeName(enmFsTypeFile))); + } + + if (fHostDrive) + { + hrc = pMediumAtt->COMGETTER(Passthrough)(&fPassthrough); H(); + } + } + + ComObjPtr<IBandwidthGroup> pBwGroup; + Bstr bstrBwGroup; + hrc = pMediumAtt->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H(); + + if (!pBwGroup.isNull()) + { + hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H(); + } + + /* + * Insert the SCSI driver for hotplug events on the SCSI/USB based storage controllers + * or for SATA if the new device is a CD/DVD drive. + */ + if ( (fHotplug || !fAttachDetach) + && ( (enmBus == StorageBus_SCSI || enmBus == StorageBus_SAS || enmBus == StorageBus_USB || enmBus == StorageBus_VirtioSCSI) + || (enmBus == StorageBus_SATA && lType == DeviceType_DVD && !fPassthrough))) + { + InsertConfigString(pLunL0, "Driver", "SCSI"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + + vrc = i_configMedium(pLunL0, + !!fPassthrough, + lType, + fUseHostIOCache, + fBuiltinIOCache, + fInsertDiskIntegrityDrv, + fSetupMerge, + uMergeSource, + uMergeTarget, + bstrBwGroup.isEmpty() ? NULL : Utf8Str(bstrBwGroup).c_str(), + !!fDiscard, + !!fNonRotational, + ptrMedium, + aMachineState, + phrc); + if (RT_FAILURE(vrc)) + return vrc; + + if (fAttachDetach) + { + /* Attach the new driver. */ + if (enmBus == StorageBus_USB) + { + if (fHotplug) + { + USBStorageDevice UsbMsd = USBStorageDevice(); + RTUuidCreate(&UsbMsd.mUuid); + UsbMsd.iPort = uInstance; + vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, pcszDevice, pCtlInst, &UsbMsd.mUuid, NULL); + if (RT_SUCCESS(vrc)) + mUSBStorageDevices.push_back(UsbMsd); + } + else + vrc = pVMM->pfnPDMR3UsbDriverAttach(pUVM, pcszDevice, uInstance, uLUN, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/); + } + else if ( !fHotplug + && ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI) + || (enmBus == StorageBus_SATA && lType == DeviceType_DVD))) + vrc = pVMM->pfnPDMR3DriverAttach(pUVM, pcszDevice, uInstance, uLUN, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/); + else + vrc = pVMM->pfnPDMR3DeviceAttach(pUVM, pcszDevice, uInstance, uLUN, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/); + AssertRCReturn(vrc, vrc); + + /* + * Make the secret key helper interface known to the VD driver if it is attached, + * so we can get notified about missing keys. + */ + PPDMIBASE pIBase = NULL; + vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "VD", &pIBase); + if (RT_SUCCESS(vrc) && pIBase) + { + PPDMIMEDIA pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (pIMedium) + { + vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp); + Assert(RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED); + } + } + + /* There is no need to handle removable medium mounting, as we + * unconditionally replace everthing including the block driver level. + * This means the new medium will be picked up automatically. */ + } + + if (paLedDevType) + paLedDevType[uLUN] = lType; + + /* Dump the changed LUN if possible, dump the complete device otherwise */ + if ( aMachineState != MachineState_Starting + && aMachineState != MachineState_Restoring) + pVMM->pfnCFGMR3Dump(pLunL0 ? pLunL0 : pCtlInst); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + +#undef H + + return VINF_SUCCESS; +} + +int Console::i_configMedium(PCFGMNODE pLunL0, + bool fPassthrough, + DeviceType_T enmType, + bool fUseHostIOCache, + bool fBuiltinIOCache, + bool fInsertDiskIntegrityDrv, + bool fSetupMerge, + unsigned uMergeSource, + unsigned uMergeTarget, + const char *pcszBwGroup, + bool fDiscard, + bool fNonRotational, + ComPtr<IMedium> ptrMedium, + MachineState_T aMachineState, + HRESULT *phrc) +{ + // InsertConfig* throws + try + { + HRESULT hrc; + Bstr bstr; + PCFGMNODE pCfg = NULL; + +#define H() \ + AssertMsgReturnStmt(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), if (phrc) *phrc = hrc, Global::vboxStatusCodeFromCOM(hrc)) + + + BOOL fHostDrive = FALSE; + MediumType_T mediumType = MediumType_Normal; + if (ptrMedium.isNotNull()) + { + hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H(); + hrc = ptrMedium->COMGETTER(Type)(&mediumType); H(); + } + + if (fHostDrive) + { + Assert(ptrMedium.isNotNull()); + if (enmType == DeviceType_DVD) + { + InsertConfigString(pLunL0, "Driver", "HostDVD"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "Path", bstr); + + InsertConfigInteger(pCfg, "Passthrough", fPassthrough); + } + else if (enmType == DeviceType_Floppy) + { + InsertConfigString(pLunL0, "Driver", "HostFloppy"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "Path", bstr); + } + } + else + { + if (fInsertDiskIntegrityDrv) + { + /* + * The actual configuration is done through CFGM extra data + * for each inserted driver separately. + */ + InsertConfigString(pLunL0, "Driver", "DiskIntegrity"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + + InsertConfigString(pLunL0, "Driver", "VD"); + InsertConfigNode(pLunL0, "Config", &pCfg); + switch (enmType) + { + case DeviceType_DVD: + InsertConfigString(pCfg, "Type", "DVD"); + InsertConfigInteger(pCfg, "Mountable", 1); + break; + case DeviceType_Floppy: + InsertConfigString(pCfg, "Type", "Floppy 1.44"); + InsertConfigInteger(pCfg, "Mountable", 1); + break; + case DeviceType_HardDisk: + default: + InsertConfigString(pCfg, "Type", "HardDisk"); + InsertConfigInteger(pCfg, "Mountable", 0); + } + + if ( ptrMedium.isNotNull() + && ( enmType == DeviceType_DVD + || enmType == DeviceType_Floppy) + ) + { + // if this medium represents an ISO image and this image is inaccessible, + // the ignore it instead of causing a failure; this can happen when we + // restore a VM state and the ISO has disappeared, e.g. because the Guest + // Additions were mounted and the user upgraded VirtualBox. Previously + // we failed on startup, but that's not good because the only way out then + // would be to discard the VM state... + MediumState_T mediumState; + hrc = ptrMedium->RefreshState(&mediumState); H(); + if (mediumState == MediumState_Inaccessible) + { + Bstr loc; + hrc = ptrMedium->COMGETTER(Location)(loc.asOutParam()); H(); + i_atVMRuntimeErrorCallbackF(0, "DvdOrFloppyImageInaccessible", + N_("The image file '%ls' is inaccessible and is being ignored. " + "Please select a different image file for the virtual %s drive."), + loc.raw(), + enmType == DeviceType_DVD ? "DVD" : "floppy"); + ptrMedium.setNull(); + } + } + + if (ptrMedium.isNotNull()) + { + /* Start with length of parent chain, as the list is reversed */ + unsigned uImage = 0; + ComPtr<IMedium> ptrTmp = ptrMedium; + while (ptrTmp.isNotNull()) + { + uImage++; + ComPtr<IMedium> ptrParent; + hrc = ptrTmp->COMGETTER(Parent)(ptrParent.asOutParam()); H(); + ptrTmp = ptrParent; + } + /* Index of last image */ + uImage--; + +# ifdef VBOX_WITH_EXTPACK + if (mptrExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME)) + { + /* Configure loading the VDPlugin. */ + static const char s_szVDPlugin[] = "VDPluginCrypt"; + PCFGMNODE pCfgPlugins = NULL; + PCFGMNODE pCfgPlugin = NULL; + Utf8Str strPlugin; + hrc = mptrExtPackManager->i_getLibraryPathForExtPack(s_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin); + // Don't fail, this is optional! + if (SUCCEEDED(hrc)) + { + InsertConfigNode(pCfg, "Plugins", &pCfgPlugins); + InsertConfigNode(pCfgPlugins, s_szVDPlugin, &pCfgPlugin); + InsertConfigString(pCfgPlugin, "Path", strPlugin.c_str()); + } + } +# endif + + hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "Path", bstr); + + hrc = ptrMedium->COMGETTER(Format)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "Format", bstr); + + if (mediumType == MediumType_Readonly) + InsertConfigInteger(pCfg, "ReadOnly", 1); + else if (enmType == DeviceType_Floppy) + InsertConfigInteger(pCfg, "MaybeReadOnly", 1); + + /* Start without exclusive write access to the images. */ + /** @todo Live Migration: I don't quite like this, we risk screwing up when + * we're resuming the VM if some 3rd dude have any of the VDIs open + * with write sharing denied. However, if the two VMs are sharing a + * image it really is necessary.... + * + * So, on the "lock-media" command, the target teleporter should also + * make DrvVD undo TempReadOnly. It gets interesting if we fail after + * that. Grumble. */ + if ( enmType == DeviceType_HardDisk + && aMachineState == MachineState_TeleportingIn) + InsertConfigInteger(pCfg, "TempReadOnly", 1); + + /* Flag for opening the medium for sharing between VMs. This + * is done at the moment only for the first (and only) medium + * in the chain, as shared media can have no diffs. */ + if (mediumType == MediumType_Shareable) + InsertConfigInteger(pCfg, "Shareable", 1); + + if (!fUseHostIOCache) + { + InsertConfigInteger(pCfg, "UseNewIo", 1); + /* + * Activate the builtin I/O cache for harddisks only. + * It caches writes only which doesn't make sense for DVD drives + * and just increases the overhead. + */ + if ( fBuiltinIOCache + && (enmType == DeviceType_HardDisk)) + InsertConfigInteger(pCfg, "BlockCache", 1); + } + + if (fSetupMerge) + { + InsertConfigInteger(pCfg, "SetupMerge", 1); + if (uImage == uMergeSource) + InsertConfigInteger(pCfg, "MergeSource", 1); + else if (uImage == uMergeTarget) + InsertConfigInteger(pCfg, "MergeTarget", 1); + } + + if (pcszBwGroup) + InsertConfigString(pCfg, "BwGroup", pcszBwGroup); + + if (fDiscard) + InsertConfigInteger(pCfg, "Discard", 1); + + if (fNonRotational) + InsertConfigInteger(pCfg, "NonRotationalMedium", 1); + + /* Pass all custom parameters. */ + bool fHostIP = true; + bool fEncrypted = false; + hrc = i_configMediumProperties(pCfg, ptrMedium, &fHostIP, &fEncrypted); H(); + + /* Create an inverted list of parents. */ + uImage--; + ComPtr<IMedium> ptrParentMedium = ptrMedium; + for (PCFGMNODE pParent = pCfg;; uImage--) + { + ComPtr<IMedium> ptrCurMedium; + hrc = ptrParentMedium->COMGETTER(Parent)(ptrCurMedium.asOutParam()); H(); + if (ptrCurMedium.isNull()) + break; + + PCFGMNODE pCur; + InsertConfigNode(pParent, "Parent", &pCur); + hrc = ptrCurMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCur, "Path", bstr); + + hrc = ptrCurMedium->COMGETTER(Format)(bstr.asOutParam()); H(); + InsertConfigString(pCur, "Format", bstr); + + if (fSetupMerge) + { + if (uImage == uMergeSource) + InsertConfigInteger(pCur, "MergeSource", 1); + else if (uImage == uMergeTarget) + InsertConfigInteger(pCur, "MergeTarget", 1); + } + + /* Configure medium properties. */ + hrc = i_configMediumProperties(pCur, ptrCurMedium, &fHostIP, &fEncrypted); H(); + + /* next */ + pParent = pCur; + ptrParentMedium = ptrCurMedium; + } + + /* Custom code: put marker to not use host IP stack to driver + * configuration node. Simplifies life of DrvVD a bit. */ + if (!fHostIP) + InsertConfigInteger(pCfg, "HostIPStack", 0); + + if (fEncrypted) + m_cDisksEncrypted++; + } + else + { + /* Set empty drive flag for DVD or floppy without media. */ + if ( enmType == DeviceType_DVD + || enmType == DeviceType_Floppy) + InsertConfigInteger(pCfg, "EmptyDrive", 1); + } + } +#undef H + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + + return VINF_SUCCESS; +} + +/** + * Adds the medium properties to the CFGM tree. + * + * @returns VBox status code. + * @param pCur The current CFGM node. + * @param pMedium The medium object to configure. + * @param pfHostIP Where to return the value of the \"HostIPStack\" property if found. + * @param pfEncrypted Where to return whether the medium is encrypted. + */ +int Console::i_configMediumProperties(PCFGMNODE pCur, IMedium *pMedium, bool *pfHostIP, bool *pfEncrypted) +{ + /* Pass all custom parameters. */ + SafeArray<BSTR> aNames; + SafeArray<BSTR> aValues; + HRESULT hrc = pMedium->GetProperties(NULL, ComSafeArrayAsOutParam(aNames), + ComSafeArrayAsOutParam(aValues)); + + if ( SUCCEEDED(hrc) + && aNames.size() != 0) + { + PCFGMNODE pVDC; + InsertConfigNode(pCur, "VDConfig", &pVDC); + for (size_t ii = 0; ii < aNames.size(); ++ii) + { + if (aValues[ii] && *aValues[ii]) + { + Utf8Str name = aNames[ii]; + Utf8Str value = aValues[ii]; + size_t offSlash = name.find("/", 0); + if ( offSlash != name.npos + && !name.startsWith("Special/")) + { + com::Utf8Str strFilter; + com::Utf8Str strKey; + + hrc = strFilter.assignEx(name, 0, offSlash); + if (FAILED(hrc)) + break; + + hrc = strKey.assignEx(name, offSlash + 1, name.length() - offSlash - 1); /* Skip slash */ + if (FAILED(hrc)) + break; + + PCFGMNODE pCfgFilterConfig = mpVMM->pfnCFGMR3GetChild(pVDC, strFilter.c_str()); + if (!pCfgFilterConfig) + InsertConfigNode(pVDC, strFilter.c_str(), &pCfgFilterConfig); + + InsertConfigString(pCfgFilterConfig, strKey.c_str(), value); + } + else + { + InsertConfigString(pVDC, name.c_str(), value); + if ( name.compare("HostIPStack") == 0 + && value.compare("0") == 0) + *pfHostIP = false; + } + + if ( name.compare("CRYPT/KeyId") == 0 + && pfEncrypted) + *pfEncrypted = true; + } + } + } + + return hrc; +} + + +/** + * Configure proxy parameters the Network configuration tree. + * Parameters may differ depending on the IP address being accessed. + * + * @returns VBox status code. + * + * @param virtualBox The VirtualBox object. + * @param pCfg Configuration node for the driver. + * @param pcszPrefix The prefix for CFGM parameters: "Primary" or "Secondary". + * @param strIpAddr The public IP address to be accessed via a proxy. + * + * @thread EMT + */ +int Console::i_configProxy(ComPtr<IVirtualBox> virtualBox, PCFGMNODE pCfg, const char *pcszPrefix, const com::Utf8Str &strIpAddr) +{ + RTHTTPPROXYINFO ProxyInfo; + ComPtr<ISystemProperties> systemProperties; + ProxyMode_T enmProxyMode; + HRESULT hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("CLOUD-NET: Failed to obtain system properties. hrc=%x\n", hrc)); + return false; + } + hrc = systemProperties->COMGETTER(ProxyMode)(&enmProxyMode); + if (FAILED(hrc)) + { + LogRel(("CLOUD-NET: Failed to obtain default machine folder. hrc=%x\n", hrc)); + return VERR_INTERNAL_ERROR; + } + + RTHTTP hHttp; + int vrc = RTHttpCreate(&hHttp); + if (RT_FAILURE(vrc)) + { + LogRel(("CLOUD-NET: Failed to create HTTP context (rc=%Rrc)\n", vrc)); + return vrc; + } + + char *pszProxyType = NULL; + + if (enmProxyMode == ProxyMode_Manual) + { + /* + * Unfortunately we cannot simply call RTHttpSetProxyByUrl because it never + * exposes proxy settings. Calling RTHttpQueryProxyInfoForUrl afterward + * won't help either as it uses system-wide proxy settings instead of + * parameters we would have set with RTHttpSetProxyByUrl. Hence we parse + * proxy URL ourselves here. + */ + Bstr proxyUrl; + hrc = systemProperties->COMGETTER(ProxyURL)(proxyUrl.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("CLOUD-NET: Failed to obtain proxy URL. hrc=%x\n", hrc)); + return false; + } + Utf8Str strProxyUrl = proxyUrl; + if (!strProxyUrl.contains("://")) + strProxyUrl = "http://" + strProxyUrl; + const char *pcszProxyUrl = strProxyUrl.c_str(); + RTURIPARSED Parsed; + vrc = RTUriParse(pcszProxyUrl, &Parsed); + if (RT_FAILURE(vrc)) + { + LogRel(("CLOUD-NET: Failed to parse proxy URL: %ls (vrc=%Rrc)\n", proxyUrl.raw(), vrc)); + return false; + } + + pszProxyType = RTUriParsedScheme(pcszProxyUrl, &Parsed); + if (!pszProxyType) + { + LogRel(("CLOUD-NET: Failed to get proxy scheme from proxy URL: %s\n", pcszProxyUrl)); + return false; + } + RTStrToUpper(pszProxyType); + + ProxyInfo.pszProxyHost = RTUriParsedAuthorityHost(pcszProxyUrl, &Parsed); + if (!ProxyInfo.pszProxyHost) + { + LogRel(("CLOUD-NET: Failed to get proxy host name from proxy URL: %s\n", pcszProxyUrl)); + return false; + } + ProxyInfo.uProxyPort = RTUriParsedAuthorityPort(pcszProxyUrl, &Parsed); + if (ProxyInfo.uProxyPort == UINT32_MAX) + { + LogRel(("CLOUD-NET: Failed to get proxy port from proxy URL: %s\n", pcszProxyUrl)); + return false; + } + ProxyInfo.pszProxyUsername = RTUriParsedAuthorityUsername(pcszProxyUrl, &Parsed); + ProxyInfo.pszProxyPassword = RTUriParsedAuthorityPassword(pcszProxyUrl, &Parsed); + } + else if (enmProxyMode == ProxyMode_System) + { + vrc = RTHttpUseSystemProxySettings(hHttp); + if (RT_FAILURE(vrc)) + { + LogRel(("%s: RTHttpUseSystemProxySettings() failed: %Rrc", __FUNCTION__, vrc)); + RTHttpDestroy(hHttp); + return vrc; + } + vrc = RTHttpQueryProxyInfoForUrl(hHttp, ("http://" + strIpAddr).c_str(), &ProxyInfo); + RTHttpDestroy(hHttp); + if (RT_FAILURE(vrc)) + { + LogRel(("CLOUD-NET: Failed to get proxy for %s (rc=%Rrc)\n", strIpAddr.c_str(), vrc)); + return vrc; + } + + switch (ProxyInfo.enmProxyType) + { + case RTHTTPPROXYTYPE_NOPROXY: + /* Nothing to do */ + return VINF_SUCCESS; + case RTHTTPPROXYTYPE_HTTP: + pszProxyType = RTStrDup("HTTP"); + break; + case RTHTTPPROXYTYPE_HTTPS: + case RTHTTPPROXYTYPE_SOCKS4: + case RTHTTPPROXYTYPE_SOCKS5: + /* break; -- Fall through until support is implemented */ + case RTHTTPPROXYTYPE_UNKNOWN: + case RTHTTPPROXYTYPE_INVALID: + case RTHTTPPROXYTYPE_END: + case RTHTTPPROXYTYPE_32BIT_HACK: + LogRel(("CLOUD-NET: Unsupported proxy type %u\n", ProxyInfo.enmProxyType)); + RTHttpFreeProxyInfo(&ProxyInfo); + return VERR_INVALID_PARAMETER; + } + } + else + { + Assert(enmProxyMode == ProxyMode_NoProxy); + return VINF_SUCCESS; + } + + /* Resolve proxy host name to IP address if necessary */ + RTNETADDR addr; + RTSocketParseInetAddress(ProxyInfo.pszProxyHost, ProxyInfo.uProxyPort, &addr); + if (addr.enmType != RTNETADDRTYPE_IPV4) + { + LogRel(("CLOUD-NET: Unsupported address type %u\n", addr.enmType)); + RTHttpFreeProxyInfo(&ProxyInfo); + return VERR_INVALID_PARAMETER; + } + + InsertConfigString(pCfg, Utf8StrFmt("%sProxyType", pcszPrefix).c_str(), pszProxyType); + InsertConfigInteger(pCfg, Utf8StrFmt("%sProxyPort", pcszPrefix).c_str(), ProxyInfo.uProxyPort); + if (ProxyInfo.pszProxyHost) + InsertConfigString(pCfg, Utf8StrFmt("%sProxyHost", pcszPrefix).c_str(), Utf8StrFmt("%RTnaipv4", addr.uAddr.IPv4)); + if (ProxyInfo.pszProxyUsername) + InsertConfigString(pCfg, Utf8StrFmt("%sProxyUser", pcszPrefix).c_str(), ProxyInfo.pszProxyUsername); + if (ProxyInfo.pszProxyPassword) + InsertConfigPassword(pCfg, Utf8StrFmt("%sProxyPassword", pcszPrefix).c_str(), ProxyInfo.pszProxyPassword); + + RTHttpFreeProxyInfo(&ProxyInfo); + RTStrFree(pszProxyType); + return vrc; +} + + +/** + * Construct the Network configuration tree + * + * @returns VBox status code. + * + * @param pszDevice The PDM device name. + * @param uInstance The PDM device instance. + * @param uLun The PDM LUN number of the drive. + * @param aNetworkAdapter The network adapter whose attachment needs to be changed + * @param pCfg Configuration node for the device + * @param pLunL0 To store the pointer to the LUN#0. + * @param pInst The instance CFGM node + * @param fAttachDetach To determine if the network attachment should + * be attached/detached after/before + * configuration. + * @param fIgnoreConnectFailure + * True if connection failures should be ignored + * (makes only sense for bridged/host-only networks). + * @param pUVM The usermode VM handle. + * @param pVMM The VMM vtable. + * + * @note Locks this object for writing. + * @thread EMT + */ +int Console::i_configNetwork(const char *pszDevice, + unsigned uInstance, + unsigned uLun, + INetworkAdapter *aNetworkAdapter, + PCFGMNODE pCfg, + PCFGMNODE pLunL0, + PCFGMNODE pInst, + bool fAttachDetach, + bool fIgnoreConnectFailure, + PUVM pUVM, + PCVMMR3VTABLE pVMM) +{ + RT_NOREF(fIgnoreConnectFailure); + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + // InsertConfig* throws + try + { + int vrc = VINF_SUCCESS; + HRESULT hrc; + Bstr bstr; + +#ifdef VBOX_WITH_CLOUD_NET + /* We'll need device's pCfg for cloud attachments */ + PCFGMNODE pDevCfg = pCfg; +#endif /* VBOX_WITH_CLOUD_NET */ + +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + + /* + * Locking the object before doing VMR3* calls is quite safe here, since + * we're on EMT. Write lock is necessary because we indirectly modify the + * meAttachmentType member. + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + ComPtr<IMachine> pMachine = i_machine(); + + ComPtr<IVirtualBox> virtualBox; + hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H(); + + ComPtr<IHost> host; + hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H(); + + BOOL fSniffer; + hrc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fSniffer); H(); + + NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy; + hrc = aNetworkAdapter->COMGETTER(PromiscModePolicy)(&enmPromiscModePolicy); H(); + const char *pszPromiscuousGuestPolicy; + switch (enmPromiscModePolicy) + { + case NetworkAdapterPromiscModePolicy_Deny: pszPromiscuousGuestPolicy = "deny"; break; + case NetworkAdapterPromiscModePolicy_AllowNetwork: pszPromiscuousGuestPolicy = "allow-network"; break; + case NetworkAdapterPromiscModePolicy_AllowAll: pszPromiscuousGuestPolicy = "allow-all"; break; + default: AssertFailedReturn(VERR_INTERNAL_ERROR_4); + } + + if (fAttachDetach) + { + vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/); + if (vrc == VINF_PDM_NO_DRIVER_ATTACHED_TO_LUN) + vrc = VINF_SUCCESS; + AssertLogRelRCReturn(vrc, vrc); + + /* Nuke anything which might have been left behind. */ + pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", uLun)); + } + + Bstr networkName, trunkName, trunkType; + NetworkAttachmentType_T eAttachmentType; + hrc = aNetworkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H(); + +#ifdef VBOX_WITH_NETSHAPER + ComObjPtr<IBandwidthGroup> pBwGroup; + Bstr bstrBwGroup; + hrc = aNetworkAdapter->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H(); + + if (!pBwGroup.isNull()) + { + hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H(); + } +#endif /* VBOX_WITH_NETSHAPER */ + + AssertMsg(uLun == 0, ("Network attachments with LUN > 0 are not supported yet\n")); + InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", uLun); + + /* + * Do not insert neither a shaper nor a sniffer if we are not attached to anything. + * This way we can easily detect if we are attached to anything at the device level. + */ +#ifdef VBOX_WITH_NETSHAPER + if (bstrBwGroup.isNotEmpty() && eAttachmentType != NetworkAttachmentType_Null) + { + InsertConfigString(pLunL0, "Driver", "NetShaper"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "BwGroup", bstrBwGroup); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } +#endif /* VBOX_WITH_NETSHAPER */ + + if (fSniffer && eAttachmentType != NetworkAttachmentType_Null) + { + InsertConfigString(pLunL0, "Driver", "NetSniffer"); + InsertConfigNode(pLunL0, "Config", &pCfg); + hrc = aNetworkAdapter->COMGETTER(TraceFile)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) /* check convention for indicating default file. */ + InsertConfigString(pCfg, "File", bstr); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + + switch (eAttachmentType) + { + case NetworkAttachmentType_Null: + break; + + case NetworkAttachmentType_NAT: + { + ComPtr<INATEngine> natEngine; + hrc = aNetworkAdapter->COMGETTER(NATEngine)(natEngine.asOutParam()); H(); + InsertConfigString(pLunL0, "Driver", "NAT"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + /* Configure TFTP prefix and boot filename. */ + hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + InsertConfigString(pCfg, "TFTPPrefix", Utf8StrFmt("%ls%c%s", bstr.raw(), RTPATH_DELIMITER, "TFTP")); + hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "BootFile", Utf8StrFmt("%ls.pxe", bstr.raw())); + + hrc = natEngine->COMGETTER(Network)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + InsertConfigString(pCfg, "Network", bstr); + else + { + ULONG uSlot; + hrc = aNetworkAdapter->COMGETTER(Slot)(&uSlot); H(); + InsertConfigString(pCfg, "Network", Utf8StrFmt("10.0.%d.0/24", uSlot+2)); + } + hrc = natEngine->COMGETTER(HostIP)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + InsertConfigString(pCfg, "BindIP", bstr); + ULONG mtu = 0; + ULONG sockSnd = 0; + ULONG sockRcv = 0; + ULONG tcpSnd = 0; + ULONG tcpRcv = 0; + hrc = natEngine->GetNetworkSettings(&mtu, &sockSnd, &sockRcv, &tcpSnd, &tcpRcv); H(); + if (mtu) + InsertConfigInteger(pCfg, "SlirpMTU", mtu); + if (sockRcv) + InsertConfigInteger(pCfg, "SockRcv", sockRcv); + if (sockSnd) + InsertConfigInteger(pCfg, "SockSnd", sockSnd); + if (tcpRcv) + InsertConfigInteger(pCfg, "TcpRcv", tcpRcv); + if (tcpSnd) + InsertConfigInteger(pCfg, "TcpSnd", tcpSnd); + hrc = natEngine->COMGETTER(TFTPPrefix)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + { + RemoveConfigValue(pCfg, "TFTPPrefix"); + InsertConfigString(pCfg, "TFTPPrefix", bstr); + } + hrc = natEngine->COMGETTER(TFTPBootFile)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + { + RemoveConfigValue(pCfg, "BootFile"); + InsertConfigString(pCfg, "BootFile", bstr); + } + hrc = natEngine->COMGETTER(TFTPNextServer)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + InsertConfigString(pCfg, "NextServer", bstr); + BOOL fDNSFlag; + hrc = natEngine->COMGETTER(DNSPassDomain)(&fDNSFlag); H(); + InsertConfigInteger(pCfg, "PassDomain", fDNSFlag); + hrc = natEngine->COMGETTER(DNSProxy)(&fDNSFlag); H(); + InsertConfigInteger(pCfg, "DNSProxy", fDNSFlag); + hrc = natEngine->COMGETTER(DNSUseHostResolver)(&fDNSFlag); H(); + InsertConfigInteger(pCfg, "UseHostResolver", fDNSFlag); + + ULONG aliasMode; + hrc = natEngine->COMGETTER(AliasMode)(&aliasMode); H(); + InsertConfigInteger(pCfg, "AliasMode", aliasMode); + + BOOL fLocalhostReachable; + hrc = natEngine->COMGETTER(LocalhostReachable)(&fLocalhostReachable); H(); + InsertConfigInteger(pCfg, "LocalhostReachable", fLocalhostReachable); + + /* port-forwarding */ + SafeArray<BSTR> pfs; + hrc = natEngine->COMGETTER(Redirects)(ComSafeArrayAsOutParam(pfs)); H(); + + PCFGMNODE pPFTree = NULL; + if (pfs.size() > 0) + InsertConfigNode(pCfg, "PortForwarding", &pPFTree); + + for (unsigned int i = 0; i < pfs.size(); ++i) + { + PCFGMNODE pPF = NULL; /* /Devices/Dev/.../Config/PortForwarding/$n/ */ + + uint16_t port = 0; + Utf8Str utf = pfs[i]; + Utf8Str strName; + Utf8Str strProto; + Utf8Str strHostPort; + Utf8Str strHostIP; + Utf8Str strGuestPort; + Utf8Str strGuestIP; + size_t pos, ppos; + pos = ppos = 0; +#define ITERATE_TO_NEXT_TERM(res, str, pos, ppos) \ + { \ + pos = str.find(",", ppos); \ + if (pos == Utf8Str::npos) \ + { \ + Log(( #res " extracting from %s is failed\n", str.c_str())); \ + continue; \ + } \ + res = str.substr(ppos, pos - ppos); \ + Log2((#res " %s pos:%d, ppos:%d\n", res.c_str(), pos, ppos)); \ + ppos = pos + 1; \ + } /* no do { ... } while because of 'continue' */ + ITERATE_TO_NEXT_TERM(strName, utf, pos, ppos); + ITERATE_TO_NEXT_TERM(strProto, utf, pos, ppos); + ITERATE_TO_NEXT_TERM(strHostIP, utf, pos, ppos); + ITERATE_TO_NEXT_TERM(strHostPort, utf, pos, ppos); + ITERATE_TO_NEXT_TERM(strGuestIP, utf, pos, ppos); + strGuestPort = utf.substr(ppos, utf.length() - ppos); +#undef ITERATE_TO_NEXT_TERM + + uint32_t proto = strProto.toUInt32(); + bool fValid = true; + switch (proto) + { + case NATProtocol_UDP: + strProto = "UDP"; + break; + case NATProtocol_TCP: + strProto = "TCP"; + break; + default: + fValid = false; + } + /* continue with next rule if no valid proto was passed */ + if (!fValid) + continue; + + InsertConfigNode(pPFTree, Utf8StrFmt("%u", i).c_str(), &pPF); + + if (!strName.isEmpty()) + InsertConfigString(pPF, "Name", strName); + + InsertConfigString(pPF, "Protocol", strProto); + + if (!strHostIP.isEmpty()) + InsertConfigString(pPF, "BindIP", strHostIP); + + if (!strGuestIP.isEmpty()) + InsertConfigString(pPF, "GuestIP", strGuestIP); + + port = RTStrToUInt16(strHostPort.c_str()); + if (port) + InsertConfigInteger(pPF, "HostPort", port); + + port = RTStrToUInt16(strGuestPort.c_str()); + if (port) + InsertConfigInteger(pPF, "GuestPort", port); + } + break; + } + + case NetworkAttachmentType_Bridged: + { +#if (defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)) && !defined(VBOX_WITH_NETFLT) + hrc = i_attachToTapInterface(aNetworkAdapter); + if (FAILED(hrc)) + { + switch (hrc) + { + case E_ACCESSDENIED: + return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_( + "Failed to open '/dev/net/tun' for read/write access. Please check the " + "permissions of that node. Either run 'chmod 0666 /dev/net/tun' or " + "change the group of that node and make yourself a member of that group. " + "Make sure that these changes are permanent, especially if you are " + "using udev")); + default: + AssertMsgFailed(("Could not attach to host interface! Bad!\n")); + return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, + N_("Failed to initialize Host Interface Networking")); + } + } + + Assert((intptr_t)maTapFD[uInstance] >= 0); + if ((intptr_t)maTapFD[uInstance] >= 0) + { + InsertConfigString(pLunL0, "Driver", "HostInterface"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]); + } + +#elif defined(VBOX_WITH_NETFLT) + /* + * This is the new VBoxNetFlt+IntNet stuff. + */ + Bstr BridgedIfName; + hrc = aNetworkAdapter->COMGETTER(BridgedInterface)(BridgedIfName.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_Bridged: COMGETTER(BridgedInterface) failed, hrc (0x%x)\n", hrc)); + H(); + } + + Utf8Str BridgedIfNameUtf8(BridgedIfName); + const char *pszBridgedIfName = BridgedIfNameUtf8.c_str(); + + ComPtr<IHostNetworkInterface> hostInterface; + hrc = host->FindHostNetworkInterfaceByName(BridgedIfName.raw(), + hostInterface.asOutParam()); + if (!SUCCEEDED(hrc)) + { + AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: FindByName failed, rc=%Rhrc (0x%x)\n", hrc, hrc)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Nonexistent host networking interface, name '%ls'"), + BridgedIfName.raw()); + } + +# if defined(RT_OS_DARWIN) + /* The name is in the format 'ifX: long name', chop it off at the colon. */ + char szTrunk[INTNET_MAX_TRUNK_NAME]; + RTStrCopy(szTrunk, sizeof(szTrunk), pszBridgedIfName); + char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk)); +// Quick fix for @bugref{5633} +// if (!pszColon) +// { +// /* +// * Dynamic changing of attachment causes an attempt to configure +// * network with invalid host adapter (as it is must be changed before +// * the attachment), calling Detach here will cause a deadlock. +// * See @bugref{4750}. +// * hrc = aNetworkAdapter->Detach(); H(); +// */ +// return VMSetError(VMR3GetVM(mpUVM), VERR_INTERNAL_ERROR, RT_SRC_POS, +// N_("Malformed host interface networking name '%ls'"), +// BridgedIfName.raw()); +// } + if (pszColon) + *pszColon = '\0'; + const char *pszTrunk = szTrunk; + +# elif defined(RT_OS_SOLARIS) + /* The name is in the format 'ifX[:1] - long name, chop it off at space. */ + char szTrunk[256]; + strlcpy(szTrunk, pszBridgedIfName, sizeof(szTrunk)); + char *pszSpace = (char *)memchr(szTrunk, ' ', sizeof(szTrunk)); + + /* + * Currently don't bother about malformed names here for the sake of people using + * VBoxManage and setting only the NIC name from there. If there is a space we + * chop it off and proceed, otherwise just use whatever we've got. + */ + if (pszSpace) + *pszSpace = '\0'; + + /* Chop it off at the colon (zone naming eg: e1000g:1 we need only the e1000g) */ + char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk)); + if (pszColon) + *pszColon = '\0'; + + const char *pszTrunk = szTrunk; + +# elif defined(RT_OS_WINDOWS) + HostNetworkInterfaceType_T eIfType; + hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_Bridged: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc)); + H(); + } + + if (eIfType != HostNetworkInterfaceType_Bridged) + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Interface ('%ls') is not a Bridged Adapter interface"), + BridgedIfName.raw()); + + hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Id) failed, hrc (0x%x)\n", hrc)); + H(); + } + Guid hostIFGuid(bstr); + + INetCfg *pNc; + ComPtr<INetCfgComponent> pAdaptorComponent; + LPWSTR pszApp; + + hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp); + Assert(hrc == S_OK); + if (hrc != S_OK) + { + LogRel(("NetworkAttachmentType_Bridged: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc)); + H(); + } + + /* get the adapter's INetCfgComponent*/ + hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(), + pAdaptorComponent.asOutParam()); + if (hrc != S_OK) + { + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + LogRel(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)\n", hrc)); + H(); + } +# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\" + char szTrunkName[INTNET_MAX_TRUNK_NAME]; + char *pszTrunkName = szTrunkName; + wchar_t * pswzBindName; + hrc = pAdaptorComponent->GetBindName(&pswzBindName); + Assert(hrc == S_OK); + if (hrc == S_OK) + { + int cwBindName = (int)wcslen(pswzBindName) + 1; + int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX); + if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName) + { + strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX); + pszTrunkName += cbFullBindNamePrefix-1; + if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName, + sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL)) + { + DWORD err = GetLastError(); + hrc = HRESULT_FROM_WIN32(err); + AssertMsgFailed(("hrc=%Rhrc %#x\n", hrc, hrc)); + AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n", + hrc, hrc, err)); + } + } + else + { + AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: insufficient szTrunkName buffer space\n")); + /** @todo set appropriate error code */ + hrc = E_FAIL; + } + + if (hrc != S_OK) + { + AssertFailed(); + CoTaskMemFree(pswzBindName); + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + H(); + } + + /* we're not freeing the bind name since we'll use it later for detecting wireless*/ + } + else + { + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)", + hrc)); + H(); + } + + const char *pszTrunk = szTrunkName; + /* we're not releasing the INetCfg stuff here since we use it later to figure out whether it is wireless */ + +# elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) +# if defined(RT_OS_FREEBSD) + /* + * If we bridge to a tap interface open it the `old' direct way. + * This works and performs better than bridging a physical + * interface via the current FreeBSD vboxnetflt implementation. + */ + if (!strncmp(pszBridgedIfName, RT_STR_TUPLE("tap"))) { + hrc = i_attachToTapInterface(aNetworkAdapter); + if (FAILED(hrc)) + { + switch (hrc) + { + case E_ACCESSDENIED: + return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_( + "Failed to open '/dev/%s' for read/write access. Please check the " + "permissions of that node, and that the net.link.tap.user_open " + "sysctl is set. Either run 'chmod 0666 /dev/%s' or change the " + "group of that node to vboxusers and make yourself a member of " + "that group. Make sure that these changes are permanent."), + pszBridgedIfName, pszBridgedIfName); + default: + AssertMsgFailed(("Could not attach to tap interface! Bad!\n")); + return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, + N_("Failed to initialize Host Interface Networking")); + } + } + + Assert((intptr_t)maTapFD[uInstance] >= 0); + if ((intptr_t)maTapFD[uInstance] >= 0) + { + InsertConfigString(pLunL0, "Driver", "HostInterface"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]); + } + break; + } +# endif + /** @todo Check for malformed names. */ + const char *pszTrunk = pszBridgedIfName; + + /* Issue a warning if the interface is down */ + { + int iSock = socket(AF_INET, SOCK_DGRAM, 0); + if (iSock >= 0) + { + struct ifreq Req; + RT_ZERO(Req); + RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pszBridgedIfName); + if (ioctl(iSock, SIOCGIFFLAGS, &Req) >= 0) + if ((Req.ifr_flags & IFF_UP) == 0) + i_atVMRuntimeErrorCallbackF(0, "BridgedInterfaceDown", + N_("Bridged interface %s is down. Guest will not be able to use this interface"), + pszBridgedIfName); + + close(iSock); + } + } + +# else +# error "PORTME (VBOX_WITH_NETFLT)" +# endif + +# if defined(RT_OS_DARWIN) && defined(VBOX_WITH_VMNET) + InsertConfigString(pLunL0, "Driver", "VMNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "Trunk", pszTrunk); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt); +# else + InsertConfigString(pLunL0, "Driver", "IntNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "Trunk", pszTrunk); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt); + InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure); + InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy); + char szNetwork[INTNET_MAX_NETWORK_NAME]; + +# if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN) + /* + * 'pszTrunk' contains just the interface name required in ring-0, while 'pszBridgedIfName' contains + * interface name + optional description. We must not pass any description to the VM as it can differ + * for the same interface name, eg: "nge0 - ethernet" (GUI) vs "nge0" (VBoxManage). + */ + RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszTrunk); +# else + RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszBridgedIfName); +# endif + InsertConfigString(pCfg, "Network", szNetwork); + networkName = Bstr(szNetwork); + trunkName = Bstr(pszTrunk); + trunkType = Bstr(TRUNKTYPE_NETFLT); + + BOOL fSharedMacOnWire = false; + hrc = hostInterface->COMGETTER(Wireless)(&fSharedMacOnWire); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Wireless) failed, hrc (0x%x)\n", hrc)); + H(); + } + else if (fSharedMacOnWire) + { + InsertConfigInteger(pCfg, "SharedMacOnWire", true); + Log(("Set SharedMacOnWire\n")); + } + +# if defined(RT_OS_SOLARIS) +# if 0 /* bird: this is a bit questionable and might cause more trouble than its worth. */ + /* Zone access restriction, don't allow snooping the global zone. */ + zoneid_t ZoneId = getzoneid(); + if (ZoneId != GLOBAL_ZONEID) + { + InsertConfigInteger(pCfg, "IgnoreAllPromisc", true); + } +# endif +# endif +# endif + +#elif defined(RT_OS_WINDOWS) /* not defined NetFlt */ + /* NOTHING TO DO HERE */ +#elif defined(RT_OS_LINUX) +/// @todo aleksey: is there anything to be done here? +#elif defined(RT_OS_FREEBSD) +/** @todo FreeBSD: Check out this later (HIF networking). */ +#else +# error "Port me" +#endif + break; + } + + case NetworkAttachmentType_Internal: + { + hrc = aNetworkAdapter->COMGETTER(InternalNetwork)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + { + InsertConfigString(pLunL0, "Driver", "IntNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "Network", bstr); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone); + InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy); + networkName = bstr; + trunkType = Bstr(TRUNKTYPE_WHATEVER); + } + break; + } + + case NetworkAttachmentType_HostOnly: + { + InsertConfigString(pLunL0, "Driver", "IntNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + Bstr HostOnlyName; + hrc = aNetworkAdapter->COMGETTER(HostOnlyInterface)(HostOnlyName.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(HostOnlyInterface) failed, hrc (0x%x)\n", hrc)); + H(); + } + + Utf8Str HostOnlyNameUtf8(HostOnlyName); + const char *pszHostOnlyName = HostOnlyNameUtf8.c_str(); +#ifdef VBOX_WITH_VMNET + /* Check if the matching host-only network has already been created. */ + Bstr bstrLowerIP, bstrUpperIP, bstrNetworkMask; + BstrFmt bstrNetworkName("Legacy %s Network", pszHostOnlyName); + ComPtr<IHostOnlyNetwork> hostOnlyNetwork; + hrc = virtualBox->FindHostOnlyNetworkByName(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam()); + if (FAILED(hrc)) + { + /* + * With VMNET there is no VBoxNetAdp to create vboxnetX adapters, + * which means that the Host object won't be able to re-create + * them from extra data. Go through existing DHCP/adapter config + * to derive the parameters for the new network. + */ + BstrFmt bstrOldNetworkName("HostInterfaceNetworking-%s", pszHostOnlyName); + ComPtr<IDHCPServer> dhcpServer; + hrc = virtualBox->FindDHCPServerByNetworkName(bstrOldNetworkName.raw(), + dhcpServer.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* There is a DHCP server available for this network. */ + hrc = dhcpServer->COMGETTER(LowerIP)(bstrLowerIP.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("Console::i_configNetwork: COMGETTER(LowerIP) failed, hrc (%Rhrc)\n", hrc)); + H(); + } + hrc = dhcpServer->COMGETTER(UpperIP)(bstrUpperIP.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("Console::i_configNetwork: COMGETTER(UpperIP) failed, hrc (%Rhrc)\n", hrc)); + H(); + } + hrc = dhcpServer->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("Console::i_configNetwork: COMGETTER(NetworkMask) failed, hrc (%Rhrc)\n", hrc)); + H(); + } + } + else + { + /* No DHCP server for this hostonly interface, let's look at extra data */ + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress", + pszHostOnlyName).raw(), + bstrLowerIP.asOutParam()); + if (SUCCEEDED(hrc) && !bstrLowerIP.isEmpty()) + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask", + pszHostOnlyName).raw(), + bstrNetworkMask.asOutParam()); + + } + RTNETADDRIPV4 ipAddr, ipMask; + vrc = bstrLowerIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr); + if (RT_FAILURE(vrc)) + { + /* We failed to locate any valid config of this vboxnetX interface, assume defaults. */ + LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing lower IP '%ls', using '%ls' instead.\n", + bstrLowerIP.raw(), getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw())); + bstrLowerIP = getDefaultIPv4Address(Bstr(pszHostOnlyName)); + bstrNetworkMask.setNull(); + bstrUpperIP.setNull(); + vrc = RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr); + AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrLowerIP.raw(), vrc), + VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR); + } + vrc = bstrNetworkMask.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask); + if (RT_FAILURE(vrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing network mask '%ls', using '%s' instead.\n", + bstrNetworkMask.raw(), VBOXNET_IPV4MASK_DEFAULT)); + bstrNetworkMask = VBOXNET_IPV4MASK_DEFAULT; + vrc = RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask); + AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrNetworkMask.raw(), vrc), + VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR); + } + vrc = bstrUpperIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrUpperIP).c_str(), &ipAddr); + if (RT_FAILURE(vrc)) + { + ipAddr.au32[0] = RT_H2N_U32((RT_N2H_U32(ipAddr.au32[0]) | ~RT_N2H_U32(ipMask.au32[0])) - 1); /* Do we need to exlude the last IP? */ + LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing upper IP '%ls', using '%RTnaipv4' instead.\n", + bstrUpperIP.raw(), ipAddr)); + bstrUpperIP = BstrFmt("%RTnaipv4", ipAddr); + } + + /* All parameters are set, create the new network. */ + hrc = virtualBox->CreateHostOnlyNetwork(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: failed to create host-only network, hrc (0x%x)\n", hrc)); + H(); + } + hrc = hostOnlyNetwork->COMSETTER(NetworkMask)(bstrNetworkMask.raw()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(NetworkMask) failed, hrc (0x%x)\n", hrc)); + H(); + } + hrc = hostOnlyNetwork->COMSETTER(LowerIP)(bstrLowerIP.raw()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(LowerIP) failed, hrc (0x%x)\n", hrc)); + H(); + } + hrc = hostOnlyNetwork->COMSETTER(UpperIP)(bstrUpperIP.raw()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(UpperIP) failed, hrc (0x%x)\n", hrc)); + H(); + } + LogRel(("Console: created host-only network '%ls' with mask '%ls' and range '%ls'-'%ls'\n", + bstrNetworkName.raw(), bstrNetworkMask.raw(), bstrLowerIP.raw(), bstrUpperIP.raw())); + } + else + { + /* The matching host-only network already exists. Tell the user to switch to it. */ + hrc = hostOnlyNetwork->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(NetworkMask) failed, hrc (0x%x)\n", hrc)); + H(); + } + hrc = hostOnlyNetwork->COMGETTER(LowerIP)(bstrLowerIP.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(LowerIP) failed, hrc (0x%x)\n", hrc)); + H(); + } + hrc = hostOnlyNetwork->COMGETTER(UpperIP)(bstrUpperIP.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(UpperIP) failed, hrc (0x%x)\n", hrc)); + H(); + } + } + return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Host-only adapters are no longer supported!\n" + "For your convenience a host-only network named '%ls' has been " + "created with network mask '%ls' and IP address range '%ls' - '%ls'.\n" + "To fix this problem, switch to 'Host-only Network' " + "attachment type in the VM settings.\n"), + bstrNetworkName.raw(), bstrNetworkMask.raw(), + bstrLowerIP.raw(), bstrUpperIP.raw()); +#endif /* VBOX_WITH_VMNET */ + ComPtr<IHostNetworkInterface> hostInterface; + hrc = host->FindHostNetworkInterfaceByName(HostOnlyName.raw(), + hostInterface.asOutParam()); + if (!SUCCEEDED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: FindByName failed, vrc=%Rrc\n", vrc)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Nonexistent host networking interface, name '%ls'"), HostOnlyName.raw()); + } + + char szNetwork[INTNET_MAX_NETWORK_NAME]; + RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszHostOnlyName); + +#if defined(RT_OS_WINDOWS) +# ifndef VBOX_WITH_NETFLT + hrc = E_NOTIMPL; + LogRel(("NetworkAttachmentType_HostOnly: Not Implemented\n")); + H(); +# else /* defined VBOX_WITH_NETFLT*/ + /** @todo r=bird: Put this in a function. */ + + HostNetworkInterfaceType_T eIfType; + hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc)); + H(); + } + + if (eIfType != HostNetworkInterfaceType_HostOnly) + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Interface ('%ls') is not a Host-Only Adapter interface"), + HostOnlyName.raw()); + + hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(Id) failed, hrc (0x%x)\n", hrc)); + H(); + } + Guid hostIFGuid(bstr); + + INetCfg *pNc; + ComPtr<INetCfgComponent> pAdaptorComponent; + LPWSTR pszApp; + hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp); + Assert(hrc == S_OK); + if (hrc != S_OK) + { + LogRel(("NetworkAttachmentType_HostOnly: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc)); + H(); + } + + /* get the adapter's INetCfgComponent*/ + hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(), + pAdaptorComponent.asOutParam()); + if (hrc != S_OK) + { + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + LogRel(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n", hrc, hrc)); + H(); + } +# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\" + char szTrunkName[INTNET_MAX_TRUNK_NAME]; + bool fNdis6 = false; + wchar_t * pwszHelpText; + hrc = pAdaptorComponent->GetHelpText(&pwszHelpText); + Assert(hrc == S_OK); + if (hrc == S_OK) + { + Log(("help-text=%ls\n", pwszHelpText)); + if (!wcscmp(pwszHelpText, L"VirtualBox NDIS 6.0 Miniport Driver")) + fNdis6 = true; + CoTaskMemFree(pwszHelpText); + } + if (fNdis6) + { + strncpy(szTrunkName, pszHostOnlyName, sizeof(szTrunkName) - 1); + Log(("trunk=%s\n", szTrunkName)); + } + else + { + char *pszTrunkName = szTrunkName; + wchar_t * pswzBindName; + hrc = pAdaptorComponent->GetBindName(&pswzBindName); + Assert(hrc == S_OK); + if (hrc == S_OK) + { + int cwBindName = (int)wcslen(pswzBindName) + 1; + int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX); + if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName) + { + strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX); + pszTrunkName += cbFullBindNamePrefix-1; + if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName, + sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL)) + { + DWORD err = GetLastError(); + hrc = HRESULT_FROM_WIN32(err); + AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n", + hrc, hrc, err)); + } + } + else + { + AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: insufficient szTrunkName buffer space\n")); + /** @todo set appropriate error code */ + hrc = E_FAIL; + } + + if (hrc != S_OK) + { + AssertFailed(); + CoTaskMemFree(pswzBindName); + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + H(); + } + } + else + { + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n", + hrc, hrc)); + H(); + } + + + CoTaskMemFree(pswzBindName); + } + + trunkType = TRUNKTYPE_NETADP; + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp); + + pAdaptorComponent.setNull(); + /* release the pNc finally */ + VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/); + + const char *pszTrunk = szTrunkName; + + InsertConfigString(pCfg, "Trunk", pszTrunk); + InsertConfigString(pCfg, "Network", szNetwork); + InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure); /** @todo why is this + windows only?? */ + networkName = Bstr(szNetwork); + trunkName = Bstr(pszTrunk); +# endif /* defined VBOX_WITH_NETFLT*/ +#elif defined(RT_OS_DARWIN) + InsertConfigString(pCfg, "Trunk", pszHostOnlyName); + InsertConfigString(pCfg, "Network", szNetwork); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp); + networkName = Bstr(szNetwork); + trunkName = Bstr(pszHostOnlyName); + trunkType = TRUNKTYPE_NETADP; +#else + InsertConfigString(pCfg, "Trunk", pszHostOnlyName); + InsertConfigString(pCfg, "Network", szNetwork); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt); + networkName = Bstr(szNetwork); + trunkName = Bstr(pszHostOnlyName); + trunkType = TRUNKTYPE_NETFLT; +#endif + InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy); + +#if !defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT) + + Bstr tmpAddr, tmpMask; + + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress", + pszHostOnlyName).raw(), + tmpAddr.asOutParam()); + if (SUCCEEDED(hrc) && !tmpAddr.isEmpty()) + { + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask", + pszHostOnlyName).raw(), + tmpMask.asOutParam()); + if (SUCCEEDED(hrc) && !tmpMask.isEmpty()) + hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(), + tmpMask.raw()); + else + hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(), + Bstr(VBOXNET_IPV4MASK_DEFAULT).raw()); + } + else + { + /* Grab the IP number from the 'vboxnetX' instance number (see netif.h) */ + hrc = hostInterface->EnableStaticIPConfig(getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw(), + Bstr(VBOXNET_IPV4MASK_DEFAULT).raw()); + } + + ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */ + + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6Address", + pszHostOnlyName).raw(), + tmpAddr.asOutParam()); + if (SUCCEEDED(hrc)) + hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6NetMask", pszHostOnlyName).raw(), + tmpMask.asOutParam()); + if (SUCCEEDED(hrc) && !tmpAddr.isEmpty() && !tmpMask.isEmpty()) + { + hrc = hostInterface->EnableStaticIPConfigV6(tmpAddr.raw(), + Utf8Str(tmpMask).toUInt32()); + ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */ + } +#endif + break; + } + + case NetworkAttachmentType_Generic: + { + hrc = aNetworkAdapter->COMGETTER(GenericDriver)(bstr.asOutParam()); H(); + SafeArray<BSTR> names; + SafeArray<BSTR> values; + hrc = aNetworkAdapter->GetProperties(Bstr().raw(), + ComSafeArrayAsOutParam(names), + ComSafeArrayAsOutParam(values)); H(); + + InsertConfigString(pLunL0, "Driver", bstr); + InsertConfigNode(pLunL0, "Config", &pCfg); + for (size_t ii = 0; ii < names.size(); ++ii) + { + if (values[ii] && *values[ii]) + { + Utf8Str name = names[ii]; + Utf8Str value = values[ii]; + InsertConfigString(pCfg, name.c_str(), value); + } + } + break; + } + + case NetworkAttachmentType_NATNetwork: + { + hrc = aNetworkAdapter->COMGETTER(NATNetwork)(bstr.asOutParam()); H(); + if (!bstr.isEmpty()) + { + /** @todo add intnet prefix to separate namespaces, and add trunk if dealing with vboxnatX */ + InsertConfigString(pLunL0, "Driver", "IntNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "Network", bstr); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone); + InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy); + networkName = bstr; + trunkType = Bstr(TRUNKTYPE_WHATEVER); + } + break; + } + +#ifdef VBOX_WITH_CLOUD_NET + case NetworkAttachmentType_Cloud: + { + static const char *s_pszCloudExtPackName = "Oracle VM VirtualBox Extension Pack"; + /* + * Cloud network attachments do not work wihout installed extpack. + * Without extpack support they won't work either. + */ +# ifdef VBOX_WITH_EXTPACK + if (!mptrExtPackManager->i_isExtPackUsable(s_pszCloudExtPackName)) +# endif + { + return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Implementation of the cloud network attachment not found!\n" + "To fix this problem, either install the '%s' or switch to " + "another network attachment type in the VM settings."), + s_pszCloudExtPackName); + } + + ComPtr<ICloudNetwork> network; + hrc = aNetworkAdapter->COMGETTER(CloudNetwork)(bstr.asOutParam()); H(); + hrc = pMachine->COMGETTER(Name)(mGateway.mTargetVM.asOutParam()); H(); + hrc = virtualBox->FindCloudNetworkByName(bstr.raw(), network.asOutParam()); H(); + hrc = generateKeys(mGateway); + if (FAILED(hrc)) + { + if (hrc == E_NOTIMPL) + return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Failed to generate a key pair due to missing libssh\n" + "To fix this problem, either build VirtualBox with libssh " + "support or switch to another network attachment type in " + "the VM settings.")); + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Failed to generate a key pair due to libssh error!")); + } + hrc = startCloudGateway(virtualBox, network, mGateway); + if (FAILED(hrc)) + { + if (hrc == VBOX_E_OBJECT_NOT_FOUND) + return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS, + N_("Failed to start cloud gateway instance.\nCould not find suitable " + "standard cloud images. Make sure you ran 'VBoxManage cloud network setup' " + "with correct '--gateway-os-name' and '--gateway-os-version' parameters. " + "Check VBoxSVC.log for actual values used to look up cloud images.")); + return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS, + N_("Failed to start cloud gateway instance.\nMake sure you set up " + "cloud networking properly with 'VBoxManage cloud network setup'. " + "Check VBoxSVC.log for details.")); + } + InsertConfigBytes(pDevCfg, "MAC", &mGateway.mCloudMacAddress, sizeof(mGateway.mCloudMacAddress)); + if (!bstr.isEmpty()) + { + InsertConfigString(pLunL0, "Driver", "CloudTunnel"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigPassword(pCfg, "SshKey", mGateway.mPrivateSshKey); + InsertConfigString(pCfg, "PrimaryIP", mGateway.mCloudPublicIp); + InsertConfigString(pCfg, "SecondaryIP", mGateway.mCloudSecondaryPublicIp); + InsertConfigBytes(pCfg, "TargetMAC", &mGateway.mLocalMacAddress, sizeof(mGateway.mLocalMacAddress)); + hrc = i_configProxy(virtualBox, pCfg, "Primary", mGateway.mCloudPublicIp); + if (FAILED(hrc)) + { + return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS, + N_("Failed to configure proxy for accessing cloud gateway instance via primary VNIC.\n" + "Check VirtualBox.log for details.")); + } + hrc = i_configProxy(virtualBox, pCfg, "Secondary", mGateway.mCloudSecondaryPublicIp); + if (FAILED(hrc)) + { + return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS, + N_("Failed to configure proxy for accessing cloud gateway instance via secondary VNIC.\n" + "Check VirtualBox.log for details.")); + } + networkName = bstr; + trunkType = Bstr(TRUNKTYPE_WHATEVER); + } + break; + } +#endif /* VBOX_WITH_CLOUD_NET */ + +#ifdef VBOX_WITH_VMNET + case NetworkAttachmentType_HostOnlyNetwork: + { + Bstr bstrId, bstrNetMask, bstrLowerIP, bstrUpperIP; + ComPtr<IHostOnlyNetwork> network; + hrc = aNetworkAdapter->COMGETTER(HostOnlyNetwork)(bstr.asOutParam()); H(); + hrc = virtualBox->FindHostOnlyNetworkByName(bstr.raw(), network.asOutParam()); + if (FAILED(hrc)) + { + LogRel(("NetworkAttachmentType_HostOnlyNetwork: FindByName failed, hrc (0x%x)\n", hrc)); + return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Nonexistent host-only network '%ls'"), bstr.raw()); + } + hrc = network->COMGETTER(Id)(bstrId.asOutParam()); H(); + hrc = network->COMGETTER(NetworkMask)(bstrNetMask.asOutParam()); H(); + hrc = network->COMGETTER(LowerIP)(bstrLowerIP.asOutParam()); H(); + hrc = network->COMGETTER(UpperIP)(bstrUpperIP.asOutParam()); H(); + if (!bstr.isEmpty()) + { + InsertConfigString(pLunL0, "Driver", "VMNet"); + InsertConfigNode(pLunL0, "Config", &pCfg); + // InsertConfigString(pCfg, "Trunk", Utf8Str(bstr).c_str()); + // InsertConfigString(pCfg, "Network", BstrFmt("HostOnlyNetworking-%ls", bstr.raw())); + InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp); + InsertConfigString(pCfg, "Id", Utf8Str(bstrId).c_str()); + InsertConfigString(pCfg, "NetworkMask", Utf8Str(bstrNetMask).c_str()); + InsertConfigString(pCfg, "LowerIP", Utf8Str(bstrLowerIP).c_str()); + InsertConfigString(pCfg, "UpperIP", Utf8Str(bstrUpperIP).c_str()); + // InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy); + networkName.setNull(); // We do not want DHCP server on our network! + // trunkType = Bstr(TRUNKTYPE_WHATEVER); + } + break; + } +#endif /* VBOX_WITH_VMNET */ + + default: + AssertMsgFailed(("should not get here!\n")); + break; + } + + /* + * Attempt to attach the driver. + */ + switch (eAttachmentType) + { + case NetworkAttachmentType_Null: + break; + + case NetworkAttachmentType_Bridged: + case NetworkAttachmentType_Internal: + case NetworkAttachmentType_HostOnly: +#ifdef VBOX_WITH_VMNET + case NetworkAttachmentType_HostOnlyNetwork: +#endif /* VBOX_WITH_VMNET */ + case NetworkAttachmentType_NAT: + case NetworkAttachmentType_Generic: + case NetworkAttachmentType_NATNetwork: +#ifdef VBOX_WITH_CLOUD_NET + case NetworkAttachmentType_Cloud: +#endif /* VBOX_WITH_CLOUD_NET */ + { + if (SUCCEEDED(hrc) && RT_SUCCESS(vrc)) + { + if (fAttachDetach) + { + vrc = pVMM->pfnPDMR3DriverAttach(mpUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/, NULL /* ppBase */); + //AssertRC(vrc); + } + + { + /** @todo pritesh: get the dhcp server name from the + * previous network configuration and then stop the server + * else it may conflict with the dhcp server running with + * the current attachment type + */ + /* Stop the hostonly DHCP Server */ + } + + /* + * NAT networks start their DHCP server theirself, see NATNetwork::Start() + */ + if ( !networkName.isEmpty() + && eAttachmentType != NetworkAttachmentType_NATNetwork) + { + /* + * Until we implement service reference counters DHCP Server will be stopped + * by DHCPServerRunner destructor. + */ + ComPtr<IDHCPServer> dhcpServer; + hrc = virtualBox->FindDHCPServerByNetworkName(networkName.raw(), dhcpServer.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* there is a DHCP server available for this network */ + BOOL fEnabledDhcp; + hrc = dhcpServer->COMGETTER(Enabled)(&fEnabledDhcp); + if (FAILED(hrc)) + { + LogRel(("DHCP svr: COMGETTER(Enabled) failed, hrc (%Rhrc)\n", hrc)); + H(); + } + + if (fEnabledDhcp) + hrc = dhcpServer->Start(trunkName.raw(), trunkType.raw()); + } + else + hrc = S_OK; + } + } + + break; + } + + default: + AssertMsgFailed(("should not get here!\n")); + break; + } + + meAttachmentType[uInstance] = eAttachmentType; + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + +#undef H + + return VINF_SUCCESS; +} + + +/** + * Configures the serial port at the given CFGM node with the supplied parameters. + * + * @returns VBox status code. + * @param pInst The instance CFGM node. + * @param ePortMode The port mode to sue. + * @param pszPath The serial port path. + * @param fServer Flag whether the port should act as a server + * for the pipe and TCP mode or connect as a client. + */ +int Console::i_configSerialPort(PCFGMNODE pInst, PortMode_T ePortMode, const char *pszPath, bool fServer) +{ + PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */ + PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */ + PCFGMNODE pLunL1Cfg = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/Config */ + + try + { + InsertConfigNode(pInst, "LUN#0", &pLunL0); + if (ePortMode == PortMode_HostPipe) + { + InsertConfigString(pLunL0, "Driver", "Char"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "NamedPipe"); + InsertConfigNode(pLunL1, "Config", &pLunL1Cfg); + InsertConfigString(pLunL1Cfg, "Location", pszPath); + InsertConfigInteger(pLunL1Cfg, "IsServer", fServer); + } + else if (ePortMode == PortMode_HostDevice) + { + InsertConfigString(pLunL0, "Driver", "Host Serial"); + InsertConfigNode(pLunL0, "Config", &pLunL1); + InsertConfigString(pLunL1, "DevicePath", pszPath); + } + else if (ePortMode == PortMode_TCP) + { + InsertConfigString(pLunL0, "Driver", "Char"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "TCP"); + InsertConfigNode(pLunL1, "Config", &pLunL1Cfg); + InsertConfigString(pLunL1Cfg, "Location", pszPath); + InsertConfigInteger(pLunL1Cfg, "IsServer", fServer); + } + else if (ePortMode == PortMode_RawFile) + { + InsertConfigString(pLunL0, "Driver", "Char"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1); + InsertConfigString(pLunL1, "Driver", "RawFile"); + InsertConfigNode(pLunL1, "Config", &pLunL1Cfg); + InsertConfigString(pLunL1Cfg, "Location", pszPath); + } + } + catch (ConfigError &x) + { + /* InsertConfig threw something */ + return x.m_vrc; + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp new file mode 100644 index 00000000..e8c6fc54 --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp @@ -0,0 +1,1483 @@ +/* $Id: ConsoleImplTeleporter.cpp $ */ +/** @file + * VBox Console COM Class implementation, The Teleporter Part. + */ + +/* + * Copyright (C) 2010-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE +#include "LoggingNew.h" + +#include "ConsoleImpl.h" +#include "ProgressImpl.h" +#include "Global.h" +#include "StringifyEnums.h" + +#include "AutoCaller.h" +#include "HashedPw.h" + +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/rand.h> +#include <iprt/socket.h> +#include <iprt/tcp.h> +#include <iprt/timer.h> + +#include <VBox/vmm/vmapi.h> +#include <VBox/vmm/ssm.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include <VBox/com/string.h> +#include "VBox/com/ErrorInfo.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Base class for the teleporter state. + * + * These classes are used as advanced structs, not as proper classes. + */ +class TeleporterState +{ +public: + ComPtr<Console> mptrConsole; + PUVM mpUVM; + PCVMMR3VTABLE mpVMM; + ComObjPtr<Progress> mptrProgress; + Utf8Str mstrPassword; + bool const mfIsSource; + + /** @name stream stuff + * @{ */ + RTSOCKET mhSocket; + uint64_t moffStream; + uint32_t mcbReadBlock; + bool volatile mfStopReading; + bool volatile mfEndOfStream; + bool volatile mfIOError; + /** @} */ + + TeleporterState(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, bool fIsSource) + : mptrConsole(pConsole) + , mpUVM(pUVM) + , mpVMM(pVMM) + , mptrProgress(pProgress) + , mfIsSource(fIsSource) + , mhSocket(NIL_RTSOCKET) + , moffStream(UINT64_MAX / 2) + , mcbReadBlock(0) + , mfStopReading(false) + , mfEndOfStream(false) + , mfIOError(false) + { + pVMM->pfnVMR3RetainUVM(mpUVM); + } + + ~TeleporterState() + { + if (mpVMM) + mpVMM->pfnVMR3ReleaseUVM(mpUVM); + mpUVM = NULL; + } +}; + + +/** + * Teleporter state used by the source side. + */ +class TeleporterStateSrc : public TeleporterState +{ +public: + Utf8Str mstrHostname; + uint32_t muPort; + uint32_t mcMsMaxDowntime; + MachineState_T menmOldMachineState; + bool mfSuspendedByUs; + bool mfUnlockedMedia; + + TeleporterStateSrc(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, MachineState_T enmOldMachineState) + : TeleporterState(pConsole, pUVM, pVMM, pProgress, true /*fIsSource*/) + , muPort(UINT32_MAX) + , mcMsMaxDowntime(250) + , menmOldMachineState(enmOldMachineState) + , mfSuspendedByUs(false) + , mfUnlockedMedia(false) + { + } +}; + + +/** + * Teleporter state used by the destination side. + */ +class TeleporterStateTrg : public TeleporterState +{ +public: + IMachine *mpMachine; + IInternalMachineControl *mpControl; + PRTTCPSERVER mhServer; + PRTTIMERLR mphTimerLR; + bool mfLockedMedia; + int mRc; + Utf8Str mErrorText; + + TeleporterStateTrg(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, + IMachine *pMachine, IInternalMachineControl *pControl, + PRTTIMERLR phTimerLR, bool fStartPaused) + : TeleporterState(pConsole, pUVM, pVMM, pProgress, false /*fIsSource*/) + , mpMachine(pMachine) + , mpControl(pControl) + , mhServer(NULL) + , mphTimerLR(phTimerLR) + , mfLockedMedia(false) + , mRc(VINF_SUCCESS) + , mErrorText() + { + RT_NOREF(fStartPaused); /** @todo figure out why fStartPaused isn't used */ + } +}; + + +/** + * TCP stream header. + * + * This is an extra layer for fixing the problem with figuring out when the SSM + * stream ends. + */ +typedef struct TELEPORTERTCPHDR +{ + /** Magic value. */ + uint32_t u32Magic; + /** The size of the data block following this header. + * 0 indicates the end of the stream, while UINT32_MAX indicates + * cancelation. */ + uint32_t cb; +} TELEPORTERTCPHDR; +/** Magic value for TELEPORTERTCPHDR::u32Magic. (Egberto Gismonti Amin) */ +#define TELEPORTERTCPHDR_MAGIC UINT32_C(0x19471205) +/** The max block size. */ +#define TELEPORTERTCPHDR_MAX_SIZE UINT32_C(0x00fffff8) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char g_szWelcome[] = "VirtualBox-Teleporter-1.0\n"; + + +/** + * Reads a string from the socket. + * + * @returns VBox status code. + * + * @param pState The teleporter state structure. + * @param pszBuf The output buffer. + * @param cchBuf The size of the output buffer. + * + */ +static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf) +{ + char *pszStart = pszBuf; + RTSOCKET hSocket = pState->mhSocket; + + AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR); + *pszBuf = '\0'; + + /* dead simple approach. */ + for (;;) + { + char ch; + int vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL); + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", vrc, pszStart)); + return vrc; + } + if ( ch == '\n' + || ch == '\0') + return VINF_SUCCESS; + if (cchBuf <= 1) + { + LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart)); + return VERR_BUFFER_OVERFLOW; + } + *pszBuf++ = ch; + *pszBuf = '\0'; + cchBuf--; + } +} + + +/** + * Reads an ACK or NACK. + * + * @returns S_OK on ACK, E_FAIL+setError() on failure or NACK. + * @param pState The teleporter source state. + * @param pszWhich Which ACK is this this? + * @param pszNAckMsg Optional NACK message. + * + * @remarks the setError laziness forces this to be a Console member. + */ +HRESULT +Console::i_teleporterSrcReadACK(TeleporterStateSrc *pState, const char *pszWhich, const char *pszNAckMsg /*= NULL*/) +{ + char szMsg[256]; + int vrc = teleporterTcpReadLine(pState, szMsg, sizeof(szMsg)); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed reading ACK(%s): %Rrc"), pszWhich, vrc); + + if (!strcmp(szMsg, "ACK")) + return S_OK; + + if (!strncmp(szMsg, RT_STR_TUPLE("NACK="))) + { + char *pszMsgText = strchr(szMsg, ';'); + if (pszMsgText) + *pszMsgText++ = '\0'; + + int32_t vrc2; + vrc = RTStrToInt32Full(&szMsg[sizeof("NACK=") - 1], 10, &vrc2); + if (vrc == VINF_SUCCESS) + { + /* + * Well formed NACK, transform it into an error. + */ + if (pszNAckMsg) + { + LogRel(("Teleporter: %s: NACK=%Rrc (%d)\n", pszWhich, vrc2, vrc2)); + return setError(E_FAIL, pszNAckMsg); + } + + if (pszMsgText) + { + pszMsgText = RTStrStrip(pszMsgText); + for (size_t off = 0; pszMsgText[off]; off++) + if (pszMsgText[off] == '\r') + pszMsgText[off] = '\n'; + + LogRel(("Teleporter: %s: NACK=%Rrc (%d) - '%s'\n", pszWhich, vrc2, vrc2, pszMsgText)); + if (strlen(pszMsgText) > 4) + return setError(E_FAIL, "%s", pszMsgText); + return setError(E_FAIL, "NACK(%s) - %Rrc (%d) '%s'", pszWhich, vrc2, vrc2, pszMsgText); + } + + return setError(E_FAIL, "NACK(%s) - %Rrc (%d)", pszWhich, vrc2, vrc2); + } + + if (pszMsgText) + pszMsgText[-1] = ';'; + } + return setError(E_FAIL, tr("%s: Expected ACK or NACK, got '%s'"), pszWhich, szMsg); +} + + +/** + * Submitts a command to the destination and waits for the ACK. + * + * @returns S_OK on ACKed command, E_FAIL+setError() on failure. + * + * @param pState The teleporter source state. + * @param pszCommand The command. + * @param fWaitForAck Whether to wait for the ACK. + * + * @remarks the setError laziness forces this to be a Console member. + */ +HRESULT Console::i_teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck /*= true*/) +{ + int vrc = RTTcpSgWriteL(pState->mhSocket, 2, pszCommand, strlen(pszCommand), "\n", sizeof("\n") - 1); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed writing command '%s': %Rrc"), pszCommand, vrc); + if (!fWaitForAck) + return S_OK; + return i_teleporterSrcReadACK(pState, pszCommand); +} + + +/** + * @copydoc SSMSTRMOPS::pfnWrite + */ +static DECLCALLBACK(int) teleporterTcpOpWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite) +{ + RT_NOREF(offStream); + TeleporterState *pState = (TeleporterState *)pvUser; + + AssertReturn(cbToWrite > 0, VINF_SUCCESS); + AssertReturn(cbToWrite < UINT32_MAX, VERR_OUT_OF_RANGE); + AssertReturn(pState->mfIsSource, VERR_INVALID_HANDLE); + + for (;;) + { + TELEPORTERTCPHDR Hdr; + Hdr.u32Magic = TELEPORTERTCPHDR_MAGIC; + Hdr.cb = RT_MIN((uint32_t)cbToWrite, TELEPORTERTCPHDR_MAX_SIZE); + int vrc = RTTcpSgWriteL(pState->mhSocket, 2, &Hdr, sizeof(Hdr), pvBuf, (size_t)Hdr.cb); + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter/TCP: Write error: %Rrc (cb=%#x)\n", vrc, Hdr.cb)); + return vrc; + } + pState->moffStream += Hdr.cb; + if (Hdr.cb == cbToWrite) + return VINF_SUCCESS; + + /* advance */ + cbToWrite -= Hdr.cb; + pvBuf = (uint8_t const *)pvBuf + Hdr.cb; + } +} + + +/** + * Selects and poll for close condition. + * + * We can use a relatively high poll timeout here since it's only used to get + * us out of error paths. In the normal cause of events, we'll get a + * end-of-stream header. + * + * @returns VBox status code. + * + * @param pState The teleporter state data. + */ +static int teleporterTcpReadSelect(TeleporterState *pState) +{ + int vrc; + do + { + vrc = RTTcpSelectOne(pState->mhSocket, 1000); + if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Header select error: %Rrc\n", vrc)); + break; + } + if (pState->mfStopReading) + { + vrc = VERR_EOF; + break; + } + } while (vrc == VERR_TIMEOUT); + return vrc; +} + + +/** + * @copydoc SSMSTRMOPS::pfnRead + */ +static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + RT_NOREF(offStream); + TeleporterState *pState = (TeleporterState *)pvUser; + AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE); + + for (;;) + { + int vrc; + + /* + * Check for various conditions and may have been signalled. + */ + if (pState->mfEndOfStream) + return VERR_EOF; + if (pState->mfStopReading) + return VERR_EOF; + if (pState->mfIOError) + return VERR_IO_GEN_FAILURE; + + /* + * If there is no more data in the current block, read the next + * block header. + */ + if (!pState->mcbReadBlock) + { + vrc = teleporterTcpReadSelect(pState); + if (RT_FAILURE(vrc)) + return vrc; + TELEPORTERTCPHDR Hdr; + vrc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL); + if (RT_FAILURE(vrc)) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Header read error: %Rrc\n", vrc)); + return vrc; + } + + if (RT_UNLIKELY( Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC + || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE + || Hdr.cb == 0)) + { + if ( Hdr.u32Magic == TELEPORTERTCPHDR_MAGIC + && ( Hdr.cb == 0 + || Hdr.cb == UINT32_MAX) + ) + { + pState->mfEndOfStream = true; + pState->mcbReadBlock = 0; + return Hdr.cb ? VERR_SSM_CANCELLED : VERR_EOF; + } + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb)); + return VERR_IO_GEN_FAILURE; + } + + pState->mcbReadBlock = Hdr.cb; + if (pState->mfStopReading) + return VERR_EOF; + } + + /* + * Read more data. + */ + vrc = teleporterTcpReadSelect(pState); + if (RT_FAILURE(vrc)) + return vrc; + uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead); + vrc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead); + if (RT_FAILURE(vrc)) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", vrc, cb)); + return vrc; + } + if (pcbRead) + { + cb = (uint32_t)*pcbRead; + pState->moffStream += cb; + pState->mcbReadBlock -= cb; + return VINF_SUCCESS; + } + pState->moffStream += cb; + pState->mcbReadBlock -= cb; + if (cbToRead == cb) + return VINF_SUCCESS; + + /* Advance to the next block. */ + cbToRead -= cb; + pvBuf = (uint8_t *)pvBuf + cb; + } +} + + +/** + * @copydoc SSMSTRMOPS::pfnSeek + */ +static DECLCALLBACK(int) teleporterTcpOpSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual) +{ + RT_NOREF(pvUser, offSeek, uMethod, poffActual); + return VERR_NOT_SUPPORTED; +} + + +/** + * @copydoc SSMSTRMOPS::pfnTell + */ +static DECLCALLBACK(uint64_t) teleporterTcpOpTell(void *pvUser) +{ + TeleporterState *pState = (TeleporterState *)pvUser; + return pState->moffStream; +} + + +/** + * @copydoc SSMSTRMOPS::pfnSize + */ +static DECLCALLBACK(int) teleporterTcpOpSize(void *pvUser, uint64_t *pcb) +{ + RT_NOREF(pvUser, pcb); + return VERR_NOT_SUPPORTED; +} + + +/** + * @copydoc SSMSTRMOPS::pfnIsOk + */ +static DECLCALLBACK(int) teleporterTcpOpIsOk(void *pvUser) +{ + TeleporterState *pState = (TeleporterState *)pvUser; + + if (pState->mfIsSource) + { + /* Poll for incoming NACKs and errors from the other side */ + int vrc = RTTcpSelectOne(pState->mhSocket, 0); + if (vrc != VERR_TIMEOUT) + { + if (RT_SUCCESS(vrc)) + { + LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n")); + vrc = VERR_SSM_CANCELLED; + } + else + LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", vrc)); + return vrc; + } + } + + return VINF_SUCCESS; +} + + +/** + * @copydoc SSMSTRMOPS::pfnClose + */ +static DECLCALLBACK(int) teleporterTcpOpClose(void *pvUser, bool fCancelled) +{ + TeleporterState *pState = (TeleporterState *)pvUser; + + if (pState->mfIsSource) + { + TELEPORTERTCPHDR EofHdr; + EofHdr.u32Magic = TELEPORTERTCPHDR_MAGIC; + EofHdr.cb = fCancelled ? UINT32_MAX : 0; + int vrc = RTTcpWrite(pState->mhSocket, &EofHdr, sizeof(EofHdr)); + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter/TCP: EOF Header write error: %Rrc\n", vrc)); + return vrc; + } + } + else + { + ASMAtomicWriteBool(&pState->mfStopReading, true); + } + + return VINF_SUCCESS; +} + + +/** + * Method table for a TCP based stream. + */ +static SSMSTRMOPS const g_teleporterTcpOps = +{ + SSMSTRMOPS_VERSION, + teleporterTcpOpWrite, + teleporterTcpOpRead, + teleporterTcpOpSeek, + teleporterTcpOpTell, + teleporterTcpOpSize, + teleporterTcpOpIsOk, + teleporterTcpOpClose, + SSMSTRMOPS_VERSION +}; + + +/** + * Progress cancelation callback. + */ +static void teleporterProgressCancelCallback(void *pvUser) +{ + TeleporterState *pState = (TeleporterState *)pvUser; + pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM); + if (!pState->mfIsSource) + { + TeleporterStateTrg *pStateTrg = (TeleporterStateTrg *)pState; + RTTcpServerShutdown(pStateTrg->mhServer); + } +} + +/** + * @copydoc PFNVMPROGRESS + */ +static DECLCALLBACK(int) teleporterProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser) +{ + TeleporterState *pState = (TeleporterState *)pvUser; + if (pState->mptrProgress) + { + HRESULT hrc = pState->mptrProgress->SetCurrentOperationProgress(uPercent); + if (FAILED(hrc)) + { + /* check if the failure was caused by cancellation. */ + BOOL fCanceled; + hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled); + if (SUCCEEDED(hrc) && fCanceled) + { + pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM); + return VERR_SSM_CANCELLED; + } + } + } + + NOREF(pUVM); + return VINF_SUCCESS; +} + + +/** + * @copydoc FNRTTIMERLR + */ +static DECLCALLBACK(void) teleporterDstTimeout(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick) +{ + RT_NOREF(hTimerLR, iTick); + /* This is harmless for any open connections. */ + RTTcpServerShutdown((PRTTCPSERVER)pvUser); +} + + +/** + * Do the teleporter. + * + * @returns VBox status code. + * @param pState The teleporter state. + */ +HRESULT Console::i_teleporterSrc(TeleporterStateSrc *pState) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* + * Wait for Console::Teleport to change the state. + */ + { AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); } + + BOOL fCanceled = TRUE; + HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled); + if (FAILED(hrc)) + return hrc; + if (fCanceled) + return setError(E_FAIL, tr("canceled")); + + /* + * Try connect to the destination machine, disable Nagle. + * (Note. The caller cleans up mhSocket, so we can return without worries.) + */ + int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed to connect to port %u on '%s': %Rrc"), + pState->muPort, pState->mstrHostname.c_str(), vrc); + vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/); + AssertRC(vrc); + + /* Read and check the welcome message. */ + char szLine[RT_MAX(128, sizeof(g_szWelcome))]; + RT_ZERO(szLine); + vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed to read welcome message: %Rrc"), vrc); + if (strcmp(szLine, g_szWelcome)) + return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine); + + /* password */ + pState->mstrPassword.append('\n'); + vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length()); + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("Failed to send password: %Rrc"), vrc); + + /* ACK */ + hrc = i_teleporterSrcReadACK(pState, "password", tr("Invalid password")); + if (FAILED(hrc)) + return hrc; + + /* + * Start loading the state. + * + * Note! The saved state includes vital configuration data which will be + * verified against the VM config on the other end. This is all done + * in the first pass, so we should fail pretty promptly on misconfig. + */ + hrc = i_teleporterSrcSubmitCommand(pState, "load"); + if (FAILED(hrc)) + return hrc; + + RTSocketRetain(pState->mhSocket); + void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState)); + vrc = pState->mpVMM->pfnVMR3Teleport(pState->mpUVM, + pState->mcMsMaxDowntime, + &g_teleporterTcpOps, pvUser, + teleporterProgressCallback, pvUser, + &pState->mfSuspendedByUs); + RTSocketRelease(pState->mhSocket); + if (RT_FAILURE(vrc)) + { + if ( vrc == VERR_SSM_CANCELLED + && RT_SUCCESS(RTTcpSelectOne(pState->mhSocket, 1))) + { + hrc = i_teleporterSrcReadACK(pState, "load-complete"); + if (FAILED(hrc)) + return hrc; + } + return setErrorBoth(E_FAIL, vrc, "VMR3Teleport -> %Rrc", vrc); + } + + hrc = i_teleporterSrcReadACK(pState, "load-complete"); + if (FAILED(hrc)) + return hrc; + + /* + * We're at the point of no return. + */ + if (FAILED(pState->mptrProgress->NotifyPointOfNoReturn())) + { + i_teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/); + return E_FAIL; + } + + /* + * Hand over any media which we might be sharing. + * + * Note! This is only important on localhost teleportations. + */ + /** @todo Maybe we should only do this if it's a local teleportation... */ + hrc = mControl->UnlockMedia(); + if (FAILED(hrc)) + return hrc; + pState->mfUnlockedMedia = true; + + hrc = i_teleporterSrcSubmitCommand(pState, "lock-media"); + if (FAILED(hrc)) + return hrc; + + /* + * The FINAL step is giving the target instructions how to proceed with the VM. + */ + if ( vrc == VINF_SSM_LIVE_SUSPENDED + || pState->menmOldMachineState == MachineState_Paused) + hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-paused"); + else + hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-resume"); + if (FAILED(hrc)) + return hrc; + + /* + * teleporterSrcThreadWrapper will do the automatic power off because it + * has to release the AutoVMCaller. + */ + return S_OK; +} + + +/** + * Static thread method wrapper. + * + * @returns VINF_SUCCESS (ignored). + * @param hThreadSelf The thread. + * @param pvUser Pointer to a TeleporterStateSrc instance. + */ +/*static*/ DECLCALLBACK(int) +Console::i_teleporterSrcThreadWrapper(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + TeleporterStateSrc *pState = (TeleporterStateSrc *)pvUser; + + /* + * Console::teleporterSrc does the work, we just grab onto the VM handle + * and do the cleanups afterwards. + */ + SafeVMPtr ptrVM(pState->mptrConsole); + HRESULT hrc = ptrVM.rc(); + + if (SUCCEEDED(hrc)) + hrc = pState->mptrConsole->i_teleporterSrc(pState); + + /* Close the connection ASAP on so that the other side can complete. */ + if (pState->mhSocket != NIL_RTSOCKET) + { + RTTcpClientClose(pState->mhSocket); + pState->mhSocket = NIL_RTSOCKET; + } + + /* Aaarg! setMachineState trashes error info on Windows, so we have to + complete things here on failure instead of right before cleanup. */ + if (FAILED(hrc)) + pState->mptrProgress->i_notifyComplete(hrc); + + /* We can no longer be canceled (success), or it doesn't matter any longer (failure). */ + pState->mptrProgress->i_setCancelCallback(NULL, NULL); + + /* + * Write lock the console before resetting mptrCancelableProgress and + * fixing the state. + */ + AutoWriteLock autoLock(pState->mptrConsole COMMA_LOCKVAL_SRC_POS); + pState->mptrConsole->mptrCancelableProgress.setNull(); + + VMSTATE const enmVMState = pState->mpVMM->pfnVMR3GetStateU(pState->mpUVM); + MachineState_T const enmMachineState = pState->mptrConsole->mMachineState; + if (SUCCEEDED(hrc)) + { + /* + * Automatically shut down the VM on success. + * + * Note! We have to release the VM caller object or we'll deadlock in + * powerDown. + */ + AssertLogRelMsg(enmVMState == VMSTATE_SUSPENDED, ("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState))); + AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, ("%s\n", ::stringifyMachineState(enmMachineState))); + + ptrVM.release(); + + pState->mptrConsole->mVMIsAlreadyPoweringOff = true; /* (Make sure we stick in the TeleportingPausedVM state.) */ + autoLock.release(); + + hrc = pState->mptrConsole->i_powerDown(); + + autoLock.acquire(); + pState->mptrConsole->mVMIsAlreadyPoweringOff = false; + + pState->mptrProgress->i_notifyComplete(hrc); + } + else + { + /* + * Work the state machinery on failure. + * + * If the state is no longer 'Teleporting*', some other operation has + * canceled us and there is nothing we need to do here. In all other + * cases, we've failed one way or another. + */ + if ( enmMachineState == MachineState_Teleporting + || enmMachineState == MachineState_TeleportingPausedVM + ) + { + if (pState->mfUnlockedMedia) + { + ErrorInfoKeeper Oak; + HRESULT hrc2 = pState->mptrConsole->mControl->LockMedia(); + if (FAILED(hrc2)) + { + uint64_t StartMS = RTTimeMilliTS(); + do + { + RTThreadSleep(2); + hrc2 = pState->mptrConsole->mControl->LockMedia(); + } while ( FAILED(hrc2) + && RTTimeMilliTS() - StartMS < 2000); + } + if (SUCCEEDED(hrc2)) + pState->mfUnlockedMedia = true; + else + LogRel(("FATAL ERROR: Failed to re-take the media locks. hrc2=%Rhrc\n", hrc2)); + } + + switch (enmVMState) + { + case VMSTATE_RUNNING: + case VMSTATE_RUNNING_LS: + case VMSTATE_DEBUGGING: + case VMSTATE_DEBUGGING_LS: + case VMSTATE_POWERING_OFF: + case VMSTATE_POWERING_OFF_LS: + case VMSTATE_RESETTING: + case VMSTATE_RESETTING_LS: + case VMSTATE_SOFT_RESETTING: + case VMSTATE_SOFT_RESETTING_LS: + Assert(!pState->mfSuspendedByUs); + Assert(!pState->mfUnlockedMedia); + pState->mptrConsole->i_setMachineState(MachineState_Running); + break; + + case VMSTATE_GURU_MEDITATION: + case VMSTATE_GURU_MEDITATION_LS: + pState->mptrConsole->i_setMachineState(MachineState_Stuck); + break; + + case VMSTATE_FATAL_ERROR: + case VMSTATE_FATAL_ERROR_LS: + pState->mptrConsole->i_setMachineState(MachineState_Paused); + break; + + default: + AssertMsgFailed(("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState))); + RT_FALL_THRU(); + case VMSTATE_SUSPENDED: + case VMSTATE_SUSPENDED_LS: + case VMSTATE_SUSPENDING: + case VMSTATE_SUSPENDING_LS: + case VMSTATE_SUSPENDING_EXT_LS: + if (!pState->mfUnlockedMedia) + { + pState->mptrConsole->i_setMachineState(MachineState_Paused); + if (pState->mfSuspendedByUs) + { + autoLock.release(); + int vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORT_FAILED); + AssertLogRelMsgRC(vrc, ("VMR3Resume -> %Rrc\n", vrc)); + autoLock.acquire(); + } + } + else + { + /* Faking a guru meditation is the best I can think of doing here... */ + pState->mptrConsole->i_setMachineState(MachineState_Stuck); + } + break; + } + } + } + autoLock.release(); + + /* + * Cleanup. + */ + Assert(pState->mhSocket == NIL_RTSOCKET); + delete pState; + + return VINF_SUCCESS; /* ignored */ +} + + +/** + * Start teleporter to the specified target. + * + * @returns COM status code. + * + * @param aHostname The name of the target host. + * @param aTcpport The TCP port number. + * @param aPassword The password. + * @param aMaxDowntime Max allowed "downtime" in milliseconds. + * @param aProgress Where to return the progress object. + */ +HRESULT Console::teleport(const com::Utf8Str &aHostname, ULONG aTcpport, const com::Utf8Str &aPassword, + ULONG aMaxDowntime, ComPtr<IProgress> &aProgress) +{ + /* + * Validate parameters, check+hold object status, write lock the object + * and validate the state. + */ + Utf8Str strPassword(aPassword); + if (!strPassword.isEmpty()) + { + if (VBoxIsPasswordHashed(&strPassword)) + return setError(E_INVALIDARG, tr("The specified password resembles a hashed password, expected plain text")); + VBoxHashPassword(&strPassword); + } + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + + switch (mMachineState) + { + case MachineState_Running: + case MachineState_Paused: + break; + + default: + return setError(VBOX_E_INVALID_VM_STATE, tr("Invalid machine state: %s (must be Running or Paused)"), + Global::stringifyMachineState(mMachineState)); + } + + + /* + * Create a progress object, spawn a worker thread and change the state. + * Note! The thread won't start working until we release the lock. + */ + LogFlowThisFunc(("Initiating TELEPORT request...\n")); + + ComObjPtr<Progress> ptrProgress; + HRESULT hrc = ptrProgress.createObject(); + if (SUCCEEDED(hrc)) + hrc = ptrProgress->init(static_cast<IConsole *>(this), + Bstr(tr("Teleporter")).raw(), + TRUE /*aCancelable*/); + if (FAILED(hrc)) + return hrc; + + TeleporterStateSrc *pState = new TeleporterStateSrc(this, mpUVM, mpVMM, ptrProgress, mMachineState); + pState->mstrPassword = strPassword; + pState->mstrHostname = aHostname; + pState->muPort = aTcpport; + pState->mcMsMaxDowntime = aMaxDowntime; + + void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState)); + ptrProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser); + + int vrc = RTThreadCreate(NULL, Console::i_teleporterSrcThreadWrapper, (void *)pState, 0 /*cbStack*/, + RTTHREADTYPE_EMULATION, 0 /*fFlags*/, "Teleport"); + if (RT_SUCCESS(vrc)) + { + if (mMachineState == MachineState_Running) + hrc = i_setMachineState(MachineState_Teleporting); + else + hrc = i_setMachineState(MachineState_TeleportingPausedVM); + if (SUCCEEDED(hrc)) + { + ptrProgress.queryInterfaceTo(aProgress.asOutParam()); + mptrCancelableProgress = aProgress; + } + else + ptrProgress->Cancel(); + } + else + { + ptrProgress->i_setCancelCallback(NULL, NULL); + delete pState; + hrc = setErrorBoth(E_FAIL, vrc, "RTThreadCreate -> %Rrc", vrc); + } + + return hrc; +} + + +/** + * Creates a TCP server that listens for the source machine and passes control + * over to Console::teleporterTrgServeConnection(). + * + * @returns VBox status code. + * @param pUVM The user-mode VM handle + * @param pVMM The VMM vtable. + * @param pMachine The IMachine for the virtual machine. + * @param pErrorMsg Pointer to the error string for VMSetError. + * @param fStartPaused Whether to start it in the Paused (true) or + * Running (false) state, + * @param pProgress Pointer to the progress object. + * @param pfPowerOffOnFailure Whether the caller should power off + * the VM on failure. + * + * @remarks The caller expects error information to be set on failure. + * @todo Check that all the possible failure paths sets error info... + */ +HRESULT Console::i_teleporterTrg(PUVM pUVM, PCVMMR3VTABLE pVMM, IMachine *pMachine, Utf8Str *pErrorMsg, bool fStartPaused, + Progress *pProgress, bool *pfPowerOffOnFailure) +{ + LogThisFunc(("pUVM=%p pVMM=%p pMachine=%p fStartPaused=%RTbool pProgress=%p\n", pUVM, pVMM, pMachine, fStartPaused, pProgress)); + + *pfPowerOffOnFailure = true; + + /* + * Get the config. + */ + ULONG uPort; + HRESULT hrc = pMachine->COMGETTER(TeleporterPort)(&uPort); + if (FAILED(hrc)) + return hrc; + ULONG const uPortOrg = uPort; + + Bstr bstrAddress; + hrc = pMachine->COMGETTER(TeleporterAddress)(bstrAddress.asOutParam()); + if (FAILED(hrc)) + return hrc; + Utf8Str strAddress(bstrAddress); + const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str(); + + Bstr bstrPassword; + hrc = pMachine->COMGETTER(TeleporterPassword)(bstrPassword.asOutParam()); + if (FAILED(hrc)) + return hrc; + Utf8Str strPassword(bstrPassword); + strPassword.append('\n'); /* To simplify password checking. */ + + /* + * Create the TCP server. + */ + int vrc = VINF_SUCCESS; /* Shut up MSC */ + PRTTCPSERVER hServer = NULL; /* ditto */ + if (uPort) + vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer); + else + { + for (int cTries = 10240; cTries > 0; cTries--) + { + uPort = RTRandU32Ex(cTries >= 8192 ? 49152 : 1024, 65534); + vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer); + if (vrc != VERR_NET_ADDRESS_IN_USE) + break; + } + if (RT_SUCCESS(vrc)) + { + hrc = pMachine->COMSETTER(TeleporterPort)(uPort); + if (FAILED(hrc)) + { + RTTcpServerDestroy(hServer); + return hrc; + } + } + } + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("RTTcpServerCreateEx failed with status %Rrc"), vrc); + + /* + * Create a one-shot timer for timing out after 5 mins. + */ + RTTIMERLR hTimerLR; + vrc = RTTimerLRCreateEx(&hTimerLR, 0 /*ns*/, RTTIMER_FLAGS_CPU_ANY, teleporterDstTimeout, hServer); + if (RT_SUCCESS(vrc)) + { + vrc = RTTimerLRStart(hTimerLR, 5*60*UINT64_C(1000000000) /*ns*/); + if (RT_SUCCESS(vrc)) + { + /* + * Do the job, when it returns we're done. + */ + TeleporterStateTrg theState(this, pUVM, pVMM, pProgress, pMachine, mControl, &hTimerLR, fStartPaused); + theState.mstrPassword = strPassword; + theState.mhServer = hServer; + + void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(&theState)); + if (pProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser)) + { + LogRel(("Teleporter: Waiting for incoming VM...\n")); + hrc = pProgress->SetNextOperation(Bstr(tr("Waiting for incoming VM")).raw(), 1); + if (SUCCEEDED(hrc)) + { + vrc = RTTcpServerListen(hServer, Console::i_teleporterTrgServeConnection, &theState); + pProgress->i_setCancelCallback(NULL, NULL); + + if (vrc == VERR_TCP_SERVER_STOP) + { + vrc = theState.mRc; + /* Power off the VM on failure unless the state callback + already did that. */ + *pfPowerOffOnFailure = false; + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + { + VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM); + if ( enmVMState != VMSTATE_OFF + && enmVMState != VMSTATE_POWERING_OFF) + *pfPowerOffOnFailure = true; + + /* Set error. */ + if (pErrorMsg->length()) + hrc = setError(E_FAIL, "%s", pErrorMsg->c_str()); + else + hrc = setError(E_FAIL, tr("Teleporation failed (%Rrc)"), vrc); + } + } + else if (vrc == VERR_TCP_SERVER_SHUTDOWN) + { + BOOL fCanceled = TRUE; + hrc = pProgress->COMGETTER(Canceled)(&fCanceled); + if (FAILED(hrc) || fCanceled) + hrc = setError(E_FAIL, tr("Teleporting canceled")); + else + hrc = setError(E_FAIL, tr("Teleporter timed out waiting for incoming connection")); + LogRel(("Teleporter: RTTcpServerListen aborted - %Rrc\n", vrc)); + } + else + { + hrc = setErrorBoth(E_FAIL, vrc, tr("Unexpected RTTcpServerListen status code %Rrc"), vrc); + LogRel(("Teleporter: Unexpected RTTcpServerListen rc: %Rrc\n", vrc)); + } + } + else + LogThisFunc(("SetNextOperation failed, %Rhrc\n", hrc)); + } + else + { + LogThisFunc(("Canceled - check point #1\n")); + hrc = setError(E_FAIL, tr("Teleporting canceled")); + } + } + else + hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRStart -> %Rrc", vrc); + + RTTimerLRDestroy(hTimerLR); + } + else + hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRCreate -> %Rrc", vrc); + RTTcpServerDestroy(hServer); + + /* + * If we change TeleporterPort above, set it back to it's original + * value before returning. + */ + if (uPortOrg != uPort) + { + ErrorInfoKeeper Eik; + pMachine->COMSETTER(TeleporterPort)(uPortOrg); + } + + return hrc; +} + + +/** + * Unlock the media. + * + * This is used in error paths. + * + * @param pState The teleporter state. + */ +static void teleporterTrgUnlockMedia(TeleporterStateTrg *pState) +{ + if (pState->mfLockedMedia) + { + pState->mpControl->UnlockMedia(); + pState->mfLockedMedia = false; + } +} + + +static int teleporterTcpWriteACK(TeleporterStateTrg *pState, bool fAutomaticUnlock = true) +{ + int vrc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1); + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", vrc)); + if (fAutomaticUnlock) + teleporterTrgUnlockMedia(pState); + } + return vrc; +} + + +static int teleporterTcpWriteNACK(TeleporterStateTrg *pState, int32_t rc2, const char *pszMsgText = NULL) +{ + /* + * Unlock media sending the NACK. That way the other doesn't have to spin + * waiting to regain the locks. + */ + teleporterTrgUnlockMedia(pState); + + char szMsg[256]; + size_t cch; + if (pszMsgText && *pszMsgText) + { + cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d;%s\n", rc2, pszMsgText); + for (size_t off = 6; off + 1 < cch; off++) + if (szMsg[off] == '\n') + szMsg[off] = '\r'; + } + else + cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d\n", rc2); + int vrc = RTTcpWrite(pState->mhSocket, szMsg, cch); + if (RT_FAILURE(vrc)) + LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, vrc)); + return vrc; +} + + +/** + * @copydoc FNRTTCPSERVE + * + * @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP. + */ +/*static*/ DECLCALLBACK(int) +Console::i_teleporterTrgServeConnection(RTSOCKET hSocket, void *pvUser) +{ + TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser; + pState->mhSocket = hSocket; + + /* + * Disable Nagle and say hello. + */ + int vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/); + AssertRC(vrc); + vrc = RTTcpWrite(hSocket, g_szWelcome, sizeof(g_szWelcome) - 1); + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc)); + return VINF_SUCCESS; + } + + /* + * Password (includes '\n', see i_teleporterTrg). + */ + const char *pszPassword = pState->mstrPassword.c_str(); + unsigned off = 0; + while (pszPassword[off]) + { + char ch; + vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL); + if ( RT_FAILURE(vrc) + || pszPassword[off] != ch) + { + if (RT_FAILURE(vrc)) + LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc)); + else + { + /* Must read the whole password before NACK'ing it. */ + size_t const cchMaxRead = RT_ALIGN_Z(pState->mstrPassword.length() * 3, _1K); + while (off < cchMaxRead && RT_SUCCESS(vrc) && ch != '\n') + { + vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL); + off++; + } + LogRel(("Teleporter: Invalid password\n")); + } + RTThreadSleep(RTRandU32Ex(64, 1024)); /* stagger retries */ + teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE); + return VINF_SUCCESS; + } + off++; + } + vrc = teleporterTcpWriteACK(pState); + if (RT_FAILURE(vrc)) + return VINF_SUCCESS; + + /* + * Update the progress bar, with peer name if available. + */ + HRESULT hrc; + RTNETADDR Addr; + vrc = RTTcpGetPeerAddress(hSocket, &Addr); + if (RT_SUCCESS(vrc)) + { + LogRel(("Teleporter: Incoming VM from %RTnaddr!\n", &Addr)); + hrc = pState->mptrProgress->SetNextOperation(BstrFmt(tr("Teleporting VM from %RTnaddr"), &Addr).raw(), 8); + } + else + { + LogRel(("Teleporter: Incoming VM!\n")); + hrc = pState->mptrProgress->SetNextOperation(Bstr(tr("Teleporting VM")).raw(), 8); + } + AssertMsg(SUCCEEDED(hrc) || hrc == E_FAIL, ("%Rhrc\n", hrc)); + + /* + * Stop the server and cancel the timeout timer. + * + * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior + * to it we must not return that value! + */ + RTTcpServerShutdown(pState->mhServer); + RTTimerLRDestroy(*pState->mphTimerLR); + *pState->mphTimerLR = NIL_RTTIMERLR; + + /* + * Command processing loop. + */ + bool fDone = false; + for (;;) + { + char szCmd[128]; + vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd)); + if (RT_FAILURE(vrc)) + break; + + if (!strcmp(szCmd, "load")) + { + vrc = teleporterTcpWriteACK(pState); + if (RT_FAILURE(vrc)) + break; + + int vrc2 = pState->mpVMM->pfnVMR3AtErrorRegister(pState->mpUVM, Console::i_genericVMSetErrorCallback, + &pState->mErrorText); + AssertRC(vrc2); + RTSocketRetain(pState->mhSocket); /* For concurrent access by I/O thread and EMT. */ + pState->moffStream = 0; + + void *pvUser2 = static_cast<void *>(static_cast<TeleporterState *>(pState)); + vrc = pState->mpVMM->pfnVMR3LoadFromStream(pState->mpUVM, + &g_teleporterTcpOps, pvUser2, + teleporterProgressCallback, pvUser2, + true /*fTeleporting*/); + + RTSocketRelease(pState->mhSocket); + vrc2 = pState->mpVMM->pfnVMR3AtErrorDeregister(pState->mpUVM, Console::i_genericVMSetErrorCallback, &pState->mErrorText); + AssertRC(vrc2); + + if (RT_FAILURE(vrc)) + { + LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc)); + teleporterTcpWriteNACK(pState, vrc, pState->mErrorText.c_str()); + break; + } + + /* The EOS might not have been read, make sure it is. */ + pState->mfStopReading = false; + size_t cbRead; + vrc = teleporterTcpOpRead(pvUser2, pState->moffStream, szCmd, 1, &cbRead); + if (vrc != VERR_EOF) + { + LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc)); + teleporterTcpWriteNACK(pState, vrc); + break; + } + + vrc = teleporterTcpWriteACK(pState); + } + else if (!strcmp(szCmd, "cancel")) + { + /* Don't ACK this. */ + LogRel(("Teleporter: Received cancel command.\n")); + vrc = VERR_SSM_CANCELLED; + } + else if (!strcmp(szCmd, "lock-media")) + { + hrc = pState->mpControl->LockMedia(); + if (SUCCEEDED(hrc)) + { + pState->mfLockedMedia = true; + vrc = teleporterTcpWriteACK(pState); + } + else + { + vrc = VERR_FILE_LOCK_FAILED; + teleporterTcpWriteNACK(pState, vrc); + } + } + else if ( !strcmp(szCmd, "hand-over-resume") + || !strcmp(szCmd, "hand-over-paused")) + { + /* + * Point of no return. + * + * Note! Since we cannot tell whether a VMR3Resume failure is + * destructive for the source or not, we have little choice + * but to ACK it first and take any failures locally. + * + * Ideally, we should try resume it first and then ACK (or + * NACK) the request since this would reduce latency and + * make it possible to recover from some VMR3Resume failures. + */ + if ( SUCCEEDED(pState->mptrProgress->NotifyPointOfNoReturn()) + && pState->mfLockedMedia) + { + vrc = teleporterTcpWriteACK(pState); + if (RT_SUCCESS(vrc)) + { + if (!strcmp(szCmd, "hand-over-resume")) + vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORTED); + else + pState->mptrConsole->i_setMachineState(MachineState_Paused); + fDone = true; + break; + } + } + else + { + vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED; + teleporterTcpWriteNACK(pState, vrc); + } + } + else + { + LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd)); + vrc = VERR_NOT_IMPLEMENTED; + teleporterTcpWriteNACK(pState, vrc); + } + + if (RT_FAILURE(vrc)) + break; + } + + if (RT_SUCCESS(vrc) && !fDone) + vrc = VERR_WRONG_ORDER; + if (RT_FAILURE(vrc)) + teleporterTrgUnlockMedia(pState); + + pState->mRc = vrc; + pState->mhSocket = NIL_RTSOCKET; + LogFlowFunc(("returns mRc=%Rrc\n", vrc)); + return VERR_TCP_SERVER_STOP; +} + diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp new file mode 100644 index 00000000..bee4968e --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp @@ -0,0 +1,4059 @@ +/* $Id: ConsoleVRDPServer.cpp $ */ +/** @file + * VBox Console VRDP helper class. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE +#include "LoggingNew.h" + +#include "ConsoleVRDPServer.h" +#include "ConsoleImpl.h" +#include "DisplayImpl.h" +#include "KeyboardImpl.h" +#include "MouseImpl.h" +#ifdef VBOX_WITH_AUDIO_VRDE +#include "DrvAudioVRDE.h" +#endif +#ifdef VBOX_WITH_EXTPACK +# include "ExtPackManagerImpl.h" +#endif +#include "VMMDev.h" +#ifdef VBOX_WITH_USB_CARDREADER +# include "UsbCardReader.h" +#endif +#include "UsbWebcamInterface.h" + +#include "Global.h" +#include "AutoCaller.h" + +#include <iprt/asm.h> +#include <iprt/alloca.h> +#include <iprt/ldr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/cpp/utils.h> + +#include <VBox/err.h> +#include <VBox/RemoteDesktop/VRDEOrders.h> +#include <VBox/com/listeners.h> + + +class VRDPConsoleListener +{ +public: + VRDPConsoleListener() + { + } + + virtual ~VRDPConsoleListener() + { + } + + HRESULT init(ConsoleVRDPServer *server) + { + m_server = server; + return S_OK; + } + + void uninit() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent) + { + switch (aType) + { + case VBoxEventType_OnMousePointerShapeChanged: + { + ComPtr<IMousePointerShapeChangedEvent> mpscev = aEvent; + Assert(mpscev); + BOOL visible, alpha; + ULONG xHot, yHot, width, height; + com::SafeArray <BYTE> shape; + + mpscev->COMGETTER(Visible)(&visible); + mpscev->COMGETTER(Alpha)(&alpha); + mpscev->COMGETTER(Xhot)(&xHot); + mpscev->COMGETTER(Yhot)(&yHot); + mpscev->COMGETTER(Width)(&width); + mpscev->COMGETTER(Height)(&height); + mpscev->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape)); + + m_server->onMousePointerShapeChange(visible, alpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape)); + break; + } + case VBoxEventType_OnMouseCapabilityChanged: + { + ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent; + Assert(mccev); + if (m_server) + { + BOOL fAbsoluteMouse; + mccev->COMGETTER(SupportsAbsolute)(&fAbsoluteMouse); + m_server->NotifyAbsoluteMouse(!!fAbsoluteMouse); + } + break; + } + case VBoxEventType_OnKeyboardLedsChanged: + { + ComPtr<IKeyboardLedsChangedEvent> klcev = aEvent; + Assert(klcev); + + if (m_server) + { + BOOL fNumLock, fCapsLock, fScrollLock; + klcev->COMGETTER(NumLock)(&fNumLock); + klcev->COMGETTER(CapsLock)(&fCapsLock); + klcev->COMGETTER(ScrollLock)(&fScrollLock); + m_server->NotifyKeyboardLedsChange(fNumLock, fCapsLock, fScrollLock); + } + break; + } + + default: + AssertFailed(); + } + + return S_OK; + } + +private: + ConsoleVRDPServer *m_server; +}; + +typedef ListenerImpl<VRDPConsoleListener, ConsoleVRDPServer*> VRDPConsoleListenerImpl; + +VBOX_LISTENER_DECLARE(VRDPConsoleListenerImpl) + +#ifdef DEBUG_sunlover +#define LOGDUMPPTR Log +void dumpPointer(const uint8_t *pu8Shape, uint32_t width, uint32_t height, bool fXorMaskRGB32) +{ + unsigned i; + + const uint8_t *pu8And = pu8Shape; + + for (i = 0; i < height; i++) + { + unsigned j; + LOGDUMPPTR(("%p: ", pu8And)); + for (j = 0; j < (width + 7) / 8; j++) + { + unsigned k; + for (k = 0; k < 8; k++) + { + LOGDUMPPTR(("%d", ((*pu8And) & (1 << (7 - k)))? 1: 0)); + } + + pu8And++; + } + LOGDUMPPTR(("\n")); + } + + if (fXorMaskRGB32) + { + uint32_t *pu32Xor = (uint32_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3)); + + for (i = 0; i < height; i++) + { + unsigned j; + LOGDUMPPTR(("%p: ", pu32Xor)); + for (j = 0; j < width; j++) + { + LOGDUMPPTR(("%08X", *pu32Xor++)); + } + LOGDUMPPTR(("\n")); + } + } + else + { + /* RDP 24 bit RGB mask. */ + uint8_t *pu8Xor = (uint8_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3)); + for (i = 0; i < height; i++) + { + unsigned j; + LOGDUMPPTR(("%p: ", pu8Xor)); + for (j = 0; j < width; j++) + { + LOGDUMPPTR(("%02X%02X%02X", pu8Xor[2], pu8Xor[1], pu8Xor[0])); + pu8Xor += 3; + } + LOGDUMPPTR(("\n")); + } + } +} +#else +#define dumpPointer(a, b, c, d) do {} while (0) +#endif /* DEBUG_sunlover */ + +static void findTopLeftBorder(const uint8_t *pu8AndMask, const uint8_t *pu8XorMask, uint32_t width, + uint32_t height, uint32_t *pxSkip, uint32_t *pySkip) +{ + /* + * Find the top border of the AND mask. First assign to special value. + */ + uint32_t ySkipAnd = UINT32_MAX; + + const uint8_t *pu8And = pu8AndMask; + const uint32_t cbAndRow = (width + 7) / 8; + const uint8_t maskLastByte = (uint8_t)( 0xFF << (cbAndRow * 8 - width) ); + + Assert(cbAndRow > 0); + + unsigned y; + unsigned x; + + for (y = 0; y < height && ySkipAnd == ~(uint32_t)0; y++, pu8And += cbAndRow) + { + /* For each complete byte in the row. */ + for (x = 0; x < cbAndRow - 1; x++) + { + if (pu8And[x] != 0xFF) + { + ySkipAnd = y; + break; + } + } + + if (ySkipAnd == ~(uint32_t)0) + { + /* Last byte. */ + if ((pu8And[cbAndRow - 1] & maskLastByte) != maskLastByte) + { + ySkipAnd = y; + } + } + } + + if (ySkipAnd == ~(uint32_t)0) + { + ySkipAnd = 0; + } + + /* + * Find the left border of the AND mask. + */ + uint32_t xSkipAnd = UINT32_MAX; + + /* For all bit columns. */ + for (x = 0; x < width && xSkipAnd == ~(uint32_t)0; x++) + { + pu8And = pu8AndMask + x/8; /* Currently checking byte. */ + uint8_t mask = 1 << (7 - x%8); /* Currently checking bit in the byte. */ + + for (y = ySkipAnd; y < height; y++, pu8And += cbAndRow) + { + if ((*pu8And & mask) == 0) + { + xSkipAnd = x; + break; + } + } + } + + if (xSkipAnd == ~(uint32_t)0) + { + xSkipAnd = 0; + } + + /* + * Find the XOR mask top border. + */ + uint32_t ySkipXor = UINT32_MAX; + + uint32_t *pu32XorStart = (uint32_t *)pu8XorMask; + + uint32_t *pu32Xor = pu32XorStart; + + for (y = 0; y < height && ySkipXor == ~(uint32_t)0; y++, pu32Xor += width) + { + for (x = 0; x < width; x++) + { + if (pu32Xor[x] != 0) + { + ySkipXor = y; + break; + } + } + } + + if (ySkipXor == ~(uint32_t)0) + { + ySkipXor = 0; + } + + /* + * Find the left border of the XOR mask. + */ + uint32_t xSkipXor = ~(uint32_t)0; + + /* For all columns. */ + for (x = 0; x < width && xSkipXor == ~(uint32_t)0; x++) + { + pu32Xor = pu32XorStart + x; /* Currently checking dword. */ + + for (y = ySkipXor; y < height; y++, pu32Xor += width) + { + if (*pu32Xor != 0) + { + xSkipXor = x; + break; + } + } + } + + if (xSkipXor == ~(uint32_t)0) + { + xSkipXor = 0; + } + + *pxSkip = RT_MIN(xSkipAnd, xSkipXor); + *pySkip = RT_MIN(ySkipAnd, ySkipXor); +} + +/* Generate an AND mask for alpha pointers here, because + * guest driver does not do that correctly for Vista pointers. + * Similar fix, changing the alpha threshold, could be applied + * for the guest driver, but then additions reinstall would be + * necessary, which we try to avoid. + */ +static void mousePointerGenerateANDMask(uint8_t *pu8DstAndMask, int cbDstAndMask, const uint8_t *pu8SrcAlpha, int w, int h) +{ + memset(pu8DstAndMask, 0xFF, cbDstAndMask); + + int y; + for (y = 0; y < h; y++) + { + uint8_t bitmask = 0x80; + + int x; + for (x = 0; x < w; x++, bitmask >>= 1) + { + if (bitmask == 0) + { + bitmask = 0x80; + } + + /* Whether alpha channel value is not transparent enough for the pixel to be seen. */ + if (pu8SrcAlpha[x * 4 + 3] > 0x7f) + { + pu8DstAndMask[x / 8] &= ~bitmask; + } + } + + /* Point to next source and dest scans. */ + pu8SrcAlpha += w * 4; + pu8DstAndMask += (w + 7) / 8; + } +} + +void ConsoleVRDPServer::onMousePointerShapeChange(BOOL visible, + BOOL alpha, + ULONG xHot, + ULONG yHot, + ULONG width, + ULONG height, + ComSafeArrayIn(BYTE,inShape)) +{ + Log9(("VRDPConsoleListener::OnMousePointerShapeChange: %d, %d, %lux%lu, @%lu,%lu\n", + visible, alpha, width, height, xHot, yHot)); + + com::SafeArray <BYTE> aShape(ComSafeArrayInArg(inShape)); + if (aShape.size() == 0) + { + if (!visible) + { + MousePointerHide(); + } + } + else if (width != 0 && height != 0) + { + uint8_t* shape = aShape.raw(); + + dumpPointer(shape, width, height, true); + + /* Try the new interface. */ + if (MousePointer(alpha, xHot, yHot, width, height, shape) == VINF_SUCCESS) + { + return; + } + + /* Continue with the old interface. */ + + /* Pointer consists of 1 bpp AND and 24 BPP XOR masks. + * 'shape' AND mask followed by XOR mask. + * XOR mask contains 32 bit (lsb)BGR0(msb) values. + * + * We convert this to RDP color format which consist of + * one bpp AND mask and 24 BPP (BGR) color XOR image. + * + * RDP clients expect 8 aligned width and height of + * pointer (preferably 32x32). + * + * They even contain bugs which do not appear for + * 32x32 pointers but would appear for a 41x32 one. + * + * So set pointer size to 32x32. This can be done safely + * because most pointers are 32x32. + */ + + int cbDstAndMask = (((width + 7) / 8) * height + 3) & ~3; + + uint8_t *pu8AndMask = shape; + uint8_t *pu8XorMask = shape + cbDstAndMask; + + if (alpha) + { + pu8AndMask = (uint8_t*)alloca(cbDstAndMask); + + mousePointerGenerateANDMask(pu8AndMask, cbDstAndMask, pu8XorMask, width, height); + } + + /* Windows guest alpha pointers are wider than 32 pixels. + * Try to find out the top-left border of the pointer and + * then copy only meaningful bits. All complete top rows + * and all complete left columns where (AND == 1 && XOR == 0) + * are skipped. Hot spot is adjusted. + */ + uint32_t ySkip = 0; /* How many rows to skip at the top. */ + uint32_t xSkip = 0; /* How many columns to skip at the left. */ + + findTopLeftBorder(pu8AndMask, pu8XorMask, width, height, &xSkip, &ySkip); + + /* Must not skip the hot spot. */ + xSkip = RT_MIN(xSkip, xHot); + ySkip = RT_MIN(ySkip, yHot); + + /* + * Compute size and allocate memory for the pointer. + */ + const uint32_t dstwidth = 32; + const uint32_t dstheight = 32; + + VRDECOLORPOINTER *pointer = NULL; + + uint32_t dstmaskwidth = (dstwidth + 7) / 8; + + uint32_t rdpmaskwidth = dstmaskwidth; + uint32_t rdpmasklen = dstheight * rdpmaskwidth; + + uint32_t rdpdatawidth = dstwidth * 3; + uint32_t rdpdatalen = dstheight * rdpdatawidth; + + pointer = (VRDECOLORPOINTER *)RTMemTmpAlloc(sizeof(VRDECOLORPOINTER) + rdpmasklen + rdpdatalen); + + if (pointer) + { + uint8_t *maskarray = (uint8_t*)pointer + sizeof(VRDECOLORPOINTER); + uint8_t *dataarray = maskarray + rdpmasklen; + + memset(maskarray, 0xFF, rdpmasklen); + memset(dataarray, 0x00, rdpdatalen); + + uint32_t srcmaskwidth = (width + 7) / 8; + uint32_t srcdatawidth = width * 4; + + /* Copy AND mask. */ + uint8_t *src = pu8AndMask + ySkip * srcmaskwidth; + uint8_t *dst = maskarray + (dstheight - 1) * rdpmaskwidth; + + uint32_t minheight = RT_MIN(height - ySkip, dstheight); + uint32_t minwidth = RT_MIN(width - xSkip, dstwidth); + + unsigned x, y; + + for (y = 0; y < minheight; y++) + { + for (x = 0; x < minwidth; x++) + { + uint32_t byteIndex = (x + xSkip) / 8; + uint32_t bitIndex = (x + xSkip) % 8; + + bool bit = (src[byteIndex] & (1 << (7 - bitIndex))) != 0; + + if (!bit) + { + byteIndex = x / 8; + bitIndex = x % 8; + + dst[byteIndex] &= ~(1 << (7 - bitIndex)); + } + } + + src += srcmaskwidth; + dst -= rdpmaskwidth; + } + + /* Point src to XOR mask */ + src = pu8XorMask + ySkip * srcdatawidth; + dst = dataarray + (dstheight - 1) * rdpdatawidth; + + for (y = 0; y < minheight ; y++) + { + for (x = 0; x < minwidth; x++) + { + memcpy(dst + x * 3, &src[4 * (x + xSkip)], 3); + } + + src += srcdatawidth; + dst -= rdpdatawidth; + } + + pointer->u16HotX = (uint16_t)(xHot - xSkip); + pointer->u16HotY = (uint16_t)(yHot - ySkip); + + pointer->u16Width = (uint16_t)dstwidth; + pointer->u16Height = (uint16_t)dstheight; + + pointer->u16MaskLen = (uint16_t)rdpmasklen; + pointer->u16DataLen = (uint16_t)rdpdatalen; + + dumpPointer((uint8_t*)pointer + sizeof(*pointer), dstwidth, dstheight, false); + + MousePointerUpdate(pointer); + + RTMemTmpFree(pointer); + } + } +} + + +// ConsoleVRDPServer +//////////////////////////////////////////////////////////////////////////////// + +RTLDRMOD ConsoleVRDPServer::mVRDPLibrary = NIL_RTLDRMOD; + +PFNVRDECREATESERVER ConsoleVRDPServer::mpfnVRDECreateServer = NULL; + +VRDEENTRYPOINTS_4 ConsoleVRDPServer::mEntryPoints; /* A copy of the server entry points. */ +VRDEENTRYPOINTS_4 *ConsoleVRDPServer::mpEntryPoints = NULL; + +VRDECALLBACKS_4 ConsoleVRDPServer::mCallbacks = +{ + { VRDE_INTERFACE_VERSION_4, sizeof(VRDECALLBACKS_4) }, + ConsoleVRDPServer::VRDPCallbackQueryProperty, + ConsoleVRDPServer::VRDPCallbackClientLogon, + ConsoleVRDPServer::VRDPCallbackClientConnect, + ConsoleVRDPServer::VRDPCallbackClientDisconnect, + ConsoleVRDPServer::VRDPCallbackIntercept, + ConsoleVRDPServer::VRDPCallbackUSB, + ConsoleVRDPServer::VRDPCallbackClipboard, + ConsoleVRDPServer::VRDPCallbackFramebufferQuery, + ConsoleVRDPServer::VRDPCallbackFramebufferLock, + ConsoleVRDPServer::VRDPCallbackFramebufferUnlock, + ConsoleVRDPServer::VRDPCallbackInput, + ConsoleVRDPServer::VRDPCallbackVideoModeHint, + ConsoleVRDPServer::VRDECallbackAudioIn +}; + +DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackQueryProperty(void *pvCallback, uint32_t index, void *pvBuffer, + uint32_t cbBuffer, uint32_t *pcbOut) +{ + ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback); + + int vrc = VERR_NOT_SUPPORTED; + + switch (index) + { + case VRDE_QP_NETWORK_PORT: + { + /* This is obsolete, the VRDE server uses VRDE_QP_NETWORK_PORT_RANGE instead. */ + ULONG port = 0; + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)port; + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_NETWORK_ADDRESS: + { + com::Bstr bstr; + server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Address").raw(), bstr.asOutParam()); + + /* The server expects UTF8. */ + com::Utf8Str address = bstr; + + size_t cbAddress = address.length() + 1; + + if (cbAddress >= 0x10000) + { + /* More than 64K seems to be an invalid address. */ + vrc = VERR_TOO_MUCH_DATA; + break; + } + + if ((size_t)cbBuffer >= cbAddress) + { + memcpy(pvBuffer, address.c_str(), cbAddress); + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = (uint32_t)cbAddress; + } break; + + case VRDE_QP_NUMBER_MONITORS: + { + uint32_t cMonitors = server->mConsole->i_getDisplay()->i_getMonitorCount(); + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)cMonitors; + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_NETWORK_PORT_RANGE: + { + com::Bstr bstr; + HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam()); + + if (hrc != S_OK) + { + bstr = ""; + } + + if (bstr == "0") + { + bstr = "3389"; + } + + /* The server expects UTF8. */ + com::Utf8Str portRange = bstr; + + size_t cbPortRange = portRange.length() + 1; + + if (cbPortRange >= _64K) + { + /* More than 64K seems to be an invalid port range string. */ + vrc = VERR_TOO_MUCH_DATA; + break; + } + + if ((size_t)cbBuffer >= cbPortRange) + { + memcpy(pvBuffer, portRange.c_str(), cbPortRange); + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = (uint32_t)cbPortRange; + } break; + + case VRDE_QP_VIDEO_CHANNEL: + { + com::Bstr bstr; + HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Enabled").raw(), + bstr.asOutParam()); + + if (hrc != S_OK) + { + bstr = ""; + } + + com::Utf8Str value = bstr; + + BOOL fVideoEnabled = RTStrICmp(value.c_str(), "true") == 0 + || RTStrICmp(value.c_str(), "1") == 0; + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)fVideoEnabled; + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_VIDEO_CHANNEL_QUALITY: + { + com::Bstr bstr; + HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Quality").raw(), + bstr.asOutParam()); + + if (hrc != S_OK) + { + bstr = ""; + } + + com::Utf8Str value = bstr; + + ULONG ulQuality = RTStrToUInt32(value.c_str()); /* This returns 0 on invalid string which is ok. */ + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)ulQuality; + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_VIDEO_CHANNEL_SUNFLSH: + { + ULONG ulSunFlsh = 1; + + com::Bstr bstr; + HRESULT hrc = server->mConsole->i_machine()->GetExtraData(Bstr("VRDP/SunFlsh").raw(), + bstr.asOutParam()); + if (hrc == S_OK && !bstr.isEmpty()) + { + com::Utf8Str sunFlsh = bstr; + if (!sunFlsh.isEmpty()) + { + ulSunFlsh = sunFlsh.toUInt32(); + } + } + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)ulSunFlsh; + vrc = VINF_SUCCESS; + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_FEATURE: + { + if (cbBuffer < sizeof(VRDEFEATURE)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + size_t cbInfo = cbBuffer - RT_UOFFSETOF(VRDEFEATURE, achInfo); + + VRDEFEATURE *pFeature = (VRDEFEATURE *)pvBuffer; + + size_t cchInfo = 0; + vrc = RTStrNLenEx(pFeature->achInfo, cbInfo, &cchInfo); + + if (RT_FAILURE(vrc)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + Log(("VRDE_QP_FEATURE [%s]\n", pFeature->achInfo)); + + com::Bstr bstrValue; + + if ( RTStrICmp(pFeature->achInfo, "Client/DisableDisplay") == 0 + || RTStrICmp(pFeature->achInfo, "Client/DisableInput") == 0 + || RTStrICmp(pFeature->achInfo, "Client/DisableAudio") == 0 + || RTStrICmp(pFeature->achInfo, "Client/DisableUSB") == 0 + || RTStrICmp(pFeature->achInfo, "Client/DisableClipboard") == 0 + ) + { + /** @todo these features should be per client. */ + NOREF(pFeature->u32ClientId); + + /* These features are mapped to "VRDE/Feature/NAME" extra data. */ + com::Utf8Str extraData("VRDE/Feature/"); + extraData += pFeature->achInfo; + + HRESULT hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(), + bstrValue.asOutParam()); + if (FAILED(hrc) || bstrValue.isEmpty()) + { + /* Also try the old "VRDP/Feature/NAME" */ + extraData = "VRDP/Feature/"; + extraData += pFeature->achInfo; + + hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(), + bstrValue.asOutParam()); + if (FAILED(hrc)) + { + vrc = VERR_NOT_SUPPORTED; + } + } + } + else if (RTStrNCmp(pFeature->achInfo, "Property/", 9) == 0) + { + /* Generic properties. */ + const char *pszPropertyName = &pFeature->achInfo[9]; + HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr(pszPropertyName).raw(), + bstrValue.asOutParam()); + if (FAILED(hrc)) + { + vrc = VERR_NOT_SUPPORTED; + } + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + /* Copy the value string to the callers buffer. */ + if (vrc == VINF_SUCCESS) + { + com::Utf8Str value = bstrValue; + + size_t cb = value.length() + 1; + + if ((size_t)cbInfo >= cb) + { + memcpy(pFeature->achInfo, value.c_str(), cb); + } + else + { + vrc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = (uint32_t)cb; + } + } break; + + case VRDE_SP_NETWORK_BIND_PORT: + { + if (cbBuffer != sizeof(uint32_t)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + ULONG port = *(uint32_t *)pvBuffer; + + server->mVRDPBindPort = port; + + vrc = VINF_SUCCESS; + + if (pcbOut) + { + *pcbOut = sizeof(uint32_t); + } + + server->mConsole->i_onVRDEServerInfoChange(); + } break; + + case VRDE_SP_CLIENT_STATUS: + { + if (cbBuffer < sizeof(VRDECLIENTSTATUS)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + size_t cbStatus = cbBuffer - RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus); + + VRDECLIENTSTATUS *pStatus = (VRDECLIENTSTATUS *)pvBuffer; + + if (cbBuffer < RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus) + pStatus->cbStatus) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + size_t cchStatus = 0; + vrc = RTStrNLenEx(pStatus->achStatus, cbStatus, &cchStatus); + + if (RT_FAILURE(vrc)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + Log(("VRDE_SP_CLIENT_STATUS [%s]\n", pStatus->achStatus)); + + server->mConsole->i_VRDPClientStatusChange(pStatus->u32ClientId, pStatus->achStatus); + + vrc = VINF_SUCCESS; + + if (pcbOut) + { + *pcbOut = cbBuffer; + } + + server->mConsole->i_onVRDEServerInfoChange(); + } break; + + default: + break; + } + + return vrc; +} + +DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClientLogon(void *pvCallback, uint32_t u32ClientId, const char *pszUser, + const char *pszPassword, const char *pszDomain) +{ + ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback); + + return server->mConsole->i_VRDPClientLogon(u32ClientId, pszUser, pszPassword, pszDomain); +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientConnect(void *pvCallback, uint32_t u32ClientId) +{ + ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback); + + pServer->mConsole->i_VRDPClientConnect(u32ClientId); + + /* Should the server report usage of an interface for each client? + * Similar to Intercept. + */ + int c = ASMAtomicIncS32(&pServer->mcClients); + if (c == 1) + { + /* Features which should be enabled only if there is a client. */ + pServer->remote3DRedirect(true); + } + +#ifdef VBOX_WITH_AUDIO_VRDE + AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE(); + if (pVRDE) + pVRDE->onVRDEClientConnect(u32ClientId); +#endif +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientDisconnect(void *pvCallback, uint32_t u32ClientId, + uint32_t fu32Intercepted) +{ + ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback); + AssertPtrReturnVoid(pServer); + + pServer->mConsole->i_VRDPClientDisconnect(u32ClientId, fu32Intercepted); + + if (ASMAtomicReadU32(&pServer->mu32AudioInputClientId) == u32ClientId) + { + LogFunc(("Disconnected client %u\n", u32ClientId)); + ASMAtomicWriteU32(&pServer->mu32AudioInputClientId, 0); + +#ifdef VBOX_WITH_AUDIO_VRDE + AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE(); + if (pVRDE) + { + pVRDE->onVRDEInputIntercept(false /* fIntercept */); + pVRDE->onVRDEClientDisconnect(u32ClientId); + } +#endif + } + + int32_t cClients = ASMAtomicDecS32(&pServer->mcClients); + if (cClients == 0) + { + /* Features which should be enabled only if there is a client. */ + pServer->remote3DRedirect(false); + } +} + +DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackIntercept(void *pvCallback, uint32_t u32ClientId, uint32_t fu32Intercept, + void **ppvIntercept) +{ + ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback); + AssertPtrReturn(pServer, VERR_INVALID_POINTER); + + LogFlowFunc(("%x\n", fu32Intercept)); + + int vrc = VERR_NOT_SUPPORTED; + + switch (fu32Intercept) + { + case VRDE_CLIENT_INTERCEPT_AUDIO: + { + pServer->mConsole->i_VRDPInterceptAudio(u32ClientId); + if (ppvIntercept) + { + *ppvIntercept = pServer; + } + vrc = VINF_SUCCESS; + } break; + + case VRDE_CLIENT_INTERCEPT_USB: + { + pServer->mConsole->i_VRDPInterceptUSB(u32ClientId, ppvIntercept); + vrc = VINF_SUCCESS; + } break; + + case VRDE_CLIENT_INTERCEPT_CLIPBOARD: + { + pServer->mConsole->i_VRDPInterceptClipboard(u32ClientId); + if (ppvIntercept) + { + *ppvIntercept = pServer; + } + vrc = VINF_SUCCESS; + } break; + + case VRDE_CLIENT_INTERCEPT_AUDIO_INPUT: + { + /* + * This request is processed internally by the ConsoleVRDPServer. + * Only one client is allowed to intercept audio input. + */ + if (ASMAtomicCmpXchgU32(&pServer->mu32AudioInputClientId, u32ClientId, 0) == true) + { + LogFunc(("Intercepting audio input by client %RU32\n", u32ClientId)); + +#ifdef VBOX_WITH_AUDIO_VRDE + AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE(); + if (pVRDE) + pVRDE->onVRDEInputIntercept(true /* fIntercept */); +#endif + } + else + { + Log(("AUDIOIN: ignored client %RU32, active client %RU32\n", u32ClientId, pServer->mu32AudioInputClientId)); + vrc = VERR_NOT_SUPPORTED; + } + } break; + + default: + break; + } + + return vrc; +} + +DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackUSB(void *pvCallback, void *pvIntercept, uint32_t u32ClientId, + uint8_t u8Code, const void *pvRet, uint32_t cbRet) +{ + RT_NOREF(pvCallback); +#ifdef VBOX_WITH_USB + return USBClientResponseCallback(pvIntercept, u32ClientId, u8Code, pvRet, cbRet); +#else + RT_NOREF(pvCallback, pvIntercept, u32ClientId, u8Code, pvRet, cbRet); + return VERR_NOT_SUPPORTED; +#endif +} + +DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClipboard(void *pvCallback, void *pvIntercept, uint32_t u32ClientId, + uint32_t u32Function, uint32_t u32Format, + const void *pvData, uint32_t cbData) +{ + RT_NOREF(pvCallback); + return ClipboardCallback(pvIntercept, u32ClientId, u32Function, u32Format, pvData, cbData); +} + +DECLCALLBACK(bool) ConsoleVRDPServer::VRDPCallbackFramebufferQuery(void *pvCallback, unsigned uScreenId, + VRDEFRAMEBUFFERINFO *pInfo) +{ + ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback); + + bool fAvailable = false; + + /* Obtain the new screen bitmap. */ + HRESULT hr = server->mConsole->i_getDisplay()->QuerySourceBitmap(uScreenId, server->maSourceBitmaps[uScreenId].asOutParam()); + if (SUCCEEDED(hr)) + { + LONG xOrigin = 0; + LONG yOrigin = 0; + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + hr = server->maSourceBitmaps[uScreenId]->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + + if (SUCCEEDED(hr)) + { + ULONG dummy; + GuestMonitorStatus_T monitorStatus; + hr = server->mConsole->i_getDisplay()->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy, + &xOrigin, &yOrigin, &monitorStatus); + + if (SUCCEEDED(hr)) + { + /* Now fill the information as requested by the caller. */ + pInfo->pu8Bits = pAddress; + pInfo->xOrigin = xOrigin; + pInfo->yOrigin = yOrigin; + pInfo->cWidth = ulWidth; + pInfo->cHeight = ulHeight; + pInfo->cBitsPerPixel = ulBitsPerPixel; + pInfo->cbLine = ulBytesPerLine; + + fAvailable = true; + } + } + } + + return fAvailable; +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferLock(void *pvCallback, unsigned uScreenId) +{ + NOREF(pvCallback); + NOREF(uScreenId); + /* Do nothing */ +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferUnlock(void *pvCallback, unsigned uScreenId) +{ + NOREF(pvCallback); + NOREF(uScreenId); + /* Do nothing */ +} + +static void fixKbdLockStatus(VRDPInputSynch *pInputSynch, IKeyboard *pKeyboard) +{ + if ( pInputSynch->cGuestNumLockAdaptions + && (pInputSynch->fGuestNumLock != pInputSynch->fClientNumLock)) + { + pInputSynch->cGuestNumLockAdaptions--; + pKeyboard->PutScancode(0x45); + pKeyboard->PutScancode(0x45 | 0x80); + } + if ( pInputSynch->cGuestCapsLockAdaptions + && (pInputSynch->fGuestCapsLock != pInputSynch->fClientCapsLock)) + { + pInputSynch->cGuestCapsLockAdaptions--; + pKeyboard->PutScancode(0x3a); + pKeyboard->PutScancode(0x3a | 0x80); + } +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackInput(void *pvCallback, int type, const void *pvInput, unsigned cbInput) +{ + ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback); + Console *pConsole = server->mConsole; + + switch (type) + { + case VRDE_INPUT_SCANCODE: + { + if (cbInput == sizeof(VRDEINPUTSCANCODE)) + { + IKeyboard *pKeyboard = pConsole->i_getKeyboard(); + + const VRDEINPUTSCANCODE *pInputScancode = (VRDEINPUTSCANCODE *)pvInput; + + /* Track lock keys. */ + if (pInputScancode->uScancode == 0x45) + { + server->m_InputSynch.fClientNumLock = !server->m_InputSynch.fClientNumLock; + } + else if (pInputScancode->uScancode == 0x3a) + { + server->m_InputSynch.fClientCapsLock = !server->m_InputSynch.fClientCapsLock; + } + else if (pInputScancode->uScancode == 0x46) + { + server->m_InputSynch.fClientScrollLock = !server->m_InputSynch.fClientScrollLock; + } + else if ((pInputScancode->uScancode & 0x80) == 0) + { + /* Key pressed. */ + fixKbdLockStatus(&server->m_InputSynch, pKeyboard); + } + + pKeyboard->PutScancode((LONG)pInputScancode->uScancode); + } + } break; + + case VRDE_INPUT_POINT: + { + if (cbInput == sizeof(VRDEINPUTPOINT)) + { + const VRDEINPUTPOINT *pInputPoint = (VRDEINPUTPOINT *)pvInput; + + int mouseButtons = 0; + int iWheel = 0; + + if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON1) + { + mouseButtons |= MouseButtonState_LeftButton; + } + if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON2) + { + mouseButtons |= MouseButtonState_RightButton; + } + if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON3) + { + mouseButtons |= MouseButtonState_MiddleButton; + } + if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_UP) + { + mouseButtons |= MouseButtonState_WheelUp; + iWheel = -1; + } + if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_DOWN) + { + mouseButtons |= MouseButtonState_WheelDown; + iWheel = 1; + } + + if (server->m_fGuestWantsAbsolute) + { + pConsole->i_getMouse()->PutMouseEventAbsolute(pInputPoint->x + 1, pInputPoint->y + 1, iWheel, + 0 /* Horizontal wheel */, mouseButtons); + } else + { + pConsole->i_getMouse()->PutMouseEvent(pInputPoint->x - server->m_mousex, + pInputPoint->y - server->m_mousey, + iWheel, 0 /* Horizontal wheel */, mouseButtons); + server->m_mousex = pInputPoint->x; + server->m_mousey = pInputPoint->y; + } + } + } break; + + case VRDE_INPUT_CAD: + { + pConsole->i_getKeyboard()->PutCAD(); + } break; + + case VRDE_INPUT_RESET: + { + pConsole->Reset(); + } break; + + case VRDE_INPUT_SYNCH: + { + if (cbInput == sizeof(VRDEINPUTSYNCH)) + { + IKeyboard *pKeyboard = pConsole->i_getKeyboard(); + + const VRDEINPUTSYNCH *pInputSynch = (VRDEINPUTSYNCH *)pvInput; + + server->m_InputSynch.fClientNumLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_NUMLOCK) != 0; + server->m_InputSynch.fClientCapsLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_CAPITAL) != 0; + server->m_InputSynch.fClientScrollLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_SCROLL) != 0; + + /* The client initiated synchronization. Always make the guest to reflect the client state. + * Than means, when the guest changes the state itself, it is forced to return to the client + * state. + */ + if (server->m_InputSynch.fClientNumLock != server->m_InputSynch.fGuestNumLock) + { + server->m_InputSynch.cGuestNumLockAdaptions = 2; + } + + if (server->m_InputSynch.fClientCapsLock != server->m_InputSynch.fGuestCapsLock) + { + server->m_InputSynch.cGuestCapsLockAdaptions = 2; + } + + fixKbdLockStatus(&server->m_InputSynch, pKeyboard); + } + } break; + + default: + break; + } +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackVideoModeHint(void *pvCallback, unsigned cWidth, unsigned cHeight, + unsigned cBitsPerPixel, unsigned uScreenId) +{ + ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback); + + server->mConsole->i_getDisplay()->SetVideoModeHint(uScreenId, TRUE /*=enabled*/, + FALSE /*=changeOrigin*/, 0/*=OriginX*/, 0/*=OriginY*/, + cWidth, cHeight, cBitsPerPixel, TRUE /*=notify*/); +} + +DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackAudioIn(void *pvCallback, + void *pvCtx, + uint32_t u32ClientId, + uint32_t u32Event, + const void *pvData, + uint32_t cbData) +{ + RT_NOREF(u32ClientId); + ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback); + AssertPtrReturnVoid(pServer); + +#ifdef VBOX_WITH_AUDIO_VRDE + AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE(); + if (!pVRDE) /* Nothing to do, bail out early. */ + return; + + switch (u32Event) + { + case VRDE_AUDIOIN_BEGIN: + { + pVRDE->onVRDEInputBegin(pvCtx, (PVRDEAUDIOINBEGIN)pvData); + break; + } + + case VRDE_AUDIOIN_DATA: + pVRDE->onVRDEInputData(pvCtx, pvData, cbData); + break; + + case VRDE_AUDIOIN_END: + pVRDE->onVRDEInputEnd(pvCtx); + break; + + default: + break; + } +#else + RT_NOREF(pvCtx, u32Event, pvData, cbData); +#endif /* VBOX_WITH_AUDIO_VRDE */ +} + +ConsoleVRDPServer::ConsoleVRDPServer(Console *console) + : mhClipboard(NULL) +{ + mConsole = console; + + int vrc = RTCritSectInit(&mCritSect); + AssertRC(vrc); + + mcClipboardRefs = 0; + mpfnClipboardCallback = NULL; +#ifdef VBOX_WITH_USB + mUSBBackends.pHead = NULL; + mUSBBackends.pTail = NULL; + + mUSBBackends.thread = NIL_RTTHREAD; + mUSBBackends.fThreadRunning = false; + mUSBBackends.event = 0; +#endif + + mhServer = 0; + mServerInterfaceVersion = 0; + + mcInResize = 0; + + m_fGuestWantsAbsolute = false; + m_mousex = 0; + m_mousey = 0; + + m_InputSynch.cGuestNumLockAdaptions = 2; + m_InputSynch.cGuestCapsLockAdaptions = 2; + + m_InputSynch.fGuestNumLock = false; + m_InputSynch.fGuestCapsLock = false; + m_InputSynch.fGuestScrollLock = false; + + m_InputSynch.fClientNumLock = false; + m_InputSynch.fClientCapsLock = false; + m_InputSynch.fClientScrollLock = false; + + { + ComPtr<IEventSource> es; + console->COMGETTER(EventSource)(es.asOutParam()); + ComObjPtr<VRDPConsoleListenerImpl> aConsoleListener; + aConsoleListener.createObject(); + aConsoleListener->init(new VRDPConsoleListener(), this); + mConsoleListener = aConsoleListener; + com::SafeArray <VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnMousePointerShapeChanged); + eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged); + eventTypes.push_back(VBoxEventType_OnKeyboardLedsChanged); + es->RegisterListener(mConsoleListener, ComSafeArrayAsInParam(eventTypes), true); + } + + mVRDPBindPort = -1; + +#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC + RT_ZERO(mAuthLibCtx); +#endif + + mu32AudioInputClientId = 0; + mcClients = 0; + + /* + * Optional interfaces. + */ + m_fInterfaceImage = false; + RT_ZERO(m_interfaceImage); + RT_ZERO(m_interfaceCallbacksImage); + RT_ZERO(m_interfaceMousePtr); + RT_ZERO(m_interfaceSCard); + RT_ZERO(m_interfaceCallbacksSCard); + RT_ZERO(m_interfaceTSMF); + RT_ZERO(m_interfaceCallbacksTSMF); + RT_ZERO(m_interfaceVideoIn); + RT_ZERO(m_interfaceCallbacksVideoIn); + RT_ZERO(m_interfaceInput); + RT_ZERO(m_interfaceCallbacksInput); + + vrc = RTCritSectInit(&mTSMFLock); + AssertRC(vrc); + + mEmWebcam = new EmWebcam(this); + AssertPtr(mEmWebcam); +} + +ConsoleVRDPServer::~ConsoleVRDPServer() +{ + Stop(); + + if (mConsoleListener) + { + ComPtr<IEventSource> es; + mConsole->COMGETTER(EventSource)(es.asOutParam()); + es->UnregisterListener(mConsoleListener); + mConsoleListener.setNull(); + } + + unsigned i; + for (i = 0; i < RT_ELEMENTS(maSourceBitmaps); i++) + { + maSourceBitmaps[i].setNull(); + } + + if (mEmWebcam) + { + delete mEmWebcam; + mEmWebcam = NULL; + } + + if (RTCritSectIsInitialized(&mCritSect)) + { + RTCritSectDelete(&mCritSect); + RT_ZERO(mCritSect); + } + + if (RTCritSectIsInitialized(&mTSMFLock)) + { + RTCritSectDelete(&mTSMFLock); + RT_ZERO(mTSMFLock); + } +} + +int ConsoleVRDPServer::Launch(void) +{ + LogFlowThisFunc(("\n")); + + IVRDEServer *server = mConsole->i_getVRDEServer(); + AssertReturn(server, VERR_INTERNAL_ERROR_2); + + /* + * Check if VRDE is enabled. + */ + BOOL fEnabled; + HRESULT hrc = server->COMGETTER(Enabled)(&fEnabled); + AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc)); + if (!fEnabled) + return VINF_SUCCESS; + + /* + * Check that a VRDE extension pack name is set and resolve it into a + * library path. + */ + Bstr bstrExtPack; + hrc = server->COMGETTER(VRDEExtPack)(bstrExtPack.asOutParam()); + if (FAILED(hrc)) + return Global::vboxStatusCodeFromCOM(hrc); + if (bstrExtPack.isEmpty()) + return VINF_NOT_SUPPORTED; + + Utf8Str strExtPack(bstrExtPack); + Utf8Str strVrdeLibrary; + int vrc = VINF_SUCCESS; + if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME)) + strVrdeLibrary = "VBoxVRDP"; + else + { +#ifdef VBOX_WITH_EXTPACK + ExtPackManager *pExtPackMgr = mConsole->i_getExtPackManager(); + vrc = pExtPackMgr->i_getVrdeLibraryPathForExtPack(&strExtPack, &strVrdeLibrary); +#else + vrc = VERR_FILE_NOT_FOUND; +#endif + } + if (RT_SUCCESS(vrc)) + { + /* + * Load the VRDE library and start the server, if it is enabled. + */ + vrc = loadVRDPLibrary(strVrdeLibrary.c_str()); + if (RT_SUCCESS(vrc)) + { + VRDEENTRYPOINTS_4 *pEntryPoints4; + vrc = mpfnVRDECreateServer(&mCallbacks.header, this, (VRDEINTERFACEHDR **)&pEntryPoints4, &mhServer); + + if (RT_SUCCESS(vrc)) + { + mServerInterfaceVersion = 4; + mEntryPoints = *pEntryPoints4; + mpEntryPoints = &mEntryPoints; + } + else if (vrc == VERR_VERSION_MISMATCH) + { + /* An older version of VRDE is installed, try version 3. */ + VRDEENTRYPOINTS_3 *pEntryPoints3; + + static VRDECALLBACKS_3 sCallbacks3 = + { + { VRDE_INTERFACE_VERSION_3, sizeof(VRDECALLBACKS_3) }, + ConsoleVRDPServer::VRDPCallbackQueryProperty, + ConsoleVRDPServer::VRDPCallbackClientLogon, + ConsoleVRDPServer::VRDPCallbackClientConnect, + ConsoleVRDPServer::VRDPCallbackClientDisconnect, + ConsoleVRDPServer::VRDPCallbackIntercept, + ConsoleVRDPServer::VRDPCallbackUSB, + ConsoleVRDPServer::VRDPCallbackClipboard, + ConsoleVRDPServer::VRDPCallbackFramebufferQuery, + ConsoleVRDPServer::VRDPCallbackFramebufferLock, + ConsoleVRDPServer::VRDPCallbackFramebufferUnlock, + ConsoleVRDPServer::VRDPCallbackInput, + ConsoleVRDPServer::VRDPCallbackVideoModeHint, + ConsoleVRDPServer::VRDECallbackAudioIn + }; + + vrc = mpfnVRDECreateServer(&sCallbacks3.header, this, (VRDEINTERFACEHDR **)&pEntryPoints3, &mhServer); + if (RT_SUCCESS(vrc)) + { + mServerInterfaceVersion = 3; + mEntryPoints.header = pEntryPoints3->header; + mEntryPoints.VRDEDestroy = pEntryPoints3->VRDEDestroy; + mEntryPoints.VRDEEnableConnections = pEntryPoints3->VRDEEnableConnections; + mEntryPoints.VRDEDisconnect = pEntryPoints3->VRDEDisconnect; + mEntryPoints.VRDEResize = pEntryPoints3->VRDEResize; + mEntryPoints.VRDEUpdate = pEntryPoints3->VRDEUpdate; + mEntryPoints.VRDEColorPointer = pEntryPoints3->VRDEColorPointer; + mEntryPoints.VRDEHidePointer = pEntryPoints3->VRDEHidePointer; + mEntryPoints.VRDEAudioSamples = pEntryPoints3->VRDEAudioSamples; + mEntryPoints.VRDEAudioVolume = pEntryPoints3->VRDEAudioVolume; + mEntryPoints.VRDEUSBRequest = pEntryPoints3->VRDEUSBRequest; + mEntryPoints.VRDEClipboard = pEntryPoints3->VRDEClipboard; + mEntryPoints.VRDEQueryInfo = pEntryPoints3->VRDEQueryInfo; + mEntryPoints.VRDERedirect = pEntryPoints3->VRDERedirect; + mEntryPoints.VRDEAudioInOpen = pEntryPoints3->VRDEAudioInOpen; + mEntryPoints.VRDEAudioInClose = pEntryPoints3->VRDEAudioInClose; + mEntryPoints.VRDEGetInterface = NULL; + mpEntryPoints = &mEntryPoints; + } + else if (vrc == VERR_VERSION_MISMATCH) + { + /* An older version of VRDE is installed, try version 1. */ + VRDEENTRYPOINTS_1 *pEntryPoints1; + + static VRDECALLBACKS_1 sCallbacks1 = + { + { VRDE_INTERFACE_VERSION_1, sizeof(VRDECALLBACKS_1) }, + ConsoleVRDPServer::VRDPCallbackQueryProperty, + ConsoleVRDPServer::VRDPCallbackClientLogon, + ConsoleVRDPServer::VRDPCallbackClientConnect, + ConsoleVRDPServer::VRDPCallbackClientDisconnect, + ConsoleVRDPServer::VRDPCallbackIntercept, + ConsoleVRDPServer::VRDPCallbackUSB, + ConsoleVRDPServer::VRDPCallbackClipboard, + ConsoleVRDPServer::VRDPCallbackFramebufferQuery, + ConsoleVRDPServer::VRDPCallbackFramebufferLock, + ConsoleVRDPServer::VRDPCallbackFramebufferUnlock, + ConsoleVRDPServer::VRDPCallbackInput, + ConsoleVRDPServer::VRDPCallbackVideoModeHint + }; + + vrc = mpfnVRDECreateServer(&sCallbacks1.header, this, (VRDEINTERFACEHDR **)&pEntryPoints1, &mhServer); + if (RT_SUCCESS(vrc)) + { + mServerInterfaceVersion = 1; + mEntryPoints.header = pEntryPoints1->header; + mEntryPoints.VRDEDestroy = pEntryPoints1->VRDEDestroy; + mEntryPoints.VRDEEnableConnections = pEntryPoints1->VRDEEnableConnections; + mEntryPoints.VRDEDisconnect = pEntryPoints1->VRDEDisconnect; + mEntryPoints.VRDEResize = pEntryPoints1->VRDEResize; + mEntryPoints.VRDEUpdate = pEntryPoints1->VRDEUpdate; + mEntryPoints.VRDEColorPointer = pEntryPoints1->VRDEColorPointer; + mEntryPoints.VRDEHidePointer = pEntryPoints1->VRDEHidePointer; + mEntryPoints.VRDEAudioSamples = pEntryPoints1->VRDEAudioSamples; + mEntryPoints.VRDEAudioVolume = pEntryPoints1->VRDEAudioVolume; + mEntryPoints.VRDEUSBRequest = pEntryPoints1->VRDEUSBRequest; + mEntryPoints.VRDEClipboard = pEntryPoints1->VRDEClipboard; + mEntryPoints.VRDEQueryInfo = pEntryPoints1->VRDEQueryInfo; + mEntryPoints.VRDERedirect = NULL; + mEntryPoints.VRDEAudioInOpen = NULL; + mEntryPoints.VRDEAudioInClose = NULL; + mEntryPoints.VRDEGetInterface = NULL; + mpEntryPoints = &mEntryPoints; + } + } + } + + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: loaded version %d of the server.\n", mServerInterfaceVersion)); + + if (mServerInterfaceVersion >= 4) + { + /* The server supports optional interfaces. */ + Assert(mpEntryPoints->VRDEGetInterface != NULL); + + /* Image interface. */ + m_interfaceImage.header.u64Version = 1; + m_interfaceImage.header.u64Size = sizeof(m_interfaceImage); + + m_interfaceCallbacksImage.header.u64Version = 1; + m_interfaceCallbacksImage.header.u64Size = sizeof(m_interfaceCallbacksImage); + m_interfaceCallbacksImage.VRDEImageCbNotify = VRDEImageCbNotify; + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_IMAGE_INTERFACE_NAME, + &m_interfaceImage.header, + &m_interfaceCallbacksImage.header, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_IMAGE_INTERFACE_NAME)); + m_fInterfaceImage = true; + } + + /* Mouse pointer interface. */ + m_interfaceMousePtr.header.u64Version = 1; + m_interfaceMousePtr.header.u64Size = sizeof(m_interfaceMousePtr); + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_MOUSEPTR_INTERFACE_NAME, + &m_interfaceMousePtr.header, + NULL, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_MOUSEPTR_INTERFACE_NAME)); + } + else + { + RT_ZERO(m_interfaceMousePtr); + } + + /* Smartcard interface. */ + m_interfaceSCard.header.u64Version = 1; + m_interfaceSCard.header.u64Size = sizeof(m_interfaceSCard); + + m_interfaceCallbacksSCard.header.u64Version = 1; + m_interfaceCallbacksSCard.header.u64Size = sizeof(m_interfaceCallbacksSCard); + m_interfaceCallbacksSCard.VRDESCardCbNotify = VRDESCardCbNotify; + m_interfaceCallbacksSCard.VRDESCardCbResponse = VRDESCardCbResponse; + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_SCARD_INTERFACE_NAME, + &m_interfaceSCard.header, + &m_interfaceCallbacksSCard.header, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_SCARD_INTERFACE_NAME)); + } + else + { + RT_ZERO(m_interfaceSCard); + } + + /* Raw TSMF interface. */ + m_interfaceTSMF.header.u64Version = 1; + m_interfaceTSMF.header.u64Size = sizeof(m_interfaceTSMF); + + m_interfaceCallbacksTSMF.header.u64Version = 1; + m_interfaceCallbacksTSMF.header.u64Size = sizeof(m_interfaceCallbacksTSMF); + m_interfaceCallbacksTSMF.VRDETSMFCbNotify = VRDETSMFCbNotify; + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_TSMF_INTERFACE_NAME, + &m_interfaceTSMF.header, + &m_interfaceCallbacksTSMF.header, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_TSMF_INTERFACE_NAME)); + } + else + { + RT_ZERO(m_interfaceTSMF); + } + + /* VideoIn interface. */ + m_interfaceVideoIn.header.u64Version = 1; + m_interfaceVideoIn.header.u64Size = sizeof(m_interfaceVideoIn); + + m_interfaceCallbacksVideoIn.header.u64Version = 1; + m_interfaceCallbacksVideoIn.header.u64Size = sizeof(m_interfaceCallbacksVideoIn); + m_interfaceCallbacksVideoIn.VRDECallbackVideoInNotify = VRDECallbackVideoInNotify; + m_interfaceCallbacksVideoIn.VRDECallbackVideoInDeviceDesc = VRDECallbackVideoInDeviceDesc; + m_interfaceCallbacksVideoIn.VRDECallbackVideoInControl = VRDECallbackVideoInControl; + m_interfaceCallbacksVideoIn.VRDECallbackVideoInFrame = VRDECallbackVideoInFrame; + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_VIDEOIN_INTERFACE_NAME, + &m_interfaceVideoIn.header, + &m_interfaceCallbacksVideoIn.header, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_VIDEOIN_INTERFACE_NAME)); + } + else + { + RT_ZERO(m_interfaceVideoIn); + } + + /* Input interface. */ + m_interfaceInput.header.u64Version = 1; + m_interfaceInput.header.u64Size = sizeof(m_interfaceInput); + + m_interfaceCallbacksInput.header.u64Version = 1; + m_interfaceCallbacksInput.header.u64Size = sizeof(m_interfaceCallbacksInput); + m_interfaceCallbacksInput.VRDECallbackInputSetup = VRDECallbackInputSetup; + m_interfaceCallbacksInput.VRDECallbackInputEvent = VRDECallbackInputEvent; + + vrc = mpEntryPoints->VRDEGetInterface(mhServer, + VRDE_INPUT_INTERFACE_NAME, + &m_interfaceInput.header, + &m_interfaceCallbacksInput.header, + this); + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDE: [%s]\n", VRDE_INPUT_INTERFACE_NAME)); + } + else + { + RT_ZERO(m_interfaceInput); + } + + /* Since these interfaces are optional, it is always a success here. */ + vrc = VINF_SUCCESS; + } +#ifdef VBOX_WITH_USB + remoteUSBThreadStart(); +#endif + + /* + * Re-init the server current state, which is usually obtained from events. + */ + fetchCurrentState(); + } + else + { + if (vrc != VERR_NET_ADDRESS_IN_USE) + LogRel(("VRDE: Could not start the server rc = %Rrc\n", vrc)); + /* Don't unload the lib, because it prevents us trying again or + because there may be other users? */ + } + } + } + + return vrc; +} + +void ConsoleVRDPServer::fetchCurrentState(void) +{ + ComPtr<IMousePointerShape> mps; + mConsole->i_getMouse()->COMGETTER(PointerShape)(mps.asOutParam()); + if (!mps.isNull()) + { + BOOL visible, alpha; + ULONG hotX, hotY, width, height; + com::SafeArray <BYTE> shape; + + mps->COMGETTER(Visible)(&visible); + mps->COMGETTER(Alpha)(&alpha); + mps->COMGETTER(HotX)(&hotX); + mps->COMGETTER(HotY)(&hotY); + mps->COMGETTER(Width)(&width); + mps->COMGETTER(Height)(&height); + mps->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape)); + + onMousePointerShapeChange(visible, alpha, hotX, hotY, width, height, ComSafeArrayAsInParam(shape)); + } +} + +#if 0 /** @todo Chromium got removed (see @bugref{9529}) and this is not available for VMSVGA yet. */ +typedef struct H3DORInstance +{ + ConsoleVRDPServer *pThis; + HVRDEIMAGE hImageBitmap; + int32_t x; + int32_t y; + uint32_t w; + uint32_t h; + bool fCreated; + bool fFallback; + bool fTopDown; +} H3DORInstance; + +#define H3DORLOG Log + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORBegin(const void *pvContext, void **ppvInstance, + const char *pszFormat) +{ + H3DORLOG(("H3DORBegin: ctx %p [%s]\n", pvContext, pszFormat)); + + H3DORInstance *p = (H3DORInstance *)RTMemAlloc(sizeof(H3DORInstance)); + + if (p) + { + p->pThis = (ConsoleVRDPServer *)pvContext; + p->hImageBitmap = NULL; + p->x = 0; + p->y = 0; + p->w = 0; + p->h = 0; + p->fCreated = false; + p->fFallback = false; + + /* Host 3D service passes the actual format of data in this redirect instance. + * That is what will be in the H3DORFrame's parameters pvData and cbData. + */ + if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA_TOPDOWN) == 0) + { + /* Accept it. */ + p->fTopDown = true; + } + else if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA) == 0) + { + /* Accept it. */ + p->fTopDown = false; + } + else + { + RTMemFree(p); + p = NULL; + } + } + + H3DORLOG(("H3DORBegin: ins %p\n", p)); + + /* Caller checks this for NULL. */ + *ppvInstance = p; +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORGeometry(void *pvInstance, + int32_t x, int32_t y, uint32_t w, uint32_t h) +{ + H3DORLOG(("H3DORGeometry: ins %p %d,%d %dx%d\n", pvInstance, x, y, w, h)); + + H3DORInstance *p = (H3DORInstance *)pvInstance; + AssertPtrReturnVoid(p); + AssertPtrReturnVoid(p->pThis); + + /** @todo find out what to do if size changes to 0x0 from non zero */ + if (w == 0 || h == 0) + { + /* Do nothing. */ + return; + } + + RTRECT rect; + rect.xLeft = x; + rect.yTop = y; + rect.xRight = x + w; + rect.yBottom = y + h; + + if (p->hImageBitmap) + { + /* An image handle has been already created, + * check if it has the same size as the reported geometry. + */ + if ( p->x == x + && p->y == y + && p->w == w + && p->h == h) + { + H3DORLOG(("H3DORGeometry: geometry not changed\n")); + /* Do nothing. Continue using the existing handle. */ + } + else + { + int vrc = p->fFallback? + VERR_NOT_SUPPORTED: /* Try to go out of fallback mode. */ + p->pThis->m_interfaceImage.VRDEImageGeometrySet(p->hImageBitmap, &rect); + if (RT_SUCCESS(rc)) + { + p->x = x; + p->y = y; + p->w = w; + p->h = h; + } + else + { + /* The handle must be recreated. Delete existing handle here. */ + p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap); + p->hImageBitmap = NULL; + } + } + } + + if (!p->hImageBitmap) + { + /* Create a new bitmap handle. */ + uint32_t u32ScreenId = 0; /** @todo clip to corresponding screens. + * Clipping can be done here or in VRDP server. + * If VRDP does clipping, then uScreenId parameter + * is not necessary and coords must be global. + * (have to check which coords are used in opengl service). + * Since all VRDE API uses a ScreenId, + * the clipping must be done here in ConsoleVRDPServer + */ + uint32_t fu32CompletionFlags = 0; + p->fFallback = false; + int vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer, + &p->hImageBitmap, + p, + u32ScreenId, + VRDE_IMAGE_F_CREATE_CONTENT_3D + | VRDE_IMAGE_F_CREATE_WINDOW, + &rect, + VRDE_IMAGE_FMT_ID_BITMAP_BGRA8, + NULL, + 0, + &fu32CompletionFlags); + if (RT_FAILURE(rc)) + { + /* No support for a 3D + WINDOW. Try bitmap updates. */ + H3DORLOG(("H3DORGeometry: Fallback to bitmaps\n")); + fu32CompletionFlags = 0; + p->fFallback = true; + vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer, + &p->hImageBitmap, + p, + u32ScreenId, + 0, + &rect, + VRDE_IMAGE_FMT_ID_BITMAP_BGRA8, + NULL, + 0, + &fu32CompletionFlags); + } + + H3DORLOG(("H3DORGeometry: Image handle create %Rrc, flags 0x%RX32\n", rc, fu32CompletionFlags)); + + if (RT_SUCCESS(vrc)) + { + p->x = x; + p->y = y; + p->w = w; + p->h = h; + + if ((fu32CompletionFlags & VRDE_IMAGE_F_COMPLETE_ASYNC) == 0) + { + p->fCreated = true; + } + } + else + { + p->hImageBitmap = NULL; + p->w = 0; + p->h = 0; + } + } + + H3DORLOG(("H3DORGeometry: ins %p completed\n", pvInstance)); +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORVisibleRegion(void *pvInstance, + uint32_t cRects, const RTRECT *paRects) +{ + H3DORLOG(("H3DORVisibleRegion: ins %p %d\n", pvInstance, cRects)); + + H3DORInstance *p = (H3DORInstance *)pvInstance; + AssertPtrReturnVoid(p); + AssertPtrReturnVoid(p->pThis); + + if (cRects == 0) + { + /* Complete image is visible. */ + RTRECT rect; + rect.xLeft = p->x; + rect.yTop = p->y; + rect.xRight = p->x + p->w; + rect.yBottom = p->y + p->h; + p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap, + 1, + &rect); + } + else + { + p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap, + cRects, + paRects); + } + + H3DORLOG(("H3DORVisibleRegion: ins %p completed\n", pvInstance)); +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORFrame(void *pvInstance, + void *pvData, uint32_t cbData) +{ + H3DORLOG(("H3DORFrame: ins %p %p %d\n", pvInstance, pvData, cbData)); + + H3DORInstance *p = (H3DORInstance *)pvInstance; + AssertPtrReturnVoid(p); + AssertPtrReturnVoid(p->pThis); + + /* Currently only a topdown BGR0 bitmap format is supported. */ + VRDEIMAGEBITMAP image; + + image.cWidth = p->w; + image.cHeight = p->h; + image.pvData = pvData; + image.cbData = cbData; + image.pvScanLine0 = (uint8_t *)pvData + (p->h - 1) * p->w * 4; + image.iScanDelta = 4 * p->w; + if (p->fTopDown) + { + image.iScanDelta = -image.iScanDelta; + } + + p->pThis->m_interfaceImage.VRDEImageUpdate (p->hImageBitmap, + p->x, + p->y, + p->w, + p->h, + &image, + sizeof(VRDEIMAGEBITMAP)); + + H3DORLOG(("H3DORFrame: ins %p completed\n", pvInstance)); +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DOREnd(void *pvInstance) +{ + H3DORLOG(("H3DOREnd: ins %p\n", pvInstance)); + + H3DORInstance *p = (H3DORInstance *)pvInstance; + AssertPtrReturnVoid(p); + AssertPtrReturnVoid(p->pThis); + + p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap); + + RT_ZERO(*p); + RTMemFree(p); + + H3DORLOG(("H3DOREnd: ins %p completed\n", pvInstance)); +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::H3DORContextProperty(const void *pvContext, uint32_t index, + void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut) +{ + RT_NOREF(pvContext, pvBuffer); + int vrc = VINF_SUCCESS; + + H3DORLOG(("H3DORContextProperty: index %d\n", index)); + + if (index == H3DOR_PROP_FORMATS) + { + /* Return a comma separated list of supported formats. */ + uint32_t cbOut = (uint32_t)strlen(H3DOR_FMT_RGBA_TOPDOWN) + 1 + + (uint32_t)strlen(H3DOR_FMT_RGBA) + 1; + if (cbOut <= cbBuffer) + { + char *pch = (char *)pvBuffer; + memcpy(pch, H3DOR_FMT_RGBA_TOPDOWN, strlen(H3DOR_FMT_RGBA_TOPDOWN)); + pch += strlen(H3DOR_FMT_RGBA_TOPDOWN); + *pch++ = ','; + memcpy(pch, H3DOR_FMT_RGBA, strlen(H3DOR_FMT_RGBA)); + pch += strlen(H3DOR_FMT_RGBA); + *pch++ = '\0'; + } + else + { + vrc = VERR_BUFFER_OVERFLOW; + } + *pcbOut = cbOut; + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + H3DORLOG(("H3DORContextProperty: %Rrc\n", vrc)); + return vrc; +} +#endif + +void ConsoleVRDPServer::remote3DRedirect(bool fEnable) +{ + if (!m_fInterfaceImage) + { + /* No redirect without corresponding interface. */ + return; + } + + /* Check if 3D redirection has been enabled. It is enabled by default. */ + com::Bstr bstr; + HRESULT hrc = mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("H3DRedirect/Enabled").raw(), bstr.asOutParam()); + + com::Utf8Str value = hrc == S_OK? bstr: ""; + + bool fAllowed = RTStrICmp(value.c_str(), "true") == 0 + || RTStrICmp(value.c_str(), "1") == 0 + || value.c_str()[0] == 0; + + if (!fAllowed && fEnable) + { + return; + } + +#if 0 /** @todo Implement again for VMSVGA. */ + /* Tell the host 3D service to redirect output using the ConsoleVRDPServer callbacks. */ + H3DOUTPUTREDIRECT outputRedirect = + { + this, + H3DORBegin, + H3DORGeometry, + H3DORVisibleRegion, + H3DORFrame, + H3DOREnd, + H3DORContextProperty + }; + + if (!fEnable) + { + /* This will tell the service to disable rediection. */ + RT_ZERO(outputRedirect); + } +#endif + + return; +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDEImageCbNotify (void *pvContext, + void *pvUser, + HVRDEIMAGE hVideo, + uint32_t u32Id, + void *pvData, + uint32_t cbData) +{ + RT_NOREF(hVideo); + Log(("H3DOR: VRDEImageCbNotify: pvContext %p, pvUser %p, hVideo %p, u32Id %u, pvData %p, cbData %d\n", + pvContext, pvUser, hVideo, u32Id, pvData, cbData)); + + ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvContext); NOREF(pServer); + +#if 0 /** @todo Implement again for VMSVGA. */ + H3DORInstance *p = (H3DORInstance *)pvUser; + Assert(p); + Assert(p->pThis); + Assert(p->pThis == pServer); + + if (u32Id == VRDE_IMAGE_NOTIFY_HANDLE_CREATE) + { + if (cbData != sizeof(uint32_t)) + { + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + uint32_t u32StreamId = *(uint32_t *)pvData; + Log(("H3DOR: VRDE_IMAGE_NOTIFY_HANDLE_CREATE u32StreamId %d\n", + u32StreamId)); + + if (u32StreamId != 0) + { + p->fCreated = true; /// @todo not needed? + } + else + { + /* The stream has not been created. */ + } + } +#else + RT_NOREF(pvUser, u32Id, pvData, cbData); +#endif + + return VINF_SUCCESS; +} + +#undef H3DORLOG + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbNotify(void *pvContext, + uint32_t u32Id, + void *pvData, + uint32_t cbData) +{ +#ifdef VBOX_WITH_USB_CARDREADER + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext); + UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader(); + return pReader->VRDENotify(u32Id, pvData, cbData); +#else + NOREF(pvContext); + NOREF(u32Id); + NOREF(pvData); + NOREF(cbData); + return VERR_NOT_SUPPORTED; +#endif +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbResponse(void *pvContext, + int vrcRequest, + void *pvUser, + uint32_t u32Function, + void *pvData, + uint32_t cbData) +{ +#ifdef VBOX_WITH_USB_CARDREADER + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext); + UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader(); + return pReader->VRDEResponse(vrcRequest, pvUser, u32Function, pvData, cbData); +#else + NOREF(pvContext); + NOREF(vrcRequest); + NOREF(pvUser); + NOREF(u32Function); + NOREF(pvData); + NOREF(cbData); + return VERR_NOT_SUPPORTED; +#endif +} + +int ConsoleVRDPServer::SCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData) +{ + int vrc = VINF_SUCCESS; + + if (mhServer && mpEntryPoints && m_interfaceSCard.VRDESCardRequest) + { + vrc = m_interfaceSCard.VRDESCardRequest(mhServer, pvUser, u32Function, pvData, cbData); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + + +struct TSMFHOSTCHCTX; +struct TSMFVRDPCTX; + +typedef struct TSMFHOSTCHCTX +{ + ConsoleVRDPServer *pThis; + + struct TSMFVRDPCTX *pVRDPCtx; /* NULL if no corresponding host channel context. */ + + void *pvDataReceived; + uint32_t cbDataReceived; + uint32_t cbDataAllocated; +} TSMFHOSTCHCTX; + +typedef struct TSMFVRDPCTX +{ + ConsoleVRDPServer *pThis; + + VBOXHOSTCHANNELCALLBACKS *pCallbacks; + void *pvCallbacks; + + TSMFHOSTCHCTX *pHostChCtx; /* NULL if no corresponding host channel context. */ + + uint32_t u32ChannelHandle; +} TSMFVRDPCTX; + +static int tsmfContextsAlloc(TSMFHOSTCHCTX **ppHostChCtx, TSMFVRDPCTX **ppVRDPCtx) +{ + TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)RTMemAllocZ(sizeof(TSMFHOSTCHCTX)); + if (!pHostChCtx) + { + return VERR_NO_MEMORY; + } + + TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)RTMemAllocZ(sizeof(TSMFVRDPCTX)); + if (!pVRDPCtx) + { + RTMemFree(pHostChCtx); + return VERR_NO_MEMORY; + } + + *ppHostChCtx = pHostChCtx; + *ppVRDPCtx = pVRDPCtx; + return VINF_SUCCESS; +} + +int ConsoleVRDPServer::tsmfLock(void) +{ + int vrc = RTCritSectEnter(&mTSMFLock); + AssertRC(vrc); + return vrc; +} + +void ConsoleVRDPServer::tsmfUnlock(void) +{ + RTCritSectLeave(&mTSMFLock); +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelAttach(void *pvProvider, + void **ppvChannel, + uint32_t u32Flags, + VBOXHOSTCHANNELCALLBACKS *pCallbacks, + void *pvCallbacks) +{ + LogFlowFunc(("\n")); + + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvProvider); + + /* Create 2 context structures: for the VRDP server and for the host service. */ + TSMFHOSTCHCTX *pHostChCtx = NULL; + TSMFVRDPCTX *pVRDPCtx = NULL; + + int vrc = tsmfContextsAlloc(&pHostChCtx, &pVRDPCtx); + if (RT_FAILURE(vrc)) + { + return vrc; + } + + pHostChCtx->pThis = pThis; + pHostChCtx->pVRDPCtx = pVRDPCtx; + + pVRDPCtx->pThis = pThis; + pVRDPCtx->pCallbacks = pCallbacks; + pVRDPCtx->pvCallbacks = pvCallbacks; + pVRDPCtx->pHostChCtx = pHostChCtx; + + vrc = pThis->m_interfaceTSMF.VRDETSMFChannelCreate(pThis->mhServer, pVRDPCtx, u32Flags); + + if (RT_SUCCESS(vrc)) + { + /** @todo contexts should be in a list for accounting. */ + *ppvChannel = pHostChCtx; + } + else + { + RTMemFree(pHostChCtx); + RTMemFree(pVRDPCtx); + } + + return vrc; +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::tsmfHostChannelDetach(void *pvChannel) +{ + LogFlowFunc(("\n")); + + TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel; + ConsoleVRDPServer *pThis = pHostChCtx->pThis; + + int vrc = pThis->tsmfLock(); + if (RT_SUCCESS(vrc)) + { + bool fClose = false; + uint32_t u32ChannelHandle = 0; + + if (pHostChCtx->pVRDPCtx) + { + /* There is still a VRDP context for this channel. */ + pHostChCtx->pVRDPCtx->pHostChCtx = NULL; + u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle; + fClose = true; + } + + pThis->tsmfUnlock(); + + RTMemFree(pHostChCtx); + + if (fClose) + { + LogFlowFunc(("Closing VRDE channel %d.\n", u32ChannelHandle)); + pThis->m_interfaceTSMF.VRDETSMFChannelClose(pThis->mhServer, u32ChannelHandle); + } + else + { + LogFlowFunc(("No VRDE channel.\n")); + } + } +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelSend(void *pvChannel, + const void *pvData, + uint32_t cbData) +{ + LogFlowFunc(("cbData %d\n", cbData)); + + TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel; + ConsoleVRDPServer *pThis = pHostChCtx->pThis; + + int vrc = pThis->tsmfLock(); + if (RT_SUCCESS(vrc)) + { + bool fSend = false; + uint32_t u32ChannelHandle = 0; + + if (pHostChCtx->pVRDPCtx) + { + u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle; + fSend = true; + } + + pThis->tsmfUnlock(); + + if (fSend) + { + LogFlowFunc(("Send to VRDE channel %d.\n", u32ChannelHandle)); + vrc = pThis->m_interfaceTSMF.VRDETSMFChannelSend(pThis->mhServer, u32ChannelHandle, + pvData, cbData); + } + } + + return vrc; +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelRecv(void *pvChannel, + void *pvData, + uint32_t cbData, + uint32_t *pcbReceived, + uint32_t *pcbRemaining) +{ + LogFlowFunc(("cbData %d\n", cbData)); + + TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel; + ConsoleVRDPServer *pThis = pHostChCtx->pThis; + + int vrc = pThis->tsmfLock(); + if (RT_SUCCESS(vrc)) + { + uint32_t cbToCopy = RT_MIN(cbData, pHostChCtx->cbDataReceived); + uint32_t cbRemaining = pHostChCtx->cbDataReceived - cbToCopy; + + LogFlowFunc(("cbToCopy %d, cbRemaining %d\n", cbToCopy, cbRemaining)); + + if (cbToCopy != 0) + { + memcpy(pvData, pHostChCtx->pvDataReceived, cbToCopy); + + if (cbRemaining != 0) + { + memmove(pHostChCtx->pvDataReceived, (uint8_t *)pHostChCtx->pvDataReceived + cbToCopy, cbRemaining); + } + + pHostChCtx->cbDataReceived = cbRemaining; + } + + pThis->tsmfUnlock(); + + *pcbRemaining = cbRemaining; + *pcbReceived = cbToCopy; + } + + return vrc; +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelControl(void *pvChannel, + uint32_t u32Code, + const void *pvParm, + uint32_t cbParm, + const void *pvData, + uint32_t cbData, + uint32_t *pcbDataReturned) +{ + RT_NOREF(pvParm, cbParm, pvData, cbData); + LogFlowFunc(("u32Code %u\n", u32Code)); + + if (!pvChannel) + { + /* Special case, the provider must answer rather than a channel instance. */ + if (u32Code == VBOX_HOST_CHANNEL_CTRL_EXISTS) + { + *pcbDataReturned = 0; + return VINF_SUCCESS; + } + + return VERR_NOT_IMPLEMENTED; + } + + /* Channels do not support this. */ + return VERR_NOT_IMPLEMENTED; +} + + +void ConsoleVRDPServer::setupTSMF(void) +{ + if (m_interfaceTSMF.header.u64Size == 0) + { + return; + } + + /* Register with the host channel service. */ + VBOXHOSTCHANNELINTERFACE hostChannelInterface = + { + this, + tsmfHostChannelAttach, + tsmfHostChannelDetach, + tsmfHostChannelSend, + tsmfHostChannelRecv, + tsmfHostChannelControl + }; + + VBoxHostChannelHostRegister parms; + + static char szProviderName[] = "/vrde/tsmf"; + + parms.name.type = VBOX_HGCM_SVC_PARM_PTR; + parms.name.u.pointer.addr = &szProviderName[0]; + parms.name.u.pointer.size = sizeof(szProviderName); + + parms.iface.type = VBOX_HGCM_SVC_PARM_PTR; + parms.iface.u.pointer.addr = &hostChannelInterface; + parms.iface.u.pointer.size = sizeof(hostChannelInterface); + + VMMDev *pVMMDev = mConsole->i_getVMMDev(); + + if (!pVMMDev) + { + AssertMsgFailed(("setupTSMF no vmmdev\n")); + return; + } + + int vrc = pVMMDev->hgcmHostCall("VBoxHostChannel", + VBOX_HOST_CHANNEL_HOST_FN_REGISTER, + 2, + &parms.name); + + if (!RT_SUCCESS(vrc)) + { + Log(("VBOX_HOST_CHANNEL_HOST_FN_REGISTER failed with %Rrc\n", vrc)); + return; + } + + LogRel(("VRDE: Enabled TSMF channel.\n")); + + return; +} + +/** @todo these defines must be in a header, which is used by guest component as well. */ +#define VBOX_TSMF_HCH_CREATE_ACCEPTED (VBOX_HOST_CHANNEL_EVENT_USER + 0) +#define VBOX_TSMF_HCH_CREATE_DECLINED (VBOX_HOST_CHANNEL_EVENT_USER + 1) +#define VBOX_TSMF_HCH_DISCONNECTED (VBOX_HOST_CHANNEL_EVENT_USER + 2) + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDETSMFCbNotify(void *pvContext, + uint32_t u32Notification, + void *pvChannel, + const void *pvParm, + uint32_t cbParm) +{ + RT_NOREF(cbParm); + int vrc = VINF_SUCCESS; + + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext); + + TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)pvChannel; + + Assert(pVRDPCtx->pThis == pThis); + + if (pVRDPCtx->pCallbacks == NULL) + { + LogFlowFunc(("tsmfHostChannel: Channel disconnected. Skipping.\n")); + return; + } + + switch (u32Notification) + { + case VRDE_TSMF_N_CREATE_ACCEPTED: + { + VRDETSMFNOTIFYCREATEACCEPTED *p = (VRDETSMFNOTIFYCREATEACCEPTED *)pvParm; + Assert(cbParm == sizeof(VRDETSMFNOTIFYCREATEACCEPTED)); + + LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_ACCEPTED(%p): p->u32ChannelHandle %d\n", + pVRDPCtx, p->u32ChannelHandle)); + + pVRDPCtx->u32ChannelHandle = p->u32ChannelHandle; + + pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx, + VBOX_TSMF_HCH_CREATE_ACCEPTED, + NULL, 0); + } break; + + case VRDE_TSMF_N_CREATE_DECLINED: + { + LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_DECLINED(%p)\n", pVRDPCtx)); + + pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx, + VBOX_TSMF_HCH_CREATE_DECLINED, + NULL, 0); + } break; + + case VRDE_TSMF_N_DATA: + { + /* Save the data in the intermediate buffer and send the event. */ + VRDETSMFNOTIFYDATA *p = (VRDETSMFNOTIFYDATA *)pvParm; + Assert(cbParm == sizeof(VRDETSMFNOTIFYDATA)); + + LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA(%p): p->cbData %d\n", pVRDPCtx, p->cbData)); + + VBOXHOSTCHANNELEVENTRECV ev; + ev.u32SizeAvailable = 0; + + vrc = pThis->tsmfLock(); + + if (RT_SUCCESS(vrc)) + { + TSMFHOSTCHCTX *pHostChCtx = pVRDPCtx->pHostChCtx; + + if (pHostChCtx) + { + if (pHostChCtx->pvDataReceived) + { + uint32_t cbAlloc = p->cbData + pHostChCtx->cbDataReceived; + pHostChCtx->pvDataReceived = RTMemRealloc(pHostChCtx->pvDataReceived, cbAlloc); + memcpy((uint8_t *)pHostChCtx->pvDataReceived + pHostChCtx->cbDataReceived, p->pvData, p->cbData); + + pHostChCtx->cbDataReceived += p->cbData; + pHostChCtx->cbDataAllocated = cbAlloc; + } + else + { + pHostChCtx->pvDataReceived = RTMemAlloc(p->cbData); + memcpy(pHostChCtx->pvDataReceived, p->pvData, p->cbData); + + pHostChCtx->cbDataReceived = p->cbData; + pHostChCtx->cbDataAllocated = p->cbData; + } + + ev.u32SizeAvailable = p->cbData; + } + else + { + LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA: no host channel. Skipping\n")); + } + + pThis->tsmfUnlock(); + } + + pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx, + VBOX_HOST_CHANNEL_EVENT_RECV, + &ev, sizeof(ev)); + } break; + + case VRDE_TSMF_N_DISCONNECTED: + { + LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DISCONNECTED(%p)\n", pVRDPCtx)); + + pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx, + VBOX_TSMF_HCH_DISCONNECTED, + NULL, 0); + + /* The callback context will not be used anymore. */ + pVRDPCtx->pCallbacks->HostChannelCallbackDeleted(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx); + pVRDPCtx->pCallbacks = NULL; + pVRDPCtx->pvCallbacks = NULL; + + vrc = pThis->tsmfLock(); + if (RT_SUCCESS(vrc)) + { + if (pVRDPCtx->pHostChCtx) + { + /* There is still a host channel context for this channel. */ + pVRDPCtx->pHostChCtx->pVRDPCtx = NULL; + } + + pThis->tsmfUnlock(); + + RT_ZERO(*pVRDPCtx); + RTMemFree(pVRDPCtx); + } + } break; + + default: + { + AssertFailed(); + } break; + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInNotify(void *pvCallback, + uint32_t u32Id, + const void *pvData, + uint32_t cbData) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbNotify(u32Id, pvData, cbData); + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInDeviceDesc(void *pvCallback, + int vrcRequest, + void *pDeviceCtx, + void *pvUser, + const VRDEVIDEOINDEVICEDESC *pDeviceDesc, + uint32_t cbDevice) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbDeviceDesc(vrcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDevice); + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInControl(void *pvCallback, + int vrcRequest, + void *pDeviceCtx, + void *pvUser, + const VRDEVIDEOINCTRLHDR *pControl, + uint32_t cbControl) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbControl(vrcRequest, pDeviceCtx, pvUser, pControl, cbControl); + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInFrame(void *pvCallback, + int vrcRequest, + void *pDeviceCtx, + const VRDEVIDEOINPAYLOADHDR *pFrame, + uint32_t cbFrame) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbFrame(vrcRequest, pDeviceCtx, pFrame, cbFrame); + } +} + +int ConsoleVRDPServer::VideoInDeviceAttach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, void *pvDeviceCtx) +{ + int vrc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceAttach) + { + vrc = m_interfaceVideoIn.VRDEVideoInDeviceAttach(mhServer, pDeviceHandle, pvDeviceCtx); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + +int ConsoleVRDPServer::VideoInDeviceDetach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle) +{ + int vrc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceDetach) + { + vrc = m_interfaceVideoIn.VRDEVideoInDeviceDetach(mhServer, pDeviceHandle); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + +int ConsoleVRDPServer::VideoInGetDeviceDesc(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle) +{ + int vrc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInGetDeviceDesc) + { + vrc = m_interfaceVideoIn.VRDEVideoInGetDeviceDesc(mhServer, pvUser, pDeviceHandle); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + +int ConsoleVRDPServer::VideoInControl(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, + const VRDEVIDEOINCTRLHDR *pReq, uint32_t cbReq) +{ + int vrc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInControl) + { + vrc = m_interfaceVideoIn.VRDEVideoInControl(mhServer, pvUser, pDeviceHandle, pReq, cbReq); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputSetup(void *pvCallback, + int vrcRequest, + uint32_t u32Method, + const void *pvResult, + uint32_t cbResult) +{ + NOREF(pvCallback); + NOREF(vrcRequest); + NOREF(u32Method); + NOREF(pvResult); + NOREF(cbResult); +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputEvent(void *pvCallback, + uint32_t u32Method, + const void *pvEvent, + uint32_t cbEvent) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + + if (u32Method == VRDE_INPUT_METHOD_TOUCH) + { + if (cbEvent >= sizeof(VRDEINPUTHEADER)) + { + VRDEINPUTHEADER *pHeader = (VRDEINPUTHEADER *)pvEvent; + + if (pHeader->u16EventId == VRDEINPUT_EVENTID_TOUCH) + { + IMouse *pMouse = pThis->mConsole->i_getMouse(); + + VRDEINPUT_TOUCH_EVENT_PDU *p = (VRDEINPUT_TOUCH_EVENT_PDU *)pHeader; + + uint16_t iFrame; + for (iFrame = 0; iFrame < p->u16FrameCount; iFrame++) + { + VRDEINPUT_TOUCH_FRAME *pFrame = &p->aFrames[iFrame]; + + com::SafeArray<LONG64> aContacts(pFrame->u16ContactCount); + + uint16_t iContact; + for (iContact = 0; iContact < pFrame->u16ContactCount; iContact++) + { + VRDEINPUT_CONTACT_DATA *pContact = &pFrame->aContacts[iContact]; + + int16_t x = (int16_t)(pContact->i32X + 1); + int16_t y = (int16_t)(pContact->i32Y + 1); + uint8_t contactId = pContact->u8ContactId; + uint8_t contactState = TouchContactState_None; + + if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INRANGE) + { + contactState |= TouchContactState_InRange; + } + if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INCONTACT) + { + contactState |= TouchContactState_InContact; + } + + aContacts[iContact] = RT_MAKE_U64_FROM_U16((uint16_t)x, + (uint16_t)y, + RT_MAKE_U16(contactId, contactState), + 0); + } + + if (pFrame->u64FrameOffset == 0) + { + pThis->mu64TouchInputTimestampMCS = 0; + } + else + { + pThis->mu64TouchInputTimestampMCS += pFrame->u64FrameOffset; + } + + pMouse->PutEventMultiTouch(pFrame->u16ContactCount, + ComSafeArrayAsInParam(aContacts), + true /* isTouchScreen */, + (ULONG)(pThis->mu64TouchInputTimestampMCS / 1000)); /* Micro->milliseconds. */ + } + } + else if (pHeader->u16EventId == VRDEINPUT_EVENTID_DISMISS_HOVERING_CONTACT) + { + /** @todo */ + } + else + { + AssertMsgFailed(("EventId %d\n", pHeader->u16EventId)); + } + } + } +} + + +void ConsoleVRDPServer::EnableConnections(void) +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEEnableConnections(mhServer, true); + + /* Setup the generic TSMF channel. */ + setupTSMF(); + } +} + +void ConsoleVRDPServer::DisconnectClient(uint32_t u32ClientId, bool fReconnect) +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEDisconnect(mhServer, u32ClientId, fReconnect); + } +} + +int ConsoleVRDPServer::MousePointer(BOOL alpha, + ULONG xHot, + ULONG yHot, + ULONG width, + ULONG height, + const uint8_t *pu8Shape) +{ + int vrc = VINF_SUCCESS; + + if (mhServer && mpEntryPoints && m_interfaceMousePtr.VRDEMousePtr) + { + size_t cbMask = (((width + 7) / 8) * height + 3) & ~3; + size_t cbData = width * height * 4; + + size_t cbDstMask = alpha? 0: cbMask; + + size_t cbPointer = sizeof(VRDEMOUSEPTRDATA) + cbDstMask + cbData; + uint8_t *pu8Pointer = (uint8_t *)RTMemAlloc(cbPointer); + if (pu8Pointer != NULL) + { + VRDEMOUSEPTRDATA *pPointer = (VRDEMOUSEPTRDATA *)pu8Pointer; + + pPointer->u16HotX = (uint16_t)xHot; + pPointer->u16HotY = (uint16_t)yHot; + pPointer->u16Width = (uint16_t)width; + pPointer->u16Height = (uint16_t)height; + pPointer->u16MaskLen = (uint16_t)cbDstMask; + pPointer->u32DataLen = (uint32_t)cbData; + + /* AND mask. */ + uint8_t *pu8Mask = pu8Pointer + sizeof(VRDEMOUSEPTRDATA); + if (cbDstMask) + { + memcpy(pu8Mask, pu8Shape, cbDstMask); + } + + /* XOR mask */ + uint8_t *pu8Data = pu8Mask + pPointer->u16MaskLen; + memcpy(pu8Data, pu8Shape + cbMask, cbData); + + m_interfaceMousePtr.VRDEMousePtr(mhServer, pPointer); + + RTMemFree(pu8Pointer); + } + else + { + vrc = VERR_NO_MEMORY; + } + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + +void ConsoleVRDPServer::MousePointerUpdate(const VRDECOLORPOINTER *pPointer) +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEColorPointer(mhServer, pPointer); + } +} + +void ConsoleVRDPServer::MousePointerHide(void) +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEHidePointer(mhServer); + } +} + +void ConsoleVRDPServer::Stop(void) +{ + AssertPtr(this); /** @todo r=bird: there are(/was) some odd cases where this buster was invalid on + * linux. Just remove this when it's 100% sure that problem has been fixed. */ + +#ifdef VBOX_WITH_USB + remoteUSBThreadStop(); +#endif /* VBOX_WITH_USB */ + + if (mhServer) + { + HVRDESERVER hServer = mhServer; + + /* Reset the handle to avoid further calls to the server. */ + mhServer = 0; + + /* Workaround for VM process hangs on termination. + * + * Make sure that the server is not currently processing a resize. + * mhServer 0 will not allow to enter the server again. + * Wait until any current resize returns from the server. + */ + if (mcInResize) + { + LogRel(("VRDP: waiting for resize %d\n", mcInResize)); + + int i = 0; + while (mcInResize && ++i < 100) + { + RTThreadSleep(10); + } + } + + if (mpEntryPoints && hServer) + { + mpEntryPoints->VRDEDestroy(hServer); + } + } + +#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC + AuthLibUnload(&mAuthLibCtx); +#endif +} + +/* Worker thread for Remote USB. The thread polls the clients for + * the list of attached USB devices. + * The thread is also responsible for attaching/detaching devices + * to/from the VM. + * + * It is expected that attaching/detaching is not a frequent operation. + * + * The thread is always running when the VRDP server is active. + * + * The thread scans backends and requests the device list every 2 seconds. + * + * When device list is available, the thread calls the Console to process it. + * + */ +#define VRDP_DEVICE_LIST_PERIOD_MS (2000) + +#ifdef VBOX_WITH_USB +static DECLCALLBACK(int) threadRemoteUSB(RTTHREAD self, void *pvUser) +{ + ConsoleVRDPServer *pOwner = (ConsoleVRDPServer *)pvUser; + + LogFlow(("Console::threadRemoteUSB: start. owner = %p.\n", pOwner)); + + pOwner->notifyRemoteUSBThreadRunning(self); + + while (pOwner->isRemoteUSBThreadRunning()) + { + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + while ((pRemoteUSBBackend = pOwner->usbBackendGetNext(pRemoteUSBBackend)) != NULL) + { + pRemoteUSBBackend->PollRemoteDevices(); + } + + pOwner->waitRemoteUSBThreadEvent(VRDP_DEVICE_LIST_PERIOD_MS); + + LogFlow(("Console::threadRemoteUSB: iteration. owner = %p.\n", pOwner)); + } + + return VINF_SUCCESS; +} + +void ConsoleVRDPServer::notifyRemoteUSBThreadRunning(RTTHREAD thread) +{ + mUSBBackends.thread = thread; + mUSBBackends.fThreadRunning = true; + int vrc = RTThreadUserSignal(thread); + AssertRC(vrc); +} + +bool ConsoleVRDPServer::isRemoteUSBThreadRunning(void) +{ + return mUSBBackends.fThreadRunning; +} + +void ConsoleVRDPServer::waitRemoteUSBThreadEvent(RTMSINTERVAL cMillies) +{ + int vrc = RTSemEventWait(mUSBBackends.event, cMillies); + Assert(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT); + NOREF(vrc); +} + +void ConsoleVRDPServer::remoteUSBThreadStart(void) +{ + int vrc = RTSemEventCreate(&mUSBBackends.event); + + if (RT_FAILURE(vrc)) + { + AssertFailed(); + mUSBBackends.event = 0; + } + + if (RT_SUCCESS(vrc)) + { + vrc = RTThreadCreate(&mUSBBackends.thread, threadRemoteUSB, this, 65536, + RTTHREADTYPE_VRDP_IO, RTTHREADFLAGS_WAITABLE, "remote usb"); + } + + if (RT_FAILURE(vrc)) + { + LogRel(("Warning: could not start the remote USB thread, vrc = %Rrc!!!\n", vrc)); + mUSBBackends.thread = NIL_RTTHREAD; + } + else + { + /* Wait until the thread is ready. */ + vrc = RTThreadUserWait(mUSBBackends.thread, 60000); + AssertRC(vrc); + Assert (mUSBBackends.fThreadRunning || RT_FAILURE(vrc)); + } +} + +void ConsoleVRDPServer::remoteUSBThreadStop(void) +{ + mUSBBackends.fThreadRunning = false; + + if (mUSBBackends.thread != NIL_RTTHREAD) + { + Assert (mUSBBackends.event != 0); + + RTSemEventSignal(mUSBBackends.event); + + int vrc = RTThreadWait(mUSBBackends.thread, 60000, NULL); + AssertRC(vrc); + + mUSBBackends.thread = NIL_RTTHREAD; + } + + if (mUSBBackends.event) + { + RTSemEventDestroy(mUSBBackends.event); + mUSBBackends.event = 0; + } +} +#endif /* VBOX_WITH_USB */ + +AuthResult ConsoleVRDPServer::Authenticate(const Guid &uuid, AuthGuestJudgement guestJudgement, + const char *pszUser, const char *pszPassword, const char *pszDomain, + uint32_t u32ClientId) +{ + LogFlowFunc(("uuid = %RTuuid, guestJudgement = %d, pszUser = %s, pszPassword = %s, pszDomain = %s, u32ClientId = %d\n", + uuid.raw(), guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId)); + + AuthResult result = AuthResultAccessDenied; + +#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC + try + { + /* Init auth parameters. Order is important. */ + SafeArray<BSTR> authParams; + Bstr("VRDEAUTH" ).detachTo(authParams.appendedRaw()); + Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw()); + BstrFmt("%u", guestJudgement).detachTo(authParams.appendedRaw()); + Bstr(pszUser ).detachTo(authParams.appendedRaw()); + Bstr(pszPassword ).detachTo(authParams.appendedRaw()); + Bstr(pszDomain ).detachTo(authParams.appendedRaw()); + BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw()); + + Bstr authResult; + HRESULT hr = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams), + authResult.asOutParam()); + LogFlowFunc(("%Rhrc [%ls]\n", hr, authResult.raw())); + + size_t cbPassword = RTUtf16Len((PRTUTF16)authParams[4]) * sizeof(RTUTF16); + if (cbPassword) + RTMemWipeThoroughly(authParams[4], cbPassword, 10 /* cPasses */); + + if (SUCCEEDED(hr) && authResult == "granted") + result = AuthResultAccessGranted; + } + catch (std::bad_alloc &) + { + } +#else + /* + * Called only from VRDP input thread. So thread safety is not required. + */ + + if (!mAuthLibCtx.hAuthLibrary) + { + /* Load the external authentication library. */ + Bstr authLibrary; + mConsole->i_getVRDEServer()->COMGETTER(AuthLibrary)(authLibrary.asOutParam()); + + Utf8Str filename = authLibrary; + + int vrc = AuthLibLoad(&mAuthLibCtx, filename.c_str()); + if (RT_FAILURE(vrc)) + { + mConsole->setErrorBoth(E_FAIL, vrc, tr("Could not load the external authentication library '%s' (%Rrc)"), + filename.c_str(), vrc); + return AuthResultAccessDenied; + } + } + + result = AuthLibAuthenticate(&mAuthLibCtx, + uuid.raw(), guestJudgement, + pszUser, pszPassword, pszDomain, + u32ClientId); +#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */ + + switch (result) + { + case AuthResultAccessDenied: + LogRel(("AUTH: external authentication module returned 'access denied'\n")); + break; + case AuthResultAccessGranted: + LogRel(("AUTH: external authentication module returned 'access granted'\n")); + break; + case AuthResultDelegateToGuest: + LogRel(("AUTH: external authentication module returned 'delegate request to guest'\n")); + break; + default: + LogRel(("AUTH: external authentication module returned incorrect return code %d\n", result)); + result = AuthResultAccessDenied; + } + + LogFlowFunc(("result = %d\n", result)); + + return result; +} + +void ConsoleVRDPServer::AuthDisconnect(const Guid &uuid, uint32_t u32ClientId) +{ + LogFlow(("ConsoleVRDPServer::AuthDisconnect: uuid = %RTuuid, u32ClientId = %d\n", + uuid.raw(), u32ClientId)); + +#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC + try + { + /* Init auth parameters. Order is important. */ + SafeArray<BSTR> authParams; + Bstr("VRDEAUTHDISCONNECT").detachTo(authParams.appendedRaw()); + Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw()); + BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw()); + + Bstr authResult; + HRESULT hrc = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams), + authResult.asOutParam()); + LogFlowFunc(("%Rhrc [%ls]\n", hrc, authResult.raw())); NOREF(hrc); + } + catch (std::bad_alloc &) + { + } +#else + AuthLibDisconnect(&mAuthLibCtx, uuid.raw(), u32ClientId); +#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */ +} + +int ConsoleVRDPServer::lockConsoleVRDPServer(void) +{ + int vrc = RTCritSectEnter(&mCritSect); + AssertRC(vrc); + return vrc; +} + +void ConsoleVRDPServer::unlockConsoleVRDPServer(void) +{ + RTCritSectLeave(&mCritSect); +} + +DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, + uint32_t u32ClientId, + uint32_t u32Function, + uint32_t u32Format, + const void *pvData, + uint32_t cbData) +{ + LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n", + pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData)); + + int vrc = VINF_SUCCESS; + + ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvCallback); + + RT_NOREF(u32ClientId); + + switch (u32Function) + { + case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE: + { + if (pServer->mpfnClipboardCallback) + { + vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, + u32Format, + (void *)pvData, + cbData); + } + } break; + + case VRDE_CLIPBOARD_FUNCTION_DATA_READ: + { + if (pServer->mpfnClipboardCallback) + { + vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, + u32Format, + (void *)pvData, + cbData); + } + } break; + + default: + { + vrc = VERR_NOT_SUPPORTED; + } break; + } + + return vrc; +} + +/*static*/ DECLCALLBACK(int) +ConsoleVRDPServer::ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms) +{ + RT_NOREF(cbParms); + LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n", + pvExtension, u32Function, pvParms, cbParms)); + + int vrc = VINF_SUCCESS; + + ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvExtension); + + SHCLEXTPARMS *pParms = (SHCLEXTPARMS *)pvParms; + + switch (u32Function) + { + case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: + { + pServer->mpfnClipboardCallback = pParms->u.pfnCallback; + } break; + + case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE: + { + /* The guest announces clipboard formats. This must be delivered to all clients. */ + if (mpEntryPoints && pServer->mhServer) + { + mpEntryPoints->VRDEClipboard(pServer->mhServer, + VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE, + pParms->uFormat, + NULL, + 0, + NULL); + } + } break; + + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + { + /* The clipboard service expects that the pvData buffer will be filled + * with clipboard data. The server returns the data from the client that + * announced the requested format most recently. + */ + if (mpEntryPoints && pServer->mhServer) + { + mpEntryPoints->VRDEClipboard(pServer->mhServer, + VRDE_CLIPBOARD_FUNCTION_DATA_READ, + pParms->uFormat, + pParms->u.pvData, + pParms->cbData, + &pParms->cbData); + } + } break; + + case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + { + if (mpEntryPoints && pServer->mhServer) + { + mpEntryPoints->VRDEClipboard(pServer->mhServer, + VRDE_CLIPBOARD_FUNCTION_DATA_WRITE, + pParms->uFormat, + pParms->u.pvData, + pParms->cbData, + NULL); + } + } break; + + default: + vrc = VERR_NOT_SUPPORTED; + } + + return vrc; +} + +void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId) +{ + RT_NOREF(u32ClientId); + + int vrc = lockConsoleVRDPServer(); + if (RT_SUCCESS(vrc)) + { + if (mcClipboardRefs == 0) + { + vrc = HGCMHostRegisterServiceExtension(&mhClipboard, "VBoxSharedClipboard", ClipboardServiceExtension, this); + AssertRC(vrc); + } + + mcClipboardRefs++; + unlockConsoleVRDPServer(); + } +} + +void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId) +{ + RT_NOREF(u32ClientId); + + int vrc = lockConsoleVRDPServer(); + if (RT_SUCCESS(vrc)) + { + Assert(mcClipboardRefs); + if (mcClipboardRefs > 0) + { + mcClipboardRefs--; + + if (mcClipboardRefs == 0 && mhClipboard) + { + HGCMHostUnregisterServiceExtension(mhClipboard); + mhClipboard = NULL; + } + } + + unlockConsoleVRDPServer(); + } +} + +/* That is called on INPUT thread of the VRDP server. + * The ConsoleVRDPServer keeps a list of created backend instances. + */ +void ConsoleVRDPServer::USBBackendCreate(uint32_t u32ClientId, void **ppvIntercept) +{ +#ifdef VBOX_WITH_USB + LogFlow(("ConsoleVRDPServer::USBBackendCreate: u32ClientId = %d\n", u32ClientId)); + + /* Create a new instance of the USB backend for the new client. */ + RemoteUSBBackend *pRemoteUSBBackend = new RemoteUSBBackend(mConsole, this, u32ClientId); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->AddRef(); /* 'Release' called in USBBackendDelete. */ + + /* Append the new instance in the list. */ + int vrc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(vrc)) + { + pRemoteUSBBackend->pNext = mUSBBackends.pHead; + if (mUSBBackends.pHead) + { + mUSBBackends.pHead->pPrev = pRemoteUSBBackend; + } + else + { + mUSBBackends.pTail = pRemoteUSBBackend; + } + + mUSBBackends.pHead = pRemoteUSBBackend; + + unlockConsoleVRDPServer(); + + if (ppvIntercept) + { + *ppvIntercept = pRemoteUSBBackend; + } + } + + if (RT_FAILURE(vrc)) + { + pRemoteUSBBackend->Release(); + } + } +#else + RT_NOREF(u32ClientId, ppvIntercept); +#endif /* VBOX_WITH_USB */ +} + +void ConsoleVRDPServer::USBBackendDelete(uint32_t u32ClientId) +{ +#ifdef VBOX_WITH_USB + LogFlow(("ConsoleVRDPServer::USBBackendDelete: u32ClientId = %d\n", u32ClientId)); + + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + /* Find the instance. */ + int vrc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(vrc)) + { + pRemoteUSBBackend = usbBackendFind(u32ClientId); + + if (pRemoteUSBBackend) + { + /* Notify that it will be deleted. */ + pRemoteUSBBackend->NotifyDelete(); + } + + unlockConsoleVRDPServer(); + } + + if (pRemoteUSBBackend) + { + /* Here the instance has been excluded from the list and can be dereferenced. */ + pRemoteUSBBackend->Release(); + } +#else + RT_NOREF(u32ClientId); +#endif +} + +void *ConsoleVRDPServer::USBBackendRequestPointer(uint32_t u32ClientId, const Guid *pGuid) +{ +#ifdef VBOX_WITH_USB + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + /* Find the instance. */ + int vrc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(vrc)) + { + pRemoteUSBBackend = usbBackendFind(u32ClientId); + + if (pRemoteUSBBackend) + { + /* Inform the backend instance that it is referenced by the Guid. */ + bool fAdded = pRemoteUSBBackend->addUUID(pGuid); + + if (fAdded) + { + /* Reference the instance because its pointer is being taken. */ + pRemoteUSBBackend->AddRef(); /* 'Release' is called in USBBackendReleasePointer. */ + } + else + { + pRemoteUSBBackend = NULL; + } + } + + unlockConsoleVRDPServer(); + } + + if (pRemoteUSBBackend) + { + return pRemoteUSBBackend->GetBackendCallbackPointer(); + } +#else + RT_NOREF(u32ClientId, pGuid); +#endif + return NULL; +} + +void ConsoleVRDPServer::USBBackendReleasePointer(const Guid *pGuid) +{ +#ifdef VBOX_WITH_USB + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + /* Find the instance. */ + int vrc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(vrc)) + { + pRemoteUSBBackend = usbBackendFindByUUID(pGuid); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->removeUUID(pGuid); + } + + unlockConsoleVRDPServer(); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->Release(); + } + } +#else + RT_NOREF(pGuid); +#endif +} + +RemoteUSBBackend *ConsoleVRDPServer::usbBackendGetNext(RemoteUSBBackend *pRemoteUSBBackend) +{ + LogFlow(("ConsoleVRDPServer::usbBackendGetNext: pBackend = %p\n", pRemoteUSBBackend)); + + RemoteUSBBackend *pNextRemoteUSBBackend = NULL; +#ifdef VBOX_WITH_USB + + int vrc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(vrc)) + { + if (pRemoteUSBBackend == NULL) + { + /* The first backend in the list is requested. */ + pNextRemoteUSBBackend = mUSBBackends.pHead; + } + else + { + /* Get pointer to the next backend. */ + pNextRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext; + } + + if (pNextRemoteUSBBackend) + { + pNextRemoteUSBBackend->AddRef(); + } + + unlockConsoleVRDPServer(); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->Release(); + } + } +#endif + + return pNextRemoteUSBBackend; +} + +#ifdef VBOX_WITH_USB +/* Internal method. Called under the ConsoleVRDPServerLock. */ +RemoteUSBBackend *ConsoleVRDPServer::usbBackendFind(uint32_t u32ClientId) +{ + RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead; + + while (pRemoteUSBBackend) + { + if (pRemoteUSBBackend->ClientId() == u32ClientId) + { + break; + } + + pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext; + } + + return pRemoteUSBBackend; +} + +/* Internal method. Called under the ConsoleVRDPServerLock. */ +RemoteUSBBackend *ConsoleVRDPServer::usbBackendFindByUUID(const Guid *pGuid) +{ + RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead; + + while (pRemoteUSBBackend) + { + if (pRemoteUSBBackend->findUUID(pGuid)) + { + break; + } + + pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext; + } + + return pRemoteUSBBackend; +} +#endif + +/* Internal method. Called by the backend destructor. */ +void ConsoleVRDPServer::usbBackendRemoveFromList(RemoteUSBBackend *pRemoteUSBBackend) +{ +#ifdef VBOX_WITH_USB + int vrc = lockConsoleVRDPServer(); + AssertRC(vrc); + + /* Exclude the found instance from the list. */ + if (pRemoteUSBBackend->pNext) + { + pRemoteUSBBackend->pNext->pPrev = pRemoteUSBBackend->pPrev; + } + else + { + mUSBBackends.pTail = (RemoteUSBBackend *)pRemoteUSBBackend->pPrev; + } + + if (pRemoteUSBBackend->pPrev) + { + pRemoteUSBBackend->pPrev->pNext = pRemoteUSBBackend->pNext; + } + else + { + mUSBBackends.pHead = (RemoteUSBBackend *)pRemoteUSBBackend->pNext; + } + + pRemoteUSBBackend->pNext = pRemoteUSBBackend->pPrev = NULL; + + unlockConsoleVRDPServer(); +#else + RT_NOREF(pRemoteUSBBackend); +#endif +} + + +void ConsoleVRDPServer::SendUpdate(unsigned uScreenId, void *pvUpdate, uint32_t cbUpdate) const +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEUpdate(mhServer, uScreenId, pvUpdate, cbUpdate); + } +} + +void ConsoleVRDPServer::SendResize(void) +{ + if (mpEntryPoints && mhServer) + { + ++mcInResize; + mpEntryPoints->VRDEResize(mhServer); + --mcInResize; + } +} + +void ConsoleVRDPServer::SendUpdateBitmap(unsigned uScreenId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) const +{ + VRDEORDERHDR update; + update.x = (uint16_t)x; + update.y = (uint16_t)y; + update.w = (uint16_t)w; + update.h = (uint16_t)h; + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEUpdate(mhServer, uScreenId, &update, sizeof(update)); + } +} + +void ConsoleVRDPServer::SendAudioSamples(void const *pvSamples, uint32_t cSamples, VRDEAUDIOFORMAT format) const +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEAudioSamples(mhServer, pvSamples, cSamples, format); + } +} + +void ConsoleVRDPServer::SendAudioVolume(uint16_t left, uint16_t right) const +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEAudioVolume(mhServer, left, right); + } +} + +void ConsoleVRDPServer::SendUSBRequest(uint32_t u32ClientId, void *pvParms, uint32_t cbParms) const +{ + if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEUSBRequest(mhServer, u32ClientId, pvParms, cbParms); + } +} + +int ConsoleVRDPServer::SendAudioInputBegin(void **ppvUserCtx, + void *pvContext, + uint32_t cSamples, + uint32_t iSampleHz, + uint32_t cChannels, + uint32_t cBits) +{ + if ( mhServer + && mpEntryPoints && mpEntryPoints->VRDEAudioInOpen) + { + uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId); + if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */ + { + VRDEAUDIOFORMAT audioFormat = VRDE_AUDIO_FMT_MAKE(iSampleHz, cChannels, cBits, 0); + mpEntryPoints->VRDEAudioInOpen(mhServer, + pvContext, + u32ClientId, + audioFormat, + cSamples); + if (ppvUserCtx) + *ppvUserCtx = NULL; /* This is the ConsoleVRDPServer context. + * Currently not used because only one client is allowed to + * do audio input and the client ID is saved by the ConsoleVRDPServer. + */ + return VINF_SUCCESS; + } + } + + /* + * Not supported or no client connected. + */ + return VERR_NOT_SUPPORTED; +} + +void ConsoleVRDPServer::SendAudioInputEnd(void *pvUserCtx) +{ + RT_NOREF(pvUserCtx); + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEAudioInClose) + { + uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId); + if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */ + { + mpEntryPoints->VRDEAudioInClose(mhServer, u32ClientId); + } + } +} + +void ConsoleVRDPServer::QueryInfo(uint32_t index, void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut) const +{ + if (index == VRDE_QI_PORT) + { + uint32_t cbOut = sizeof(int32_t); + + if (cbBuffer >= cbOut) + { + *pcbOut = cbOut; + *(int32_t *)pvBuffer = (int32_t)mVRDPBindPort; + } + } + else if (mpEntryPoints && mhServer) + { + mpEntryPoints->VRDEQueryInfo(mhServer, index, pvBuffer, cbBuffer, pcbOut); + } +} + +/* static */ int ConsoleVRDPServer::loadVRDPLibrary(const char *pszLibraryName) +{ + int vrc = VINF_SUCCESS; + + if (mVRDPLibrary == NIL_RTLDRMOD) + { + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + if (RTPathHavePath(pszLibraryName)) + vrc = SUPR3HardenedLdrLoadPlugIn(pszLibraryName, &mVRDPLibrary, &ErrInfo.Core); + else + vrc = SUPR3HardenedLdrLoadAppPriv(pszLibraryName, &mVRDPLibrary, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core); + if (RT_SUCCESS(vrc)) + { + struct SymbolEntry + { + const char *name; + void **ppfn; + }; + + #define DEFSYMENTRY(a) { #a, (void**)&mpfn##a } + + static const struct SymbolEntry s_aSymbols[] = + { + DEFSYMENTRY(VRDECreateServer) + }; + + #undef DEFSYMENTRY + + for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++) + { + vrc = RTLdrGetSymbol(mVRDPLibrary, s_aSymbols[i].name, s_aSymbols[i].ppfn); + + if (RT_FAILURE(vrc)) + { + LogRel(("VRDE: Error resolving symbol '%s', vrc %Rrc.\n", s_aSymbols[i].name, vrc)); + break; + } + } + } + else + { + if (RTErrInfoIsSet(&ErrInfo.Core)) + LogRel(("VRDE: Error loading the library '%s': %s (%Rrc)\n", pszLibraryName, ErrInfo.Core.pszMsg, vrc)); + else + LogRel(("VRDE: Error loading the library '%s' vrc = %Rrc.\n", pszLibraryName, vrc)); + + mVRDPLibrary = NIL_RTLDRMOD; + } + } + + if (RT_FAILURE(vrc)) + { + if (mVRDPLibrary != NIL_RTLDRMOD) + { + RTLdrClose(mVRDPLibrary); + mVRDPLibrary = NIL_RTLDRMOD; + } + } + + return vrc; +} + +/* + * IVRDEServerInfo implementation. + */ +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +VRDEServerInfo::VRDEServerInfo() + : mParent(NULL) +{ +} + +VRDEServerInfo::~VRDEServerInfo() +{ +} + + +HRESULT VRDEServerInfo::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void VRDEServerInfo::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the guest object. + */ +HRESULT VRDEServerInfo::init(Console *aParent) +{ + LogFlowThisFunc(("aParent=%p\n", aParent)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void VRDEServerInfo::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unconst(mParent) = NULL; +} + +// IVRDEServerInfo properties +///////////////////////////////////////////////////////////////////////////// + +#define IMPL_GETTER_BOOL(_aType, _aName, _aIndex) \ + HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \ + { \ + /** @todo Not sure if a AutoReadLock would be sufficient. */ \ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \ + \ + uint32_t value; \ + uint32_t cbOut = 0; \ + \ + mParent->i_consoleVRDPServer()->QueryInfo \ + (_aIndex, &value, sizeof(value), &cbOut); \ + \ + *a##_aName = cbOut? !!value: FALSE; \ + \ + return S_OK; \ + } \ + extern void IMPL_GETTER_BOOL_DUMMY(void) + +#define IMPL_GETTER_SCALAR(_aType, _aName, _aIndex, _aValueMask) \ + HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \ + { \ + /** @todo Not sure if a AutoReadLock would be sufficient. */ \ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \ + \ + _aType value; \ + uint32_t cbOut = 0; \ + \ + mParent->i_consoleVRDPServer()->QueryInfo \ + (_aIndex, &value, sizeof(value), &cbOut); \ + \ + if (_aValueMask) value &= (_aValueMask); \ + *a##_aName = cbOut? value: 0; \ + \ + return S_OK; \ + } \ + extern void IMPL_GETTER_SCALAR_DUMMY(void) + +#define IMPL_GETTER_UTF8STR(_aType, _aName, _aIndex) \ + HRESULT VRDEServerInfo::get##_aName(_aType &a##_aName) \ + { \ + /** @todo Not sure if a AutoReadLock would be sufficient. */ \ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \ + \ + uint32_t cbOut = 0; \ + \ + mParent->i_consoleVRDPServer()->QueryInfo \ + (_aIndex, NULL, 0, &cbOut); \ + \ + if (cbOut == 0) \ + { \ + a##_aName = Utf8Str::Empty; \ + return S_OK; \ + } \ + \ + char *pchBuffer = (char *)RTMemTmpAlloc(cbOut); \ + \ + if (!pchBuffer) \ + { \ + Log(("VRDEServerInfo::" \ + #_aName \ + ": Failed to allocate memory %d bytes\n", cbOut)); \ + return E_OUTOFMEMORY; \ + } \ + \ + mParent->i_consoleVRDPServer()->QueryInfo \ + (_aIndex, pchBuffer, cbOut, &cbOut); \ + \ + a##_aName = pchBuffer; \ + \ + RTMemTmpFree(pchBuffer); \ + \ + return S_OK; \ + } \ + extern void IMPL_GETTER_BSTR_DUMMY(void) + +IMPL_GETTER_BOOL (BOOL, Active, VRDE_QI_ACTIVE); +IMPL_GETTER_SCALAR (LONG, Port, VRDE_QI_PORT, 0); +IMPL_GETTER_SCALAR (ULONG, NumberOfClients, VRDE_QI_NUMBER_OF_CLIENTS, 0); +IMPL_GETTER_SCALAR (LONG64, BeginTime, VRDE_QI_BEGIN_TIME, 0); +IMPL_GETTER_SCALAR (LONG64, EndTime, VRDE_QI_END_TIME, 0); +IMPL_GETTER_SCALAR (LONG64, BytesSent, VRDE_QI_BYTES_SENT, INT64_MAX); +IMPL_GETTER_SCALAR (LONG64, BytesSentTotal, VRDE_QI_BYTES_SENT_TOTAL, INT64_MAX); +IMPL_GETTER_SCALAR (LONG64, BytesReceived, VRDE_QI_BYTES_RECEIVED, INT64_MAX); +IMPL_GETTER_SCALAR (LONG64, BytesReceivedTotal, VRDE_QI_BYTES_RECEIVED_TOTAL, INT64_MAX); +IMPL_GETTER_UTF8STR(Utf8Str, User, VRDE_QI_USER); +IMPL_GETTER_UTF8STR(Utf8Str, Domain, VRDE_QI_DOMAIN); +IMPL_GETTER_UTF8STR(Utf8Str, ClientName, VRDE_QI_CLIENT_NAME); +IMPL_GETTER_UTF8STR(Utf8Str, ClientIP, VRDE_QI_CLIENT_IP); +IMPL_GETTER_SCALAR (ULONG, ClientVersion, VRDE_QI_CLIENT_VERSION, 0); +IMPL_GETTER_SCALAR (ULONG, EncryptionStyle, VRDE_QI_ENCRYPTION_STYLE, 0); + +#undef IMPL_GETTER_UTF8STR +#undef IMPL_GETTER_SCALAR +#undef IMPL_GETTER_BOOL +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/DisplayImpl.cpp b/src/VBox/Main/src-client/DisplayImpl.cpp new file mode 100644 index 00000000..03dfd137 --- /dev/null +++ b/src/VBox/Main/src-client/DisplayImpl.cpp @@ -0,0 +1,3872 @@ +/* $Id: DisplayImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY +#include "LoggingNew.h" + +#include "DisplayImpl.h" +#include "DisplayUtils.h" +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" +#include "GuestImpl.h" +#include "VMMDev.h" + +#include "AutoCaller.h" + +/* generated header */ +#include "VBoxEvents.h" + +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/asm.h> +#include <iprt/time.h> +#include <iprt/cpp/utils.h> +#include <iprt/alloca.h> + +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/vmm/pdmdrv.h> + +#ifdef VBOX_WITH_VIDEOHWACCEL +# include <VBoxVideo.h> +#endif +#include <VBoxVideo3D.h> + +#include <VBox/com/array.h> + +#ifdef VBOX_WITH_RECORDING +# include <iprt/path.h> +# include "Recording.h" + +# include <VBox/vmm/pdmapi.h> +# include <VBox/vmm/pdmaudioifs.h> +#endif + +/** + * Display driver instance data. + * + * @implements PDMIDISPLAYCONNECTOR + */ +typedef struct DRVMAINDISPLAY +{ + /** Pointer to the display object. */ + Display *pDisplay; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the display port interface of the driver/device above us. */ + PPDMIDISPLAYPORT pUpPort; + /** Our display connector interface. */ + PDMIDISPLAYCONNECTOR IConnector; +#if defined(VBOX_WITH_VIDEOHWACCEL) + /** VBVA callbacks */ + PPDMIDISPLAYVBVACALLBACKS pVBVACallbacks; +#endif +} DRVMAINDISPLAY, *PDRVMAINDISPLAY; + +/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */ +#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) RT_FROM_MEMBER(pInterface, DRVMAINDISPLAY, IConnector) + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +Display::Display() + : mParent(NULL) +{ +} + +Display::~Display() +{ +} + + +HRESULT Display::FinalConstruct() +{ + int vrc = videoAccelConstruct(&mVideoAccelLegacy); + AssertRC(vrc); + + mfVideoAccelVRDP = false; + mfu32SupportedOrders = 0; + mcVRDPRefs = 0; + + mfSeamlessEnabled = false; + mpRectVisibleRegion = NULL; + mcRectVisibleRegion = 0; + + mpDrv = NULL; + + vrc = RTCritSectInit(&mVideoAccelLock); + AssertRC(vrc); + +#ifdef VBOX_WITH_HGSMI + mu32UpdateVBVAFlags = 0; + mfVMMDevSupportsGraphics = false; + mfGuestVBVACapabilities = 0; + mfHostCursorCapabilities = 0; +#endif + +#ifdef VBOX_WITH_RECORDING + vrc = RTCritSectInit(&mVideoRecLock); + AssertRC(vrc); + + for (unsigned i = 0; i < RT_ELEMENTS(maRecordingEnabled); i++) + maRecordingEnabled[i] = true; +#endif + + return BaseFinalConstruct(); +} + +void Display::FinalRelease() +{ + uninit(); + +#ifdef VBOX_WITH_RECORDING + if (RTCritSectIsInitialized(&mVideoRecLock)) + { + RTCritSectDelete(&mVideoRecLock); + RT_ZERO(mVideoRecLock); + } +#endif + + videoAccelDestroy(&mVideoAccelLegacy); + i_saveVisibleRegion(0, NULL); + + if (RTCritSectIsInitialized(&mVideoAccelLock)) + { + RTCritSectDelete(&mVideoAccelLock); + RT_ZERO(mVideoAccelLock); + } + + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +#define kMaxSizeThumbnail 64 + +/** + * Save thumbnail and screenshot of the guest screen. + */ +static int displayMakeThumbnail(uint8_t *pbData, uint32_t cx, uint32_t cy, + uint8_t **ppu8Thumbnail, uint32_t *pcbThumbnail, uint32_t *pcxThumbnail, uint32_t *pcyThumbnail) +{ + int vrc = VINF_SUCCESS; + + uint8_t *pu8Thumbnail = NULL; + uint32_t cbThumbnail = 0; + uint32_t cxThumbnail = 0; + uint32_t cyThumbnail = 0; + + if (cx > cy) + { + cxThumbnail = kMaxSizeThumbnail; + cyThumbnail = (kMaxSizeThumbnail * cy) / cx; + } + else + { + cyThumbnail = kMaxSizeThumbnail; + cxThumbnail = (kMaxSizeThumbnail * cx) / cy; + } + + LogRelFlowFunc(("%dx%d -> %dx%d\n", cx, cy, cxThumbnail, cyThumbnail)); + + cbThumbnail = cxThumbnail * 4 * cyThumbnail; + pu8Thumbnail = (uint8_t *)RTMemAlloc(cbThumbnail); + + if (pu8Thumbnail) + { + uint8_t *dst = pu8Thumbnail; + uint8_t *src = pbData; + int dstW = cxThumbnail; + int dstH = cyThumbnail; + int srcW = cx; + int srcH = cy; + int iDeltaLine = cx * 4; + + BitmapScale32(dst, + dstW, dstH, + src, + iDeltaLine, + srcW, srcH); + + *ppu8Thumbnail = pu8Thumbnail; + *pcbThumbnail = cbThumbnail; + *pcxThumbnail = cxThumbnail; + *pcyThumbnail = cyThumbnail; + } + else + { + vrc = VERR_NO_MEMORY; + } + + return vrc; +} + +/** + * @callback_method_impl{FNSSMEXTSAVEEXEC} + */ +DECLCALLBACK(int) Display::i_displaySSMSaveScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser) +{ + Display * const pThat = static_cast<Display *>(pvUser); + AssertPtrReturn(pThat, VERR_INVALID_POINTER); + + /* 32bpp small RGB image. */ + uint8_t *pu8Thumbnail = NULL; + uint32_t cbThumbnail = 0; + uint32_t cxThumbnail = 0; + uint32_t cyThumbnail = 0; + + /* PNG screenshot. */ + uint8_t *pu8PNG = NULL; + uint32_t cbPNG = 0; + uint32_t cxPNG = 0; + uint32_t cyPNG = 0; + + Console::SafeVMPtr ptrVM(pThat->mParent); + if (ptrVM.isOk()) + { + /* Query RGB bitmap. */ + /* SSM code is executed on EMT(0), therefore no need to use VMR3ReqCallWait. */ + uint8_t *pbData = NULL; + size_t cbData = 0; + uint32_t cx = 0; + uint32_t cy = 0; + bool fFreeMem = false; + int vrc = Display::i_displayTakeScreenshotEMT(pThat, VBOX_VIDEO_PRIMARY_SCREEN, &pbData, &cbData, &cx, &cy, &fFreeMem); + + /* + * It is possible that success is returned but everything is 0 or NULL. + * (no display attached if a VM is running with VBoxHeadless on OSE for example) + */ + if (RT_SUCCESS(vrc) && pbData) + { + Assert(cx && cy); + + /* Prepare a small thumbnail and a PNG screenshot. */ + displayMakeThumbnail(pbData, cx, cy, &pu8Thumbnail, &cbThumbnail, &cxThumbnail, &cyThumbnail); + vrc = DisplayMakePNG(pbData, cx, cy, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 1); + if (RT_FAILURE(vrc)) + { + if (pu8PNG) + { + RTMemFree(pu8PNG); + pu8PNG = NULL; + } + cbPNG = 0; + cxPNG = 0; + cyPNG = 0; + } + + if (fFreeMem) + RTMemFree(pbData); + else + pThat->mpDrv->pUpPort->pfnFreeScreenshot(pThat->mpDrv->pUpPort, pbData); + } + } + else + { + LogFunc(("Failed to get VM pointer 0x%x\n", ptrVM.rc())); + } + + /* Regardless of rc, save what is available: + * Data format: + * uint32_t cBlocks; + * [blocks] + * + * Each block is: + * uint32_t cbBlock; if 0 - no 'block data'. + * uint32_t typeOfBlock; 0 - 32bpp RGB bitmap, 1 - PNG, ignored if 'cbBlock' is 0. + * [block data] + * + * Block data for bitmap and PNG: + * uint32_t cx; + * uint32_t cy; + * [image data] + */ + pVMM->pfnSSMR3PutU32(pSSM, 2); /* Write thumbnail and PNG screenshot. */ + + /* First block. */ + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbThumbnail + 2 * sizeof(uint32_t))); + pVMM->pfnSSMR3PutU32(pSSM, 0); /* Block type: thumbnail. */ + + if (cbThumbnail) + { + pVMM->pfnSSMR3PutU32(pSSM, cxThumbnail); + pVMM->pfnSSMR3PutU32(pSSM, cyThumbnail); + pVMM->pfnSSMR3PutMem(pSSM, pu8Thumbnail, cbThumbnail); + } + + /* Second block. */ + pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbPNG + 2 * sizeof(uint32_t))); + pVMM->pfnSSMR3PutU32(pSSM, 1); /* Block type: png. */ + + if (cbPNG) + { + pVMM->pfnSSMR3PutU32(pSSM, cxPNG); + pVMM->pfnSSMR3PutU32(pSSM, cyPNG); + pVMM->pfnSSMR3PutMem(pSSM, pu8PNG, cbPNG); + } + + RTMemFree(pu8PNG); + RTMemFree(pu8Thumbnail); + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMEXTLOADEXEC} + */ +DECLCALLBACK(int) +Display::i_displaySSMLoadScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + Display * const pThat = static_cast<Display *>(pvUser); + AssertPtrReturn(pThat, VERR_INVALID_POINTER); + Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass); + + if (uVersion != sSSMDisplayScreenshotVer) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + /* Skip data. */ + uint32_t cBlocks; + int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cBlocks); + AssertRCReturn(vrc, vrc); + + for (uint32_t i = 0; i < cBlocks; i++) + { + uint32_t cbBlock; + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbBlock); + AssertRCReturn(vrc, vrc); + + uint32_t typeOfBlock; + vrc = pVMM->pfnSSMR3GetU32(pSSM, &typeOfBlock); + AssertRCReturn(vrc, vrc); + + LogRelFlowFunc(("[%d] type %d, size %d bytes\n", i, typeOfBlock, cbBlock)); + + /* Note: displaySSMSaveScreenshot writes size of a block = 8 and + * do not write any data if the image size was 0. + */ + /** @todo Fix and increase saved state version. */ + if (cbBlock > 2 * sizeof(uint32_t)) + { + vrc = pVMM->pfnSSMR3Skip(pSSM, cbBlock); + AssertRCReturn(vrc, vrc); + } + } + + return vrc; +} + +/** + * @callback_method_impl{FNSSMEXTSAVEEXEC, Save some important guest state} + */ +/*static*/ DECLCALLBACK(int) +Display::i_displaySSMSave(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser) +{ + Display * const pThat = static_cast<Display *>(pvUser); + AssertPtrReturn(pThat, VERR_INVALID_POINTER); + + pVMM->pfnSSMR3PutU32(pSSM, pThat->mcMonitors); + for (unsigned i = 0; i < pThat->mcMonitors; i++) + { + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32Offset); + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32MaxFramebufferSize); + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32InformationSize); + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].w); + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].h); + pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].xOrigin); + pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].yOrigin); + pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].flags); + } + pVMM->pfnSSMR3PutS32(pSSM, pThat->xInputMappingOrigin); + pVMM->pfnSSMR3PutS32(pSSM, pThat->yInputMappingOrigin); + pVMM->pfnSSMR3PutU32(pSSM, pThat->cxInputMapping); + pVMM->pfnSSMR3PutU32(pSSM, pThat->cyInputMapping); + pVMM->pfnSSMR3PutU32(pSSM, pThat->mfGuestVBVACapabilities); + return pVMM->pfnSSMR3PutU32(pSSM, pThat->mfHostCursorCapabilities); +} + +/** + * @callback_method_impl{FNSSMEXTLOADEXEC, Load some important guest state} + */ +/*static*/ DECLCALLBACK(int) +Display::i_displaySSMLoad(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + Display * const pThat = static_cast<Display *>(pvUser); + AssertPtrReturn(pThat, VERR_INVALID_POINTER); + + if ( uVersion != sSSMDisplayVer + && uVersion != sSSMDisplayVer2 + && uVersion != sSSMDisplayVer3 + && uVersion != sSSMDisplayVer4 + && uVersion != sSSMDisplayVer5) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + uint32_t cMonitors; + int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cMonitors); + AssertRCReturn(vrc, vrc); + if (cMonitors != pThat->mcMonitors) + return pVMM->pfnSSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Number of monitors changed (%d->%d)!"), cMonitors, pThat->mcMonitors); + + for (uint32_t i = 0; i < cMonitors; i++) + { + pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32Offset); + pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32MaxFramebufferSize); + pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32InformationSize); + if ( uVersion == sSSMDisplayVer2 + || uVersion == sSSMDisplayVer3 + || uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + uint32_t w; + uint32_t h; + pVMM->pfnSSMR3GetU32(pSSM, &w); + vrc = pVMM->pfnSSMR3GetU32(pSSM, &h); + AssertRCReturn(vrc, vrc); + pThat->maFramebuffers[i].w = w; + pThat->maFramebuffers[i].h = h; + } + if ( uVersion == sSSMDisplayVer3 + || uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + int32_t xOrigin; + int32_t yOrigin; + uint32_t flags; + pVMM->pfnSSMR3GetS32(pSSM, &xOrigin); + pVMM->pfnSSMR3GetS32(pSSM, &yOrigin); + vrc = pVMM->pfnSSMR3GetU32(pSSM, &flags); + AssertRCReturn(vrc, vrc); + pThat->maFramebuffers[i].xOrigin = xOrigin; + pThat->maFramebuffers[i].yOrigin = yOrigin; + pThat->maFramebuffers[i].flags = (uint16_t)flags; + pThat->maFramebuffers[i].fDisabled = (pThat->maFramebuffers[i].flags & VBVA_SCREEN_F_DISABLED) != 0; + } + } + if ( uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + pVMM->pfnSSMR3GetS32(pSSM, &pThat->xInputMappingOrigin); + pVMM->pfnSSMR3GetS32(pSSM, &pThat->yInputMappingOrigin); + pVMM->pfnSSMR3GetU32(pSSM, &pThat->cxInputMapping); + pVMM->pfnSSMR3GetU32(pSSM, &pThat->cyInputMapping); + } + if (uVersion == sSSMDisplayVer5) + { + pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfGuestVBVACapabilities); + pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfHostCursorCapabilities); + } + + return VINF_SUCCESS; +} + +/** + * Initializes the display object. + * + * @returns COM result indicator + * @param aParent handle of our parent object + */ +HRESULT Display::init(Console *aParent) +{ + ComAssertRet(aParent, E_INVALIDARG); + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + mfSourceBitmapEnabled = true; + fVGAResizing = false; + + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + HRESULT hrc = mParent->i_machine()->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + AssertComRCReturnRC(hrc); + AssertReturn(!pGraphicsAdapter.isNull(), E_FAIL); + + ULONG ul; + pGraphicsAdapter->COMGETTER(MonitorCount)(&ul); + mcMonitors = ul; + xInputMappingOrigin = 0; + yInputMappingOrigin = 0; + cxInputMapping = 0; + cyInputMapping = 0; + + for (ul = 0; ul < mcMonitors; ul++) + { + maFramebuffers[ul].u32Offset = 0; + maFramebuffers[ul].u32MaxFramebufferSize = 0; + maFramebuffers[ul].u32InformationSize = 0; + + maFramebuffers[ul].pFramebuffer = NULL; + /* All secondary monitors are disabled at startup. */ + maFramebuffers[ul].fDisabled = ul > 0; + + maFramebuffers[ul].u32Caps = 0; + + maFramebuffers[ul].updateImage.pu8Address = NULL; + maFramebuffers[ul].updateImage.cbLine = 0; + + maFramebuffers[ul].xOrigin = 0; + maFramebuffers[ul].yOrigin = 0; + + maFramebuffers[ul].w = 0; + maFramebuffers[ul].h = 0; + + maFramebuffers[ul].flags = maFramebuffers[ul].fDisabled? VBVA_SCREEN_F_DISABLED: 0; + + maFramebuffers[ul].u16BitsPerPixel = 0; + maFramebuffers[ul].pu8FramebufferVRAM = NULL; + maFramebuffers[ul].u32LineSize = 0; + + maFramebuffers[ul].pHostEvents = NULL; + + maFramebuffers[ul].fDefaultFormat = false; + +#ifdef VBOX_WITH_HGSMI + maFramebuffers[ul].fVBVAEnabled = false; + maFramebuffers[ul].fVBVAForceResize = false; + maFramebuffers[ul].pVBVAHostFlags = NULL; +#endif /* VBOX_WITH_HGSMI */ + } + + { + // register listener for state change events + ComPtr<IEventSource> es; + mParent->COMGETTER(EventSource)(es.asOutParam()); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnStateChanged); + es->RegisterListener(this, ComSafeArrayAsInParam(eventTypes), true); + } + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void Display::uninit() +{ + LogRelFlowFunc(("this=%p\n", this)); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unsigned uScreenId; + for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++) + { + maFramebuffers[uScreenId].pSourceBitmap.setNull(); + maFramebuffers[uScreenId].updateImage.pSourceBitmap.setNull(); + maFramebuffers[uScreenId].updateImage.pu8Address = NULL; + maFramebuffers[uScreenId].updateImage.cbLine = 0; + maFramebuffers[uScreenId].pFramebuffer.setNull(); +#ifdef VBOX_WITH_RECORDING + maFramebuffers[uScreenId].Recording.pSourceBitmap.setNull(); +#endif + } + + if (mParent) + { + ComPtr<IEventSource> es; + mParent->COMGETTER(EventSource)(es.asOutParam()); + es->UnregisterListener(this); + } + + unconst(mParent) = NULL; + + if (mpDrv) + mpDrv->pDisplay = NULL; + + mpDrv = NULL; +} + +/** + * Register the SSM methods. Called by the power up thread to be able to + * pass pVM + */ +int Display::i_registerSSM(PUVM pUVM) +{ + PCVMMR3VTABLE const pVMM = mParent->i_getVMMVTable(); + AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3); + + /* Version 2 adds width and height of the framebuffer; version 3 adds + * the framebuffer offset in the virtual desktop and the framebuffer flags; + * version 4 adds guest to host input event mapping and version 5 adds + * guest VBVA and host cursor capabilities. + */ + int vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 0, sSSMDisplayVer5, + mcMonitors * sizeof(uint32_t) * 8 + sizeof(uint32_t), + NULL, NULL, NULL, + NULL, i_displaySSMSave, NULL, + NULL, i_displaySSMLoad, NULL, this); + AssertRCReturn(vrc, vrc); + + /* + * Register loaders for old saved states where iInstance was + * 3 * sizeof(uint32_t *) due to a code mistake. + */ + vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 12 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, i_displaySSMLoad, NULL, this); + AssertRCReturn(vrc, vrc); + + vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 24 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, i_displaySSMLoad, NULL, this); + AssertRCReturn(vrc, vrc); + + /* uInstance is an arbitrary value greater than 1024. Such a value will ensure a quick seek in saved state file. */ + vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayScreenshot", 1100 /*uInstance*/, sSSMDisplayScreenshotVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, i_displaySSMSaveScreenshot, NULL, + NULL, i_displaySSMLoadScreenshot, NULL, this); + + AssertRCReturn(vrc, vrc); + + return VINF_SUCCESS; +} + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Handles display resize event. + * + * @param uScreenId Screen ID + * @param bpp New bits per pixel. + * @param pvVRAM VRAM pointer. + * @param cbLine New bytes per line. + * @param w New display width. + * @param h New display height. + * @param flags Flags of the new video mode. + * @param xOrigin New display origin X. + * @param yOrigin New display origin Y. + * @param fVGAResize Whether the resize is originated from the VGA device (DevVGA). + */ +int Display::i_handleDisplayResize(unsigned uScreenId, uint32_t bpp, void *pvVRAM, + uint32_t cbLine, uint32_t w, uint32_t h, uint16_t flags, + int32_t xOrigin, int32_t yOrigin, bool fVGAResize) +{ + LogRel2(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X\n", uScreenId, + pvVRAM, w, h, bpp, cbLine, flags)); + + /* Caller must not hold the object lock. */ + AssertReturn(!isWriteLockOnCurrentThread(), VERR_INVALID_STATE); + + /* Note: the old code checked if the video mode was actually changed and + * did not invalidate the source bitmap if the mode did not change. + * The new code always invalidates the source bitmap, i.e. it will + * notify the frontend even if nothing actually changed. + * + * Implementing the filtering is possible but might lead to pfnSetRenderVRAM races + * between this method and QuerySourceBitmap. Such races can be avoided by implementing + * the @todo below. + */ + + /* Make sure that the VGA device does not access the source bitmap. */ + if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN && mpDrv) + { + /// @todo It is probably more convenient to implement + // mpDrv->pUpPort->pfnSetOutputBitmap(pvVRAM, cbScanline, cBits, cx, cy, bool fSet); + // and remove IConnector.pbData, cbScanline, cBits, cx, cy. + // fSet = false disables rendering and VGA can check + // if it is already rendering to a different bitmap, avoiding + // enable/disable rendering races. + mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, false); + + mpDrv->IConnector.pbData = NULL; + mpDrv->IConnector.cbScanline = 0; + mpDrv->IConnector.cBits = 32; /* DevVGA does not work with cBits == 0. */ + mpDrv->IConnector.cx = 0; + mpDrv->IConnector.cy = 0; + } + + /* Update maFramebuffers[uScreenId] under lock. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (uScreenId >= mcMonitors) + { + LogRel(("Display::i_handleDisplayResize: mcMonitors=%u < uScreenId=%u (pvVRAM=%p w=%u h=%u bpp=%d cbLine=0x%X flags=0x%X)\n", + mcMonitors, uScreenId, pvVRAM, w, h, bpp, cbLine, flags)); + return VINF_SUCCESS; + } + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + + /* Whether the monitor position has changed. + * A resize initiated by the VGA device does not change the monitor position. + */ + const bool fNewOrigin = !fVGAResize + && ( pFBInfo->xOrigin != xOrigin + || pFBInfo->yOrigin != yOrigin); + + /* The event for disabled->enabled transition. + * VGA resizes also come when the guest uses VBVA mode. They do not affect pFBInfo->fDisabled. + * The primary screen is re-enabled when the guest leaves the VBVA mode in i_displayVBVADisable. + */ + const bool fGuestMonitorChangedEvent = !fVGAResize + && (pFBInfo->fDisabled != RT_BOOL(flags & VBVA_SCREEN_F_DISABLED)); + + /* Reset the update mode. */ + pFBInfo->updateImage.pSourceBitmap.setNull(); + pFBInfo->updateImage.pu8Address = NULL; + pFBInfo->updateImage.cbLine = 0; + + /* Release the current source bitmap. */ + pFBInfo->pSourceBitmap.setNull(); + + /* VGA blanking is signaled as w=0, h=0, bpp=0 and cbLine=0, and it's + * best to keep the old resolution, as otherwise the window size would + * change before the new resolution is known. */ + const bool fVGABlank = fVGAResize && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN + && w == 0 && h == 0 && bpp == 0 && cbLine == 0; + if (fVGABlank) + { + w = pFBInfo->w; + h = pFBInfo->h; + } + + /* Log changes. */ + if ( pFBInfo->w != w + || pFBInfo->h != h + || pFBInfo->u32LineSize != cbLine + /*|| pFBInfo->pu8FramebufferVRAM != (uint8_t *)pvVRAM - too noisy */ + || ( !fVGAResize + && ( pFBInfo->xOrigin != xOrigin + || pFBInfo->yOrigin != yOrigin + || pFBInfo->flags != flags))) + LogRel(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X origin=%d,%d\n", + uScreenId, pvVRAM, w, h, bpp, cbLine, flags, xOrigin, yOrigin)); + + /* Update the video mode information. */ + pFBInfo->w = w; + pFBInfo->h = h; + pFBInfo->u16BitsPerPixel = (uint16_t)bpp; + pFBInfo->pu8FramebufferVRAM = (uint8_t *)pvVRAM; + pFBInfo->u32LineSize = cbLine; + if (!fVGAResize) + { + /* Fields which are not used in not VBVA modes and not affected by a VGA resize. */ + pFBInfo->flags = flags; + pFBInfo->xOrigin = xOrigin; + pFBInfo->yOrigin = yOrigin; + pFBInfo->fDisabled = RT_BOOL(flags & VBVA_SCREEN_F_DISABLED); + pFBInfo->fVBVAForceResize = false; + } + else + { + pFBInfo->flags = VBVA_SCREEN_F_ACTIVE; + if (fVGABlank) + pFBInfo->flags |= VBVA_SCREEN_F_BLANK; + pFBInfo->fDisabled = false; + } + + /* Prepare local vars for the notification code below. */ + ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer; + const bool fDisabled = pFBInfo->fDisabled; + + alock.release(); + + if (!pFramebuffer.isNull()) + { + HRESULT hr = pFramebuffer->NotifyChange(uScreenId, 0, 0, w, h); /** @todo origin */ + LogFunc(("NotifyChange hr %08X\n", hr)); + NOREF(hr); + } + + if (fGuestMonitorChangedEvent) + { + if (fDisabled) + ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), + GuestMonitorChangedEventType_Disabled, uScreenId, 0, 0, 0, 0); + else + ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), + GuestMonitorChangedEventType_Enabled, uScreenId, xOrigin, yOrigin, w, h); + } + + if (fNewOrigin) + ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), + GuestMonitorChangedEventType_NewOrigin, uScreenId, xOrigin, yOrigin, 0, 0); + + /* Inform the VRDP server about the change of display parameters. */ + LogRelFlowFunc(("Calling VRDP\n")); + mParent->i_consoleVRDPServer()->SendResize(); + + /* And re-send the seamless rectangles if necessary. */ + if (mfSeamlessEnabled) + i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion); + +#ifdef VBOX_WITH_RECORDING + i_recordingScreenChanged(uScreenId); +#endif + + LogRelFlowFunc(("[%d]: default format %d\n", uScreenId, pFBInfo->fDefaultFormat)); + + return VINF_SUCCESS; +} + +static void i_checkCoordBounds(int *px, int *py, int *pw, int *ph, int cx, int cy) +{ + /* Correct negative x and y coordinates. */ + if (*px < 0) + { + *px += *pw; /* Compute xRight which is also the new width. */ + + *pw = (*px < 0)? 0: *px; + + *px = 0; + } + + if (*py < 0) + { + *py += *ph; /* Compute xBottom, which is also the new height. */ + + *ph = (*py < 0)? 0: *py; + + *py = 0; + } + + /* Also check if coords are greater than the display resolution. */ + if (*px + *pw > cx) + { + *pw = cx > *px? cx - *px: 0; + } + + if (*py + *ph > cy) + { + *ph = cy > *py? cy - *py: 0; + } +} + +void Display::i_handleDisplayUpdate(unsigned uScreenId, int x, int y, int w, int h) +{ + /* + * Always runs under either VBVA lock or, for HGSMI, DevVGA lock. + * Safe to use VBVA vars and take the framebuffer lock. + */ + +#ifdef DEBUG_sunlover + LogFlowFunc(("[%d] %d,%d %dx%d\n", + uScreenId, x, y, w, h)); +#endif /* DEBUG_sunlover */ + + /* No updates for a disabled guest screen. */ + if (maFramebuffers[uScreenId].fDisabled) + return; + + /* No updates for a blank guest screen. */ + /** @note Disabled for now, as the GUI does not update the picture when we + * first blank. */ + /* if (maFramebuffers[uScreenId].flags & VBVA_SCREEN_F_BLANK) + return; */ + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + AutoReadLock alockr(this COMMA_LOCKVAL_SRC_POS); + + ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer; + ComPtr<IDisplaySourceBitmap> pSourceBitmap = pFBInfo->updateImage.pSourceBitmap; + + alockr.release(); + + if (RT_LIKELY(!pFramebuffer.isNull())) + { + if (RT_LIKELY(!RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_UpdateImage))) + { + i_checkCoordBounds(&x, &y, &w, &h, pFBInfo->w, pFBInfo->h); + + if (w != 0 && h != 0) + { + pFramebuffer->NotifyUpdate(x, y, w, h); + } + } + else + { + if (RT_LIKELY(!pSourceBitmap.isNull())) + { /* likely */ } + else + { + /* Create a source bitmap if UpdateImage mode is used. */ + HRESULT hr = QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam()); + if (SUCCEEDED(hr)) + { + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + hr = pSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hr)) + { + AutoWriteLock alockw(this COMMA_LOCKVAL_SRC_POS); + + if (pFBInfo->updateImage.pSourceBitmap.isNull()) + { + pFBInfo->updateImage.pSourceBitmap = pSourceBitmap; + pFBInfo->updateImage.pu8Address = pAddress; + pFBInfo->updateImage.cbLine = ulBytesPerLine; + } + + pSourceBitmap = pFBInfo->updateImage.pSourceBitmap; + + alockw.release(); + } + } + } + + if (RT_LIKELY(!pSourceBitmap.isNull())) + { + BYTE *pbAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + HRESULT hr = pSourceBitmap->QueryBitmapInfo(&pbAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hr)) + { + /* Make sure that the requested update is within the source bitmap dimensions. */ + i_checkCoordBounds(&x, &y, &w, &h, ulWidth, ulHeight); + + if (w != 0 && h != 0) + { + const size_t cbData = w * h * 4; + com::SafeArray<BYTE> image(cbData); + + uint8_t *pu8Dst = image.raw(); + const uint8_t *pu8Src = pbAddress + ulBytesPerLine * y + x * 4; + + int i; + for (i = y; i < y + h; ++i) + { + memcpy(pu8Dst, pu8Src, w * 4); + pu8Dst += w * 4; + pu8Src += ulBytesPerLine; + } + + pFramebuffer->NotifyUpdateImage(x, y, w, h, ComSafeArrayAsInParam(image)); + } + } + } + } + } + +#ifndef VBOX_WITH_HGSMI + if (!mVideoAccelLegacy.fVideoAccelEnabled) +#else + if (!mVideoAccelLegacy.fVideoAccelEnabled && !maFramebuffers[uScreenId].fVBVAEnabled) +#endif + { + /* When VBVA is enabled, the VRDP server is informed + * either in VideoAccelFlush or displayVBVAUpdateProcess. + * Inform the server here only if VBVA is disabled. + */ + mParent->i_consoleVRDPServer()->SendUpdateBitmap(uScreenId, x, y, w, h); + } +} + +void Display::i_updateGuestGraphicsFacility(void) +{ + Guest* pGuest = mParent->i_getGuest(); + AssertPtrReturnVoid(pGuest); + /* The following is from GuestImpl.cpp. */ + /** @todo A nit: The timestamp is wrong on saved state restore. Would be better + * to move the graphics and seamless capability -> facility translation to + * VMMDev so this could be saved. */ + RTTIMESPEC TimeSpecTS; + RTTimeNow(&TimeSpecTS); + + if ( mfVMMDevSupportsGraphics + || (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS) != 0) + pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics, + VBoxGuestFacilityStatus_Active, + 0 /*fFlags*/, &TimeSpecTS); + else + pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics, + VBoxGuestFacilityStatus_Inactive, + 0 /*fFlags*/, &TimeSpecTS); +} + +void Display::i_handleUpdateVMMDevSupportsGraphics(bool fSupportsGraphics) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (mfVMMDevSupportsGraphics == fSupportsGraphics) + return; + mfVMMDevSupportsGraphics = fSupportsGraphics; + i_updateGuestGraphicsFacility(); + /* The VMMDev interface notifies the console. */ +} + +void Display::i_handleUpdateGuestVBVACapabilities(uint32_t fNewCapabilities) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + bool fNotify = (fNewCapabilities & VBVACAPS_VIDEO_MODE_HINTS) != (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS); + + mfGuestVBVACapabilities = fNewCapabilities; + if (!fNotify) + return; + i_updateGuestGraphicsFacility(); + /* Tell the console about it */ + mParent->i_onAdditionsStateChange(); +} + +void Display::i_handleUpdateVBVAInputMapping(int32_t xOrigin, int32_t yOrigin, uint32_t cx, uint32_t cy) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + xInputMappingOrigin = xOrigin; + yInputMappingOrigin = yOrigin; + cxInputMapping = cx; + cyInputMapping = cy; + + /* Re-send the seamless rectangles if necessary. */ + if (mfSeamlessEnabled) + i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion); +} + +/** + * Returns the upper left and lower right corners of the virtual framebuffer. + * The lower right is "exclusive" (i.e. first pixel beyond the framebuffer), + * and the origin is (0, 0), not (1, 1) like the GUI returns. + */ +void Display::i_getFramebufferDimensions(int32_t *px1, int32_t *py1, + int32_t *px2, int32_t *py2) +{ + int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertPtrReturnVoid(px1); + AssertPtrReturnVoid(py1); + AssertPtrReturnVoid(px2); + AssertPtrReturnVoid(py2); + LogRelFlowFunc(("\n")); + + if (!mpDrv) + return; + + if (maFramebuffers[0].fVBVAEnabled && cxInputMapping && cyInputMapping) + { + /* Guest uses VBVA with explicit mouse mapping dimensions. */ + x1 = xInputMappingOrigin; + y1 = yInputMappingOrigin; + x2 = xInputMappingOrigin + cxInputMapping; + y2 = yInputMappingOrigin + cyInputMapping; + } + else + { + /* If VBVA is not in use then this flag will not be set and this + * will still work as it should. */ + if (!maFramebuffers[0].fDisabled) + { + x1 = (int32_t)maFramebuffers[0].xOrigin; + y1 = (int32_t)maFramebuffers[0].yOrigin; + x2 = (int32_t)maFramebuffers[0].w + (int32_t)maFramebuffers[0].xOrigin; + y2 = (int32_t)maFramebuffers[0].h + (int32_t)maFramebuffers[0].yOrigin; + } + + for (unsigned i = 1; i < mcMonitors; ++i) + { + if (!maFramebuffers[i].fDisabled) + { + x1 = RT_MIN(x1, maFramebuffers[i].xOrigin); + y1 = RT_MIN(y1, maFramebuffers[i].yOrigin); + x2 = RT_MAX(x2, maFramebuffers[i].xOrigin + (int32_t)maFramebuffers[i].w); + y2 = RT_MAX(y2, maFramebuffers[i].yOrigin + (int32_t)maFramebuffers[i].h); + } + } + } + + *px1 = x1; + *py1 = y1; + *px2 = x2; + *py2 = y2; +} + +/** Updates the device's view of the host cursor handling capabilities. + * Calls into mpDrv->pUpPort. */ +void Display::i_UpdateDeviceCursorCapabilities(void) +{ + bool fRenderCursor = true; + bool fMoveCursor = mcVRDPRefs == 0; +#ifdef VBOX_WITH_RECORDING + RecordingContext *pCtx = mParent->i_recordingGetContext(); + + if ( pCtx + && pCtx->IsStarted() + && pCtx->IsFeatureEnabled(RecordingFeature_Video)) + fRenderCursor = fMoveCursor = false; + else +#endif /* VBOX_WITH_RECORDING */ + { + for (unsigned uScreenId = 0; uScreenId < mcMonitors; uScreenId++) + { + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + if (!(pFBInfo->u32Caps & FramebufferCapabilities_RenderCursor)) + fRenderCursor = false; + if (!(pFBInfo->u32Caps & FramebufferCapabilities_MoveCursor)) + fMoveCursor = false; + } + } + + if (mpDrv) + mpDrv->pUpPort->pfnReportHostCursorCapabilities(mpDrv->pUpPort, fRenderCursor, fMoveCursor); +} + +HRESULT Display::i_reportHostCursorCapabilities(uint32_t fCapabilitiesAdded, uint32_t fCapabilitiesRemoved) +{ + /* Do we need this to access mParent? I presume that the safe VM pointer + * ensures that mpDrv will remain valid. */ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + uint32_t fHostCursorCapabilities = (mfHostCursorCapabilities | fCapabilitiesAdded) + & ~fCapabilitiesRemoved; + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + if (mfHostCursorCapabilities == fHostCursorCapabilities) + return S_OK; + CHECK_CONSOLE_DRV(mpDrv); + alock.release(); /* Release before calling up for lock order reasons. */ + mfHostCursorCapabilities = fHostCursorCapabilities; + i_UpdateDeviceCursorCapabilities(); + return S_OK; +} + +HRESULT Display::i_reportHostCursorPosition(int32_t x, int32_t y, bool fOutOfRange) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + uint32_t xAdj = (uint32_t)RT_MAX(x - xInputMappingOrigin, 0); + uint32_t yAdj = (uint32_t)RT_MAX(y - yInputMappingOrigin, 0); + xAdj = RT_MIN(xAdj, cxInputMapping); + yAdj = RT_MIN(yAdj, cyInputMapping); + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + CHECK_CONSOLE_DRV(mpDrv); + alock.release(); /* Release before calling up for lock order reasons. */ + if (fOutOfRange) + mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, 0, 0, true); + else + mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, xAdj, yAdj, false); + return S_OK; +} + +static bool displayIntersectRect(RTRECT *prectResult, + const RTRECT *prect1, + const RTRECT *prect2) +{ + /* Initialize result to an empty record. */ + memset(prectResult, 0, sizeof(RTRECT)); + + int xLeftResult = RT_MAX(prect1->xLeft, prect2->xLeft); + int xRightResult = RT_MIN(prect1->xRight, prect2->xRight); + + if (xLeftResult < xRightResult) + { + /* There is intersection by X. */ + + int yTopResult = RT_MAX(prect1->yTop, prect2->yTop); + int yBottomResult = RT_MIN(prect1->yBottom, prect2->yBottom); + + if (yTopResult < yBottomResult) + { + /* There is intersection by Y. */ + + prectResult->xLeft = xLeftResult; + prectResult->yTop = yTopResult; + prectResult->xRight = xRightResult; + prectResult->yBottom = yBottomResult; + + return true; + } + } + + return false; +} + +int Display::i_saveVisibleRegion(uint32_t cRect, PRTRECT pRect) +{ + RTRECT *pRectVisibleRegion = NULL; + + if (pRect == mpRectVisibleRegion) + return VINF_SUCCESS; + if (cRect != 0) + { + pRectVisibleRegion = (RTRECT *)RTMemAlloc(cRect * sizeof(RTRECT)); + if (!pRectVisibleRegion) + { + return VERR_NO_MEMORY; + } + memcpy(pRectVisibleRegion, pRect, cRect * sizeof(RTRECT)); + } + if (mpRectVisibleRegion) + RTMemFree(mpRectVisibleRegion); + mcRectVisibleRegion = cRect; + mpRectVisibleRegion = pRectVisibleRegion; + return VINF_SUCCESS; +} + +int Display::i_handleSetVisibleRegion(uint32_t cRect, PRTRECT pRect) +{ + RTRECT *pVisibleRegion = (RTRECT *)RTMemTmpAlloc( RT_MAX(cRect, 1) + * sizeof(RTRECT)); + LogRel2(("%s: cRect=%u\n", __PRETTY_FUNCTION__, cRect)); + if (!pVisibleRegion) + { + return VERR_NO_TMP_MEMORY; + } + int vrc = i_saveVisibleRegion(cRect, pRect); + if (RT_FAILURE(vrc)) + { + RTMemTmpFree(pVisibleRegion); + return vrc; + } + + unsigned uScreenId; + for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++) + { + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + + if ( !pFBInfo->pFramebuffer.isNull() + && RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_VisibleRegion)) + { + /* Prepare a new array of rectangles which intersect with the framebuffer. + */ + RTRECT rectFramebuffer; + rectFramebuffer.xLeft = pFBInfo->xOrigin - xInputMappingOrigin; + rectFramebuffer.yTop = pFBInfo->yOrigin - yInputMappingOrigin; + rectFramebuffer.xRight = rectFramebuffer.xLeft + pFBInfo->w; + rectFramebuffer.yBottom = rectFramebuffer.yTop + pFBInfo->h; + + uint32_t cRectVisibleRegion = 0; + + uint32_t i; + for (i = 0; i < cRect; i++) + { + if (displayIntersectRect(&pVisibleRegion[cRectVisibleRegion], &pRect[i], &rectFramebuffer)) + { + pVisibleRegion[cRectVisibleRegion].xLeft -= rectFramebuffer.xLeft; + pVisibleRegion[cRectVisibleRegion].yTop -= rectFramebuffer.yTop; + pVisibleRegion[cRectVisibleRegion].xRight -= rectFramebuffer.xLeft; + pVisibleRegion[cRectVisibleRegion].yBottom -= rectFramebuffer.yTop; + + cRectVisibleRegion++; + } + } + pFBInfo->pFramebuffer->SetVisibleRegion((BYTE *)pVisibleRegion, cRectVisibleRegion); + } + } + + RTMemTmpFree(pVisibleRegion); + + return VINF_SUCCESS; +} + +int Display::i_handleUpdateMonitorPositions(uint32_t cPositions, PCRTPOINT paPositions) +{ + AssertMsgReturn(paPositions, ("Empty monitor position array\n"), E_INVALIDARG); + for (unsigned i = 0; i < cPositions; ++i) + LogRel2(("Display::i_handleUpdateMonitorPositions: uScreenId=%d xOrigin=%d yOrigin=%dX\n", + i, paPositions[i].x, paPositions[i].y)); + + if (mpDrv && mpDrv->pUpPort->pfnReportMonitorPositions) + mpDrv->pUpPort->pfnReportMonitorPositions(mpDrv->pUpPort, cPositions, paPositions); + return VINF_SUCCESS; +} + +int Display::i_handleQueryVisibleRegion(uint32_t *pcRects, PRTRECT paRects) +{ + /// @todo Currently not used by the guest and is not implemented in + /// framebuffers. Remove? + RT_NOREF(pcRects, paRects); + return VERR_NOT_SUPPORTED; +} + +#ifdef VBOX_WITH_HGSMI +static void vbvaSetMemoryFlagsHGSMI(unsigned uScreenId, + uint32_t fu32SupportedOrders, + bool fVideoAccelVRDP, + DISPLAYFBINFO *pFBInfo) +{ + LogRelFlowFunc(("HGSMI[%d]: %p\n", uScreenId, pFBInfo->pVBVAHostFlags)); + + if (pFBInfo->pVBVAHostFlags) + { + uint32_t fu32HostEvents = VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET; + + if (pFBInfo->fVBVAEnabled) + { + fu32HostEvents |= VBVA_F_MODE_ENABLED; + + if (fVideoAccelVRDP) + { + fu32HostEvents |= VBVA_F_MODE_VRDP; + } + } + + ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32HostEvents, fu32HostEvents); + ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32SupportedOrders, fu32SupportedOrders); + + LogRelFlowFunc((" fu32HostEvents = 0x%08X, fu32SupportedOrders = 0x%08X\n", fu32HostEvents, fu32SupportedOrders)); + } +} + +static void vbvaSetMemoryFlagsAllHGSMI(uint32_t fu32SupportedOrders, + bool fVideoAccelVRDP, + DISPLAYFBINFO *paFBInfos, + unsigned cFBInfos) +{ + unsigned uScreenId; + + for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++) + { + vbvaSetMemoryFlagsHGSMI(uScreenId, fu32SupportedOrders, fVideoAccelVRDP, &paFBInfos[uScreenId]); + } +} +#endif /* VBOX_WITH_HGSMI */ + +int Display::VideoAccelEnableVMMDev(bool fEnable, VBVAMEMORY *pVbvaMemory) +{ + LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory)); + int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy); + if (RT_SUCCESS(vrc)) + { + vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort); + videoAccelLeaveVMMDev(&mVideoAccelLegacy); + } + LogFlowFunc(("leave %Rrc\n", vrc)); + return vrc; +} + +int Display::VideoAccelEnableVGA(bool fEnable, VBVAMEMORY *pVbvaMemory) +{ + LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory)); + int vrc = videoAccelEnterVGA(&mVideoAccelLegacy); + if (RT_SUCCESS(vrc)) + { + vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort); + videoAccelLeaveVGA(&mVideoAccelLegacy); + } + LogFlowFunc(("leave %Rrc\n", vrc)); + return vrc; +} + +void Display::VideoAccelFlushVMMDev(void) +{ + LogFlowFunc(("enter\n")); + int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy); + if (RT_SUCCESS(vrc)) + { + i_VideoAccelFlush(mpDrv->pUpPort); + videoAccelLeaveVMMDev(&mVideoAccelLegacy); + } + LogFlowFunc(("leave\n")); +} + +/* Called always by one VRDP server thread. Can be thread-unsafe. + */ +void Display::i_VRDPConnectionEvent(bool fConnect) +{ + LogRelFlowFunc(("fConnect = %d\n", fConnect)); + + int c = fConnect? + ASMAtomicIncS32(&mcVRDPRefs): + ASMAtomicDecS32(&mcVRDPRefs); + + i_VideoAccelVRDP(fConnect, c); + i_UpdateDeviceCursorCapabilities(); +} + + +void Display::i_VideoAccelVRDP(bool fEnable, int c) +{ + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + + Assert (c >= 0); + RT_NOREF(fEnable); + + /* This can run concurrently with Display videoaccel state change. */ + RTCritSectEnter(&mVideoAccelLock); + + if (c == 0) + { + /* The last client has disconnected, and the accel can be + * disabled. + */ + Assert(fEnable == false); + + mfVideoAccelVRDP = false; + mfu32SupportedOrders = 0; + + i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders, + maFramebuffers, mcMonitors); +#ifdef VBOX_WITH_HGSMI + /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */ + ASMAtomicIncU32(&mu32UpdateVBVAFlags); +#endif /* VBOX_WITH_HGSMI */ + + LogRel(("VBVA: VRDP acceleration has been disabled.\n")); + } + else if ( c == 1 + && !mfVideoAccelVRDP) + { + /* The first client has connected. Enable the accel. + */ + Assert(fEnable == true); + + mfVideoAccelVRDP = true; + /* Supporting all orders. */ + mfu32SupportedOrders = UINT32_MAX; + + i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders, + maFramebuffers, mcMonitors); +#ifdef VBOX_WITH_HGSMI + /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */ + ASMAtomicIncU32(&mu32UpdateVBVAFlags); +#endif /* VBOX_WITH_HGSMI */ + + LogRel(("VBVA: VRDP acceleration has been requested.\n")); + } + else + { + /* A client is connected or disconnected but there is no change in the + * accel state. It remains enabled. + */ + Assert(mfVideoAccelVRDP == true); + } + + RTCritSectLeave(&mVideoAccelLock); +} + +void Display::i_notifyPowerDown(void) +{ + LogRelFlowFunc(("\n")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Source bitmaps are not available anymore. */ + mfSourceBitmapEnabled = false; + + alock.release(); + + /* Resize all displays to tell framebuffers to forget current source bitmap. */ + unsigned uScreenId = mcMonitors; + while (uScreenId > 0) + { + --uScreenId; + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + if (!pFBInfo->fDisabled) + { + i_handleDisplayResize(uScreenId, 32, + pFBInfo->pu8FramebufferVRAM, + pFBInfo->u32LineSize, + pFBInfo->w, + pFBInfo->h, + pFBInfo->flags, + pFBInfo->xOrigin, + pFBInfo->yOrigin, + false); + } + } +} + +// Wrapped IDisplay methods +///////////////////////////////////////////////////////////////////////////// +HRESULT Display::getScreenResolution(ULONG aScreenId, ULONG *aWidth, ULONG *aHeight, ULONG *aBitsPerPixel, + LONG *aXOrigin, LONG *aYOrigin, GuestMonitorStatus_T *aGuestMonitorStatus) +{ + LogRelFlowFunc(("aScreenId=%RU32\n", aScreenId)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aScreenId >= mcMonitors) + return E_INVALIDARG; + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId]; + + GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled; + + if (pFBInfo->flags & VBVA_SCREEN_F_DISABLED) + guestMonitorStatus = GuestMonitorStatus_Disabled; + else if (pFBInfo->flags & (VBVA_SCREEN_F_BLANK | VBVA_SCREEN_F_BLANK2)) + guestMonitorStatus = GuestMonitorStatus_Blank; + + if (aWidth) + *aWidth = pFBInfo->w; + if (aHeight) + *aHeight = pFBInfo->h; + if (aBitsPerPixel) + *aBitsPerPixel = pFBInfo->u16BitsPerPixel; + if (aXOrigin) + *aXOrigin = pFBInfo->xOrigin; + if (aYOrigin) + *aYOrigin = pFBInfo->yOrigin; + if (aGuestMonitorStatus) + *aGuestMonitorStatus = guestMonitorStatus; + + return S_OK; +} + + +HRESULT Display::attachFramebuffer(ULONG aScreenId, const ComPtr<IFramebuffer> &aFramebuffer, com::Guid &aId) +{ + LogRelFlowFunc(("aScreenId = %d\n", aScreenId)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aScreenId >= mcMonitors) + return setError(E_INVALIDARG, tr("AttachFramebuffer: Invalid screen %d (total %d)"), + aScreenId, mcMonitors); + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId]; + if (!pFBInfo->pFramebuffer.isNull()) + return setError(E_FAIL, tr("AttachFramebuffer: Framebuffer already attached to %d"), + aScreenId); + + pFBInfo->pFramebuffer = aFramebuffer; + pFBInfo->framebufferId.create(); + aId = pFBInfo->framebufferId; + + SafeArray<FramebufferCapabilities_T> caps; + pFBInfo->pFramebuffer->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(caps)); + pFBInfo->u32Caps = 0; + size_t i; + for (i = 0; i < caps.size(); ++i) + pFBInfo->u32Caps |= caps[i]; + + alock.release(); + + /* The driver might not have been constructed yet */ + if (mpDrv) + { + /* Inform the framebuffer about the actual screen size. */ + HRESULT hr = aFramebuffer->NotifyChange(aScreenId, 0, 0, pFBInfo->w, pFBInfo->h); /** @todo origin */ + LogFunc(("NotifyChange hr %08X\n", hr)); NOREF(hr); + + /* Re-send the seamless rectangles if necessary. */ + if (mfSeamlessEnabled) + i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion); + } + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, aScreenId, false); + + LogRelFlowFunc(("Attached to %d %RTuuid\n", aScreenId, aId.raw())); + return S_OK; +} + +HRESULT Display::detachFramebuffer(ULONG aScreenId, const com::Guid &aId) +{ + LogRelFlowFunc(("aScreenId = %d %RTuuid\n", aScreenId, aId.raw())); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aScreenId >= mcMonitors) + return setError(E_INVALIDARG, tr("DetachFramebuffer: Invalid screen %d (total %d)"), + aScreenId, mcMonitors); + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId]; + + if (pFBInfo->framebufferId != aId) + { + LogRelFlowFunc(("Invalid framebuffer aScreenId = %d, attached %p\n", aScreenId, pFBInfo->framebufferId.raw())); + return setError(E_FAIL, tr("DetachFramebuffer: Invalid framebuffer object")); + } + + pFBInfo->pFramebuffer.setNull(); + pFBInfo->framebufferId.clear(); + + alock.release(); + return S_OK; +} + +HRESULT Display::queryFramebuffer(ULONG aScreenId, ComPtr<IFramebuffer> &aFramebuffer) +{ + LogRelFlowFunc(("aScreenId = %d\n", aScreenId)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aScreenId >= mcMonitors) + return setError(E_INVALIDARG, tr("QueryFramebuffer: Invalid screen %d (total %d)"), + aScreenId, mcMonitors); + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId]; + + pFBInfo->pFramebuffer.queryInterfaceTo(aFramebuffer.asOutParam()); + + return S_OK; +} + +HRESULT Display::setVideoModeHint(ULONG aDisplay, BOOL aEnabled, + BOOL aChangeOrigin, LONG aOriginX, LONG aOriginY, + ULONG aWidth, ULONG aHeight, ULONG aBitsPerPixel, + BOOL aNotify) +{ + if (aWidth == 0 || aHeight == 0 || aBitsPerPixel == 0) + { + /* Some of parameters must not change. Query current mode. */ + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + HRESULT hr = getScreenResolution(aDisplay, &ulWidth, &ulHeight, &ulBitsPerPixel, NULL, NULL, NULL); + if (FAILED(hr)) + return hr; + + /* Assign current values to not changing parameters. */ + if (aWidth == 0) + aWidth = ulWidth; + if (aHeight == 0) + aHeight = ulHeight; + if (aBitsPerPixel == 0) + aBitsPerPixel = ulBitsPerPixel; + } + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aDisplay >= mcMonitors) + return E_INVALIDARG; + + VMMDevDisplayDef d; + d.idDisplay = aDisplay; + d.xOrigin = aOriginX; + d.yOrigin = aOriginY; + d.cx = aWidth; + d.cy = aHeight; + d.cBitsPerPixel = aBitsPerPixel; + d.fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP; + if (!aEnabled) + d.fDisplayFlags |= VMMDEV_DISPLAY_DISABLED; + if (aChangeOrigin) + d.fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN; + if (aDisplay == 0) + d.fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY; + + /* Remember the monitor information. */ + maFramebuffers[aDisplay].monitorDesc = d; + + CHECK_CONSOLE_DRV(mpDrv); + + /* + * It is up to the guest to decide whether the hint is + * valid. Therefore don't do any VRAM sanity checks here. + */ + + /* Have to release the lock because the pfnRequestDisplayChange + * will call EMT. */ + alock.release(); + + /* We always send the hint to the graphics card in case the guest enables + * support later. For now we notify exactly when support is enabled. */ + mpDrv->pUpPort->pfnSendModeHint(mpDrv->pUpPort, aWidth, aHeight, + aBitsPerPixel, aDisplay, + aChangeOrigin ? aOriginX : ~0, + aChangeOrigin ? aOriginY : ~0, + RT_BOOL(aEnabled), + (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS) + && aNotify); + if ( mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS + && !(mfGuestVBVACapabilities & VBVACAPS_IRQ) + && aNotify) + mParent->i_sendACPIMonitorHotPlugEvent(); + + /* We currently never suppress the VMMDev hint if the guest has requested + * it. Specifically the video graphics driver may not be responsible for + * screen positioning in the guest virtual desktop, and the component + * responsible may want to get the hint from VMMDev. */ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, 1, &d, false, RT_BOOL(aNotify)); + } + /* Notify listeners. */ + ::FireGuestMonitorInfoChangedEvent(mParent->i_getEventSource(), aDisplay); + return S_OK; +} + +HRESULT Display::getVideoModeHint(ULONG cDisplay, BOOL *pfEnabled, + BOOL *pfChangeOrigin, LONG *pxOrigin, LONG *pyOrigin, + ULONG *pcx, ULONG *pcy, ULONG *pcBitsPerPixel) +{ + if (cDisplay >= mcMonitors) + return E_INVALIDARG; + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (pfEnabled) + *pfEnabled = !( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags + & VMMDEV_DISPLAY_DISABLED); + if (pfChangeOrigin) + *pfChangeOrigin = RT_BOOL( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags + & VMMDEV_DISPLAY_ORIGIN); + if (pxOrigin) + *pxOrigin = maFramebuffers[cDisplay].monitorDesc.xOrigin; + if (pyOrigin) + *pyOrigin = maFramebuffers[cDisplay].monitorDesc.yOrigin; + if (pcx) + *pcx = maFramebuffers[cDisplay].monitorDesc.cx; + if (pcy) + *pcy = maFramebuffers[cDisplay].monitorDesc.cy; + if (pcBitsPerPixel) + *pcBitsPerPixel = maFramebuffers[cDisplay].monitorDesc.cBitsPerPixel; + return S_OK; +} + +HRESULT Display::setSeamlessMode(BOOL enabled) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Have to release the lock because the pfnRequestSeamlessChange will call EMT. */ + alock.release(); + + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + pVMMDevPort->pfnRequestSeamlessChange(pVMMDevPort, !!enabled); + } + mfSeamlessEnabled = RT_BOOL(enabled); + return S_OK; +} + +/*static*/ DECLCALLBACK(int) +Display::i_displayTakeScreenshotEMT(Display *pDisplay, ULONG aScreenId, uint8_t **ppbData, size_t *pcbData, + uint32_t *pcx, uint32_t *pcy, bool *pfMemFree) +{ + int vrc; + if ( aScreenId == VBOX_VIDEO_PRIMARY_SCREEN + && pDisplay->maFramebuffers[aScreenId].fVBVAEnabled == false) /* A non-VBVA mode. */ + { + if (pDisplay->mpDrv) + { + vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy); + *pfMemFree = false; + } + else + { + /* No image. */ + *ppbData = NULL; + *pcbData = 0; + *pcx = 0; + *pcy = 0; + *pfMemFree = true; + vrc = VINF_SUCCESS; + } + } + else if (aScreenId < pDisplay->mcMonitors) + { + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId]; + + uint32_t width = pFBInfo->w; + uint32_t height = pFBInfo->h; + + /* Allocate 32 bit per pixel bitmap. */ + size_t cbRequired = width * 4 * height; + + if (cbRequired) + { + uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRequired); + if (pbDst != NULL) + { + if (pFBInfo->flags & VBVA_SCREEN_F_ACTIVE) + { + /* Copy guest VRAM to the allocated 32bpp buffer. */ + const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM; + int32_t xSrc = 0; + int32_t ySrc = 0; + uint32_t u32SrcWidth = width; + uint32_t u32SrcHeight = height; + uint32_t u32SrcLineSize = pFBInfo->u32LineSize; + uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel; + + int32_t xDst = 0; + int32_t yDst = 0; + uint32_t u32DstWidth = u32SrcWidth; + uint32_t u32DstHeight = u32SrcHeight; + uint32_t u32DstLineSize = u32DstWidth * 4; + uint32_t u32DstBitsPerPixel = 32; + + vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort, + width, height, + pu8Src, + xSrc, ySrc, + u32SrcWidth, u32SrcHeight, + u32SrcLineSize, u32SrcBitsPerPixel, + pbDst, + xDst, yDst, + u32DstWidth, u32DstHeight, + u32DstLineSize, u32DstBitsPerPixel); + } + else + { + memset(pbDst, 0, cbRequired); + vrc = VINF_SUCCESS; + } + if (RT_SUCCESS(vrc)) + { + *ppbData = pbDst; + *pcbData = cbRequired; + *pcx = width; + *pcy = height; + *pfMemFree = true; + } + else + { + RTMemFree(pbDst); + + /* CopyRect can fail if VBVA was paused in VGA device, retry using the generic method. */ + if ( vrc == VERR_INVALID_STATE + && aScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy); + *pfMemFree = false; + } + } + } + else + vrc = VERR_NO_MEMORY; + } + else + { + /* No image. */ + *ppbData = NULL; + *pcbData = 0; + *pcx = 0; + *pcy = 0; + *pfMemFree = true; + vrc = VINF_SUCCESS; + } + } + else + vrc = VERR_INVALID_PARAMETER; + return vrc; +} + +static int i_displayTakeScreenshot(PUVM pUVM, PCVMMR3VTABLE pVMM, Display *pDisplay, struct DRVMAINDISPLAY *pDrv, + ULONG aScreenId, BYTE *address, ULONG width, ULONG height) +{ + uint8_t *pbData = NULL; + size_t cbData = 0; + uint32_t cx = 0; + uint32_t cy = 0; + bool fFreeMem = false; + int vrc = VINF_SUCCESS; + + int cRetries = 5; + while (cRetries-- > 0) + { + /* Note! Not sure if the priority call is such a good idea here, but + it would be nice to have an accurate screenshot for the bug + report if the VM deadlocks. */ + vrc = pVMM->pfnVMR3ReqPriorityCallWaitU(pUVM, VMCPUID_ANY, (PFNRT)Display::i_displayTakeScreenshotEMT, 7, + pDisplay, aScreenId, &pbData, &cbData, &cx, &cy, &fFreeMem); + if (vrc != VERR_TRY_AGAIN) + { + break; + } + + RTThreadSleep(10); + } + + if (RT_SUCCESS(vrc) && pbData) + { + if (cx == width && cy == height) + { + /* No scaling required. */ + memcpy(address, pbData, cbData); + } + else + { + /* Scale. */ + LogRelFlowFunc(("SCALE: %dx%d -> %dx%d\n", cx, cy, width, height)); + + uint8_t *dst = address; + uint8_t *src = pbData; + int dstW = width; + int dstH = height; + int srcW = cx; + int srcH = cy; + int iDeltaLine = cx * 4; + + BitmapScale32(dst, + dstW, dstH, + src, + iDeltaLine, + srcW, srcH); + } + + if (fFreeMem) + RTMemFree(pbData); + else + { + /* This can be called from any thread. */ + pDrv->pUpPort->pfnFreeScreenshot(pDrv->pUpPort, pbData); + } + } + + return vrc; +} + +HRESULT Display::takeScreenShotWorker(ULONG aScreenId, + BYTE *aAddress, + ULONG aWidth, + ULONG aHeight, + BitmapFormat_T aBitmapFormat, + ULONG *pcbOut) +{ + HRESULT hrc = S_OK; + + /* Do not allow too small and too large screenshots. This also filters out negative + * values passed as either 'aWidth' or 'aHeight'. + */ + CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767); + CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767); + + if ( aBitmapFormat != BitmapFormat_BGR0 + && aBitmapFormat != BitmapFormat_BGRA + && aBitmapFormat != BitmapFormat_RGBA + && aBitmapFormat != BitmapFormat_PNG) + { + return setError(E_NOTIMPL, + tr("Unsupported screenshot format 0x%08X"), aBitmapFormat); + } + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + int vrc = i_displayTakeScreenshot(ptrVM.rawUVM(), ptrVM.vtable(), this, mpDrv, aScreenId, aAddress, aWidth, aHeight); + if (RT_SUCCESS(vrc)) + { + const size_t cbData = aWidth * 4 * aHeight; + + /* Most of uncompressed formats. */ + *pcbOut = (ULONG)cbData; + + if (aBitmapFormat == BitmapFormat_BGR0) + { + /* Do nothing. */ + } + else if (aBitmapFormat == BitmapFormat_BGRA) + { + uint32_t *pu32 = (uint32_t *)aAddress; + size_t cPixels = aWidth * aHeight; + while (cPixels--) + *pu32++ |= UINT32_C(0xFF000000); + } + else if (aBitmapFormat == BitmapFormat_RGBA) + { + uint8_t *pu8 = aAddress; + size_t cPixels = aWidth * aHeight; + while (cPixels--) + { + uint8_t u8 = pu8[0]; + pu8[0] = pu8[2]; + pu8[2] = u8; + pu8[3] = 0xFF; + + pu8 += 4; + } + } + else if (aBitmapFormat == BitmapFormat_PNG) + { + uint8_t *pu8PNG = NULL; + uint32_t cbPNG = 0; + uint32_t cxPNG = 0; + uint32_t cyPNG = 0; + + vrc = DisplayMakePNG(aAddress, aWidth, aHeight, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 0); + if (RT_SUCCESS(vrc)) + { + if (cbPNG <= cbData) + { + memcpy(aAddress, pu8PNG, cbPNG); + *pcbOut = cbPNG; + } + else + hrc = setError(E_FAIL, tr("PNG is larger than 32bpp bitmap")); + } + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not convert screenshot to PNG (%Rrc)"), vrc); + RTMemFree(pu8PNG); + } + } + else if (vrc == VERR_TRY_AGAIN) + hrc = setErrorBoth(E_UNEXPECTED, vrc, tr("Screenshot is not available at this time")); + else if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not take a screenshot (%Rrc)"), vrc); + + return hrc; +} + +HRESULT Display::takeScreenShot(ULONG aScreenId, + BYTE *aAddress, + ULONG aWidth, + ULONG aHeight, + BitmapFormat_T aBitmapFormat) +{ + LogRelFlowFunc(("[%d] address=%p, width=%d, height=%d, format 0x%08X\n", + aScreenId, aAddress, aWidth, aHeight, aBitmapFormat)); + + ULONG cbOut = 0; + HRESULT hrc = takeScreenShotWorker(aScreenId, aAddress, aWidth, aHeight, aBitmapFormat, &cbOut); + NOREF(cbOut); + + LogRelFlowFunc(("%Rhrc\n", hrc)); + return hrc; +} + +HRESULT Display::takeScreenShotToArray(ULONG aScreenId, + ULONG aWidth, + ULONG aHeight, + BitmapFormat_T aBitmapFormat, + std::vector<BYTE> &aScreenData) +{ + LogRelFlowFunc(("[%d] width=%d, height=%d, format 0x%08X\n", + aScreenId, aWidth, aHeight, aBitmapFormat)); + + /* Do not allow too small and too large screenshots. This also filters out negative + * values passed as either 'aWidth' or 'aHeight'. + */ + CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767); + CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767); + + const size_t cbData = aWidth * 4 * aHeight; + aScreenData.resize(cbData); + + ULONG cbOut = 0; + HRESULT hrc = takeScreenShotWorker(aScreenId, &aScreenData.front(), aWidth, aHeight, aBitmapFormat, &cbOut); + if (FAILED(hrc)) + cbOut = 0; + + aScreenData.resize(cbOut); + + LogRelFlowFunc(("%Rhrc\n", hrc)); + return hrc; +} + +#ifdef VBOX_WITH_RECORDING +/** + * Invalidates the recording configuration. + * + * @returns IPRT status code. + */ +int Display::i_recordingInvalidate(void) +{ + RecordingContext *pCtx = mParent->i_recordingGetContext(); + if (!pCtx || !pCtx->IsStarted()) + return VINF_SUCCESS; + + /* + * Invalidate screens. + */ + for (unsigned uScreen = 0; uScreen < mcMonitors; uScreen++) + { + RecordingStream *pRecordingStream = pCtx->GetStream(uScreen); + + const bool fStreamEnabled = pRecordingStream->IsReady(); + bool fChanged = maRecordingEnabled[uScreen] != fStreamEnabled; + + maRecordingEnabled[uScreen] = fStreamEnabled; + + if (fChanged && uScreen < mcMonitors) + i_recordingScreenChanged(uScreen); + } + + return VINF_SUCCESS; +} + +void Display::i_recordingScreenChanged(unsigned uScreenId) +{ + RecordingContext *pCtx = mParent->i_recordingGetContext(); + + i_UpdateDeviceCursorCapabilities(); + if ( RT_LIKELY(!maRecordingEnabled[uScreenId]) + || !pCtx || !pCtx->IsStarted()) + { + /* Skip recording this screen. */ + return; + } + + /* Get a new source bitmap which will be used by video recording code. */ + ComPtr<IDisplaySourceBitmap> pSourceBitmap; + QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam()); + + int vrc2 = RTCritSectEnter(&mVideoRecLock); + if (RT_SUCCESS(vrc2)) + { + maFramebuffers[uScreenId].Recording.pSourceBitmap = pSourceBitmap; + + vrc2 = RTCritSectLeave(&mVideoRecLock); + AssertRC(vrc2); + } +} +#endif /* VBOX_WITH_RECORDING */ + +/*static*/ DECLCALLBACK(int) +Display::i_drawToScreenEMT(Display *pDisplay, ULONG aScreenId, BYTE *address, ULONG x, ULONG y, ULONG width, ULONG height) +{ + int vrc = VINF_SUCCESS; + + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId]; + + if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + vrc = pDisplay->mpDrv->pUpPort->pfnDisplayBlt(pDisplay->mpDrv->pUpPort, address, x, y, width, height); + } + else if (aScreenId < pDisplay->mcMonitors) + { + /* Copy the bitmap to the guest VRAM. */ + const uint8_t *pu8Src = address; + int32_t xSrc = 0; + int32_t ySrc = 0; + uint32_t u32SrcWidth = width; + uint32_t u32SrcHeight = height; + uint32_t u32SrcLineSize = width * 4; + uint32_t u32SrcBitsPerPixel = 32; + + uint8_t *pu8Dst = pFBInfo->pu8FramebufferVRAM; + int32_t xDst = x; + int32_t yDst = y; + uint32_t u32DstWidth = pFBInfo->w; + uint32_t u32DstHeight = pFBInfo->h; + uint32_t u32DstLineSize = pFBInfo->u32LineSize; + uint32_t u32DstBitsPerPixel = pFBInfo->u16BitsPerPixel; + + vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort, + width, height, + pu8Src, + xSrc, ySrc, + u32SrcWidth, u32SrcHeight, + u32SrcLineSize, u32SrcBitsPerPixel, + pu8Dst, + xDst, yDst, + u32DstWidth, u32DstHeight, + u32DstLineSize, u32DstBitsPerPixel); + if (RT_SUCCESS(vrc)) + { + if (!pFBInfo->pSourceBitmap.isNull()) + { + /* Update the changed screen area. When source bitmap uses VRAM directly, just notify + * frontend to update. And for default format, render the guest VRAM to the source bitmap. + */ + if ( pFBInfo->fDefaultFormat + && !pFBInfo->fDisabled) + { + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hrc)) + { + pu8Src = pFBInfo->pu8FramebufferVRAM; + xSrc = x; + ySrc = y; + u32SrcWidth = pFBInfo->w; + u32SrcHeight = pFBInfo->h; + u32SrcLineSize = pFBInfo->u32LineSize; + u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel; + + /* Default format is 32 bpp. */ + pu8Dst = pAddress; + xDst = xSrc; + yDst = ySrc; + u32DstWidth = u32SrcWidth; + u32DstHeight = u32SrcHeight; + u32DstLineSize = u32DstWidth * 4; + u32DstBitsPerPixel = 32; + + pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort, + width, height, + pu8Src, + xSrc, ySrc, + u32SrcWidth, u32SrcHeight, + u32SrcLineSize, u32SrcBitsPerPixel, + pu8Dst, + xDst, yDst, + u32DstWidth, u32DstHeight, + u32DstLineSize, u32DstBitsPerPixel); + } + } + } + + pDisplay->i_handleDisplayUpdate(aScreenId, x, y, width, height); + } + } + else + { + vrc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(vrc)) + pDisplay->mParent->i_consoleVRDPServer()->SendUpdateBitmap(aScreenId, x, y, width, height); + + return vrc; +} + +HRESULT Display::drawToScreen(ULONG aScreenId, BYTE *aAddress, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight) +{ + /// @todo (r=dmik) this function may take too long to complete if the VM + // is doing something like saving state right now. Which, in case if it + // is called on the GUI thread, will make it unresponsive. We should + // check the machine state here (by enclosing the check and VMRequCall + // within the Console lock to make it atomic). + + LogRelFlowFunc(("aAddress=%p, x=%d, y=%d, width=%d, height=%d\n", + (void *)aAddress, aX, aY, aWidth, aHeight)); + + CheckComArgExpr(aWidth, aWidth != 0); + CheckComArgExpr(aHeight, aHeight != 0); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_CONSOLE_DRV(mpDrv); + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* Release lock because the call scheduled on EMT may also try to take it. */ + alock.release(); + + /* + * Again we're lazy and make the graphics device do all the + * dirty conversion work. + */ + int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_drawToScreenEMT, 7, + this, aScreenId, aAddress, aX, aY, aWidth, aHeight); + + /* + * If the function returns not supported, we'll have to do all the + * work ourselves using the framebuffer. + */ + HRESULT hrc = S_OK; + if (vrc == VERR_NOT_SUPPORTED || vrc == VERR_NOT_IMPLEMENTED) + { + /** @todo implement generic fallback for screen blitting. */ + hrc = E_NOTIMPL; + } + else if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not draw to the screen (%Rrc)"), vrc); +/// @todo +// else +// { +// /* All ok. Redraw the screen. */ +// handleDisplayUpdate(x, y, width, height); +// } + + LogRelFlowFunc(("hrc=%Rhrc\n", hrc)); + return hrc; +} + +/** @todo r=bird: cannot quite see why this would be required to run on an + * EMT any more. It's not an issue in the COM methods, but for the + * VGA device interface it is an issue, see querySourceBitmap. */ +/*static*/ DECLCALLBACK(int) Display::i_InvalidateAndUpdateEMT(Display *pDisplay, unsigned uId, bool fUpdateAll) +{ + LogRelFlowFunc(("uId=%d, fUpdateAll %d\n", uId, fUpdateAll)); + + unsigned uScreenId; + for (uScreenId = (fUpdateAll ? 0 : uId); uScreenId < pDisplay->mcMonitors; uScreenId++) + { + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId]; + + if ( !pFBInfo->fVBVAEnabled + && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + pDisplay->mpDrv->pUpPort->pfnUpdateDisplayAll(pDisplay->mpDrv->pUpPort, /* fFailOnResize = */ true); + else + { + if (!pFBInfo->fDisabled) + { + /* Render complete VRAM screen to the framebuffer. + * When framebuffer uses VRAM directly, just notify it to update. + */ + if (pFBInfo->fDefaultFormat && !pFBInfo->pSourceBitmap.isNull()) + { + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hrc)) + { + uint32_t width = pFBInfo->w; + uint32_t height = pFBInfo->h; + + const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM; + int32_t xSrc = 0; + int32_t ySrc = 0; + uint32_t u32SrcWidth = pFBInfo->w; + uint32_t u32SrcHeight = pFBInfo->h; + uint32_t u32SrcLineSize = pFBInfo->u32LineSize; + uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel; + + /* Default format is 32 bpp. */ + uint8_t *pu8Dst = pAddress; + int32_t xDst = xSrc; + int32_t yDst = ySrc; + uint32_t u32DstWidth = u32SrcWidth; + uint32_t u32DstHeight = u32SrcHeight; + uint32_t u32DstLineSize = u32DstWidth * 4; + uint32_t u32DstBitsPerPixel = 32; + + /* if uWidth != pFBInfo->w and uHeight != pFBInfo->h + * implies resize of Framebuffer is in progress and + * copyrect should not be called. + */ + if (ulWidth == pFBInfo->w && ulHeight == pFBInfo->h) + pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort, + width, height, + pu8Src, + xSrc, ySrc, + u32SrcWidth, u32SrcHeight, + u32SrcLineSize, u32SrcBitsPerPixel, + pu8Dst, + xDst, yDst, + u32DstWidth, u32DstHeight, + u32DstLineSize, u32DstBitsPerPixel); + } + } + + pDisplay->i_handleDisplayUpdate(uScreenId, 0, 0, pFBInfo->w, pFBInfo->h); + } + } + if (!fUpdateAll) + break; + } + LogRelFlowFunc(("done\n")); + return VINF_SUCCESS; +} + +/** + * Does a full invalidation of the VM display and instructs the VM + * to update it immediately. + * + * @returns COM status code + */ + +HRESULT Display::invalidateAndUpdate() +{ + LogRelFlowFunc(("\n")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_CONSOLE_DRV(mpDrv); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + LogRelFlowFunc(("Sending DPYUPDATE request\n")); + + /* Have to release the lock when calling EMT. */ + alock.release(); + + int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, 0, true); + alock.acquire(); + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not invalidate and update the screen (%Rrc)"), vrc); + } + + LogRelFlowFunc(("hrc=%Rhrc\n", hrc)); + return hrc; +} + +HRESULT Display::invalidateAndUpdateScreen(ULONG aScreenId) +{ + LogRelFlowFunc(("\n")); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, aScreenId, false); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not invalidate and update the screen %d (%Rrc)"), aScreenId, vrc); + } + + LogRelFlowFunc(("hrc=%Rhrc\n", hrc)); + return hrc; +} + +HRESULT Display::completeVHWACommand(BYTE *aCommand) +{ +#ifdef VBOX_WITH_VIDEOHWACCEL + mpDrv->pVBVACallbacks->pfnVHWACommandCompleteAsync(mpDrv->pVBVACallbacks, (VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)aCommand); + return S_OK; +#else + RT_NOREF(aCommand); + return E_NOTIMPL; +#endif +} + +HRESULT Display::viewportChanged(ULONG aScreenId, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight) +{ + AssertMsgReturn(aScreenId < mcMonitors, ("aScreendId=%d mcMonitors=%d\n", aScreenId, mcMonitors), E_INVALIDARG); + + /* The driver might not have been constructed yet */ + if (mpDrv && mpDrv->pUpPort->pfnSetViewport) + mpDrv->pUpPort->pfnSetViewport(mpDrv->pUpPort, aScreenId, aX, aY, aWidth, aHeight); + + return S_OK; +} + +HRESULT Display::querySourceBitmap(ULONG aScreenId, + ComPtr<IDisplaySourceBitmap> &aDisplaySourceBitmap) +{ + LogRelFlowFunc(("aScreenId = %d\n", aScreenId)); + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + CHECK_CONSOLE_DRV(mpDrv); + + bool fSetRenderVRAM = false; + bool fInvalidate = false; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aScreenId >= mcMonitors) + return setError(E_INVALIDARG, tr("QuerySourceBitmap: Invalid screen %d (total %d)"), aScreenId, mcMonitors); + + if (!mfSourceBitmapEnabled) + { + aDisplaySourceBitmap = NULL; + return E_FAIL; + } + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId]; + + /* No source bitmap for a blank guest screen. */ + if (pFBInfo->flags & VBVA_SCREEN_F_BLANK) + { + aDisplaySourceBitmap = NULL; + return E_FAIL; + } + + HRESULT hr = S_OK; + + if (pFBInfo->pSourceBitmap.isNull()) + { + /* Create a new object. */ + ComObjPtr<DisplaySourceBitmap> obj; + hr = obj.createObject(); + if (SUCCEEDED(hr)) + hr = obj->init(this, aScreenId, pFBInfo); + + if (SUCCEEDED(hr)) + { + pFBInfo->pSourceBitmap = obj; + pFBInfo->fDefaultFormat = !obj->i_usesVRAM(); + + if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + /* Start buffer updates. */ + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + + mpDrv->IConnector.pbData = pAddress; + mpDrv->IConnector.cbScanline = ulBytesPerLine; + mpDrv->IConnector.cBits = ulBitsPerPixel; + mpDrv->IConnector.cx = ulWidth; + mpDrv->IConnector.cy = ulHeight; + + fSetRenderVRAM = pFBInfo->fDefaultFormat; + } + + /* Make sure that the bitmap contains the latest image. */ + fInvalidate = pFBInfo->fDefaultFormat; + } + } + + if (SUCCEEDED(hr)) + { + pFBInfo->pSourceBitmap.queryInterfaceTo(aDisplaySourceBitmap.asOutParam()); + } + + /* Leave the IDisplay lock because the VGA device must not be called under it. */ + alock.release(); + + if (SUCCEEDED(hr)) + { + if (fSetRenderVRAM) + mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, true); + + if (fInvalidate) +#if 1 /* bird: Cannot see why this needs to run on an EMT. It deadlocks now with timer callback moving to non-EMT worker threads. */ + Display::i_InvalidateAndUpdateEMT(this, aScreenId, false /*fUpdateAll*/); +#else + VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, aScreenId, false); +#endif + } + + LogRelFlowFunc(("%Rhrc\n", hr)); + return hr; +} + +HRESULT Display::getGuestScreenLayout(std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenLayout) +{ + NOREF(aGuestScreenLayout); + return E_NOTIMPL; +} + +HRESULT Display::setScreenLayout(ScreenLayoutMode_T aScreenLayoutMode, + const std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenInfo) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aGuestScreenInfo.size() != mcMonitors) + return E_INVALIDARG; + + CHECK_CONSOLE_DRV(mpDrv); + + /* + * It is up to the guest to decide whether the hint is + * valid. Therefore don't do any VRAM sanity checks here. + */ + + /* Have to release the lock because the pfnRequestDisplayChange + * will call EMT. */ + alock.release(); + + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + { + uint32_t const cDisplays = (uint32_t)aGuestScreenInfo.size(); + + size_t const cbAlloc = cDisplays * sizeof(VMMDevDisplayDef); + VMMDevDisplayDef *paDisplayDefs = (VMMDevDisplayDef *)RTMemAlloc(cbAlloc); + if (paDisplayDefs) + { + for (uint32_t i = 0; i < cDisplays; ++i) + { + VMMDevDisplayDef *p = &paDisplayDefs[i]; + ComPtr<IGuestScreenInfo> pScreenInfo = aGuestScreenInfo[i]; + + ULONG screenId = 0; + GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled; + BOOL origin = FALSE; + BOOL primary = FALSE; + LONG originX = 0; + LONG originY = 0; + ULONG width = 0; + ULONG height = 0; + ULONG bitsPerPixel = 0; + + pScreenInfo->COMGETTER(ScreenId) (&screenId); + pScreenInfo->COMGETTER(GuestMonitorStatus)(&guestMonitorStatus); + pScreenInfo->COMGETTER(Primary) (&primary); + pScreenInfo->COMGETTER(Origin) (&origin); + pScreenInfo->COMGETTER(OriginX) (&originX); + pScreenInfo->COMGETTER(OriginY) (&originY); + pScreenInfo->COMGETTER(Width) (&width); + pScreenInfo->COMGETTER(Height) (&height); + pScreenInfo->COMGETTER(BitsPerPixel)(&bitsPerPixel); + + LogFlowFunc(("%d %d,%d %dx%d\n", screenId, originX, originY, width, height)); + + p->idDisplay = screenId; + p->xOrigin = originX; + p->yOrigin = originY; + p->cx = width; + p->cy = height; + p->cBitsPerPixel = bitsPerPixel; + p->fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP; + if (guestMonitorStatus == GuestMonitorStatus_Disabled) + p->fDisplayFlags |= VMMDEV_DISPLAY_DISABLED; + if (origin) + p->fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN; + if (primary) + p->fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY; + } + + bool const fForce = aScreenLayoutMode == ScreenLayoutMode_Reset + || aScreenLayoutMode == ScreenLayoutMode_Apply; + bool const fNotify = aScreenLayoutMode != ScreenLayoutMode_Silent; + pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, cDisplays, paDisplayDefs, fForce, fNotify); + + RTMemFree(paDisplayDefs); + } + } + } + return S_OK; +} + +HRESULT Display::detachScreens(const std::vector<LONG> &aScreenIds) +{ + NOREF(aScreenIds); + return E_NOTIMPL; +} + +HRESULT Display::createGuestScreenInfo(ULONG aDisplay, + GuestMonitorStatus_T aStatus, + BOOL aPrimary, + BOOL aChangeOrigin, + LONG aOriginX, + LONG aOriginY, + ULONG aWidth, + ULONG aHeight, + ULONG aBitsPerPixel, + ComPtr<IGuestScreenInfo> &aGuestScreenInfo) +{ + /* Create a new object. */ + ComObjPtr<GuestScreenInfo> obj; + HRESULT hr = obj.createObject(); + if (SUCCEEDED(hr)) + hr = obj->init(aDisplay, aStatus, aPrimary, aChangeOrigin, aOriginX, aOriginY, + aWidth, aHeight, aBitsPerPixel); + if (SUCCEEDED(hr)) + obj.queryInterfaceTo(aGuestScreenInfo.asOutParam()); + + return hr; +} + + +/* + * GuestScreenInfo implementation. + */ +DEFINE_EMPTY_CTOR_DTOR(GuestScreenInfo) + +HRESULT GuestScreenInfo::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void GuestScreenInfo::FinalRelease() +{ + uninit(); + + BaseFinalRelease(); +} + +HRESULT GuestScreenInfo::init(ULONG aDisplay, + GuestMonitorStatus_T aGuestMonitorStatus, + BOOL aPrimary, + BOOL aChangeOrigin, + LONG aOriginX, + LONG aOriginY, + ULONG aWidth, + ULONG aHeight, + ULONG aBitsPerPixel) +{ + LogFlowThisFunc(("[%u]\n", aDisplay)); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + mScreenId = aDisplay; + mGuestMonitorStatus = aGuestMonitorStatus; + mPrimary = aPrimary; + mOrigin = aChangeOrigin; + mOriginX = aOriginX; + mOriginY = aOriginY; + mWidth = aWidth; + mHeight = aHeight; + mBitsPerPixel = aBitsPerPixel; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +void GuestScreenInfo::uninit() +{ + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFunc(("[%u]\n", mScreenId)); +} + +HRESULT GuestScreenInfo::getScreenId(ULONG *aScreenId) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aScreenId = mScreenId; + return S_OK; +} + +HRESULT GuestScreenInfo::getGuestMonitorStatus(GuestMonitorStatus_T *aGuestMonitorStatus) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aGuestMonitorStatus = mGuestMonitorStatus; + return S_OK; +} + +HRESULT GuestScreenInfo::getPrimary(BOOL *aPrimary) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aPrimary = mPrimary; + return S_OK; +} + +HRESULT GuestScreenInfo::getOrigin(BOOL *aOrigin) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aOrigin = mOrigin; + return S_OK; +} + +HRESULT GuestScreenInfo::getOriginX(LONG *aOriginX) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aOriginX = mOriginX; + return S_OK; +} + +HRESULT GuestScreenInfo::getOriginY(LONG *aOriginY) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aOriginY = mOriginY; + return S_OK; +} + +HRESULT GuestScreenInfo::getWidth(ULONG *aWidth) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aWidth = mWidth; + return S_OK; +} + +HRESULT GuestScreenInfo::getHeight(ULONG *aHeight) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aHeight = mHeight; + return S_OK; +} + +HRESULT GuestScreenInfo::getBitsPerPixel(ULONG *aBitsPerPixel) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aBitsPerPixel = mBitsPerPixel; + return S_OK; +} + +HRESULT GuestScreenInfo::getExtendedInfo(com::Utf8Str &aExtendedInfo) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aExtendedInfo = com::Utf8Str(); + return S_OK; +} + +// wrapped IEventListener method +HRESULT Display::handleEvent(const ComPtr<IEvent> &aEvent) +{ + VBoxEventType_T aType = VBoxEventType_Invalid; + + aEvent->COMGETTER(Type)(&aType); + switch (aType) + { + case VBoxEventType_OnStateChanged: + { + ComPtr<IStateChangedEvent> scev = aEvent; + Assert(scev); + MachineState_T machineState; + scev->COMGETTER(State)(&machineState); + if ( machineState == MachineState_Running + || machineState == MachineState_Teleporting + || machineState == MachineState_LiveSnapshotting + || machineState == MachineState_DeletingSnapshotOnline + ) + { + LogRelFlowFunc(("Machine is running.\n")); + + } + break; + } + default: + AssertFailed(); + } + + return S_OK; +} + + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Handle display resize event issued by the VGA device for the primary screen. + * + * @see PDMIDISPLAYCONNECTOR::pfnResize + */ +DECLCALLBACK(int) Display::i_displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface, + uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + LogRelFlowFunc(("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n", + bpp, pvVRAM, cbLine, cx, cy)); + + bool f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, true, false); + if (!f) + { + /* This is a result of recursive call when the source bitmap is being updated + * during a VGA resize. Tell the VGA device to ignore the call. + * + * @todo It is a workaround, actually pfnUpdateDisplayAll must + * fail on resize. + */ + LogRel(("displayResizeCallback: already processing\n")); + return VINF_VGA_RESIZE_IN_PROGRESS; + } + + int vrc = pThis->i_handleDisplayResize(VBOX_VIDEO_PRIMARY_SCREEN, bpp, pvVRAM, cbLine, cx, cy, 0, 0, 0, true); + + /* Restore the flag. */ + f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, false, true); + AssertRelease(f); + + return vrc; +} + +/** + * Handle display update. + * + * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect + */ +DECLCALLBACK(void) Display::i_displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface, + uint32_t x, uint32_t y, uint32_t cx, uint32_t cy) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + +#ifdef DEBUG_sunlover + LogFlowFunc(("fVideoAccelEnabled = %d, %d,%d %dx%d\n", + pDrv->pDisplay->mVideoAccelLegacy.fVideoAccelEnabled, x, y, cx, cy)); +#endif /* DEBUG_sunlover */ + + /* This call does update regardless of VBVA status. + * But in VBVA mode this is called only as result of + * pfnUpdateDisplayAll in the VGA device. + */ + + pDrv->pDisplay->i_handleDisplayUpdate(VBOX_VIDEO_PRIMARY_SCREEN, x, y, cx, cy); +} + +/** + * Periodic display refresh callback. + * + * @see PDMIDISPLAYCONNECTOR::pfnRefresh + * @thread EMT + */ +/*static*/ DECLCALLBACK(void) Display::i_displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + +#ifdef DEBUG_sunlover_2 + LogFlowFunc(("pDrv->pDisplay->mfVideoAccelEnabled = %d\n", + pDrv->pDisplay->mfVideoAccelEnabled)); +#endif /* DEBUG_sunlover_2 */ + + Display *pDisplay = pDrv->pDisplay; + unsigned uScreenId; + + int vrc = pDisplay->i_videoAccelRefreshProcess(pDrv->pUpPort); + if (vrc != VINF_TRY_AGAIN) /* Means 'do nothing' here. */ + { + if (vrc == VWRN_INVALID_STATE) + { + /* No VBVA do a display update. */ + pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort); + } + + /* Inform the VRDP server that the current display update sequence is + * completed. At this moment the framebuffer memory contains a definite + * image, that is synchronized with the orders already sent to VRDP client. + * The server can now process redraw requests from clients or initial + * fullscreen updates for new clients. + */ + for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++) + { + Assert(pDisplay->mParent && pDisplay->mParent->i_consoleVRDPServer()); + pDisplay->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, NULL, 0); + } + } + +#ifdef VBOX_WITH_RECORDING + AssertPtr(pDisplay->mParent); + RecordingContext *pCtx = pDisplay->mParent->i_recordingGetContext(); + + if ( pCtx + && pCtx->IsStarted() + && pCtx->IsFeatureEnabled(RecordingFeature_Video)) + { + do + { + /* If the recording context has reached the configured recording + * limit, disable recording. */ + if (pCtx->IsLimitReached()) + { + pDisplay->mParent->i_onRecordingChange(FALSE /* Disable */); + break; + } + + uint64_t tsNowMs = RTTimeProgramMilliTS(); + for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++) + { + if (!pDisplay->maRecordingEnabled[uScreenId]) + continue; + + if (!pCtx->NeedsUpdate(uScreenId, tsNowMs)) + continue; + + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId]; + if (!pFBInfo->fDisabled) + { + ComPtr<IDisplaySourceBitmap> pSourceBitmap; + int vrc2 = RTCritSectEnter(&pDisplay->mVideoRecLock); + if (RT_SUCCESS(vrc2)) + { + pSourceBitmap = pFBInfo->Recording.pSourceBitmap; + RTCritSectLeave(&pDisplay->mVideoRecLock); + } + + if (!pSourceBitmap.isNull()) + { + BYTE *pbAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + HRESULT hrc = pSourceBitmap->QueryBitmapInfo(&pbAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hrc) && pbAddress) + vrc = pCtx->SendVideoFrame(uScreenId, 0, 0, BitmapFormat_BGR, + ulBitsPerPixel, ulBytesPerLine, ulWidth, ulHeight, + pbAddress, tsNowMs); + else + vrc = VERR_NOT_SUPPORTED; + + pSourceBitmap.setNull(); + } + else + vrc = VERR_NOT_SUPPORTED; + + if (vrc == VINF_TRY_AGAIN) + break; + } + } + } while (0); + } +#endif /* VBOX_WITH_RECORDING */ + +#ifdef DEBUG_sunlover_2 + LogFlowFunc(("leave\n")); +#endif /* DEBUG_sunlover_2 */ +} + +/** + * Reset notification + * + * @see PDMIDISPLAYCONNECTOR::pfnReset + */ +DECLCALLBACK(void) Display::i_displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + LogRelFlowFunc(("\n")); + + /* Disable VBVA mode. */ + pDrv->pDisplay->VideoAccelEnableVGA(false, NULL); +} + +/** + * LFBModeChange notification + * + * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange + */ +DECLCALLBACK(void) Display::i_displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + LogRelFlowFunc(("fEnabled=%d\n", fEnabled)); + + NOREF(fEnabled); + + /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */ + pDrv->pDisplay->VideoAccelEnableVGA(false, NULL); +} + +/** + * Adapter information change notification. + * + * @see PDMIDISPLAYCONNECTOR::pfnProcessAdapterData + */ +DECLCALLBACK(void) Display::i_displayProcessAdapterDataCallback(PPDMIDISPLAYCONNECTOR pInterface, void *pvVRAM, + uint32_t u32VRAMSize) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + pDrv->pDisplay->processAdapterData(pvVRAM, u32VRAMSize); +} + +/** + * Display information change notification. + * + * @see PDMIDISPLAYCONNECTOR::pfnProcessDisplayData + */ +DECLCALLBACK(void) Display::i_displayProcessDisplayDataCallback(PPDMIDISPLAYCONNECTOR pInterface, + void *pvVRAM, unsigned uScreenId) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + pDrv->pDisplay->processDisplayData(pvVRAM, uScreenId); +} + +#ifdef VBOX_WITH_VIDEOHWACCEL + +int Display::i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand) +{ + /* bugref:9691 Disable the legacy VHWA interface. + * Keep the host commands enabled because they are needed when an old saved state is loaded. + */ + if (fGuestCmd) + return VERR_NOT_IMPLEMENTED; + + unsigned id = (unsigned)pCommand->iDisplay; + if (id >= mcMonitors) + return VERR_INVALID_PARAMETER; + + ComPtr<IFramebuffer> pFramebuffer; + AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS); + pFramebuffer = maFramebuffers[id].pFramebuffer; + bool fVHWASupported = RT_BOOL(maFramebuffers[id].u32Caps & FramebufferCapabilities_VHWA); + arlock.release(); + + if (pFramebuffer == NULL || !fVHWASupported) + return VERR_NOT_IMPLEMENTED; /* Implementation is not available. */ + + HRESULT hr = pFramebuffer->ProcessVHWACommand((BYTE *)pCommand, enmCmd, fGuestCmd); + if (hr == S_FALSE) + return VINF_SUCCESS; + if (SUCCEEDED(hr)) + return VINF_CALLBACK_RETURN; + if (hr == E_ACCESSDENIED) + return VERR_INVALID_STATE; /* notify we can not handle request atm */ + if (hr == E_NOTIMPL) + return VERR_NOT_IMPLEMENTED; + return VERR_GENERAL_FAILURE; +} + +DECLCALLBACK(int) Display::i_displayVHWACommandProcess(PPDMIDISPLAYCONNECTOR pInterface, int enmCmd, bool fGuestCmd, + VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + return pDrv->pDisplay->i_handleVHWACommandProcess(enmCmd, fGuestCmd, pCommand); +} + +#endif /* VBOX_WITH_VIDEOHWACCEL */ + +int Display::i_handle3DNotifyProcess(VBOX3DNOTIFY *p3DNotify) +{ + unsigned const id = (unsigned)p3DNotify->iDisplay; + if (id >= mcMonitors) + return VERR_INVALID_PARAMETER; + + ComPtr<IFramebuffer> pFramebuffer; + AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS); + pFramebuffer = maFramebuffers[id].pFramebuffer; + arlock.release(); + + int vrc = VINF_SUCCESS; + + if (!pFramebuffer.isNull()) + { + if (p3DNotify->enmNotification == VBOX3D_NOTIFY_TYPE_HW_OVERLAY_GET_ID) + { + LONG64 winId = 0; + HRESULT hrc = pFramebuffer->COMGETTER(WinId)(&winId); + if (SUCCEEDED(hrc)) + { + *(uint64_t *)&p3DNotify->au8Data[0] = winId; + } + else + vrc = VERR_NOT_SUPPORTED; + } + else + { + com::SafeArray<BYTE> data; + data.initFrom((BYTE *)&p3DNotify->au8Data[0], p3DNotify->cbData); + + HRESULT hrc = pFramebuffer->Notify3DEvent((ULONG)p3DNotify->enmNotification, ComSafeArrayAsInParam(data)); + if (FAILED(hrc)) + vrc = VERR_NOT_SUPPORTED; + } + } + else + vrc = VERR_NOT_IMPLEMENTED; + + return vrc; +} + +DECLCALLBACK(int) Display::i_display3DNotifyProcess(PPDMIDISPLAYCONNECTOR pInterface, + VBOX3DNOTIFY *p3DNotify) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + return pDrv->pDisplay->i_handle3DNotifyProcess(p3DNotify); +} + +HRESULT Display::notifyScaleFactorChange(ULONG aScreenId, ULONG aScaleFactorWMultiplied, ULONG aScaleFactorHMultiplied) +{ + RT_NOREF(aScreenId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); +# if 0 /** @todo Thank you so very much from anyone using VMSVGA3d! */ + AssertMsgFailed(("Attempt to specify OpenGL content scale factor while 3D acceleration is disabled in VM config. Ignored.\n")); +# else + /* Need an interface like this here (and the #ifdefs needs adjusting): + PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL; + if (pUpPort && pUpPort->pfnSetScaleFactor) + pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */ +# endif + return S_OK; +} + +HRESULT Display::notifyHiDPIOutputPolicyChange(BOOL fUnscaledHiDPI) +{ + RT_NOREF(fUnscaledHiDPI); + + /* Need an interface like this here (and the #ifdefs needs adjusting): + PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL; + if (pUpPort && pUpPort->pfnSetScaleFactor) + pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */ + + return S_OK; +} + +#ifdef VBOX_WITH_HGSMI +/** + * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAEnable} + */ +DECLCALLBACK(int) Display::i_displayVBVAEnable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, + VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pHostFlags) +{ + LogRelFlowFunc(("uScreenId %d\n", uScreenId)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + AssertReturn(uScreenId < pThis->mcMonitors, VERR_INVALID_PARAMETER); + + if (pThis->maFramebuffers[uScreenId].fVBVAEnabled) + { + LogRel(("Enabling different vbva mode\n")); +#ifdef DEBUG_misha + AssertMsgFailed(("enabling different vbva mode\n")); +#endif + return VERR_INVALID_STATE; + } + + pThis->maFramebuffers[uScreenId].fVBVAEnabled = true; + pThis->maFramebuffers[uScreenId].pVBVAHostFlags = pHostFlags; + pThis->maFramebuffers[uScreenId].fVBVAForceResize = true; + + vbvaSetMemoryFlagsHGSMI(uScreenId, pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, &pThis->maFramebuffers[uScreenId]); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVADisable} + */ +DECLCALLBACK(void) Display::i_displayVBVADisable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId) +{ + LogRelFlowFunc(("uScreenId %d\n", uScreenId)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + AssertReturnVoid(uScreenId < pThis->mcMonitors); + + DISPLAYFBINFO *pFBInfo = &pThis->maFramebuffers[uScreenId]; + + if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + /* Make sure that the primary screen is visible now. + * The guest can't use VBVA anymore, so only only the VGA device output works. + */ + pFBInfo->flags = 0; + if (pFBInfo->fDisabled) + { + pFBInfo->fDisabled = false; + ::FireGuestMonitorChangedEvent(pThis->mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId, + pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h); + } + } + + pFBInfo->fVBVAEnabled = false; + pFBInfo->fVBVAForceResize = false; + + vbvaSetMemoryFlagsHGSMI(uScreenId, 0, false, pFBInfo); + + pFBInfo->pVBVAHostFlags = NULL; + + if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + /* Force full screen update, because VGA device must take control, do resize, etc. */ + pThis->mpDrv->pUpPort->pfnUpdateDisplayAll(pThis->mpDrv->pUpPort, /* fFailOnResize = */ false); + } +} + +DECLCALLBACK(void) Display::i_displayVBVAUpdateBegin(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId) +{ + RT_NOREF(uScreenId); + LogFlowFunc(("uScreenId %d\n", uScreenId)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + if (ASMAtomicReadU32(&pThis->mu32UpdateVBVAFlags) > 0) + { + vbvaSetMemoryFlagsAllHGSMI(pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, pThis->maFramebuffers, + pThis->mcMonitors); + ASMAtomicDecU32(&pThis->mu32UpdateVBVAFlags); + } +} + +/** + * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAUpdateProcess} + */ +DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, + struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd) +{ + LogFlowFunc(("uScreenId %d pCmd %p cbCmd %d, @%d,%d %dx%d\n", uScreenId, pCmd, cbCmd, pCmd->x, pCmd->y, pCmd->w, pCmd->h)); + VBVACMDHDR hdrSaved; + RT_COPY_VOLATILE(hdrSaved, *pCmd); + RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + DISPLAYFBINFO *pFBInfo; + AssertReturnVoid(uScreenId < pThis->mcMonitors); + + pFBInfo = &pThis->maFramebuffers[uScreenId]; + + if (pFBInfo->fDefaultFormat) + { + /* Make sure that framebuffer contains the same image as the guest VRAM. */ + if ( uScreenId == VBOX_VIDEO_PRIMARY_SCREEN + && !pFBInfo->fDisabled) + { + pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdrSaved.x, hdrSaved.y, hdrSaved.w, hdrSaved.h); + } + else if ( !pFBInfo->pSourceBitmap.isNull() + && !pFBInfo->fDisabled) + { + /* Render VRAM content to the framebuffer. */ + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hrc)) + { + uint32_t width = hdrSaved.w; + uint32_t height = hdrSaved.h; + + const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM; + int32_t xSrc = hdrSaved.x - pFBInfo->xOrigin; + int32_t ySrc = hdrSaved.y - pFBInfo->yOrigin; + uint32_t u32SrcWidth = pFBInfo->w; + uint32_t u32SrcHeight = pFBInfo->h; + uint32_t u32SrcLineSize = pFBInfo->u32LineSize; + uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel; + + uint8_t *pu8Dst = pAddress; + int32_t xDst = xSrc; + int32_t yDst = ySrc; + uint32_t u32DstWidth = u32SrcWidth; + uint32_t u32DstHeight = u32SrcHeight; + uint32_t u32DstLineSize = u32DstWidth * 4; + uint32_t u32DstBitsPerPixel = 32; + + pDrv->pUpPort->pfnCopyRect(pDrv->pUpPort, + width, height, + pu8Src, + xSrc, ySrc, + u32SrcWidth, u32SrcHeight, + u32SrcLineSize, u32SrcBitsPerPixel, + pu8Dst, + xDst, yDst, + u32DstWidth, u32DstHeight, + u32DstLineSize, u32DstBitsPerPixel); + } + } + } + + /* + * Here is your classic 'temporary' solution. + */ + /** @todo New SendUpdate entry which can get a separate cmd header or coords. */ + VBVACMDHDR *pHdrUnconst = (VBVACMDHDR *)pCmd; + + pHdrUnconst->x -= (int16_t)pFBInfo->xOrigin; + pHdrUnconst->y -= (int16_t)pFBInfo->yOrigin; + + pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, pHdrUnconst, (uint32_t)cbCmd); + + *pHdrUnconst = hdrSaved; +} + +DECLCALLBACK(void) Display::i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y, + uint32_t cx, uint32_t cy) +{ + LogFlowFunc(("uScreenId %d %d,%d %dx%d\n", uScreenId, x, y, cx, cy)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + DISPLAYFBINFO *pFBInfo; + AssertReturnVoid(uScreenId < pThis->mcMonitors); + + pFBInfo = &pThis->maFramebuffers[uScreenId]; + + /** @todo handleFramebufferUpdate (uScreenId, + * x - pThis->maFramebuffers[uScreenId].xOrigin, + * y - pThis->maFramebuffers[uScreenId].yOrigin, + * cx, cy); + */ + pThis->i_handleDisplayUpdate(uScreenId, x - pFBInfo->xOrigin, y - pFBInfo->yOrigin, cx, cy); +} + +#ifdef DEBUG_sunlover +static void logVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, const DISPLAYFBINFO *pFBInfo) +{ + LogRel(("displayVBVAResize: [%d] %s\n" + " pView->u32ViewIndex %d\n" + " pView->u32ViewOffset 0x%08X\n" + " pView->u32ViewSize 0x%08X\n" + " pView->u32MaxScreenSize 0x%08X\n" + " pScreen->i32OriginX %d\n" + " pScreen->i32OriginY %d\n" + " pScreen->u32StartOffset 0x%08X\n" + " pScreen->u32LineSize 0x%08X\n" + " pScreen->u32Width %d\n" + " pScreen->u32Height %d\n" + " pScreen->u16BitsPerPixel %d\n" + " pScreen->u16Flags 0x%04X\n" + " pFBInfo->u32Offset 0x%08X\n" + " pFBInfo->u32MaxFramebufferSize 0x%08X\n" + " pFBInfo->u32InformationSize 0x%08X\n" + " pFBInfo->fDisabled %d\n" + " xOrigin, yOrigin, w, h: %d,%d %dx%d\n" + " pFBInfo->u16BitsPerPixel %d\n" + " pFBInfo->pu8FramebufferVRAM %p\n" + " pFBInfo->u32LineSize 0x%08X\n" + " pFBInfo->flags 0x%04X\n" + " pFBInfo->pHostEvents %p\n" + " pFBInfo->fDefaultFormat %d\n" + " pFBInfo->fVBVAEnabled %d\n" + " pFBInfo->fVBVAForceResize %d\n" + " pFBInfo->pVBVAHostFlags %p\n" + "", + pScreen->u32ViewIndex, + (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED)? "DISABLED": "ENABLED", + pView->u32ViewIndex, + pView->u32ViewOffset, + pView->u32ViewSize, + pView->u32MaxScreenSize, + pScreen->i32OriginX, + pScreen->i32OriginY, + pScreen->u32StartOffset, + pScreen->u32LineSize, + pScreen->u32Width, + pScreen->u32Height, + pScreen->u16BitsPerPixel, + pScreen->u16Flags, + pFBInfo->u32Offset, + pFBInfo->u32MaxFramebufferSize, + pFBInfo->u32InformationSize, + pFBInfo->fDisabled, + pFBInfo->xOrigin, + pFBInfo->yOrigin, + pFBInfo->w, + pFBInfo->h, + pFBInfo->u16BitsPerPixel, + pFBInfo->pu8FramebufferVRAM, + pFBInfo->u32LineSize, + pFBInfo->flags, + pFBInfo->pHostEvents, + pFBInfo->fDefaultFormat, + pFBInfo->fVBVAEnabled, + pFBInfo->fVBVAForceResize, + pFBInfo->pVBVAHostFlags + )); +} +#endif /* DEBUG_sunlover */ + +DECLCALLBACK(int) Display::i_displayVBVAResize(PPDMIDISPLAYCONNECTOR pInterface, PCVBVAINFOVIEW pView, + PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping) +{ + LogRelFlowFunc(("pScreen %p, pvVRAM %p\n", pScreen, pvVRAM)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + return pThis->processVBVAResize(pView, pScreen, pvVRAM, fResetInputMapping); +} + +int Display::processVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + RT_NOREF(pView); + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[pScreen->u32ViewIndex]; + +#ifdef DEBUG_sunlover + logVBVAResize(pView, pScreen, pFBInfo); +#endif + + if (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED) + { + /* Ask the framebuffer to resize using a default format. The framebuffer will be black. + * So if the frontend does not support GuestMonitorChangedEventType_Disabled event, + * the VM window will be black. */ + uint32_t u32Width = pFBInfo->w ? pFBInfo->w : 640; + uint32_t u32Height = pFBInfo->h ? pFBInfo->h : 480; + int32_t xOrigin = pFBInfo->xOrigin; + int32_t yOrigin = pFBInfo->yOrigin; + + alock.release(); + + i_handleDisplayResize(pScreen->u32ViewIndex, 0, (uint8_t *)NULL, 0, + u32Width, u32Height, pScreen->u16Flags, xOrigin, yOrigin, false); + + return VINF_SUCCESS; + } + + VBVAINFOSCREEN screenInfo; + RT_ZERO(screenInfo); + + if (pScreen->u16Flags & VBVA_SCREEN_F_BLANK2) + { + /* Init a local VBVAINFOSCREEN structure, which will be used instead of + * the original pScreen. Set VBVA_SCREEN_F_BLANK, which will force + * the code below to choose the "blanking" branches. + */ + screenInfo.u32ViewIndex = pScreen->u32ViewIndex; + screenInfo.i32OriginX = pFBInfo->xOrigin; + screenInfo.i32OriginY = pFBInfo->yOrigin; + screenInfo.u32StartOffset = 0; /* Irrelevant */ + screenInfo.u32LineSize = pFBInfo->u32LineSize; + screenInfo.u32Width = pFBInfo->w; + screenInfo.u32Height = pFBInfo->h; + screenInfo.u16BitsPerPixel = pFBInfo->u16BitsPerPixel; + screenInfo.u16Flags = pScreen->u16Flags | VBVA_SCREEN_F_BLANK; + + pScreen = &screenInfo; + } + + if (fResetInputMapping) + { + /// @todo Rename to m* and verify whether some kind of lock is required. + xInputMappingOrigin = 0; + yInputMappingOrigin = 0; + cxInputMapping = 0; + cyInputMapping = 0; + } + + alock.release(); + + return i_handleDisplayResize(pScreen->u32ViewIndex, pScreen->u16BitsPerPixel, + (uint8_t *)pvVRAM + pScreen->u32StartOffset, + pScreen->u32LineSize, pScreen->u32Width, pScreen->u32Height, pScreen->u16Flags, + pScreen->i32OriginX, pScreen->i32OriginY, false); +} + +DECLCALLBACK(int) Display::i_displayVBVAMousePointerShape(PPDMIDISPLAYCONNECTOR pInterface, bool fVisible, bool fAlpha, + uint32_t xHot, uint32_t yHot, + uint32_t cx, uint32_t cy, + const void *pvShape) +{ + LogFlowFunc(("\n")); + LogRel2(("%s: fVisible=%RTbool\n", __PRETTY_FUNCTION__, fVisible)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + uint32_t cbShape = 0; + if (pvShape) + { + cbShape = (cx + 7) / 8 * cy; /* size of the AND mask */ + cbShape = ((cbShape + 3) & ~3) + cx * 4 * cy; /* + gap + size of the XOR mask */ + } + + /* Tell the console about it */ + pDrv->pDisplay->mParent->i_onMousePointerShapeChange(fVisible, fAlpha, + xHot, yHot, cx, cy, (uint8_t *)pvShape, cbShape); + + return VINF_SUCCESS; +} + +DECLCALLBACK(void) Display::i_displayVBVAGuestCapabilityUpdate(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fCapabilities) +{ + LogFlowFunc(("\n")); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + pThis->i_handleUpdateGuestVBVACapabilities(fCapabilities); +} + +DECLCALLBACK(void) Display::i_displayVBVAInputMappingUpdate(PPDMIDISPLAYCONNECTOR pInterface, int32_t xOrigin, int32_t yOrigin, + uint32_t cx, uint32_t cy) +{ + LogFlowFunc(("\n")); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + pThis->i_handleUpdateVBVAInputMapping(xOrigin, yOrigin, cx, cy); +} + +DECLCALLBACK(void) Display::i_displayVBVAReportCursorPosition(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fFlags, uint32_t aScreenId, uint32_t x, uint32_t y) +{ + LogFlowFunc(("\n")); + LogRel2(("%s: fFlags=%RU32, aScreenId=%RU32, x=%RU32, y=%RU32\n", + __PRETTY_FUNCTION__, fFlags, aScreenId, x, y)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + if (fFlags & VBVA_CURSOR_SCREEN_RELATIVE) + { + AssertReturnVoid(aScreenId < pThis->mcMonitors); + + x += pThis->maFramebuffers[aScreenId].xOrigin; + y += pThis->maFramebuffers[aScreenId].yOrigin; + } + ::FireCursorPositionChangedEvent(pThis->mParent->i_getEventSource(), RT_BOOL(fFlags & VBVA_CURSOR_VALID_DATA), x, y); +} + +#endif /* VBOX_WITH_HGSMI */ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) Display::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINDISPLAY pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIDISPLAYCONNECTOR, &pDrv->IConnector); + return NULL; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff, + * Tries to ensure no client calls gets to HGCM or the VGA device from here on.} + */ +DECLCALLBACK(void) Display::i_drvPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY); + LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Do much of the work that i_drvDestruct does. + */ + if (pThis->pUpPort) + pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false); + + pThis->IConnector.pbData = NULL; + pThis->IConnector.cbScanline = 0; + pThis->IConnector.cBits = 32; + pThis->IConnector.cx = 0; + pThis->IConnector.cy = 0; + + if (pThis->pDisplay) + { + AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS); +#ifdef VBOX_WITH_RECORDING + pThis->pDisplay->mParent->i_recordingStop(); +#endif +#if defined(VBOX_WITH_VIDEOHWACCEL) + pThis->pVBVACallbacks = NULL; +#endif + } +} + + +/** + * Destruct a display driver instance. + * + * @returns VBox status code. + * @param pDrvIns The driver instance data. + */ +DECLCALLBACK(void) Display::i_drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY); + LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + /* + * We repeat much of what i_drvPowerOff does in case it wasn't called. + * In addition we sever the connection between us and the display. + */ + if (pThis->pUpPort) + pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false); + + pThis->IConnector.pbData = NULL; + pThis->IConnector.cbScanline = 0; + pThis->IConnector.cBits = 32; + pThis->IConnector.cx = 0; + pThis->IConnector.cy = 0; + + if (pThis->pDisplay) + { + AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS); +#ifdef VBOX_WITH_RECORDING + pThis->pDisplay->mParent->i_recordingStop(); +#endif +#if defined(VBOX_WITH_VIDEOHWACCEL) + pThis->pVBVACallbacks = NULL; +#endif + + pThis->pDisplay->mpDrv = NULL; + pThis->pDisplay = NULL; + } +#if defined(VBOX_WITH_VIDEOHWACCEL) + pThis->pVBVACallbacks = NULL; +#endif +} + + +/** + * Construct a display driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +DECLCALLBACK(int) Display::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + RT_NOREF(fFlags, pCfg); + PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY); + LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Init Interfaces. + */ + pDrvIns->IBase.pfnQueryInterface = Display::i_drvQueryInterface; + + pThis->IConnector.pfnResize = Display::i_displayResizeCallback; + pThis->IConnector.pfnUpdateRect = Display::i_displayUpdateCallback; + pThis->IConnector.pfnRefresh = Display::i_displayRefreshCallback; + pThis->IConnector.pfnReset = Display::i_displayResetCallback; + pThis->IConnector.pfnLFBModeChange = Display::i_displayLFBModeChangeCallback; + pThis->IConnector.pfnProcessAdapterData = Display::i_displayProcessAdapterDataCallback; + pThis->IConnector.pfnProcessDisplayData = Display::i_displayProcessDisplayDataCallback; +#ifdef VBOX_WITH_VIDEOHWACCEL + pThis->IConnector.pfnVHWACommandProcess = Display::i_displayVHWACommandProcess; +#endif +#ifdef VBOX_WITH_HGSMI + pThis->IConnector.pfnVBVAEnable = Display::i_displayVBVAEnable; + pThis->IConnector.pfnVBVADisable = Display::i_displayVBVADisable; + pThis->IConnector.pfnVBVAUpdateBegin = Display::i_displayVBVAUpdateBegin; + pThis->IConnector.pfnVBVAUpdateProcess = Display::i_displayVBVAUpdateProcess; + pThis->IConnector.pfnVBVAUpdateEnd = Display::i_displayVBVAUpdateEnd; + pThis->IConnector.pfnVBVAResize = Display::i_displayVBVAResize; + pThis->IConnector.pfnVBVAMousePointerShape = Display::i_displayVBVAMousePointerShape; + pThis->IConnector.pfnVBVAGuestCapabilityUpdate = Display::i_displayVBVAGuestCapabilityUpdate; + pThis->IConnector.pfnVBVAInputMappingUpdate = Display::i_displayVBVAInputMappingUpdate; + pThis->IConnector.pfnVBVAReportCursorPosition = Display::i_displayVBVAReportCursorPosition; +#endif + pThis->IConnector.pfn3DNotifyProcess = Display::i_display3DNotifyProcess; + + /* + * Get the IDisplayPort interface of the above driver/device. + */ + pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYPORT); + if (!pThis->pUpPort) + { + AssertMsgFailed(("Configuration error: No display port interface above!\n")); + return VERR_PDM_MISSING_INTERFACE_ABOVE; + } +#if defined(VBOX_WITH_VIDEOHWACCEL) + pThis->pVBVACallbacks = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYVBVACALLBACKS); + if (!pThis->pVBVACallbacks) + { + AssertMsgFailed(("Configuration error: No VBVA callback interface above!\n")); + return VERR_PDM_MISSING_INTERFACE_ABOVE; + } +#endif + /* + * Get the Display object pointer and update the mpDrv member. + */ + com::Guid uuid(COM_IIDOF(IDisplay)); + IDisplay *pIDisplay = (IDisplay *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw()); + if (!pIDisplay) + { + AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n")); + return VERR_NOT_FOUND; + } + pThis->pDisplay = static_cast<Display *>(pIDisplay); + pThis->pDisplay->mpDrv = pThis; + + /* Disable VRAM to a buffer copy initially. */ + pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false); + pThis->IConnector.cBits = 32; /* DevVGA does nothing otherwise. */ + + /* + * Start periodic screen refreshes + */ + pThis->pUpPort->pfnSetRefreshRate(pThis->pUpPort, 20); + + return VINF_SUCCESS; +} + + +/** + * Display driver registration record. + */ +const PDMDRVREG Display::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "MainDisplay", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main display driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_DISPLAY, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINDISPLAY), + /* pfnConstruct */ + Display::i_drvConstruct, + /* pfnDestruct */ + Display::i_drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + Display::i_drvPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp new file mode 100644 index 00000000..7f00a4d0 --- /dev/null +++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp @@ -0,0 +1,1018 @@ +/* $Id: DisplayImplLegacy.cpp $ */ +/** @file + * VirtualBox IDisplay implementation, helpers for legacy GAs. + * + * Methods and helpers to support old Guest Additions 3.x or older. + * This is not used by the current Guest Additions. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY +#include "LoggingNew.h" + +#include "DisplayImpl.h" +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" +#include "VMMDev.h" +#include <VBox/VMMDev.h> + +/* generated header */ +#include "VBoxEvents.h" + + +int videoAccelConstruct(VIDEOACCEL *pVideoAccel) +{ + pVideoAccel->pVbvaMemory = NULL; + pVideoAccel->fVideoAccelEnabled = false; + + pVideoAccel->pu8VbvaPartial = NULL; + pVideoAccel->cbVbvaPartial = 0; + + pVideoAccel->hXRoadsVideoAccel = NIL_RTSEMXROADS; + int vrc = RTSemXRoadsCreate(&pVideoAccel->hXRoadsVideoAccel); + AssertRC(vrc); + + return vrc; +} + +void videoAccelDestroy(VIDEOACCEL *pVideoAccel) +{ + RTSemXRoadsDestroy(pVideoAccel->hXRoadsVideoAccel); + RT_ZERO(*pVideoAccel); +} + +static unsigned mapCoordsToScreen(DISPLAYFBINFO *pInfos, unsigned cInfos, int *px, int *py, int *pw, int *ph) +{ + RT_NOREF(pw, ph); + + DISPLAYFBINFO *pInfo = pInfos; + unsigned uScreenId; + Log9(("mapCoordsToScreen: %d,%d %dx%d\n", *px, *py, *pw, *ph)); + for (uScreenId = 0; uScreenId < cInfos; uScreenId++, pInfo++) + { + Log9((" [%d] %d,%d %dx%d\n", uScreenId, pInfo->xOrigin, pInfo->yOrigin, pInfo->w, pInfo->h)); + if ( (pInfo->xOrigin <= *px && *px < pInfo->xOrigin + (int)pInfo->w) + && (pInfo->yOrigin <= *py && *py < pInfo->yOrigin + (int)pInfo->h)) + { + /* The rectangle belongs to the screen. Correct coordinates. */ + *px -= pInfo->xOrigin; + *py -= pInfo->yOrigin; + Log9((" -> %d,%d", *px, *py)); + break; + } + } + if (uScreenId == cInfos) + { + /* Map to primary screen. */ + uScreenId = 0; + } + Log9((" scr %d\n", uScreenId)); + return uScreenId; +} + + +typedef struct _VBVADIRTYREGION +{ + /* Copies of object's pointers used by vbvaRgn functions. */ + DISPLAYFBINFO *paFramebuffers; + unsigned cMonitors; + Display *pDisplay; + PPDMIDISPLAYPORT pPort; + + /* The rectangle that includes all dirty rectangles. */ + RTRECT aDirtyRects[SchemaDefs::MaxGuestMonitors]; + +} VBVADIRTYREGION; + +static void vbvaRgnInit(VBVADIRTYREGION *prgn, DISPLAYFBINFO *paFramebuffers, unsigned cMonitors, + Display *pd, PPDMIDISPLAYPORT pp) +{ + prgn->paFramebuffers = paFramebuffers; + prgn->cMonitors = cMonitors; + prgn->pDisplay = pd; + prgn->pPort = pp; + + RT_ZERO(prgn->aDirtyRects); +} + +static void vbvaRgnDirtyRect(VBVADIRTYREGION *prgn, unsigned uScreenId, VBVACMDHDR *phdr) +{ + Log9(("x = %d, y = %d, w = %d, h = %d\n", phdr->x, phdr->y, phdr->w, phdr->h)); + + /* + * Here update rectangles are accumulated to form an update area. + */ + /** @todo + * Now the simplest method is used which builds one rectangle that + * includes all update areas. A bit more advanced method can be + * employed here. The method should be fast however. + */ + if (phdr->w == 0 || phdr->h == 0) + { + /* Empty rectangle. */ + return; + } + + int32_t xRight = phdr->x + phdr->w; + int32_t yBottom = phdr->y + phdr->h; + + RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId]; + DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId]; + + if (pDirtyRect->xRight == 0) + { + /* This is the first rectangle to be added. */ + pDirtyRect->xLeft = phdr->x; + pDirtyRect->yTop = phdr->y; + pDirtyRect->xRight = xRight; + pDirtyRect->yBottom = yBottom; + } + else + { + /* Adjust region coordinates. */ + if (pDirtyRect->xLeft > phdr->x) + { + pDirtyRect->xLeft = phdr->x; + } + + if (pDirtyRect->yTop > phdr->y) + { + pDirtyRect->yTop = phdr->y; + } + + if (pDirtyRect->xRight < xRight) + { + pDirtyRect->xRight = xRight; + } + + if (pDirtyRect->yBottom < yBottom) + { + pDirtyRect->yBottom = yBottom; + } + } + + if (pFBInfo->fDefaultFormat) + { + /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer + prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, phdr->x, phdr->y, phdr->w, phdr->h); + prgn->pDisplay->i_handleDisplayUpdate(uScreenId, phdr->x, phdr->y, phdr->w, phdr->h); + } + + return; +} + +static void vbvaRgnUpdateFramebuffer(VBVADIRTYREGION *prgn, unsigned uScreenId) +{ + RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId]; + DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId]; + + uint32_t w = pDirtyRect->xRight - pDirtyRect->xLeft; + uint32_t h = pDirtyRect->yBottom - pDirtyRect->yTop; + + if (!pFBInfo->fDefaultFormat && w != 0 && h != 0) + { + /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer + prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, pDirtyRect->xLeft, pDirtyRect->yTop, w, h); + prgn->pDisplay->i_handleDisplayUpdate(uScreenId, pDirtyRect->xLeft, pDirtyRect->yTop, w, h); + } +} + +void i_vbvaSetMemoryFlags(VBVAMEMORY *pVbvaMemory, + bool fVideoAccelEnabled, + bool fVideoAccelVRDP, + uint32_t fu32SupportedOrders, + DISPLAYFBINFO *paFBInfos, + unsigned cFBInfos) +{ + if (pVbvaMemory) + { + /* This called only on changes in mode. So reset VRDP always. */ + uint32_t fu32Flags = VBVA_F_MODE_VRDP_RESET; + + if (fVideoAccelEnabled) + { + fu32Flags |= VBVA_F_MODE_ENABLED; + + if (fVideoAccelVRDP) + { + fu32Flags |= VBVA_F_MODE_VRDP | VBVA_F_MODE_VRDP_ORDER_MASK; + + pVbvaMemory->fu32SupportedOrders = fu32SupportedOrders; + } + } + + pVbvaMemory->fu32ModeFlags = fu32Flags; + } + + unsigned uScreenId; + for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++) + { + if (paFBInfos[uScreenId].pHostEvents) + { + paFBInfos[uScreenId].pHostEvents->fu32Events |= VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET; + } + } +} + +bool Display::i_VideoAccelAllowed(void) +{ + return true; +} + +int videoAccelEnterVGA(VIDEOACCEL *pVideoAccel) +{ + return RTSemXRoadsNSEnter(pVideoAccel->hXRoadsVideoAccel); +} + +void videoAccelLeaveVGA(VIDEOACCEL *pVideoAccel) +{ + RTSemXRoadsNSLeave(pVideoAccel->hXRoadsVideoAccel); +} + +int videoAccelEnterVMMDev(VIDEOACCEL *pVideoAccel) +{ + return RTSemXRoadsEWEnter(pVideoAccel->hXRoadsVideoAccel); +} + +void videoAccelLeaveVMMDev(VIDEOACCEL *pVideoAccel) +{ + RTSemXRoadsEWLeave(pVideoAccel->hXRoadsVideoAccel); +} + +/** + * @thread EMT + */ +int Display::i_VideoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort) +{ + LogRelFlowFunc(("fEnable = %d\n", fEnable)); + + int vrc = i_videoAccelEnable(fEnable, pVbvaMemory, pUpPort); + + LogRelFlowFunc(("%Rrc.\n", vrc)); + return vrc; +} + +int Display::i_videoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort) +{ + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + + /* Called each time the guest wants to use acceleration, + * or when the VGA device disables acceleration, + * or when restoring the saved state with accel enabled. + * + * VGA device disables acceleration on each video mode change + * and on reset. + * + * Guest enabled acceleration at will. And it has to enable + * acceleration after a mode change. + */ + LogRelFlowFunc(("mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n", + pVideoAccel->fVideoAccelEnabled, fEnable, pVbvaMemory)); + + /* Strictly check parameters. Callers must not pass anything in the case. */ + Assert((fEnable && pVbvaMemory) || (!fEnable && pVbvaMemory == NULL)); + + if (!i_VideoAccelAllowed ()) + return VERR_NOT_SUPPORTED; + + /* Check that current status is not being changed */ + if (pVideoAccel->fVideoAccelEnabled == fEnable) + return VINF_SUCCESS; + + if (pVideoAccel->fVideoAccelEnabled) + { + /* Process any pending orders and empty the VBVA ring buffer. */ + i_videoAccelFlush (pUpPort); + } + + if (!fEnable && pVideoAccel->pVbvaMemory) + pVideoAccel->pVbvaMemory->fu32ModeFlags &= ~VBVA_F_MODE_ENABLED; + + if (fEnable) + { + /* Process any pending VGA device changes, resize. */ + pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false); + } + + /* Protect the videoaccel state transition. */ + RTCritSectEnter(&mVideoAccelLock); + + if (fEnable) + { + /* Initialize the hardware memory. */ + i_vbvaSetMemoryFlags(pVbvaMemory, true, mfVideoAccelVRDP, + mfu32SupportedOrders, maFramebuffers, mcMonitors); + pVbvaMemory->off32Data = 0; + pVbvaMemory->off32Free = 0; + + memset(pVbvaMemory->aRecords, 0, sizeof(pVbvaMemory->aRecords)); + pVbvaMemory->indexRecordFirst = 0; + pVbvaMemory->indexRecordFree = 0; + + pVideoAccel->pVbvaMemory = pVbvaMemory; + pVideoAccel->fVideoAccelEnabled = true; + + LogRel(("VBVA: Enabled.\n")); + } + else + { + pVideoAccel->pVbvaMemory = NULL; + pVideoAccel->fVideoAccelEnabled = false; + + LogRel(("VBVA: Disabled.\n")); + } + + RTCritSectLeave(&mVideoAccelLock); + + if (!fEnable) + { + pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false); + } + + /* Notify the VMMDev, which saves VBVA status in the saved state, + * and needs to know current status. + */ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + pVMMDevPort->pfnVBVAChange(pVMMDevPort, fEnable); + } + + LogRelFlowFunc(("VINF_SUCCESS.\n")); + return VINF_SUCCESS; +} + +static bool i_vbvaVerifyRingBuffer(VBVAMEMORY *pVbvaMemory) +{ + RT_NOREF(pVbvaMemory); + return true; +} + +static void i_vbvaFetchBytes(VBVAMEMORY *pVbvaMemory, uint8_t *pu8Dst, uint32_t cbDst) +{ + if (cbDst >= VBVA_RING_BUFFER_SIZE) + { + AssertMsgFailed(("cbDst = 0x%08X, ring buffer size 0x%08X\n", cbDst, VBVA_RING_BUFFER_SIZE)); + return; + } + + uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data; + uint8_t *src = &pVbvaMemory->au8RingBuffer[pVbvaMemory->off32Data]; + int32_t i32Diff = cbDst - u32BytesTillBoundary; + + if (i32Diff <= 0) + { + /* Chunk will not cross buffer boundary. */ + memcpy (pu8Dst, src, cbDst); + } + else + { + /* Chunk crosses buffer boundary. */ + memcpy(pu8Dst, src, u32BytesTillBoundary); + memcpy(pu8Dst + u32BytesTillBoundary, &pVbvaMemory->au8RingBuffer[0], i32Diff); + } + + /* Advance data offset. */ + pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbDst) % VBVA_RING_BUFFER_SIZE; + + return; +} + + +static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory) +{ + uint8_t *pu8New; + + LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n", + *ppu8, *pcb, cbRecord)); + + if (*ppu8) + { + Assert (*pcb); + pu8New = (uint8_t *)RTMemRealloc(*ppu8, cbRecord); + } + else + { + Assert (!*pcb); + pu8New = (uint8_t *)RTMemAlloc(cbRecord); + } + + if (!pu8New) + { + /* Memory allocation failed, fail the function. */ + Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n", + cbRecord)); + + if (*ppu8) + { + RTMemFree(*ppu8); + } + + *ppu8 = NULL; + *pcb = 0; + + return false; + } + + /* Fetch data from the ring buffer. */ + i_vbvaFetchBytes(pVbvaMemory, pu8New + *pcb, cbRecord - *pcb); + + *ppu8 = pu8New; + *pcb = cbRecord; + + return true; +} + +/* For contiguous chunks just return the address in the buffer. + * For crossing boundary - allocate a buffer from heap. + */ +static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t *pcbCmd) +{ + VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory; + + uint32_t indexRecordFirst = pVbvaMemory->indexRecordFirst; + uint32_t indexRecordFree = pVbvaMemory->indexRecordFree; + +#ifdef DEBUG_sunlover + LogFlowFunc(("first = %d, free = %d\n", + indexRecordFirst, indexRecordFree)); +#endif /* DEBUG_sunlover */ + + if (!i_vbvaVerifyRingBuffer(pVbvaMemory)) + { + return false; + } + + if (indexRecordFirst == indexRecordFree) + { + /* No records to process. Return without assigning output variables. */ + return true; + } + + uint32_t cbRecordCurrent = ASMAtomicReadU32(&pVbvaMemory->aRecords[indexRecordFirst].cbRecord); + +#ifdef DEBUG_sunlover + LogFlowFunc(("cbRecord = 0x%08X\n", cbRecordCurrent)); +#endif /* DEBUG_sunlover */ + + uint32_t cbRecord = cbRecordCurrent & ~VBVA_F_RECORD_PARTIAL; + + if (pVideoAccel->cbVbvaPartial) + { + /* There is a partial read in process. Continue with it. */ + + Assert(pVideoAccel->pu8VbvaPartial); + + LogFlowFunc(("continue partial record cbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n", + pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree)); + + if (cbRecord > pVideoAccel->cbVbvaPartial) + { + /* New data has been added to the record. */ + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory)) + { + return false; + } + } + + if (!(cbRecordCurrent & VBVA_F_RECORD_PARTIAL)) + { + /* The record is completed by guest. Return it to the caller. */ + *ppHdr = (VBVACMDHDR *)pVideoAccel->pu8VbvaPartial; + *pcbCmd = pVideoAccel->cbVbvaPartial; + + pVideoAccel->pu8VbvaPartial = NULL; + pVideoAccel->cbVbvaPartial = 0; + + /* Advance the record index. */ + pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS; + +#ifdef DEBUG_sunlover + LogFlowFunc(("partial done ok, data = %d, free = %d\n", + pVbvaMemory->off32Data, pVbvaMemory->off32Free)); +#endif /* DEBUG_sunlover */ + } + + return true; + } + + /* A new record need to be processed. */ + if (cbRecordCurrent & VBVA_F_RECORD_PARTIAL) + { + /* Current record is being written by guest. '=' is important here. */ + if (cbRecord >= VBVA_RING_BUFFER_SIZE - VBVA_RING_BUFFER_THRESHOLD) + { + /* Partial read must be started. */ + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory)) + { + return false; + } + + LogFlowFunc(("started partial record cbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n", + pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree)); + } + + return true; + } + + /* Current record is complete. If it is not empty, process it. */ + if (cbRecord) + { + /* The size of largest contiguous chunk in the ring biffer. */ + uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data; + + /* The ring buffer pointer. */ + uint8_t *au8RingBuffer = &pVbvaMemory->au8RingBuffer[0]; + + /* The pointer to data in the ring buffer. */ + uint8_t *src = &au8RingBuffer[pVbvaMemory->off32Data]; + + /* Fetch or point the data. */ + if (u32BytesTillBoundary >= cbRecord) + { + /* The command does not cross buffer boundary. Return address in the buffer. */ + *ppHdr = (VBVACMDHDR *)src; + + /* Advance data offset. */ + pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE; + } + else + { + /* The command crosses buffer boundary. Rare case, so not optimized. */ + uint8_t *dst = (uint8_t *)RTMemAlloc(cbRecord); + + if (!dst) + { + LogRelFlowFunc(("could not allocate %d bytes from heap!!!\n", cbRecord)); + pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE; + return false; + } + + i_vbvaFetchBytes(pVbvaMemory, dst, cbRecord); + + *ppHdr = (VBVACMDHDR *)dst; + +#ifdef DEBUG_sunlover + LogFlowFunc(("Allocated from heap %p\n", dst)); +#endif /* DEBUG_sunlover */ + } + } + + *pcbCmd = cbRecord; + + /* Advance the record index. */ + pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS; + +#ifdef DEBUG_sunlover + LogFlowFunc(("done ok, data = %d, free = %d\n", + pVbvaMemory->off32Data, pVbvaMemory->off32Free)); +#endif /* DEBUG_sunlover */ + + return true; +} + +static void i_vbvaReleaseCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR *pHdr, int32_t cbCmd) +{ + RT_NOREF(cbCmd); + uint8_t *au8RingBuffer = pVideoAccel->pVbvaMemory->au8RingBuffer; + + if ( (uint8_t *)pHdr >= au8RingBuffer + && (uint8_t *)pHdr < &au8RingBuffer[VBVA_RING_BUFFER_SIZE]) + { + /* The pointer is inside ring buffer. Must be continuous chunk. */ + Assert(VBVA_RING_BUFFER_SIZE - ((uint8_t *)pHdr - au8RingBuffer) >= cbCmd); + + /* Do nothing. */ + + Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0); + } + else + { + /* The pointer is outside. It is then an allocated copy. */ + +#ifdef DEBUG_sunlover + LogFlowFunc(("Free heap %p\n", pHdr)); +#endif /* DEBUG_sunlover */ + + if ((uint8_t *)pHdr == pVideoAccel->pu8VbvaPartial) + { + pVideoAccel->pu8VbvaPartial = NULL; + pVideoAccel->cbVbvaPartial = 0; + } + else + { + Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0); + } + + RTMemFree(pHdr); + } + + return; +} + + +/** + * Called regularly on the DisplayRefresh timer. + * Also on behalf of guest, when the ring buffer is full. + * + * @thread EMT + */ +void Display::i_VideoAccelFlush(PPDMIDISPLAYPORT pUpPort) +{ + int vrc = i_videoAccelFlush(pUpPort); + if (RT_FAILURE(vrc)) + { + /* Disable on errors. */ + i_videoAccelEnable(false, NULL, pUpPort); + } +} + +int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort) +{ + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory; + +#ifdef DEBUG_sunlover_2 + LogFlowFunc(("fVideoAccelEnabled = %d\n", pVideoAccel->fVideoAccelEnabled)); +#endif /* DEBUG_sunlover_2 */ + + if (!pVideoAccel->fVideoAccelEnabled) + { + Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n")); + return VINF_SUCCESS; + } + + /* Here VBVA is enabled and we have the accelerator memory pointer. */ + Assert(pVbvaMemory); + +#ifdef DEBUG_sunlover_2 + LogFlowFunc(("indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n", + pVbvaMemory->indexRecordFirst, pVbvaMemory->indexRecordFree, + pVbvaMemory->off32Data, pVbvaMemory->off32Free)); +#endif /* DEBUG_sunlover_2 */ + + /* Quick check for "nothing to update" case. */ + if (pVbvaMemory->indexRecordFirst == pVbvaMemory->indexRecordFree) + { + return VINF_SUCCESS; + } + + /* Process the ring buffer */ + unsigned uScreenId; + + /* Initialize dirty rectangles accumulator. */ + VBVADIRTYREGION rgn; + vbvaRgnInit(&rgn, maFramebuffers, mcMonitors, this, pUpPort); + + for (;;) + { + VBVACMDHDR *phdr = NULL; + uint32_t cbCmd = UINT32_MAX; + + /* Fetch the command data. */ + if (!i_vbvaFetchCmd(pVideoAccel, &phdr, &cbCmd)) + { + Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n", + pVbvaMemory->off32Data, pVbvaMemory->off32Free)); + return VERR_INVALID_STATE; + } + + if (cbCmd == uint32_t(~0)) + { + /* No more commands yet in the queue. */ +#ifdef DEBUG_sunlover + LogFlowFunc(("no command\n")); +#endif /* DEBUG_sunlover */ + break; + } + + if (cbCmd != 0) + { +#ifdef DEBUG_sunlover + LogFlowFunc(("hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n", + cbCmd, phdr->x, phdr->y, phdr->w, phdr->h)); +#endif /* DEBUG_sunlover */ + + VBVACMDHDR hdrSaved = *phdr; + + int x = phdr->x; + int y = phdr->y; + int w = phdr->w; + int h = phdr->h; + + uScreenId = mapCoordsToScreen(maFramebuffers, mcMonitors, &x, &y, &w, &h); + + phdr->x = (int16_t)x; + phdr->y = (int16_t)y; + phdr->w = (uint16_t)w; + phdr->h = (uint16_t)h; + + /* Handle the command. + * + * Guest is responsible for updating the guest video memory. + * The Windows guest does all drawing using Eng*. + * + * For local output, only dirty rectangle information is used + * to update changed areas. + * + * Dirty rectangles are accumulated to exclude overlapping updates and + * group small updates to a larger one. + */ + + /* Accumulate the update. */ + vbvaRgnDirtyRect(&rgn, uScreenId, phdr); + + /* Forward the command to VRDP server. */ + mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, phdr, cbCmd); + + *phdr = hdrSaved; + } + + i_vbvaReleaseCmd(pVideoAccel, phdr, cbCmd); + } + + for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++) + { + /* Draw the framebuffer. */ + vbvaRgnUpdateFramebuffer(&rgn, uScreenId); + } + return VINF_SUCCESS; +} + +int Display::i_videoAccelRefreshProcess(PPDMIDISPLAYPORT pUpPort) +{ + int vrc = VWRN_INVALID_STATE; /* Default is to do a display update in VGA device. */ + + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + + videoAccelEnterVGA(pVideoAccel); + + if (pVideoAccel->fVideoAccelEnabled) + { + Assert(pVideoAccel->pVbvaMemory); + vrc = i_videoAccelFlush(pUpPort); + if (RT_FAILURE(vrc)) + { + /* Disable on errors. */ + i_videoAccelEnable(false, NULL, pUpPort); + vrc = VWRN_INVALID_STATE; /* Do a display update in VGA device. */ + } + else + { + vrc = VINF_SUCCESS; + } + } + + videoAccelLeaveVGA(pVideoAccel); + + return vrc; +} + +void Display::processAdapterData(void *pvVRAM, uint32_t u32VRAMSize) +{ + RT_NOREF(u32VRAMSize); + if (pvVRAM == NULL) + { + unsigned i; + for (i = 0; i < mcMonitors; i++) + { + DISPLAYFBINFO *pFBInfo = &maFramebuffers[i]; + + pFBInfo->u32Offset = 0; + pFBInfo->u32MaxFramebufferSize = 0; + pFBInfo->u32InformationSize = 0; + } + } +#ifndef VBOX_WITH_HGSMI + else + { + uint8_t *pu8 = (uint8_t *)pvVRAM; + pu8 += u32VRAMSize - VBOX_VIDEO_ADAPTER_INFORMATION_SIZE; + + /// @todo + uint8_t *pu8End = pu8 + VBOX_VIDEO_ADAPTER_INFORMATION_SIZE; + + VBOXVIDEOINFOHDR *pHdr; + + for (;;) + { + pHdr = (VBOXVIDEOINFOHDR *)pu8; + pu8 += sizeof(VBOXVIDEOINFOHDR); + + if (pu8 >= pu8End) + { + LogRel(("VBoxVideo: Guest adapter information overflow!!!\n")); + break; + } + + if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_DISPLAY) + { + if (pHdr->u16Length != sizeof(VBOXVIDEOINFODISPLAY)) + { + LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "DISPLAY", pHdr->u16Length)); + break; + } + + VBOXVIDEOINFODISPLAY *pDisplay = (VBOXVIDEOINFODISPLAY *)pu8; + + if (pDisplay->u32Index >= mcMonitors) + { + LogRel(("VBoxVideo: Guest adapter information invalid display index %d!!!\n", pDisplay->u32Index)); + break; + } + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[pDisplay->u32Index]; + + pFBInfo->u32Offset = pDisplay->u32Offset; + pFBInfo->u32MaxFramebufferSize = pDisplay->u32FramebufferSize; + pFBInfo->u32InformationSize = pDisplay->u32InformationSize; + + LogRelFlow(("VBOX_VIDEO_INFO_TYPE_DISPLAY: %d: at 0x%08X, size 0x%08X, info 0x%08X\n", pDisplay->u32Index, + pDisplay->u32Offset, pDisplay->u32FramebufferSize, pDisplay->u32InformationSize)); + } + else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_QUERY_CONF32) + { + if (pHdr->u16Length != sizeof(VBOXVIDEOINFOQUERYCONF32)) + { + LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "CONF32", pHdr->u16Length)); + break; + } + + VBOXVIDEOINFOQUERYCONF32 *pConf32 = (VBOXVIDEOINFOQUERYCONF32 *)pu8; + + switch (pConf32->u32Index) + { + case VBOX_VIDEO_QCI32_MONITOR_COUNT: + { + pConf32->u32Value = mcMonitors; + } break; + + case VBOX_VIDEO_QCI32_OFFSCREEN_HEAP_SIZE: + { + /** @todo make configurable. */ + pConf32->u32Value = _1M; + } break; + + default: + LogRel(("VBoxVideo: CONF32 %d not supported!!! Skipping.\n", pConf32->u32Index)); + } + } + else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END) + { + if (pHdr->u16Length != 0) + { + LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length)); + break; + } + + break; + } + else if (pHdr->u8Type != VBOX_VIDEO_INFO_TYPE_NV_HEAP) + { + /** @todo why is Additions/WINNT/Graphics/Miniport/VBoxVideo. cpp pushing this to us? */ + LogRel(("Guest adapter information contains unsupported type %d. The block has been skipped.\n", pHdr->u8Type)); + } + + pu8 += pHdr->u16Length; + } + } +#endif /* !VBOX_WITH_HGSMI */ +} + +void Display::processDisplayData(void *pvVRAM, unsigned uScreenId) +{ + if (uScreenId >= mcMonitors) + { + LogRel(("VBoxVideo: Guest display information invalid display index %d!!!\n", uScreenId)); + return; + } + + /* Get the display information structure. */ + DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId]; + + uint8_t *pu8 = (uint8_t *)pvVRAM; + pu8 += pFBInfo->u32Offset + pFBInfo->u32MaxFramebufferSize; + + /// @todo + uint8_t *pu8End = pu8 + pFBInfo->u32InformationSize; + + VBOXVIDEOINFOHDR *pHdr; + + for (;;) + { + pHdr = (VBOXVIDEOINFOHDR *)pu8; + pu8 += sizeof(VBOXVIDEOINFOHDR); + + if (pu8 >= pu8End) + { + LogRel(("VBoxVideo: Guest display information overflow!!!\n")); + break; + } + + if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_SCREEN) + { + if (pHdr->u16Length != sizeof(VBOXVIDEOINFOSCREEN)) + { + LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "SCREEN", pHdr->u16Length)); + break; + } + + VBOXVIDEOINFOSCREEN *pScreen = (VBOXVIDEOINFOSCREEN *)pu8; + + pFBInfo->xOrigin = pScreen->xOrigin; + pFBInfo->yOrigin = pScreen->yOrigin; + + pFBInfo->w = pScreen->u16Width; + pFBInfo->h = pScreen->u16Height; + + LogRelFlow(("VBOX_VIDEO_INFO_TYPE_SCREEN: (%p) %d: at %d,%d, linesize 0x%X, size %dx%d, bpp %d, flags 0x%02X\n", + pHdr, uScreenId, pScreen->xOrigin, pScreen->yOrigin, pScreen->u32LineSize, pScreen->u16Width, + pScreen->u16Height, pScreen->bitsPerPixel, pScreen->u8Flags)); + + if (uScreenId != VBOX_VIDEO_PRIMARY_SCREEN) + { + /* Primary screen resize is eeeeeeeee by the VGA device. */ + if (pFBInfo->fDisabled) + { + pFBInfo->fDisabled = false; + ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId, + pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h); + } + + i_handleDisplayResize(uScreenId, pScreen->bitsPerPixel, + (uint8_t *)pvVRAM + pFBInfo->u32Offset, + pScreen->u32LineSize, + pScreen->u16Width, pScreen->u16Height, + VBVA_SCREEN_F_ACTIVE, + pScreen->xOrigin, pScreen->yOrigin, false); + } + } + else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END) + { + if (pHdr->u16Length != 0) + { + LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length)); + break; + } + + break; + } + else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_HOST_EVENTS) + { + if (pHdr->u16Length != sizeof(VBOXVIDEOINFOHOSTEVENTS)) + { + LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "HOST_EVENTS", pHdr->u16Length)); + break; + } + + VBOXVIDEOINFOHOSTEVENTS *pHostEvents = (VBOXVIDEOINFOHOSTEVENTS *)pu8; + + pFBInfo->pHostEvents = pHostEvents; + + LogFlow(("VBOX_VIDEO_INFO_TYPE_HOSTEVENTS: (%p)\n", + pHostEvents)); + } + else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_LINK) + { + if (pHdr->u16Length != sizeof(VBOXVIDEOINFOLINK)) + { + LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "LINK", pHdr->u16Length)); + break; + } + + VBOXVIDEOINFOLINK *pLink = (VBOXVIDEOINFOLINK *)pu8; + pu8 += pLink->i32Offset; + } + else + { + LogRel(("Guest display information contains unsupported type %d\n", pHdr->u8Type)); + } + + pu8 += pHdr->u16Length; + } +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp new file mode 100644 index 00000000..7f190307 --- /dev/null +++ b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp @@ -0,0 +1,196 @@ +/* $Id: DisplaySourceBitmapImpl.cpp $ */ +/** @file + * Bitmap of a guest screen implementation. + */ + +/* + * Copyright (C) 2014-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAYSOURCEBITMAP +#include "LoggingNew.h" + +#include "DisplayImpl.h" + +/* + * DisplaySourceBitmap implementation. + */ +DEFINE_EMPTY_CTOR_DTOR(DisplaySourceBitmap) + +HRESULT DisplaySourceBitmap::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void DisplaySourceBitmap::FinalRelease() +{ + uninit(); + + BaseFinalRelease(); +} + +HRESULT DisplaySourceBitmap::init(ComObjPtr<Display> pDisplay, unsigned uScreenId, DISPLAYFBINFO *pFBInfo) +{ + LogFlowThisFunc(("[%u]\n", uScreenId)); + + ComAssertRet(!pDisplay.isNull(), E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m.pDisplay = pDisplay; + m.uScreenId = uScreenId; + m.pFBInfo = pFBInfo; + + m.pu8Allocated = NULL; + + m.pu8Address = NULL; + m.ulWidth = 0; + m.ulHeight = 0; + m.ulBitsPerPixel = 0; + m.ulBytesPerLine = 0; + m.bitmapFormat = BitmapFormat_Opaque; + + int vrc = initSourceBitmap(uScreenId, pFBInfo); + if (RT_FAILURE(vrc)) + return E_FAIL; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +void DisplaySourceBitmap::uninit() +{ + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFunc(("[%u]\n", m.uScreenId)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + m.pDisplay.setNull(); + RTMemFree(m.pu8Allocated); +} + +HRESULT DisplaySourceBitmap::getScreenId(ULONG *aScreenId) +{ + HRESULT hrc = S_OK; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aScreenId = m.uScreenId; + return hrc; +} + +HRESULT DisplaySourceBitmap::queryBitmapInfo(BYTE **aAddress, + ULONG *aWidth, + ULONG *aHeight, + ULONG *aBitsPerPixel, + ULONG *aBytesPerLine, + BitmapFormat_T *aBitmapFormat) +{ + HRESULT hrc = S_OK; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aAddress = m.pu8Address; + *aWidth = m.ulWidth; + *aHeight = m.ulHeight; + *aBitsPerPixel = m.ulBitsPerPixel; + *aBytesPerLine = m.ulBytesPerLine; + *aBitmapFormat = m.bitmapFormat; + + return hrc; +} + +int DisplaySourceBitmap::initSourceBitmap(unsigned aScreenId, + DISPLAYFBINFO *pFBInfo) +{ + RT_NOREF(aScreenId); + int vrc = VINF_SUCCESS; + + if (pFBInfo->w == 0 || pFBInfo->h == 0) + { + return VERR_NOT_SUPPORTED; + } + + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + if (pFBInfo->pu8FramebufferVRAM && pFBInfo->u16BitsPerPixel == 32 && !pFBInfo->fDisabled) + { + /* From VRAM. */ + LogFunc(("%d from VRAM\n", aScreenId)); + pAddress = pFBInfo->pu8FramebufferVRAM; + ulWidth = pFBInfo->w; + ulHeight = pFBInfo->h; + ulBitsPerPixel = pFBInfo->u16BitsPerPixel; + ulBytesPerLine = pFBInfo->u32LineSize; + bitmapFormat = BitmapFormat_BGR; + m.pu8Allocated = NULL; + } + else + { + /* Allocated byffer */ + LogFunc(("%d allocated\n", aScreenId)); + pAddress = NULL; + ulWidth = pFBInfo->w; + ulHeight = pFBInfo->h; + ulBitsPerPixel = 32; + ulBytesPerLine = ulWidth * 4; + bitmapFormat = BitmapFormat_BGR; + + m.pu8Allocated = (uint8_t *)RTMemAlloc(ulBytesPerLine * ulHeight); + if (m.pu8Allocated == NULL) + { + vrc = VERR_NO_MEMORY; + } + else + { + pAddress = m.pu8Allocated; + } + } + + if (RT_SUCCESS(vrc)) + { + m.pu8Address = pAddress; + m.ulWidth = ulWidth; + m.ulHeight = ulHeight; + m.ulBitsPerPixel = ulBitsPerPixel; + m.ulBytesPerLine = ulBytesPerLine; + m.bitmapFormat = bitmapFormat; + if (pFBInfo->fDisabled) + { + RT_BZERO(pAddress, ulBytesPerLine * ulHeight); + } + } + + return vrc; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/DrvAudioRec.cpp b/src/VBox/Main/src-client/DrvAudioRec.cpp new file mode 100644 index 00000000..b30b4f87 --- /dev/null +++ b/src/VBox/Main/src-client/DrvAudioRec.cpp @@ -0,0 +1,972 @@ +/* $Id: DrvAudioRec.cpp $ */ +/** @file + * Video recording audio backend for Main. + * + * This driver is part of Main and is responsible for providing audio + * data to Main's video capturing feature. + * + * The driver itself implements a PDM host audio backend, which in turn + * provides the driver with the required audio data and audio events. + * + * For now there is support for the following destinations (called "sinks"): + * + * - Direct writing of .webm files to the host. + * - Communicating with Main via the Console object to send the encoded audio data to. + * The Console object in turn then will route the data to the Display / video capturing interface then. + */ + +/* + * Copyright (C) 2016-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_RECORDING +#include "LoggingNew.h" + +#include "DrvAudioRec.h" +#include "ConsoleImpl.h" + +#include "WebMWriter.h" + +#include <iprt/mem.h> +#include <iprt/cdefs.h> + +#include "VBox/com/VirtualBox.h" +#include <VBox/vmm/cfgm.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmaudioinline.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/err.h> +#include "VBox/settings.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Enumeration for specifying the recording container type. + */ +typedef enum AVRECCONTAINERTYPE +{ + /** Unknown / invalid container type. */ + AVRECCONTAINERTYPE_UNKNOWN = 0, + /** Recorded data goes to Main / Console. */ + AVRECCONTAINERTYPE_MAIN_CONSOLE = 1, + /** Recorded data will be written to a .webm file. */ + AVRECCONTAINERTYPE_WEBM = 2 +} AVRECCONTAINERTYPE; + +/** + * Structure for keeping generic container parameters. + */ +typedef struct AVRECCONTAINERPARMS +{ + /** Stream index (hint). */ + uint32_t idxStream; + /** The container's type. */ + AVRECCONTAINERTYPE enmType; + union + { + /** WebM file specifics. */ + struct + { + /** Allocated file name to write .webm file to. Must be free'd. */ + char *pszFile; + } WebM; + }; + +} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS; + +/** + * Structure for keeping container-specific data. + */ +typedef struct AVRECCONTAINER +{ + /** Generic container parameters. */ + AVRECCONTAINERPARMS Parms; + + union + { + struct + { + /** Pointer to Console. */ + Console *pConsole; + } Main; + + struct + { + /** Pointer to WebM container to write recorded audio data to. + * See the AVRECMODE enumeration for more information. */ + WebMWriter *pWebM; + /** Assigned track number from WebM container. */ + uint8_t uTrack; + } WebM; + }; +} AVRECCONTAINER, *PAVRECCONTAINER; + +/** + * Audio video recording sink. + */ +typedef struct AVRECSINK +{ + /** Pointer (weak) to recording stream to bind to. */ + RecordingStream *pRecStream; + /** Container data to use for data processing. */ + AVRECCONTAINER Con; + /** Timestamp (in ms) of when the sink was created. */ + uint64_t tsStartMs; +} AVRECSINK, *PAVRECSINK; + +/** + * Audio video recording (output) stream. + */ +typedef struct AVRECSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** (Audio) frame buffer. */ + PRTCIRCBUF pCircBuf; + /** Pointer to sink to use for writing. */ + PAVRECSINK pSink; + /** Last encoded PTS (in ms). */ + uint64_t uLastPTSMs; + /** Temporary buffer for the input (source) data to encode. */ + void *pvSrcBuf; + /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */ + size_t cbSrcBuf; +} AVRECSTREAM, *PAVRECSTREAM; + +/** + * Video recording audio driver instance data. + */ +typedef struct DRVAUDIORECORDING +{ + /** Pointer to audio video recording object. */ + AudioVideoRec *pAudioVideoRec; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Pointer to the console object. */ + ComPtr<Console> pConsole; + /** Pointer to the DrvAudio port interface that is above us. */ + AVRECCONTAINERPARMS ContainerParms; + /** Weak pointer to recording context to use. */ + RecordingContext *pRecCtx; + /** The driver's sink for writing output to. */ + AVRECSINK Sink; +} DRVAUDIORECORDING, *PDRVAUDIORECORDING; + + +AudioVideoRec::AudioVideoRec(Console *pConsole) + : AudioDriver(pConsole) + , mpDrv(NULL) +{ +} + + +AudioVideoRec::~AudioVideoRec(void) +{ + if (mpDrv) + { + mpDrv->pAudioVideoRec = NULL; + mpDrv = NULL; + } +} + + +/** + * Applies recording settings to this driver instance. + * + * @returns VBox status code. + * @param Settings Recording settings to apply. + */ +int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings) +{ + /** @todo Do some validation here. */ + mSettings = Settings; /* Note: Does have an own copy operator. */ + return VINF_SUCCESS; +} + + +int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM) +{ + /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */ + unsigned const idxScreen = 0; + + AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER); + const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen]; + + int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest); + AssertRCReturn(vrc, vrc); + if (screenSettings.enmDest == RecordingDestination_File) + { + vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str()); + AssertRCReturn(vrc, vrc); + } + + vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen); + AssertRCReturn(vrc, vrc); + + return AudioDriver::configureDriver(pLunCfg, pVMM); +} + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec"); + pBackendCfg->cbStream = sizeof(AVRECSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsIn = 0; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Creates an audio output stream and associates it with the specified recording sink. + * + * @returns VBox status code. + * @param pThis Driver instance. + * @param pStreamAV Audio output stream to create. + * @param pSink Recording sink to associate audio output stream to. + * @param pCfgReq Requested configuration by the audio backend. + * @param pCfgAcq Acquired configuration by the audio output stream. + */ +static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV, + PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); + AssertPtrReturn(pSink, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT) + { + LogRel(("Recording: Support for surround audio not implemented yet\n")); + AssertFailed(); + return VERR_NOT_SUPPORTED; + } + + PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec(); + + /* Stuff which has to be set by now. */ + Assert(pCodec->Parms.cbFrame); + Assert(pCodec->Parms.msFrame); + + int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */); + if (RT_SUCCESS(vrc)) + { + size_t cbScratchBuf = pCodec->Parms.cbFrame; + pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf); + if (pStreamAV->pvSrcBuf) + { + pStreamAV->cbSrcBuf = cbScratchBuf; + + pStreamAV->pSink = pSink; /* Assign sink to stream. */ + pStreamAV->uLastPTSMs = 0; + + /* Make sure to let the driver backend know that we need the audio data in + * a specific sampling rate the codec is optimized for. */ + pCfgAcq->Props = pCodec->Parms.Audio.PCMProps; + + /* Every codec frame marks a period for now. Optimize this later. */ + pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame); + pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2; + pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod; + } + else + vrc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + return VERR_NOT_SUPPORTED; + + /* For now we only have one sink, namely the driver's one. + * Later each stream could have its own one, to e.g. router different stream to different sinks .*/ + PAVRECSINK pSink = &pThis->Sink; + + int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq); + PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq); + + return vrc; +} + + +/** + * Destroys (closes) an audio output stream. + * + * @returns VBox status code. + * @param pThis Driver instance. + * @param pStreamAV Audio output stream to destroy. + */ +static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV) +{ + RT_NOREF(pThis); + + if (pStreamAV->pCircBuf) + { + RTCircBufDestroy(pStreamAV->pCircBuf); + pStreamAV->pCircBuf = NULL; + } + + if (pStreamAV->pvSrcBuf) + { + Assert(pStreamAV->cbSrcBuf); + RTMemFree(pStreamAV->pvSrcBuf); + pStreamAV->pvSrcBuf = NULL; + pStreamAV->cbSrcBuf = 0; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + RT_NOREF(fImmediate); + + int vrc = VINF_SUCCESS; + if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT) + vrc = avRecDestroyStreamOut(pThis, pStreamAV); + + return vrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + + RecordingStream *pRecStream = pStreamAV->pSink->pRecStream; + PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec(); + + return pCodec->Parms.cbFrame; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(pcbWritten, VERR_INVALID_PARAMETER); + + int vrc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + + PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf; + AssertPtr(pCircBuf); + + uint32_t cbToWrite = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf)); + AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW); + + /* + * Write as much as we can into our internal ring buffer. + */ + while (cbToWrite) + { + void *pvCircBuf = NULL; + size_t cbCircBuf = 0; + RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf); + + Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf)); + + memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf), + cbWrittenTotal += (uint32_t)cbCircBuf; + Assert(cbWrittenTotal <= cbBuf); + Assert(cbToWrite >= cbCircBuf); + cbToWrite -= (uint32_t)cbCircBuf; + + RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf); + } + + RecordingStream *pRecStream = pStreamAV->pSink->pRecStream; + PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec(); + + /* + * Process our internal ring buffer and send the obtained audio data to our encoding thread. + */ + cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf); + + /** @todo Can we encode more than a frame at a time? Optimize this! */ + uint32_t const cbFrame = pCodec->Parms.cbFrame; + + /* Only encode data if we have data for at least one full codec frame. */ + while (cbToWrite >= cbFrame) + { + uint32_t cbSrc = 0; + do + { + void *pvCircBuf = NULL; + size_t cbCircBuf = 0; + RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf); + + Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf)); + + memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf); + + cbSrc += (uint32_t)cbCircBuf; + Assert(cbSrc <= pStreamAV->cbSrcBuf); + Assert(cbSrc <= cbFrame); + + RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf); + + if (cbSrc == cbFrame) /* Only send full codec frames. */ + { + vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS()); + if (RT_FAILURE(vrc)) + break; + } + + } while (cbSrc < cbFrame); + + Assert(cbToWrite >= cbFrame); + cbToWrite -= cbFrame; + + if (RT_FAILURE(vrc)) + break; + + } /* while */ + + *pcbWritten = cbWrittenTotal; + + LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc)); + return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */ +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return 0; /* Video capturing does not provide any input. */ +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface, pStream, pvBuf, cbBuf); + *pcbRead = 0; + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * Shuts down (closes) a recording sink, + * + * @returns VBox status code. + * @param pSink Recording sink to shut down. + */ +static void avRecSinkShutdown(PAVRECSINK pSink) +{ + AssertPtrReturnVoid(pSink); + + pSink->pRecStream = NULL; + + switch (pSink->Con.Parms.enmType) + { + case AVRECCONTAINERTYPE_WEBM: + { + if (pSink->Con.WebM.pWebM) + { + LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n", + pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize())); + + int vrc2 = pSink->Con.WebM.pWebM->Close(); + AssertRC(vrc2); + + delete pSink->Con.WebM.pWebM; + pSink->Con.WebM.pWebM = NULL; + } + break; + } + + case AVRECCONTAINERTYPE_MAIN_CONSOLE: + RT_FALL_THROUGH(); + default: + break; + } +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); + LogFlowFuncEnter(); + avRecSinkShutdown(&pThis->Sink); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnDestruct} + */ +/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); + + LogFlowFuncEnter(); + + switch (pThis->ContainerParms.enmType) + { + case AVRECCONTAINERTYPE_WEBM: + { + avRecSinkShutdown(&pThis->Sink); + RTStrFree(pThis->ContainerParms.WebM.pszFile); + break; + } + + default: + break; + } + + /* + * If the AudioVideoRec object is still alive, we must clear it's reference to + * us since we'll be invalid when we return from this method. + */ + if (pThis->pAudioVideoRec) + { + pThis->pAudioVideoRec->mpDrv = NULL; + pThis->pAudioVideoRec = NULL; + } + + LogFlowFuncLeave(); +} + + +/** + * Initializes a recording sink. + * + * @returns VBox status code. + * @param pThis Driver instance. + * @param pSink Sink to initialize. + * @param pConParms Container parameters to set. + * @param pStream Recording stream to asssign sink to. + */ +static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream) +{ + pSink->pRecStream = pStream; + + int vrc = VINF_SUCCESS; + + /* + * Container setup. + */ + try + { + switch (pConParms->enmType) + { + case AVRECCONTAINERTYPE_MAIN_CONSOLE: + { + if (pThis->pConsole) + { + pSink->Con.Main.pConsole = pThis->pConsole; + } + else + vrc = VERR_NOT_SUPPORTED; + break; + } + + case AVRECCONTAINERTYPE_WEBM: + { + #if 0 + /* If we only record audio, create our own WebM writer instance here. */ + if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */ + { + /** @todo Add sink name / number to file name. */ + const char *pszFile = pSink->Con.Parms.WebM.pszFile; + AssertPtr(pszFile); + + pSink->Con.WebM.pWebM = new WebMWriter(); + vrc = pSink->Con.WebM.pWebM->Open(pszFile, + /** @todo Add option to add some suffix if file exists instead of overwriting? */ + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, + pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None); + if (RT_SUCCESS(vrc)) + { + const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps; + + vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec, + PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), + PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack); + if (RT_SUCCESS(vrc)) + { + LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile)); + } + else + LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc)); + } + else + LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc)); + } + break; + #endif + } + + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(vrc)) + { + pSink->Con.Parms.enmType = pConParms->enmType; + pSink->tsStartMs = RTTimeMilliTS(); + + return VINF_SUCCESS; + } + + LogRel(("Recording: Error creating sink (%Rrc)\n", vrc)); + return vrc; +} + + +/** + * Construct a audio video recording driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); + RT_NOREF(fFlags); + + LogRel(("Audio: Initializing video recording audio driver\n")); + LogFlowFunc(("fFlags=0x%x\n", fFlags)); + + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture; + + /* + * Read configuration. + */ + PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3; + /** @todo validate it. */ + + /* + * Get the Console object pointer. + */ + com::Guid ConsoleUuid(COM_IIDOF(IConsole)); + IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw()); + AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3); + Console *pConsole = static_cast<Console *>(pIConsole); + AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3); + + pThis->pConsole = pConsole; + AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER); + pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv(); + AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER); + + pThis->pAudioVideoRec->mpDrv = pThis; + + /* + * Get the recording container parameters from the audio driver instance. + */ + RT_ZERO(pThis->ContainerParms); + PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms; + + int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream); + AssertRCReturn(vrc, vrc); + + vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType); + AssertRCReturn(vrc, vrc); + + switch (pConParams->enmType) + { + case AVRECCONTAINERTYPE_WEBM: + vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile); + AssertRCReturn(vrc, vrc); + break; + + default: + break; + } + + /* + * Obtain the recording context. + */ + pThis->pRecCtx = pConsole->i_recordingGetContext(); + AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER); + + /* + * Get the codec configuration. + */ + RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + /* + * Init the recording sink. + */ + vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream); + if (RT_SUCCESS(vrc)) + LogRel2(("Recording: Audio recording driver initialized\n")); + else + LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc)); + + return vrc; +} + + +/** + * Video recording audio driver registration record. + */ +const PDMDRVREG AudioVideoRec::DrvReg = +{ + PDM_DRVREG_VERSION, + /* szName */ + "AudioVideoRec", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Audio driver for video recording", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVAUDIORECORDING), + /* pfnConstruct */ + AudioVideoRec::drvConstruct, + /* pfnDestruct */ + AudioVideoRec::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + AudioVideoRec::drvPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; diff --git a/src/VBox/Main/src-client/DrvAudioVRDE.cpp b/src/VBox/Main/src-client/DrvAudioVRDE.cpp new file mode 100644 index 00000000..62615bff --- /dev/null +++ b/src/VBox/Main/src-client/DrvAudioVRDE.cpp @@ -0,0 +1,823 @@ +/* $Id: DrvAudioVRDE.cpp $ */ +/** @file + * VRDE audio backend for Main. + */ + +/* + * Copyright (C) 2013-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include "LoggingNew.h" + +#include <VBox/log.h> +#include "DrvAudioVRDE.h" +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" + +#include <iprt/mem.h> +#include <iprt/cdefs.h> +#include <iprt/circbuf.h> + +#include <VBox/vmm/cfgm.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmaudioinline.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/RemoteDesktop/VRDE.h> +#include <VBox/err.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * VRDE stream. + */ +typedef struct VRDESTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + union + { + struct + { + /** Circular buffer for holding the recorded audio frames from the host. */ + PRTCIRCBUF pCircBuf; + } In; + }; +} VRDESTREAM; +/** Pointer to a VRDE stream. */ +typedef VRDESTREAM *PVRDESTREAM; + +/** + * VRDE (host) audio driver instance data. + */ +typedef struct DRVAUDIOVRDE +{ + /** Pointer to audio VRDE object. */ + AudioVRDE *pAudioVRDE; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the VRDP's console object. */ + ConsoleVRDPServer *pConsoleVRDPServer; + /** Number of connected clients to this VRDE instance. */ + uint32_t cClients; + /** Interface to the driver above us (DrvAudio). */ + PDMIHOSTAUDIOPORT *pIHostAudioPort; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; +} DRVAUDIOVRDE; +/** Pointer to the instance data for an VRDE audio driver. */ +typedef DRVAUDIOVRDE *PDRVAUDIOVRDE; + + +/********************************************************************************************************************************* +* Class AudioVRDE * +*********************************************************************************************************************************/ + +AudioVRDE::AudioVRDE(Console *pConsole) + : AudioDriver(pConsole) + , mpDrv(NULL) +{ + RTCritSectInit(&mCritSect); +} + + +AudioVRDE::~AudioVRDE(void) +{ + RTCritSectEnter(&mCritSect); + if (mpDrv) + { + mpDrv->pAudioVRDE = NULL; + mpDrv = NULL; + } + RTCritSectLeave(&mCritSect); + RTCritSectDelete(&mCritSect); +} + + +int AudioVRDE::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM) +{ + return AudioDriver::configureDriver(pLunCfg, pVMM); +} + + +void AudioVRDE::onVRDEClientConnect(uint32_t uClientID) +{ + RT_NOREF(uClientID); + + RTCritSectEnter(&mCritSect); + if (mpDrv) + { + mpDrv->cClients++; + LogRel2(("Audio: VRDE client connected (#%u)\n", mpDrv->cClients)); + +#if 0 /* later, maybe */ + /* + * The first client triggers a device change event in both directions + * so that can start talking to the audio device. + * + * Note! Should be okay to stay in the critical section here, as it's only + * used at construction and destruction time. + */ + if (mpDrv->cClients == 1) + { + VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns); + if (enmState <= VMSTATE_POWERING_OFF) + { + PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort; + AssertPtr(pIHostAudioPort); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/); + } + } +#endif + } + RTCritSectLeave(&mCritSect); +} + + +void AudioVRDE::onVRDEClientDisconnect(uint32_t uClientID) +{ + RT_NOREF(uClientID); + + RTCritSectEnter(&mCritSect); + if (mpDrv) + { + Assert(mpDrv->cClients > 0); + mpDrv->cClients--; + LogRel2(("Audio: VRDE client disconnected (%u left)\n", mpDrv->cClients)); +#if 0 /* later maybe */ + /* + * The last client leaving triggers a device change event in both + * directions so the audio devices can stop wasting time trying to + * talk to us. (There is an additional safeguard in + * drvAudioVrdeHA_StreamGetStatus.) + */ + if (mpDrv->cClients == 0) + { + VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns); + if (enmState <= VMSTATE_POWERING_OFF) + { + PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort; + AssertPtr(pIHostAudioPort); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/); + } + } +#endif + } + RTCritSectLeave(&mCritSect); +} + + +int AudioVRDE::onVRDEControl(bool fEnable, uint32_t uFlags) +{ + RT_NOREF(fEnable, uFlags); + LogFlowThisFunc(("fEnable=%RTbool, uFlags=0x%x\n", fEnable, uFlags)); + + if (mpDrv == NULL) + return VERR_INVALID_STATE; + + return VINF_SUCCESS; /* Never veto. */ +} + + +/** + * Marks the beginning of sending captured audio data from a connected + * RDP client. + * + * @returns VBox status code. + * @param pvContext The context; in this case a pointer to a + * VRDESTREAMIN structure. + * @param pVRDEAudioBegin Pointer to a VRDEAUDIOINBEGIN structure. + */ +int AudioVRDE::onVRDEInputBegin(void *pvContext, PVRDEAUDIOINBEGIN pVRDEAudioBegin) +{ + AssertPtrReturn(pvContext, VERR_INVALID_POINTER); + AssertPtrReturn(pVRDEAudioBegin, VERR_INVALID_POINTER); + PVRDESTREAM pVRDEStrmIn = (PVRDESTREAM)pvContext; + AssertPtrReturn(pVRDEStrmIn, VERR_INVALID_POINTER); + +#ifdef LOG_ENABLED + VRDEAUDIOFORMAT const audioFmt = pVRDEAudioBegin->fmt; + LogFlowFunc(("cbSample=%RU32, iSampleHz=%d, cChannels=%d, cBits=%d, fUnsigned=%RTbool\n", + VRDE_AUDIO_FMT_BYTES_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SAMPLE_FREQ(audioFmt), + VRDE_AUDIO_FMT_CHANNELS(audioFmt), VRDE_AUDIO_FMT_BITS_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SIGNED(audioFmt))); +#endif + + return VINF_SUCCESS; +} + + +int AudioVRDE::onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData) +{ + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pvContext; + AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER); + LogFlowFunc(("cbData=%#x\n", cbData)); + + void *pvBuf = NULL; + size_t cbBuf = 0; + RTCircBufAcquireWriteBlock(pStreamVRDE->In.pCircBuf, cbData, &pvBuf, &cbBuf); + + if (cbBuf) + memcpy(pvBuf, pvData, cbBuf); + + RTCircBufReleaseWriteBlock(pStreamVRDE->In.pCircBuf, cbBuf); + + if (cbBuf < cbData) + LogRelMax(999, ("VRDE: Capturing audio data lost %zu bytes\n", cbData - cbBuf)); /** @todo Use an error counter. */ + + return VINF_SUCCESS; /** @todo r=andy How to tell the caller if we were not able to handle *all* input data? */ +} + + +int AudioVRDE::onVRDEInputEnd(void *pvContext) +{ + RT_NOREF(pvContext); + return VINF_SUCCESS; +} + + +int AudioVRDE::onVRDEInputIntercept(bool fEnabled) +{ + RT_NOREF(fEnabled); + return VINF_SUCCESS; /* Never veto. */ +} + + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VRDE"); + pBackendCfg->cbStream = sizeof(VRDESTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVrdeHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVAUDIOVRDE pThis = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + /* + * Only create a stream if we have clients. + */ + int vrc; + NOREF(pThis); +#if 0 /* later maybe */ + if (pThis->cClients == 0) + { + LogFunc(("No clients, failing with VERR_AUDIO_STREAM_COULD_NOT_CREATE.\n")); + vrc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + else +#endif + { + /* + * The VRDP server does its own mixing and resampling because it may be + * sending the audio to any number of different clients all with different + * formats (including clients which hasn't yet connected). So, it desires + * the raw data from the mixer (somewhat akind to stereo signed 64-bit, + * see st_sample_t and PDMAUDIOFRAME). + */ + PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/, + 22050 /*Hz - VRDP_AUDIO_CHUNK_INTERNAL_FREQ_HZ*/, + true /*fLittleEndian*/, true /*fRaw*/); + + /* According to the VRDP docs (VRDP_AUDIO_CHUNK_TIME_MS), the VRDP server + stores audio in 200ms chunks. */ + const uint32_t cFramesVrdpServer = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 200 /*ms*/); + + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + pCfgAcq->Backend.cFramesBufferSize = cFramesVrdpServer; + pCfgAcq->Backend.cFramesPeriod = cFramesVrdpServer / 4; /* This is utter non-sense, but whatever. */ + pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * cFramesVrdpServer + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + vrc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, PDMAudioPropsFramesToBytes(&pCfgAcq->Props, cFramesVrdpServer)); + } + else + { + /** @todo r=bird: So, if VRDP does 200ms chunks, why do we report 100ms + * buffer and 20ms period? How does these parameters at all correlate + * with the above comment?!? */ + pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 20 /*ms*/); + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); + pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2; + vrc = VINF_SUCCESS; + } + + PDMAudioStrmCfgCopy(&pStreamVRDE->Cfg, pCfgAcq); + } + return vrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER); + RT_NOREF(fImmediate); + + if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN) + { + LogFlowFunc(("Calling SendAudioInputEnd\n")); + if (pDrv->pConsoleVRDPServer) + pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL); + + if (pStreamVRDE->In.pCircBuf) + { + RTCircBufDestroy(pStreamVRDE->In.pCircBuf); + pStreamVRDE->In.pCircBuf = NULL; + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + int vrc; + if (!pDrv->pConsoleVRDPServer) + { + LogRelMax(32, ("Audio: VRDP console not ready (enable)\n")); + vrc = VERR_AUDIO_STREAM_NOT_READY; + } + else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN) + { + vrc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE, + PDMAudioPropsMilliToFrames(&pStreamVRDE->Cfg.Props, 200 /*ms*/), + PDMAudioPropsHz(&pStreamVRDE->Cfg.Props), + PDMAudioPropsChannels(&pStreamVRDE->Cfg.Props), + PDMAudioPropsSampleBits(&pStreamVRDE->Cfg.Props)); + LogFlowFunc(("SendAudioInputBegin returns %Rrc\n", vrc)); + if (vrc == VERR_NOT_SUPPORTED) + { + LogRelMax(64, ("Audio: No VRDE client connected, so no input recording available\n")); + vrc = VERR_AUDIO_STREAM_NOT_READY; + } + } + else + vrc = VINF_SUCCESS; + LogFlowFunc(("returns %Rrc\n", vrc)); + return vrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + int vrc; + if (!pDrv->pConsoleVRDPServer) + { + LogRelMax(32, ("Audio: VRDP console not ready (disable)\n")); + vrc = VERR_AUDIO_STREAM_NOT_READY; + } + else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN) + { + LogFlowFunc(("Calling SendAudioInputEnd\n")); + pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */); + vrc = VINF_SUCCESS; + } + else + vrc = VINF_SUCCESS; + LogFlowFunc(("returns %Rrc\n", vrc)); + return vrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + RT_NOREF(pStream); + + if (!pDrv->pConsoleVRDPServer) + { + LogRelMax(32, ("Audio: VRDP console not ready (pause)\n")); + return VERR_AUDIO_STREAM_NOT_READY; + } + LogFlowFunc(("returns VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + RT_NOREF(pStream); + + if (!pDrv->pConsoleVRDPServer) + { + LogRelMax(32, ("Audio: VRDP console not ready (resume)\n")); + return VERR_AUDIO_STREAM_NOT_READY; + } + LogFlowFunc(("returns VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + LogFlowFunc(("returns VINF_SUCCESS\n")); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVrdeHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + return pDrv->cClients > 0 ? PDMHOSTAUDIOSTREAMSTATE_OKAY : PDMHOSTAUDIOSTREAMSTATE_INACTIVE; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */ + if (pDrv->cClients) + return PDMAudioPropsFramesToBytes(&pStreamVRDE->Cfg.Props, pStreamVRDE->Cfg.Backend.cFramesBufferSize); + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + AssertPtr(pDrv); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + if (!pDrv->pConsoleVRDPServer) + return VERR_NOT_AVAILABLE; + + /* Prepate the format. */ + PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props; + VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), + PDMAudioPropsChannels(pProps), + PDMAudioPropsSampleBits(pProps), + pProps->fSigned); + Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true)); + + /** @todo r=bird: there was some incoherent mumbling about "using the + * internal counter to track if we (still) can write to the VRDP + * server or if need to wait another round (time slot)". However it + * wasn't accessing any internal counter nor doing anything else + * sensible, so I've removed it. */ + + uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStream->pStream->Cfg.Props, cbBuf); + Assert(cFrames == cbBuf / (sizeof(uint64_t) * 2)); + pDrv->pConsoleVRDPServer->SendAudioSamples(pvBuf, cFrames, uVrdpFormat); + + Log3Func(("cFramesWritten=%RU32\n", cFrames)); + *pcbWritten = PDMAudioPropsFramesToBytes(&pStream->pStream->Cfg.Props, cFrames); + Assert(*pcbWritten == cbBuf); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + AssertReturn(pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN, 0); + uint32_t cbRet = (uint32_t)RTCircBufUsed(pStreamVRDE->In.pCircBuf); + Log4Func(("returns %#x\n", cbRet)); + return cbRet; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER); + + *pcbRead = 0; + while (cbBuf > 0 && RTCircBufUsed(pStreamVRDE->In.pCircBuf) > 0) + { + size_t cbData = 0; + void *pvData = NULL; + RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData); + + memcpy(pvBuf, pvData, cbData); + + RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData); + + *pcbRead += (uint32_t)cbData; + cbBuf -= (uint32_t)cbData; + pvData = (uint8_t *)pvData + cbData; + } + + LogFlowFunc(("returns %#x bytes\n", *pcbRead)); + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE); + LogFlowFuncEnter(); + + if (pThis->pConsoleVRDPServer) + pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnDestruct} + */ +/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE); + LogFlowFuncEnter(); + + /** @todo For runtime detach maybe: + if (pThis->pConsoleVRDPServer) + pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */ + + /* + * If the AudioVRDE object is still alive, we must clear it's reference to + * us since we'll be invalid when we return from this method. + */ + AudioVRDE *pAudioVRDE = pThis->pAudioVRDE; + if (pAudioVRDE) + { + RTCritSectEnter(&pAudioVRDE->mCritSect); + pAudioVRDE->mpDrv = NULL; + pThis->pAudioVRDE = NULL; + RTCritSectLeave(&pAudioVRDE->mCritSect); + } +} + + +/** + * Construct a VRDE audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +/* static */ +DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE); + RT_NOREF(fFlags); + + AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + + LogRel(("Audio: Initializing VRDE driver\n")); + LogFlowFunc(("fFlags=0x%x\n", fFlags)); + + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + pThis->cClients = 0; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvAudioVrdeHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvAudioVrdeHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvAudioVrdeHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvAudioVrdeHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvAudioVrdeHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvAudioVrdeHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture; + + /* + * Resolve the interface to the driver above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + + /* Get the Console object pointer. */ + com::Guid ConsoleUuid(COM_IIDOF(IConsole)); + IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw()); + AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3); + Console *pConsole = static_cast<Console *>(pIConsole); + AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3); + + /* Get the console VRDP object pointer. */ + pThis->pConsoleVRDPServer = pConsole->i_consoleVRDPServer(); + AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer, + ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER); + + /* Get the AudioVRDE object pointer. */ + pThis->pAudioVRDE = pConsole->i_getAudioVRDE(); + AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER); + RTCritSectEnter(&pThis->pAudioVRDE->mCritSect); + pThis->pAudioVRDE->mpDrv = pThis; + RTCritSectLeave(&pThis->pAudioVRDE->mCritSect); + + return VINF_SUCCESS; +} + + +/** + * VRDE audio driver registration record. + */ +const PDMDRVREG AudioVRDE::DrvReg = +{ + PDM_DRVREG_VERSION, + /* szName */ + "AudioVRDE", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Audio driver for VRDE backend", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVAUDIOVRDE), + /* pfnConstruct */ + AudioVRDE::drvConstruct, + /* pfnDestruct */ + AudioVRDE::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + AudioVRDE::drvPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Main/src-client/EBMLWriter.cpp b/src/VBox/Main/src-client/EBMLWriter.cpp new file mode 100644 index 00000000..270b35f6 --- /dev/null +++ b/src/VBox/Main/src-client/EBMLWriter.cpp @@ -0,0 +1,275 @@ +/* $Id: EBMLWriter.cpp $ */ +/** @file + * EBMLWriter.cpp - EBML writer implementation. + */ + +/* + * Copyright (C) 2013-2022 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 + */ + +/** + * For more information, see: + * - https://w3c.github.io/media-source/webm-byte-stream-format.html + * - https://www.webmproject.org/docs/container/#muxer-guidelines + */ + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY +#include "LoggingNew.h" + +#include <list> +#include <map> +#include <queue> +#include <stack> + +#include <math.h> /* For lround.h. */ + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/cdefs.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/rand.h> +#include <iprt/string.h> + +#include <VBox/log.h> +#include <VBox/version.h> + +#include "EBMLWriter.h" +#include "EBML_MKV.h" + +/** No flags set. */ +#define VBOX_EBMLWRITER_FLAG_NONE 0 +/** The file handle was inherited. */ +#define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0) + +/** Creates an EBML output file using an existing, open file handle. */ +int EBMLWriter::createEx(const char *a_pszFile, PRTFILE phFile) +{ + AssertPtrReturn(phFile, VERR_INVALID_POINTER); + + m_hFile = *phFile; + m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED; + m_strFile = a_pszFile; + + return VINF_SUCCESS; +} + +/** Creates an EBML output file using a file name. */ +int EBMLWriter::create(const char *a_pszFile, uint64_t fOpen) +{ + int vrc = RTFileOpen(&m_hFile, a_pszFile, fOpen); + if (RT_SUCCESS(vrc)) + m_strFile = a_pszFile; + + return vrc; +} + +/** Returns available space on storage. */ +uint64_t EBMLWriter::getAvailableSpace(void) +{ + RTFOFF pcbFree; + int vrc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0); + return (RT_SUCCESS(vrc)? (uint64_t)pcbFree : UINT64_MAX); +} + +/** Closes the file. */ +void EBMLWriter::close(void) +{ + if (!isOpen()) + return; + + AssertMsg(m_Elements.size() == 0, + ("%zu elements are not closed yet (next element to close is 0x%x)\n", + m_Elements.size(), m_Elements.top().classId)); + + if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED)) + { + RTFileClose(m_hFile); + m_hFile = NIL_RTFILE; + } + + m_fFlags = VBOX_EBMLWRITER_FLAG_NONE; + m_strFile = ""; +} + +/** Starts an EBML sub-element. */ +EBMLWriter& EBMLWriter::subStart(EbmlClassId classId) +{ + writeClassId(classId); + /* store the current file offset. */ + m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId)); + /* Indicates that size of the element + * is unkown (as according to EBML specs). + */ + writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF)); + return *this; +} + +/** Ends an EBML sub-element. */ +EBMLWriter& EBMLWriter::subEnd(EbmlClassId classId) +{ +#ifdef VBOX_STRICT + /* Class ID on the top of the stack should match the class ID passed + * to the function. Otherwise it may mean that we have a bug in the code. + */ + AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n")); + AssertMsg(m_Elements.top().classId == classId, + ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId)); +#else + RT_NOREF(classId); +#endif + + uint64_t uPos = RTFileTell(m_hFile); + uint64_t uSize = uPos - m_Elements.top().offset - 8; + RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL); + + /* Make sure that size will be serialized as uint64_t. */ + writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000)); + RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL); + m_Elements.pop(); + return *this; +} + +/** Serializes a null-terminated string. */ +EBMLWriter& EBMLWriter::serializeString(EbmlClassId classId, const char *str) +{ + writeClassId(classId); + uint64_t size = strlen(str); + writeSize(size); + write(str, size); + return *this; +} + +/** Serializes an UNSIGNED integer. + * If size is zero then it will be detected automatically. */ +EBMLWriter& EBMLWriter::serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size /* = 0 */) +{ + writeClassId(classId); + if (!size) size = getSizeOfUInt(parm); + writeSize(size); + writeUnsignedInteger(parm, size); + return *this; +} + +/** Serializes a floating point value. + * + * Only 8-bytes double precision values are supported + * by this function. + */ +EBMLWriter& EBMLWriter::serializeFloat(EbmlClassId classId, float value) +{ + writeClassId(classId); + Assert(sizeof(uint32_t) == sizeof(float)); + writeSize(sizeof(float)); + + union + { + float f; + uint8_t u8[4]; + } u; + + u.f = value; + + for (int i = 3; i >= 0; i--) /* Converts values to big endian. */ + write(&u.u8[i], 1); + + return *this; +} + +/** Serializes binary data. */ +EBMLWriter& EBMLWriter::serializeData(EbmlClassId classId, const void *pvData, size_t cbData) +{ + writeClassId(classId); + writeSize(cbData); + write(pvData, cbData); + return *this; +} + +/** Writes raw data to file. */ +int EBMLWriter::write(const void *data, size_t size) +{ + return RTFileWrite(m_hFile, data, size, NULL); +} + +/** Writes an unsigned integer of variable of fixed size. */ +void EBMLWriter::writeUnsignedInteger(uint64_t value, size_t size /* = sizeof(uint64_t) */) +{ + /* convert to big-endian */ + value = RT_H2BE_U64(value); + write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size); +} + +/** Writes EBML class ID to file. + * + * EBML ID already has a UTF8-like represenation + * so getSizeOfUInt is used to determine + * the number of its bytes. + */ +void EBMLWriter::writeClassId(EbmlClassId parm) +{ + writeUnsignedInteger(parm, getSizeOfUInt(parm)); +} + +/** Writes data size value. */ +void EBMLWriter::writeSize(uint64_t parm) +{ + /* The following expression defines the size of the value that will be serialized + * as an EBML UTF-8 like integer (with trailing bits represeting its size): + 1xxx xxxx - value 0 to 2^7-2 + 01xx xxxx xxxx xxxx - value 0 to 2^14-2 + 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 + 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 + 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 + 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 + 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 + 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 + */ + size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) - + ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) - + ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) - + ! (parm & (UINT64_MAX << 7)); + /* One is subtracted in order to avoid loosing significant bit when size = 8. */ + uint64_t mask = RT_BIT_64(size * 8 - 1); + writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size); +} + +/** Size calculation for variable size UNSIGNED integer. + * + * The function defines the size of the number by trimming + * consequent trailing zero bytes starting from the most significant. + * The following statement is always true: + * 1 <= getSizeOfUInt(arg) <= 8. + * + * Every !(arg & (UINT64_MAX << X)) expression gives one + * if an only if all the bits from X to 63 are set to zero. + */ +size_t EBMLWriter::getSizeOfUInt(uint64_t arg) +{ + return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) - + ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) - + ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) - + ! (arg & (UINT64_MAX << 8)); +} + diff --git a/src/VBox/Main/src-client/EmulatedUSBImpl.cpp b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp new file mode 100644 index 00000000..cdfc6eb2 --- /dev/null +++ b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp @@ -0,0 +1,678 @@ +/* $Id: EmulatedUSBImpl.cpp $ */ +/** @file + * Emulated USB manager implementation. + */ + +/* + * Copyright (C) 2013-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_EMULATEDUSB +#include "LoggingNew.h" + +#include "EmulatedUSBImpl.h" +#include "ConsoleImpl.h" + +#include <VBox/vmm/pdmusb.h> +#include <VBox/vmm/vmmr3vtable.h> + + +/* + * Emulated USB webcam device instance. + */ +typedef std::map <Utf8Str, Utf8Str> EUSBSettingsMap; + +typedef enum EUSBDEVICESTATUS +{ + EUSBDEVICE_CREATED, + EUSBDEVICE_ATTACHING, + EUSBDEVICE_ATTACHED +} EUSBDEVICESTATUS; + +class EUSBWEBCAM /* : public EUSBDEVICE */ +{ +private: + int32_t volatile mcRefs; + + EmulatedUSB *mpEmulatedUSB; + + RTUUID mUuid; + char mszUuid[RTUUID_STR_LENGTH]; + + Utf8Str mPath; + Utf8Str mSettings; + + EUSBSettingsMap mDevSettings; + EUSBSettingsMap mDrvSettings; + + void *mpvObject; + + static DECLCALLBACK(int) emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver); + static DECLCALLBACK(int) emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis); + + HRESULT settingsParse(void); + + ~EUSBWEBCAM() + { + } + +public: + EUSBWEBCAM() + : + mcRefs(1), + mpEmulatedUSB(NULL), + mpvObject(NULL), + enmStatus(EUSBDEVICE_CREATED) + { + RT_ZERO(mUuid); + RT_ZERO(mszUuid); + } + + int32_t AddRef(void) + { + return ASMAtomicIncS32(&mcRefs); + } + + void Release(void) + { + int32_t c = ASMAtomicDecS32(&mcRefs); + if (c == 0) + { + delete this; + } + } + + HRESULT Initialize(Console *pConsole, + EmulatedUSB *pEmulatedUSB, + const com::Utf8Str *aPath, + const com::Utf8Str *aSettings, + void *pvObject); + HRESULT Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver); + HRESULT Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM); + + bool HasId(const char *pszId) { return RTStrCmp(pszId, mszUuid) == 0;} + + void *getObjectPtr() { return mpvObject; } + + EUSBDEVICESTATUS enmStatus; +}; + + +static int emulatedWebcamInsertSettings(PCFGMNODE pConfig, PCVMMR3VTABLE pVMM, EUSBSettingsMap *pSettings) +{ + for (EUSBSettingsMap::const_iterator it = pSettings->begin(); it != pSettings->end(); ++it) + { + /* Convert some well known settings for backward compatibility. */ + int vrc; + if ( RTStrCmp(it->first.c_str(), "MaxPayloadTransferSize") == 0 + || RTStrCmp(it->first.c_str(), "MaxFramerate") == 0) + { + uint32_t u32 = 0; + vrc = RTStrToUInt32Full(it->second.c_str(), 10, &u32); + if (vrc == VINF_SUCCESS) + vrc = pVMM->pfnCFGMR3InsertInteger(pConfig, it->first.c_str(), u32); + else if (RT_SUCCESS(vrc)) /* VWRN_* */ + vrc = VERR_INVALID_PARAMETER; + } + else + vrc = pVMM->pfnCFGMR3InsertString(pConfig, it->first.c_str(), it->second.c_str()); + if (RT_FAILURE(vrc)) + return vrc; + } + + return VINF_SUCCESS; +} + +/*static*/ DECLCALLBACK(int) +EUSBWEBCAM::emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver) +{ + PCFGMNODE pInstance = pVMM->pfnCFGMR3CreateTree(pUVM); + PCFGMNODE pConfig; + int vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "Config", &pConfig); + AssertRCReturn(vrc, vrc); + vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDevSettings); + AssertRCReturn(vrc, vrc); + + PCFGMNODE pEUSB; + vrc = pVMM->pfnCFGMR3InsertNode(pConfig, "EmulatedUSB", &pEUSB); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnCFGMR3InsertString(pEUSB, "Id", pThis->mszUuid); + AssertRCReturn(vrc, vrc); + + PCFGMNODE pLunL0; + vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "LUN#0", &pLunL0); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", pszDriver); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pConfig); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnCFGMR3InsertString(pConfig, "DevicePath", pThis->mPath.c_str()); + AssertRCReturn(vrc, vrc); + vrc = pVMM->pfnCFGMR3InsertString(pConfig, "Id", pThis->mszUuid); + AssertRCReturn(vrc, vrc); + vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDrvSettings); + AssertRCReturn(vrc, vrc); + + /* pInstance will be used by PDM and deallocated on error. */ + vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, "Webcam", pInstance, &pThis->mUuid, NULL); + LogRelFlowFunc(("PDMR3UsbCreateEmulatedDevice %Rrc\n", vrc)); + return vrc; +} + +/*static*/ DECLCALLBACK(int) +EUSBWEBCAM::emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis) +{ + return pVMM->pfnPDMR3UsbDetachDevice(pUVM, &pThis->mUuid); +} + +HRESULT EUSBWEBCAM::Initialize(Console *pConsole, + EmulatedUSB *pEmulatedUSB, + const com::Utf8Str *aPath, + const com::Utf8Str *aSettings, + void *pvObject) +{ + HRESULT hrc = S_OK; + + int vrc = RTUuidCreate(&mUuid); + AssertRCReturn(vrc, pConsole->setError(vrc, EmulatedUSB::tr("Init emulated USB webcam (RTUuidCreate -> %Rrc)"), vrc)); + + RTStrPrintf(mszUuid, sizeof(mszUuid), "%RTuuid", &mUuid); + hrc = mPath.assignEx(*aPath); + if (SUCCEEDED(hrc)) + { + hrc = mSettings.assignEx(*aSettings); + if (SUCCEEDED(hrc)) + { + hrc = settingsParse(); + if (SUCCEEDED(hrc)) + { + mpEmulatedUSB = pEmulatedUSB; + mpvObject = pvObject; + } + } + } + + return hrc; +} + +HRESULT EUSBWEBCAM::settingsParse(void) +{ + HRESULT hr = S_OK; + + /* Parse mSettings string: + * "[dev:|drv:]Name1=Value1;[dev:|drv:]Name2=Value2" + */ + char *pszSrc = mSettings.mutableRaw(); + + if (pszSrc) + { + while (*pszSrc) + { + /* Does the setting belong to device of driver. Default is both. */ + bool fDev = true; + bool fDrv = true; + if (RTStrNICmp(pszSrc, RT_STR_TUPLE("drv:")) == 0) + { + pszSrc += sizeof("drv:")-1; + fDev = false; + } + else if (RTStrNICmp(pszSrc, RT_STR_TUPLE("dev:")) == 0) + { + pszSrc += sizeof("dev:")-1; + fDrv = false; + } + + char *pszEq = strchr(pszSrc, '='); + if (!pszEq) + { + hr = E_INVALIDARG; + break; + } + + char *pszEnd = strchr(pszEq, ';'); + if (!pszEnd) + pszEnd = pszEq + strlen(pszEq); + + *pszEq = 0; + char chEnd = *pszEnd; + *pszEnd = 0; + + /* Empty strings not allowed. */ + if (*pszSrc != 0 && pszEq[1] != 0) + { + if (fDev) + mDevSettings[pszSrc] = &pszEq[1]; + if (fDrv) + mDrvSettings[pszSrc] = &pszEq[1]; + } + + *pszEq = '='; + *pszEnd = chEnd; + + pszSrc = pszEnd; + if (*pszSrc == ';') + pszSrc++; + } + + if (SUCCEEDED(hr)) + { + EUSBSettingsMap::const_iterator it; + for (it = mDevSettings.begin(); it != mDevSettings.end(); ++it) + LogRelFlowFunc(("[dev:%s] = [%s]\n", it->first.c_str(), it->second.c_str())); + for (it = mDrvSettings.begin(); it != mDrvSettings.end(); ++it) + LogRelFlowFunc(("[drv:%s] = [%s]\n", it->first.c_str(), it->second.c_str())); + } + } + + return hr; +} + +HRESULT EUSBWEBCAM::Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver) +{ + int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)emulatedWebcamAttach, 4, + pUVM, pVMM, this, pszDriver); + if (RT_SUCCESS(vrc)) + return S_OK; + LogFlowThisFunc(("%Rrc\n", vrc)); + return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Attach emulated USB webcam (%Rrc)"), vrc); +} + +HRESULT EUSBWEBCAM::Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)emulatedWebcamDetach, 3, + pUVM, pVMM, this); + if (RT_SUCCESS(vrc)) + return S_OK; + LogFlowThisFunc(("%Rrc\n", vrc)); + return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Detach emulated USB webcam (%Rrc)"), vrc); +} + + +/* + * EmulatedUSB implementation. + */ +DEFINE_EMPTY_CTOR_DTOR(EmulatedUSB) + +HRESULT EmulatedUSB::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void EmulatedUSB::FinalRelease() +{ + uninit(); + + BaseFinalRelease(); +} + +/* + * Initializes the instance. + * + * @param pConsole The owner. + */ +HRESULT EmulatedUSB::init(ComObjPtr<Console> pConsole) +{ + LogFlowThisFunc(("\n")); + + ComAssertRet(!pConsole.isNull(), E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m.pConsole = pConsole; + + mEmUsbIf.pvUser = this; + mEmUsbIf.pfnQueryEmulatedUsbDataById = EmulatedUSB::i_QueryEmulatedUsbDataById; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/* + * Uninitializes the instance. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void EmulatedUSB::uninit() +{ + LogFlowThisFunc(("\n")); + + m.pConsole.setNull(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + for (WebcamsMap::iterator it = m.webcams.begin(); it != m.webcams.end(); ++it) + { + EUSBWEBCAM *p = it->second; + if (p) + { + it->second = NULL; + p->Release(); + } + } + m.webcams.clear(); + alock.release(); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; +} + +HRESULT EmulatedUSB::getWebcams(std::vector<com::Utf8Str> &aWebcams) +{ + HRESULT hrc = S_OK; + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + try + { + aWebcams.resize(m.webcams.size()); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + catch (...) + { + hrc = E_FAIL; + } + + if (SUCCEEDED(hrc)) + { + size_t i; + WebcamsMap::const_iterator it; + for (i = 0, it = m.webcams.begin(); it != m.webcams.end(); ++it) + aWebcams[i++] = it->first; + } + + return hrc; +} + +PEMULATEDUSBIF EmulatedUSB::i_getEmulatedUsbIf() +{ + return &mEmUsbIf; +} + +static const Utf8Str s_pathDefault(".0"); + +HRESULT EmulatedUSB::webcamAttach(const com::Utf8Str &aPath, + const com::Utf8Str &aSettings) +{ + return i_webcamAttachInternal(aPath, aSettings, "HostWebcam", NULL); +} + +HRESULT EmulatedUSB::i_webcamAttachInternal(const com::Utf8Str &aPath, + const com::Utf8Str &aSettings, + const char *pszDriver, + void *pvObject) +{ + HRESULT hrc = S_OK; + + const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath; + + Console::SafeVMPtr ptrVM(m.pConsole); + if (ptrVM.isOk()) + { + EUSBWEBCAM *p = new EUSBWEBCAM(); + if (p) + { + hrc = p->Initialize(m.pConsole, this, &path, &aSettings, pvObject); + if (SUCCEEDED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + WebcamsMap::const_iterator it = m.webcams.find(path); + if (it == m.webcams.end()) + { + p->AddRef(); + try + { + m.webcams[path] = p; + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + catch (...) + { + hrc = E_FAIL; + } + p->enmStatus = EUSBDEVICE_ATTACHING; + } + else + { + hrc = E_FAIL; + } + } + + if (SUCCEEDED(hrc)) + hrc = p->Attach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable(), pszDriver); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (SUCCEEDED(hrc)) + p->enmStatus = EUSBDEVICE_ATTACHED; + else if (p->enmStatus != EUSBDEVICE_CREATED) + m.webcams.erase(path); + alock.release(); + + p->Release(); + } + else + { + hrc = E_OUTOFMEMORY; + } + } + else + { + hrc = VBOX_E_INVALID_VM_STATE; + } + + return hrc; +} + +HRESULT EmulatedUSB::webcamDetach(const com::Utf8Str &aPath) +{ + return i_webcamDetachInternal(aPath); +} + +HRESULT EmulatedUSB::i_webcamDetachInternal(const com::Utf8Str &aPath) +{ + HRESULT hrc = S_OK; + + const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath; + + Console::SafeVMPtr ptrVM(m.pConsole); + if (ptrVM.isOk()) + { + EUSBWEBCAM *p = NULL; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + WebcamsMap::iterator it = m.webcams.find(path); + if (it != m.webcams.end()) + { + if (it->second->enmStatus == EUSBDEVICE_ATTACHED) + { + p = it->second; + m.webcams.erase(it); + } + } + alock.release(); + + if (p) + { + hrc = p->Detach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable()); + p->Release(); + } + else + { + hrc = E_INVALIDARG; + } + } + else + { + hrc = VBOX_E_INVALID_VM_STATE; + } + + return hrc; +} + +/*static*/ DECLCALLBACK(int) +EmulatedUSB::eusbCallbackEMT(EmulatedUSB *pThis, char *pszId, uint32_t iEvent, void *pvData, uint32_t cbData) +{ + LogRelFlowFunc(("id %s event %d, data %p %d\n", pszId, iEvent, pvData, cbData)); + + NOREF(cbData); + + int vrc = VINF_SUCCESS; + if (iEvent == 0) + { + com::Utf8Str path; + HRESULT hrc = pThis->webcamPathFromId(&path, pszId); + if (SUCCEEDED(hrc)) + { + hrc = pThis->webcamDetach(path); + if (FAILED(hrc)) + { + vrc = VERR_INVALID_STATE; + } + } + else + { + vrc = VERR_NOT_FOUND; + } + } + else + { + vrc = VERR_INVALID_PARAMETER; + } + + RTMemFree(pszId); + RTMemFree(pvData); + + LogRelFlowFunc(("rc %Rrc\n", vrc)); + return vrc; +} + +/* static */ DECLCALLBACK(int) +EmulatedUSB::i_eusbCallback(void *pv, const char *pszId, uint32_t iEvent, const void *pvData, uint32_t cbData) +{ + /* Make a copy of parameters, forward to EMT and leave the callback to not hold any lock in the device. */ + int vrc = VINF_SUCCESS; + void *pvDataCopy = NULL; + if (cbData > 0) + { + pvDataCopy = RTMemDup(pvData, cbData); + if (!pvDataCopy) + vrc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(vrc)) + { + void *pvIdCopy = RTMemDup(pszId, strlen(pszId) + 1); + if (pvIdCopy) + { + if (RT_SUCCESS(vrc)) + { + EmulatedUSB *pThis = (EmulatedUSB *)pv; + Console::SafeVMPtr ptrVM(pThis->m.pConsole); + if (ptrVM.isOk()) + { + /* No wait. */ + vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), 0 /* idDstCpu */, + (PFNRT)EmulatedUSB::eusbCallbackEMT, 5, + pThis, pvIdCopy, iEvent, pvDataCopy, cbData); + if (RT_SUCCESS(vrc)) + return vrc; + } + else + vrc = VERR_INVALID_STATE; + } + RTMemFree(pvIdCopy); + } + else + vrc = VERR_NO_MEMORY; + RTMemFree(pvDataCopy); + } + return vrc; +} + +/*static*/ +DECLCALLBACK(int) EmulatedUSB::i_QueryEmulatedUsbDataById(void *pvUser, const char *pszId, void **ppvEmUsbCb, void **ppvEmUsbCbData, void **ppvObject) +{ + EmulatedUSB *pEmUsb = (EmulatedUSB *)pvUser; + + AutoReadLock alock(pEmUsb COMMA_LOCKVAL_SRC_POS); + WebcamsMap::const_iterator it; + for (it = pEmUsb->m.webcams.begin(); it != pEmUsb->m.webcams.end(); ++it) + { + EUSBWEBCAM *p = it->second; + if (p->HasId(pszId)) + { + if (ppvEmUsbCb) + *ppvEmUsbCb = (void *)EmulatedUSB::i_eusbCallback; + if (ppvEmUsbCbData) + *ppvEmUsbCbData = pEmUsb; + if (ppvObject) + *ppvObject = p->getObjectPtr(); + + return VINF_SUCCESS; + } + } + + return VERR_NOT_FOUND; +} + +HRESULT EmulatedUSB::webcamPathFromId(com::Utf8Str *pPath, const char *pszId) +{ + HRESULT hrc = S_OK; + + Console::SafeVMPtr ptrVM(m.pConsole); + if (ptrVM.isOk()) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + WebcamsMap::const_iterator it; + for (it = m.webcams.begin(); it != m.webcams.end(); ++it) + { + EUSBWEBCAM *p = it->second; + if (p->HasId(pszId)) + { + *pPath = it->first; + break; + } + } + + if (it == m.webcams.end()) + { + hrc = E_FAIL; + } + alock.release(); + } + else + { + hrc = VBOX_E_INVALID_VM_STATE; + } + + return hrc; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/GuestCtrlImpl.cpp b/src/VBox/Main/src-client/GuestCtrlImpl.cpp new file mode 100644 index 00000000..d99bab29 --- /dev/null +++ b/src/VBox/Main/src-client/GuestCtrlImpl.cpp @@ -0,0 +1,726 @@ +/* $Id: GuestCtrlImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation: Guest + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_GUEST_CONTROL +#include "LoggingNew.h" + +#include "GuestImpl.h" +#ifdef VBOX_WITH_GUEST_CONTROL +# include "GuestSessionImpl.h" +# include "GuestSessionImplTasks.h" +# include "GuestCtrlImplPrivate.h" +#endif + +#include "Global.h" +#include "ConsoleImpl.h" +#include "ProgressImpl.h" +#include "VBoxEvents.h" +#include "VMMDev.h" + +#include "AutoCaller.h" + +#include <VBox/VMMDev.h> +#ifdef VBOX_WITH_GUEST_CONTROL +# include <VBox/com/array.h> +# include <VBox/com/ErrorInfo.h> +#endif +#include <iprt/cpp/utils.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/list.h> +#include <iprt/path.h> +#include <VBox/vmm/pgm.h> +#include <VBox/AssertGuest.h> + +#include <memory> + + +/* + * This #ifdef goes almost to the end of the file where there are a couple of + * IGuest method implementations. + */ +#ifdef VBOX_WITH_GUEST_CONTROL + + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Static callback function for receiving updates on guest control messages + * from the guest. Acts as a dispatcher for the actual class instance. + * + * @returns VBox status code. + * @param pvExtension Pointer to HGCM service extension. + * @param idMessage HGCM message ID the callback was called for. + * @param pvData Pointer to user-supplied callback data. + * @param cbData Size (in bytes) of user-supplied callback data. + */ +/* static */ +DECLCALLBACK(int) Guest::i_notifyCtrlDispatcher(void *pvExtension, + uint32_t idMessage, + void *pvData, + uint32_t cbData) +{ + using namespace guestControl; + + /* + * No locking, as this is purely a notification which does not make any + * changes to the object state. + */ + Log2Func(("pvExtension=%p, idMessage=%RU32, pvParms=%p, cbParms=%RU32\n", pvExtension, idMessage, pvData, cbData)); + + ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension); + AssertReturn(pGuest.isNotNull(), VERR_WRONG_ORDER); + + /* + * The data packet should ever be a problem, but check to be sure. + */ + AssertMsgReturn(cbData == sizeof(VBOXGUESTCTRLHOSTCALLBACK), + ("Guest control host callback data has wrong size (expected %zu, got %zu) - buggy host service!\n", + sizeof(VBOXGUESTCTRLHOSTCALLBACK), cbData), VERR_INVALID_PARAMETER); + PVBOXGUESTCTRLHOSTCALLBACK pSvcCb = (PVBOXGUESTCTRLHOSTCALLBACK)pvData; + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + /* + * Deal with GUEST_MSG_REPORT_FEATURES here as it shouldn't be handed + * i_dispatchToSession() and has different parameters. + */ + if (idMessage == GUEST_MSG_REPORT_FEATURES) + { + Assert(pSvcCb->mParms == 2); + Assert(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_64BIT); + Assert(pSvcCb->mpaParms[1].type == VBOX_HGCM_SVC_PARM_64BIT); + Assert(pSvcCb->mpaParms[1].u.uint64 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE); + pGuest->mData.mfGuestFeatures0 = pSvcCb->mpaParms[0].u.uint64; + pGuest->mData.mfGuestFeatures1 = pSvcCb->mpaParms[1].u.uint64; + LogRel(("Guest Control: GUEST_MSG_REPORT_FEATURES: %#RX64, %#RX64\n", + pGuest->mData.mfGuestFeatures0, pGuest->mData.mfGuestFeatures1)); + return VINF_SUCCESS; + } + + /* + * For guest control 2.0 using the legacy messages we need to do the following here: + * - Get the callback header to access the context ID + * - Get the context ID of the callback + * - Extract the session ID out of the context ID + * - Dispatch the whole stuff to the appropriate session (if still exists) + * + * At least context ID parameter must always be present. + */ + ASSERT_GUEST_RETURN(pSvcCb->mParms > 0, VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_RETURN(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, + ("type=%d\n", pSvcCb->mpaParms[0].type), VERR_WRONG_PARAMETER_TYPE); + uint32_t const idContext = pSvcCb->mpaParms[0].u.uint32; + + VBOXGUESTCTRLHOSTCBCTX CtxCb = { idMessage, idContext }; + int vrc = pGuest->i_dispatchToSession(&CtxCb, pSvcCb); + + Log2Func(("CID=%#x, idSession=%RU32, uObject=%RU32, uCount=%RU32, vrc=%Rrc\n", + idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext), VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(idContext), + VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(idContext), vrc)); + return vrc; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Dispatches a host service callback to the appropriate guest control session object. + * + * @returns VBox status code. + * @param pCtxCb Pointer to host callback context. + * @param pSvcCb Pointer to callback parameters. + */ +int Guest::i_dispatchToSession(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb)); + + AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + Log2Func(("uMessage=%RU32, uContextID=%RU32, uProtocol=%RU32\n", pCtxCb->uMessage, pCtxCb->uContextID, pCtxCb->uProtocol)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + const uint32_t uSessionID = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtxCb->uContextID); + + Log2Func(("uSessionID=%RU32 (%zu total)\n", uSessionID, mData.mGuestSessions.size())); + + GuestSessions::const_iterator itSession = mData.mGuestSessions.find(uSessionID); + + int vrc; + if (itSession != mData.mGuestSessions.end()) + { + ComObjPtr<GuestSession> pSession(itSession->second); + Assert(!pSession.isNull()); + + alock.release(); + +#ifdef DEBUG + /* + * Pre-check: If we got a status message with an error and VERR_TOO_MUCH_DATA + * it means that that guest could not handle the entire message + * because of its exceeding size. This should not happen on daily + * use but testcases might try this. It then makes no sense to dispatch + * this further because we don't have a valid context ID. + */ + bool fDispatch = true; + vrc = VERR_INVALID_FUNCTION; + if ( pCtxCb->uMessage == GUEST_MSG_EXEC_STATUS + && pSvcCb->mParms >= 5) + { + CALLBACKDATA_PROC_STATUS dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + HGCMSvcGetU32(&pSvcCb->mpaParms[1], &dataCb.uPID); + HGCMSvcGetU32(&pSvcCb->mpaParms[2], &dataCb.uStatus); + HGCMSvcGetU32(&pSvcCb->mpaParms[3], &dataCb.uFlags); + HGCMSvcGetPv(&pSvcCb->mpaParms[4], &dataCb.pvData, &dataCb.cbData); + + if ( dataCb.uStatus == PROC_STS_ERROR + && (int32_t)dataCb.uFlags == VERR_TOO_MUCH_DATA) + { + LogFlowFunc(("Requested message with too much data, skipping dispatching ...\n")); + Assert(dataCb.uPID == 0); + fDispatch = false; + } + } + if (fDispatch) +#endif + { + switch (pCtxCb->uMessage) + { + case GUEST_MSG_DISCONNECTED: + vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb); + break; + + /* Process stuff. */ + case GUEST_MSG_EXEC_STATUS: + case GUEST_MSG_EXEC_OUTPUT: + case GUEST_MSG_EXEC_INPUT_STATUS: + case GUEST_MSG_EXEC_IO_NOTIFY: + vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + + /* File stuff. */ + case GUEST_MSG_FILE_NOTIFY: + vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + + /* Session stuff. */ + case GUEST_MSG_SESSION_NOTIFY: + vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb); + break; + + default: + vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + } + } + } + else + vrc = VERR_INVALID_SESSION_ID; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Creates a new guest session. + * This will invoke VBoxService running on the guest creating a new (dedicated) guest session + * On older Guest Additions this call has no effect on the guest, and only the credentials will be + * used for starting/impersonating guest processes. + * + * @returns VBox status code. + * @param ssInfo Guest session startup information. + * @param guestCreds Guest OS (user) credentials to use on the guest for creating the session. + * The specified user must be able to logon to the guest and able to start new processes. + * @param pGuestSession Where to store the created guest session on success. + * + * @note Takes the write lock. + */ +int Guest::i_sessionCreate(const GuestSessionStartupInfo &ssInfo, + const GuestCredentials &guestCreds, ComObjPtr<GuestSession> &pGuestSession) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VERR_MAX_PROCS_REACHED; + if (mData.mGuestSessions.size() >= VBOX_GUESTCTRL_MAX_SESSIONS) + return vrc; + + try + { + /* Create a new session ID and assign it. */ + uint32_t uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE; + uint32_t uTries = 0; + + for (;;) + { + /* Is the context ID already used? */ + if (!i_sessionExists(uNewSessionID)) + { + vrc = VINF_SUCCESS; + break; + } + uNewSessionID++; + if (uNewSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS) + uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE; + + if (++uTries == VBOX_GUESTCTRL_MAX_SESSIONS) + break; /* Don't try too hard. */ + } + if (RT_FAILURE(vrc)) throw vrc; + + /* Create the session object. */ + HRESULT hrc = pGuestSession.createObject(); + if (FAILED(hrc)) throw VERR_COM_UNEXPECTED; + + /** @todo Use an overloaded copy operator. Later. */ + GuestSessionStartupInfo startupInfo; + startupInfo.mID = uNewSessionID; /* Assign new session ID. */ + startupInfo.mName = ssInfo.mName; + startupInfo.mOpenFlags = ssInfo.mOpenFlags; + startupInfo.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS; + + GuestCredentials guestCredentials; + if (!guestCreds.mUser.isEmpty()) + { + /** @todo Use an overloaded copy operator. Later. */ + guestCredentials.mUser = guestCreds.mUser; + guestCredentials.mPassword = guestCreds.mPassword; + guestCredentials.mDomain = guestCreds.mDomain; + } + else + { + /* Internal (annonymous) session. */ + startupInfo.mIsInternal = true; + } + + vrc = pGuestSession->init(this, startupInfo, guestCredentials); + if (RT_FAILURE(vrc)) throw vrc; + + /* + * Add session object to our session map. This is necessary + * before calling openSession because the guest calls back + * with the creation result of this session. + */ + mData.mGuestSessions[uNewSessionID] = pGuestSession; + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestSessionRegisteredEvent(mEventSource, pGuestSession, true /* Registered */); + } + catch (int vrc2) + { + vrc = vrc2; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Destroys a given guest session and removes it from the internal list. + * + * @returns VBox status code. + * @param uSessionID ID of the guest control session to destroy. + * + * @note Takes the write lock. + */ +int Guest::i_sessionDestroy(uint32_t uSessionID) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VERR_NOT_FOUND; + + LogFlowThisFunc(("Destroying session (ID=%RU32) ...\n", uSessionID)); + + GuestSessions::iterator itSessions = mData.mGuestSessions.find(uSessionID); + if (itSessions == mData.mGuestSessions.end()) + return VERR_NOT_FOUND; + + /* Make sure to consume the pointer before the one of the + * iterator gets released. */ + ComObjPtr<GuestSession> pSession = itSessions->second; + + LogFlowThisFunc(("Removing session %RU32 (now total %ld sessions)\n", + uSessionID, mData.mGuestSessions.size() ? mData.mGuestSessions.size() - 1 : 0)); + + vrc = pSession->i_onRemove(); + mData.mGuestSessions.erase(itSessions); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestSessionRegisteredEvent(mEventSource, pSession, false /* Unregistered */); + pSession.setNull(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Returns whether a guest control session with a specific ID exists or not. + * + * @returns Returns \c true if the session exists, \c false if not. + * @param uSessionID ID to check for. + * + * @note No locking done, as inline function! + */ +inline bool Guest::i_sessionExists(uint32_t uSessionID) +{ + GuestSessions::const_iterator itSessions = mData.mGuestSessions.find(uSessionID); + return (itSessions == mData.mGuestSessions.end()) ? false : true; +} + +#endif /* VBOX_WITH_GUEST_CONTROL */ + + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// +HRESULT Guest::createSession(const com::Utf8Str &aUser, const com::Utf8Str &aPassword, const com::Utf8Str &aDomain, + const com::Utf8Str &aSessionName, ComPtr<IGuestSession> &aGuestSession) + +{ +#ifndef VBOX_WITH_GUEST_CONTROL + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_CONTROL */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Do not allow anonymous sessions (with system rights) with public API. */ + if (RT_UNLIKELY(!aUser.length())) + return setError(E_INVALIDARG, tr("No user name specified")); + + LogFlowFuncEnter(); + + GuestSessionStartupInfo startupInfo; + startupInfo.mName = aSessionName; + + GuestCredentials guestCreds; + guestCreds.mUser = aUser; + guestCreds.mPassword = aPassword; + guestCreds.mDomain = aDomain; + + ComObjPtr<GuestSession> pSession; + int vrc = i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_SUCCESS(vrc)) + { + /* Return guest session to the caller. */ + HRESULT hr2 = pSession.queryInterfaceTo(aGuestSession.asOutParam()); + if (FAILED(hr2)) + vrc = VERR_COM_OBJECT_NOT_FOUND; + } + + if (RT_SUCCESS(vrc)) + /* Start (fork) the session asynchronously + * on the guest. */ + vrc = pSession->i_startSessionAsync(); + + HRESULT hr = S_OK; + + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_MAX_PROCS_REACHED: + hr = setErrorBoth(VBOX_E_MAXIMUM_REACHED, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"), + VBOX_GUESTCTRL_MAX_SESSIONS); + break; + + /** @todo Add more errors here. */ + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc); + break; + } + } + + LogFlowThisFunc(("Returning rc=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_GUEST_CONTROL */ +} + +HRESULT Guest::findSession(const com::Utf8Str &aSessionName, std::vector<ComPtr<IGuestSession> > &aSessions) +{ +#ifndef VBOX_WITH_GUEST_CONTROL + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_CONTROL */ + + LogFlowFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Utf8Str strName(aSessionName); + std::list < ComObjPtr<GuestSession> > listSessions; + + GuestSessions::const_iterator itSessions = mData.mGuestSessions.begin(); + while (itSessions != mData.mGuestSessions.end()) + { + if (strName.contains(itSessions->second->i_getName())) /** @todo Use a (simple) pattern match (IPRT?). */ + listSessions.push_back(itSessions->second); + ++itSessions; + } + + LogFlowFunc(("Sessions with \"%s\" = %RU32\n", + aSessionName.c_str(), listSessions.size())); + + aSessions.resize(listSessions.size()); + if (!listSessions.empty()) + { + size_t i = 0; + for (std::list < ComObjPtr<GuestSession> >::const_iterator it = listSessions.begin(); it != listSessions.end(); ++it, ++i) + (*it).queryInterfaceTo(aSessions[i].asOutParam()); + + return S_OK; + + } + + return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, + tr("Could not find sessions with name '%s'"), + aSessionName.c_str()); +#endif /* VBOX_WITH_GUEST_CONTROL */ +} + +HRESULT Guest::shutdown(const std::vector<GuestShutdownFlag_T> &aFlags) +{ +#ifndef VBOX_WITH_GUEST_CONTROL + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_CONTROL */ + + /* Validate flags. */ + uint32_t fFlags = GuestShutdownFlag_None; + if (aFlags.size()) + for (size_t i = 0; i < aFlags.size(); ++i) + fFlags |= aFlags[i]; + + const uint32_t fValidFlags = GuestShutdownFlag_None + | GuestShutdownFlag_PowerOff | GuestShutdownFlag_Reboot | GuestShutdownFlag_Force; + if (fFlags & ~fValidFlags) + return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); + + if ( (fFlags & GuestShutdownFlag_PowerOff) + && (fFlags & GuestShutdownFlag_Reboot)) + return setError(E_INVALIDARG, tr("Invalid combination of flags (%#x)"), fFlags); + + Utf8Str strAction = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting") : tr("Shutting down"); + + /* + * Create an anonymous session. This is required to run shutting down / rebooting + * the guest with administrative rights. + */ + GuestSessionStartupInfo startupInfo; + startupInfo.mName = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting guest") : tr("Shutting down guest"); + + GuestCredentials guestCreds; + + HRESULT hrc = S_OK; + + ComObjPtr<GuestSession> pSession; + int vrc = i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_SUCCESS(vrc)) + { + Assert(!pSession.isNull()); + + int vrcGuest = VERR_GSTCTL_GUEST_ERROR; + vrc = pSession->i_startSession(&vrcGuest); + if (RT_SUCCESS(vrc)) + { + vrc = pSession->i_shutdown(fFlags, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_NOT_SUPPORTED: + hrc = setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("%s not supported by installed Guest Additions"), strAction.c_str()); + break; + + default: + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + vrc = vrcGuest; + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error %s guest: %Rrc"), strAction.c_str(), vrc); + break; + } + } + } + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + vrc = vrcGuest; + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc); + } + } + else + { + switch (vrc) + { + case VERR_MAX_PROCS_REACHED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"), + VBOX_GUESTCTRL_MAX_SESSIONS); + break; + + /** @todo Add more errors here. */ + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc); + break; + } + } + + LogFlowFunc(("Returning hrc=%Rhrc\n", hrc)); + return hrc; +#endif /* VBOX_WITH_GUEST_CONTROL */ +} + +HRESULT Guest::updateGuestAdditions(const com::Utf8Str &aSource, const std::vector<com::Utf8Str> &aArguments, + const std::vector<AdditionsUpdateFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ +#ifndef VBOX_WITH_GUEST_CONTROL + ReturnComNotImplemented(); +#else /* VBOX_WITH_GUEST_CONTROL */ + + /* Validate flags. */ + uint32_t fFlags = AdditionsUpdateFlag_None; + if (aFlags.size()) + for (size_t i = 0; i < aFlags.size(); ++i) + fFlags |= aFlags[i]; + + if (fFlags && !(fFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)) + return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags); + + + /* Copy arguments into aArgs: */ + ProcessArguments aArgs; + try + { + aArgs.resize(0); + for (size_t i = 0; i < aArguments.size(); ++i) + aArgs.push_back(aArguments[i]); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + + + /* + * Create an anonymous session. This is required to run the Guest Additions + * update process with administrative rights. + */ + GuestSessionStartupInfo startupInfo; + startupInfo.mName = "Updating Guest Additions"; + + GuestCredentials guestCreds; + + HRESULT hrc; + ComObjPtr<GuestSession> pSession; + int vrc = i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_SUCCESS(vrc)) + { + Assert(!pSession.isNull()); + + int vrcGuest = VERR_GSTCTL_GUEST_ERROR; + vrc = pSession->i_startSession(&vrcGuest); + if (RT_SUCCESS(vrc)) + { + /* + * Create the update task. + */ + GuestSessionTaskUpdateAdditions *pTask = NULL; + try + { + pTask = new GuestSessionTaskUpdateAdditions(pSession /* GuestSession */, aSource, aArgs, fFlags); + hrc = S_OK; + } + catch (std::bad_alloc &) + { + hrc = setError(E_OUTOFMEMORY, tr("Failed to create SessionTaskUpdateAdditions object")); + } + if (SUCCEEDED(hrc)) + { + try + { + hrc = pTask->Init(Utf8StrFmt(tr("Updating Guest Additions"))); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + { + ComPtr<Progress> ptrProgress = pTask->GetProgressObject(); + + /* + * Kick off the thread. Note! consumes pTask! + */ + hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + pTask = NULL; + if (SUCCEEDED(hrc)) + hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam()); + else + hrc = setError(hrc, tr("Starting thread for updating Guest Additions on the guest failed")); + } + else + { + hrc = setError(hrc, tr("Failed to initialize SessionTaskUpdateAdditions object")); + delete pTask; + } + } + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + vrc = vrcGuest; + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc); + } + } + else + { + switch (vrc) + { + case VERR_MAX_PROCS_REACHED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"), + VBOX_GUESTCTRL_MAX_SESSIONS); + break; + + /** @todo Add more errors here. */ + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc); + break; + } + } + + LogFlowFunc(("Returning hrc=%Rhrc\n", hrc)); + return hrc; +#endif /* VBOX_WITH_GUEST_CONTROL */ +} + diff --git a/src/VBox/Main/src-client/GuestCtrlPrivate.cpp b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp new file mode 100644 index 00000000..a9dc9897 --- /dev/null +++ b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp @@ -0,0 +1,1900 @@ +/* $Id: GuestCtrlPrivate.cpp $ */ +/** @file + * Internal helpers/structures for guest control functionality. + */ + +/* + * Copyright (C) 2011-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_CONTROL +#include "LoggingNew.h" + +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestCtrlImplPrivate.h" +#include "GuestSessionImpl.h" +#include "VMMDev.h" + +#include <iprt/asm.h> +#include <iprt/cpp/utils.h> /* For unconst(). */ +#include <iprt/ctype.h> +#ifdef DEBUG +# include <iprt/file.h> +#endif +#include <iprt/fs.h> +#include <iprt/path.h> +#include <iprt/rand.h> +#include <iprt/time.h> +#include <VBox/AssertGuest.h> + + +/** + * Extracts the timespec from a given stream block key. + * + * @return Pointer to handed-in timespec, or NULL if invalid / not found. + * @param strmBlk Stream block to extract timespec from. + * @param strKey Key to get timespec for. + * @param pTimeSpec Where to store the extracted timespec. + */ +/* static */ +PRTTIMESPEC GuestFsObjData::TimeSpecFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey, PRTTIMESPEC pTimeSpec) +{ + AssertPtrReturn(pTimeSpec, NULL); + + Utf8Str strTime = strmBlk.GetString(strKey.c_str()); + if (strTime.isEmpty()) + return NULL; + + if (!RTTimeSpecFromString(pTimeSpec, strTime.c_str())) + return NULL; + + return pTimeSpec; +} + +/** + * Extracts the nanoseconds relative from Unix epoch for a given stream block key. + * + * @return Nanoseconds relative from Unix epoch, or 0 if invalid / not found. + * @param strmBlk Stream block to extract nanoseconds from. + * @param strKey Key to get nanoseconds for. + */ +/* static */ +int64_t GuestFsObjData::UnixEpochNsFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey) +{ + RTTIMESPEC TimeSpec; + if (!GuestFsObjData::TimeSpecFromKey(strmBlk, strKey, &TimeSpec)) + return 0; + + return TimeSpec.i64NanosecondsRelativeToUnixEpoch; +} + +/** + * Initializes this object data with a stream block from VBOXSERVICE_TOOL_LS. + * + * This is also used by FromStat since the output should be identical given that + * they use the same output function on the guest side when fLong is true. + * + * @return VBox status code. + * @param strmBlk Stream block to use for initialization. + * @param fLong Whether the stream block contains long (detailed) information or not. + */ +int GuestFsObjData::FromLs(const GuestProcessStreamBlock &strmBlk, bool fLong) +{ + LogFlowFunc(("\n")); +#ifdef DEBUG + strmBlk.DumpToLog(); +#endif + + /* Object name. */ + mName = strmBlk.GetString("name"); + ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND); + + /* Type & attributes. */ + bool fHaveAttribs = false; + char szAttribs[32]; + memset(szAttribs, '?', sizeof(szAttribs) - 1); + mType = FsObjType_Unknown; + const char *psz = strmBlk.GetString("ftype"); + if (psz) + { + fHaveAttribs = true; + szAttribs[0] = *psz; + switch (*psz) + { + case '-': mType = FsObjType_File; break; + case 'd': mType = FsObjType_Directory; break; + case 'l': mType = FsObjType_Symlink; break; + case 'c': mType = FsObjType_DevChar; break; + case 'b': mType = FsObjType_DevBlock; break; + case 'f': mType = FsObjType_Fifo; break; + case 's': mType = FsObjType_Socket; break; + case 'w': mType = FsObjType_WhiteOut; break; + default: + AssertMsgFailed(("%s\n", psz)); + szAttribs[0] = '?'; + fHaveAttribs = false; + break; + } + } + psz = strmBlk.GetString("owner_mask"); + if ( psz + && (psz[0] == '-' || psz[0] == 'r') + && (psz[1] == '-' || psz[1] == 'w') + && (psz[2] == '-' || psz[2] == 'x')) + { + szAttribs[1] = psz[0]; + szAttribs[2] = psz[1]; + szAttribs[3] = psz[2]; + fHaveAttribs = true; + } + psz = strmBlk.GetString("group_mask"); + if ( psz + && (psz[0] == '-' || psz[0] == 'r') + && (psz[1] == '-' || psz[1] == 'w') + && (psz[2] == '-' || psz[2] == 'x')) + { + szAttribs[4] = psz[0]; + szAttribs[5] = psz[1]; + szAttribs[6] = psz[2]; + fHaveAttribs = true; + } + psz = strmBlk.GetString("other_mask"); + if ( psz + && (psz[0] == '-' || psz[0] == 'r') + && (psz[1] == '-' || psz[1] == 'w') + && (psz[2] == '-' || psz[2] == 'x')) + { + szAttribs[7] = psz[0]; + szAttribs[8] = psz[1]; + szAttribs[9] = psz[2]; + fHaveAttribs = true; + } + szAttribs[10] = ' '; /* Reserve three chars for sticky bits. */ + szAttribs[11] = ' '; + szAttribs[12] = ' '; + szAttribs[13] = ' '; /* Separator. */ + psz = strmBlk.GetString("dos_mask"); + if ( psz + && (psz[ 0] == '-' || psz[ 0] == 'R') + && (psz[ 1] == '-' || psz[ 1] == 'H') + && (psz[ 2] == '-' || psz[ 2] == 'S') + && (psz[ 3] == '-' || psz[ 3] == 'D') + && (psz[ 4] == '-' || psz[ 4] == 'A') + && (psz[ 5] == '-' || psz[ 5] == 'd') + && (psz[ 6] == '-' || psz[ 6] == 'N') + && (psz[ 7] == '-' || psz[ 7] == 'T') + && (psz[ 8] == '-' || psz[ 8] == 'P') + && (psz[ 9] == '-' || psz[ 9] == 'J') + && (psz[10] == '-' || psz[10] == 'C') + && (psz[11] == '-' || psz[11] == 'O') + && (psz[12] == '-' || psz[12] == 'I') + && (psz[13] == '-' || psz[13] == 'E')) + { + memcpy(&szAttribs[14], psz, 14); + fHaveAttribs = true; + } + szAttribs[28] = '\0'; + if (fHaveAttribs) + mFileAttrs = szAttribs; + + /* Object size. */ + int rc = strmBlk.GetInt64Ex("st_size", &mObjectSize); + ASSERT_GUEST_RC_RETURN(rc, rc); + strmBlk.GetInt64Ex("alloc", &mAllocatedSize); + + /* INode number and device. */ + psz = strmBlk.GetString("node_id"); + if (!psz) + psz = strmBlk.GetString("cnode_id"); /* copy & past error fixed in 6.0 RC1 */ + if (psz) + mNodeID = RTStrToInt64(psz); + mNodeIDDevice = strmBlk.GetUInt32("inode_dev"); /* (Produced by GAs prior to 6.0 RC1.) */ + + if (fLong) + { + /* Dates. */ + mAccessTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_atime"); + mBirthTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_birthtime"); + mChangeTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_ctime"); + mModificationTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_mtime"); + + /* Owner & group. */ + mUID = strmBlk.GetInt32("uid"); + psz = strmBlk.GetString("username"); + if (psz) + mUserName = psz; + mGID = strmBlk.GetInt32("gid"); + psz = strmBlk.GetString("groupname"); + if (psz) + mGroupName = psz; + + /* Misc attributes: */ + mNumHardLinks = strmBlk.GetUInt32("hlinks", 1); + mDeviceNumber = strmBlk.GetUInt32("st_rdev"); + mGenerationID = strmBlk.GetUInt32("st_gen"); + mUserFlags = strmBlk.GetUInt32("st_flags"); + + /** @todo ACL */ + } + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** + * Parses stream block output data which came from the 'rm' (vbox_rm) + * VBoxService toolbox command. The result will be stored in this object. + * + * @returns VBox status code. + * @param strmBlk Stream block output data to parse. + */ +int GuestFsObjData::FromRm(const GuestProcessStreamBlock &strmBlk) +{ +#ifdef DEBUG + strmBlk.DumpToLog(); +#endif + /* Object name. */ + mName = strmBlk.GetString("fname"); + + /* Return the stream block's rc. */ + return strmBlk.GetRc(); +} + +/** + * Parses stream block output data which came from the 'stat' (vbox_stat) + * VBoxService toolbox command. The result will be stored in this object. + * + * @returns VBox status code. + * @param strmBlk Stream block output data to parse. + */ +int GuestFsObjData::FromStat(const GuestProcessStreamBlock &strmBlk) +{ + /* Should be identical output. */ + return GuestFsObjData::FromLs(strmBlk, true /*fLong*/); +} + +/** + * Parses stream block output data which came from the 'mktemp' (vbox_mktemp) + * VBoxService toolbox command. The result will be stored in this object. + * + * @returns VBox status code. + * @param strmBlk Stream block output data to parse. + */ +int GuestFsObjData::FromMkTemp(const GuestProcessStreamBlock &strmBlk) +{ + LogFlowFunc(("\n")); + +#ifdef DEBUG + strmBlk.DumpToLog(); +#endif + /* Object name. */ + mName = strmBlk.GetString("name"); + ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND); + + /* Assign the stream block's rc. */ + int rc = strmBlk.GetRc(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Returns the IPRT-compatible file mode. + * Note: Only handling RTFS_TYPE_ flags are implemented for now. + * + * @return IPRT file mode. + */ +RTFMODE GuestFsObjData::GetFileMode(void) const +{ + RTFMODE fMode = 0; + + switch (mType) + { + case FsObjType_Directory: + fMode |= RTFS_TYPE_DIRECTORY; + break; + + case FsObjType_File: + fMode |= RTFS_TYPE_FILE; + break; + + case FsObjType_Symlink: + fMode |= RTFS_TYPE_SYMLINK; + break; + + default: + break; + } + + /** @todo Implement more stuff. */ + + return fMode; +} + +/////////////////////////////////////////////////////////////////////////////// + +/** @todo *NOT* thread safe yet! */ +/** @todo Add exception handling for STL stuff! */ + +GuestProcessStreamBlock::GuestProcessStreamBlock(void) +{ + +} + +GuestProcessStreamBlock::~GuestProcessStreamBlock() +{ + Clear(); +} + +/** + * Clears (destroys) the currently stored stream pairs. + */ +void GuestProcessStreamBlock::Clear(void) +{ + mPairs.clear(); +} + +#ifdef DEBUG +/** + * Dumps the currently stored stream pairs to the (debug) log. + */ +void GuestProcessStreamBlock::DumpToLog(void) const +{ + LogFlowFunc(("Dumping contents of stream block=0x%p (%ld items):\n", + this, mPairs.size())); + + for (GuestCtrlStreamPairMapIterConst it = mPairs.begin(); + it != mPairs.end(); ++it) + { + LogFlowFunc(("\t%s=%s\n", it->first.c_str(), it->second.mValue.c_str())); + } +} +#endif + +/** + * Returns a 64-bit signed integer of a specified key. + * + * @return VBox status code. VERR_NOT_FOUND if key was not found. + * @param pszKey Name of key to get the value for. + * @param piVal Pointer to value to return. + */ +int GuestProcessStreamBlock::GetInt64Ex(const char *pszKey, int64_t *piVal) const +{ + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + AssertPtrReturn(piVal, VERR_INVALID_POINTER); + const char *pszValue = GetString(pszKey); + if (pszValue) + { + *piVal = RTStrToInt64(pszValue); + return VINF_SUCCESS; + } + return VERR_NOT_FOUND; +} + +/** + * Returns a 64-bit integer of a specified key. + * + * @return int64_t Value to return, 0 if not found / on failure. + * @param pszKey Name of key to get the value for. + */ +int64_t GuestProcessStreamBlock::GetInt64(const char *pszKey) const +{ + int64_t iVal; + if (RT_SUCCESS(GetInt64Ex(pszKey, &iVal))) + return iVal; + return 0; +} + +/** + * Returns the current number of stream pairs. + * + * @return uint32_t Current number of stream pairs. + */ +size_t GuestProcessStreamBlock::GetCount(void) const +{ + return mPairs.size(); +} + +/** + * Gets the return code (name = "rc") of this stream block. + * + * @return VBox status code. + * @retval VERR_NOT_FOUND if the return code string ("rc") was not found. + */ +int GuestProcessStreamBlock::GetRc(void) const +{ + const char *pszValue = GetString("rc"); + if (pszValue) + { + return RTStrToInt16(pszValue); + } + /** @todo We probably should have a dedicated error for that, VERR_GSTCTL_GUEST_TOOLBOX_whatever. */ + return VERR_NOT_FOUND; +} + +/** + * Returns a string value of a specified key. + * + * @return uint32_t Pointer to string to return, NULL if not found / on failure. + * @param pszKey Name of key to get the value for. + */ +const char *GuestProcessStreamBlock::GetString(const char *pszKey) const +{ + AssertPtrReturn(pszKey, NULL); + + try + { + GuestCtrlStreamPairMapIterConst itPairs = mPairs.find(pszKey); + if (itPairs != mPairs.end()) + return itPairs->second.mValue.c_str(); + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + } + return NULL; +} + +/** + * Returns a 32-bit unsigned integer of a specified key. + * + * @return VBox status code. VERR_NOT_FOUND if key was not found. + * @param pszKey Name of key to get the value for. + * @param puVal Pointer to value to return. + */ +int GuestProcessStreamBlock::GetUInt32Ex(const char *pszKey, uint32_t *puVal) const +{ + const char *pszValue = GetString(pszKey); + if (pszValue) + { + *puVal = RTStrToUInt32(pszValue); + return VINF_SUCCESS; + } + return VERR_NOT_FOUND; +} + +/** + * Returns a 32-bit signed integer of a specified key. + * + * @returns 32-bit signed value + * @param pszKey Name of key to get the value for. + * @param iDefault The default to return on error if not found. + */ +int32_t GuestProcessStreamBlock::GetInt32(const char *pszKey, int32_t iDefault) const +{ + const char *pszValue = GetString(pszKey); + if (pszValue) + { + int32_t iRet; + int rc = RTStrToInt32Full(pszValue, 0, &iRet); + if (RT_SUCCESS(rc)) + return iRet; + ASSERT_GUEST_MSG_FAILED(("%s=%s\n", pszKey, pszValue)); + } + return iDefault; +} + +/** + * Returns a 32-bit unsigned integer of a specified key. + * + * @return uint32_t Value to return, 0 if not found / on failure. + * @param pszKey Name of key to get the value for. + * @param uDefault The default value to return. + */ +uint32_t GuestProcessStreamBlock::GetUInt32(const char *pszKey, uint32_t uDefault /*= 0*/) const +{ + uint32_t uVal; + if (RT_SUCCESS(GetUInt32Ex(pszKey, &uVal))) + return uVal; + return uDefault; +} + +/** + * Sets a value to a key or deletes a key by setting a NULL value. + * + * @return VBox status code. + * @param pszKey Key name to process. + * @param pszValue Value to set. Set NULL for deleting the key. + */ +int GuestProcessStreamBlock::SetValue(const char *pszKey, const char *pszValue) +{ + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + try + { + Utf8Str Utf8Key(pszKey); + + /* Take a shortcut and prevent crashes on some funny versions + * of STL if map is empty initially. */ + if (!mPairs.empty()) + { + GuestCtrlStreamPairMapIter it = mPairs.find(Utf8Key); + if (it != mPairs.end()) + mPairs.erase(it); + } + + if (pszValue) + { + GuestProcessStreamValue val(pszValue); + mPairs[Utf8Key] = val; + } + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + } + return rc; +} + +/////////////////////////////////////////////////////////////////////////////// + +GuestProcessStream::GuestProcessStream(void) + : m_cbMax(_32M) + , m_cbAllocated(0) + , m_cbUsed(0) + , m_offBuffer(0) + , m_pbBuffer(NULL) { } + +GuestProcessStream::~GuestProcessStream(void) +{ + Destroy(); +} + +/** + * Adds data to the internal parser buffer. Useful if there + * are multiple rounds of adding data needed. + * + * @return VBox status code. Will return VERR_TOO_MUCH_DATA if the buffer's maximum (limit) has been reached. + * @param pbData Pointer to data to add. + * @param cbData Size (in bytes) of data to add. + */ +int GuestProcessStream::AddData(const BYTE *pbData, size_t cbData) +{ + AssertPtrReturn(pbData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + + /* Rewind the buffer if it's empty. */ + size_t cbInBuf = m_cbUsed - m_offBuffer; + bool const fAddToSet = cbInBuf == 0; + if (fAddToSet) + m_cbUsed = m_offBuffer = 0; + + /* Try and see if we can simply append the data. */ + if (cbData + m_cbUsed <= m_cbAllocated) + { + memcpy(&m_pbBuffer[m_cbUsed], pbData, cbData); + m_cbUsed += cbData; + } + else + { + /* Move any buffered data to the front. */ + cbInBuf = m_cbUsed - m_offBuffer; + if (cbInBuf == 0) + m_cbUsed = m_offBuffer = 0; + else if (m_offBuffer) /* Do we have something to move? */ + { + memmove(m_pbBuffer, &m_pbBuffer[m_offBuffer], cbInBuf); + m_cbUsed = cbInBuf; + m_offBuffer = 0; + } + + /* Do we need to grow the buffer? */ + if (cbData + m_cbUsed > m_cbAllocated) + { + size_t cbAlloc = m_cbUsed + cbData; + if (cbAlloc <= m_cbMax) + { + cbAlloc = RT_ALIGN_Z(cbAlloc, _64K); + void *pvNew = RTMemRealloc(m_pbBuffer, cbAlloc); + if (pvNew) + { + m_pbBuffer = (uint8_t *)pvNew; + m_cbAllocated = cbAlloc; + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_TOO_MUCH_DATA; + } + + /* Finally, copy the data. */ + if (RT_SUCCESS(rc)) + { + if (cbData + m_cbUsed <= m_cbAllocated) + { + memcpy(&m_pbBuffer[m_cbUsed], pbData, cbData); + m_cbUsed += cbData; + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + + return rc; +} + +/** + * Destroys the internal data buffer. + */ +void GuestProcessStream::Destroy(void) +{ + if (m_pbBuffer) + { + RTMemFree(m_pbBuffer); + m_pbBuffer = NULL; + } + + m_cbAllocated = 0; + m_cbUsed = 0; + m_offBuffer = 0; +} + +#ifdef DEBUG +/** + * Dumps the raw guest process output to a file on the host. + * If the file on the host already exists, it will be overwritten. + * + * @param pszFile Absolute path to host file to dump the output to. + */ +void GuestProcessStream::Dump(const char *pszFile) +{ + LogFlowFunc(("Dumping contents of stream=0x%p (cbAlloc=%u, cbSize=%u, cbOff=%u) to %s\n", + m_pbBuffer, m_cbAllocated, m_cbUsed, m_offBuffer, pszFile)); + + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, m_pbBuffer, m_cbUsed, NULL /* pcbWritten */); + RTFileClose(hFile); + } +} +#endif + +/** + * Tries to parse the next upcoming pair block within the internal + * buffer. + * + * Returns VERR_NO_DATA is no data is in internal buffer or buffer has been + * completely parsed already. + * + * Returns VERR_MORE_DATA if current block was parsed (with zero or more pairs + * stored in stream block) but still contains incomplete (unterminated) + * data. + * + * Returns VINF_SUCCESS if current block was parsed until the next upcoming + * block (with zero or more pairs stored in stream block). + * + * @return VBox status code. + * @param streamBlock Reference to guest stream block to fill. + */ +int GuestProcessStream::ParseBlock(GuestProcessStreamBlock &streamBlock) +{ + if ( !m_pbBuffer + || !m_cbUsed) + return VERR_NO_DATA; + + AssertReturn(m_offBuffer <= m_cbUsed, VERR_INVALID_PARAMETER); + if (m_offBuffer == m_cbUsed) + return VERR_NO_DATA; + + int rc = VINF_SUCCESS; + char * const pszOff = (char *)&m_pbBuffer[m_offBuffer]; + size_t cbLeft = m_offBuffer < m_cbUsed ? m_cbUsed - m_offBuffer : 0; + char *pszStart = pszOff; + while (cbLeft > 0 && *pszStart != '\0') + { + char * const pszPairEnd = RTStrEnd(pszStart, cbLeft); + if (!pszPairEnd) + { + rc = VERR_MORE_DATA; + break; + } + size_t const cchPair = (size_t)(pszPairEnd - pszStart); + char *pszSep = (char *)memchr(pszStart, '=', cchPair); + if (pszSep) + *pszSep = '\0'; /* Terminate the separator so that we can use pszStart as our key from now on. */ + else + { + rc = VERR_MORE_DATA; /** @todo r=bird: This is BOGUS because we'll be stuck here if the guest feeds us bad data! */ + break; + } + char const * const pszVal = pszSep + 1; + + rc = streamBlock.SetValue(pszStart, pszVal); + if (RT_FAILURE(rc)) + return rc; + + /* Next pair. */ + pszStart = pszPairEnd + 1; + cbLeft -= cchPair + 1; + } + + /* If we did not do any movement but we have stuff left + * in our buffer just skip the current termination so that + * we can try next time. */ + size_t cbDistance = (pszStart - pszOff); + if ( !cbDistance + && cbLeft > 0 + && *pszStart == '\0' + && m_offBuffer < m_cbUsed) + cbDistance++; + m_offBuffer += cbDistance; + + return rc; +} + +GuestBase::GuestBase(void) + : mConsole(NULL) + , mNextContextID(RTRandU32() % VBOX_GUESTCTRL_MAX_CONTEXTS) +{ +} + +GuestBase::~GuestBase(void) +{ +} + +/** + * Separate initialization function for the base class. + * + * @returns VBox status code. + */ +int GuestBase::baseInit(void) +{ + int rc = RTCritSectInit(&mWaitEventCritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Separate uninitialization function for the base class. + */ +void GuestBase::baseUninit(void) +{ + LogFlowThisFuncEnter(); + + /* Make sure to cancel any outstanding wait events. */ + int rc2 = cancelWaitEvents(); + AssertRC(rc2); + + rc2 = RTCritSectDelete(&mWaitEventCritSect); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc2); + /* No return value. */ +} + +/** + * Cancels all outstanding wait events. + * + * @returns VBox status code. + */ +int GuestBase::cancelWaitEvents(void) +{ + LogFlowThisFuncEnter(); + + int rc = RTCritSectEnter(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + { + GuestEventGroup::iterator itEventGroups = mWaitEventGroups.begin(); + while (itEventGroups != mWaitEventGroups.end()) + { + GuestWaitEvents::iterator itEvents = itEventGroups->second.begin(); + while (itEvents != itEventGroups->second.end()) + { + GuestWaitEvent *pEvent = itEvents->second; + AssertPtr(pEvent); + + /* + * Just cancel the event, but don't remove it from the + * wait events map. Don't delete it though, this (hopefully) + * is done by the caller using unregisterWaitEvent(). + */ + int rc2 = pEvent->Cancel(); + AssertRC(rc2); + + ++itEvents; + } + + ++itEventGroups; + } + + int rc2 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Handles generic messages not bound to a specific object type. + * + * @return VBox status code. VERR_NOT_FOUND if no handler has been found or VERR_NOT_SUPPORTED + * if this class does not support the specified callback. + * @param pCtxCb Host callback context. + * @param pSvcCb Service callback data. + */ +int GuestBase::dispatchGeneric(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb)); + + AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + int vrc; + + try + { + Log2Func(("uFunc=%RU32, cParms=%RU32\n", pCtxCb->uMessage, pSvcCb->mParms)); + + switch (pCtxCb->uMessage) + { + case GUEST_MSG_PROGRESS_UPDATE: + vrc = VINF_SUCCESS; + break; + + case GUEST_MSG_REPLY: + { + if (pSvcCb->mParms >= 4) + { + int idx = 1; /* Current parameter index. */ + CALLBACKDATA_MSG_REPLY dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetPv(&pSvcCb->mpaParms[idx++], &dataCb.pvPayload, &dataCb.cbPayload); + AssertRCReturn(vrc, vrc); + + try + { + GuestWaitEventPayload evPayload(dataCb.uType, dataCb.pvPayload, dataCb.cbPayload); + vrc = signalWaitEventInternal(pCtxCb, dataCb.rc, &evPayload); + } + catch (int rcEx) /* Thrown by GuestWaitEventPayload constructor. */ + { + vrc = rcEx; + } + } + else + vrc = VERR_INVALID_PARAMETER; + break; + } + + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + catch (int rc) + { + vrc = rc; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Generates a context ID (CID) by incrementing the object's count. + * A CID consists of a session ID, an object ID and a count. + * + * Note: This function does not guarantee that the returned CID is unique; + * the caller has to take care of that and eventually retry. + * + * @returns VBox status code. + * @param uSessionID Session ID to use for CID generation. + * @param uObjectID Object ID to use for CID generation. + * @param puContextID Where to store the generated CID on success. + */ +int GuestBase::generateContextID(uint32_t uSessionID, uint32_t uObjectID, uint32_t *puContextID) +{ + AssertPtrReturn(puContextID, VERR_INVALID_POINTER); + + if ( uSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS + || uObjectID >= VBOX_GUESTCTRL_MAX_OBJECTS) + return VERR_INVALID_PARAMETER; + + uint32_t uCount = ASMAtomicIncU32(&mNextContextID); + uCount %= VBOX_GUESTCTRL_MAX_CONTEXTS; + + uint32_t uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID, uObjectID, uCount); + + *puContextID = uNewContextID; + +#if 0 + LogFlowThisFunc(("mNextContextID=%RU32, uSessionID=%RU32, uObjectID=%RU32, uCount=%RU32, uNewContextID=%RU32\n", + mNextContextID, uSessionID, uObjectID, uCount, uNewContextID)); +#endif + return VINF_SUCCESS; +} + +/** + * Registers (creates) a new wait event based on a given session and object ID. + * + * From those IDs an unique context ID (CID) will be built, which only can be + * around once at a time. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)). + * @param uSessionID Session ID to register wait event for. + * @param uObjectID Object ID to register wait event for. + * @param ppEvent Pointer to registered (created) wait event on success. + * Must be destroyed with unregisterWaitEvent(). + */ +int GuestBase::registerWaitEvent(uint32_t uSessionID, uint32_t uObjectID, GuestWaitEvent **ppEvent) +{ + GuestEventTypes eventTypesEmpty; + return registerWaitEventEx(uSessionID, uObjectID, eventTypesEmpty, ppEvent); +} + +/** + * Creates and registers a new wait event object that waits on a set of events + * related to a given object within the session. + * + * From the session ID and object ID a one-time unique context ID (CID) is built + * for this wait object. Normally the CID is then passed to the guest along + * with a request, and the guest passed the CID back with the reply. The + * handler for the reply then emits a signal on the event type associated with + * the reply, which includes signalling the object returned by this method and + * the waking up the thread waiting on it. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)). + * @param uSessionID Session ID to register wait event for. + * @param uObjectID Object ID to register wait event for. + * @param lstEvents List of events to register the wait event for. + * @param ppEvent Pointer to registered (created) wait event on success. + * Must be destroyed with unregisterWaitEvent(). + */ +int GuestBase::registerWaitEventEx(uint32_t uSessionID, uint32_t uObjectID, const GuestEventTypes &lstEvents, + GuestWaitEvent **ppEvent) +{ + AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); + + uint32_t idContext; + int rc = generateContextID(uSessionID, uObjectID, &idContext); + AssertRCReturn(rc, rc); + + GuestWaitEvent *pEvent = new GuestWaitEvent(); + AssertPtrReturn(pEvent, VERR_NO_MEMORY); + + rc = pEvent->Init(idContext, lstEvents); + AssertRCReturn(rc, rc); + + LogFlowThisFunc(("New event=%p, CID=%RU32\n", pEvent, idContext)); + + rc = RTCritSectEnter(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check that we don't have any context ID collisions (should be very unlikely). + * + * The ASSUMPTION here is that mWaitEvents has all the same events as + * mWaitEventGroups, so it suffices to check one of the two. + */ + if (mWaitEvents.find(idContext) != mWaitEvents.end()) + { + uint32_t cTries = 0; + do + { + rc = generateContextID(uSessionID, uObjectID, &idContext); + AssertRCBreak(rc); + LogFunc(("Found context ID duplicate; trying a different context ID: %#x\n", idContext)); + if (mWaitEvents.find(idContext) != mWaitEvents.end()) + rc = VERR_GSTCTL_MAX_CID_COUNT_REACHED; + } while (RT_FAILURE_NP(rc) && cTries++ < 10); + } + if (RT_SUCCESS(rc)) + { + /* + * Insert event into matching event group. This is for faster per-group lookup of all events later. + */ + uint32_t cInserts = 0; + for (GuestEventTypes::const_iterator ItType = lstEvents.begin(); ItType != lstEvents.end(); ++ItType) + { + GuestWaitEvents &eventGroup = mWaitEventGroups[*ItType]; + if (eventGroup.find(idContext) == eventGroup.end()) + { + try + { + eventGroup.insert(std::pair<uint32_t, GuestWaitEvent *>(idContext, pEvent)); + cInserts++; + } + catch (std::bad_alloc &) + { + while (ItType != lstEvents.begin()) + { + --ItType; + mWaitEventGroups[*ItType].erase(idContext); + } + rc = VERR_NO_MEMORY; + break; + } + } + else + Assert(cInserts > 0); /* else: lstEvents has duplicate entries. */ + } + if (RT_SUCCESS(rc)) + { + Assert(cInserts > 0 || lstEvents.size() == 0); + RT_NOREF(cInserts); + + /* + * Register event in the regular event list. + */ + try + { + mWaitEvents[idContext] = pEvent; + } + catch (std::bad_alloc &) + { + for (GuestEventTypes::const_iterator ItType = lstEvents.begin(); ItType != lstEvents.end(); ++ItType) + mWaitEventGroups[*ItType].erase(idContext); + rc = VERR_NO_MEMORY; + } + } + } + + RTCritSectLeave(&mWaitEventCritSect); + } + if (RT_SUCCESS(rc)) + { + *ppEvent = pEvent; + return rc; + } + + if (pEvent) + delete pEvent; + + return rc; +} + +/** + * Signals all wait events of a specific type (if found) + * and notifies external events accordingly. + * + * @returns VBox status code. + * @param aType Event type to signal. + * @param aEvent Which external event to notify. + */ +int GuestBase::signalWaitEvent(VBoxEventType_T aType, IEvent *aEvent) +{ + int rc = RTCritSectEnter(&mWaitEventCritSect); +#ifdef DEBUG + uint32_t cEvents = 0; +#endif + if (RT_SUCCESS(rc)) + { + GuestEventGroup::iterator itGroup = mWaitEventGroups.find(aType); + if (itGroup != mWaitEventGroups.end()) + { + /* Signal all events in the group, leaving the group empty afterwards. */ + GuestWaitEvents::iterator ItWaitEvt; + while ((ItWaitEvt = itGroup->second.begin()) != itGroup->second.end()) + { + LogFlowThisFunc(("Signalling event=%p, type=%ld (CID %#x: Session=%RU32, Object=%RU32, Count=%RU32) ...\n", + ItWaitEvt->second, aType, ItWaitEvt->first, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(ItWaitEvt->first), + VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(ItWaitEvt->first), VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(ItWaitEvt->first))); + + int rc2 = ItWaitEvt->second->SignalExternal(aEvent); + AssertRC(rc2); + + /* Take down the wait event object details before we erase it from this list and invalid ItGrpEvt. */ + const GuestEventTypes &EvtTypes = ItWaitEvt->second->Types(); + uint32_t idContext = ItWaitEvt->first; + itGroup->second.erase(ItWaitEvt); + + for (GuestEventTypes::const_iterator ItType = EvtTypes.begin(); ItType != EvtTypes.end(); ++ItType) + { + GuestEventGroup::iterator EvtTypeGrp = mWaitEventGroups.find(*ItType); + if (EvtTypeGrp != mWaitEventGroups.end()) + { + ItWaitEvt = EvtTypeGrp->second.find(idContext); + if (ItWaitEvt != EvtTypeGrp->second.end()) + { + LogFlowThisFunc(("Removing event %p (CID %#x) from type %d group\n", ItWaitEvt->second, idContext, *ItType)); + EvtTypeGrp->second.erase(ItWaitEvt); + LogFlowThisFunc(("%zu events left for type %d\n", EvtTypeGrp->second.size(), *ItType)); + Assert(EvtTypeGrp->second.find(idContext) == EvtTypeGrp->second.end()); /* no duplicates */ + } + } + } + } + } + + int rc2 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + LogFlowThisFunc(("Signalled %RU32 events, rc=%Rrc\n", cEvents, rc)); +#endif + return rc; +} + +/** + * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)). + * + * @returns VBox status code. + * @param pCbCtx Pointer to host service callback context. + * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR. + * @param pPayload Additional wait event payload data set set on return. Optional. + */ +int GuestBase::signalWaitEventInternal(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, + int rcGuest, const GuestWaitEventPayload *pPayload) +{ + if (RT_SUCCESS(rcGuest)) + return signalWaitEventInternalEx(pCbCtx, VINF_SUCCESS, + 0 /* Guest rc */, pPayload); + + return signalWaitEventInternalEx(pCbCtx, VERR_GSTCTL_GUEST_ERROR, + rcGuest, pPayload); +} + +/** + * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)). + * Extended version. + * + * @returns VBox status code. + * @param pCbCtx Pointer to host service callback context. + * @param rc Return code (rc) to set as wait result. + * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR. + * @param pPayload Additional wait event payload data set set on return. Optional. + */ +int GuestBase::signalWaitEventInternalEx(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, + int rc, int rcGuest, + const GuestWaitEventPayload *pPayload) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + /* pPayload is optional. */ + + int rc2 = RTCritSectEnter(&mWaitEventCritSect); + if (RT_SUCCESS(rc2)) + { + GuestWaitEvents::iterator itEvent = mWaitEvents.find(pCbCtx->uContextID); + if (itEvent != mWaitEvents.end()) + { + LogFlowThisFunc(("Signalling event=%p (CID %RU32, rc=%Rrc, rcGuest=%Rrc, pPayload=%p) ...\n", + itEvent->second, itEvent->first, rc, rcGuest, pPayload)); + GuestWaitEvent *pEvent = itEvent->second; + AssertPtr(pEvent); + rc2 = pEvent->SignalInternal(rc, rcGuest, pPayload); + } + else + rc2 = VERR_NOT_FOUND; + + int rc3 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc2)) + rc2 = rc3; + } + + return rc2; +} + +/** + * Unregisters (deletes) a wait event. + * + * After successful unregistration the event will not be valid anymore. + * + * @returns VBox status code. + * @param pWaitEvt Wait event to unregister (delete). + */ +int GuestBase::unregisterWaitEvent(GuestWaitEvent *pWaitEvt) +{ + if (!pWaitEvt) /* Nothing to unregister. */ + return VINF_SUCCESS; + + int rc = RTCritSectEnter(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + { + LogFlowThisFunc(("pWaitEvt=%p\n", pWaitEvt)); + +/** @todo r=bird: One way of optimizing this would be to use the pointer + * instead of the context ID as index into the groups, i.e. revert the value + * pair for the GuestWaitEvents type. + * + * An even more efficent way, would be to not use sexy std::xxx containers for + * the types, but iprt/list.h, as that would just be a RTListNodeRemove call for + * each type w/o needing to iterate much at all. I.e. add a struct { + * RTLISTNODE, GuestWaitEvent *pSelf} array to GuestWaitEvent, and change + * GuestEventGroup to std::map<VBoxEventType_T, RTListAnchorClass> + * (RTListAnchorClass == RTLISTANCHOR wrapper with a constructor)). + * + * P.S. the try/catch is now longer needed after I changed pWaitEvt->Types() to + * return a const reference rather than a copy of the type list (and it think it + * is safe to assume iterators are not hitting the heap). Copy vs reference is + * an easy mistake to make in C++. + * + * P.P.S. The mWaitEventGroups optimization is probably just a lot of extra work + * with little payoff. + */ + try + { + /* Remove the event from all event type groups. */ + const GuestEventTypes &lstTypes = pWaitEvt->Types(); + for (GuestEventTypes::const_iterator itType = lstTypes.begin(); + itType != lstTypes.end(); ++itType) + { + /** @todo Slow O(n) lookup. Optimize this. */ + GuestWaitEvents::iterator itCurEvent = mWaitEventGroups[(*itType)].begin(); + while (itCurEvent != mWaitEventGroups[(*itType)].end()) + { + if (itCurEvent->second == pWaitEvt) + { + mWaitEventGroups[(*itType)].erase(itCurEvent); + break; + } + ++itCurEvent; + } + } + + /* Remove the event from the general event list as well. */ + GuestWaitEvents::iterator itEvent = mWaitEvents.find(pWaitEvt->ContextID()); + + Assert(itEvent != mWaitEvents.end()); + Assert(itEvent->second == pWaitEvt); + + mWaitEvents.erase(itEvent); + + delete pWaitEvt; + pWaitEvt = NULL; + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + AssertFailedStmt(rc = VERR_NOT_FOUND); + } + + int rc2 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + +/** + * Waits for an already registered guest wait event. + * + * @return VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get + * the actual result. + * + * @param pWaitEvt Pointer to event to wait for. + * @param msTimeout Timeout (in ms) for waiting. + * @param pType Event type of following IEvent. Optional. + * @param ppEvent Pointer to IEvent which got triggered for this event. Optional. + */ +int GuestBase::waitForEvent(GuestWaitEvent *pWaitEvt, uint32_t msTimeout, VBoxEventType_T *pType, IEvent **ppEvent) +{ + AssertPtrReturn(pWaitEvt, VERR_INVALID_POINTER); + /* pType is optional. */ + /* ppEvent is optional. */ + + int vrc = pWaitEvt->Wait(msTimeout); + if (RT_SUCCESS(vrc)) + { + const ComPtr<IEvent> pThisEvent = pWaitEvt->Event(); + if (pThisEvent.isNotNull()) /* Make sure that we actually have an event associated. */ + { + if (pType) + { + HRESULT hr = pThisEvent->COMGETTER(Type)(pType); + if (FAILED(hr)) + vrc = VERR_COM_UNEXPECTED; + } + if ( RT_SUCCESS(vrc) + && ppEvent) + pThisEvent.queryInterfaceTo(ppEvent); + + unconst(pThisEvent).setNull(); + } + } + + return vrc; +} + +#ifndef VBOX_GUESTCTRL_TEST_CASE +/** + * Convenience function to return a pre-formatted string using an action description and a guest error information. + * + * @returns Pre-formatted string with a user-friendly error string. + * @param strAction Action of when the error occurred. + * @param guestErrorInfo Related guest error information to use. + */ +/* static */ Utf8Str GuestBase::getErrorAsString(const Utf8Str& strAction, const GuestErrorInfo& guestErrorInfo) +{ + Assert(strAction.isNotEmpty()); + return Utf8StrFmt("%s: %s", strAction.c_str(), getErrorAsString(guestErrorInfo).c_str()); +} + +/** + * Returns a user-friendly error message from a given GuestErrorInfo object. + * + * @returns Error message string. + * @param guestErrorInfo Guest error info to return error message for. + */ +/* static */ Utf8Str GuestBase::getErrorAsString(const GuestErrorInfo& guestErrorInfo) +{ + AssertMsg(RT_FAILURE(guestErrorInfo.getRc()), ("Guest rc does not indicate a failure\n")); + + Utf8Str strErr; + +#define CASE_TOOL_ERROR(a_eType, a_strTool) \ + case a_eType: \ + { \ + strErr = GuestProcessTool::guestErrorToString(a_strTool, guestErrorInfo); \ + break; \ + } + + switch (guestErrorInfo.getType()) + { + case GuestErrorInfo::Type_Session: + strErr = GuestSession::i_guestErrorToString(guestErrorInfo.getRc()); + break; + + case GuestErrorInfo::Type_Process: + strErr = GuestProcess::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str()); + break; + + case GuestErrorInfo::Type_File: + strErr = GuestFile::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str()); + break; + + case GuestErrorInfo::Type_Directory: + strErr = GuestDirectory::i_guestErrorToString(guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str()); + break; + + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolCat, VBOXSERVICE_TOOL_CAT); + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolLs, VBOXSERVICE_TOOL_LS); + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkDir, VBOXSERVICE_TOOL_MKDIR); + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkTemp, VBOXSERVICE_TOOL_MKTEMP); + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolRm, VBOXSERVICE_TOOL_RM); + CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolStat, VBOXSERVICE_TOOL_STAT); + + default: + AssertMsgFailed(("Type not implemented (type=%RU32, rc=%Rrc)\n", guestErrorInfo.getType(), guestErrorInfo.getRc())); + strErr = Utf8StrFmt("Unknown / Not implemented -- Please file a bug report (type=%RU32, rc=%Rrc)\n", + guestErrorInfo.getType(), guestErrorInfo.getRc()); + break; + } + + return strErr; +} + +#endif /* VBOX_GUESTCTRL_TEST_CASE */ + +/** + * Converts RTFMODE to FsObjType_T. + * + * @return Converted FsObjType_T type. + * @param fMode RTFMODE to convert. + */ +/* static */ +FsObjType_T GuestBase::fileModeToFsObjType(RTFMODE fMode) +{ + if (RTFS_IS_FILE(fMode)) return FsObjType_File; + else if (RTFS_IS_DIRECTORY(fMode)) return FsObjType_Directory; + else if (RTFS_IS_SYMLINK(fMode)) return FsObjType_Symlink; + + return FsObjType_Unknown; +} + +/** + * Converts a FsObjType_T to a human-readable string. + * + * @returns Human-readable string of FsObjType_T. + * @param enmType FsObjType_T to convert. + */ +/* static */ +const char *GuestBase::fsObjTypeToStr(FsObjType_T enmType) +{ + switch (enmType) + { + case FsObjType_Directory: return "directory"; + case FsObjType_Symlink: return "symbolic link"; + case FsObjType_File: return "file"; + default: break; + } + + return "unknown"; +} + +/** + * Converts a PathStyle_T to a human-readable string. + * + * @returns Human-readable string of PathStyle_T. + * @param enmPathStyle PathStyle_T to convert. + */ +/* static */ +const char *GuestBase::pathStyleToStr(PathStyle_T enmPathStyle) +{ + switch (enmPathStyle) + { + case PathStyle_DOS: return "DOS"; + case PathStyle_UNIX: return "UNIX"; + case PathStyle_Unknown: return "Unknown"; + default: break; + } + + return "<invalid>"; +} + +GuestObject::GuestObject(void) + : mSession(NULL), + mObjectID(0) +{ +} + +GuestObject::~GuestObject(void) +{ +} + +/** + * Binds this guest (control) object to a specific guest (control) session. + * + * @returns VBox status code. + * @param pConsole Pointer to console object to use. + * @param pSession Pointer to session to bind this object to. + * @param uObjectID Object ID for this object to use within that specific session. + * Each object ID must be unique per session. + */ +int GuestObject::bindToSession(Console *pConsole, GuestSession *pSession, uint32_t uObjectID) +{ + AssertPtrReturn(pConsole, VERR_INVALID_POINTER); + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + mConsole = pConsole; + mSession = pSession; + mObjectID = uObjectID; + + return VINF_SUCCESS; +} + +/** + * Registers (creates) a new wait event. + * + * @returns VBox status code. + * @param lstEvents List of events which the new wait event gets triggered at. + * @param ppEvent Returns the new wait event on success. + */ +int GuestObject::registerWaitEvent(const GuestEventTypes &lstEvents, + GuestWaitEvent **ppEvent) +{ + AssertPtr(mSession); + return GuestBase::registerWaitEventEx(mSession->i_getId(), mObjectID, lstEvents, ppEvent); +} + +/** + * Sends a HGCM message to the guest (via the guest control host service). + * + * @returns VBox status code. + * @param uMessage Message ID of message to send. + * @param cParms Number of HGCM message parameters to send. + * @param paParms Array of HGCM message parameters to send. + */ +int GuestObject::sendMessage(uint32_t uMessage, uint32_t cParms, PVBOXHGCMSVCPARM paParms) +{ +#ifndef VBOX_GUESTCTRL_TEST_CASE + ComObjPtr<Console> pConsole = mConsole; + Assert(!pConsole.isNull()); + + int vrc = VERR_HGCM_SERVICE_NOT_FOUND; + + /* Forward the information to the VMM device. */ + VMMDev *pVMMDev = pConsole->i_getVMMDev(); + if (pVMMDev) + { + /* HACK ALERT! We extend the first parameter to 64-bit and use the + two topmost bits for call destination information. */ + Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); + paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT; + paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | VBOX_GUESTCTRL_DST_SESSION; + + /* Make the call. */ + LogFlowThisFunc(("uMessage=%RU32, cParms=%RU32\n", uMessage, cParms)); + vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, cParms, paParms); + if (RT_FAILURE(vrc)) + { + /** @todo What to do here? */ + } + } +#else + LogFlowThisFuncEnter(); + + /* Not needed within testcases. */ + RT_NOREF(uMessage, cParms, paParms); + int vrc = VINF_SUCCESS; +#endif + return vrc; +} + +GuestWaitEventBase::GuestWaitEventBase(void) + : mfAborted(false), + mCID(0), + mEventSem(NIL_RTSEMEVENT), + mRc(VINF_SUCCESS), + mGuestRc(VINF_SUCCESS) +{ +} + +GuestWaitEventBase::~GuestWaitEventBase(void) +{ + if (mEventSem != NIL_RTSEMEVENT) + { + RTSemEventDestroy(mEventSem); + mEventSem = NIL_RTSEMEVENT; + } +} + +/** + * Initializes a wait event with a specific context ID (CID). + * + * @returns VBox status code. + * @param uCID Context ID (CID) to initialize wait event with. + */ +int GuestWaitEventBase::Init(uint32_t uCID) +{ + mCID = uCID; + + return RTSemEventCreate(&mEventSem); +} + +/** + * Signals a wait event. + * + * @returns VBox status code. + * @param rc Return code (rc) to set as wait result. + * @param rcGuest Guest return code (rc) to set additionally, if rc is set to VERR_GSTCTL_GUEST_ERROR. + * @param pPayload Additional wait event payload data set set on return. Optional. + */ +int GuestWaitEventBase::SignalInternal(int rc, int rcGuest, + const GuestWaitEventPayload *pPayload) +{ + if (mfAborted) + return VERR_CANCELLED; + +#ifdef VBOX_STRICT + if (rc == VERR_GSTCTL_GUEST_ERROR) + AssertMsg(RT_FAILURE(rcGuest), ("Guest error indicated but no actual guest error set (%Rrc)\n", rcGuest)); + else + AssertMsg(RT_SUCCESS(rcGuest), ("No guest error indicated but actual guest error set (%Rrc)\n", rcGuest)); +#endif + + int rc2; + if (pPayload) + rc2 = mPayload.CopyFromDeep(*pPayload); + else + rc2 = VINF_SUCCESS; + if (RT_SUCCESS(rc2)) + { + mRc = rc; + mGuestRc = rcGuest; + + rc2 = RTSemEventSignal(mEventSem); + } + + return rc2; +} + +/** + * Waits for the event to get triggered. Will return success if the + * wait was successufl (e.g. was being triggered), otherwise an error will be returned. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get + * the actual result. + * + * @param msTimeout Timeout (in ms) to wait. + * Specifiy 0 to wait indefinitely. + */ +int GuestWaitEventBase::Wait(RTMSINTERVAL msTimeout) +{ + int rc = VINF_SUCCESS; + + if (mfAborted) + rc = VERR_CANCELLED; + + if (RT_SUCCESS(rc)) + { + AssertReturn(mEventSem != NIL_RTSEMEVENT, VERR_CANCELLED); + + rc = RTSemEventWait(mEventSem, msTimeout ? msTimeout : RT_INDEFINITE_WAIT); + if ( RT_SUCCESS(rc) + && mfAborted) + { + rc = VERR_CANCELLED; + } + + if (RT_SUCCESS(rc)) + { + /* If waiting succeeded, return the overall + * result code. */ + rc = mRc; + } + } + + return rc; +} + +GuestWaitEvent::GuestWaitEvent(void) +{ +} + +GuestWaitEvent::~GuestWaitEvent(void) +{ + +} + +/** + * Cancels the event. + */ +int GuestWaitEvent::Cancel(void) +{ + if (mfAborted) /* Already aborted? */ + return VINF_SUCCESS; + + mfAborted = true; + +#ifdef DEBUG_andy + LogFlowThisFunc(("Cancelling %p ...\n")); +#endif + return RTSemEventSignal(mEventSem); +} + +/** + * Initializes a wait event with a given context ID (CID). + * + * @returns VBox status code. + * @param uCID Context ID to initialize wait event with. + */ +int GuestWaitEvent::Init(uint32_t uCID) +{ + return GuestWaitEventBase::Init(uCID); +} + +/** + * Initializes a wait event with a given context ID (CID) and a list of event types to wait for. + * + * @returns VBox status code. + * @param uCID Context ID to initialize wait event with. + * @param lstEvents List of event types to wait for this wait event to get signalled. + */ +int GuestWaitEvent::Init(uint32_t uCID, const GuestEventTypes &lstEvents) +{ + int rc = GuestWaitEventBase::Init(uCID); + if (RT_SUCCESS(rc)) + { + mEventTypes = lstEvents; + } + + return rc; +} + +/** + * Signals the event. + * + * @return VBox status code. + * @param pEvent Public IEvent to associate. + * Optional. + */ +int GuestWaitEvent::SignalExternal(IEvent *pEvent) +{ + if (pEvent) + mEvent = pEvent; + + return RTSemEventSignal(mEventSem); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GuestPath +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Builds a (final) destination path from a given source + destination path. + * + * This does not utilize any file system access whatsoever. Used for guest and host paths. + * + * @returns VBox status code. + * @param strSrcPath Source path to build destination path for. + * @param enmSrcPathStyle Path style the source path is in. + * @param strDstPath Destination path to use for building the (final) destination path. + * @param enmDstPathStyle Path style the destination path is in. + * + * @note See rules within the function. + */ +/* static */ +int GuestPath::BuildDestinationPath(const Utf8Str &strSrcPath, PathStyle_T enmSrcPathStyle, + Utf8Str &strDstPath, PathStyle_T enmDstPathStyle) +{ + /* + * Rules: + * + * # source dest final dest remarks + * + * 1 /src/path1/ /dst/path2/ /dst/path2/<contents of path1> Just copies contents of <contents of path1>, not the path1 itself. + * 2 /src/path1 /dst/path2/ /dst/path2/path1 Copies path1 into path2. + * 3 /src/path1 /dst/path2 /dst/path2 Overwrites stuff from path2 with stuff from path1. + * 4 Dotdot ("..") directories are forbidden for security reasons. + */ + const char *pszSrcName = RTPathFilenameEx(strSrcPath.c_str(), + enmSrcPathStyle == PathStyle_DOS + ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX); + + const char *pszDstName = RTPathFilenameEx(strDstPath.c_str(), + enmDstPathStyle == PathStyle_DOS + ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX); + + if ( (!pszSrcName && !pszDstName) /* #1 */ + || ( pszSrcName && pszDstName)) /* #3 */ + { + /* Note: Must have DirectoryFlag_CopyIntoExisting + FileFlag_NoReplace *not* set. */ + } + else if (pszSrcName && !pszDstName) /* #2 */ + { + if (!strDstPath.endsWith(PATH_STYLE_SEP_STR(enmDstPathStyle))) + strDstPath += PATH_STYLE_SEP_STR(enmDstPathStyle); + strDstPath += pszSrcName; + } + + /* Translate the built destination path to a path compatible with the destination. */ + int vrc = GuestPath::Translate(strDstPath, enmSrcPathStyle, enmDstPathStyle); + if (RT_SUCCESS(vrc)) + { + union + { + RTPATHPARSED Parsed; + RTPATHSPLIT Split; + uint8_t ab[4096]; + } u; + vrc = RTPathParse(strDstPath.c_str(), &u.Parsed, sizeof(u), enmDstPathStyle == PathStyle_DOS + ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX); + if (RT_SUCCESS(vrc)) + { + if (u.Parsed.fProps & RTPATH_PROP_DOTDOT_REFS) /* #4 */ + vrc = VERR_INVALID_PARAMETER; + } + } + + LogRel2(("Guest Control: Building destination path for '%s' (%s) -> '%s' (%s): %Rrc\n", + strSrcPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle), + strDstPath.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc)); + + return vrc; +} + +/** + * Translates a path from a specific path style into another. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if a conversion is not supported. + * @retval VERR_NOT_IMPLEMENTED if path style conversion is not implemented yet. + * @param strPath Path to translate. Will contain the translated path on success. UTF-8 only. + * @param enmSrcPathStyle Source path style \a strPath is expected in. + * @param enmDstPathStyle Destination path style to convert to. + * @param fForce Whether to force the translation to the destination path style or not. + * + * @note This does NOT remove any trailing slashes and/or perform file system lookups! + */ +/* static */ +int GuestPath::Translate(Utf8Str &strPath, PathStyle_T enmSrcPathStyle, PathStyle_T enmDstPathStyle, bool fForce /* = false */) +{ + if (strPath.isEmpty()) + return VINF_SUCCESS; + + AssertReturn(RTStrIsValidEncoding(strPath.c_str()), VERR_INVALID_PARAMETER); + + int vrc = VINF_SUCCESS; + + Utf8Str strTranslated; + + if ( ( enmSrcPathStyle == PathStyle_DOS + && enmDstPathStyle == PathStyle_UNIX) + || (fForce && enmDstPathStyle == PathStyle_UNIX)) + { + strTranslated = strPath; + RTPathChangeToUnixSlashes(strTranslated.mutableRaw(), true /* fForce */); + } + else if ( ( enmSrcPathStyle == PathStyle_UNIX + && enmDstPathStyle == PathStyle_DOS) + || (fForce && enmDstPathStyle == PathStyle_DOS)) + + { + strTranslated = strPath; + RTPathChangeToDosSlashes(strTranslated.mutableRaw(), true /* fForce */); + } + + if ( strTranslated.isEmpty() /* Not forced. */ + && enmSrcPathStyle == enmDstPathStyle) + { + strTranslated = strPath; + } + + if (RT_FAILURE(vrc)) + { + LogRel(("Guest Control: Translating path '%s' (%s) -> '%s' (%s) failed, vrc=%Rrc\n", + strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle), + strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc)); + return vrc; + } + + /* Cleanup. */ + const char *psz = strTranslated.mutableRaw(); + size_t const cch = strTranslated.length(); + size_t off = 0; + while (off < cch) + { + if (off + 1 > cch) + break; + /* Remove double back slashes (DOS only). */ + if ( enmDstPathStyle == PathStyle_DOS + && psz[off] == '\\' + && psz[off + 1] == '\\') + { + strTranslated.erase(off + 1, 1); + off++; + } + /* Remove double forward slashes (UNIX only). */ + if ( enmDstPathStyle == PathStyle_UNIX + && psz[off] == '/' + && psz[off + 1] == '/') + { + strTranslated.erase(off + 1, 1); + off++; + } + off++; + } + + /* Note: Do not trim() paths here, as technically it's possible to create paths with trailing spaces. */ + + strTranslated.jolt(); + + LogRel2(("Guest Control: Translating '%s' (%s) -> '%s' (%s): %Rrc\n", + strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle), + strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc)); + + if (RT_SUCCESS(vrc)) + strPath = strTranslated; + + return vrc; +} + diff --git a/src/VBox/Main/src-client/GuestDirectoryImpl.cpp b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp new file mode 100644 index 00000000..77554ae1 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp @@ -0,0 +1,501 @@ +/* $Id: GuestDirectoryImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest directory handling. + */ + +/* + * Copyright (C) 2012-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTDIRECTORY +#include "LoggingNew.h" + +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestDirectoryImpl.h" +#include "GuestSessionImpl.h" +#include "GuestCtrlImplPrivate.h" + +#include "Global.h" +#include "AutoCaller.h" + +#include <VBox/com/array.h> + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestDirectory) + +HRESULT GuestDirectory::FinalConstruct(void) +{ + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void GuestDirectory::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +int GuestDirectory::init(Console *pConsole, GuestSession *pSession, ULONG aObjectID, const GuestDirectoryOpenInfo &openInfo) +{ + LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s, strFilter=%s, uFlags=%x\n", + pConsole, pSession, aObjectID, openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags)); + + AssertPtrReturn(pConsole, VERR_INVALID_POINTER); + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); + + int vrc = bindToSession(pConsole, pSession, aObjectID); + if (RT_SUCCESS(vrc)) + { + mSession = pSession; + mObjectID = aObjectID; + + mData.mOpenInfo = openInfo; + } + + if (RT_SUCCESS(vrc)) + { + /* Start the directory process on the guest. */ + GuestProcessStartupInfo procInfo; + procInfo.mName.printf(tr("Opening directory \"%s\""), openInfo.mPath.c_str()); + procInfo.mTimeoutMS = 5 * 60 * 1000; /* 5 minutes timeout. */ + procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; + procInfo.mExecutable= Utf8Str(VBOXSERVICE_TOOL_LS); + + procInfo.mArguments.push_back(procInfo.mExecutable); + procInfo.mArguments.push_back(Utf8Str("--machinereadable")); + /* We want the long output format which contains all the object details. */ + procInfo.mArguments.push_back(Utf8Str("-l")); +#if 0 /* Flags are not supported yet. */ + if (uFlags & DirectoryOpenFlag_NoSymlinks) + procInfo.mArguments.push_back(Utf8Str("--nosymlinks")); /** @todo What does GNU here? */ +#endif + /** @todo Recursion support? */ + procInfo.mArguments.push_back(openInfo.mPath); /* The directory we want to open. */ + + /* + * Start the process synchronously and keep it around so that we can use + * it later in subsequent read() calls. + */ + vrc = mData.mProcessTool.init(mSession, procInfo, false /* Async */, NULL /* Guest rc */); + if (RT_SUCCESS(vrc)) + { + /* As we need to know if the directory we were about to open exists and and is accessible, + * do the first read here in order to return a meaningful status here. */ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + vrc = i_readInternal(mData.mObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + /* + * We need to actively terminate our process tool in case of an error here, + * as this otherwise would be done on (directory) object destruction implicitly. + * This in turn then will run into a timeout, as the directory object won't be + * around anymore at that time. Ugly, but that's how it is for the moment. + */ + int vrcTerm = mData.mProcessTool.terminate(30 * RT_MS_1SEC, NULL /* prcGuest */); + AssertRC(vrcTerm); + + if (vrc == VERR_GSTCTL_GUEST_ERROR) + vrc = vrcGuest; + } + } + } + + /* Confirm a successful initialization when it's the case. */ + if (RT_SUCCESS(vrc)) + autoInitSpan.setSucceeded(); + else + autoInitSpan.setFailed(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestDirectory::uninit(void) +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFuncLeave(); +} + +// implementation of private wrapped getters/setters for attributes +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDirectory::getDirectoryName(com::Utf8Str &aDirectoryName) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aDirectoryName = mData.mOpenInfo.mPath; + + return S_OK; +} + +HRESULT GuestDirectory::getFilter(com::Utf8Str &aFilter) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFilter = mData.mOpenInfo.mFilter; + + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Entry point for guest side directory callbacks. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCb Host callback data. + */ +int GuestDirectory::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n", + mData.mOpenInfo.mPath.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb)); + + int vrc; + switch (pCbCtx->uMessage) + { + case GUEST_MSG_DIR_NOTIFY: + { + int idx = 1; /* Current parameter index. */ + CALLBACKDATA_DIR_NOTIFY dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType); + HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc); + + LogFlowFunc(("uType=%RU32, vrcGguest=%Rrc\n", dataCb.uType, (int)dataCb.rc)); + + switch (dataCb.uType) + { + /* Nothing here yet, nothing to dispatch further. */ + + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + break; + } + + default: + /* Silently ignore not implemented functions. */ + vrc = VERR_NOT_SUPPORTED; + break; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Converts a given guest directory error to a string. + * + * @returns Error string. + * @param rcGuest Guest file error to return string for. + * @param pcszWhat Hint of what was involved when the error occurred. + */ +/* static */ +Utf8Str GuestDirectory::i_guestErrorToString(int rcGuest, const char *pcszWhat) +{ + AssertPtrReturn(pcszWhat, ""); + + Utf8Str strErr; + switch (rcGuest) + { +#define CASE_MSG(a_iRc, ...) \ + case a_iRc: strErr.printf(__VA_ARGS__); break; + CASE_MSG(VERR_CANT_CREATE , tr("Access to guest directory \"%s\" is denied"), pcszWhat); + CASE_MSG(VERR_DIR_NOT_EMPTY, tr("Guest directory \"%s\" is not empty"), pcszWhat); + default: + strErr.printf(tr("Error %Rrc for guest directory \"%s\" occurred\n"), rcGuest, pcszWhat); + break; + } + +#undef CASE_MSG + + return strErr; +} + +/** + * @copydoc GuestObject::i_onUnregister + */ +int GuestDirectory::i_onUnregister(void) +{ + LogFlowThisFuncEnter(); + + int vrc = VINF_SUCCESS; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc GuestObject::i_onSessionStatusChange + */ +int GuestDirectory::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus) +{ + RT_NOREF(enmSessionStatus); + + LogFlowThisFuncEnter(); + + int vrc = VINF_SUCCESS; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Closes this guest directory and removes it from the + * guest session's directory list. + * + * @return VBox status code. + * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned. + */ +int GuestDirectory::i_closeInternal(int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + int vrc = mData.mProcessTool.terminate(30 * 1000 /* 30s timeout */, prcGuest); + if (RT_FAILURE(vrc)) + return vrc; + + AssertPtr(mSession); + int vrc2 = mSession->i_directoryUnregister(this); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Reads the next directory entry, internal version. + * + * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available. + * @param objData Where to store the read directory entry as internal object data. + * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned. + */ +int GuestDirectory::i_readInternal(GuestFsObjData &objData, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + GuestProcessStreamBlock curBlock; + int vrc = mData.mProcessTool.waitEx(GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK, &curBlock, prcGuest); + if (RT_SUCCESS(vrc)) + { + /* + * Note: The guest process can still be around to serve the next + * upcoming stream block next time. + */ + if (!mData.mProcessTool.isRunning()) + vrc = mData.mProcessTool.getTerminationStatus(); /* Tool process is not running (anymore). Check termination status. */ + + if (RT_SUCCESS(vrc)) + { + if (curBlock.GetCount()) /* Did we get content? */ + { + if (curBlock.GetString("name")) + { + vrc = objData.FromLs(curBlock, true /* fLong */); + } + else + vrc = VERR_PATH_NOT_FOUND; + } + else + { + /* Nothing to read anymore. Tell the caller. */ + vrc = VERR_NO_MORE_FILES; + } + } + } + + LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Reads the next directory entry. + * + * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available. + * @param fsObjInfo Where to store the read directory entry. + * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned. + */ +int GuestDirectory::i_read(ComObjPtr<GuestFsObjInfo> &fsObjInfo, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + /* Create the FS info object. */ + HRESULT hr = fsObjInfo.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + int vrc; + + /* If we have a valid object data cache, read from it. */ + if (mData.mObjData.mName.isNotEmpty()) + { + vrc = fsObjInfo->init(mData.mObjData); + if (RT_SUCCESS(vrc)) + { + mData.mObjData.mName = ""; /* Mark the object data as being empty (beacon). */ + } + } + else /* Otherwise ask the guest for the next object data (block). */ + { + + GuestFsObjData objData; + vrc = i_readInternal(objData, prcGuest); + if (RT_SUCCESS(vrc)) + vrc = fsObjInfo->init(objData); + } + + LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc)); + return vrc; +} + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// +HRESULT GuestDirectory::close() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_closeInternal(&vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, mData.mOpenInfo.mPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest directory failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + case VERR_NOT_SUPPORTED: + /* Silently skip old Guest Additions which do not support killing the + * the guest directory handling process. */ + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Closing guest directory \"%s\" failed: %Rrc"), mData.mOpenInfo.mPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestDirectory::read(ComPtr<IFsObjInfo> &aObjInfo) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + ComObjPtr<GuestFsObjInfo> fsObjInfo; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_read(fsObjInfo, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + /* Return info object to the caller. */ + hrc = fsObjInfo.queryInterfaceTo(aObjInfo.asOutParam()); + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolLs, vrcGuest, mData.mOpenInfo.mPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Reading guest directory failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + case VERR_GSTCTL_PROCESS_EXIT_CODE: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: %Rrc"), + mData.mOpenInfo.mPath.c_str(), mData.mProcessTool.getRc()); + break; + + case VERR_PATH_NOT_FOUND: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: Path not found"), + mData.mOpenInfo.mPath.c_str()); + break; + + case VERR_NO_MORE_FILES: + /* See SDK reference. */ + hrc = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("Reading guest directory \"%s\" failed: No more entries"), + mData.mOpenInfo.mPath.c_str()); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" returned error: %Rrc\n"), + mData.mOpenInfo.mPath.c_str(), vrc); + break; + } + } + + LogFlowThisFunc(("Returning hrc=%Rhrc / vrc=%Rrc\n", hrc, vrc)); + return hrc; +} + diff --git a/src/VBox/Main/src-client/GuestDnDPrivate.cpp b/src/VBox/Main/src-client/GuestDnDPrivate.cpp new file mode 100644 index 00000000..12715fac --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDPrivate.cpp @@ -0,0 +1,1642 @@ +/* $Id: GuestDnDPrivate.cpp $ */ +/** @file + * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource. + */ + +/* + * Copyright (C) 2011-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include "LoggingNew.h" + +#include "GuestImpl.h" +#include "AutoCaller.h" + +#ifdef VBOX_WITH_DRAG_AND_DROP +# include "ConsoleImpl.h" +# include "ProgressImpl.h" +# include "GuestDnDPrivate.h" + +# include <algorithm> + +# include <iprt/dir.h> +# include <iprt/path.h> +# include <iprt/stream.h> +# include <iprt/semaphore.h> +# include <iprt/cpp/utils.h> + +# include <VMMDev.h> + +# include <VBox/GuestHost/DragAndDrop.h> +# include <VBox/HostServices/DragAndDropSvc.h> +# include <VBox/version.h> + +/** @page pg_main_dnd Dungeons & Dragons - Overview + * Overview: + * + * Drag and Drop is handled over the internal HGCM service for the host <-> + * guest communication. Beside that we need to map the Drag and Drop protocols + * of the various OS's we support to our internal channels, this is also highly + * communicative in both directions. Unfortunately HGCM isn't really designed + * for that. Next we have to foul some of the components. This includes to + * trick X11 on the guest side, but also Qt needs to be tricked on the host + * side a little bit. + * + * The following components are involved: + * + * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content + * of it to the Main IGuest / IGuestDnDSource / IGuestDnDTarget interfaces. + * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress + * interfaces for blocking the caller by showing a progress dialog (see + * this file). + * 3. HGCM service: Handle all messages from the host to the guest at once and + * encapsulate the internal communication details (see dndmanager.cpp and + * friends). + * 4. Guest Additions: Split into the platform neutral part (see + * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts. + * Receive/send message from/to the HGCM service and does all guest specific + * operations. For Windows guests VBoxTray is in charge, whereas on UNIX-y guests + * VBoxClient will be used. + * + * Terminology: + * + * All transfers contain a MIME format and according meta data. This meta data then can + * be interpreted either as raw meta data or something else. When raw meta data is + * being handled, this gets passed through to the destination (guest / host) without + * modification. Other meta data (like URI lists) can and will be modified by the + * receiving side before passing to OS. How and when modifications will be applied + * depends on the MIME format. + * + * Host -> Guest: + * 1. There are DnD Enter, Move, Leave events which are send exactly like this + * to the guest. The info includes the position, MIME types and allowed actions. + * The guest has to respond with an action it would accept, so the GUI could + * change the cursor accordingly. + * 2. On drop, first a drop event is sent. If this is accepted a drop data + * event follows. This blocks the GUI and shows some progress indicator. + * + * Guest -> Host: + * 1. The GUI is asking the guest if a DnD event is pending when the user moves + * the cursor out of the view window. If so, this returns the mimetypes and + * allowed actions. + * (2. On every mouse move this is asked again, to make sure the DnD event is + * still valid.) + * 3. On drop the host request the data from the guest. This blocks the GUI and + * shows some progress indicator. + * + * Implementation hints: + * m_strSupportedFormats here in this file defines the allowed mime-types. + * This is necessary because we need special handling for some of the + * mime-types. E.g. for URI lists we need to transfer the actual dirs and + * files. Text EOL may to be changed. Also unknown mime-types may need special + * handling as well, which may lead to undefined behavior in the host/guest, if + * not done. + * + * Dropping of a directory, means recursively transferring _all_ the content. + * + * Directories and files are placed into the user's temporary directory on the + * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the + * DnD operation, because we didn't know what the DnD target does with it. E.g. + * it could just be opened in place. This could lead ofc to filling up the disk + * within the guest. To inform the user about this, a small app could be + * developed which scans this directory regularly and inform the user with a + * tray icon hint (and maybe the possibility to clean this up instantly). The + * same has to be done in the G->H direction when it is implemented. + * + * Only regular files are supported; symlinks are not allowed. + * + * Transfers currently are an all-succeed or all-fail operation (see todos). + * + * On MacOS hosts we had to implement own DnD "promises" support for file transfers, + * as Qt does not support this out-of-the-box. + * + * The code tries to preserve the file modes of the transfered directories / files. + * This is useful (and maybe necessary) for two things: + * 1. If a file is executable, it should be also after the transfer, so the + * user can just execute it, without manually tweaking the modes first. + * 2. If a dir/file is not accessible by group/others in the host, it shouldn't + * be in the guest. + * In any case, the user mode is always set to rwx (so that we can access it + * ourself, in e.g. for a cleanup case after cancel). + * + * ACEs / ACLs currently are not supported. + * + * Cancelling ongoing transfers is supported in both directions by the guest + * and/or host side and cleans up all previous steps. This also involves + * removing partially transferred directories / files in the temporary directory. + * + ** @todo + * - ESC doesn't really work (on Windows guests it's already implemented) + * ... in any case it seems a little bit difficult to handle from the Qt side. + * - Transfers currently do not have any interactive (UI) callbacks / hooks which + * e.g. would allow to skip / replace / rename and entry, or abort the operation on failure. + * - Add support for more MIME types (especially images, csv) + * - Test unusual behavior: + * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11) + * - Not expected order of the events between HGCM and the guest + * - Security considerations: We transfer a lot of memory between the guest and + * the host and even allow the creation of dirs/files. Maybe there should be + * limits introduced to preventing DoS attacks or filling up all the memory + * (both in the host and the guest). + */ + + +/********************************************************************************************************************************* + * Internal macros. * + ********************************************************************************************************************************/ + +/** Tries locking the GuestDnD object and returns on failure. */ +#define GUESTDND_LOCK() \ + { \ + int rcLock = RTCritSectEnter(&m_CritSect); \ + if (RT_FAILURE(rcLock)) \ + return rcLock; \ + } + +/** Tries locking the GuestDnD object and returns a_Ret failure. */ +#define GUESTDND_LOCK_RET(a_Ret) \ + { \ + int rcLock = RTCritSectEnter(&m_CritSect); \ + if (RT_FAILURE(rcLock)) \ + return a_Ret; \ + } + +/** Unlocks a formerly locked GuestDnD object. */ +#define GUESTDND_UNLOCK() \ + { \ + int rcUnlock = RTCritSectLeave(&m_CritSect); RT_NOREF(rcUnlock); \ + AssertRC(rcUnlock); \ + } + +/********************************************************************************************************************************* + * GuestDnDSendCtx implementation. * + ********************************************************************************************************************************/ + +GuestDnDSendCtx::GuestDnDSendCtx(void) + : pTarget(NULL) + , pState(NULL) +{ + reset(); +} + +/** + * Resets a GuestDnDSendCtx object. + */ +void GuestDnDSendCtx::reset(void) +{ + uScreenID = 0; + + Transfer.reset(); + + int rc2 = EventCallback.Reset(); + AssertRC(rc2); + + GuestDnDData::reset(); +} + +/********************************************************************************************************************************* + * GuestDnDRecvCtx implementation. * + ********************************************************************************************************************************/ + +GuestDnDRecvCtx::GuestDnDRecvCtx(void) + : pSource(NULL) + , pState(NULL) +{ + reset(); +} + +/** + * Resets a GuestDnDRecvCtx object. + */ +void GuestDnDRecvCtx::reset(void) +{ + lstFmtOffered.clear(); + strFmtReq = ""; + strFmtRecv = ""; + enmAction = 0; + + Transfer.reset(); + + int rc2 = EventCallback.Reset(); + AssertRC(rc2); + + GuestDnDData::reset(); +} + +/********************************************************************************************************************************* + * GuestDnDCallbackEvent implementation. * + ********************************************************************************************************************************/ + +GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void) +{ + if (NIL_RTSEMEVENT != m_SemEvent) + RTSemEventDestroy(m_SemEvent); +} + +/** + * Resets a GuestDnDCallbackEvent object. + */ +int GuestDnDCallbackEvent::Reset(void) +{ + int rc = VINF_SUCCESS; + + if (NIL_RTSEMEVENT == m_SemEvent) + rc = RTSemEventCreate(&m_SemEvent); + + m_Rc = VINF_SUCCESS; + return rc; +} + +/** + * Completes a callback event by notifying the waiting side. + * + * @returns VBox status code. + * @param rc Result code to use for the event completion. + */ +int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */) +{ + m_Rc = rc; + return RTSemEventSignal(m_SemEvent); +} + +/** + * Waits on a callback event for being notified. + * + * @returns VBox status code. + * @param msTimeout Timeout (in ms) to wait for callback event. + */ +int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout) +{ + return RTSemEventWait(m_SemEvent, msTimeout); +} + +/******************************************************************************************************************************** + * + ********************************************************************************************************************************/ + +GuestDnDState::GuestDnDState(const ComObjPtr<Guest>& pGuest) + : m_uProtocolVersion(0) + , m_fGuestFeatures0(VBOX_DND_GF_NONE) + , m_EventSem(NIL_RTSEMEVENT) + , m_pParent(pGuest) +{ + reset(); + + int rc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(rc)) + throw rc; + rc = RTSemEventCreate(&m_EventSem); + if (RT_FAILURE(rc)) + throw rc; +} + +GuestDnDState::~GuestDnDState(void) +{ + int rc = RTSemEventDestroy(m_EventSem); + AssertRC(rc); + rc = RTCritSectDelete(&m_CritSect); + AssertRC(rc); +} + +/** + * Notifies the waiting side about a guest notification response. + * + * @returns VBox status code. + * @param rcGuest Guest rc to set for the response. + * Defaults to VINF_SUCCESS (for success). + */ +int GuestDnDState::notifyAboutGuestResponse(int rcGuest /* = VINF_SUCCESS */) +{ + m_rcGuest = rcGuest; + return RTSemEventSignal(m_EventSem); +} + +/** + * Resets a guest drag'n drop state. + */ +void GuestDnDState::reset(void) +{ + LogRel2(("DnD: Reset\n")); + + m_enmState = VBOXDNDSTATE_UNKNOWN; + + m_dndActionDefault = VBOX_DND_ACTION_IGNORE; + m_dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE; + + m_lstFormats.clear(); + m_mapCallbacks.clear(); + + m_rcGuest = VERR_IPE_UNINITIALIZED_STATUS; +} + +/** + * Default callback handler for guest callbacks. + * + * This handler acts as a fallback in case important callback messages are not being handled + * by the specific callers. + * + * @returns VBox status code. Will get sent back to the host service. + * @retval VERR_NO_DATA if no new messages from the host side are available at the moment. + * @retval VERR_CANCELLED for indicating that the current operation was cancelled. + * @param uMsg HGCM message ID (function number). + * @param pvParms Pointer to additional message data. Optional and can be NULL. + * @param cbParms Size (in bytes) additional message data. Optional and can be 0. + * @param pvUser User-supplied pointer on callback registration. + */ +/* static */ +DECLCALLBACK(int) GuestDnDState::i_defaultCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + GuestDnDState *pThis = (GuestDnDState *)pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFunc(("uMsg=%RU32 (%#x)\n", uMsg, uMsg)); + + int vrc = VERR_IPE_UNINITIALIZED_STATUS; + + switch (uMsg) + { + case GUEST_DND_FN_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + if (RT_SUCCESS(pCBData->rc)) + { + AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n")); + pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */ + } + + vrc = pThis->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc, + Utf8StrFmt("Received error from guest: %Rrc", pCBData->rc)); + AssertRCBreak(vrc); + vrc = pThis->notifyAboutGuestResponse(pCBData->rc); + AssertRCBreak(vrc); + break; + } + + case GUEST_DND_FN_GET_NEXT_HOST_MSG: + vrc = VERR_NO_DATA; /* Indicate back to the host service that there are no new messages. */ + break; + + default: + AssertMsgBreakStmt(pThis->isProgressRunning() == false, + ("Progress object not completed / canceld yet! State is '%s' (%#x)\n", + DnDStateToStr(pThis->m_enmState), pThis->m_enmState), + vrc = VERR_INVALID_STATE); /* Please report this! */ + vrc = VERR_CANCELLED; + break; + } + + LogFlowFunc(("Returning rc=%Rrc\n", vrc)); + return vrc; +} + +/** + * Resets the progress object. + * + * @returns HRESULT + * @param pParent Parent to set for the progress object. + * @param strDesc Description of the progress. + */ +HRESULT GuestDnDState::resetProgress(const ComObjPtr<Guest>& pParent, const Utf8Str &strDesc) +{ + AssertReturn(strDesc.isNotEmpty(), E_INVALIDARG); + + m_pProgress.setNull(); + + HRESULT hr = m_pProgress.createObject(); + if (SUCCEEDED(hr)) + { + hr = m_pProgress->init(static_cast<IGuest *>(pParent), + Bstr(strDesc).raw(), + TRUE /* aCancelable */); + } + + return hr; +} + +/** + * Returns whether the progress object has been canceled or not. + * + * @returns \c true if canceled or progress does not exist, \c false if not. + */ +bool GuestDnDState::isProgressCanceled(void) const +{ + if (m_pProgress.isNull()) + return true; + + BOOL fCanceled; + HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled); + AssertComRCReturn(hr, false); + return RT_BOOL(fCanceled); +} + +/** + * Returns whether the progress object still is in a running state or not. + * + * @returns \c true if running, \c false if not. + */ +bool GuestDnDState::isProgressRunning(void) const +{ + if (m_pProgress.isNull()) + return false; + + BOOL fCompleted; + HRESULT const hr = m_pProgress->COMGETTER(Completed)(&fCompleted); + AssertComRCReturn(hr, false); + return !RT_BOOL(fCompleted); +} + +/** + * Sets (registers or unregisters) a callback for a specific HGCM message. + * + * @returns VBox status code. + * @param uMsg HGCM message ID to set callback for. + * @param pfnCallback Callback function pointer to use. Pass NULL to unregister. + * @param pvUser User-provided arguments for the callback function. Optional and can be NULL. + */ +int GuestDnDState::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */) +{ + GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg); + + /* Register. */ + if (pfnCallback) + { + try + { + m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + return VINF_SUCCESS; + } + + /* Unregister. */ + if (it != m_mapCallbacks.end()) + m_mapCallbacks.erase(it); + + return VINF_SUCCESS; +} + +/** + * Sets the progress object to a new state. + * + * @returns VBox status code. + * @param uPercentage Percentage (0-100) to set. + * @param uStatus Status (of type DND_PROGRESS_XXX) to set. + * @param rcOp IPRT-style result code to set. Optional. + * @param strMsg Message to set. Optional. + */ +int GuestDnDState::setProgress(unsigned uPercentage, uint32_t uStatus, + int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */) +{ + LogFlowFunc(("uPercentage=%u, uStatus=%RU32, , rcOp=%Rrc, strMsg=%s\n", + uPercentage, uStatus, rcOp, strMsg.c_str())); + + HRESULT hr = S_OK; + + if (m_pProgress.isNull()) + return VINF_SUCCESS; + + BOOL fCompleted = FALSE; + hr = m_pProgress->COMGETTER(Completed)(&fCompleted); + AssertComRCReturn(hr, VERR_COM_UNEXPECTED); + + BOOL fCanceled = FALSE; + hr = m_pProgress->COMGETTER(Canceled)(&fCanceled); + AssertComRCReturn(hr, VERR_COM_UNEXPECTED); + + LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled)); + + int rc = VINF_SUCCESS; + + switch (uStatus) + { + case DragAndDropSvc::DND_PROGRESS_ERROR: + { + LogRel(("DnD: Guest reported error %Rrc\n", rcOp)); + + if (!fCompleted) + hr = m_pProgress->i_notifyComplete(VBOX_E_DND_ERROR, + COM_IIDOF(IGuest), + m_pParent->getComponentName(), strMsg.c_str()); + break; + } + + case DragAndDropSvc::DND_PROGRESS_CANCELLED: + { + LogRel2(("DnD: Guest cancelled operation\n")); + + if (!fCanceled) + { + hr = m_pProgress->Cancel(); + AssertComRC(hr); + } + + if (!fCompleted) + { + hr = m_pProgress->i_notifyComplete(S_OK); + AssertComRC(hr); + } + break; + } + + case DragAndDropSvc::DND_PROGRESS_RUNNING: + RT_FALL_THROUGH(); + case DragAndDropSvc::DND_PROGRESS_COMPLETE: + { + LogRel2(("DnD: Guest reporting running/completion status with %u%%\n", uPercentage)); + + if ( !fCompleted + && !fCanceled) + { + hr = m_pProgress->SetCurrentOperationProgress(uPercentage); + AssertComRCReturn(hr, VERR_COM_UNEXPECTED); + if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE + || uPercentage >= 100) + { + hr = m_pProgress->i_notifyComplete(S_OK); + AssertComRCReturn(hr, VERR_COM_UNEXPECTED); + } + } + break; + } + + default: + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Dispatching function for handling the host service service callback. + * + * @returns VBox status code. + * @param u32Function HGCM message ID to handle. + * @param pvParms Pointer to optional data provided for a particular message. Optional. + * @param cbParms Size (in bytes) of \a pvParms. + */ +int GuestDnDState::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms) +{ + LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); + + int rc = VERR_WRONG_ORDER; /* Play safe. */ + + /* Whether or not to try calling host-installed callbacks after successfully processing the message. */ + bool fTryCallbacks = false; + + switch (u32Function) + { + case DragAndDropSvc::GUEST_DND_FN_CONNECT: + { + DragAndDropSvc::PVBOXDNDCBCONNECTDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBCONNECTDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBCONNECTDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_CONNECT == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + m_uProtocolVersion = pCBData->uProtocolVersion; + /** @todo Handle flags. */ + + LogThisFunc(("Client connected, using protocol v%RU32\n", m_uProtocolVersion)); + + rc = VINF_SUCCESS; + break; + } + + case DragAndDropSvc::GUEST_DND_FN_REPORT_FEATURES: + { + DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBREPORTFEATURESDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_REPORT_FEATURES == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + m_fGuestFeatures0 = pCBData->fGuestFeatures0; + + LogThisFunc(("Client reported features: %#RX64\n", m_fGuestFeatures0)); + + rc = VINF_SUCCESS; + break; + } + + /* Note: GUEST_DND_FN_EVT_ERROR is handled in either the state's default callback or in specific + * (overriden) callbacks (e.g. GuestDnDSendCtx / GuestDnDRecvCtx). */ + + case DragAndDropSvc::GUEST_DND_FN_DISCONNECT: + { + LogThisFunc(("Client disconnected\n")); + rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS); + break; + } + + case DragAndDropSvc::GUEST_DND_FN_HG_ACK_OP: + { + DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + LogRel2(("DnD: Guest responded with action '%s' for host->guest drag event\n", DnDActionToStr(pCBData->uAction))); + + setActionDefault(pCBData->uAction); + rc = notifyAboutGuestResponse(); + break; + } + + case DragAndDropSvc::GUEST_DND_FN_HG_REQ_DATA: + { + DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + if ( pCBData->cbFormat == 0 + || pCBData->cbFormat > _64K /** @todo Make this configurable? */ + || pCBData->pszFormat == NULL) + { + rc = VERR_INVALID_PARAMETER; + } + else if (!RTStrIsValidEncoding(pCBData->pszFormat)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + setFormats(GuestDnD::toFormatList(pCBData->pszFormat)); + rc = VINF_SUCCESS; + } + + int rc2 = notifyAboutGuestResponse(); + if (RT_SUCCESS(rc)) + rc = rc2; + break; + } + + case DragAndDropSvc::GUEST_DND_FN_HG_EVT_PROGRESS: + { + DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData = + reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc); + if (RT_SUCCESS(rc)) + rc = notifyAboutGuestResponse(pCBData->rc); + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case DragAndDropSvc::GUEST_DND_FN_GH_ACK_PENDING: + { + DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData = + reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + LogRel2(("DnD: Guest responded with pending action '%s' (%RU32 bytes format data) to guest->host drag event\n", + DnDActionToStr(pCBData->uDefAction), pCBData->cbFormat)); + + if ( pCBData->cbFormat == 0 + || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */ + || pCBData->pszFormat == NULL) + { + rc = VERR_INVALID_PARAMETER; + } + else if (!RTStrIsValidEncoding(pCBData->pszFormat)) + { + rc = VERR_INVALID_PARAMETER; + } + else + { + setFormats (GuestDnD::toFormatList(pCBData->pszFormat)); + setActionDefault (pCBData->uDefAction); + setActionsAllowed(pCBData->uAllActions); + + rc = VINF_SUCCESS; + } + + int rc2 = notifyAboutGuestResponse(); + if (RT_SUCCESS(rc)) + rc = rc2; + break; + } +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + default: + /* * Try if the event is covered by a registered callback. */ + fTryCallbacks = true; + break; + } + + /* + * Try the host's installed callbacks (if any). + */ + if (fTryCallbacks) + { + GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function); + if (it != m_mapCallbacks.end()) + { + AssertPtr(it->second.pfnCallback); + rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser); + } + else + { + /* Invoke the default callback handler in case we don't have any registered callback above. */ + rc = i_defaultCallback(u32Function, pvParms, cbParms, this /* pvUser */); + } + } + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +/** + * Helper function to query the internal progress object to an IProgress interface. + * + * @returns HRESULT + * @param ppProgress Where to query the progress object to. + */ +HRESULT GuestDnDState::queryProgressTo(IProgress **ppProgress) +{ + return m_pProgress.queryInterfaceTo(ppProgress); +} + +/** + * Waits for a guest response to happen, extended version. + * + * @returns VBox status code. + * @retval VERR_TIMEOUT when waiting has timed out. + * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest. + * @param msTimeout Timeout (in ms) for waiting. Optional, waits 3000 ms if not specified. + * @param prcGuest Where to return the guest error when VERR_DND_GUEST_ERROR is returned. Optional. + */ +int GuestDnDState::waitForGuestResponseEx(RTMSINTERVAL msTimeout /* = 3000 */, int *prcGuest /* = NULL */) +{ + int vrc = RTSemEventWait(m_EventSem, msTimeout); + if (RT_SUCCESS(vrc)) + { + if (RT_FAILURE(m_rcGuest)) + vrc = VERR_DND_GUEST_ERROR; + if (prcGuest) + *prcGuest = m_rcGuest; + } + return vrc; +} + +/** + * Waits for a guest response to happen. + * + * @returns VBox status code. + * @retval VERR_TIMEOUT when waiting has timed out. + * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest. + * @param prcGuest Where to return the guest error when VERR_DND_GUEST_ERROR is returned. Optional. + * + * @note Uses the default timeout of 3000 ms. + */ +int GuestDnDState::waitForGuestResponse(int *prcGuest /* = NULL */) +{ + return waitForGuestResponseEx(3000 /* ms */, prcGuest); +} + +/********************************************************************************************************************************* + * GuestDnD implementation. * + ********************************************************************************************************************************/ + +/** Static (Singleton) instance of the GuestDnD object. */ +GuestDnD* GuestDnD::s_pInstance = NULL; + +GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest) + : m_pGuest(pGuest) + , m_cTransfersPending(0) +{ + LogFlowFuncEnter(); + + try + { + m_pState = new GuestDnDState(pGuest); + } + catch (std::bad_alloc &) + { + throw VERR_NO_MEMORY; + } + + int rc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(rc)) + throw rc; + + /* List of supported default MIME types. */ + LogRel2(("DnD: Supported default host formats:\n")); + const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT }; + for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++) + { + m_strDefaultFormats.push_back(arrEntries[i]); + LogRel2(("DnD: \t%s\n", arrEntries[i].c_str())); + } +} + +GuestDnD::~GuestDnD(void) +{ + LogFlowFuncEnter(); + + Assert(m_cTransfersPending == 0); /* Sanity. */ + + RTCritSectDelete(&m_CritSect); + + if (m_pState) + delete m_pState; +} + +/** + * Adjusts coordinations to a given screen. + * + * @returns HRESULT + * @param uScreenId ID of screen to adjust coordinates to. + * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success. + * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success. + */ +HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const +{ + /** @todo r=andy Save the current screen's shifting coordinates to speed things up. + * Only query for new offsets when the screen ID or the screen's resolution has changed. */ + + /* For multi-monitor support we need to add shift values to the coordinates + * (depending on the screen number). */ + ComObjPtr<Console> pConsole = m_pGuest->i_getConsole(); + ComPtr<IDisplay> pDisplay; + HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam()); + if (FAILED(hr)) + return hr; + + ULONG dummy; + LONG xShift, yShift; + GuestMonitorStatus_T monitorStatus; + hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy, + &xShift, &yShift, &monitorStatus); + if (FAILED(hr)) + return hr; + + if (puX) + *puX += xShift; + if (puY) + *puY += yShift; + + LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0)); + return S_OK; +} + +/** + * Returns a DnD guest state. + * + * @returns Pointer to DnD guest state, or NULL if not found / invalid. + * @param uID ID of DnD guest state to return. + */ +GuestDnDState *GuestDnD::getState(uint32_t uID /* = 0 */) const +{ + AssertMsgReturn(uID == 0, ("Only one state (0) is supported at the moment\n"), NULL); + + return m_pState; +} + +/** + * Sends a (blocking) message to the host side of the host service. + * + * @returns VBox status code. + * @param u32Function HGCM message ID to send. + * @param cParms Number of parameters to send. + * @param paParms Array of parameters to send. Must match \c cParms. + */ +int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const +{ + Assert(!m_pGuest.isNull()); + ComObjPtr<Console> pConsole = m_pGuest->i_getConsole(); + + /* Forward the information to the VMM device. */ + Assert(!pConsole.isNull()); + VMMDev *pVMMDev = pConsole->i_getVMMDev(); + if (!pVMMDev) + return VERR_COM_OBJECT_NOT_FOUND; + + return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms); +} + +/** + * Registers a GuestDnDSource object with the GuestDnD manager. + * + * Currently only one source is supported at a time. + * + * @returns VBox status code. + * @param Source Source to register. + */ +int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source) +{ + GUESTDND_LOCK(); + + Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */ + m_lstSrc.push_back(Source); + + GUESTDND_UNLOCK(); + return VINF_SUCCESS; +} + +/** + * Unregisters a GuestDnDSource object from the GuestDnD manager. + * + * @returns VBox status code. + * @param Source Source to unregister. + */ +int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source) +{ + GUESTDND_LOCK(); + + GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source); + if (itSrc != m_lstSrc.end()) + m_lstSrc.erase(itSrc); + + GUESTDND_UNLOCK(); + return VINF_SUCCESS; +} + +/** + * Returns the current number of registered sources. + * + * @returns Current number of registered sources. + */ +size_t GuestDnD::getSourceCount(void) +{ + GUESTDND_LOCK_RET(0); + + size_t cSources = m_lstSrc.size(); + + GUESTDND_UNLOCK(); + return cSources; +} + +/** + * Registers a GuestDnDTarget object with the GuestDnD manager. + * + * Currently only one target is supported at a time. + * + * @returns VBox status code. + * @param Target Target to register. + */ +int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target) +{ + GUESTDND_LOCK(); + + Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */ + m_lstTgt.push_back(Target); + + GUESTDND_UNLOCK(); + return VINF_SUCCESS; +} + +/** + * Unregisters a GuestDnDTarget object from the GuestDnD manager. + * + * @returns VBox status code. + * @param Target Target to unregister. + */ +int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target) +{ + GUESTDND_LOCK(); + + GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target); + if (itTgt != m_lstTgt.end()) + m_lstTgt.erase(itTgt); + + GUESTDND_UNLOCK(); + return VINF_SUCCESS; +} + +/** + * Returns the current number of registered targets. + * + * @returns Current number of registered targets. + */ +size_t GuestDnD::getTargetCount(void) +{ + GUESTDND_LOCK_RET(0); + + size_t cTargets = m_lstTgt.size(); + + GUESTDND_UNLOCK(); + return cTargets; +} + +/** + * Static main dispatcher function to handle callbacks from the DnD host service. + * + * @returns VBox status code. + * @param pvExtension Pointer to service extension. + * @param u32Function Callback HGCM message ID. + * @param pvParms Pointer to optional data provided for a particular message. Optional. + * @param cbParms Size (in bytes) of \a pvParms. + */ +/* static */ +DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function, + void *pvParms, uint32_t cbParms) +{ + LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", + pvExtension, u32Function, pvParms, cbParms)); + + GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension); + AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER); + + /** @todo In case we need to handle multiple guest DnD responses at a time this + * would be the place to lookup and dispatch to those. For the moment we + * only have one response -- simple. */ + if (pGuestDnD->m_pState) + return pGuestDnD->m_pState->onDispatch(u32Function, pvParms, cbParms); + + return VERR_NOT_SUPPORTED; +} + +/** + * Static helper function to determine whether a format is part of a given MIME list. + * + * @returns \c true if found, \c false if not. + * @param strFormat Format to search for. + * @param lstFormats MIME list to search in. + */ +/* static */ +bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats) +{ + return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end(); +} + +/** + * Static helper function to create a GuestDnDMIMEList out of a format list string. + * + * @returns MIME list object. + * @param strFormats List of formats to convert. + * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR_STR will be used. + */ +/* static */ +GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */) +{ + GuestDnDMIMEList lstFormats; + RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep); + + for (size_t i = 0; i < lstFormatsTmp.size(); i++) + lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i))); + + return lstFormats; +} + +/** + * Static helper function to create a format list string from a given GuestDnDMIMEList object. + * + * @returns Format list string. + * @param lstFormats GuestDnDMIMEList to convert. + * @param strSep Separator to use between formats. + * Uses DND_FORMATS_SEPARATOR_STR as default. + */ +/* static */ +com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */) +{ + com::Utf8Str strFormat; + for (size_t i = 0; i < lstFormats.size(); i++) + { + const com::Utf8Str &f = lstFormats.at(i); + strFormat += f + strSep; + } + + return strFormat; +} + +/** + * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats. + * + * @returns Filtered MIME list object. + * @param lstFormatsSupported MIME list of supported formats. + * @param lstFormatsWanted MIME list of wanted formats in returned object. + */ +/* static */ +GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted) +{ + GuestDnDMIMEList lstFormats; + + for (size_t i = 0; i < lstFormatsWanted.size(); i++) + { + /* Only keep supported format types. */ + if (std::find(lstFormatsSupported.begin(), + lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end()) + { + lstFormats.push_back(lstFormatsWanted[i]); + } + } + + return lstFormats; +} + +/** + * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats. + * + * @returns Filtered MIME list object. + * @param lstFormatsSupported MIME list of supported formats. + * @param strFormatsWanted Format list string of wanted formats in returned object. + */ +/* static */ +GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted) +{ + GuestDnDMIMEList lstFmt; + + RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR_STR); + size_t i = 0; + while (i < lstFormats.size()) + { + /* Only keep allowed format types. */ + if (std::find(lstFormatsSupported.begin(), + lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end()) + { + lstFmt.push_back(lstFormats[i]); + } + i++; + } + + return lstFmt; +} + +/** + * Static helper function to convert a Main DnD action an internal DnD action. + * + * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported. + * @param enmAction Main DnD action to convert. + */ +/* static */ +VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction) +{ + VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE; + switch (enmAction) + { + case DnDAction_Copy: + dndAction = VBOX_DND_ACTION_COPY; + break; + case DnDAction_Move: + dndAction = VBOX_DND_ACTION_MOVE; + break; + case DnDAction_Link: + /* For now it doesn't seems useful to allow a link + action between host & guest. Later? */ + case DnDAction_Ignore: + /* Ignored. */ + break; + default: + AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction)); + break; + } + + return dndAction; +} + +/** + * Static helper function to convert a Main DnD default action and allowed Main actions to their + * corresponding internal representations. + * + * @param enmDnDActionDefault Default Main action to convert. + * @param pDnDActionDefault Where to store the converted default action. + * @param vecDnDActionsAllowed Allowed Main actions to convert. + * @param pDnDLstActionsAllowed Where to store the converted allowed actions. + */ +/* static */ +void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault, + VBOXDNDACTION *pDnDActionDefault, + const std::vector<DnDAction_T> vecDnDActionsAllowed, + VBOXDNDACTIONLIST *pDnDLstActionsAllowed) +{ + VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE; + VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault); + + if (!vecDnDActionsAllowed.empty()) + { + /* First convert the allowed actions to a bit array. */ + for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++) + dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]); + + /* + * If no default action is set (ignoring), try one of the + * set allowed actions, preferring copy, move (in that order). + */ + if (isDnDIgnoreAction(dndActionDefault)) + { + if (hasDnDCopyAction(dndLstActionsAllowed)) + dndActionDefault = VBOX_DND_ACTION_COPY; + else if (hasDnDMoveAction(dndLstActionsAllowed)) + dndActionDefault = VBOX_DND_ACTION_MOVE; + } + } + + if (pDnDActionDefault) + *pDnDActionDefault = dndActionDefault; + if (pDnDLstActionsAllowed) + *pDnDLstActionsAllowed = dndLstActionsAllowed; +} + +/** + * Static helper function to convert an internal DnD action to its Main representation. + * + * @returns Converted Main DnD action. + * @param dndAction DnD action to convert. + */ +/* static */ +DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction) +{ + /* For now it doesn't seems useful to allow a + * link action between host & guest. Maybe later! */ + return isDnDCopyAction(dndAction) ? DnDAction_Copy + : isDnDMoveAction(dndAction) ? DnDAction_Move + : DnDAction_Ignore; +} + +/** + * Static helper function to convert an internal DnD action list to its Main representation. + * + * @returns Converted Main DnD action list. + * @param dndActionList DnD action list to convert. + */ +/* static */ +std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList) +{ + std::vector<DnDAction_T> vecActions; + + /* For now it doesn't seems useful to allow a + * link action between host & guest. Maybe later! */ + RTCList<DnDAction_T> lstActions; + if (hasDnDCopyAction(dndActionList)) + lstActions.append(DnDAction_Copy); + if (hasDnDMoveAction(dndActionList)) + lstActions.append(DnDAction_Move); + + for (size_t i = 0; i < lstActions.size(); ++i) + vecActions.push_back(lstActions.at(i)); + + return vecActions; +} + +/********************************************************************************************************************************* + * GuestDnDBase implementation. * + ********************************************************************************************************************************/ + +GuestDnDBase::GuestDnDBase(VirtualBoxBase *pBase) + : m_pBase(pBase) + , m_fIsPending(false) +{ + /* Initialize public attributes. */ + m_lstFmtSupported = GuestDnDInst()->defaultFormats(); +} + +GuestDnDBase::~GuestDnDBase(void) +{ +} + +/** + * Checks whether a given DnD format is supported or not. + * + * @returns \c true if supported, \c false if not. + * @param aFormat DnD format to check. + */ +bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const +{ + return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end(); +} + +/** + * Returns the currently supported DnD formats. + * + * @returns List of the supported DnD formats. + */ +const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const +{ + return m_lstFmtSupported; +} + +/** + * Adds DnD formats to the supported formats list. + * + * @returns HRESULT + * @param aFormats List of DnD formats to add. + */ +HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats) +{ + for (size_t i = 0; i < aFormats.size(); ++i) + { + Utf8Str strFormat = aFormats.at(i); + if (std::find(m_lstFmtSupported.begin(), + m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end()) + { + m_lstFmtSupported.push_back(strFormat); + } + } + + return S_OK; +} + +/** + * Removes DnD formats from tehh supported formats list. + * + * @returns HRESULT + * @param aFormats List of DnD formats to remove. + */ +HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats) +{ + for (size_t i = 0; i < aFormats.size(); ++i) + { + Utf8Str strFormat = aFormats.at(i); + GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(), + m_lstFmtSupported.end(), strFormat); + if (itFormat != m_lstFmtSupported.end()) + m_lstFmtSupported.erase(itFormat); + } + + return S_OK; +} + +/** + * Prints an error in the release log and sets the COM error info. + * + * @returns HRESULT + * @param vrc IPRT-style error code to print in addition. + * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code. + * @param pcszMsgFmt Format string. + * @param va Format arguments. + * @note + */ +HRESULT GuestDnDBase::i_setErrorV(int vrc, const char *pcszMsgFmt, va_list va) +{ + char *psz = NULL; + if (RTStrAPrintfV(&psz, pcszMsgFmt, va) < 0) + return E_OUTOFMEMORY; + AssertPtrReturn(psz, E_OUTOFMEMORY); + + HRESULT hrc; + if (RT_FAILURE(vrc)) + { + LogRel(("DnD: Error: %s (%Rrc)\n", psz, vrc)); + hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s (%Rrc)", psz, vrc); + } + else + { + LogRel(("DnD: Error: %s\n", psz)); + hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s", psz); + } + + RTStrFree(psz); + return hrc; +} + +/** + * Prints an error in the release log and sets the COM error info. + * + * @returns HRESULT + * @param vrc IPRT-style error code to print in addition. + * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code. + * @param pcszMsgFmt Format string. + * @param ... Format arguments. + * @note + */ +HRESULT GuestDnDBase::i_setError(int vrc, const char *pcszMsgFmt, ...) +{ + va_list va; + va_start(va, pcszMsgFmt); + HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va); + va_end(va); + + return hrc; +} + +/** + * Prints an error in the release log, sets the COM error info and calls the object's reset function. + * + * @returns HRESULT + * @param pcszMsgFmt Format string. + * @param va Format arguments. + * @note + */ +HRESULT GuestDnDBase::i_setErrorAndReset(const char *pcszMsgFmt, ...) +{ + va_list va; + va_start(va, pcszMsgFmt); + HRESULT const hrc = i_setErrorV(VINF_SUCCESS, pcszMsgFmt, va); + va_end(va); + + i_reset(); + + return hrc; +} + +/** + * Prints an error in the release log, sets the COM error info and calls the object's reset function. + * + * @returns HRESULT + * @param vrc IPRT-style error code to print in addition. + * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code. + * @param pcszMsgFmt Format string. + * @param ... Format arguments. + * @note + */ +HRESULT GuestDnDBase::i_setErrorAndReset(int vrc, const char *pcszMsgFmt, ...) +{ + va_list va; + va_start(va, pcszMsgFmt); + HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va); + va_end(va); + + i_reset(); + + return hrc; +} + +/** + * Adds a new guest DnD message to the internal message queue. + * + * @returns VBox status code. + * @param pMsg Pointer to message to add. + */ +int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg) +{ + m_DataBase.lstMsgOut.push_back(pMsg); + return VINF_SUCCESS; +} + +/** + * Returns the next guest DnD message in the internal message queue (FIFO). + * + * @returns Pointer to guest DnD message, or NULL if none found. + */ +GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void) +{ + if (m_DataBase.lstMsgOut.empty()) + return NULL; + return m_DataBase.lstMsgOut.front(); +} + +/** + * Removes the next guest DnD message from the internal message queue. + */ +void GuestDnDBase::msgQueueRemoveNext(void) +{ + if (!m_DataBase.lstMsgOut.empty()) + { + GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front(); + if (pMsg) + delete pMsg; + m_DataBase.lstMsgOut.pop_front(); + } +} + +/** + * Clears the internal message queue. + */ +void GuestDnDBase::msgQueueClear(void) +{ + LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size())); + + GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin(); + while (itMsg != m_DataBase.lstMsgOut.end()) + { + GuestDnDMsg *pMsg = *itMsg; + if (pMsg) + delete pMsg; + + itMsg++; + } + + m_DataBase.lstMsgOut.clear(); +} + +/** + * Sends a request to the guest side to cancel the current DnD operation. + * + * @returns VBox status code. + */ +int GuestDnDBase::sendCancel(void) +{ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_CANCEL); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + + LogRel2(("DnD: Cancelling operation on guest ...\n")); + + int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_FAILURE(rc)) + LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", rc)); + + return rc; +} + +/** + * Helper function to update the progress based on given a GuestDnDData object. + * + * @returns VBox status code. + * @param pData GuestDnDData object to use for accounting. + * @param pState Guest state to update its progress object for. + * @param cbDataAdd By how much data (in bytes) to update the progress. + */ +int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDState *pState, + size_t cbDataAdd /* = 0 */) +{ + AssertPtrReturn(pData, VERR_INVALID_POINTER); + AssertPtrReturn(pState, VERR_INVALID_POINTER); + /* cbDataAdd is optional. */ + + LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n", + pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd)); + + if ( !pState + || !cbDataAdd) /* Only update if something really changes. */ + return VINF_SUCCESS; + + if (cbDataAdd) + pData->addProcessed(cbDataAdd); + + const uint8_t uPercent = pData->getPercentComplete(); + + LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent)); + + int rc = pState->setProgress(uPercent, + pData->isComplete() + ? DND_PROGRESS_COMPLETE + : DND_PROGRESS_RUNNING); + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Waits for a specific guest callback event to get signalled. + * + * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object. + * @param pEvent Callback event to wait for. + * @param pState Guest state to update. + * @param msTimeout Timeout (in ms) to wait. + */ +int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + AssertPtrReturn(pState, VERR_INVALID_POINTER); + + int rc; + + LogFlowFunc(("msTimeout=%RU32\n", msTimeout)); + + uint64_t tsStart = RTTimeMilliTS(); + do + { + /* + * Wait until our desired callback triggered the + * wait event. As we don't want to block if the guest does not + * respond, do busy waiting here. + */ + rc = pEvent->Wait(500 /* ms */); + if (RT_SUCCESS(rc)) + { + rc = pEvent->Result(); + LogFlowFunc(("Callback done, result is %Rrc\n", rc)); + break; + } + else if (rc == VERR_TIMEOUT) /* Continue waiting. */ + rc = VINF_SUCCESS; + + if ( msTimeout != RT_INDEFINITE_WAIT + && RTTimeMilliTS() - tsStart > msTimeout) + { + rc = VERR_TIMEOUT; + LogRel2(("DnD: Error: Guest did not respond within time\n")); + } + else if (pState->isProgressCanceled()) + { + LogRel2(("DnD: Operation was canceled by user\n")); + rc = VERR_CANCELLED; + } + + } while (RT_SUCCESS(rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP */ + diff --git a/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp new file mode 100644 index 00000000..8a127aa5 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp @@ -0,0 +1,1733 @@ +/* $Id: GuestDnDSourceImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation - Guest drag and drop source. + */ + +/* + * Copyright (C) 2014-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE +#include "LoggingNew.h" + +#include "GuestImpl.h" +#include "GuestDnDSourceImpl.h" +#include "GuestDnDPrivate.h" +#include "ConsoleImpl.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "ThreadTask.h" + +#include <iprt/asm.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/path.h> +#include <iprt/uri.h> + +#include <iprt/cpp/utils.h> /* For unconst(). */ + +#include <VBox/com/array.h> + + +/** + * Base class for a source task. + */ +class GuestDnDSourceTask : public ThreadTask +{ +public: + + GuestDnDSourceTask(GuestDnDSource *pSource) + : ThreadTask("GenericGuestDnDSourceTask") + , mSource(pSource) + , mRC(VINF_SUCCESS) { } + + virtual ~GuestDnDSourceTask(void) { } + + /** Returns the overall result of the task. */ + int getRC(void) const { return mRC; } + /** Returns if the overall result of the task is ok (succeeded) or not. */ + bool isOk(void) const { return RT_SUCCESS(mRC); } + +protected: + + /** COM object pointer to the parent (source). */ + const ComObjPtr<GuestDnDSource> mSource; + /** Overall result of the task. */ + int mRC; +}; + +/** + * Task structure for receiving data from a source using + * a worker thread. + */ +class GuestDnDRecvDataTask : public GuestDnDSourceTask +{ +public: + + GuestDnDRecvDataTask(GuestDnDSource *pSource, GuestDnDRecvCtx *pCtx) + : GuestDnDSourceTask(pSource) + , mpCtx(pCtx) + { + m_strTaskName = "dndSrcRcvData"; + } + + void handler() + { + LogFlowThisFunc(("\n")); + + const ComObjPtr<GuestDnDSource> pThis(mSource); + Assert(!pThis.isNull()); + + AutoCaller autoCaller(pThis); + if (FAILED(autoCaller.rc())) + return; + + int vrc = pThis->i_receiveData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */); + if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */ + { + if (vrc != VERR_CANCELLED) + LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc)); + + /* Make sure to fire a cancel request to the guest side in case something went wrong. */ + pThis->sendCancel(); + } + } + + virtual ~GuestDnDRecvDataTask(void) { } + +protected: + + /** Pointer to receive data context. */ + GuestDnDRecvCtx *mpCtx; +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +GuestDnDSource::GuestDnDSource(void) + : GuestDnDBase(this) { } + +GuestDnDSource::~GuestDnDSource(void) { } + +HRESULT GuestDnDSource::FinalConstruct(void) +{ + /* + * Set the maximum block size this source can handle to 64K. This always has + * been hardcoded until now. + * + * Note: Never ever rely on information from the guest; the host dictates what and + * how to do something, so try to negogiate a sensible value here later. + */ + mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ + + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void GuestDnDSource::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDSource::init(const ComObjPtr<Guest>& pGuest) +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(m_pGuest) = pGuest; + + /* Set the response we're going to use for this object. + * + * At the moment we only have one response total, as we + * don't allow + * 1) parallel transfers (multiple G->H at the same time) + * nor 2) mixed transfers (G->H + H->G at the same time). + */ + m_pState = GuestDnDInst()->getState(); + AssertPtrReturn(m_pState, E_POINTER); + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestDnDSource::uninit(void) +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; +} + +// implementation of wrapped IDnDBase methods. +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE; + + return S_OK; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFormats = GuestDnDBase::i_getFormats(); + + return S_OK; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_addFormats(aFormats); +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDSource::removeFormats(const GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_removeFormats(aFormats); +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +// implementation of wrapped IDnDSource methods. +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats, + std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + /* aDefaultAction is optional. */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Default is ignoring the action. */ + if (aDefaultAction) + *aDefaultAction = DnDAction_Ignore; + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtr(pState); + + /* Check if any operation is active, and if so, bail out, returning an ignore action (see above). */ + if (pState->get() != VBOXDNDSTATE_UNKNOWN) + return S_OK; + + pState->set(VBOXDNDSTATE_QUERY_FORMATS); + + HRESULT hrc = S_OK; + + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_GH_REQ_PENDING); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendUInt32(uScreenId); + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + int vrcGuest; + vrc = pState->waitForGuestResponseEx(100 /* Timeout in ms */, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (!isDnDIgnoreAction(pState->getActionDefault())) + { + /* + * In the GuestDnDSource case the source formats are from the guest, + * as GuestDnDSource acts as a target for the guest. The host always + * dictates what's supported and what's not, so filter out all formats + * which are not supported by the host. + */ + GuestDnDMIMEList const &lstGuest = pState->formats(); + GuestDnDMIMEList const lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, lstGuest); + if (lstFiltered.size()) + { + LogRel2(("DnD: Host offered the following formats:\n")); + for (size_t i = 0; i < lstFiltered.size(); i++) + LogRel2(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str())); + + aFormats = lstFiltered; + aAllowedActions = GuestDnD::toMainActions(pState->getActionsAllowed()); + if (aDefaultAction) + *aDefaultAction = GuestDnD::toMainAction(pState->getActionDefault()); + + /* Apply the (filtered) formats list. */ + m_lstFmtOffered = lstFiltered; + } + else + { + bool fSetError = true; /* Whether to set an error and reset or not. */ + + /* + * HACK ALERT: As we now expose an error (via i_setErrorAndReset(), see below) back to the API client, we + * have to add a kludge here. Older X11-based Guest Additions report "TARGETS, MULTIPLE" back + * to us, even if they don't offer any other *supported* formats of the host. This then in turn + * would lead to exposing an error, whereas we just should ignore those specific X11-based + * formats. For anything other we really want to be notified by setting an error though. + */ + if ( lstGuest.size() == 2 + && GuestDnD::isFormatInFormatList("TARGETS", lstGuest) + && GuestDnD::isFormatInFormatList("MULTIPLE", lstGuest)) + { + fSetError = false; + } + /* HACK ALERT END */ + + if (fSetError) + hrc = i_setErrorAndReset(tr("Negotiation of formats between guest and host failed!\n\nHost offers: %s\n\nGuest offers: %s"), + GuestDnD::toFormatString(m_lstFmtSupported , ",").c_str(), + GuestDnD::toFormatString(pState->formats() , ",").c_str()); + else /* Just silently reset. */ + i_reset(); + } + } + /* Note: Don't report an error here when the action is "ignore" -- that only means that the current window on the guest + simply doesn't support the format or drag and drop at all. */ + } + else + hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Requesting pending data from guest failed")); + } + else + { + switch (vrc) + { + case VERR_ACCESS_DENIED: + { + hrc = i_setErrorAndReset(tr("Dragging from guest to host not allowed -- make sure that the correct drag'n drop mode is set")); + break; + } + + case VERR_NOT_SUPPORTED: + { + hrc = i_setErrorAndReset(tr("Dragging from guest to host not supported by guest -- make sure that the Guest Additions are properly installed and running")); + break; + } + + default: + { + hrc = i_setErrorAndReset(vrc, tr("Sending drag pending event to guest failed")); + break; + } + } + } + + pState->set(VBOXDNDSTATE_UNKNOWN); + + LogFlowFunc(("hr=%Rhrc\n", hrc)); + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction)); + + /* Input validation. */ + if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No drop format specified")); + + /* Is the specified format in our list of (left over) offered formats? */ + if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered)) + return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str()); + + /* Check that the given action is supported by us. */ + VBOXDNDACTION dndAction = GuestDnD::toHGCMAction(aAction); + if (isDnDIgnoreAction(dndAction)) /* If there is no usable action, ignore this request. */ + return S_OK; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Check if this object still is in a pending state and bail out if so. */ + if (m_fIsPending) + return setError(E_FAIL, tr("Current drop operation to host still in progress")); + + /* Reset our internal state. */ + i_reset(); + + /* At the moment we only support one transfer at a time. */ + if (GuestDnDInst()->getSourceCount()) + return setError(E_INVALIDARG, tr("Another drag and drop operation to the host already is in progress")); + + /* Reset progress object. */ + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtr(pState); + HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to host")); + if (FAILED(hr)) + return hr; + + GuestDnDRecvDataTask *pTask = NULL; + + try + { + mData.mRecvCtx.pSource = this; + mData.mRecvCtx.pState = pState; + mData.mRecvCtx.enmAction = dndAction; + mData.mRecvCtx.strFmtReq = aFormat; + mData.mRecvCtx.lstFmtOffered = m_lstFmtOffered; + + LogRel2(("DnD: Requesting data from guest in format '%s'\n", aFormat.c_str())); + + pTask = new GuestDnDRecvDataTask(this, &mData.mRecvCtx); + if (!pTask->isOk()) + { + delete pTask; + LogRel2(("DnD: Receive data task failed to initialize\n")); + throw hr = E_FAIL; + } + + /* Drop write lock before creating thread. */ + alock.release(); + + /* This function delete pTask in case of exceptions, + * so there is no need in the call of delete operator. */ + hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER); + pTask = NULL; /* Note: pTask is now owned by the worker thread. */ + } + catch (std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + catch (...) + { + LogRel2(("DnD: Could not create thread for data receiving task\n")); + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) + { + /* Register ourselves at the DnD manager. */ + GuestDnDInst()->registerSource(this); + + hr = pState->queryProgressTo(aProgress.asOutParam()); + ComAssertComRC(hr); + + } + else + hr = i_setErrorAndReset(tr("Starting thread for GuestDnDSource failed (%Rhrc)"), hr); + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Don't allow receiving the actual data until our current transfer is complete. */ + if (m_fIsPending) + return setError(E_FAIL, tr("Current drop operation to host still in progress")); + + HRESULT hr = S_OK; + + try + { + GuestDnDRecvCtx *pCtx = &mData.mRecvCtx; + if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length())) + { + PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles; + + const char *pcszDropDirAbs = DnDDroppedFilesGetDirAbs(pDF); + AssertPtr(pcszDropDirAbs); + + LogRel2(("DnD: Using drop directory '%s', got %RU64 root entries\n", + pcszDropDirAbs, DnDTransferListGetRootCount(&pCtx->Transfer.List))); + + /* We return the data as "text/uri-list" MIME data here. */ + char *pszBuf = NULL; + size_t cbBuf = 0; + int rc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, + pcszDropDirAbs, DND_PATH_SEPARATOR_STR, &pszBuf, &cbBuf); + if (RT_SUCCESS(rc)) + { + Assert(cbBuf); + AssertPtr(pszBuf); + + aData.resize(cbBuf); + memcpy(&aData.front(), pszBuf, cbBuf); + RTStrFree(pszBuf); + } + else + LogRel(("DnD: Unable to build source root list, rc=%Rrc\n", rc)); + } + else /* Raw data. */ + { + if (pCtx->Meta.cbData) + { + /* Copy the data into a safe array of bytes. */ + aData.resize(pCtx->Meta.cbData); + memcpy(&aData.front(), pCtx->Meta.pvData, pCtx->Meta.cbData); + } + else + aData.resize(0); + } + } + catch (std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +// implementation of internal methods. +///////////////////////////////////////////////////////////////////////////// + +/** + * Returns an error string from a guest DnD error. + * + * @returns Error string. + * @param guestRc Guest error to return error string for. + */ +/* static */ +Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc) +{ + Utf8Str strError; + + switch (guestRc) + { + case VERR_ACCESS_DENIED: + strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest " + "user does not have the appropriate access rights for. Please make sure that all selected " + "elements can be accessed and that your guest user has the appropriate rights")); + break; + + case VERR_NOT_FOUND: + /* Should not happen due to file locking on the guest, but anyway ... */ + strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not" + "found on the guest anymore. This can be the case if the guest files were moved and/or" + "altered while the drag and drop operation was in progress")); + break; + + case VERR_SHARING_VIOLATION: + strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. " + "Please make sure that all selected elements can be accessed and that your guest user has " + "the appropriate rights")); + break; + + case VERR_TIMEOUT: + strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time")); + break; + + default: + strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc); + break; + } + + return strError; +} + +/** + * Returns an error string from a host DnD error. + * + * @returns Error string. + * @param hostRc Host error to return error string for. + */ +/* static */ +Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc) +{ + Utf8Str strError; + + switch (hostRc) + { + case VERR_ACCESS_DENIED: + strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host " + "user does not have the appropriate access rights for. Please make sure that all selected " + "elements can be accessed and that your host user has the appropriate rights.")); + break; + + case VERR_DISK_FULL: + strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full).")); + break; + + case VERR_NOT_FOUND: + /* Should not happen due to file locking on the host, but anyway ... */ + strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not" + "found on the host anymore. This can be the case if the host files were moved and/or" + "altered while the drag and drop operation was in progress.")); + break; + + case VERR_SHARING_VIOLATION: + strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. " + "Please make sure that all selected elements can be accessed and that your host user has " + "the appropriate rights.")); + break; + + default: + strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc); + break; + } + + return strError; +} + +/** + * Resets all internal data and state. + */ +void GuestDnDSource::i_reset(void) +{ + LogRel2(("DnD: Source reset\n")); + + mData.mRecvCtx.reset(); + + m_fIsPending = false; + + /* Unregister ourselves from the DnD manager. */ + GuestDnDInst()->unregisterSource(this); +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + +/** + * Handles receiving a send data header from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param pDataHdr Pointer to send data header from the guest. + */ +int GuestDnDSource::i_onReceiveDataHdr(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + + LogRel2(("DnD: Receiving %RU64 bytes total data (%RU32 bytes meta data, %RU64 objects) from guest ...\n", + pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects)); + + AssertReturn(pDataHdr->cbTotal >= pDataHdr->cbMeta, VERR_INVALID_PARAMETER); + + pCtx->Meta.cbAnnounced = pDataHdr->cbMeta; + pCtx->cbExtra = pDataHdr->cbTotal - pDataHdr->cbMeta; + + Assert(pCtx->Transfer.cObjToProcess == 0); /* Sanity. */ + Assert(pCtx->Transfer.cObjProcessed == 0); + + pCtx->Transfer.reset(); + + pCtx->Transfer.cObjToProcess = pDataHdr->cObjects; + + /** @todo Handle compression type. */ + /** @todo Handle checksum type. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** + * Main function for receiving data from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param pSndData Pointer to send data block from the guest. + */ +int GuestDnDSource::i_onReceiveData(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATA pSndData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSndData, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + try + { + GuestDnDTransferRecvData *pTransfer = &pCtx->Transfer; + + size_t cbData; + void *pvData; + size_t cbTotalAnnounced; + size_t cbMetaAnnounced; + + if (m_pState->m_uProtocolVersion < 3) + { + cbData = pSndData->u.v1.cbData; + pvData = pSndData->u.v1.pvData; + + /* Sends the total data size to receive for every data chunk. */ + cbTotalAnnounced = pSndData->u.v1.cbTotalSize; + + /* Meta data size always is cbData, meaning there cannot be an + * extended data chunk transfer by sending further data. */ + cbMetaAnnounced = cbData; + } + else + { + cbData = pSndData->u.v3.cbData; + pvData = pSndData->u.v3.pvData; + + /* Note: Data sizes get initialized in i_onReceiveDataHdr(). + * So just use the set values here. */ + cbTotalAnnounced = pCtx->getTotalAnnounced(); + cbMetaAnnounced = pCtx->Meta.cbAnnounced; + } + + if (cbData > cbTotalAnnounced) + { + AssertMsgFailed(("Incoming data size invalid: cbData=%zu, cbTotal=%zu\n", cbData, cbTotalAnnounced)); + rc = VERR_INVALID_PARAMETER; + } + else if ( cbTotalAnnounced == 0 + || cbTotalAnnounced < cbMetaAnnounced) + { + AssertMsgFailed(("cbTotal (%zu) is smaller than cbMeta (%zu)\n", cbTotalAnnounced, cbMetaAnnounced)); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_FAILURE(rc)) + return rc; + + AssertReturn(cbData <= mData.mcbBlockSize, VERR_BUFFER_OVERFLOW); + + const size_t cbMetaRecv = pCtx->Meta.add(pvData, cbData); + AssertReturn(cbMetaRecv <= pCtx->Meta.cbData, VERR_BUFFER_OVERFLOW); + + LogFlowThisFunc(("cbData=%zu, cbMetaRecv=%zu, cbMetaAnnounced=%zu, cbTotalAnnounced=%zu\n", + cbData, cbMetaRecv, cbMetaAnnounced, cbTotalAnnounced)); + + LogRel2(("DnD: %RU8%% of meta data complete (%zu/%zu bytes)\n", + (uint8_t)(cbMetaRecv * 100 / RT_MAX(cbMetaAnnounced, 1)), cbMetaRecv, cbMetaAnnounced)); + + /* + * (Meta) Data transfer complete? + */ + if (cbMetaAnnounced == cbMetaRecv) + { + LogRel2(("DnD: Receiving meta data complete\n")); + + if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length())) + { + rc = DnDTransferListInitEx(&pTransfer->List, + DnDDroppedFilesGetDirAbs(&pTransfer->DroppedFiles), DNDTRANSFERLISTFMT_NATIVE); + if (RT_SUCCESS(rc)) + rc = DnDTransferListAppendRootsFromBuffer(&pTransfer->List, DNDTRANSFERLISTFMT_URI, + (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR, + DNDTRANSFERLIST_FLAGS_NONE); + /* Validation. */ + if (RT_SUCCESS(rc)) + { + uint64_t cRoots = DnDTransferListGetRootCount(&pTransfer->List); + + LogRel2(("DnD: Received %RU64 root entries from guest\n", cRoots)); + + if ( cRoots == 0 + || cRoots > pTransfer->cObjToProcess) + { + LogRel(("DnD: Number of root entries invalid / mismatch: Got %RU64, expected %RU64\n", + cRoots, pTransfer->cObjToProcess)); + rc = VERR_INVALID_PARAMETER; + } + } + + if (RT_SUCCESS(rc)) + { + /* Update our process with the data we already received. */ + rc = updateProgress(pCtx, pCtx->pState, cbMetaAnnounced); + AssertRC(rc); + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Error building root entry list, rc=%Rrc\n", rc)); + } + else /* Raw data. */ + { + rc = updateProgress(pCtx, pCtx->pState, cbData); + AssertRC(rc); + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Error receiving meta data, rc=%Rrc\n", rc)); + } + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +int GuestDnDSource::i_onReceiveDir(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cbPath, VERR_INVALID_PARAMETER); + + LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode)); + + const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur; + const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles; + + int rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_DIRECTORY, + DnDDroppedFilesGetDirAbs(pDF), pszPath); + if (RT_SUCCESS(rc)) + { + const char *pcszPathAbs = DnDTransferObjectGetSourcePath(pObj); + AssertPtr(pcszPathAbs); + + rc = RTDirCreateFullPath(pcszPathAbs, fMode); + if (RT_SUCCESS(rc)) + { + pCtx->Transfer.cObjProcessed++; + if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess) + { + rc = DnDDroppedFilesAddDir(pDF, pcszPathAbs); + } + else + rc = VERR_TOO_MUCH_DATA; + + DnDTransferObjectDestroy(pObj); + + if (RT_FAILURE(rc)) + LogRel2(("DnD: Created guest directory '%s' on host\n", pcszPathAbs)); + } + else + LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pcszPathAbs, rc)); + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Receiving guest directory '%s' failed with rc=%Rrc\n", pszPath, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a file header from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param pszPath File path of file to use. + * @param cbPath Size (in bytes, including terminator) of file path. + * @param cbSize File size (in bytes) to receive. + * @param fMode File mode to use. + * @param fFlags Additional receive flags; not used yet. + */ +int GuestDnDSource::i_onReceiveFileHdr(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, + uint64_t cbSize, uint32_t fMode, uint32_t fFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cbPath, VERR_INVALID_PARAMETER); + AssertReturn(fMode, VERR_INVALID_PARAMETER); + /* fFlags are optional. */ + + RT_NOREF(fFlags); + + LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags)); + + AssertMsgReturn(cbSize <= pCtx->cbExtra, + ("File size (%RU64) exceeds extra size to transfer (%RU64)\n", cbSize, pCtx->cbExtra), VERR_INVALID_PARAMETER); + AssertMsgReturn( pCtx->isComplete() == false + && pCtx->Transfer.cObjToProcess, + ("Data transfer already complete, bailing out\n"), VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + + do + { + const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur; + + if ( DnDTransferObjectIsOpen(pObj) + && !DnDTransferObjectIsComplete(pObj)) + { + AssertMsgFailed(("Object '%s' not complete yet\n", DnDTransferObjectGetSourcePath(pObj))); + rc = VERR_WRONG_ORDER; + break; + } + + const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles; + + rc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_FILE, DnDDroppedFilesGetDirAbs(pDF), pszPath); + AssertRCBreak(rc); + + const char *pcszSource = DnDTransferObjectGetSourcePath(pObj); + AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER); + + /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */ + rc = DnDTransferObjectOpen(pObj, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE, + (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR, DNDTRANSFEROBJECT_FLAGS_NONE); + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pcszSource, rc)); + break; + } + + /* Note: Protocol v1 does not send any file sizes, so always 0. */ + if (m_pState->m_uProtocolVersion >= 2) + rc = DnDTransferObjectSetSize(pObj, cbSize); + + /** @todo Unescape path before printing. */ + LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode %#x)\n", + pcszSource, DnDTransferObjectGetSize(pObj), DnDTransferObjectGetMode(pObj))); + + /** @todo Set progress object title to current file being transferred? */ + + if (DnDTransferObjectIsComplete(pObj)) /* 0-byte file? We're done already. */ + { + LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pcszSource)); + + pCtx->Transfer.cObjProcessed++; + if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess) + { + /* Add for having a proper rollback. */ + rc = DnDDroppedFilesAddFile(pDF, pcszSource); + } + else + rc = VERR_TOO_MUCH_DATA; + + DnDTransferObjectDestroy(pObj); + } + + } while (0); + + if (RT_FAILURE(rc)) + LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives file data from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param pvData Pointer to file data received from the guest. + * @param pCtx Size (in bytes) of file data received from the guest. + */ +int GuestDnDSource::i_onReceiveFileData(GuestDnDRecvCtx *pCtx, const void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + + LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize)); + + /* + * Sanity checking. + */ + if (cbData > mData.mcbBlockSize) + return VERR_INVALID_PARAMETER; + + do + { + const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur; + + const char *pcszSource = DnDTransferObjectGetSourcePath(pObj); + AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER); + + AssertMsgReturn(DnDTransferObjectIsOpen(pObj), + ("Object '%s' not open (anymore)\n", pcszSource), VERR_WRONG_ORDER); + AssertMsgReturn(DnDTransferObjectIsComplete(pObj) == false, + ("Object '%s' already marked as complete\n", pcszSource), VERR_WRONG_ORDER); + + uint32_t cbWritten; + rc = DnDTransferObjectWrite(pObj, pvData, cbData, &cbWritten); + if (RT_FAILURE(rc)) + LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pcszSource, rc)); + + Assert(cbWritten <= cbData); + if (cbWritten < cbData) + { + LogRel(("DnD: Only written %RU32 of %RU32 bytes of guest file '%s' -- disk full?\n", + cbWritten, cbData, pcszSource)); + rc = VERR_IO_GEN_FAILURE; /** @todo Find a better rc. */ + break; + } + + rc = updateProgress(pCtx, pCtx->pState, cbWritten); + AssertRCBreak(rc); + + if (DnDTransferObjectIsComplete(pObj)) + { + LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pcszSource)); + + pCtx->Transfer.cObjProcessed++; + if (pCtx->Transfer.cObjProcessed > pCtx->Transfer.cObjToProcess) + rc = VERR_TOO_MUCH_DATA; + + DnDTransferObjectDestroy(pObj); + } + + } while (0); + + if (RT_FAILURE(rc)) + LogRel(("DnD: Error receiving guest file data, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + +/** + * Main function to receive DnD data from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param msTimeout Timeout (in ms) to wait for receiving data. + */ +int GuestDnDSource::i_receiveData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* Sanity. */ + AssertMsgReturn(pCtx->enmAction, + ("Action to perform is none when it shouldn't\n"), VERR_INVALID_PARAMETER); + AssertMsgReturn(pCtx->strFmtReq.isNotEmpty(), + ("Requested format from host is empty when it shouldn't\n"), VERR_INVALID_PARAMETER); + + /* + * Do we need to receive a different format than initially requested? + * + * For example, receiving a file link as "text/plain" requires still to receive + * the file from the guest as "text/uri-list" first, then pointing to + * the file path on the host in the "text/plain" data returned. + */ + + bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */ + + LogFlowFunc(("strFmtReq=%s, strFmtRecv=%s, enmAction=0x%x\n", + pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str(), pCtx->enmAction)); + + /* Plain text wanted? */ + if ( pCtx->strFmtReq.equalsIgnoreCase("text/plain") + || pCtx->strFmtReq.equalsIgnoreCase("text/plain;charset=utf-8")) + { + /* Did the guest offer a file? Receive a file instead. */ + if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered)) + pCtx->strFmtRecv = "text/uri-list"; + /* Guest only offers (plain) text. */ + else + pCtx->strFmtRecv = "text/plain;charset=utf-8"; + + /** @todo Add more conversions here. */ + } + /* File(s) wanted? */ + else if (pCtx->strFmtReq.equalsIgnoreCase("text/uri-list")) + { + /* Does the guest support sending files? */ + if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered)) + pCtx->strFmtRecv = "text/uri-list"; + else /* Bail out. */ + fFoundFormat = false; + } + + int rc = VINF_SUCCESS; + + if (fFoundFormat) + { + if (!pCtx->strFmtRecv.equals(pCtx->strFmtReq)) + LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n", + pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str())); + + /* + * Call the appropriate receive handler based on the data format to handle. + */ + bool fURIData = DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()); + if (fURIData) + { + rc = i_receiveTransferData(pCtx, msTimeout); + } + else + { + rc = i_receiveRawData(pCtx, msTimeout); + } + } + else /* Just inform the user (if verbose release logging is enabled). */ + { + LogRel(("DnD: The guest does not support format '%s':\n", pCtx->strFmtReq.c_str())); + LogRel(("DnD: Guest offered the following formats:\n")); + for (size_t i = 0; i < pCtx->lstFmtOffered.size(); i++) + LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->lstFmtOffered.at(i).c_str())); + + rc = VERR_NOT_SUPPORTED; + } + + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Receiving data from guest failed with %Rrc\n", rc)); + + /* Let the guest side know first. */ + sendCancel(); + + /* Reset state. */ + i_reset(); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives raw (meta) data from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param msTimeout Timeout (in ms) to wait for receiving data. + */ +int GuestDnDSource::i_receiveRawData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int rc; + + LogFlowFuncEnter(); + + GuestDnDState *pState = pCtx->pState; + AssertPtr(pCtx->pState); + + GuestDnD *pInst = GuestDnDInst(); + if (!pInst) + return VERR_INVALID_POINTER; + +#define REGISTER_CALLBACK(x) \ + do { \ + rc = pState->setCallback(x, i_receiveRawDataCallback, pCtx); \ + if (RT_FAILURE(rc)) \ + return rc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int rc2 = pState->setCallback(x, NULL); \ + AssertRC(rc2); \ + } while (0) + + /* + * Register callbacks. + */ + REGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + if (m_pState->m_uProtocolVersion >= 3) + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR); + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA); + + do + { + /* + * Receive the raw data. + */ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_GH_EVT_DROPPED); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1); + Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1); + Msg.appendUInt32(pCtx->enmAction); + + /* Make the initial call to the guest by telling that we initiated the "dropped" event on + * the host and therefore now waiting for the actual raw data. */ + rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + rc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout); + if (RT_SUCCESS(rc)) + rc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + } + + } while (0); + + /* + * Unregister callbacks. + */ + UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + if (m_pState->m_uProtocolVersion >= 3) + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA); + +#undef REGISTER_CALLBACK +#undef UNREGISTER_CALLBACK + + if (RT_FAILURE(rc)) + { + if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */ + { + /* + * Now that we've cleaned up tell the guest side to cancel. + * This does not imply we're waiting for the guest to react, as the + * host side never must depend on anything from the guest. + */ + int rc2 = sendCancel(); + AssertRC(rc2); + + rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED); + AssertRC(rc2); + } + else if (rc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + int rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, + rc, GuestDnDSource::i_hostErrorToString(rc)); + AssertRC(rc2); + } + + rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */ + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives transfer data (files / directories / ...) from the guest. + * + * @returns VBox status code. + * @param pCtx Receive context to use. + * @param msTimeout Timeout (in ms) to wait for receiving data. + */ +int GuestDnDSource::i_receiveTransferData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int rc; + + LogFlowFuncEnter(); + + GuestDnDState *pState = pCtx->pState; + AssertPtr(pCtx->pState); + + GuestDnD *pInst = GuestDnDInst(); + if (!pInst) + return VERR_INVALID_POINTER; + +#define REGISTER_CALLBACK(x) \ + do { \ + rc = pState->setCallback(x, i_receiveTransferDataCallback, pCtx); \ + if (RT_FAILURE(rc)) \ + return rc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int rc2 = pState->setCallback(x, NULL); \ + AssertRC(rc2); \ + } while (0) + + /* + * Register callbacks. + */ + /* Guest callbacks. */ + REGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + if (m_pState->m_uProtocolVersion >= 3) + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR); + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA); + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR); + if (m_pState->m_uProtocolVersion >= 2) + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR); + REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA); + + const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles; + + do + { + rc = DnDDroppedFilesOpenTemp(pDF, 0 /* fFlags */); + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Opening dropped files directory '%s' on the host failed with rc=%Rrc\n", + DnDDroppedFilesGetDirAbs(pDF), rc)); + break; + } + + /* + * Receive the transfer list. + */ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_GH_EVT_DROPPED); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1); + Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1); + Msg.appendUInt32(pCtx->enmAction); + + /* Make the initial call to the guest by telling that we initiated the "dropped" event on + * the host and therefore now waiting for the actual URI data. */ + rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Waiting ...\n")); + + rc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout); + if (RT_SUCCESS(rc)) + rc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + + LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc)); + } + + } while (0); + + /* + * Unregister callbacks. + */ + UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR); + UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA); + +#undef REGISTER_CALLBACK +#undef UNREGISTER_CALLBACK + + if (RT_FAILURE(rc)) + { + int rc2 = DnDDroppedFilesRollback(pDF); + if (RT_FAILURE(rc2)) + LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n", + rc2, DnDDroppedFilesGetDirAbs(pDF))); + + if (rc == VERR_CANCELLED) + { + /* + * Now that we've cleaned up tell the guest side to cancel. + * This does not imply we're waiting for the guest to react, as the + * host side never must depend on anything from the guest. + */ + rc2 = sendCancel(); + AssertRC(rc2); + + rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED); + AssertRC(rc2); + + /* Cancelling is not an error, just set success here. */ + rc = VINF_SUCCESS; + } + else if (rc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + rc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, + rc, GuestDnDSource::i_hostErrorToString(rc)); + AssertRC(rc2); + } + } + + DnDDroppedFilesClose(pDF); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Static HGCM service callback which handles receiving raw data. + * + * @returns VBox status code. Will get sent back to the host service. + * @param uMsg HGCM message ID (function number). + * @param pvParms Pointer to additional message data. Optional and can be NULL. + * @param cbParms Size (in bytes) additional message data. Optional and can be 0. + * @param pvUser User-supplied pointer on callback registration. + */ +/* static */ +DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDSource *pThis = pCtx->pSource; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg)); + + int rc = VINF_SUCCESS; + + int rcCallback = VINF_SUCCESS; /* rc for the callback. */ + bool fNotify = false; + + switch (uMsg) + { + case GUEST_DND_FN_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_FN_DISCONNECT: + rc = VERR_CANCELLED; + break; + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_FN_GH_SND_DATA_HDR: + { + PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data); + break; + } + case GUEST_DND_FN_GH_SND_DATA: + { + PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveData(pCtx, &pCBData->data); + break; + } + case GUEST_DND_FN_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->pState->reset(); + + if (RT_SUCCESS(pCBData->rc)) + { + AssertMsgFailed(("Received guest error with no error code set\n")); + pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */ + } + else if (pCBData->rc == VERR_WRONG_ORDER) + { + rc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED); + } + else + rc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc, + GuestDnDSource::i_guestErrorToString(pCBData->rc)); + + LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc)); + + if (RT_SUCCESS(rc)) + rcCallback = VERR_DND_GUEST_ERROR; + break; + } +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if ( RT_FAILURE(rc) + || RT_FAILURE(rcCallback)) + { + fNotify = true; + if (RT_SUCCESS(rcCallback)) + rcCallback = rc; + } + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_NO_DATA: + LogRel2(("DnD: Data transfer to host complete\n")); + break; + + case VERR_CANCELLED: + LogRel2(("DnD: Data transfer to host canceled\n")); + break; + + default: + LogRel(("DnD: Error %Rrc occurred, aborting data transfer to host\n", rc)); + break; + } + + /* Unregister this callback. */ + AssertPtr(pCtx->pState); + int rc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(rc2); + } + + /* All data processed? */ + if (pCtx->isComplete()) + fNotify = true; + + LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n", + pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc)); + + if (fNotify) + { + int rc2 = pCtx->EventCallback.Notify(rcCallback); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; /* Tell the guest. */ +} + +/** + * Static HGCM service callback which handles receiving transfer data from the guest. + * + * @returns VBox status code. Will get sent back to the host service. + * @param uMsg HGCM message ID (function number). + * @param pvParms Pointer to additional message data. Optional and can be NULL. + * @param cbParms Size (in bytes) additional message data. Optional and can be 0. + * @param pvUser User-supplied pointer on callback registration. + */ +/* static */ +DECLCALLBACK(int) GuestDnDSource::i_receiveTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDSource *pThis = pCtx->pSource; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg)); + + int rc = VINF_SUCCESS; + + int rcCallback = VINF_SUCCESS; /* rc for the callback. */ + bool fNotify = false; + + switch (uMsg) + { + case GUEST_DND_FN_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_FN_DISCONNECT: + rc = VERR_CANCELLED; + break; + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_FN_GH_SND_DATA_HDR: + { + PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data); + break; + } + case GUEST_DND_FN_GH_SND_DATA: + { + PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveData(pCtx, &pCBData->data); + break; + } + case GUEST_DND_FN_GH_SND_DIR: + { + PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode); + break; + } + case GUEST_DND_FN_GH_SND_FILE_HDR: + { + PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath, + pCBData->cbSize, pCBData->fMode, pCBData->fFlags); + break; + } + case GUEST_DND_FN_GH_SND_FILE_DATA: + { + PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + if (pThis->m_pState->m_uProtocolVersion <= 1) + { + /** + * Notes for protocol v1 (< VBox 5.0): + * - Every time this command is being sent it includes the file header, + * so just process both calls here. + * - There was no information whatsoever about the total file size; the old code only + * appended data to the desired file. So just pass 0 as cbSize. + */ + rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath, + 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData); + } + else /* Protocol v2 and up. */ + rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData); + break; + } + case GUEST_DND_FN_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->pState->reset(); + + if (RT_SUCCESS(pCBData->rc)) + { + AssertMsgFailed(("Received guest error with no error code set\n")); + pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */ + } + else if (pCBData->rc == VERR_WRONG_ORDER) + { + rc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED); + } + else + rc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc, + GuestDnDSource::i_guestErrorToString(pCBData->rc)); + + LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc)); + + if (RT_SUCCESS(rc)) + rcCallback = VERR_DND_GUEST_ERROR; + break; + } +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if ( RT_FAILURE(rc) + || RT_FAILURE(rcCallback)) + { + fNotify = true; + if (RT_SUCCESS(rcCallback)) + rcCallback = rc; + } + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_NO_DATA: + LogRel2(("DnD: File transfer to host complete\n")); + break; + + case VERR_CANCELLED: + LogRel2(("DnD: File transfer to host canceled\n")); + break; + + default: + LogRel(("DnD: Error %Rrc occurred, aborting file transfer to host\n", rc)); + break; + } + + /* Unregister this callback. */ + AssertPtr(pCtx->pState); + int rc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(rc2); + } + + /* All data processed? */ + if ( pCtx->Transfer.isComplete() + && pCtx->isComplete()) + { + fNotify = true; + } + + LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n", + pCtx->cbProcessed, pCtx->cbExtra, fNotify, rcCallback, rc)); + + if (fNotify) + { + int rc2 = pCtx->EventCallback.Notify(rcCallback); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; /* Tell the guest. */ +} + diff --git a/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp new file mode 100644 index 00000000..50008d80 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp @@ -0,0 +1,1789 @@ +/* $Id: GuestDnDTargetImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation - Guest drag'n drop target. + */ + +/* + * Copyright (C) 2014-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDTARGET +#include "LoggingNew.h" + +#include "GuestImpl.h" +#include "GuestDnDTargetImpl.h" +#include "ConsoleImpl.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "ThreadTask.h" + +#include <algorithm> /* For std::find(). */ + +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/uri.h> +#include <iprt/cpp/utils.h> /* For unconst(). */ + +#include <VBox/com/array.h> + +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/HostServices/Service.h> + + +/** + * Base class for a target task. + */ +class GuestDnDTargetTask : public ThreadTask +{ +public: + + GuestDnDTargetTask(GuestDnDTarget *pTarget) + : ThreadTask("GenericGuestDnDTargetTask") + , mTarget(pTarget) + , mRC(VINF_SUCCESS) { } + + virtual ~GuestDnDTargetTask(void) { } + + /** Returns the overall result of the task. */ + int getRC(void) const { return mRC; } + /** Returns if the overall result of the task is ok (succeeded) or not. */ + bool isOk(void) const { return RT_SUCCESS(mRC); } + +protected: + + /** COM object pointer to the parent (source). */ + const ComObjPtr<GuestDnDTarget> mTarget; + /** Overall result of the task. */ + int mRC; +}; + +/** + * Task structure for sending data to a target using + * a worker thread. + */ +class GuestDnDSendDataTask : public GuestDnDTargetTask +{ +public: + + GuestDnDSendDataTask(GuestDnDTarget *pTarget, GuestDnDSendCtx *pCtx) + : GuestDnDTargetTask(pTarget), + mpCtx(pCtx) + { + m_strTaskName = "dndTgtSndData"; + } + + void handler() + { + const ComObjPtr<GuestDnDTarget> pThis(mTarget); + Assert(!pThis.isNull()); + + AutoCaller autoCaller(pThis); + if (autoCaller.isNotOk()) + return; + + /* ignore rc */ pThis->i_sendData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */); + } + + virtual ~GuestDnDSendDataTask(void) { } + +protected: + + /** Pointer to send data context. */ + GuestDnDSendCtx *mpCtx; +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +GuestDnDTarget::GuestDnDTarget(void) + : GuestDnDBase(this) { } + +GuestDnDTarget::~GuestDnDTarget(void) { } + +HRESULT GuestDnDTarget::FinalConstruct(void) +{ + /* Set the maximum block size our guests can handle to 64K. This always has + * been hardcoded until now. */ + /* Note: Never ever rely on information from the guest; the host dictates what and + * how to do something, so try to negogiate a sensible value here later. */ + mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ + + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void GuestDnDTarget::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest) +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(m_pGuest) = pGuest; + + /* Set the response we're going to use for this object. + * + * At the moment we only have one response total, as we + * don't allow + * 1) parallel transfers (multiple G->H at the same time) + * nor 2) mixed transfers (G->H + H->G at the same time). + */ + m_pState = GuestDnDInst()->getState(); + AssertPtrReturn(m_pState, E_POINTER); + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestDnDTarget::uninit(void) +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; +} + +// implementation of wrapped IDnDBase methods. +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE; + + return S_OK; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFormats = GuestDnDBase::i_getFormats(); + + return S_OK; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_addFormats(aFormats); +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_removeFormats(aFormats); +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +// implementation of wrapped IDnDTarget methods. +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY, + DnDAction_T aDefaultAction, + const std::vector<DnDAction_T> &aAllowedActions, + const GuestDnDMIMEList &aFormats, + DnDAction_T *aResultAction) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + /* Input validation. */ + if (aDefaultAction == DnDAction_Ignore) + return setError(E_INVALIDARG, tr("No default action specified")); + if (!aAllowedActions.size()) + return setError(E_INVALIDARG, tr("Number of allowed actions is empty")); + if (!aFormats.size()) + return setError(E_INVALIDARG, tr("Number of supported formats is empty")); + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + /* Default action is ignoring. */ + DnDAction_T resAction = DnDAction_Ignore; + + /* Check & convert the drag & drop actions. */ + VBOXDNDACTION dndActionDefault = 0; + VBOXDNDACTIONLIST dndActionListAllowed = 0; + GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault, + aAllowedActions, &dndActionListAllowed); + + /* If there is no usable action, ignore this request. */ + if (isDnDIgnoreAction(dndActionDefault)) + return S_OK; + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtrReturn(pState, E_POINTER); + + /* + * Make a flat data string out of the supported format list. + * In the GuestDnDTarget case the source formats are from the host, + * as GuestDnDTarget acts as a source for the guest. + */ + Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats)); + if (strFormats.isEmpty()) + return setError(E_INVALIDARG, tr("No or not supported format(s) specified")); + const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */ + + LogRel2(("DnD: Offered formats to guest:\n")); + RTCList<RTCString> lstFormats = strFormats.split(DND_PATH_SEPARATOR_STR); + for (size_t i = 0; i < lstFormats.size(); i++) + LogRel2(("DnD: \t%s\n", lstFormats[i].c_str())); + + /* Save the formats offered to the guest. This is needed to later + * decide what to do with the data when sending stuff to the guest. */ + m_lstFmtOffered = aFormats; + Assert(m_lstFmtOffered.size()); + + /* Adjust the coordinates in a multi-monitor setup. */ + HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (SUCCEEDED(hrc)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_EVT_ENTER); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendUInt32(aScreenId); + Msg.appendUInt32(aX); + Msg.appendUInt32(aY); + Msg.appendUInt32(dndActionDefault); + Msg.appendUInt32(dndActionListAllowed); + Msg.appendPointer((void *)strFormats.c_str(), cbFormats); + Msg.appendUInt32(cbFormats); + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + int vrcGuest; + if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest))) + { + resAction = GuestDnD::toMainAction(m_pState->getActionDefault()); + + LogRel2(("DnD: Host enters the VM window at %RU32,%RU32 (screen %u, default action is '%s') -> guest reported back action '%s'\n", + aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction))); + + pState->set(VBOXDNDSTATE_ENTERED); + } + else + hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Entering VM window failed")); + } + else + { + switch (vrc) + { + case VERR_ACCESS_DENIED: + { + hrc = i_setErrorAndReset(tr("Drag and drop to guest not allowed. Select the right mode first")); + break; + } + + case VERR_NOT_SUPPORTED: + { + hrc = i_setErrorAndReset(tr("Drag and drop to guest not possible -- either the guest OS does not support this, " + "or the Guest Additions are not installed")); + break; + } + + default: + hrc = i_setErrorAndReset(vrc, tr("Entering VM window failed")); + break; + } + } + } + + if (SUCCEEDED(hrc)) + { + if (aResultAction) + *aResultAction = resAction; + } + + LogFlowFunc(("hrc=%Rhrc, resAction=%ld\n", hrc, resAction)); + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY, + DnDAction_T aDefaultAction, + const std::vector<DnDAction_T> &aAllowedActions, + const GuestDnDMIMEList &aFormats, + DnDAction_T *aResultAction) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + /* Input validation. */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + /* Default action is ignoring. */ + DnDAction_T resAction = DnDAction_Ignore; + + /* Check & convert the drag & drop actions. */ + VBOXDNDACTION dndActionDefault = 0; + VBOXDNDACTIONLIST dndActionListAllowed = 0; + GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault, + aAllowedActions, &dndActionListAllowed); + + /* If there is no usable action, ignore this request. */ + if (isDnDIgnoreAction(dndActionDefault)) + return S_OK; + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtrReturn(pState, E_POINTER); + + /* + * Make a flat data string out of the supported format list. + * In the GuestDnDTarget case the source formats are from the host, + * as GuestDnDTarget acts as a source for the guest. + */ + Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats)); + if (strFormats.isEmpty()) + return setError(E_INVALIDARG, tr("No or not supported format(s) specified")); + const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */ + + HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (SUCCEEDED(hrc)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_EVT_MOVE); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendUInt32(aScreenId); + Msg.appendUInt32(aX); + Msg.appendUInt32(aY); + Msg.appendUInt32(dndActionDefault); + Msg.appendUInt32(dndActionListAllowed); + Msg.appendPointer((void *)strFormats.c_str(), cbFormats); + Msg.appendUInt32(cbFormats); + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + int vrcGuest; + if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest))) + { + resAction = GuestDnD::toMainAction(pState->getActionDefault()); + + LogRel2(("DnD: Host moved to %RU32,%RU32 in VM window (screen %u, default action is '%s') -> guest reported back action '%s'\n", + aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction))); + + pState->set(VBOXDNDSTATE_DRAGGING); + } + else + hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, + tr("Moving to %RU32,%RU32 (screen %u) failed"), aX, aY, aScreenId); + } + else + { + switch (vrc) + { + case VERR_ACCESS_DENIED: + { + hrc = i_setErrorAndReset(tr("Moving in guest not allowed. Select the right mode first")); + break; + } + + case VERR_NOT_SUPPORTED: + { + hrc = i_setErrorAndReset(tr("Moving in guest not possible -- either the guest OS does not support this, " + "or the Guest Additions are not installed")); + break; + } + + default: + hrc = i_setErrorAndReset(vrc, tr("Moving in VM window failed")); + break; + } + } + } + else + hrc = i_setErrorAndReset(tr("Retrieving move coordinates failed")); + + if (SUCCEEDED(hrc)) + { + if (aResultAction) + *aResultAction = resAction; + } + + LogFlowFunc(("hrc=%Rhrc, *pResultAction=%ld\n", hrc, resAction)); + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::leave(ULONG uScreenId) +{ + RT_NOREF(uScreenId); +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (autoCaller.isNotOk()) return autoCaller.rc(); + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtrReturn(pState, E_POINTER); + + if (pState->get() == VBOXDNDSTATE_DROP_STARTED) + return S_OK; + + HRESULT hrc = S_OK; + + LogRel2(("DnD: Host left the VM window (screen %u)\n", uScreenId)); + + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_EVT_LEAVE); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + int vrcGuest; + if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest))) + { + pState->set(VBOXDNDSTATE_LEFT); + } + else + hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Leaving VM window failed")); + } + else + { + switch (vrc) + { + case VERR_ACCESS_DENIED: + { + hrc = i_setErrorAndReset(tr("Leaving guest not allowed. Select the right mode first")); + break; + } + + case VERR_NOT_SUPPORTED: + { + hrc = i_setErrorAndReset(tr("Leaving guest not possible -- either the guest OS does not support this, " + "or the Guest Additions are not installed")); + break; + } + + default: + hrc = i_setErrorAndReset(vrc, tr("Leaving VM window failed")); + break; + } + } + + LogFlowFunc(("hrc=%Rhrc\n", hrc)); + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY, + DnDAction_T aDefaultAction, + const std::vector<DnDAction_T> &aAllowedActions, + const GuestDnDMIMEList &aFormats, + com::Utf8Str &aFormat, + DnDAction_T *aResultAction) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + if (aDefaultAction == DnDAction_Ignore) + return setError(E_INVALIDARG, tr("Invalid default action specified")); + if (!aAllowedActions.size()) + return setError(E_INVALIDARG, tr("Invalid allowed actions specified")); + if (!aFormats.size()) + return setError(E_INVALIDARG, tr("No drop format(s) specified")); + /* aResultAction is optional. */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Default action is ignoring. */ + DnDAction_T resAct = DnDAction_Ignore; + Utf8Str resFmt; + + /* Check & convert the drag & drop actions to HGCM codes. */ + VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE; + VBOXDNDACTIONLIST dndActionListAllowed = 0; + GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault, + aAllowedActions, &dndActionListAllowed); + + /* If there is no usable action, ignore this request. */ + if (isDnDIgnoreAction(dndActionDefault)) + { + aFormat = ""; + if (aResultAction) + *aResultAction = DnDAction_Ignore; + return S_OK; + } + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtrReturn(pState, E_POINTER); + + /* + * Make a flat data string out of the supported format list. + * In the GuestDnDTarget case the source formats are from the host, + * as GuestDnDTarget acts as a source for the guest. + */ + Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats)); + if (strFormats.isEmpty()) + return setError(E_INVALIDARG, tr("No or not supported format(s) specified")); + const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */ + + /* Adjust the coordinates in a multi-monitor setup. */ + HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (SUCCEEDED(hrc)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_EVT_DROPPED); + if (m_pState->m_uProtocolVersion >= 3) + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendUInt32(aScreenId); + Msg.appendUInt32(aX); + Msg.appendUInt32(aY); + Msg.appendUInt32(dndActionDefault); + Msg.appendUInt32(dndActionListAllowed); + Msg.appendPointer((void*)strFormats.c_str(), cbFormats); + Msg.appendUInt32(cbFormats); + + LogRel2(("DnD: Host drops at %RU32,%RU32 in VM window (screen %u, default action is '%s')\n", + aX, aY, aScreenId, DnDActionToStr(dndActionDefault))); + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + int vrcGuest; + if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest))) + { + resAct = GuestDnD::toMainAction(pState->getActionDefault()); + if (resAct != DnDAction_Ignore) /* Does the guest accept a drop at the current position? */ + { + GuestDnDMIMEList lstFormats = pState->formats(); + if (lstFormats.size() == 1) /* Exactly one format to use specified? */ + { + resFmt = lstFormats.at(0); + + LogRel2(("DnD: Guest accepted drop in format '%s' (action %#x, %zu format(s))\n", + resFmt.c_str(), resAct, lstFormats.size())); + + pState->set(VBOXDNDSTATE_DROP_STARTED); + } + else + { + if (lstFormats.size() == 0) + hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but did not specify the format")); + else + hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but returned more than one drop format (%zu formats)"), + lstFormats.size()); + } + } + } + else + hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Dropping into VM failed")); + } + else + hrc = i_setErrorAndReset(vrc, tr("Sending dropped event to guest failed")); + } + else + hrc = i_setErrorAndReset(hrc, tr("Retrieving drop coordinates failed")); + + if (SUCCEEDED(hrc)) + { + aFormat = resFmt; + if (aResultAction) + *aResultAction = resAct; + } + + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +/** + * Initiates a data transfer from the host to the guest. + * + * The source is the host, whereas the target is the guest. + * + * @return HRESULT + * @param aScreenId Screen ID where this data transfer was initiated from. + * @param aFormat Format of data to send. MIME-style. + * @param aData Actual data to send. + * @param aProgress Where to return the progress object on success. + */ +HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData, + ComPtr<IProgress> &aProgress) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Input validation. */ + if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No data format specified")); + if (RT_UNLIKELY(!aData.size())) + return setError(E_INVALIDARG, tr("No data to send specified")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Check if this object still is in a pending state and bail out if so. */ + if (m_fIsPending) + return setError(E_FAIL, tr("Current drop operation to guest still in progress")); + + /* At the moment we only support one transfer at a time. */ + if (GuestDnDInst()->getTargetCount()) + return setError(E_INVALIDARG, tr("Another drag and drop operation to the guest already is in progress")); + + /* Reset progress object. */ + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtr(pState); + HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to guest")); + if (FAILED(hr)) + return hr; + + GuestDnDSendDataTask *pTask = NULL; + + try + { + mData.mSendCtx.reset(); + + mData.mSendCtx.pTarget = this; + mData.mSendCtx.pState = pState; + mData.mSendCtx.uScreenID = aScreenId; + + mData.mSendCtx.Meta.strFmt = aFormat; + mData.mSendCtx.Meta.add(aData); + + LogRel2(("DnD: Host sends data in format '%s'\n", aFormat.c_str())); + + pTask = new GuestDnDSendDataTask(this, &mData.mSendCtx); + if (!pTask->isOk()) + { + delete pTask; + LogRel(("DnD: Could not create SendDataTask object\n")); + throw hr = E_FAIL; + } + + /* This function delete pTask in case of exceptions, + * so there is no need in the call of delete operator. */ + hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER); + pTask = NULL; /* Note: pTask is now owned by the worker thread. */ + } + catch (std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + catch (...) + { + LogRel(("DnD: Could not create thread for data sending task\n")); + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) + { + /* Register ourselves at the DnD manager. */ + GuestDnDInst()->registerTarget(this); + + /* Return progress to caller. */ + hr = pState->queryProgressTo(aProgress.asOutParam()); + ComAssertComRC(hr); + } + else + hr = i_setErrorAndReset(tr("Starting thread for GuestDnDTarget failed (%Rhrc)"), hr); + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +/** + * Returns an error string from a guest DnD error. + * + * @returns Error string. + * @param guestRc Guest error to return error string for. + */ +/* static */ +Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc) +{ + Utf8Str strError; + + switch (guestRc) + { + case VERR_ACCESS_DENIED: + strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest " + "user does not have the appropriate access rights for. Please make sure that all selected " + "elements can be accessed and that your guest user has the appropriate rights")); + break; + + case VERR_NOT_FOUND: + /* Should not happen due to file locking on the guest, but anyway ... */ + strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not" + "found on the guest anymore. This can be the case if the guest files were moved and/or" + "altered while the drag and drop operation was in progress")); + break; + + case VERR_SHARING_VIOLATION: + strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. " + "Please make sure that all selected elements can be accessed and that your guest user has " + "the appropriate rights")); + break; + + case VERR_TIMEOUT: + strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time")); + break; + + default: + strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc); + break; + } + + return strError; +} + +/** + * Returns an error string from a host DnD error. + * + * @returns Error string. + * @param hostRc Host error to return error string for. + */ +/* static */ +Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc) +{ + Utf8Str strError; + + switch (hostRc) + { + case VERR_ACCESS_DENIED: + strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host " + "user does not have the appropriate access rights for. Please make sure that all selected " + "elements can be accessed and that your host user has the appropriate rights.")); + break; + + case VERR_NOT_FOUND: + /* Should not happen due to file locking on the host, but anyway ... */ + strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not" + "found on the host anymore. This can be the case if the host files were moved and/or" + "altered while the drag and drop operation was in progress.")); + break; + + case VERR_SHARING_VIOLATION: + strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. " + "Please make sure that all selected elements can be accessed and that your host user has " + "the appropriate rights.")); + break; + + default: + strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc); + break; + } + + return strError; +} + +/** + * Resets all internal data and state. + */ +void GuestDnDTarget::i_reset(void) +{ + LogRel2(("DnD: Target reset\n")); + + mData.mSendCtx.reset(); + + m_fIsPending = false; + + /* Unregister ourselves from the DnD manager. */ + GuestDnDInst()->unregisterTarget(this); +} + +/** + * Main function for sending DnD host data to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param msTimeout Timeout (in ms) to wait for getting the data sent. + */ +int GuestDnDTarget::i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* Don't allow receiving the actual data until our current transfer is complete. */ + if (m_fIsPending) + return setError(E_FAIL, tr("Current drop operation to guest still in progress")); + + /* Clear all remaining outgoing messages. */ + m_DataBase.lstMsgOut.clear(); + + /** + * Do we need to build up a file tree? + * Note: The decision whether we need to build up a file tree and sending + * actual file data only depends on the actual formats offered by this target. + * If the guest does not want a transfer list ("text/uri-list") but text ("TEXT" and + * friends) instead, still send the data over to the guest -- the file as such still + * is needed on the guest in this case, as the guest then just wants a simple path + * instead of a transfer list (pointing to a file on the guest itself). + * + ** @todo Support more than one format; add a format<->function handler concept. Later. */ + int vrc; + const bool fHasURIList = std::find(m_lstFmtOffered.begin(), + m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end(); + if (fHasURIList) + { + vrc = i_sendTransferData(pCtx, msTimeout); + } + else + { + vrc = i_sendRawData(pCtx, msTimeout); + } + + GuestDnDState *pState = GuestDnDInst()->getState(); + AssertPtrReturn(pState, E_POINTER); + + if (RT_SUCCESS(vrc)) + { + pState->set(VBOXDNDSTATE_DROP_ENDED); + } + else + { + if (vrc == VERR_CANCELLED) + { + LogRel(("DnD: Sending data to guest cancelled by the user\n")); + pState->set(VBOXDNDSTATE_CANCELLED); + } + else + { + LogRel(("DnD: Sending data to guest failed with %Rrc\n", vrc)); + pState->set(VBOXDNDSTATE_ERROR); + } + + /* Make sure to fire a cancel request to the guest side in any case to prevent any guest side hangs. */ + sendCancel(); + } + + /* Reset state. */ + i_reset(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sends the common meta data body to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + */ +int GuestDnDTarget::i_sendMetaDataBody(GuestDnDSendCtx *pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + uint8_t *pvData = (uint8_t *)pCtx->Meta.pvData; + size_t cbData = pCtx->Meta.cbData; + + int vrc = VINF_SUCCESS; + + const size_t cbFmt = pCtx->Meta.strFmt.length() + 1; /* Include terminator. */ + const char *pcszFmt = pCtx->Meta.strFmt.c_str(); + + LogFlowFunc(("uProtoVer=%RU32, szFmt=%s, cbFmt=%RU32, cbData=%zu\n", m_pState->m_uProtocolVersion, pcszFmt, cbFmt, cbData)); + + LogRel2(("DnD: Sending meta data to guest as '%s' (%zu bytes)\n", pcszFmt, cbData)); + +#ifdef DEBUG + RTCList<RTCString> lstFilesURI = RTCString((char *)pvData, cbData).split(DND_PATH_SEPARATOR_STR); + LogFlowFunc(("lstFilesURI=%zu\n", lstFilesURI.size())); + for (size_t i = 0; i < lstFilesURI.size(); i++) + LogFlowFunc(("\t%s\n", lstFilesURI.at(i).c_str())); +#endif + + uint8_t *pvChunk = pvData; + size_t cbChunk = RT_MIN(mData.mcbBlockSize, cbData); + while (cbData) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_SND_DATA); + + if (m_pState->m_uProtocolVersion < 3) + { + Msg.appendUInt32(pCtx->uScreenID); /* uScreenId */ + Msg.appendPointer(unconst(pcszFmt), (uint32_t)cbFmt); /* pvFormat */ + Msg.appendUInt32((uint32_t)cbFmt); /* cbFormat */ + Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */ + /* Fill in the current data block size to send. + * Note: Only supports uint32_t. */ + Msg.appendUInt32((uint32_t)cbChunk); /* cbData */ + } + else + { + Msg.appendUInt32(0); /** @todo ContextID not used yet. */ + Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */ + Msg.appendUInt32((uint32_t)cbChunk); /* cbData */ + Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */ + Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */ + } + + vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_FAILURE(vrc)) + break; + + pvChunk += cbChunk; + AssertBreakStmt(cbData >= cbChunk, VERR_BUFFER_UNDERFLOW); + cbData -= cbChunk; + } + + if (RT_SUCCESS(vrc)) + { + vrc = updateProgress(pCtx, pCtx->pState, (uint32_t)pCtx->Meta.cbData); + AssertRC(vrc); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sends the common meta data header to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + */ +int GuestDnDTarget::i_sendMetaDataHeader(GuestDnDSendCtx *pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + if (m_pState->m_uProtocolVersion < 3) /* Protocol < v3 did not support this, skip. */ + return VINF_SUCCESS; + + GuestDnDMsg Msg; + Msg.setType(HOST_DND_FN_HG_SND_DATA_HDR); + + LogRel2(("DnD: Sending meta data header to guest (%RU64 bytes total data, %RU32 bytes meta data, %RU64 objects)\n", + pCtx->getTotalAnnounced(), pCtx->Meta.cbData, pCtx->Transfer.cObjToProcess)); + + Msg.appendUInt32(0); /** @todo uContext; not used yet. */ + Msg.appendUInt32(0); /** @todo uFlags; not used yet. */ + Msg.appendUInt32(pCtx->uScreenID); /* uScreen */ + Msg.appendUInt64(pCtx->getTotalAnnounced()); /* cbTotal */ + Msg.appendUInt32((uint32_t)pCtx->Meta.cbData); /* cbMeta*/ + Msg.appendPointer(unconst(pCtx->Meta.strFmt.c_str()), (uint32_t)pCtx->Meta.strFmt.length() + 1); /* pvMetaFmt */ + Msg.appendUInt32((uint32_t)pCtx->Meta.strFmt.length() + 1); /* cbMetaFmt */ + Msg.appendUInt64(pCtx->Transfer.cObjToProcess); /* cObjects */ + Msg.appendUInt32(0); /** @todo enmCompression; not used yet. */ + Msg.appendUInt32(0); /** @todo enmChecksumType; not used yet. */ + Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */ + Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */ + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sends a directory entry to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param pObj Transfer object to send. Must be a directory. + * @param pMsg Where to store the message to send. + */ +int GuestDnDTarget::i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj); + AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER); + const size_t cchPath = RTStrNLen(pcszDstPath, RTPATH_MAX); /* Note: Maximum is RTPATH_MAX on guest side. */ + AssertReturn(cchPath, VERR_INVALID_PARAMETER); + + LogRel2(("DnD: Transferring host directory '%s' to guest\n", DnDTransferObjectGetSourcePath(pObj))); + + pMsg->setType(HOST_DND_FN_HG_SND_DIR); + if (m_pState->m_uProtocolVersion >= 3) + pMsg->appendUInt32(0); /** @todo ContextID not used yet. */ + pMsg->appendString(pcszDstPath); /* path */ + pMsg->appendUInt32((uint32_t)(cchPath + 1)); /* path length, including terminator. */ + pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* mode */ + + return VINF_SUCCESS; +} + +/** + * Sends a file to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param pObj Transfer object to send. Must be a file. + * @param pMsg Where to store the message to send. + */ +int GuestDnDTarget::i_sendFile(GuestDnDSendCtx *pCtx, + PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj); + AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER); + const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj); + AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER); + + int vrc = VINF_SUCCESS; + + if (!DnDTransferObjectIsOpen(pObj)) + { + LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", pcszSrcPath)); + + vrc = DnDTransferObjectOpen(pObj, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fMode */, + DNDTRANSFEROBJECT_FLAGS_NONE); + if (RT_FAILURE(vrc)) + LogRel(("DnD: Opening host file '%s' failed, rc=%Rrc\n", pcszSrcPath, vrc)); + } + + if (RT_FAILURE(vrc)) + return vrc; + + bool fSendData = false; + if (RT_SUCCESS(vrc)) /** @todo r=aeichner Could save an identation level here as there is a error check above already... */ + { + if (m_pState->m_uProtocolVersion >= 2) + { + if (!(pCtx->Transfer.fObjState & DND_OBJ_STATE_HAS_HDR)) + { + const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX); + const size_t cbSize = DnDTransferObjectGetSize(pObj); + const RTFMODE fMode = DnDTransferObjectGetMode(pObj); + + /* + * Since protocol v2 the file header and the actual file contents are + * separate messages, so send the file header first. + * The just registered callback will be called by the guest afterwards. + */ + pMsg->setType(HOST_DND_FN_HG_SND_FILE_HDR); + pMsg->appendUInt32(0); /** @todo ContextID not used yet. */ + pMsg->appendString(pcszDstPath); /* pvName */ + pMsg->appendUInt32((uint32_t)(cchDstPath + 1)); /* cbName */ + pMsg->appendUInt32(0); /* uFlags */ + pMsg->appendUInt32(fMode); /* fMode */ + pMsg->appendUInt64(cbSize); /* uSize */ + + LogRel2(("DnD: Transferring host file '%s' to guest (as '%s', %zu bytes, mode %#x)\n", + pcszSrcPath, pcszDstPath, cbSize, fMode)); + + /** @todo Set progress object title to current file being transferred? */ + + /* Update object state to reflect that we have sent the file header. */ + pCtx->Transfer.fObjState |= DND_OBJ_STATE_HAS_HDR; + } + else + { + /* File header was sent, so only send the actual file data. */ + fSendData = true; + } + } + else /* Protocol v1. */ + { + /* Always send the file data, every time. */ + fSendData = true; + } + } + + if ( RT_SUCCESS(vrc) + && fSendData) + { + vrc = i_sendFileData(pCtx, pObj, pMsg); + } + + if (RT_FAILURE(vrc)) + LogRel(("DnD: Sending host file '%s' to guest failed, rc=%Rrc\n", pcszSrcPath, vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Helper function to send actual file data to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param pObj Transfer object to send. Must be a file. + * @param pMsg Where to store the message to send. + */ +int GuestDnDTarget::i_sendFileData(GuestDnDSendCtx *pCtx, + PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + AssertPtrReturn(pCtx->pState, VERR_WRONG_ORDER); + + /** @todo Don't allow concurrent reads per context! */ + + /* Set the message type. */ + pMsg->setType(HOST_DND_FN_HG_SND_FILE_DATA); + + const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj); + const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj); + + /* Protocol version 1 sends the file path *every* time with a new file chunk. + * In protocol version 2 we only do this once with HOST_DND_FN_HG_SND_FILE_HDR. */ + if (m_pState->m_uProtocolVersion <= 1) + { + const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX); + + pMsg->appendString(pcszDstPath); /* pvName */ + pMsg->appendUInt32((uint32_t)cchDstPath + 1); /* cbName */ + } + else if (m_pState->m_uProtocolVersion >= 2) + { + pMsg->appendUInt32(0); /** @todo ContextID not used yet. */ + } + + void *pvBuf = pCtx->Transfer.pvScratchBuf; + AssertPtr(pvBuf); + size_t cbBuf = pCtx->Transfer.cbScratchBuf; + Assert(cbBuf); + + uint32_t cbRead; + + int vrc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead); + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead)); + + if (m_pState->m_uProtocolVersion <= 1) + { + pMsg->appendPointer(pvBuf, cbRead); /* pvData */ + pMsg->appendUInt32(cbRead); /* cbData */ + pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */ + } + else /* Protocol v2 and up. */ + { + pMsg->appendPointer(pvBuf, cbRead); /* pvData */ + pMsg->appendUInt32(cbRead); /* cbData */ + + if (m_pState->m_uProtocolVersion >= 3) + { + /** @todo Calculate checksum. */ + pMsg->appendPointer(NULL, 0); /* pvChecksum */ + pMsg->appendUInt32(0); /* cbChecksum */ + } + } + + int vrc2 = updateProgress(pCtx, pCtx->pState, (uint32_t)cbRead); + AssertRC(vrc2); + + /* DnDTransferObjectRead() will return VINF_EOF if reading is complete. */ + if (vrc == VINF_EOF) + vrc = VINF_SUCCESS; + + if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */ + LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath)); + } + else + LogRel(("DnD: Reading from host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Static HGCM service callback which handles sending transfer data to the guest. + * + * @returns VBox status code. Will get sent back to the host service. + * @param uMsg HGCM message ID (function number). + * @param pvParms Pointer to additional message data. Optional and can be NULL. + * @param cbParms Size (in bytes) additional message data. Optional and can be 0. + * @param pvUser User-supplied pointer on callback registration. + */ +/* static */ +DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDTarget *pThis = pCtx->pTarget; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + /* At the moment we only have one transfer list per transfer. */ + PDNDTRANSFERLIST pList = &pCtx->Transfer.List; + + LogFlowFunc(("pThis=%p, pList=%p, uMsg=%RU32\n", pThis, pList, uMsg)); + + int vrc = VINF_SUCCESS; + int vrcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_DND_GUEST_ERROR. */ + bool fNotify = false; + + switch (uMsg) + { + case GUEST_DND_FN_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_FN_DISCONNECT: + vrc = VERR_CANCELLED; + break; + + case GUEST_DND_FN_GET_NEXT_HOST_MSG: + { + PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + try + { + GuestDnDMsg *pMsg = new GuestDnDMsg(); + + vrc = pThis->i_sendTransferListObject(pCtx, pList, pMsg); + if (vrc == VINF_EOF) /* Transfer complete? */ + { + LogFlowFunc(("Last transfer item processed, bailing out\n")); + } + else if (RT_SUCCESS(vrc)) + { + vrc = pThis->msgQueueAdd(pMsg); + if (RT_SUCCESS(vrc)) /* Return message type & required parameter count to the guest. */ + { + LogFlowFunc(("GUEST_DND_FN_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount())); + pCBData->uMsg = pMsg->getType(); + pCBData->cParms = pMsg->getCount(); + } + } + + if ( RT_FAILURE(vrc) + || vrc == VINF_EOF) /* Transfer complete? */ + { + delete pMsg; + pMsg = NULL; + } + } + catch(std::bad_alloc & /*e*/) + { + vrc = VERR_NO_MEMORY; + } + break; + } + case GUEST_DND_FN_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->pState->reset(); + + if (RT_SUCCESS(pCBData->rc)) + { + AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n")); + pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */ + } + + vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc, + GuestDnDTarget::i_guestErrorToString(pCBData->rc)); + if (RT_SUCCESS(vrc)) + { + vrc = VERR_DND_GUEST_ERROR; + vrcGuest = pCBData->rc; + } + break; + } + case HOST_DND_FN_HG_SND_DIR: + case HOST_DND_FN_HG_SND_FILE_HDR: + case HOST_DND_FN_HG_SND_FILE_DATA: + { + PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData + = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER); + + LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms)); + + GuestDnDMsg *pMsg = pThis->msgQueueGetNext(); + if (pMsg) + { + /* + * Sanity checks. + */ + if ( pCBData->uMsg != uMsg + || pCBData->paParms == NULL + || pCBData->cParms != pMsg->getCount()) + { + LogFlowFunc(("Current message does not match:\n")); + LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n", + pCBData->uMsg, pCBData->cParms, pCBData->paParms)); + LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount())); + + /* Start over. */ + pThis->msgQueueClear(); + + vrc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("Returning uMsg=%RU32\n", uMsg)); + vrc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(), + false /* fDeepCopy */); + if (RT_SUCCESS(vrc)) + { + pCBData->cParms = pMsg->getCount(); + pThis->msgQueueRemoveNext(); + } + else + LogFlowFunc(("Copying parameters failed with vrc=%Rrc\n", vrc)); + } + } + else + vrc = VERR_NO_DATA; + + LogFlowFunc(("Processing next message ended with vrc=%Rrc\n", vrc)); + break; + } + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + + int vrcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */ + + /* + * Resolve errors. + */ + switch (vrc) + { + case VINF_SUCCESS: + break; + + case VINF_EOF: + { + LogRel2(("DnD: Transfer to guest complete\n")); + + /* Complete operation on host side. */ + fNotify = true; + + /* The guest expects VERR_NO_DATA if the transfer is complete. */ + vrcToGuest = VERR_NO_DATA; + break; + } + + case VERR_DND_GUEST_ERROR: + { + LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", vrcGuest)); + break; + } + + case VERR_CANCELLED: + { + LogRel2(("DnD: Transfer to guest canceled\n")); + vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */ + break; + } + + default: + { + LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", vrc)); + vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */ + break; + } + } + + if (RT_FAILURE(vrc)) + { + /* Unregister this callback. */ + AssertPtr(pCtx->pState); + int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(vrc2); + + /* Let the waiter(s) know. */ + fNotify = true; + } + + LogFlowFunc(("fNotify=%RTbool, vrc=%Rrc, vrcToGuest=%Rrc\n", fNotify, vrc, vrcToGuest)); + + if (fNotify) + { + int vrc2 = pCtx->EventCallback.Notify(vrc); /** @todo Also pass guest error back? */ + AssertRC(vrc2); + } + + LogFlowFuncLeaveRC(vrc); + return vrcToGuest; /* Tell the guest. */ +} + +/** + * Main function for sending the actual transfer data (i.e. files + directories) to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param msTimeout Timeout (in ms) to use for getting the data sent. + */ +int GuestDnDTarget::i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtr(pCtx->pState); + +#define REGISTER_CALLBACK(x) \ + do { \ + vrc = pCtx->pState->setCallback(x, i_sendTransferDataCallback, pCtx); \ + if (RT_FAILURE(vrc)) \ + return vrc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int vrc2 = pCtx->pState->setCallback(x, NULL); \ + AssertRC(vrc2); \ + } while (0) + + int vrc = pCtx->Transfer.init(mData.mcbBlockSize); + if (RT_FAILURE(vrc)) + return vrc; + + vrc = pCtx->EventCallback.Reset(); + if (RT_FAILURE(vrc)) + return vrc; + + /* + * Register callbacks. + */ + /* Guest callbacks. */ + REGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG); + REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + /* Host callbacks. */ + REGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR); + if (m_pState->m_uProtocolVersion >= 2) + REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR); + REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA); + + do + { + /* + * Extract transfer list from current meta data. + */ + vrc = DnDTransferListAppendPathsFromBuffer(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, + (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR, + DNDTRANSFERLIST_FLAGS_RECURSIVE); + if (RT_FAILURE(vrc)) + break; + + /* + * Update internal state to reflect everything we need to work with it. + */ + pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List); + /* cbExtra can be 0, if all files are of 0 bytes size. */ + pCtx->Transfer.cObjToProcess = DnDTransferListObjCount(&pCtx->Transfer.List); + AssertBreakStmt(pCtx->Transfer.cObjToProcess, vrc = VERR_INVALID_PARAMETER); + + /* Update the meta data to have the current root transfer entries in the right shape. */ + if (DnDMIMEHasFileURLs(pCtx->Meta.strFmt.c_str(), RTSTR_MAX)) + { + /* Save original format we're still going to use after updating the actual meta data. */ + Utf8Str strFmt = pCtx->Meta.strFmt; + + /* Reset stale data. */ + pCtx->Meta.reset(); + + void *pvData; + size_t cbData; +#ifdef DEBUG + vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, "" /* pcszPathBase */, + "\n" /* pcszSeparator */, (char **)&pvData, &cbData); + AssertRCReturn(vrc, vrc); + LogFlowFunc(("URI data:\n%s", (char *)pvData)); + RTMemFree(pvData); + cbData = 0; +#endif + vrc = DnDTransferListGetRoots(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, + (char **)&pvData, &cbData); + AssertRCReturn(vrc, vrc); + + /* pCtx->Meta now owns the allocated data. */ + pCtx->Meta.strFmt = strFmt; + pCtx->Meta.pvData = pvData; + pCtx->Meta.cbData = cbData; + pCtx->Meta.cbAllocated = cbData; + pCtx->Meta.cbAnnounced = cbData; + } + + /* + * The first message always is the data header. The meta data itself then follows + * and *only* contains the root elements of a transfer list. + * + * After the meta data we generate the messages required to send the + * file/directory data itself. + * + * Note: Protocol < v3 use the first data message to tell what's being sent. + */ + + /* + * Send the data header first. + */ + if (m_pState->m_uProtocolVersion >= 3) + vrc = i_sendMetaDataHeader(pCtx); + + /* + * Send the (meta) data body. + */ + if (RT_SUCCESS(vrc)) + vrc = i_sendMetaDataBody(pCtx); + + if (RT_SUCCESS(vrc)) + { + vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout); + if (RT_SUCCESS(vrc)) + pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + } + + } while (0); + + /* + * Unregister callbacks. + */ + /* Guest callbacks. */ + UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG); + UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR); + /* Host callbacks. */ + UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR); + if (m_pState->m_uProtocolVersion >= 2) + UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR); + UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA); + +#undef REGISTER_CALLBACK +#undef UNREGISTER_CALLBACK + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */ + { + /* + * Now that we've cleaned up tell the guest side to cancel. + * This does not imply we're waiting for the guest to react, as the + * host side never must depend on anything from the guest. + */ + int vrc2 = sendCancel(); + AssertRC(vrc2); + + LogRel2(("DnD: Sending transfer data to guest cancelled by user\n")); + + vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS); + AssertRC(vrc2); + + /* Cancelling is not an error, just set success here. */ + vrc = VINF_SUCCESS; + } + else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + LogRel(("DnD: Sending transfer data to guest failed with vrc=%Rrc\n", vrc)); + int vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, vrc, + GuestDnDTarget::i_hostErrorToString(vrc)); + AssertRC(vrc2); + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sends the next object of a transfer list to the guest. + * + * @returns VBox status code. VINF_EOF if the transfer list is complete. + * @param pCtx Send context to use. + * @param pList Transfer list to use. + * @param pMsg Message to store send data into. + */ +int GuestDnDTarget::i_sendTransferListObject(GuestDnDSendCtx *pCtx, PDNDTRANSFERLIST pList, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + int vrc = updateProgress(pCtx, pCtx->pState); + AssertRCReturn(vrc, vrc); + + PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pList); + if (!pObj) /* Transfer complete? */ + return VINF_EOF; + + switch (DnDTransferObjectGetType(pObj)) + { + case DNDTRANSFEROBJTYPE_DIRECTORY: + vrc = i_sendDirectory(pCtx, pObj, pMsg); + break; + + case DNDTRANSFEROBJTYPE_FILE: + vrc = i_sendFile(pCtx, pObj, pMsg); + break; + + default: + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + break; + } + + if ( RT_SUCCESS(vrc) + && DnDTransferObjectIsComplete(pObj)) + { + DnDTransferListObjRemove(pList, pObj); + pObj = NULL; + + AssertReturn(pCtx->Transfer.cObjProcessed + 1 <= pCtx->Transfer.cObjToProcess, VERR_WRONG_ORDER); + pCtx->Transfer.cObjProcessed++; + + pCtx->Transfer.fObjState = DND_OBJ_STATE_NONE; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Main function for sending raw data (e.g. text, RTF, ...) to the guest. + * + * @returns VBox status code. + * @param pCtx Send context to use. + * @param msTimeout Timeout (in ms) to use for getting the data sent. + */ +int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + NOREF(msTimeout); + + /** @todo At the moment we only allow sending up to 64K raw data. + * For protocol v1+v2: Fix this by using HOST_DND_FN_HG_SND_MORE_DATA. + * For protocol v3 : Send another HOST_DND_FN_HG_SND_DATA message. */ + if (!pCtx->Meta.cbData) + return VINF_SUCCESS; + + int vrc = i_sendMetaDataHeader(pCtx); + if (RT_SUCCESS(vrc)) + vrc = i_sendMetaDataBody(pCtx); + + int vrc2; + if (RT_FAILURE(vrc)) + { + LogRel(("DnD: Sending raw data to guest failed with vrc=%Rrc\n", vrc)); + vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_ERROR, vrc, + GuestDnDTarget::i_hostErrorToString(vrc)); + } + else + vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, vrc); + AssertRC(vrc2); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Cancels sending DnD data. + * + * @returns VBox status code. + * @param aVeto Whether cancelling was vetoed or not. + * Not implemented yet. + */ +HRESULT GuestDnDTarget::cancel(BOOL *aVeto) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + LogRel2(("DnD: Sending cancelling request to the guest ...\n")); + + int vrc = GuestDnDBase::sendCancel(); + + if (aVeto) + *aVeto = FALSE; /** @todo Implement vetoing. */ + + HRESULT hrc = RT_SUCCESS(vrc) ? S_OK : VBOX_E_DND_ERROR; + + LogFlowFunc(("hrc=%Rhrc\n", hrc)); + return hrc; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + diff --git a/src/VBox/Main/src-client/GuestFileImpl.cpp b/src/VBox/Main/src-client/GuestFileImpl.cpp new file mode 100644 index 00000000..d2560a05 --- /dev/null +++ b/src/VBox/Main/src-client/GuestFileImpl.cpp @@ -0,0 +1,1902 @@ +/* $Id: GuestFileImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest file handling. + */ + +/* + * Copyright (C) 2012-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTFILE +#include "LoggingNew.h" + +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestFileImpl.h" +#include "GuestSessionImpl.h" +#include "GuestCtrlImplPrivate.h" +#include "ConsoleImpl.h" +#include "VirtualBoxErrorInfoImpl.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "VBoxEvents.h" + +#include <iprt/cpp/utils.h> /* For unconst(). */ +#include <iprt/file.h> + +#include <VBox/com/array.h> +#include <VBox/com/listeners.h> +#include <VBox/AssertGuest.h> + + +/** + * Internal listener class to serve events in an + * active manner, e.g. without polling delays. + */ +class GuestFileListener +{ +public: + + GuestFileListener(void) + { + } + + virtual ~GuestFileListener() + { + } + + HRESULT init(GuestFile *pFile) + { + AssertPtrReturn(pFile, E_POINTER); + mFile = pFile; + return S_OK; + } + + void uninit(void) + { + mFile = NULL; + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) + { + switch (aType) + { + case VBoxEventType_OnGuestFileStateChanged: + case VBoxEventType_OnGuestFileOffsetChanged: + case VBoxEventType_OnGuestFileRead: + case VBoxEventType_OnGuestFileWrite: + { + AssertPtrReturn(mFile, E_POINTER); + int vrc2 = mFile->signalWaitEvent(aType, aEvent); + RT_NOREF(vrc2); +#ifdef DEBUG_andy + LogFlowFunc(("Signalling events of type=%RU32, file=%p resulted in vrc=%Rrc\n", + aType, mFile, vrc2)); +#endif + break; + } + + default: + AssertMsgFailed(("Unhandled event %RU32\n", aType)); + break; + } + + return S_OK; + } + +private: + + /** Weak pointer to the guest file object to listen for. */ + GuestFile *mFile; +}; +typedef ListenerImpl<GuestFileListener, GuestFile*> GuestFileListenerImpl; + +VBOX_LISTENER_DECLARE(GuestFileListenerImpl) + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestFile) + +HRESULT GuestFile::FinalConstruct(void) +{ + LogFlowThisFuncEnter(); + return BaseFinalConstruct(); +} + +void GuestFile::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes a file object but does *not* open the file on the guest + * yet. This is done in the dedidcated openFile call. + * + * @return IPRT status code. + * @param pConsole Pointer to console object. + * @param pSession Pointer to session object. + * @param aObjectID The object's ID. + * @param openInfo File opening information. + */ +int GuestFile::init(Console *pConsole, GuestSession *pSession, + ULONG aObjectID, const GuestFileOpenInfo &openInfo) +{ + LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s\n", + pConsole, pSession, aObjectID, openInfo.mFilename.c_str())); + + AssertPtrReturn(pConsole, VERR_INVALID_POINTER); + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); + + int vrc = bindToSession(pConsole, pSession, aObjectID); + if (RT_SUCCESS(vrc)) + { + mSession = pSession; + + mData.mOpenInfo = openInfo; + mData.mInitialSize = 0; + mData.mStatus = FileStatus_Undefined; + mData.mLastError = VINF_SUCCESS; + mData.mOffCurrent = 0; + + unconst(mEventSource).createObject(); + HRESULT hr = mEventSource->init(); + if (FAILED(hr)) + vrc = VERR_COM_UNEXPECTED; + } + + if (RT_SUCCESS(vrc)) + { + try + { + GuestFileListener *pListener = new GuestFileListener(); + ComObjPtr<GuestFileListenerImpl> thisListener; + HRESULT hr = thisListener.createObject(); + if (SUCCEEDED(hr)) + hr = thisListener->init(pListener, this); + + if (SUCCEEDED(hr)) + { + com::SafeArray <VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileRead); + eventTypes.push_back(VBoxEventType_OnGuestFileWrite); + hr = mEventSource->RegisterListener(thisListener, + ComSafeArrayAsInParam(eventTypes), + TRUE /* Active listener */); + if (SUCCEEDED(hr)) + { + vrc = baseInit(); + if (RT_SUCCESS(vrc)) + { + mLocalListener = thisListener; + } + } + else + vrc = VERR_COM_UNEXPECTED; + } + else + vrc = VERR_COM_UNEXPECTED; + } + catch(std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + } + else + autoInitSpan.setFailed(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestFile::uninit(void) +{ + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFuncEnter(); + + baseUninit(); + LogFlowThisFuncLeave(); +} + +// implementation of public getters/setters for attributes +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestFile::getCreationMode(ULONG *aCreationMode) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aCreationMode = mData.mOpenInfo.mCreationMode; + + return S_OK; +} + +HRESULT GuestFile::getOpenAction(FileOpenAction_T *aOpenAction) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aOpenAction = mData.mOpenInfo.mOpenAction; + + return S_OK; +} + +HRESULT GuestFile::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + /* No need to lock - lifetime constant. */ + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + return S_OK; +} + +HRESULT GuestFile::getFilename(com::Utf8Str &aFilename) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFilename = mData.mOpenInfo.mFilename; + + return S_OK; +} + +HRESULT GuestFile::getId(ULONG *aId) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aId = mObjectID; + + return S_OK; +} + +HRESULT GuestFile::getInitialSize(LONG64 *aInitialSize) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aInitialSize = mData.mInitialSize; + + return S_OK; +} + +HRESULT GuestFile::getOffset(LONG64 *aOffset) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * This is updated by GuestFile::i_onFileNotify() when read, write and seek + * confirmation messages are recevied. + * + * Note! This will not be accurate with older (< 5.2.32, 6.0.0 - 6.0.9) + * Guest Additions when using writeAt, readAt or writing to a file + * opened in append mode. + */ + *aOffset = mData.mOffCurrent; + + return S_OK; +} + +HRESULT GuestFile::getAccessMode(FileAccessMode_T *aAccessMode) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aAccessMode = mData.mOpenInfo.mAccessMode; + + return S_OK; +} + +HRESULT GuestFile::getStatus(FileStatus_T *aStatus) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aStatus = mData.mStatus; + + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Entry point for guest side file callbacks. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCb Host callback data. + */ +int GuestFile::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strName=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n", + mData.mOpenInfo.mFilename.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb)); + + int vrc; + switch (pCbCtx->uMessage) + { + case GUEST_MSG_DISCONNECTED: + vrc = i_onGuestDisconnected(pCbCtx, pSvcCb); + break; + + case GUEST_MSG_FILE_NOTIFY: + vrc = i_onFileNotify(pCbCtx, pSvcCb); + break; + + default: + /* Silently ignore not implemented functions. */ + vrc = VERR_NOT_SUPPORTED; + break; + } + +#ifdef DEBUG + LogFlowFuncLeaveRC(vrc); +#endif + return vrc; +} + +/** + * Closes the file on the guest side and unregisters it. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. + */ +int GuestFile::i_closeFile(int *prcGuest) +{ + LogFlowThisFunc(("strFile=%s\n", mData.mOpenInfo.mFilename.c_str())); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* Guest file ID */); + + vrc = sendMessage(HOST_MSG_FILE_CLOSE, i, paParms); + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, 30 * 1000 /* Timeout in ms */, + NULL /* FileStatus */, prcGuest); + unregisterWaitEvent(pEvent); + + /* Unregister the file object from the guest session. */ + AssertPtr(mSession); + int vrc2 = mSession->i_fileUnregister(this); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Converts a given guest file error to a string. + * + * @returns Error string. + * @param rcGuest Guest file error to return string for. + * @param pcszWhat Hint of what was involved when the error occurred. + */ +/* static */ +Utf8Str GuestFile::i_guestErrorToString(int rcGuest, const char *pcszWhat) +{ + AssertPtrReturn(pcszWhat, ""); + + Utf8Str strErr; + switch (rcGuest) + { +#define CASE_MSG(a_iRc, ...) \ + case a_iRc: strErr.printf(__VA_ARGS__); break; + CASE_MSG(VERR_ACCESS_DENIED , tr("Access to guest file \"%s\" denied"), pcszWhat); + CASE_MSG(VERR_ALREADY_EXISTS , tr("Guest file \"%s\" already exists"), pcszWhat); + CASE_MSG(VERR_FILE_NOT_FOUND , tr("Guest file \"%s\" not found"), pcszWhat); + CASE_MSG(VERR_NET_HOST_NOT_FOUND, tr("Host name \"%s\", not found"), pcszWhat); + CASE_MSG(VERR_SHARING_VIOLATION , tr("Sharing violation for guest file \"%s\""), pcszWhat); + default: + strErr.printf(tr("Error %Rrc for guest file \"%s\" occurred\n"), rcGuest, pcszWhat); + break; +#undef CASE_MSG + } + + return strErr; +} + +/** + * Called when the guest side notifies the host of a file event. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + */ +int GuestFile::i_onFileNotify(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + LogFlowThisFuncEnter(); + + if (pSvcCbData->mParms < 3) + return VERR_INVALID_PARAMETER; + + int idx = 1; /* Current parameter index. */ + CALLBACKDATA_FILE_NOTIFY dataCb; + RT_ZERO(dataCb); + /* pSvcCb->mpaParms[0] always contains the context ID. */ + HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.uType); + HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.rc); + + int vrcGuest = (int)dataCb.rc; /* uint32_t vs. int. */ + + LogFlowThisFunc(("uType=%RU32, vrcGuest=%Rrc\n", dataCb.uType, vrcGuest)); + + if (RT_FAILURE(vrcGuest)) + { + int vrc2 = i_setFileStatus(FileStatus_Error, vrcGuest); + AssertRC(vrc2); + + /* Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternal(pCbCtx, vrcGuest, NULL /* pPayload */); + return VINF_SUCCESS; /* Report to the guest. */ + } + + AssertMsg(mObjectID == VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID), + ("File ID %RU32 does not match object ID %RU32\n", mObjectID, + VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID))); + + int vrc = VERR_NOT_SUPPORTED; /* Play safe by default. */ + + switch (dataCb.uType) + { + case GUEST_FILE_NOTIFYTYPE_ERROR: + { + vrc = i_setFileStatus(FileStatus_Error, vrcGuest); + break; + } + + case GUEST_FILE_NOTIFYTYPE_OPEN: + { + if (pSvcCbData->mParms == 4) + { + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.u.open.uHandle); + if (RT_FAILURE(vrc)) + break; + + /* Set the process status. */ + vrc = i_setFileStatus(FileStatus_Open, vrcGuest); + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_CLOSE: + { + vrc = i_setFileStatus(FileStatus_Closed, vrcGuest); + break; + } + + case GUEST_FILE_NOTIFYTYPE_READ: + { + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + + vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[idx++], &dataCb.u.read.pvData, &dataCb.u.read.cbData); + if (RT_FAILURE(vrc)) + break; + + const uint32_t cbRead = dataCb.u.read.cbData; + Log3ThisFunc(("cbRead=%RU32\n", cbRead)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mOffCurrent += cbRead; /* Bogus for readAt, which is why we've got GUEST_FILE_NOTIFYTYPE_READ_OFFSET. */ + alock.release(); + + try + { + com::SafeArray<BYTE> data((size_t)cbRead); + AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY); + data.initFrom((BYTE *)dataCb.u.read.pvData, cbRead); + ::FireGuestFileReadEvent(mEventSource, mSession, this, mData.mOffCurrent, cbRead, ComSafeArrayAsInParam(data)); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_READ_OFFSET: + { + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT, + ("type=%u\n", pSvcCbData->mpaParms[idx + 1].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + BYTE const * const pbData = (BYTE const *)pSvcCbData->mpaParms[idx].u.pointer.addr; + uint32_t const cbRead = pSvcCbData->mpaParms[idx].u.pointer.size; + int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64; + Log3ThisFunc(("cbRead=%RU32 offNew=%RI64 (%#RX64)\n", cbRead, offNew, offNew)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (offNew < 0) /* non-seekable */ + offNew = mData.mOffCurrent + cbRead; + mData.mOffCurrent = offNew; + alock.release(); + + try + { + com::SafeArray<BYTE> data((size_t)cbRead); + AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY); + data.initFrom(pbData, cbRead); + ::FireGuestFileReadEvent(mEventSource, mSession, this, offNew, cbRead, ComSafeArrayAsInParam(data)); + vrc = VINF_SUCCESS; + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_WRITE: + { + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + + uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32; + + Log3ThisFunc(("cbWritten=%RU32\n", cbWritten)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mOffCurrent += cbWritten; /* Bogus for writeAt and append mode, thus GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET. */ + alock.release(); + + ::FireGuestFileWriteEvent(mEventSource, mSession, this, mData.mOffCurrent, cbWritten); + break; + } + + case GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET: + { + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32; + int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64; + Log3ThisFunc(("cbWritten=%RU32 offNew=%RI64 (%#RX64)\n", cbWritten, offNew, offNew)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (offNew < 0) /* non-seekable */ + offNew = mData.mOffCurrent + cbWritten; + mData.mOffCurrent = offNew; + alock.release(); + + HRESULT hrc2 = ::FireGuestFileWriteEvent(mEventSource, mSession, this, offNew, cbWritten); + vrc = SUCCEEDED(hrc2) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc2); + break; + } + + case GUEST_FILE_NOTIFYTYPE_SEEK: + { + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + + vrc = HGCMSvcGetU64(&pSvcCbData->mpaParms[idx++], &dataCb.u.seek.uOffActual); + if (RT_FAILURE(vrc)) + break; + + Log3ThisFunc(("uOffActual=%RU64\n", dataCb.u.seek.uOffActual)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mOffCurrent = dataCb.u.seek.uOffActual; + alock.release(); + + ::FireGuestFileOffsetChangedEvent(mEventSource, mSession, this, dataCb.u.seek.uOffActual, 0 /* Processed */); + break; + } + + case GUEST_FILE_NOTIFYTYPE_TELL: + /* We don't issue any HOST_MSG_FILE_TELL, so we shouldn't get these notifications! */ + AssertFailed(); + break; + + case GUEST_FILE_NOTIFYTYPE_SET_SIZE: + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms), + vrc = VERR_WRONG_PARAMETER_COUNT); + ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_64BIT, + ("type=%u\n", pSvcCbData->mpaParms[idx].type), + vrc = VERR_WRONG_PARAMETER_TYPE); + dataCb.u.SetSize.cbSize = pSvcCbData->mpaParms[idx].u.uint64; + Log3ThisFunc(("cbSize=%RU64\n", dataCb.u.SetSize.cbSize)); + + ::FireGuestFileSizeChangedEvent(mEventSource, mSession, this, dataCb.u.SetSize.cbSize); + vrc = VINF_SUCCESS; + break; + + default: + break; + } + + try + { + if (RT_SUCCESS(vrc)) + { + GuestWaitEventPayload payload(dataCb.uType, &dataCb, sizeof(dataCb)); + + /* Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternal(pCbCtx, vrcGuest, &payload); + } + else /* OOM situation, wrong HGCM parameters or smth. not expected. */ + { + /* Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternalEx(pCbCtx, vrc, 0 /* guestRc */, NULL /* pPayload */); + } + } + catch (int vrcEx) /* Thrown by GuestWaitEventPayload constructor. */ + { + /* Also try to signal the waiter, to let it know of the OOM situation. + * Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternalEx(pCbCtx, vrcEx, 0 /* guestRc */, NULL /* pPayload */); + vrc = vrcEx; + } + + LogFlowThisFunc(("uType=%RU32, rcGuest=%Rrc, rc=%Rrc\n", dataCb.uType, vrcGuest, vrc)); + return vrc; +} + +/** + * Called when the guest side of the file has been disconnected (closed, terminated, +++). + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + */ +int GuestFile::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + int vrc = i_setFileStatus(FileStatus_Down, VINF_SUCCESS); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc GuestObject::i_onUnregister + */ +int GuestFile::i_onUnregister(void) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VINF_SUCCESS; + + /* + * Note: The event source stuff holds references to this object, + * so make sure that this is cleaned up *before* calling uninit(). + */ + if (!mEventSource.isNull()) + { + mEventSource->UnregisterListener(mLocalListener); + + mLocalListener.setNull(); + unconst(mEventSource).setNull(); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc GuestObject::i_onSessionStatusChange + */ +int GuestFile::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus) +{ + LogFlowThisFuncEnter(); + + int vrc = VINF_SUCCESS; + + /* If the session now is in a terminated state, set the file status + * to "down", as there is not much else we can do now. */ + if (GuestSession::i_isTerminated(enmSessionStatus)) + vrc = i_setFileStatus(FileStatus_Down, 0 /* fileRc, ignored */); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Opens the file on the guest. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestFile::i_openFile(uint32_t uTimeoutMS, int *prcGuest) +{ + AssertReturn(mData.mOpenInfo.mFilename.isNotEmpty(), VERR_INVALID_PARAMETER); + + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("strFile=%s, enmAccessMode=%d, enmOpenAction=%d, uCreationMode=%o, mfOpenEx=%#x\n", + mData.mOpenInfo.mFilename.c_str(), mData.mOpenInfo.mAccessMode, mData.mOpenInfo.mOpenAction, + mData.mOpenInfo.mCreationMode, mData.mOpenInfo.mfOpenEx)); + + /* Validate and translate open action. */ + const char *pszOpenAction = NULL; + switch (mData.mOpenInfo.mOpenAction) + { + case FileOpenAction_OpenExisting: pszOpenAction = "oe"; break; + case FileOpenAction_OpenOrCreate: pszOpenAction = "oc"; break; + case FileOpenAction_CreateNew: pszOpenAction = "ce"; break; + case FileOpenAction_CreateOrReplace: pszOpenAction = "ca"; break; + case FileOpenAction_OpenExistingTruncated: pszOpenAction = "ot"; break; + case FileOpenAction_AppendOrCreate: + pszOpenAction = "oa"; /** @todo get rid of this one and implement AppendOnly/AppendRead. */ + break; + default: + return VERR_INVALID_PARAMETER; + } + + /* Validate and translate access mode. */ + const char *pszAccessMode = NULL; + switch (mData.mOpenInfo.mAccessMode) + { + case FileAccessMode_ReadOnly: pszAccessMode = "r"; break; + case FileAccessMode_WriteOnly: pszAccessMode = "w"; break; + case FileAccessMode_ReadWrite: pszAccessMode = "r+"; break; + case FileAccessMode_AppendOnly: pszAccessMode = "a"; break; + case FileAccessMode_AppendRead: pszAccessMode = "a+"; break; + default: return VERR_INVALID_PARAMETER; + } + + /* Validate and translate sharing mode. */ + const char *pszSharingMode = NULL; + switch (mData.mOpenInfo.mSharingMode) + { + case FileSharingMode_All: pszSharingMode = ""; break; + case FileSharingMode_Read: RT_FALL_THRU(); + case FileSharingMode_Write: RT_FALL_THRU(); + case FileSharingMode_ReadWrite: RT_FALL_THRU(); + case FileSharingMode_Delete: RT_FALL_THRU(); + case FileSharingMode_ReadDelete: RT_FALL_THRU(); + case FileSharingMode_WriteDelete: return VERR_NOT_IMPLEMENTED; + default: return VERR_INVALID_PARAMETER; + } + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetPv(&paParms[i++], (void*)mData.mOpenInfo.mFilename.c_str(), + (ULONG)mData.mOpenInfo.mFilename.length() + 1); + HGCMSvcSetStr(&paParms[i++], pszAccessMode); + HGCMSvcSetStr(&paParms[i++], pszOpenAction); + HGCMSvcSetStr(&paParms[i++], pszSharingMode); + HGCMSvcSetU32(&paParms[i++], mData.mOpenInfo.mCreationMode); + HGCMSvcSetU64(&paParms[i++], 0 /*unused offset*/); + /** @todo Next protocol version: add flags, replace strings, remove initial offset. */ + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_OPEN, i, paParms); + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, uTimeoutMS, NULL /* FileStatus */, prcGuest); + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Queries file system information from a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param objData Where to store the file system object data on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestFile::i_queryInfo(GuestFsObjData &objData, int *prcGuest) +{ + AssertPtr(mSession); + return mSession->i_fsQueryInfo(mData.mOpenInfo.mFilename, FALSE /* fFollowSymlinks */, objData, prcGuest); +} + +/** + * Reads data from a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uSize Size (in bytes) to read. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Where to store the read data on success. + * @param cbData Size (in bytes) of \a pvData on input. + * @param pcbRead Where to return to size (in bytes) read on success. + * Optional. + */ +int GuestFile::i_readData(uint32_t uSize, uint32_t uTimeoutMS, + void* pvData, uint32_t cbData, uint32_t* pcbRead) +{ + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n", + uSize, uTimeoutMS, pvData, cbData)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileRead); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */); + HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_READ, i, paParms); + if (RT_SUCCESS(vrc)) + { + uint32_t cbRead = 0; + vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("cbRead=%RU32\n", cbRead)); + if (pcbRead) + *pcbRead = cbRead; + } + else if (pEvent->HasGuestError()) /* Return guest rc if available. */ + { + vrc = pEvent->GetGuestError(); + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Reads data from a specific position from a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uOffset Offset (in bytes) to start reading from. + * @param uSize Size (in bytes) to read. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Where to store the read data on success. + * @param cbData Size (in bytes) of \a pvData on input. + * @param pcbRead Where to return to size (in bytes) read on success. + * Optional. + */ +int GuestFile::i_readDataAt(uint64_t uOffset, uint32_t uSize, uint32_t uTimeoutMS, + void* pvData, size_t cbData, size_t* pcbRead) +{ + LogFlowThisFunc(("uOffset=%RU64, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n", + uOffset, uSize, uTimeoutMS, pvData, cbData)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileRead); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */); + HGCMSvcSetU64(&paParms[i++], uOffset /* Offset (in bytes) to start reading */); + HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_READ_AT, i, paParms); + if (RT_SUCCESS(vrc)) + { + uint32_t cbRead = 0; + vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("cbRead=%RU32\n", cbRead)); + + if (pcbRead) + *pcbRead = cbRead; + } + else if (pEvent->HasGuestError()) /* Return guest rc if available. */ + { + vrc = pEvent->GetGuestError(); + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Seeks a guest file to a specific position. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param iOffset Offset (in bytes) to seek. + * @param eSeekType Seek type to use. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param puOffset Where to return the new current file position (in bytes) on success. + */ +int GuestFile::i_seekAt(int64_t iOffset, GUEST_FILE_SEEKTYPE eSeekType, + uint32_t uTimeoutMS, uint64_t *puOffset) +{ + LogFlowThisFunc(("iOffset=%RI64, uTimeoutMS=%RU32\n", + iOffset, uTimeoutMS)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */); + HGCMSvcSetU32(&paParms[i++], eSeekType /* Seek method */); + /** @todo uint64_t vs. int64_t! */ + HGCMSvcSetU64(&paParms[i++], (uint64_t)iOffset /* Offset (in bytes) to start reading */); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_SEEK, i, paParms); + if (RT_SUCCESS(vrc)) + { + uint64_t uOffset; + vrc = i_waitForOffsetChange(pEvent, uTimeoutMS, &uOffset); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("uOffset=%RU64\n", uOffset)); + + if (puOffset) + *puOffset = uOffset; + } + else if (pEvent->HasGuestError()) /* Return guest rc if available. */ + { + vrc = pEvent->GetGuestError(); + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sets the current internal file object status. + * + * @returns VBox status code. + * @param fileStatus New file status to set. + * @param fileRc New result code to set. + * + * @note Takes the write lock. + */ +int GuestFile::i_setFileStatus(FileStatus_T fileStatus, int fileRc) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, fileRc=%Rrc\n", + mData.mStatus, fileStatus, fileRc)); + +#ifdef VBOX_STRICT + if (fileStatus == FileStatus_Error) + { + AssertMsg(RT_FAILURE(fileRc), ("Guest rc must be an error (%Rrc)\n", fileRc)); + } + else + AssertMsg(RT_SUCCESS(fileRc), ("Guest rc must not be an error (%Rrc)\n", fileRc)); +#endif + + if (mData.mStatus != fileStatus) + { + mData.mStatus = fileStatus; + mData.mLastError = fileRc; + + ComObjPtr<VirtualBoxErrorInfo> errorInfo; + HRESULT hr = errorInfo.createObject(); + ComAssertComRC(hr); + if (RT_FAILURE(fileRc)) + { + hr = errorInfo->initEx(VBOX_E_IPRT_ERROR, fileRc, + COM_IIDOF(IGuestFile), getComponentName(), + i_guestErrorToString(fileRc, mData.mOpenInfo.mFilename.c_str())); + ComAssertComRC(hr); + } + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestFileStateChangedEvent(mEventSource, mSession, this, fileStatus, errorInfo); + } + + return VINF_SUCCESS; +} + +/** + * Waits for a guest file offset change. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param pEvent Guest wait event to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param puOffset Where to return the new offset (in bytes) on success. + * Optional and can be NULL. + */ +int GuestFile::i_waitForOffsetChange(GuestWaitEvent *pEvent, + uint32_t uTimeoutMS, uint64_t *puOffset) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestFileOffsetChanged) + { + if (puOffset) + { + ComPtr<IGuestFileOffsetChangedEvent> pFileEvent = pIEvent; + Assert(!pFileEvent.isNull()); + + HRESULT hr = pFileEvent->COMGETTER(Offset)((LONG64*)puOffset); + ComAssertComRC(hr); + } + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + return vrc; +} + +/** + * Waits for reading from a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param pEvent Guest wait event to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Where to store read file data on success. + * @param cbData Size (in bytes) of \a pvData. + * @param pcbRead Where to return the actual bytes read on success. + * Optional and can be NULL. + */ +int GuestFile::i_waitForRead(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, + void *pvData, size_t cbData, uint32_t *pcbRead) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestFileRead) + { + vrc = VINF_SUCCESS; + + ComPtr<IGuestFileReadEvent> pFileEvent = pIEvent; + Assert(!pFileEvent.isNull()); + + if (pvData) + { + com::SafeArray <BYTE> data; + HRESULT hrc1 = pFileEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data)); + ComAssertComRC(hrc1); + const size_t cbRead = data.size(); + if (cbRead) + { + if (cbRead <= cbData) + memcpy(pvData, data.raw(), cbRead); + else + vrc = VERR_BUFFER_OVERFLOW; + } + /* else: used to be VERR_NO_DATA, but that messes stuff up. */ + + if (pcbRead) + { + *pcbRead = (uint32_t)cbRead; + Assert(*pcbRead == cbRead); + } + } + else if (pcbRead) + { + *pcbRead = 0; + HRESULT hrc2 = pFileEvent->COMGETTER(Processed)((ULONG *)pcbRead); + ComAssertComRC(hrc2); NOREF(hrc2); + } + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + return vrc; +} + +/** + * Waits for a guest file status change. + * + * @note Similar code in GuestProcess::i_waitForStatusChange() and + * GuestSession::i_waitForStatusChange(). + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param pEvent Guest wait event to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pFileStatus Where to return the file status on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. + */ +int GuestFile::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, + FileStatus_T *pFileStatus, int *prcGuest) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + /* pFileStatus is optional. */ + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + Assert(evtType == VBoxEventType_OnGuestFileStateChanged); + ComPtr<IGuestFileStateChangedEvent> pFileEvent = pIEvent; + Assert(!pFileEvent.isNull()); + + HRESULT hr; + if (pFileStatus) + { + hr = pFileEvent->COMGETTER(Status)(pFileStatus); + ComAssertComRC(hr); + } + + ComPtr<IVirtualBoxErrorInfo> errorInfo; + hr = pFileEvent->COMGETTER(Error)(errorInfo.asOutParam()); + ComAssertComRC(hr); + + LONG lGuestRc; + hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc); + ComAssertComRC(hr); + + LogFlowThisFunc(("resultDetail=%RI32 (%Rrc)\n", + lGuestRc, lGuestRc)); + + if (RT_FAILURE((int)lGuestRc)) + vrc = VERR_GSTCTL_GUEST_ERROR; + + if (prcGuest) + *prcGuest = (int)lGuestRc; + } + /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */ + /** @todo r=bird: Andy, you seem to have forgotten this scenario. Showed up occasionally when + * using the wrong password with a copyto command in a debug build on windows, error info + * contained "Unknown Status -858993460 (0xcccccccc)". As you know windows fills the stack frames + * with 0xcccccccc in debug builds to highlight use of uninitialized data, so that's what happened + * here. It's actually good you didn't initialize lGuest, as it would be heck to find otherwise. + * + * I'm still not very impressed with the error managment or the usuefullness of the documentation + * in this code, though the latter is getting better! */ + else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) + *prcGuest = pEvent->GuestResult(); + Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc); + + return vrc; +} + +int GuestFile::i_waitForWrite(GuestWaitEvent *pEvent, + uint32_t uTimeoutMS, uint32_t *pcbWritten) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestFileWrite) + { + if (pcbWritten) + { + ComPtr<IGuestFileWriteEvent> pFileEvent = pIEvent; + Assert(!pFileEvent.isNull()); + + HRESULT hr = pFileEvent->COMGETTER(Processed)((ULONG*)pcbWritten); + ComAssertComRC(hr); + } + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + return vrc; +} + +/** + * Writes data to a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Data to write. + * @param cbData Size (in bytes) of \a pvData to write. + * @param pcbWritten Where to return to size (in bytes) written on success. + * Optional. + */ +int GuestFile::i_writeData(uint32_t uTimeoutMS, const void *pvData, uint32_t cbData, + uint32_t *pcbWritten) +{ + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n", + uTimeoutMS, pvData, cbData)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileWrite); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */); + HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */); + HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_WRITE, i, paParms); + if (RT_SUCCESS(vrc)) + { + uint32_t cbWritten = 0; + vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten)); + if (pcbWritten) + *pcbWritten = cbWritten; + } + else if (pEvent->HasGuestError()) /* Return guest rc if available. */ + { + vrc = pEvent->GetGuestError(); + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + + +/** + * Writes data to a specific position to a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uOffset Offset (in bytes) to start writing at. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Data to write. + * @param cbData Size (in bytes) of \a pvData to write. + * @param pcbWritten Where to return to size (in bytes) written on success. + * Optional. + */ +int GuestFile::i_writeDataAt(uint64_t uOffset, uint32_t uTimeoutMS, + const void *pvData, uint32_t cbData, uint32_t *pcbWritten) +{ + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("uOffset=%RU64, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n", + uOffset, uTimeoutMS, pvData, cbData)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestFileWrite); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */); + HGCMSvcSetU64(&paParms[i++], uOffset /* Offset where to starting writing */); + HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */); + HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_WRITE_AT, i, paParms); + if (RT_SUCCESS(vrc)) + { + uint32_t cbWritten = 0; + vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten)); + if (pcbWritten) + *pcbWritten = cbWritten; + } + else if (pEvent->HasGuestError()) /* Return guest rc if available. */ + { + vrc = pEvent->GetGuestError(); + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +// Wrapped IGuestFile methods +///////////////////////////////////////////////////////////////////////////// +HRESULT GuestFile::close() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + /* Close file on guest. */ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_closeFile(&vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + GuestErrorInfo ge(GuestErrorInfo::Type_File, vrcGuest, mData.mOpenInfo.mFilename.c_str()); + return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest file failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Closing guest file \"%s\" failed with %Rrc\n"), + mData.mOpenInfo.mFilename.c_str(), vrc); + } + + LogFlowThisFunc(("Returning S_OK / vrc=%Rrc\n", vrc)); + return S_OK; +} + +HRESULT GuestFile::queryInfo(ComPtr<IFsObjInfo> &aObjInfo) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + GuestFsObjData fsObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_queryInfo(fsObjData, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + ComObjPtr<GuestFsObjInfo> ptrFsObjInfo; + hrc = ptrFsObjInfo.createObject(); + if (SUCCEEDED(hrc)) + { + vrc = ptrFsObjInfo->init(fsObjData); + if (RT_SUCCESS(vrc)) + hrc = ptrFsObjInfo.queryInterfaceTo(aObjInfo.asOutParam()); + else + hrc = setErrorVrc(vrc, + tr("Initialization of guest file object for \"%s\" failed: %Rrc"), + mData.mOpenInfo.mFilename.c_str(), vrc); + } + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file information failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + else + hrc = setErrorVrc(vrc, + tr("Querying guest file information for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc); + } + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::querySize(LONG64 *aSize) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + GuestFsObjData fsObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_queryInfo(fsObjData, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + *aSize = fsObjData.mObjectSize; + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file size failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + else + hrc = setErrorVrc(vrc, tr("Querying guest file size for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc); + } + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aToRead == 0) + return setError(E_INVALIDARG, tr("The size to read is zero")); + + LogFlowThisFuncEnter(); + + /* Cap the read at 1MiB because that's all the guest will return anyway. */ + if (aToRead > _1M) + aToRead = _1M; + + HRESULT hrc = S_OK; + + int vrc; + try + { + aData.resize(aToRead); + + uint32_t cbRead; + vrc = i_readData(aToRead, aTimeoutMS, + &aData.front(), aToRead, &cbRead); + + if (RT_SUCCESS(vrc)) + { + if (aData.size() != cbRead) + aData.resize(cbRead); + } + else + { + aData.resize(0); + } + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" failed: %Rrc"), + mData.mOpenInfo.mFilename.c_str(), vrc); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::readAt(LONG64 aOffset, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aToRead == 0) + return setError(E_INVALIDARG, tr("The size to read for guest file \"%s\" is zero"), mData.mOpenInfo.mFilename.c_str()); + + LogFlowThisFuncEnter(); + + /* Cap the read at 1MiB because that's all the guest will return anyway. */ + if (aToRead > _1M) + aToRead = _1M; + + HRESULT hrc = S_OK; + + int vrc; + try + { + aData.resize(aToRead); + + size_t cbRead; + vrc = i_readDataAt(aOffset, aToRead, aTimeoutMS, + &aData.front(), aToRead, &cbRead); + if (RT_SUCCESS(vrc)) + { + if (aData.size() != cbRead) + aData.resize(cbRead); + } + else + { + aData.resize(0); + } + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" (at offset %RU64) failed: %Rrc"), + mData.mOpenInfo.mFilename.c_str(), aOffset, vrc); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LONG64 *aNewOffset) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hrc = S_OK; + + GUEST_FILE_SEEKTYPE eSeekType; + switch (aWhence) + { + case FileSeekOrigin_Begin: + eSeekType = GUEST_FILE_SEEKTYPE_BEGIN; + break; + + case FileSeekOrigin_Current: + eSeekType = GUEST_FILE_SEEKTYPE_CURRENT; + break; + + case FileSeekOrigin_End: + eSeekType = GUEST_FILE_SEEKTYPE_END; + break; + + default: + return setError(E_INVALIDARG, tr("Invalid seek type for guest file \"%s\" specified"), + mData.mOpenInfo.mFilename.c_str()); + } + + LogFlowThisFuncEnter(); + + uint64_t uNewOffset; + int vrc = i_seekAt(aOffset, eSeekType, + 30 * 1000 /* 30s timeout */, &uNewOffset); + if (RT_SUCCESS(vrc)) + *aNewOffset = RT_MIN(uNewOffset, (uint64_t)INT64_MAX); + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Seeking file \"%s\" (to offset %RI64) failed: %Rrc"), + mData.mOpenInfo.mFilename.c_str(), aOffset, vrc); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::setACL(const com::Utf8Str &aAcl, ULONG aMode) +{ + RT_NOREF(aAcl, aMode); + ReturnComNotImplemented(); +} + +HRESULT GuestFile::setSize(LONG64 aSize) +{ + LogFlowThisFuncEnter(); + + /* + * Validate. + */ + if (aSize < 0) + return setError(E_INVALIDARG, tr("The size (%RI64) for guest file \"%s\" cannot be a negative value"), + aSize, mData.mOpenInfo.mFilename.c_str()); + + /* + * Register event callbacks. + */ + int vrc; + GuestWaitEvent *pWaitEvent = NULL; + GuestEventTypes lstEventTypes; + try + { + lstEventTypes.push_back(VBoxEventType_OnGuestFileStateChanged); + lstEventTypes.push_back(VBoxEventType_OnGuestFileSizeChanged); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + vrc = registerWaitEvent(lstEventTypes, &pWaitEvent); + if (RT_SUCCESS(vrc)) + { + /* + * Send of the HGCM message. + */ + VBOXHGCMSVCPARM aParms[3]; + HGCMSvcSetU32(&aParms[0], pWaitEvent->ContextID()); + HGCMSvcSetU32(&aParms[1], mObjectID /* File handle */); + HGCMSvcSetU64(&aParms[2], aSize); + + alock.release(); /* Drop write lock before sending. */ + + vrc = sendMessage(HOST_MSG_FILE_SET_SIZE, RT_ELEMENTS(aParms), aParms); + if (RT_SUCCESS(vrc)) + { + /* + * Wait for the event. + */ + VBoxEventType_T enmEvtType; + ComPtr<IEvent> pIEvent; + vrc = waitForEvent(pWaitEvent, RT_MS_1MIN / 2, &enmEvtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (enmEvtType == VBoxEventType_OnGuestFileSizeChanged) + vrc = VINF_SUCCESS; + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + if (RT_FAILURE(vrc) && pWaitEvent->HasGuestError()) /* Return guest rc if available. */ + vrc = pWaitEvent->GetGuestError(); + } + + /* + * Unregister the wait event and deal with error reporting if needed. + */ + unregisterWaitEvent(pWaitEvent); + } + HRESULT hrc; + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Setting the guest file size of \"%s\" to %RU64 (%#RX64) bytes failed: %Rrc", "", aSize), + mData.mOpenInfo.mFilename.c_str(), aSize, aSize, vrc); + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::write(const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aData.size() == 0) + return setError(E_INVALIDARG, tr("No data to write specified"), mData.mOpenInfo.mFilename.c_str()); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + const uint32_t cbData = (uint32_t)aData.size(); + const void *pvData = (void *)&aData.front(); + int vrc = i_writeData(aTimeoutMS, pvData, cbData, (uint32_t*)aWritten); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc", "", aData.size()), + aData.size(), mData.mOpenInfo.mFilename.c_str(), vrc); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestFile::writeAt(LONG64 aOffset, const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aData.size() == 0) + return setError(E_INVALIDARG, tr("No data to write at for guest file \"%s\" specified"), mData.mOpenInfo.mFilename.c_str()); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + const uint32_t cbData = (uint32_t)aData.size(); + const void *pvData = (void *)&aData.front(); + int vrc = i_writeDataAt(aOffset, aTimeoutMS, pvData, cbData, (uint32_t*)aWritten); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Writing %zu bytes to file \"%s\" (at offset %RU64) failed: %Rrc", "", aData.size()), + aData.size(), mData.mOpenInfo.mFilename.c_str(), aOffset, vrc); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + diff --git a/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp new file mode 100644 index 00000000..df4d1c59 --- /dev/null +++ b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp @@ -0,0 +1,237 @@ +/* $Id: GuestFsObjInfoImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest file system object information handling. + */ + +/* + * Copyright (C) 2012-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTFSOBJINFO +#include "LoggingNew.h" + +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestFsObjInfoImpl.h" +#include "GuestCtrlImplPrivate.h" + +#include "Global.h" +#include "AutoCaller.h" + +#include <VBox/com/array.h> + + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestFsObjInfo) + +HRESULT GuestFsObjInfo::FinalConstruct(void) +{ + LogFlowThisFuncEnter(); + return BaseFinalConstruct(); +} + +void GuestFsObjInfo::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +int GuestFsObjInfo::init(const GuestFsObjData &objData) +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); + + mData = objData; + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return VINF_SUCCESS; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestFsObjInfo::uninit(void) +{ + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFuncEnter(); +} + +// implementation of wrapped private getters/setters for attributes +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestFsObjInfo::getAccessTime(LONG64 *aAccessTime) +{ + *aAccessTime = mData.mAccessTime; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getAllocatedSize(LONG64 *aAllocatedSize) +{ + *aAllocatedSize = mData.mAllocatedSize; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getBirthTime(LONG64 *aBirthTime) +{ + *aBirthTime = mData.mBirthTime; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getChangeTime(LONG64 *aChangeTime) +{ + *aChangeTime = mData.mChangeTime; + + return S_OK; +} + + + +HRESULT GuestFsObjInfo::getDeviceNumber(ULONG *aDeviceNumber) +{ + *aDeviceNumber = mData.mDeviceNumber; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getFileAttributes(com::Utf8Str &aFileAttributes) +{ + aFileAttributes = mData.mFileAttrs; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getGenerationId(ULONG *aGenerationId) +{ + *aGenerationId = mData.mGenerationID; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getGID(LONG *aGID) +{ + *aGID = mData.mGID; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getGroupName(com::Utf8Str &aGroupName) +{ + aGroupName = mData.mGroupName; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getHardLinks(ULONG *aHardLinks) +{ + *aHardLinks = mData.mNumHardLinks; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getModificationTime(LONG64 *aModificationTime) +{ + *aModificationTime = mData.mModificationTime; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getName(com::Utf8Str &aName) +{ + aName = mData.mName; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getNodeId(LONG64 *aNodeId) +{ + *aNodeId = mData.mNodeID; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getNodeIdDevice(ULONG *aNodeIdDevice) +{ + *aNodeIdDevice = mData.mNodeIDDevice; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getObjectSize(LONG64 *aObjectSize) +{ + *aObjectSize = mData.mObjectSize; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getType(FsObjType_T *aType) +{ + *aType = mData.mType; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getUID(LONG *aUID) +{ + *aUID = mData.mUID; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getUserFlags(ULONG *aUserFlags) +{ + *aUserFlags = mData.mUserFlags; + + return S_OK; +} + +HRESULT GuestFsObjInfo::getUserName(com::Utf8Str &aUserName) +{ + aUserName = mData.mUserName; + + return S_OK; +} + diff --git a/src/VBox/Main/src-client/GuestImpl.cpp b/src/VBox/Main/src-client/GuestImpl.cpp new file mode 100644 index 00000000..227da127 --- /dev/null +++ b/src/VBox/Main/src-client/GuestImpl.cpp @@ -0,0 +1,1201 @@ +/* $Id: GuestImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation: Guest features. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_GUEST +#include "LoggingNew.h" + +#include "GuestImpl.h" +#ifdef VBOX_WITH_GUEST_CONTROL +# include "GuestSessionImpl.h" +#endif +#include "Global.h" +#include "ConsoleImpl.h" +#include "ProgressImpl.h" +#ifdef VBOX_WITH_DRAG_AND_DROP +# include "GuestDnDPrivate.h" +#endif +#include "VMMDev.h" + +#include "AutoCaller.h" +#include "Performance.h" +#include "VBoxEvents.h" + +#include <VBox/VMMDev.h> +#include <iprt/cpp/utils.h> +#include <iprt/ctype.h> +#include <iprt/stream.h> +#include <iprt/timer.h> +#include <VBox/vmm/pgm.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/version.h> + +// defines +///////////////////////////////////////////////////////////////////////////// + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(Guest) + +HRESULT Guest::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void Guest::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the guest object. + */ +HRESULT Guest::init(Console *aParent) +{ + LogFlowThisFunc(("aParent=%p\n", aParent)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + ULONG aMemoryBalloonSize = 0; + HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize); + if (SUCCEEDED(hr)) + mMemoryBalloonSize = aMemoryBalloonSize; + else + mMemoryBalloonSize = 0; /* Default is no ballooning */ + + BOOL fPageFusionEnabled = FALSE; + hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled); + if (SUCCEEDED(hr)) + mfPageFusionEnabled = fPageFusionEnabled; + else + mfPageFusionEnabled = false; /* Default is no page fusion*/ + + mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */ + mCollectVMMStats = false; + + /* Clear statistics. */ + mNetStatRx = mNetStatTx = 0; + mNetStatLastTs = RTTimeNanoTS(); + for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++) + mCurrentGuestStat[i] = 0; + mVmValidStats = pm::VMSTATMASK_NONE; + RT_ZERO(mCurrentGuestCpuUserStat); + RT_ZERO(mCurrentGuestCpuKernelStat); + RT_ZERO(mCurrentGuestCpuIdleStat); + + mMagic = GUEST_MAGIC; + mStatTimer = NIL_RTTIMERLR; + + hr = unconst(mEventSource).createObject(); + if (SUCCEEDED(hr)) + hr = mEventSource->init(); + + mCpus = 1; + +#ifdef VBOX_WITH_DRAG_AND_DROP + if (SUCCEEDED(hr)) + { + try + { + GuestDnD::createInstance(this /* pGuest */); + hr = unconst(mDnDSource).createObject(); + if (SUCCEEDED(hr)) + hr = mDnDSource->init(this /* pGuest */); + if (SUCCEEDED(hr)) + { + hr = unconst(mDnDTarget).createObject(); + if (SUCCEEDED(hr)) + hr = mDnDTarget->init(this /* pGuest */); + } + + LogFlowFunc(("Drag and drop initializied with hr=%Rhrc\n", hr)); + } + catch (std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + } +#endif + + /* Confirm a successful initialization when it's the case: */ + if (SUCCEEDED(hr)) + autoInitSpan.setSucceeded(); + else + autoInitSpan.setFailed(); + + LogFlowFunc(("hr=%Rhrc\n", hr)); + return hr; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void Guest::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + /* Destroy stat update timer */ + int vrc = RTTimerLRDestroy(mStatTimer); + AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc)); + mStatTimer = NIL_RTTIMERLR; + mMagic = 0; + +#ifdef VBOX_WITH_GUEST_CONTROL + LogFlowThisFunc(("Closing sessions (%RU64 total)\n", + mData.mGuestSessions.size())); + GuestSessions::iterator itSessions = mData.mGuestSessions.begin(); + while (itSessions != mData.mGuestSessions.end()) + { +# ifdef DEBUG +/** @todo r=bird: hit a use-after-free situation here while debugging the + * 0xcccccccc status code issue in copyto. My bet is that this happens + * because of an uninit race, where GuestSession::close(), or someone, does + * not ensure that the parent object (Guest) is okay to use (in the AutoCaller + * sense), only their own object. */ + ULONG cRefs = itSessions->second->AddRef(); + LogFlowThisFunc(("sessionID=%RU32, cRefs=%RU32\n", itSessions->first, cRefs > 1 ? cRefs - 1 : 0)); + itSessions->second->Release(); +# endif + itSessions->second->uninit(); + ++itSessions; + } + mData.mGuestSessions.clear(); +#endif + +#ifdef VBOX_WITH_DRAG_AND_DROP + GuestDnD::destroyInstance(); + unconst(mDnDSource).setNull(); + unconst(mDnDTarget).setNull(); +#endif + + unconst(mEventSource).setNull(); + unconst(mParent) = NULL; + + LogFlowFuncLeave(); +} + +/* static */ +DECLCALLBACK(void) Guest::i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick) +{ + AssertReturnVoid(pvUser != NULL); + Guest *guest = static_cast<Guest *>(pvUser); + Assert(guest->mMagic == GUEST_MAGIC); + if (guest->mMagic == GUEST_MAGIC) + guest->i_updateStats(iTick); + + NOREF(hTimerLR); +} + +/* static */ +DECLCALLBACK(int) Guest::i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample, + STAMUNIT enmUnit, const char *pszUnit, STAMVISIBILITY enmVisiblity, + const char *pszDesc, void *pvUser) +{ + RT_NOREF(enmVisiblity, pszDesc, pszUnit); + AssertLogRelMsgReturn(enmType == STAMTYPE_COUNTER, ("Unexpected sample type %d ('%s')\n", enmType, pszName), VINF_SUCCESS); + AssertLogRelMsgReturn(enmUnit == STAMUNIT_BYTES, ("Unexpected sample unit %d ('%s')\n", enmUnit, pszName), VINF_SUCCESS); + + /* Get the base name w/ slash. */ + const char *pszLastSlash = strrchr(pszName, '/'); + AssertLogRelMsgReturn(pszLastSlash, ("Unexpected sample '%s'\n", pszName), VINF_SUCCESS); + + /* Receive or transmit? */ + bool fRx; + if (!strcmp(pszLastSlash, "/BytesReceived")) + fRx = true; + else if (!strcmp(pszLastSlash, "/BytesTransmitted")) + fRx = false; + else + AssertLogRelMsgFailedReturn(("Unexpected sample '%s'\n", pszName), VINF_SUCCESS); + +#if 0 /* not used for anything, so don't bother parsing it. */ + /* Find start of instance number. ASSUMES '/Public/Net/Name<Instance digits>/Bytes...' */ + do + --pszLastSlash; + while (pszLastSlash > pszName && RT_C_IS_DIGIT(*pszLastSlash)); + pszLastSlash++; + + uint8_t uInstance; + int vrc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance); + AssertLogRelMsgReturn(RT_SUCCESS(vrc) && vrc != VWRN_NUMBER_TOO_BIG && vrc != VWRN_NEGATIVE_UNSIGNED, + ("%Rrc '%s'\n", vrc, pszName), VINF_SUCCESS) +#endif + + /* Add the bytes to our counters. */ + PSTAMCOUNTER pCnt = (PSTAMCOUNTER)pvSample; + Guest *pGuest = (Guest *)pvUser; + uint64_t cb = pCnt->c; +#if 0 + LogFlowFunc(("%s i=%u d=%s %llu bytes\n", pszName, uInstance, fRx ? "RX" : "TX", cb)); +#else + LogFlowFunc(("%s d=%s %llu bytes\n", pszName, fRx ? "RX" : "TX", cb)); +#endif + if (fRx) + pGuest->mNetStatRx += cb; + else + pGuest->mNetStatTx += cb; + + return VINF_SUCCESS; +} + +void Guest::i_updateStats(uint64_t iTick) +{ + RT_NOREF(iTick); + + uint64_t cbFreeTotal = 0; + uint64_t cbAllocTotal = 0; + uint64_t cbBalloonedTotal = 0; + uint64_t cbSharedTotal = 0; + uint64_t cbSharedMem = 0; + ULONG uNetStatRx = 0; + ULONG uNetStatTx = 0; + ULONG aGuestStats[GUESTSTATTYPE_MAX]; + RT_ZERO(aGuestStats); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + ULONG validStats = mVmValidStats; + /* Check if we have anything to report */ + if (validStats) + { + mVmValidStats = pm::VMSTATMASK_NONE; + memcpy(aGuestStats, mCurrentGuestStat, sizeof(aGuestStats)); + } + alock.release(); + + /* + * Calling SessionMachine may take time as the object resides in VBoxSVC + * process. This is why we took a snapshot of currently collected stats + * and released the lock. + */ + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + int vrc; + + /* + * There is no point in collecting VM shared memory if other memory + * statistics are not available yet. Or is there? + */ + if (validStats) + { + /* Query the missing per-VM memory statistics. */ + uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbZeroMemIgn; + vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, + &cbSharedMem, &cbZeroMemIgn); + if (vrc == VINF_SUCCESS) + validStats |= pm::VMSTATMASK_GUEST_MEMSHARED; + } + + if (mCollectVMMStats) + { + vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, + &cbBalloonedTotal, &cbSharedTotal); + AssertRC(vrc); + if (vrc == VINF_SUCCESS) + validStats |= pm::VMSTATMASK_VMM_ALLOC | pm::VMSTATMASK_VMM_FREE + | pm::VMSTATMASK_VMM_BALOON | pm::VMSTATMASK_VMM_SHARED; + } + + uint64_t uRxPrev = mNetStatRx; + uint64_t uTxPrev = mNetStatTx; + mNetStatRx = mNetStatTx = 0; + vrc = ptrVM.vtable()->pfnSTAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this); + AssertRC(vrc); + + uint64_t uTsNow = RTTimeNanoTS(); + uint64_t cNsPassed = uTsNow - mNetStatLastTs; + if (cNsPassed >= 1000) + { + mNetStatLastTs = uTsNow; + + uNetStatRx = (ULONG)((mNetStatRx - uRxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */ + uNetStatTx = (ULONG)((mNetStatTx - uTxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */ + validStats |= pm::VMSTATMASK_NET_RX | pm::VMSTATMASK_NET_TX; + LogFlowThisFunc(("Net Rx=%llu Tx=%llu Ts=%llu Delta=%llu\n", mNetStatRx, mNetStatTx, uTsNow, cNsPassed)); + } + else + { + /* Can happen on resume or if we're using a non-monotonic clock + source for the timer and the time is adjusted. */ + mNetStatRx = uRxPrev; + mNetStatTx = uTxPrev; + LogThisFunc(("Net Ts=%llu cNsPassed=%llu - too small interval\n", uTsNow, cNsPassed)); + } + } + + mParent->i_reportVmStatistics(validStats, + aGuestStats[GUESTSTATTYPE_CPUUSER], + aGuestStats[GUESTSTATTYPE_CPUKERNEL], + aGuestStats[GUESTSTATTYPE_CPUIDLE], + /* Convert the units for RAM usage stats: page (4K) -> 1KB units */ + mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K), + mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K), + mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K), + (ULONG)(cbSharedMem / _1K), /* bytes -> KB */ + mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K), + mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K), + (ULONG)(cbAllocTotal / _1K), /* bytes -> KB */ + (ULONG)(cbFreeTotal / _1K), + (ULONG)(cbBalloonedTotal / _1K), + (ULONG)(cbSharedTotal / _1K), + uNetStatRx, + uNetStatTx); +} + +// IGuest properties +///////////////////////////////////////////////////////////////////////////// + +HRESULT Guest::getOSTypeId(com::Utf8Str &aOSTypeId) +{ + HRESULT hrc = S_OK; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (!mData.mInterfaceVersion.isEmpty()) + aOSTypeId = mData.mOSTypeId; + else + { + /* Redirect the call to IMachine if no additions are installed. */ + ComPtr<IMachine> ptrMachine(mParent->i_machine()); + alock.release(); + Bstr bstr; + hrc = ptrMachine->COMGETTER(OSTypeId)(bstr.asOutParam()); + aOSTypeId = bstr; + } + return hrc; +} + +HRESULT Guest::getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aAdditionsRunLevel = mData.mAdditionsRunLevel; + + return S_OK; +} + +HRESULT Guest::getAdditionsVersion(com::Utf8Str &aAdditionsVersion) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + HRESULT hrc = S_OK; + + /* + * Return the ReportGuestInfo2 version info if available. + */ + if ( !mData.mAdditionsVersionNew.isEmpty() + || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None) + aAdditionsVersion = mData.mAdditionsVersionNew; + else + { + /* + * If we're running older Guest Additions (< 3.2.0) try get it from + * the guest properties. Detected switched around Version and + * Revision in early 3.1.x releases (see r57115). + */ + ComPtr<IMachine> ptrMachine = mParent->i_machine(); + alock.release(); /* No need to hold this during the IPC fun. */ + + Bstr bstr; + hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam()); + if ( SUCCEEDED(hrc) + && !bstr.isEmpty()) + { + Utf8Str str(bstr); + if (str.count('.') == 0) + hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam()); + str = bstr; + if (str.count('.') != 2) + hrc = E_FAIL; + } + + if (SUCCEEDED(hrc)) + aAdditionsVersion = bstr; + else + { + /* Returning 1.4 is better than nothing. */ + alock.acquire(); + aAdditionsVersion = mData.mInterfaceVersion; + hrc = S_OK; + } + } + return hrc; +} + +HRESULT Guest::getAdditionsRevision(ULONG *aAdditionsRevision) +{ + HRESULT hrc = S_OK; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Return the ReportGuestInfo2 version info if available. + */ + if ( !mData.mAdditionsVersionNew.isEmpty() + || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None) + *aAdditionsRevision = mData.mAdditionsRevision; + else + { + /* + * If we're running older Guest Additions (< 3.2.0) try get it from + * the guest properties. Detected switched around Version and + * Revision in early 3.1.x releases (see r57115). + */ + ComPtr<IMachine> ptrMachine = mParent->i_machine(); + alock.release(); /* No need to hold this during the IPC fun. */ + + Bstr bstr; + hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam()); + if (SUCCEEDED(hrc)) + { + Utf8Str str(bstr); + uint32_t uRevision; + int vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision); + if (vrc != VINF_SUCCESS && str.count('.') == 2) + { + hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam()); + if (SUCCEEDED(hrc)) + { + str = bstr; + vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision); + } + } + if (vrc == VINF_SUCCESS) + *aAdditionsRevision = uRevision; + else + hrc = VBOX_E_IPRT_ERROR; + } + if (FAILED(hrc)) + { + /* Return 0 if we don't know. */ + *aAdditionsRevision = 0; + hrc = S_OK; + } + } + return hrc; +} + +HRESULT Guest::getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource) +{ +#ifndef VBOX_WITH_DRAG_AND_DROP + RT_NOREF(aDnDSource); + ReturnComNotImplemented(); +#else + LogFlowThisFuncEnter(); + + /* No need to lock - lifetime constant. */ + HRESULT hr = mDnDSource.queryInterfaceTo(aDnDSource.asOutParam()); + + LogFlowFuncLeaveRC(hr); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT Guest::getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget) +{ +#ifndef VBOX_WITH_DRAG_AND_DROP + RT_NOREF(aDnDTarget); + ReturnComNotImplemented(); +#else + LogFlowThisFuncEnter(); + + /* No need to lock - lifetime constant. */ + HRESULT hr = mDnDTarget.queryInterfaceTo(aDnDTarget.asOutParam()); + + LogFlowFuncLeaveRC(hr); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +HRESULT Guest::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + LogFlowThisFuncEnter(); + + /* No need to lock - lifetime constant. */ + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + LogFlowFuncLeaveRC(S_OK); + return S_OK; +} + +HRESULT Guest::getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFacilities.resize(mData.mFacilityMap.size()); + size_t i = 0; + for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it, ++i) + it->second.queryInterfaceTo(aFacilities[i].asOutParam()); + + return S_OK; +} + +HRESULT Guest::getSessions(std::vector<ComPtr<IGuestSession> > &aSessions) +{ +#ifdef VBOX_WITH_GUEST_CONTROL + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aSessions.resize(mData.mGuestSessions.size()); + size_t i = 0; + for (GuestSessions::iterator it = mData.mGuestSessions.begin(); it != mData.mGuestSessions.end(); ++it, ++i) + it->second.queryInterfaceTo(aSessions[i].asOutParam()); + + return S_OK; +#else + ReturnComNotImplemented(); +#endif +} + +BOOL Guest::i_isPageFusionEnabled() +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return mfPageFusionEnabled; +} + +HRESULT Guest::getMemoryBalloonSize(ULONG *aMemoryBalloonSize) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aMemoryBalloonSize = mMemoryBalloonSize; + + return S_OK; +} + +HRESULT Guest::setMemoryBalloonSize(ULONG aMemoryBalloonSize) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize) + * does not call us back in any way! */ + HRESULT ret = mParent->i_machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize); + if (ret == S_OK) + { + mMemoryBalloonSize = aMemoryBalloonSize; + /* forward the information to the VMM device */ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + /* MUST release all locks before calling VMM device as its critsect + * has higher lock order than anything in Main. */ + alock.release(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize); + } + } + + return ret; +} + +HRESULT Guest::getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aStatisticsUpdateInterval = mStatUpdateInterval; + return S_OK; +} + +HRESULT Guest::setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Update the timer, creating it the first time we're called with a non-zero value. */ + int vrc; + HRESULT hrc = S_OK; + if (aStatisticsUpdateInterval > 0) + { + if (mStatTimer == NIL_RTTIMERLR) + { + vrc = RTTimerLRCreate(&mStatTimer, aStatisticsUpdateInterval * RT_MS_1SEC, &Guest::i_staticUpdateStats, this); + AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to create guest statistics update timer (%Rrc)"), vrc)); + } + else if (aStatisticsUpdateInterval != mStatUpdateInterval) + { + vrc = RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval * RT_NS_1SEC_64); + AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)"), + mStatUpdateInterval, aStatisticsUpdateInterval, vrc)); + if (mStatUpdateInterval == 0) + { + vrc = RTTimerLRStart(mStatTimer, 0); + AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to start the guest statistics update timer (%Rrc)"), vrc)); + } + } + } + /* Setting interval to zero - stop the update timer if needed: */ + else if (mStatUpdateInterval > 0 && mStatTimer != NIL_RTTIMERLR) + { + vrc = RTTimerLRStop(mStatTimer); + AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to stop the guest statistics update timer (%Rrc)"), vrc)); + } + + /* Update the interval now that the timer is in sync. */ + mStatUpdateInterval = aStatisticsUpdateInterval; + + /* Forward the information to the VMM device. + MUST release all locks before calling VMM device as its critsect + has higher lock order than anything in Main. */ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + alock.release(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval); + } + + return hrc; +} + + +HRESULT Guest::internalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle, + ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon, + ULONG *aMemShared, ULONG *aMemCache, ULONG *aPageTotal, + ULONG *aMemAllocTotal, ULONG *aMemFreeTotal, + ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER]; + *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL]; + *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE]; + *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */ + *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */ + *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */ + *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */ + *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */ + + /* Play safe or smth? */ + *aMemAllocTotal = 0; + *aMemFreeTotal = 0; + *aMemBalloonTotal = 0; + *aMemSharedTotal = 0; + *aMemShared = 0; + + /* MUST release all locks before calling any PGM statistics queries, + * as they are executed by EMT and that might deadlock us by VMM device + * activity which waits for the Guest object lock. */ + alock.release(); + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return E_FAIL; + + uint64_t cbFreeTotal, cbAllocTotal, cbBalloonedTotal, cbSharedTotal; + int vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, + &cbBalloonedTotal, &cbSharedTotal); + AssertRCReturn(vrc, E_FAIL); + + *aMemAllocTotal = (ULONG)(cbAllocTotal / _1K); /* bytes -> KB */ + *aMemFreeTotal = (ULONG)(cbFreeTotal / _1K); + *aMemBalloonTotal = (ULONG)(cbBalloonedTotal / _1K); + *aMemSharedTotal = (ULONG)(cbSharedTotal / _1K); + + /* Query the missing per-VM memory statistics. */ + uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbSharedMem, cbZeroMemIgn; + vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn); + AssertRCReturn(vrc, E_FAIL); + *aMemShared = (ULONG)(cbSharedMem / _1K); + + return S_OK; +} + +HRESULT Guest::i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal) +{ + static ULONG indexToPerfMask[] = + { + pm::VMSTATMASK_GUEST_CPUUSER, + pm::VMSTATMASK_GUEST_CPUKERNEL, + pm::VMSTATMASK_GUEST_CPUIDLE, + pm::VMSTATMASK_GUEST_MEMTOTAL, + pm::VMSTATMASK_GUEST_MEMFREE, + pm::VMSTATMASK_GUEST_MEMBALLOON, + pm::VMSTATMASK_GUEST_MEMCACHE, + pm::VMSTATMASK_GUEST_PAGETOTAL, + pm::VMSTATMASK_NONE + }; + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (enmType >= GUESTSTATTYPE_MAX) + return E_INVALIDARG; + + if (aCpuId < VMM_MAX_CPU_COUNT) + { + ULONG *paCpuStats; + switch (enmType) + { + case GUESTSTATTYPE_CPUUSER: paCpuStats = mCurrentGuestCpuUserStat; break; + case GUESTSTATTYPE_CPUKERNEL: paCpuStats = mCurrentGuestCpuKernelStat; break; + case GUESTSTATTYPE_CPUIDLE: paCpuStats = mCurrentGuestCpuIdleStat; break; + default: paCpuStats = NULL; break; + } + if (paCpuStats) + { + paCpuStats[aCpuId] = aVal; + aVal = 0; + for (uint32_t i = 0; i < mCpus && i < VMM_MAX_CPU_COUNT; i++) + aVal += paCpuStats[i]; + aVal /= mCpus; + } + } + + mCurrentGuestStat[enmType] = aVal; + mVmValidStats |= indexToPerfMask[enmType]; + return S_OK; +} + +/** + * Returns the status of a specified Guest Additions facility. + * + * @return COM status code + * @param aFacility Facility to get the status from. + * @param aTimestamp Timestamp of last facility status update in ms (optional). + * @param aStatus Current status of the specified facility. + */ +HRESULT Guest::getFacilityStatus(AdditionsFacilityType_T aFacility, LONG64 *aTimestamp, AdditionsFacilityStatus_T *aStatus) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Not checking for aTimestamp is intentional; it's optional. */ + FacilityMapIterConst it = mData.mFacilityMap.find(aFacility); + if (it != mData.mFacilityMap.end()) + { + AdditionsFacility *pFacility = it->second; + ComAssert(pFacility); + *aStatus = pFacility->i_getStatus(); + if (aTimestamp) + *aTimestamp = pFacility->i_getLastUpdated(); + } + else + { + /* + * Do not fail here -- could be that the facility never has been brought up (yet) but + * the host wants to have its status anyway. So just tell we don't know at this point. + */ + *aStatus = AdditionsFacilityStatus_Unknown; + if (aTimestamp) + *aTimestamp = RTTimeMilliTS(); + } + return S_OK; +} + +HRESULT Guest::getAdditionsStatus(AdditionsRunLevelType_T aLevel, BOOL *aActive) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + switch (aLevel) + { + case AdditionsRunLevelType_System: + *aActive = (mData.mAdditionsRunLevel > AdditionsRunLevelType_None); + break; + + case AdditionsRunLevelType_Userland: + *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Userland); + break; + + case AdditionsRunLevelType_Desktop: + *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Desktop); + break; + + default: + hrc = setError(VBOX_E_NOT_SUPPORTED, + tr("Invalid status level defined: %u"), aLevel); + break; + } + + return hrc; +} + +HRESULT Guest::setCredentials(const com::Utf8Str &aUserName, const com::Utf8Str &aPassword, + const com::Utf8Str &aDomain, BOOL aAllowInteractiveLogon) +{ + /* Check for magic domain names which are used to pass encryption keys to the disk. */ + if (Utf8Str(aDomain) == "@@disk") + return mParent->i_setDiskEncryptionKeys(aPassword); + if (Utf8Str(aDomain) == "@@mem") + { + /** @todo */ + return E_NOTIMPL; + } + + /* forward the information to the VMM device */ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (pVMMDevPort) + { + uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON; + if (!aAllowInteractiveLogon) + u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON; + + pVMMDevPort->pfnSetCredentials(pVMMDevPort, + aUserName.c_str(), + aPassword.c_str(), + aDomain.c_str(), + u32Flags); + return S_OK; + } + } + + return setError(VBOX_E_VM_ERROR, tr("VMM device is not available (is the VM running?)")); +} + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Sets the general Guest Additions information like + * API (interface) version and OS type. Gets called by + * vmmdevUpdateGuestInfo. + * + * @param aInterfaceVersion + * @param aOsType + */ +void Guest::i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType) +{ + RTTIMESPEC TimeSpecTS; + RTTimeNow(&TimeSpecTS); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Note: The Guest Additions API (interface) version is deprecated + * and will not be used anymore! We might need it to at least report + * something as version number if *really* ancient Guest Additions are + * installed (without the guest version + revision properties having set). + */ + mData.mInterfaceVersion = aInterfaceVersion; + + /* + * Older Additions rely on the Additions API version whether they + * are assumed to be active or not. Since newer Additions do report + * the Additions version *before* calling this function (by calling + * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo, + * in that order) we can tell apart old and new Additions here. Old + * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion) + * so they just rely on the aInterfaceVersion string (which gets set by + * VMMDevReportGuestInfo). + * + * So only mark the Additions as being active (run level = system) when we + * don't have the Additions version set. + */ + if (mData.mAdditionsVersionNew.isEmpty()) + { + if (aInterfaceVersion.isEmpty()) + mData.mAdditionsRunLevel = AdditionsRunLevelType_None; + else + { + mData.mAdditionsRunLevel = AdditionsRunLevelType_System; + + /* + * To keep it compatible with the old Guest Additions behavior we need to set the + * "graphics" (feature) facility to active as soon as we got the Guest Additions + * interface version. + */ + i_facilityUpdate(VBoxGuestFacilityType_Graphics, VBoxGuestFacilityStatus_Active, 0 /*fFlags*/, &TimeSpecTS); + } + } + + /* + * Older Additions didn't have this finer grained capability bit, + * so enable it by default. Newer Additions will not enable this here + * and use the setSupportedFeatures function instead. + */ + /** @todo r=bird: I don't get the above comment nor the code below... + * One talks about capability bits, the one always does something to a facility. + * Then there is the comment below it all, which is placed like it addresses the + * mOSTypeId, but talks about something which doesn't remotely like mOSTypeId... + * + * Andy, could you please try clarify and make the comments shorter and more + * coherent! Also, explain why this is important and what depends on it. + * + * PS. There is the VMMDEV_GUEST_SUPPORTS_GRAPHICS capability* report... It + * should come in pretty quickly after this update, normally. + */ + i_facilityUpdate(VBoxGuestFacilityType_Graphics, + i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver) + ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive, + 0 /*fFlags*/, &TimeSpecTS); /** @todo the timestamp isn't gonna be right here on saved state restore. */ + + /* + * Note! There is a race going on between setting mAdditionsRunLevel and + * mSupportsGraphics here and disabling/enabling it later according to + * its real status when using new(er) Guest Additions. + */ + mData.mOSType = aOsType; + mData.mOSTypeId = Global::OSTypeId(aOsType); + + /* + * Always fire an event here. + */ + AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel; + alock.release(); + ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_None, AdditionsFacilityStatus_Active, + enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS)); +} + +/** + * Sets the Guest Additions version information details. + * + * Gets called by vmmdevUpdateGuestInfo2 and vmmdevUpdateGuestInfo (to clear the + * state). + * + * @param a_uFullVersion VBoxGuestInfo2::additionsMajor, + * VBoxGuestInfo2::additionsMinor and + * VBoxGuestInfo2::additionsBuild combined into + * one value by VBOX_FULL_VERSION_MAKE. + * + * When this is 0, it's vmmdevUpdateGuestInfo + * calling to reset the state. + * + * @param a_pszName Build type tag and/or publisher tag, empty + * string if neiter of those are present. + * @param a_uRevision See VBoxGuestInfo2::additionsRevision. + * @param a_fFeatures See VBoxGuestInfo2::additionsFeatures. + */ +void Guest::i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (a_uFullVersion) + { + mData.mAdditionsVersionNew = Utf8StrFmt(*a_pszName ? "%u.%u.%u_%s" : "%u.%u.%u", + VBOX_FULL_VERSION_GET_MAJOR(a_uFullVersion), + VBOX_FULL_VERSION_GET_MINOR(a_uFullVersion), + VBOX_FULL_VERSION_GET_BUILD(a_uFullVersion), + a_pszName); + mData.mAdditionsVersionFull = a_uFullVersion; + mData.mAdditionsRevision = a_uRevision; + mData.mAdditionsFeatures = a_fFeatures; + } + else + { + Assert(!a_fFeatures && !a_uRevision && !*a_pszName); + mData.mAdditionsVersionNew.setNull(); + mData.mAdditionsVersionFull = 0; + mData.mAdditionsRevision = 0; + mData.mAdditionsFeatures = 0; + } +} + +bool Guest::i_facilityIsActive(VBoxGuestFacilityType enmFacility) +{ + Assert(enmFacility < INT32_MAX); + FacilityMapIterConst it = mData.mFacilityMap.find((AdditionsFacilityType_T)enmFacility); + if (it != mData.mFacilityMap.end()) + { + AdditionsFacility *pFac = it->second; + return (pFac->i_getStatus() == AdditionsFacilityStatus_Active); + } + return false; +} + +bool Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus, + uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + AssertReturn( a_enmFacility < VBoxGuestFacilityType_All + && a_enmFacility > VBoxGuestFacilityType_Unknown, false); + + bool fChanged; + FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility); + if (it != mData.mFacilityMap.end()) + { + AdditionsFacility *pFac = it->second; + fChanged = pFac->i_update((AdditionsFacilityStatus_T)a_enmStatus, a_fFlags, a_pTimeSpecTS); + } + else + { + if (mData.mFacilityMap.size() > 64) + { + /* The easy way out for now. We could automatically destroy + inactive facilities like VMMDev does if we like... */ + AssertFailedReturn(false); + } + + ComObjPtr<AdditionsFacility> ptrFac; + HRESULT hrc = ptrFac.createObject(); + AssertComRCReturn(hrc, false); + Assert(ptrFac); + + hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus, + a_fFlags, a_pTimeSpecTS); + AssertComRCReturn(hrc, false); + try + { + mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac)); + fChanged = true; + } + catch (std::bad_alloc &) + { + fChanged = false; + } + } + return fChanged; +} + +/** + * Issued by the guest when a guest user changed its state. + * + * @return IPRT status code. + * @param aUser Guest user name. + * @param aDomain Domain of guest user account. Optional. + * @param enmState New state to indicate. + * @param pbDetails Pointer to state details. Optional. + * @param cbDetails Size (in bytes) of state details. Pass 0 if not used. + */ +void Guest::i_onUserStateChanged(const Utf8Str &aUser, const Utf8Str &aDomain, VBoxGuestUserState enmState, + const uint8_t *pbDetails, uint32_t cbDetails) +{ + RT_NOREF(pbDetails, cbDetails); + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + Utf8Str strDetails; /** @todo Implement state details here. */ + + ::FireGuestUserStateChangedEvent(mEventSource, aUser, aDomain, (GuestUserState_T)enmState, strDetails); + LogFlowFuncLeave(); +} + +/** + * Sets the status of a certain Guest Additions facility. + * + * Gets called by vmmdevUpdateGuestStatus, which just passes the report along. + * + * @param a_enmFacility The facility. + * @param a_enmStatus The status. + * @param a_fFlags Flags assoicated with the update. Currently + * reserved and should be ignored. + * @param a_pTimeSpecTS Pointer to the timestamp of this report. + * @sa PDMIVMMDEVCONNECTOR::pfnUpdateGuestStatus, vmmdevUpdateGuestStatus + * @thread The emulation thread. + */ +void Guest::i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus, + uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + Assert( a_enmFacility > VBoxGuestFacilityType_Unknown + && a_enmFacility <= VBoxGuestFacilityType_All); /* Paranoia, VMMDev checks for this. */ + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Set a specific facility status. + */ + bool fFireEvent = false; + if (a_enmFacility == VBoxGuestFacilityType_All) + for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it) + fFireEvent |= i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS); + else /* Update one facility only. */ + fFireEvent = i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS); + + /* + * Recalc the runlevel. + */ + AdditionsRunLevelType_T const enmOldRunLevel = mData.mAdditionsRunLevel; + if (i_facilityIsActive(VBoxGuestFacilityType_VBoxTrayClient)) + mData.mAdditionsRunLevel = AdditionsRunLevelType_Desktop; + else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxService)) + mData.mAdditionsRunLevel = AdditionsRunLevelType_Userland; + else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver)) + mData.mAdditionsRunLevel = AdditionsRunLevelType_System; + else + mData.mAdditionsRunLevel = AdditionsRunLevelType_None; + + /* + * Fire event if something actually changed. + */ + AdditionsRunLevelType_T const enmNewRunLevel = mData.mAdditionsRunLevel; + if (fFireEvent || enmNewRunLevel != enmOldRunLevel) + { + alock.release(); + ::FireGuestAdditionsStatusChangedEvent(mEventSource, (AdditionsFacilityType_T)a_enmFacility, + (AdditionsFacilityStatus_T)a_enmStatus, enmNewRunLevel, + RTTimeSpecGetMilli(a_pTimeSpecTS)); + } +} + +/** + * Sets the supported features (and whether they are active or not). + * + * @param aCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX). + */ +void Guest::i_setSupportedFeatures(uint32_t aCaps) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /** @todo A nit: The timestamp is wrong on saved state restore. Would be better + * to move the graphics and seamless capability -> facility translation to + * VMMDev so this could be saved. */ + RTTIMESPEC TimeSpecTS; + RTTimeNow(&TimeSpecTS); + + bool fFireEvent = i_facilityUpdate(VBoxGuestFacilityType_Seamless, + aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS + ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive, + 0 /*fFlags*/, &TimeSpecTS); + /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */ + + /* + * Fire event if the state actually changed. + */ + if (fFireEvent) + { + AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel; + alock.release(); + ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_Seamless, + aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS + ? AdditionsFacilityStatus_Active : AdditionsFacilityStatus_Inactive, + enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS)); + } +} diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp new file mode 100644 index 00000000..e2befb3f --- /dev/null +++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp @@ -0,0 +1,3031 @@ +/* $Id: GuestProcessImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest process handling. + */ + +/* + * Copyright (C) 2012-2022 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 + */ + +/** + * Locking rules: + * - When the main dispatcher (callbackDispatcher) is called it takes the + * WriteLock while dispatching to the various on* methods. + * - All other outer functions (accessible by Main) must not own a lock + * while waiting for a callback or for an event. + * - Only keep Read/WriteLocks as short as possible and only when necessary. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTPROCESS +#include "LoggingNew.h" + +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestImpl.h" +#include "GuestProcessImpl.h" +#include "GuestSessionImpl.h" +#include "GuestCtrlImplPrivate.h" +#include "ConsoleImpl.h" +#include "VirtualBoxErrorInfoImpl.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "VBoxEvents.h" +#include "ThreadTask.h" + +#include <memory> /* For auto_ptr. */ + +#include <iprt/asm.h> +#include <iprt/cpp/utils.h> /* For unconst(). */ +#include <iprt/getopt.h> + +#include <VBox/com/listeners.h> + +#include <VBox/com/array.h> + + +/** + * Base class for all guest process tasks. + */ +class GuestProcessTask : public ThreadTask +{ +public: + + GuestProcessTask(GuestProcess *pProcess) + : ThreadTask("GenericGuestProcessTask") + , mProcess(pProcess) + , mRC(VINF_SUCCESS) { } + + virtual ~GuestProcessTask(void) { } + + /** Returns the last set result code. */ + int i_rc(void) const { return mRC; } + /** Returns whether the last set result is okay (successful) or not. */ + bool i_isOk(void) const { return RT_SUCCESS(mRC); } + /** Returns the reference of the belonging progress object. */ + const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; } + +protected: + + /** Progress object this process belongs to. */ + const ComObjPtr<GuestProcess> mProcess; + /** Last set result code. */ + int mRC; +}; + +/** + * Task to start a process on the guest. + */ +class GuestProcessStartTask : public GuestProcessTask +{ +public: + + GuestProcessStartTask(GuestProcess *pProcess) + : GuestProcessTask(pProcess) + { + m_strTaskName = "gctlPrcStart"; + } + + void handler() + { + GuestProcess::i_startProcessThreadTask(this); + } +}; + +/** + * Internal listener class to serve events in an + * active manner, e.g. without polling delays. + */ +class GuestProcessListener +{ +public: + + GuestProcessListener(void) + { + } + + virtual ~GuestProcessListener(void) + { + } + + HRESULT init(GuestProcess *pProcess) + { + AssertPtrReturn(pProcess, E_POINTER); + mProcess = pProcess; + return S_OK; + } + + void uninit(void) + { + mProcess = NULL; + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) + { + switch (aType) + { + case VBoxEventType_OnGuestProcessStateChanged: + case VBoxEventType_OnGuestProcessInputNotify: + case VBoxEventType_OnGuestProcessOutput: + { + AssertPtrReturn(mProcess, E_POINTER); + int vrc2 = mProcess->signalWaitEvent(aType, aEvent); + RT_NOREF(vrc2); +#ifdef LOG_ENABLED + LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in vrc=%Rrc\n", + aType, &mProcess, vrc2)); +#endif + break; + } + + default: + AssertMsgFailed(("Unhandled event %RU32\n", aType)); + break; + } + + return S_OK; + } + +private: + + GuestProcess *mProcess; +}; +typedef ListenerImpl<GuestProcessListener, GuestProcess*> GuestProcessListenerImpl; + +VBOX_LISTENER_DECLARE(GuestProcessListenerImpl) + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestProcess) + +HRESULT GuestProcess::FinalConstruct(void) +{ + LogFlowThisFuncEnter(); + return BaseFinalConstruct(); +} + +void GuestProcess::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initialies a guest process object. + * + * @returns VBox status code. + * @param aConsole Console this process is bound to. + * @param aSession Guest session this process is bound to. + * @param aObjectID Object ID to use for this process object. + * @param aProcInfo Process startup information to use. + * @param pBaseEnv Guest environment to apply when starting the process on the guest. + */ +int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aObjectID, + const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv) +{ + LogFlowThisFunc(("aConsole=%p, aSession=%p, aObjectID=%RU32, pBaseEnv=%p\n", + aConsole, aSession, aObjectID, pBaseEnv)); + + AssertPtrReturn(aConsole, VERR_INVALID_POINTER); + AssertPtrReturn(aSession, VERR_INVALID_POINTER); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); + + HRESULT hr; + + int vrc = bindToSession(aConsole, aSession, aObjectID); + if (RT_SUCCESS(vrc)) + { + hr = unconst(mEventSource).createObject(); + if (FAILED(hr)) + vrc = VERR_NO_MEMORY; + else + { + hr = mEventSource->init(); + if (FAILED(hr)) + vrc = VERR_COM_UNEXPECTED; + } + } + + if (RT_SUCCESS(vrc)) + { + try + { + GuestProcessListener *pListener = new GuestProcessListener(); + ComObjPtr<GuestProcessListenerImpl> thisListener; + hr = thisListener.createObject(); + if (SUCCEEDED(hr)) + hr = thisListener->init(pListener, this); + + if (SUCCEEDED(hr)) + { + com::SafeArray <VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify); + eventTypes.push_back(VBoxEventType_OnGuestProcessOutput); + hr = mEventSource->RegisterListener(thisListener, + ComSafeArrayAsInParam(eventTypes), + TRUE /* Active listener */); + if (SUCCEEDED(hr)) + { + vrc = baseInit(); + if (RT_SUCCESS(vrc)) + { + mLocalListener = thisListener; + } + } + else + vrc = VERR_COM_UNEXPECTED; + } + else + vrc = VERR_COM_UNEXPECTED; + } + catch(std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + mData.mProcess = aProcInfo; + mData.mpSessionBaseEnv = pBaseEnv; + if (pBaseEnv) + pBaseEnv->retainConst(); + mData.mExitCode = 0; + mData.mPID = 0; + mData.mLastError = VINF_SUCCESS; + mData.mStatus = ProcessStatus_Undefined; + /* Everything else will be set by the actual starting routine. */ + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return vrc; + } + + autoInitSpan.setFailed(); + return vrc; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease() or IGuestSession::uninit(). + */ +void GuestProcess::uninit(void) +{ + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFunc(("mExe=%s, PID=%RU32\n", mData.mProcess.mExecutable.c_str(), mData.mPID)); + + if (mData.mpSessionBaseEnv) + { + mData.mpSessionBaseEnv->releaseConst(); + mData.mpSessionBaseEnv = NULL; + } + + baseUninit(); + + LogFlowFuncLeave(); +} + +// implementation of public getters/setters for attributes +///////////////////////////////////////////////////////////////////////////// +HRESULT GuestProcess::getArguments(std::vector<com::Utf8Str> &aArguments) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aArguments = mData.mProcess.mArguments; + return S_OK; +} + +HRESULT GuestProcess::getEnvironment(std::vector<com::Utf8Str> &aEnvironment) +{ +#ifndef VBOX_WITH_GUEST_CONTROL + ReturnComNotImplemented(); +#else + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* (Paranoia since both environment objects are immutable.) */ + HRESULT hrc; + if (mData.mpSessionBaseEnv) + { + int vrc; + if (mData.mProcess.mEnvironmentChanges.count() == 0) + vrc = mData.mpSessionBaseEnv->queryPutEnvArray(&aEnvironment); + else + { + GuestEnvironment TmpEnv; + vrc = TmpEnv.copy(*mData.mpSessionBaseEnv); + if (RT_SUCCESS(vrc)) + { + vrc = TmpEnv.applyChanges(mData.mProcess.mEnvironmentChanges); + if (RT_SUCCESS(vrc)) + vrc = TmpEnv.queryPutEnvArray(&aEnvironment); + } + } + hrc = Global::vboxStatusCodeToCOM(vrc); + } + else + hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by installed Guest Additions")); + LogFlowThisFuncLeave(); + return hrc; +#endif +} + +HRESULT GuestProcess::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + LogFlowThisFuncEnter(); + + // no need to lock - lifetime constant + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestProcess::getExecutablePath(com::Utf8Str &aExecutablePath) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aExecutablePath = mData.mProcess.mExecutable; + + return S_OK; +} + +HRESULT GuestProcess::getExitCode(LONG *aExitCode) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aExitCode = mData.mExitCode; + + return S_OK; +} + +HRESULT GuestProcess::getName(com::Utf8Str &aName) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aName = mData.mProcess.mName; + + return S_OK; +} + +HRESULT GuestProcess::getPID(ULONG *aPID) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aPID = mData.mPID; + + return S_OK; +} + +HRESULT GuestProcess::getStatus(ProcessStatus_T *aStatus) +{ + LogFlowThisFuncEnter(); + + *aStatus = i_getStatus(); + + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Entry point for guest side process callbacks. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCb Host callback data. + */ +int GuestProcess::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); +#ifdef DEBUG + LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uMessage=%RU32, pSvcCb=%p\n", + mData.mPID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb)); +#endif + + int vrc; + switch (pCbCtx->uMessage) + { + case GUEST_MSG_DISCONNECTED: + { + vrc = i_onGuestDisconnected(pCbCtx, pSvcCb); + break; + } + + case GUEST_MSG_EXEC_STATUS: + { + vrc = i_onProcessStatusChange(pCbCtx, pSvcCb); + break; + } + + case GUEST_MSG_EXEC_OUTPUT: + { + vrc = i_onProcessOutput(pCbCtx, pSvcCb); + break; + } + + case GUEST_MSG_EXEC_INPUT_STATUS: + { + vrc = i_onProcessInputStatus(pCbCtx, pSvcCb); + break; + } + + default: + /* Silently ignore not implemented functions. */ + vrc = VERR_NOT_SUPPORTED; + break; + } + +#ifdef DEBUG + LogFlowFuncLeaveRC(vrc); +#endif + return vrc; +} + +/** + * Checks if the current assigned PID matches another PID (from a callback). + * + * In protocol v1 we don't have the possibility to terminate/kill + * processes so it can happen that a formerly started process A + * (which has the context ID 0 (session=0, process=0, count=0) will + * send a delayed message to the host if this process has already + * been discarded there and the same context ID was reused by + * a process B. Process B in turn then has a different guest PID. + * + * Note: This also can happen when restoring from a saved state which + * had a guest process running. + * + * @return IPRT status code. + * @param uPID PID to check. + */ +inline int GuestProcess::i_checkPID(uint32_t uPID) +{ + int vrc = VINF_SUCCESS; + + /* Was there a PID assigned yet? */ + if (mData.mPID) + { + if (RT_UNLIKELY(mData.mPID != uPID)) + { + LogFlowFunc(("Stale guest process (PID=%RU32) sent data to a newly started process (pProcesS=%p, PID=%RU32, status=%RU32)\n", + uPID, this, mData.mPID, mData.mStatus)); + vrc = VERR_NOT_FOUND; + } + } + + return vrc; +} + +/** + * Returns the current process status. + * + * @returns Current process status. + * + * @note Takes the read lock. + */ +ProcessStatus_T GuestProcess::i_getStatus(void) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return mData.mStatus; +} + +/** + * Converts a given guest process error to a string. + * + * @returns Error as a string. + * @param rcGuest Guest process error to return string for. + * @param pcszWhat Hint of what was involved when the error occurred. + */ +/* static */ +Utf8Str GuestProcess::i_guestErrorToString(int rcGuest, const char *pcszWhat) +{ + AssertPtrReturn(pcszWhat, ""); + + Utf8Str strErr; + switch (rcGuest) + { +#define CASE_MSG(a_iRc, ...) \ + case a_iRc: strErr.printf(__VA_ARGS__); break; + + CASE_MSG(VERR_FILE_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat); /* This is the most likely error. */ + CASE_MSG(VERR_PATH_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat); + CASE_MSG(VERR_INVALID_VM_HANDLE, tr("VMM device is not available (is the VM running?)")); + CASE_MSG(VERR_HGCM_SERVICE_NOT_FOUND, tr("The guest execution service is not available")); + CASE_MSG(VERR_BAD_EXE_FORMAT, tr("The file \"%s\" is not an executable format on guest"), pcszWhat); + CASE_MSG(VERR_AUTHENTICATION_FAILURE, tr("The user \"%s\" was not able to logon on guest"), pcszWhat); + CASE_MSG(VERR_INVALID_NAME, tr("The file \"%s\" is an invalid name"), pcszWhat); + CASE_MSG(VERR_TIMEOUT, tr("The guest did not respond within time")); + CASE_MSG(VERR_CANCELLED, tr("The execution operation for \"%s\" was canceled"), pcszWhat); + CASE_MSG(VERR_GSTCTL_MAX_CID_OBJECTS_REACHED, tr("Maximum number of concurrent guest processes has been reached")); + CASE_MSG(VERR_NOT_FOUND, tr("The guest execution service is not ready (yet)")); + default: + strErr.printf(tr("Error %Rrc for guest process \"%s\" occurred\n"), rcGuest, pcszWhat); + break; +#undef CASE_MSG + } + + return strErr; +} + +/** + * Translates a process status to a human readable string. + * + * @returns Process status as a string. + * @param enmStatus Guest process status to return string for. + */ +/* static */ +Utf8Str GuestProcess::i_statusToString(ProcessStatus_T enmStatus) +{ + switch (enmStatus) + { + case ProcessStatus_Starting: + return "starting"; + case ProcessStatus_Started: + return "started"; + case ProcessStatus_Paused: + return "paused"; + case ProcessStatus_Terminating: + return "terminating"; + case ProcessStatus_TerminatedNormally: + return "successfully terminated"; + case ProcessStatus_TerminatedSignal: + return "terminated by signal"; + case ProcessStatus_TerminatedAbnormally: + return "abnormally aborted"; + case ProcessStatus_TimedOutKilled: + return "timed out"; + case ProcessStatus_TimedOutAbnormally: + return "timed out, hanging"; + case ProcessStatus_Down: + return "killed"; + case ProcessStatus_Error: + return "error"; + default: + break; + } + + AssertFailed(); /* Should never happen! */ + return "unknown"; +} + +/** + * Returns @c true if the passed in error code indicates an error which came + * from the guest side, or @c false if not. + * + * @return bool @c true if the passed in error code indicates an error which came + * from the guest side, or @c false if not. + * @param rc Error code to check. + */ +/* static */ +bool GuestProcess::i_isGuestError(int rc) +{ + return ( rc == VERR_GSTCTL_GUEST_ERROR + || rc == VERR_GSTCTL_PROCESS_EXIT_CODE); +} + +/** + * Returns whether the guest process is alive (i.e. running) or not. + * + * @returns \c true if alive and running, or \c false if not. + */ +inline bool GuestProcess::i_isAlive(void) +{ + return ( mData.mStatus == ProcessStatus_Started + || mData.mStatus == ProcessStatus_Paused + || mData.mStatus == ProcessStatus_Terminating); +} + +/** + * Returns whether the guest process has ended (i.e. terminated) or not. + * + * @returns \c true if ended, or \c false if not. + */ +inline bool GuestProcess::i_hasEnded(void) +{ + return ( mData.mStatus == ProcessStatus_TerminatedNormally + || mData.mStatus == ProcessStatus_TerminatedSignal + || mData.mStatus == ProcessStatus_TerminatedAbnormally + || mData.mStatus == ProcessStatus_TimedOutKilled + || mData.mStatus == ProcessStatus_TimedOutAbnormally + || mData.mStatus == ProcessStatus_Down + || mData.mStatus == ProcessStatus_Error); +} + +/** + * Called when the guest side of the process has been disconnected (closed, terminated, +++). + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + */ +int GuestProcess::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + int vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sets (reports) the current input status of the guest process. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + * + * @note Takes the write lock. + */ +int GuestProcess::i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + /* pCallback is optional. */ + + if (pSvcCbData->mParms < 5) + return VERR_INVALID_PARAMETER; + + CALLBACKDATA_PROC_INPUT dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[4], &dataCb.uProcessed); + AssertRCReturn(vrc, vrc); + + LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n", + dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed)); + + vrc = i_checkPID(dataCb.uPID); + if (RT_SUCCESS(vrc)) + { + ProcessInputStatus_T inputStatus = ProcessInputStatus_Undefined; + switch (dataCb.uStatus) + { + case INPUT_STS_WRITTEN: + inputStatus = ProcessInputStatus_Written; + break; + case INPUT_STS_ERROR: + inputStatus = ProcessInputStatus_Broken; + break; + case INPUT_STS_TERMINATED: + inputStatus = ProcessInputStatus_Broken; + break; + case INPUT_STS_OVERFLOW: + inputStatus = ProcessInputStatus_Overflow; + break; + case INPUT_STS_UNDEFINED: + /* Fall through is intentional. */ + default: + AssertMsg(!dataCb.uProcessed, ("Processed data is not 0 in undefined input state\n")); + break; + } + + if (inputStatus != ProcessInputStatus_Undefined) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Copy over necessary data before releasing lock again. */ + uint32_t uPID = mData.mPID; + /** @todo Also handle mSession? */ + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestProcessInputNotifyEvent(mEventSource, mSession, this, uPID, 0 /* StdIn */, dataCb.uProcessed, inputStatus); + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Notifies of an I/O operation of the guest process. + * + * @returns VERR_NOT_IMPLEMENTED -- not implemented yet. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + */ +int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + return VERR_NOT_IMPLEMENTED; +} + +/** + * Sets (reports) the current running status of the guest process. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + * + * @note Takes the write lock. + */ +int GuestProcess::i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + if (pSvcCbData->mParms < 5) + return VERR_INVALID_PARAMETER; + + CALLBACKDATA_PROC_STATUS dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData); + AssertRCReturn(vrc, vrc); + + LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n", + dataCb.uPID, dataCb.uStatus, dataCb.uFlags)); + + vrc = i_checkPID(dataCb.uPID); + if (RT_SUCCESS(vrc)) + { + ProcessStatus_T procStatus = ProcessStatus_Undefined; + int procRc = VINF_SUCCESS; + + switch (dataCb.uStatus) + { + case PROC_STS_STARTED: + { + procStatus = ProcessStatus_Started; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mPID = dataCb.uPID; /* Set the process PID. */ + break; + } + + case PROC_STS_TEN: + { + procStatus = ProcessStatus_TerminatedNormally; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mExitCode = dataCb.uFlags; /* Contains the exit code. */ + break; + } + + case PROC_STS_TES: + { + procStatus = ProcessStatus_TerminatedSignal; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mExitCode = dataCb.uFlags; /* Contains the signal. */ + break; + } + + case PROC_STS_TEA: + { + procStatus = ProcessStatus_TerminatedAbnormally; + break; + } + + case PROC_STS_TOK: + { + procStatus = ProcessStatus_TimedOutKilled; + break; + } + + case PROC_STS_TOA: + { + procStatus = ProcessStatus_TimedOutAbnormally; + break; + } + + case PROC_STS_DWN: + { + procStatus = ProcessStatus_Down; + break; + } + + case PROC_STS_ERROR: + { + procRc = dataCb.uFlags; /* mFlags contains the IPRT error sent from the guest. */ + procStatus = ProcessStatus_Error; + break; + } + + case PROC_STS_UNDEFINED: + default: + { + /* Silently skip this request. */ + procStatus = ProcessStatus_Undefined; + break; + } + } + + LogFlowThisFunc(("Got rc=%Rrc, procSts=%RU32, procRc=%Rrc\n", + vrc, procStatus, procRc)); + + /* Set the process status. */ + int vrc2 = i_setProcessStatus(procStatus, procRc); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sets (reports) the current output status of the guest process. + * + * @returns VBox status code. + * @param pCbCtx Host callback context. + * @param pSvcCbData Host callback data. + */ +int GuestProcess::i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + RT_NOREF(pCbCtx); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + if (pSvcCbData->mParms < 5) + return VERR_INVALID_PARAMETER; + + CALLBACKDATA_PROC_OUTPUT dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uHandle); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData); + AssertRCReturn(vrc, vrc); + + LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n", + dataCb.uPID, dataCb.uHandle, dataCb.uFlags, dataCb.pvData, dataCb.cbData)); + + vrc = i_checkPID(dataCb.uPID); + if (RT_SUCCESS(vrc)) + { + com::SafeArray<BYTE> data((size_t)dataCb.cbData); + if (dataCb.cbData) + data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData); + + ::FireGuestProcessOutputEvent(mEventSource, mSession, this, + mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc GuestObject::i_onUnregister + */ +int GuestProcess::i_onUnregister(void) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VINF_SUCCESS; + + /* + * Note: The event source stuff holds references to this object, + * so make sure that this is cleaned up *before* calling uninit(). + */ + if (!mEventSource.isNull()) + { + mEventSource->UnregisterListener(mLocalListener); + + mLocalListener.setNull(); + unconst(mEventSource).setNull(); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc GuestObject::i_onSessionStatusChange + */ +int GuestProcess::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus) +{ + LogFlowThisFuncEnter(); + + int vrc = VINF_SUCCESS; + + /* If the session now is in a terminated state, set the process status + * to "down", as there is not much else we can do now. */ + if (GuestSession::i_isTerminated(enmSessionStatus)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + vrc = i_setProcessStatus(ProcessStatus_Down, 0 /* rc, ignored */); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Reads data from a guest file. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uHandle Internal file handle to use for reading. + * @param uSize Size (in bytes) to read. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Where to store the read data on success. + * @param cbData Size (in bytes) of \a pvData on input. + * @param pcbRead Where to return to size (in bytes) read on success. + * Optional. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestProcess::i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS, + void *pvData, size_t cbData, uint32_t *pcbRead, int *prcGuest) +{ + LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, prcGuest=%p\n", + mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, prcGuest)); + AssertReturn(uSize, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( mData.mStatus != ProcessStatus_Started + /* Skip reading if the process wasn't started with the appropriate + * flags. */ + || ( ( uHandle == GUEST_PROC_OUT_H_STDOUT + || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED) + && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut)) + || ( uHandle == GUEST_PROC_OUT_H_STDERR + && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr)) + ) + { + if (pcbRead) + *pcbRead = 0; + if (prcGuest) + *prcGuest = VINF_SUCCESS; + return VINF_SUCCESS; /* Nothing to read anymore. */ + } + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + /* + * On Guest Additions < 4.3 there is no guarantee that the process status + * change arrives *after* the output event, e.g. if this was the last output + * block being read and the process will report status "terminate". + * So just skip checking for process status change and only wait for the + * output event. + */ + if (mSession->i_getProtocolVersion() >= 2) + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestProcessOutput); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + if (RT_SUCCESS(vrc)) + { + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mData.mPID); + HGCMSvcSetU32(&paParms[i++], uHandle); + HGCMSvcSetU32(&paParms[i++], 0 /* Flags, none set yet. */); + + alock.release(); /* Drop the write lock before sending. */ + + vrc = sendMessage(HOST_MSG_EXEC_GET_OUTPUT, i, paParms); + } + + if (RT_SUCCESS(vrc)) + vrc = i_waitForOutput(pEvent, uHandle, uTimeoutMS, + pvData, cbData, pcbRead); + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sets (reports) the current (overall) status of the guest process. + * + * @returns VBox status code. + * @param procStatus Guest process status to set. + * @param procRc Guest process result code to set. + * + * @note Takes the write lock. + */ +int GuestProcess::i_setProcessStatus(ProcessStatus_T procStatus, int procRc) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, procRc=%Rrc\n", + mData.mStatus, procStatus, procRc)); + + if (procStatus == ProcessStatus_Error) + { + AssertMsg(RT_FAILURE(procRc), ("Guest rc must be an error (%Rrc)\n", procRc)); + /* Do not allow overwriting an already set error. If this happens + * this means we forgot some error checking/locking somewhere. */ + AssertMsg(RT_SUCCESS(mData.mLastError), ("Guest rc already set (to %Rrc)\n", mData.mLastError)); + } + else + AssertMsg(RT_SUCCESS(procRc), ("Guest rc must not be an error (%Rrc)\n", procRc)); + + int vrc = VINF_SUCCESS; + + if (mData.mStatus != procStatus) /* Was there a process status change? */ + { + mData.mStatus = procStatus; + mData.mLastError = procRc; + + ComObjPtr<VirtualBoxErrorInfo> errorInfo; + HRESULT hrc = errorInfo.createObject(); + ComAssertComRC(hrc); + if (RT_FAILURE(mData.mLastError)) + { + hrc = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError, + COM_IIDOF(IGuestProcess), getComponentName(), + i_guestErrorToString(mData.mLastError, mData.mProcess.mExecutable.c_str())); + ComAssertComRC(hrc); + } + + /* Copy over necessary data before releasing lock again. */ + uint32_t uPID = mData.mPID; + /** @todo Also handle mSession? */ + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestProcessStateChangedEvent(mEventSource, mSession, this, uPID, procStatus, errorInfo); +#if 0 + /* + * On Guest Additions < 4.3 there is no guarantee that outstanding + * requests will be delivered to the host after the process has ended, + * so just cancel all waiting events here to not let clients run + * into timeouts. + */ + if ( mSession->getProtocolVersion() < 2 + && hasEnded()) + { + LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n")); + vrc = cancelWaitEvents(); + } +#endif + } + + return vrc; +} + +/** + * Starts the process on the guest. + * + * @returns VBox status code. + * @param cMsTimeout Timeout (in ms) to wait for starting the process. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestProcess::i_startProcess(uint32_t cMsTimeout, int *prcGuest) +{ + LogFlowThisFunc(("cMsTimeout=%RU32, procExe=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n", + cMsTimeout, mData.mProcess.mExecutable.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags, + mSession->i_getId())); + + /* Wait until the caller function (if kicked off by a thread) + * has returned and continue operation. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mStatus = ProcessStatus_Starting; + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + if (RT_FAILURE(vrc)) + return vrc; + + vrc = i_startProcessInner(cMsTimeout, alock, pEvent, prcGuest); + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Helper function to start a process on the guest. Do not call directly! + * + * @returns VBox status code. + * @param cMsTimeout Timeout (in ms) to wait for starting the process. + * @param rLock Write lock to use for serialization. + * @param pEvent Event to use for notifying waiters. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestProcess::i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *prcGuest) +{ + GuestSession *pSession = mSession; + AssertPtr(pSession); + uint32_t const uProtocol = pSession->i_getProtocolVersion(); + + const GuestCredentials &sessionCreds = pSession->i_getCredentials(); + + /* Prepare arguments. */ + size_t cArgs = mData.mProcess.mArguments.size(); + if (cArgs >= 128*1024) + return VERR_BUFFER_OVERFLOW; + + size_t cbArgs = 0; + char *pszArgs = NULL; + int vrc = VINF_SUCCESS; + if (cArgs) + { + char const **papszArgv = (char const **)RTMemAlloc((cArgs + 1) * sizeof(papszArgv[0])); + AssertReturn(papszArgv, VERR_NO_MEMORY); + + for (size_t i = 0; i < cArgs; i++) + { + papszArgv[i] = mData.mProcess.mArguments[i].c_str(); + AssertPtr(papszArgv[i]); + } + papszArgv[cArgs] = NULL; + + Guest *pGuest = mSession->i_getParent(); + AssertPtr(pGuest); + + const uint64_t fGuestControlFeatures0 = pGuest->i_getGuestControlFeatures0(); + + /* If the Guest Additions don't support using argv[0] correctly (< 6.1.x), don't supply it. */ + if (!(fGuestControlFeatures0 & VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0)) + vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + else /* ... else send the whole argv, including argv[0]. */ + vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + + RTMemFree(papszArgv); + if (RT_FAILURE(vrc)) + return vrc; + + /* Note! No direct returns after this. */ + } + + /* Calculate arguments size (in bytes). */ + AssertPtr(pszArgs); + cbArgs = strlen(pszArgs) + 1; /* Include terminating zero. */ + + /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */ + size_t cbEnvBlock = 0; /* Shut up MSVC. */ + char *pszzEnvBlock = NULL; /* Ditto. */ + vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock); + if (RT_SUCCESS(vrc)) + { + Assert(cbEnvBlock > 0); + cbEnvBlock--; + AssertPtr(pszzEnvBlock); + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[16]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetRTCStr(&paParms[i++], mData.mProcess.mExecutable); + HGCMSvcSetU32(&paParms[i++], mData.mProcess.mFlags); + HGCMSvcSetU32(&paParms[i++], (uint32_t)mData.mProcess.mArguments.size()); + HGCMSvcSetPv(&paParms[i++], pszArgs, (uint32_t)cbArgs); + HGCMSvcSetU32(&paParms[i++], mData.mProcess.mEnvironmentChanges.count()); + HGCMSvcSetU32(&paParms[i++], (uint32_t)cbEnvBlock); + HGCMSvcSetPv(&paParms[i++], pszzEnvBlock, (uint32_t)cbEnvBlock); + if (uProtocol < 2) + { + /* In protocol v1 (VBox < 4.3) the credentials were part of the execution + * call. In newer protocols these credentials are part of the opened guest + * session, so not needed anymore here. */ + HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mUser); + HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mPassword); + } + /* + * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout + * until the process was started - the process itself then gets an infinite timeout for execution. + * This is handy when we want to start a process inside a worker thread within a certain timeout + * but let the started process perform lengthly operations then. + */ + if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly) + HGCMSvcSetU32(&paParms[i++], UINT32_MAX /* Infinite timeout */); + else + HGCMSvcSetU32(&paParms[i++], mData.mProcess.mTimeoutMS); + if (uProtocol >= 2) + { + HGCMSvcSetU32(&paParms[i++], mData.mProcess.mPriority); + /* CPU affinity: We only support one CPU affinity block at the moment, + * so that makes up to 64 CPUs total. This can be more in the future. */ + HGCMSvcSetU32(&paParms[i++], 1); + /* The actual CPU affinity blocks. */ + HGCMSvcSetPv(&paParms[i++], (void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity)); + } + + rLock.release(); /* Drop the write lock before sending. */ + + vrc = sendMessage(HOST_MSG_EXEC_CMD, i, paParms); + if (RT_FAILURE(vrc)) + { + int vrc2 = i_setProcessStatus(ProcessStatus_Error, vrc); + AssertRC(vrc2); + } + + mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock); + } + + RTStrFree(pszArgs); + + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, cMsTimeout, + NULL /* Process status */, prcGuest); + return vrc; +} + +/** + * Starts the process asynchronously (via worker thread) on the guest. + * + * @returns VBox status code. + */ +int GuestProcess::i_startProcessAsync(void) +{ + LogFlowThisFuncEnter(); + + /* Create the task: */ + GuestProcessStartTask *pTask = NULL; + try + { + pTask = new GuestProcessStartTask(this); + } + catch (std::bad_alloc &) + { + LogFlowThisFunc(("out of memory\n")); + return VERR_NO_MEMORY; + } + AssertReturnStmt(pTask->i_isOk(), delete pTask, E_FAIL); /* cannot fail for GuestProcessStartTask. */ + LogFlowThisFunc(("Successfully created GuestProcessStartTask object\n")); + + /* Start the thread (always consumes the task): */ + HRESULT hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + return VINF_SUCCESS; + LogFlowThisFunc(("Failed to create thread for GuestProcessStartTask\n")); + return VERR_GENERAL_FAILURE; +} + +/** + * Thread task which does the asynchronous starting of a guest process. + * + * @returns VBox status code. + * @param pTask Process start task (context) to process. + */ +/* static */ +int GuestProcess::i_startProcessThreadTask(GuestProcessStartTask *pTask) +{ + LogFlowFunc(("pTask=%p\n", pTask)); + + const ComObjPtr<GuestProcess> pProcess(pTask->i_process()); + Assert(!pProcess.isNull()); + + AutoCaller autoCaller(pProcess); + if (FAILED(autoCaller.rc())) + return VERR_COM_UNEXPECTED; + + int vrc = pProcess->i_startProcess(30 * 1000 /* 30s timeout */, NULL /* Guest rc, ignored */); + /* Nothing to do here anymore. */ + + LogFlowFunc(("pProcess=%p, vrc=%Rrc\n", (GuestProcess *)pProcess, vrc)); + return vrc; +} + +/** + * Terminates a guest process. + * + * @returns VBox status code. + * @retval VWRN_INVALID_STATE if process not in running state (anymore). + * @retval VERR_NOT_SUPPORTED if process termination is not supported on the guest. + * @param uTimeoutMS Timeout (in ms) to wait for process termination. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestProcess::i_terminateProcess(uint32_t uTimeoutMS, int *prcGuest) +{ + /* prcGuest is optional. */ + LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VINF_SUCCESS; + + if (mData.mStatus != ProcessStatus_Started) + { + LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n", + mData.mStatus)); + vrc = VWRN_INVALID_STATE; + } + else + { + AssertPtr(mSession); + /* Note: VBox < 4.3 (aka protocol version 1) does not + * support this, so just skip. */ + if (mSession->i_getProtocolVersion() < 2) + vrc = VERR_NOT_SUPPORTED; + + if (RT_SUCCESS(vrc)) + { + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mData.mPID); + + alock.release(); /* Drop the write lock before sending. */ + + vrc = sendMessage(HOST_MSG_EXEC_TERMINATE, i, paParms); + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, uTimeoutMS, + NULL /* ProcessStatus */, prcGuest); + unregisterWaitEvent(pEvent); + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Converts given process status / flags and wait flag combination + * to an overall process wait result. + * + * @returns Overall process wait result. + * @param fWaitFlags Process wait flags to use for conversion. + * @param oldStatus Old process status to use for conversion. + * @param newStatus New process status to use for conversion. + * @param uProcFlags Process flags to use for conversion. + * @param uProtocol Guest Control protocol version to use for conversion. + */ +/* static */ +ProcessWaitResult_T GuestProcess::i_waitFlagsToResultEx(uint32_t fWaitFlags, + ProcessStatus_T oldStatus, ProcessStatus_T newStatus, + uint32_t uProcFlags, uint32_t uProtocol) +{ + ProcessWaitResult_T waitResult = ProcessWaitResult_None; + + switch (newStatus) + { + case ProcessStatus_TerminatedNormally: + case ProcessStatus_TerminatedSignal: + case ProcessStatus_TerminatedAbnormally: + case ProcessStatus_Down: + /* Nothing to wait for anymore. */ + waitResult = ProcessWaitResult_Terminate; + break; + + case ProcessStatus_TimedOutKilled: + case ProcessStatus_TimedOutAbnormally: + /* Dito. */ + waitResult = ProcessWaitResult_Timeout; + break; + + case ProcessStatus_Started: + switch (oldStatus) + { + case ProcessStatus_Undefined: + case ProcessStatus_Starting: + /* Also wait for process start. */ + if (fWaitFlags & ProcessWaitForFlag_Start) + waitResult = ProcessWaitResult_Start; + else + { + /* + * If ProcessCreateFlag_WaitForProcessStartOnly was specified on process creation the + * caller is not interested in getting further process statuses -- so just don't notify + * anything here anymore and return. + */ + if (uProcFlags & ProcessCreateFlag_WaitForProcessStartOnly) + waitResult = ProcessWaitResult_Start; + } + break; + + case ProcessStatus_Started: + /* Only wait for process start. */ + if (fWaitFlags & ProcessWaitForFlag_Start) + waitResult = ProcessWaitResult_Start; + break; + + default: + AssertMsgFailed(("Unhandled old status %RU32 before new status 'started'\n", + oldStatus)); + if (fWaitFlags & ProcessWaitForFlag_Start) + waitResult = ProcessWaitResult_Start; + break; + } + break; + + case ProcessStatus_Error: + /* Nothing to wait for anymore. */ + waitResult = ProcessWaitResult_Error; + break; + + case ProcessStatus_Undefined: + case ProcessStatus_Starting: + case ProcessStatus_Terminating: + case ProcessStatus_Paused: + /* No result available yet, leave wait + * flags untouched. */ + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case ProcessStatus_32BitHack: AssertFailedBreak(); /* (compiler warnings) */ +#endif + } + + if (newStatus == ProcessStatus_Started) + { + /* + * Filter out waits which are *not* supported using + * older guest control Guest Additions. + * + */ + /** @todo ProcessWaitForFlag_Std* flags are not implemented yet. */ + if (uProtocol < 99) /* See @todo above. */ + { + if ( waitResult == ProcessWaitResult_None + /* We don't support waiting for stdin, out + err, + * just skip waiting then. */ + && ( (fWaitFlags & ProcessWaitForFlag_StdIn) + || (fWaitFlags & ProcessWaitForFlag_StdOut) + || (fWaitFlags & ProcessWaitForFlag_StdErr) + ) + ) + { + /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */ + waitResult = ProcessWaitResult_WaitFlagNotSupported; + } + } + } + +#ifdef DEBUG + LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n", + oldStatus, newStatus, fWaitFlags, waitResult)); +#endif + return waitResult; +} + +/** + * Converts given wait flags to an overall process wait result. + * + * @returns Overall process wait result. + * @param fWaitFlags Process wait flags to use for conversion. + */ +ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags) +{ + AssertPtr(mSession); + return GuestProcess::i_waitFlagsToResultEx(fWaitFlags, + mData.mStatus /* oldStatus */, mData.mStatus /* newStatus */, + mData.mProcess.mFlags, mSession->i_getProtocolVersion()); +} + +/** + * Waits for certain events of the guest process. + * + * @returns VBox status code. + * @param fWaitFlags Process wait flags to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param waitResult Where to return the process wait result on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * @note Takes the read lock. + */ +int GuestProcess::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, + ProcessWaitResult_T &waitResult, int *prcGuest) +{ + AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, prcGuest=%p\n", + fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, prcGuest)); + + /* Did some error occur before? Then skip waiting and return. */ + ProcessStatus_T curStatus = mData.mStatus; + if (curStatus == ProcessStatus_Error) + { + waitResult = ProcessWaitResult_Error; + AssertMsg(RT_FAILURE(mData.mLastError), + ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError)); + if (prcGuest) + *prcGuest = mData.mLastError; /* Return last set error. */ + LogFlowThisFunc(("Process is in error state (rcGuest=%Rrc)\n", mData.mLastError)); + return VERR_GSTCTL_GUEST_ERROR; + } + + waitResult = i_waitFlagsToResult(fWaitFlags); + + /* No waiting needed? Return immediately using the last set error. */ + if (waitResult != ProcessWaitResult_None) + { + if (prcGuest) + *prcGuest = mData.mLastError; /* Return last set error (if any). */ + LogFlowThisFunc(("Nothing to wait for (rcGuest=%Rrc)\n", mData.mLastError)); + return RT_SUCCESS(mData.mLastError) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR; + } + + /* Adjust timeout. Passing 0 means RT_INDEFINITE_WAIT. */ + if (!uTimeoutMS) + uTimeoutMS = RT_INDEFINITE_WAIT; + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + alock.release(); /* Release lock before waiting. */ + + /* + * Do the actual waiting. + */ + ProcessStatus_T newStatus = ProcessStatus_Undefined; + uint64_t u64StartMS = RTTimeMilliTS(); + for (;;) + { + uint64_t u64ElapsedMS = RTTimeMilliTS() - u64StartMS; + if ( uTimeoutMS != RT_INDEFINITE_WAIT + && u64ElapsedMS >= uTimeoutMS) + { + vrc = VERR_TIMEOUT; + break; + } + + vrc = i_waitForStatusChange(pEvent, + uTimeoutMS == RT_INDEFINITE_WAIT + ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS, + &newStatus, prcGuest); + if (RT_SUCCESS(vrc)) + { + alock.acquire(); + + waitResult = i_waitFlagsToResultEx(fWaitFlags, curStatus, newStatus, + mData.mProcess.mFlags, mSession->i_getProtocolVersion()); +#ifdef DEBUG + LogFlowThisFunc(("Got new status change: fWaitFlags=0x%x, newStatus=%RU32, waitResult=%RU32\n", + fWaitFlags, newStatus, waitResult)); +#endif + if (ProcessWaitResult_None != waitResult) /* We got a waiting result. */ + break; + } + else /* Waiting failed, bail out. */ + break; + + alock.release(); /* Don't hold lock in next waiting round. */ + } + + unregisterWaitEvent(pEvent); + + LogFlowThisFunc(("Returned waitResult=%RU32, newStatus=%RU32, rc=%Rrc\n", + waitResult, newStatus, vrc)); + return vrc; +} + +/** + * Waits for a guest process input notification. + * + * @param pEvent Wait event to use for waiting. + * @param uHandle Guest process file handle to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pInputStatus Where to return the process input status on success. + * @param pcbProcessed Where to return the processed input (in bytes) on success. + */ +int GuestProcess::i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS, + ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed) +{ + RT_NOREF(uHandle); + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestProcessInputNotify) + { + ComPtr<IGuestProcessInputNotifyEvent> pProcessEvent = pIEvent; + Assert(!pProcessEvent.isNull()); + + if (pInputStatus) + { + HRESULT hr2 = pProcessEvent->COMGETTER(Status)(pInputStatus); + ComAssertComRC(hr2); + } + if (pcbProcessed) + { + HRESULT hr2 = pProcessEvent->COMGETTER(Processed)((ULONG*)pcbProcessed); + ComAssertComRC(hr2); + } + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n", + pEvent, uHandle, vrc)); + return vrc; +} + +/** + * Waits for a guest process input notification. + * + * @returns VBox status code. + * @param pEvent Wait event to use for waiting. + * @param uHandle Guest process file handle to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pvData Where to store the guest process output on success. + * @param cbData Size (in bytes) of \a pvData. + * @param pcbRead Where to return the size (in bytes) read. + */ +int GuestProcess::i_waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS, + void *pvData, size_t cbData, uint32_t *pcbRead) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + /* pvData is optional. */ + /* cbData is optional. */ + /* pcbRead is optional. */ + + LogFlowThisFunc(("cEventTypes=%zu, pEvent=%p, uHandle=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu, pcbRead=%p\n", + pEvent->TypeCount(), pEvent, uHandle, uTimeoutMS, pvData, cbData, pcbRead)); + + int vrc; + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + do + { + vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestProcessOutput) + { + ComPtr<IGuestProcessOutputEvent> pProcessEvent = pIEvent; + Assert(!pProcessEvent.isNull()); + + ULONG uHandleEvent; + HRESULT hr = pProcessEvent->COMGETTER(Handle)(&uHandleEvent); + if ( SUCCEEDED(hr) + && uHandleEvent == uHandle) + { + if (pvData) + { + com::SafeArray <BYTE> data; + hr = pProcessEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data)); + ComAssertComRC(hr); + size_t cbRead = data.size(); + if (cbRead) + { + if (cbRead <= cbData) + { + /* Copy data from event into our buffer. */ + memcpy(pvData, data.raw(), data.size()); + } + else + vrc = VERR_BUFFER_OVERFLOW; + + LogFlowThisFunc(("Read %zu bytes (uHandle=%RU32), rc=%Rrc\n", + cbRead, uHandleEvent, vrc)); + } + } + + if ( RT_SUCCESS(vrc) + && pcbRead) + { + ULONG cbRead; + hr = pProcessEvent->COMGETTER(Processed)(&cbRead); + ComAssertComRC(hr); + *pcbRead = (uint32_t)cbRead; + } + + break; + } + else if (FAILED(hr)) + vrc = VERR_COM_UNEXPECTED; + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + } while (vrc == VINF_SUCCESS); + + if ( vrc != VINF_SUCCESS + && pcbRead) + { + *pcbRead = 0; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Waits for a guest process status change. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param pEvent Guest wait event to wait for. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pProcessStatus Where to return the process status on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. + */ +int GuestProcess::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS, + ProcessStatus_T *pProcessStatus, int *prcGuest) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + /* pProcessStatus is optional. */ + /* prcGuest is optional. */ + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, + &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + Assert(evtType == VBoxEventType_OnGuestProcessStateChanged); + ComPtr<IGuestProcessStateChangedEvent> pProcessEvent = pIEvent; + Assert(!pProcessEvent.isNull()); + + ProcessStatus_T procStatus; + HRESULT hr = pProcessEvent->COMGETTER(Status)(&procStatus); + ComAssertComRC(hr); + if (pProcessStatus) + *pProcessStatus = procStatus; + + ComPtr<IVirtualBoxErrorInfo> errorInfo; + hr = pProcessEvent->COMGETTER(Error)(errorInfo.asOutParam()); + ComAssertComRC(hr); + + LONG lGuestRc; + hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc); + ComAssertComRC(hr); + + LogFlowThisFunc(("Got procStatus=%RU32, rcGuest=%RI32 (%Rrc)\n", + procStatus, lGuestRc, lGuestRc)); + + if (RT_FAILURE((int)lGuestRc)) + vrc = VERR_GSTCTL_GUEST_ERROR; + + if (prcGuest) + *prcGuest = (int)lGuestRc; + } + /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */ + else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) + *prcGuest = pEvent->GuestResult(); + Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +#if 0 /* Unused */ +/* static */ +bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult, ProcessStatus_T procStatus, uint32_t uProtocol) +{ + RT_NOREF(uProtocol); + + bool fImplies; + + switch (waitResult) + { + case ProcessWaitResult_Start: + fImplies = procStatus == ProcessStatus_Started; + break; + + case ProcessWaitResult_Terminate: + fImplies = ( procStatus == ProcessStatus_TerminatedNormally + || procStatus == ProcessStatus_TerminatedSignal + || procStatus == ProcessStatus_TerminatedAbnormally + || procStatus == ProcessStatus_TimedOutKilled + || procStatus == ProcessStatus_TimedOutAbnormally + || procStatus == ProcessStatus_Down + || procStatus == ProcessStatus_Error); + break; + + default: + fImplies = false; + break; + } + + return fImplies; +} +#endif /* unused */ + +/** + * Writes input data to a guest process. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received. + * @param uHandle Guest process file handle to write to. + * @param uFlags Input flags of type PRocessInputFlag_XXX. + * @param pvData Data to write to the guest process. + * @param cbData Size (in bytes) of \a pvData to write. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param puWritten Where to return the size (in bytes) written. Optional. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestProcess::i_writeData(uint32_t uHandle, uint32_t uFlags, + void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *prcGuest) +{ + LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, prcGuest=%p\n", + mData.mPID, uHandle, uFlags, pvData, cbData, uTimeoutMS, puWritten, prcGuest)); + /* All is optional. There can be 0 byte writes. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mData.mStatus != ProcessStatus_Started) + { + if (puWritten) + *puWritten = 0; + if (prcGuest) + *prcGuest = VINF_SUCCESS; + return VINF_SUCCESS; /* Not available for writing (anymore). */ + } + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + /* + * On Guest Additions < 4.3 there is no guarantee that the process status + * change arrives *after* the input event, e.g. if this was the last input + * block being written and the process will report status "terminate". + * So just skip checking for process status change and only wait for the + * input event. + */ + if (mSession->i_getProtocolVersion() >= 2) + eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged); + eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify); + + vrc = registerWaitEvent(eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + VBOXHGCMSVCPARM paParms[5]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mData.mPID); + HGCMSvcSetU32(&paParms[i++], uFlags); + HGCMSvcSetPv(&paParms[i++], pvData, (uint32_t)cbData); + HGCMSvcSetU32(&paParms[i++], (uint32_t)cbData); + + alock.release(); /* Drop the write lock before sending. */ + + uint32_t cbProcessed = 0; + vrc = sendMessage(HOST_MSG_EXEC_SET_INPUT, i, paParms); + if (RT_SUCCESS(vrc)) + { + ProcessInputStatus_T inputStatus; + vrc = i_waitForInputNotify(pEvent, uHandle, uTimeoutMS, + &inputStatus, &cbProcessed); + if (RT_SUCCESS(vrc)) + { + /** @todo Set rcGuest. */ + + if (puWritten) + *puWritten = cbProcessed; + } + /** @todo Error handling. */ + } + + unregisterWaitEvent(pEvent); + + LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n", + cbProcessed, vrc)); + return vrc; +} + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestProcess::read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aToRead == 0) + return setError(E_INVALIDARG, tr("The size to read is zero")); + + LogFlowThisFuncEnter(); + + aData.resize(aToRead); + + HRESULT hrc = S_OK; + + uint32_t cbRead; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (aData.size() != cbRead) + aData.resize(cbRead); + } + else + { + aData.resize(0); + + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, + tr("Reading %RU32 bytes from guest process handle %RU32 failed: %s", "", aToRead), + aToRead, aHandle, GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from guest process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + } + + LogFlowThisFunc(("rc=%Rrc, cbRead=%RU32\n", vrc, cbRead)); + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestProcess::terminate() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */, &vrcGuest); + + switch (vrc) + { + case VINF_SUCCESS: + /* Nothing to do here, all good. */ + break; + + case VWRN_INVALID_STATE: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, VWRN_INVALID_STATE, + tr("Guest process is not in '%s' state anymore (current is in '%s')"), + GuestProcess::i_statusToString(ProcessStatus_Started).c_str(), + GuestProcess::i_statusToString(i_getStatus()).c_str()); + break; + } + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Terminating guest process failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + + case VERR_NOT_SUPPORTED: + { + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Terminating guest process \"%s\" (PID %RU32) not supported by installed Guest Additions"), + mData.mProcess.mExecutable.c_str(), mData.mPID); + break; + } + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Terminating guest process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + + /* Note: Also could be VWRN_INVALID_STATE from i_terminateProcess(). + * In such a case we have to keep the process in our list in order to fullfill any upcoming responses / requests. */ + if (vrc == VINF_SUCCESS) + { + /* Remove process from guest session list. Now only API clients + * still can hold references to it. */ + AssertPtr(mSession); + int vrc2 = mSession->i_processUnregister(this); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestProcess::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, ProcessWaitResult_T *aReason) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + /* Validate flags: */ + static ULONG const s_fValidFlags = ProcessWaitForFlag_None | ProcessWaitForFlag_Start | ProcessWaitForFlag_Terminate + | ProcessWaitForFlag_StdIn | ProcessWaitForFlag_StdOut | ProcessWaitForFlag_StdErr; + if (aWaitFor & ~s_fValidFlags) + return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"), + aWaitFor, aWaitFor & ~s_fValidFlags); + + /* + * Note: Do not hold any locks here while waiting! + */ + HRESULT hrc = S_OK; + + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + ProcessWaitResult_T waitResult; + int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + *aReason = waitResult; + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Waiting for guest process (flags %#x) failed: %s"), + aWaitFor, GuestBase::getErrorAsString(ge).c_str()); + break; + } + case VERR_TIMEOUT: + *aReason = ProcessWaitResult_Timeout; + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for guest process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + } + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestProcess::waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor, + ULONG aTimeoutMS, ProcessWaitResult_T *aReason) +{ + uint32_t fWaitFor = ProcessWaitForFlag_None; + for (size_t i = 0; i < aWaitFor.size(); i++) + fWaitFor |= aWaitFor[i]; + + return WaitFor(fWaitFor, aTimeoutMS, aReason); +} + +HRESULT GuestProcess::write(ULONG aHandle, ULONG aFlags, const std::vector<BYTE> &aData, + ULONG aTimeoutMS, ULONG *aWritten) +{ + static ULONG const s_fValidFlags = ProcessInputFlag_None | ProcessInputFlag_EndOfFile; + if (aFlags & ~s_fValidFlags) + return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"), + aFlags, aFlags & ~s_fValidFlags); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + uint32_t cbWritten; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + uint32_t cbData = (uint32_t)aData.size(); + void *pvData = cbData > 0 ? (void *)&aData.front() : NULL; + int vrc = i_writeData(aHandle, aFlags, pvData, cbData, aTimeoutMS, &cbWritten, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, + tr("Writing %RU32 bytes (flags %#x) to guest process failed: %s", "", cbData), + cbData, aFlags, GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing to guest process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + } + + LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, cbWritten)); + + *aWritten = (ULONG)cbWritten; + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestProcess::writeArray(ULONG aHandle, const std::vector<ProcessInputFlag_T> &aFlags, + const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten) +{ + LogFlowThisFuncEnter(); + + ULONG fWrite = ProcessInputFlag_None; + for (size_t i = 0; i < aFlags.size(); i++) + fWrite |= aFlags[i]; + + return write(aHandle, fWrite, aData, aTimeoutMS, aWritten); +} + +/////////////////////////////////////////////////////////////////////////////// + +GuestProcessTool::GuestProcessTool(void) + : pSession(NULL), + pProcess(NULL) +{ +} + +GuestProcessTool::~GuestProcessTool(void) +{ + uninit(); +} + +/** + * Initializes and starts a process tool on the guest. + * + * @returns VBox status code. + * @param pGuestSession Guest session the process tools should be started in. + * @param startupInfo Guest process startup info to use for starting. + * @param fAsync Whether to start asynchronously or not. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestProcessTool::init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo, + bool fAsync, int *prcGuest) +{ + LogFlowThisFunc(("pGuestSession=%p, exe=%s, fAsync=%RTbool\n", + pGuestSession, startupInfo.mExecutable.c_str(), fAsync)); + + AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER); + Assert(startupInfo.mArguments[0] == startupInfo.mExecutable); + + pSession = pGuestSession; + mStartupInfo = startupInfo; + + /* Make sure the process is hidden. */ + mStartupInfo.mFlags |= ProcessCreateFlag_Hidden; + + int vrc = pSession->i_processCreateEx(mStartupInfo, pProcess); + if (RT_SUCCESS(vrc)) + { + int vrcGuest = VINF_SUCCESS; + vrc = fAsync + ? pProcess->i_startProcessAsync() + : pProcess->i_startProcess(30 * 1000 /* 30s timeout */, &vrcGuest); + + if ( RT_SUCCESS(vrc) + && !fAsync + && RT_FAILURE(vrcGuest) + ) + { + vrc = VERR_GSTCTL_GUEST_ERROR; + } + + if (prcGuest) + *prcGuest = vrcGuest; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Unitializes a guest process tool by terminating it on the guest. + */ +void GuestProcessTool::uninit(void) +{ + /* Make sure the process is terminated and unregistered from the guest session. */ + int vrcGuestIgnored; + terminate(30 * 1000 /* 30s timeout */, &vrcGuestIgnored); + + /* Unregister the process from the process (and the session's object) list. */ + if ( pSession + && pProcess) + pSession->i_processUnregister(pProcess); + + /* Release references. */ + pProcess.setNull(); + pSession.setNull(); +} + +/** + * Gets the current guest process stream block. + * + * @returns VBox status code. + * @param uHandle Guest process file handle to get current block for. + * @param strmBlock Where to return the stream block on success. + */ +int GuestProcessTool::getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock) +{ + const GuestProcessStream *pStream = NULL; + if (uHandle == GUEST_PROC_OUT_H_STDOUT) + pStream = &mStdOut; + else if (uHandle == GUEST_PROC_OUT_H_STDERR) + pStream = &mStdErr; + + if (!pStream) + return VERR_INVALID_PARAMETER; + + /** @todo Why not using pStream down below and hardcode to mStdOut? */ + + int vrc; + do + { + /* Try parsing the data to see if the current block is complete. */ + vrc = mStdOut.ParseBlock(strmBlock); + if (strmBlock.GetCount()) + break; + } while (RT_SUCCESS(vrc)); + + LogFlowThisFunc(("rc=%Rrc, %RU64 pairs\n", + vrc, strmBlock.GetCount())); + return vrc; +} + +/** + * Returns the result code from an ended guest process tool. + * + * @returns Result code from guest process tool. + */ +int GuestProcessTool::getRc(void) const +{ + LONG exitCode = -1; + HRESULT hr = pProcess->COMGETTER(ExitCode(&exitCode)); + AssertComRC(hr); + + return GuestProcessTool::exitCodeToRc(mStartupInfo, exitCode); +} + +/** + * Returns whether a guest process tool is still running or not. + * + * @returns \c true if running, or \c false if not. + */ +bool GuestProcessTool::isRunning(void) +{ + AssertReturn(!pProcess.isNull(), false); + + ProcessStatus_T procStatus = ProcessStatus_Undefined; + HRESULT hr = pProcess->COMGETTER(Status(&procStatus)); + AssertComRC(hr); + + if ( procStatus == ProcessStatus_Started + || procStatus == ProcessStatus_Paused + || procStatus == ProcessStatus_Terminating) + { + return true; + } + + return false; +} + +/** + * Returns whether the tool has been run correctly or not, based on it's internal process + * status and reported exit status. + * + * @return @c true if the tool has been run correctly (exit status 0), or @c false if some error + * occurred (exit status <> 0 or wrong process state). + */ +bool GuestProcessTool::isTerminatedOk(void) +{ + return getTerminationStatus() == VINF_SUCCESS ? true : false; +} + +/** + * Static helper function to start and wait for a certain toolbox tool. + * + * This function most likely is the one you want to use in the first place if you + * want to just use a toolbox tool and wait for its result. See runEx() if you also + * needs its output. + * + * @return VBox status code. + * @param pGuestSession Guest control session to use for starting the toolbox tool in. + * @param startupInfo Startup information about the toolbox tool. + * @param pvrcGuest Where to store the toolbox tool's specific error code in case + * VERR_GSTCTL_GUEST_ERROR is returned. + */ +/* static */ +int GuestProcessTool::run( GuestSession *pGuestSession, + const GuestProcessStartupInfo &startupInfo, + int *pvrcGuest /* = NULL */) +{ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + + GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX }; + int vrc = runErrorInfo(pGuestSession, startupInfo, errorInfo); + if (RT_SUCCESS(vrc)) + { + /* Make sure to check the error information we got from the guest tool. */ + if (GuestProcess::i_isGuestError(errorInfo.rcGuest)) + { + if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */ + vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode); + else /* At least return something. */ + vrcGuest = errorInfo.rcGuest; + + if (pvrcGuest) + *pvrcGuest = vrcGuest; + + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + + LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode)); + return vrc; +} + +/** + * Static helper function to start and wait for a certain toolbox tool, returning + * extended error information from the guest. + * + * @return VBox status code. + * @param pGuestSession Guest control session to use for starting the toolbox tool in. + * @param startupInfo Startup information about the toolbox tool. + * @param errorInfo Error information returned for error handling. + */ +/* static */ +int GuestProcessTool::runErrorInfo( GuestSession *pGuestSession, + const GuestProcessStartupInfo &startupInfo, + GuestProcessToolErrorInfo &errorInfo) +{ + return runExErrorInfo(pGuestSession, startupInfo, + NULL /* paStrmOutObjects */, 0 /* cStrmOutObjects */, errorInfo); +} + +/** + * Static helper function to start and wait for output of a certain toolbox tool. + * + * @return IPRT status code. + * @param pGuestSession Guest control session to use for starting the toolbox tool in. + * @param startupInfo Startup information about the toolbox tool. + * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool. + * Optional. + * @param cStrmOutObjects Number of stream objects passed in. Optional. + * @param pvrcGuest Error code returned from the guest side if VERR_GSTCTL_GUEST_ERROR is returned. Optional. + */ +/* static */ +int GuestProcessTool::runEx( GuestSession *pGuestSession, + const GuestProcessStartupInfo &startupInfo, + GuestCtrlStreamObjects *paStrmOutObjects, + uint32_t cStrmOutObjects, + int *pvrcGuest /* = NULL */) +{ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + + GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX }; + int vrc = GuestProcessTool::runExErrorInfo(pGuestSession, startupInfo, paStrmOutObjects, cStrmOutObjects, errorInfo); + if (RT_SUCCESS(vrc)) + { + /* Make sure to check the error information we got from the guest tool. */ + if (GuestProcess::i_isGuestError(errorInfo.rcGuest)) + { + if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */ + vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode); + else /* At least return something. */ + vrcGuest = errorInfo.rcGuest; + + if (pvrcGuest) + *pvrcGuest = vrcGuest; + + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + + LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode)); + return vrc; +} + +/** + * Static helper function to start and wait for output of a certain toolbox tool. + * + * This is the extended version, which addds the possibility of retrieving parsable so-called guest stream + * objects. Those objects are issued on the guest side as part of VBoxService's toolbox tools (think of a BusyBox-like approach) + * on stdout and can be used on the host side to retrieve more information about the actual command issued on the guest side. + * + * @return VBox status code. + * @param pGuestSession Guest control session to use for starting the toolbox tool in. + * @param startupInfo Startup information about the toolbox tool. + * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool. + * Optional. + * @param cStrmOutObjects Number of stream objects passed in. Optional. + * @param errorInfo Error information returned for error handling. + */ +/* static */ +int GuestProcessTool::runExErrorInfo( GuestSession *pGuestSession, + const GuestProcessStartupInfo &startupInfo, + GuestCtrlStreamObjects *paStrmOutObjects, + uint32_t cStrmOutObjects, + GuestProcessToolErrorInfo &errorInfo) +{ + AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER); + /* paStrmOutObjects is optional. */ + + /** @todo Check if this is a valid toolbox. */ + + GuestProcessTool procTool; + int vrc = procTool.init(pGuestSession, startupInfo, false /* Async */, &errorInfo.rcGuest); + if (RT_SUCCESS(vrc)) + { + while (cStrmOutObjects--) + { + try + { + GuestProcessStreamBlock strmBlk; + vrc = procTool.waitEx( paStrmOutObjects + ? GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK + : GUESTPROCESSTOOL_WAIT_FLAG_NONE, &strmBlk, &errorInfo.rcGuest); + if (paStrmOutObjects) + paStrmOutObjects->push_back(strmBlk); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + break; + } + } + + if (RT_SUCCESS(vrc)) + { + /* Make sure the process runs until completion. */ + vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &errorInfo.rcGuest); + if (RT_SUCCESS(vrc)) + errorInfo.rcGuest = procTool.getTerminationStatus(&errorInfo.iExitCode); + } + + LogFlowFunc(("Returned rc=%Rrc, rcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode)); + return vrc; +} + +/** + * Reports if the tool has been run correctly. + * + * @return Will return VERR_GSTCTL_PROCESS_EXIT_CODE if the tool process returned an exit code <> 0, + * VERR_GSTCTL_PROCESS_WRONG_STATE if the tool process is in a wrong state (e.g. still running), + * or VINF_SUCCESS otherwise. + * + * @param piExitCode Exit code of the tool. Optional. + */ +int GuestProcessTool::getTerminationStatus(int32_t *piExitCode /* = NULL */) +{ + Assert(!pProcess.isNull()); + /* pExitCode is optional. */ + + int vrc; + if (!isRunning()) + { + LONG iExitCode = -1; + HRESULT hr = pProcess->COMGETTER(ExitCode(&iExitCode)); + AssertComRC(hr); + + if (piExitCode) + *piExitCode = iExitCode; + + vrc = iExitCode != 0 ? VERR_GSTCTL_PROCESS_EXIT_CODE : VINF_SUCCESS; + } + else + vrc = VERR_GSTCTL_PROCESS_WRONG_STATE; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Waits for a guest process tool. + * + * @returns VBox status code. + * @param fToolWaitFlags Guest process tool wait flags to use for waiting. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestProcessTool::wait(uint32_t fToolWaitFlags, int *prcGuest) +{ + return waitEx(fToolWaitFlags, NULL /* pStrmBlkOut */, prcGuest); +} + +/** + * Waits for a guest process tool, also returning process output. + * + * @returns VBox status code. + * @param fToolWaitFlags Guest process tool wait flags to use for waiting. + * @param pStrmBlkOut Where to store the guest process output. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestProcessTool::waitEx(uint32_t fToolWaitFlags, GuestProcessStreamBlock *pStrmBlkOut, int *prcGuest) +{ + LogFlowThisFunc(("fToolWaitFlags=0x%x, pStreamBlock=%p, prcGuest=%p\n", fToolWaitFlags, pStrmBlkOut, prcGuest)); + + /* Can we parse the next block without waiting? */ + int vrc; + if (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK) + { + AssertPtr(pStrmBlkOut); + vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut); + if (RT_SUCCESS(vrc)) + return vrc; + /* else do the waiting below. */ + } + + /* Do the waiting. */ + uint32_t fProcWaitForFlags = ProcessWaitForFlag_Terminate; + if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdOut) + fProcWaitForFlags |= ProcessWaitForFlag_StdOut; + if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdErr) + fProcWaitForFlags |= ProcessWaitForFlag_StdErr; + + /** @todo Decrease timeout while running. */ + uint64_t u64StartMS = RTTimeMilliTS(); + uint32_t uTimeoutMS = mStartupInfo.mTimeoutMS; + + int vrcGuest = VINF_SUCCESS; + bool fDone = false; + + BYTE byBuf[_64K]; + uint32_t cbRead; + + bool fHandleStdOut = false; + bool fHandleStdErr = false; + + /** + * Updates the elapsed time and checks if a + * timeout happened, then breaking out of the loop. + */ +#define UPDATE_AND_CHECK_ELAPSED_TIME() \ + u64ElapsedMS = RTTimeMilliTS() - u64StartMS; \ + if ( uTimeoutMS != RT_INDEFINITE_WAIT \ + && u64ElapsedMS >= uTimeoutMS) \ + { \ + vrc = VERR_TIMEOUT; \ + break; \ + } + + /** + * Returns the remaining time (in ms). + */ +#define GET_REMAINING_TIME \ + uTimeoutMS == RT_INDEFINITE_WAIT \ + ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS \ + + ProcessWaitResult_T waitRes = ProcessWaitResult_None; + do + { + uint64_t u64ElapsedMS; + UPDATE_AND_CHECK_ELAPSED_TIME(); + + vrc = pProcess->i_waitFor(fProcWaitForFlags, GET_REMAINING_TIME, waitRes, &vrcGuest); + if (RT_FAILURE(vrc)) + break; + + switch (waitRes) + { + case ProcessWaitResult_StdIn: + vrc = VERR_NOT_IMPLEMENTED; + break; + + case ProcessWaitResult_StdOut: + fHandleStdOut = true; + break; + + case ProcessWaitResult_StdErr: + fHandleStdErr = true; + break; + + case ProcessWaitResult_WaitFlagNotSupported: + if (fProcWaitForFlags & ProcessWaitForFlag_StdOut) + fHandleStdOut = true; + if (fProcWaitForFlags & ProcessWaitForFlag_StdErr) + fHandleStdErr = true; + /* Since waiting for stdout / stderr is not supported by the guest, + * wait a bit to not hog the CPU too much when polling for data. */ + RTThreadSleep(1); /* Optional, don't check rc. */ + break; + + case ProcessWaitResult_Error: + vrc = VERR_GSTCTL_GUEST_ERROR; + break; + + case ProcessWaitResult_Terminate: + fDone = true; + break; + + case ProcessWaitResult_Timeout: + vrc = VERR_TIMEOUT; + break; + + case ProcessWaitResult_Start: + case ProcessWaitResult_Status: + /* Not used here, just skip. */ + break; + + default: + AssertMsgFailed(("Unhandled process wait result %RU32\n", waitRes)); + break; + } + + if (RT_FAILURE(vrc)) + break; + + if (fHandleStdOut) + { + UPDATE_AND_CHECK_ELAPSED_TIME(); + + cbRead = 0; + vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDOUT, sizeof(byBuf), + GET_REMAINING_TIME, + byBuf, sizeof(byBuf), + &cbRead, &vrcGuest); + if ( RT_FAILURE(vrc) + || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED) + break; + + if (cbRead) + { + LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead)); + vrc = mStdOut.AddData(byBuf, cbRead); + + if ( RT_SUCCESS(vrc) + && (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK)) + { + AssertPtr(pStrmBlkOut); + vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut); + + /* When successful, break out of the loop because we're done + * with reading the first stream block. */ + if (RT_SUCCESS(vrc)) + fDone = true; + } + } + + fHandleStdOut = false; + } + + if (fHandleStdErr) + { + UPDATE_AND_CHECK_ELAPSED_TIME(); + + cbRead = 0; + vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDERR, sizeof(byBuf), + GET_REMAINING_TIME, + byBuf, sizeof(byBuf), + &cbRead, &vrcGuest); + if ( RT_FAILURE(vrc) + || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED) + break; + + if (cbRead) + { + LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead)); + vrc = mStdErr.AddData(byBuf, cbRead); + } + + fHandleStdErr = false; + } + + } while (!fDone && RT_SUCCESS(vrc)); + +#undef UPDATE_AND_CHECK_ELAPSED_TIME +#undef GET_REMAINING_TIME + + if (RT_FAILURE(vrcGuest)) + vrc = VERR_GSTCTL_GUEST_ERROR; + + LogFlowThisFunc(("Loop ended with rc=%Rrc, vrcGuest=%Rrc, waitRes=%RU32\n", + vrc, vrcGuest, waitRes)); + if (prcGuest) + *prcGuest = vrcGuest; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Terminates a guest process tool. + * + * @returns VBox status code. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestProcessTool::terminate(uint32_t uTimeoutMS, int *prcGuest) +{ + LogFlowThisFuncEnter(); + + int vrc; + if (!pProcess.isNull()) + vrc = pProcess->i_terminateProcess(uTimeoutMS, prcGuest); + else + vrc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Converts a toolbox tool's exit code to an IPRT error code. + * + * @returns VBox status code. + * @param startupInfo Startup info of the toolbox tool to lookup error code for. + * @param iExitCode The toolbox tool's exit code to lookup IPRT error for. + */ +/* static */ +int GuestProcessTool::exitCodeToRc(const GuestProcessStartupInfo &startupInfo, int32_t iExitCode) +{ + if (startupInfo.mArguments.size() == 0) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; /* Should not happen. */ + } + + return exitCodeToRc(startupInfo.mArguments[0].c_str(), iExitCode); +} + +/** + * Converts a toolbox tool's exit code to an IPRT error code. + * + * @returns VBox status code. + * @param pszTool Name of toolbox tool to lookup error code for. + * @param iExitCode The toolbox tool's exit code to lookup IPRT error for. + */ +/* static */ +int GuestProcessTool::exitCodeToRc(const char *pszTool, int32_t iExitCode) +{ + AssertPtrReturn(pszTool, VERR_INVALID_POINTER); + + LogFlowFunc(("%s: %d\n", pszTool, iExitCode)); + + if (iExitCode == 0) /* No error? Bail out early. */ + return VINF_SUCCESS; + + if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_CAT)) + { + switch (iExitCode) + { + case VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED; + case VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND; + case VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND; + case VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION: return VERR_SHARING_VIOLATION; + case VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY: return VERR_IS_A_DIRECTORY; + default: break; + } + } + else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_LS)) + { + switch (iExitCode) + { + /** @todo Handle access denied? */ + case RTEXITCODE_FAILURE: return VERR_PATH_NOT_FOUND; + default: break; + } + } + else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_STAT)) + { + switch (iExitCode) + { + case VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED; + case VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND; + case VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND; + case VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND: return VERR_NET_PATH_NOT_FOUND; + default: break; + } + } + else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKDIR)) + { + switch (iExitCode) + { + case RTEXITCODE_FAILURE: return VERR_CANT_CREATE; + default: break; + } + } + else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKTEMP)) + { + switch (iExitCode) + { + case RTEXITCODE_FAILURE: return VERR_CANT_CREATE; + default: break; + } + } + else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_RM)) + { + switch (iExitCode) + { + case RTEXITCODE_FAILURE: return VERR_FILE_NOT_FOUND; + /** @todo RTPathRmCmd does not yet distinguish between not found and access denied yet. */ + default: break; + } + } + + LogFunc(("Warning: Exit code %d not handled for tool '%s', returning VERR_GENERAL_FAILURE\n", iExitCode, pszTool)); + + if (iExitCode == RTEXITCODE_SYNTAX) + return VERR_INTERNAL_ERROR_5; + return VERR_GENERAL_FAILURE; +} + +/** + * Returns a stringyfied error of a guest process tool error. + * + * @returns Stringyfied error. + * @param pszTool Toolbox tool name to get stringyfied error for. + * @param guestErrorInfo Guest error info to get stringyfied error for. + */ +/* static */ +Utf8Str GuestProcessTool::guestErrorToString(const char *pszTool, const GuestErrorInfo &guestErrorInfo) +{ + Utf8Str strErr; + + /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ + switch (guestErrorInfo.getRc()) + { + case VERR_ACCESS_DENIED: + strErr.printf(tr("Access to \"%s\" denied"), guestErrorInfo.getWhat().c_str()); + break; + + case VERR_FILE_NOT_FOUND: /* This is the most likely error. */ + RT_FALL_THROUGH(); + case VERR_PATH_NOT_FOUND: + strErr.printf(tr("No such file or directory \"%s\""), guestErrorInfo.getWhat().c_str()); + break; + + case VERR_INVALID_VM_HANDLE: + strErr.printf(tr("VMM device is not available (is the VM running?)")); + break; + + case VERR_HGCM_SERVICE_NOT_FOUND: + strErr.printf(tr("The guest execution service is not available")); + break; + + case VERR_BAD_EXE_FORMAT: + strErr.printf(tr("The file \"%s\" is not an executable format"), guestErrorInfo.getWhat().c_str()); + break; + + case VERR_AUTHENTICATION_FAILURE: + strErr.printf(tr("The user \"%s\" was not able to logon"), guestErrorInfo.getWhat().c_str()); + break; + + case VERR_INVALID_NAME: + strErr.printf(tr("The file \"%s\" is an invalid name"), guestErrorInfo.getWhat().c_str()); + break; + + case VERR_TIMEOUT: + strErr.printf(tr("The guest did not respond within time")); + break; + + case VERR_CANCELLED: + strErr.printf(tr("The execution operation was canceled")); + break; + + case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED: + strErr.printf(tr("Maximum number of concurrent guest processes has been reached")); + break; + + case VERR_NOT_FOUND: + strErr.printf(tr("The guest execution service is not ready (yet)")); + break; + + default: + strErr.printf(tr("Unhandled error %Rrc for \"%s\" occurred for tool \"%s\" on guest -- please file a bug report"), + guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str(), pszTool); + break; + } + + return strErr; +} + diff --git a/src/VBox/Main/src-client/GuestSessionImpl.cpp b/src/VBox/Main/src-client/GuestSessionImpl.cpp new file mode 100644 index 00000000..06d6c833 --- /dev/null +++ b/src/VBox/Main/src-client/GuestSessionImpl.cpp @@ -0,0 +1,4821 @@ +/* $Id: GuestSessionImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest session handling. + */ + +/* + * Copyright (C) 2012-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION +#include "LoggingNew.h" + +#include "GuestImpl.h" +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestSessionImpl.h" +#include "GuestSessionImplTasks.h" +#include "GuestCtrlImplPrivate.h" +#include "VirtualBoxErrorInfoImpl.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "ProgressImpl.h" +#include "VBoxEvents.h" +#include "VMMDev.h" +#include "ThreadTask.h" + +#include <memory> /* For auto_ptr. */ + +#include <iprt/cpp/utils.h> /* For unconst(). */ +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/file.h> /* For CopyTo/From. */ +#include <iprt/path.h> +#include <iprt/rand.h> + +#include <VBox/com/array.h> +#include <VBox/com/listeners.h> +#include <VBox/version.h> + + +/** + * Base class representing an internal + * asynchronous session task. + */ +class GuestSessionTaskInternal : public ThreadTask +{ +public: + + GuestSessionTaskInternal(GuestSession *pSession) + : ThreadTask("GenericGuestSessionTaskInternal") + , mSession(pSession) + , mRC(VINF_SUCCESS) { } + + virtual ~GuestSessionTaskInternal(void) { } + + /** Returns the last set result code. */ + int rc(void) const { return mRC; } + /** Returns whether the last set result code indicates success or not. */ + bool isOk(void) const { return RT_SUCCESS(mRC); } + /** Returns the task's guest session object. */ + const ComObjPtr<GuestSession> &Session(void) const { return mSession; } + +protected: + + /** Guest session the task belongs to. */ + const ComObjPtr<GuestSession> mSession; + /** The last set result code. */ + int mRC; +}; + +/** + * Class for asynchronously starting a guest session. + */ +class GuestSessionTaskInternalStart : public GuestSessionTaskInternal +{ +public: + + GuestSessionTaskInternalStart(GuestSession *pSession) + : GuestSessionTaskInternal(pSession) + { + m_strTaskName = "gctlSesStart"; + } + + void handler() + { + /* Ignore rc */ GuestSession::i_startSessionThreadTask(this); + } +}; + +/** + * Internal listener class to serve events in an + * active manner, e.g. without polling delays. + */ +class GuestSessionListener +{ +public: + + GuestSessionListener(void) + { + } + + virtual ~GuestSessionListener(void) + { + } + + HRESULT init(GuestSession *pSession) + { + AssertPtrReturn(pSession, E_POINTER); + mSession = pSession; + return S_OK; + } + + void uninit(void) + { + mSession = NULL; + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) + { + switch (aType) + { + case VBoxEventType_OnGuestSessionStateChanged: + { + AssertPtrReturn(mSession, E_POINTER); + int rc2 = mSession->signalWaitEvent(aType, aEvent); + RT_NOREF(rc2); +#ifdef DEBUG_andy + LogFlowFunc(("Signalling events of type=%RU32, session=%p resulted in rc=%Rrc\n", + aType, mSession, rc2)); +#endif + break; + } + + default: + AssertMsgFailed(("Unhandled event %RU32\n", aType)); + break; + } + + return S_OK; + } + +private: + + GuestSession *mSession; +}; +typedef ListenerImpl<GuestSessionListener, GuestSession*> GuestSessionListenerImpl; + +VBOX_LISTENER_DECLARE(GuestSessionListenerImpl) + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestSession) + +HRESULT GuestSession::FinalConstruct(void) +{ + LogFlowThisFuncEnter(); + return BaseFinalConstruct(); +} + +void GuestSession::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes a guest session but does *not* open in on the guest side + * yet. This needs to be done via the openSession() / openSessionAsync calls. + * + * @returns VBox status code. + * @param pGuest Guest object the guest session belongs to. + * @param ssInfo Guest session startup info to use. + * @param guestCreds Guest credentials to use for starting a guest session + * with a specific guest account. + */ +int GuestSession::init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo, + const GuestCredentials &guestCreds) +{ + LogFlowThisFunc(("pGuest=%p, ssInfo=%p, guestCreds=%p\n", + pGuest, &ssInfo, &guestCreds)); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); + + AssertPtrReturn(pGuest, VERR_INVALID_POINTER); + + /* + * Initialize our data members from the input. + */ + mParent = pGuest; + + /* Copy over startup info. */ + /** @todo Use an overloaded copy operator. Later. */ + mData.mSession.mID = ssInfo.mID; + mData.mSession.mIsInternal = ssInfo.mIsInternal; + mData.mSession.mName = ssInfo.mName; + mData.mSession.mOpenFlags = ssInfo.mOpenFlags; + mData.mSession.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS; + + /* Copy over session credentials. */ + /** @todo Use an overloaded copy operator. Later. */ + mData.mCredentials.mUser = guestCreds.mUser; + mData.mCredentials.mPassword = guestCreds.mPassword; + mData.mCredentials.mDomain = guestCreds.mDomain; + + /* Initialize the remainder of the data. */ + mData.mRC = VINF_SUCCESS; + mData.mStatus = GuestSessionStatus_Undefined; + mData.mpBaseEnvironment = NULL; + + /* + * Register an object for the session itself to clearly + * distinguish callbacks which are for this session directly, or for + * objects (like files, directories, ...) which are bound to this session. + */ + int rc = i_objectRegister(NULL /* pObject */, SESSIONOBJECTTYPE_SESSION, &mData.mObjectID); + if (RT_SUCCESS(rc)) + { + rc = mData.mEnvironmentChanges.initChangeRecord(pGuest->i_isGuestInWindowsNtFamily() + ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&mWaitEventCritSect); + AssertRC(rc); + } + } + + if (RT_SUCCESS(rc)) + rc = i_determineProtocolVersion(); + + if (RT_SUCCESS(rc)) + { + /* + * <Replace this if you figure out what the code is doing.> + */ + HRESULT hr = unconst(mEventSource).createObject(); + if (SUCCEEDED(hr)) + hr = mEventSource->init(); + if (SUCCEEDED(hr)) + { + try + { + GuestSessionListener *pListener = new GuestSessionListener(); + ComObjPtr<GuestSessionListenerImpl> thisListener; + hr = thisListener.createObject(); + if (SUCCEEDED(hr)) + hr = thisListener->init(pListener, this); /* thisListener takes ownership of pListener. */ + if (SUCCEEDED(hr)) + { + com::SafeArray <VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); + hr = mEventSource->RegisterListener(thisListener, + ComSafeArrayAsInParam(eventTypes), + TRUE /* Active listener */); + if (SUCCEEDED(hr)) + { + mLocalListener = thisListener; + + /* + * Mark this object as operational and return success. + */ + autoInitSpan.setSucceeded(); + LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool rc=VINF_SUCCESS\n", + mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal)); + return VINF_SUCCESS; + } + } + } + catch (std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + } + rc = Global::vboxStatusCodeFromCOM(hr); + } + + autoInitSpan.setFailed(); + LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => rc=%Rrc\n", + mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc)); + return rc; +} + +/** + * Uninitializes the instance. + * Called from FinalRelease(). + */ +void GuestSession::uninit(void) +{ + /* Enclose the state transition Ready->InUninit->NotReady. */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + LogFlowThisFuncEnter(); + + /* Call i_onRemove to take care of the object cleanups. */ + i_onRemove(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Unregister the session's object ID. */ + i_objectUnregister(mData.mObjectID); + + Assert(mData.mObjects.size () == 0); + mData.mObjects.clear(); + + mData.mEnvironmentChanges.reset(); + + if (mData.mpBaseEnvironment) + { + mData.mpBaseEnvironment->releaseConst(); + mData.mpBaseEnvironment = NULL; + } + + /* Unitialize our local listener. */ + mLocalListener.setNull(); + + baseUninit(); + + LogFlowFuncLeave(); +} + +// implementation of public getters/setters for attributes +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestSession::getUser(com::Utf8Str &aUser) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aUser = mData.mCredentials.mUser; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getDomain(com::Utf8Str &aDomain) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aDomain = mData.mCredentials.mDomain; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getName(com::Utf8Str &aName) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aName = mData.mSession.mName; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getId(ULONG *aId) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aId = mData.mSession.mID; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getStatus(GuestSessionStatus_T *aStatus) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aStatus = mData.mStatus; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getTimeout(ULONG *aTimeout) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aTimeout = mData.mTimeout; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::setTimeout(ULONG aTimeout) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mTimeout = aTimeout; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getProtocolVersion(ULONG *aProtocolVersion) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aProtocolVersion = mData.mProtocolVersion; + + LogFlowThisFuncLeave(); + return S_OK; +} + +HRESULT GuestSession::getEnvironmentChanges(std::vector<com::Utf8Str> &aEnvironmentChanges) +{ + LogFlowThisFuncEnter(); + + int vrc; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges); + } + + LogFlowFuncLeaveRC(vrc); + return Global::vboxStatusCodeToCOM(vrc); +} + +HRESULT GuestSession::setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges) +{ + LogFlowThisFuncEnter(); + + int vrc; + size_t idxError = ~(size_t)0; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData.mEnvironmentChanges.reset(); + vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges, &idxError); + } + + LogFlowFuncLeaveRC(vrc); + if (RT_SUCCESS(vrc)) + return S_OK; + if (vrc == VERR_ENV_INVALID_VAR_NAME) + return setError(E_INVALIDARG, tr("Invalid environment variable name '%s', index %zu"), + aEnvironmentChanges[idxError].c_str(), idxError); + return setErrorBoth(Global::vboxStatusCodeToCOM(vrc), vrc, tr("Failed to apply '%s', index %zu (%Rrc)"), + aEnvironmentChanges[idxError].c_str(), idxError, vrc); +} + +HRESULT GuestSession::getEnvironmentBase(std::vector<com::Utf8Str> &aEnvironmentBase) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + HRESULT hrc; + if (mData.mpBaseEnvironment) + { + int vrc = mData.mpBaseEnvironment->queryPutEnvArray(&aEnvironmentBase); + hrc = Global::vboxStatusCodeToCOM(vrc); + } + else if (mData.mProtocolVersion < 99999) + hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); + else + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); + + LogFlowFuncLeave(); + return hrc; +} + +HRESULT GuestSession::getProcesses(std::vector<ComPtr<IGuestProcess> > &aProcesses) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aProcesses.resize(mData.mProcesses.size()); + size_t i = 0; + for (SessionProcesses::iterator it = mData.mProcesses.begin(); + it != mData.mProcesses.end(); + ++it, ++i) + { + it->second.queryInterfaceTo(aProcesses[i].asOutParam()); + } + + LogFlowFunc(("mProcesses=%zu\n", aProcesses.size())); + return S_OK; +} + +HRESULT GuestSession::getPathStyle(PathStyle_T *aPathStyle) +{ + *aPathStyle = i_getGuestPathStyle(); + return S_OK; +} + +HRESULT GuestSession::getCurrentDirectory(com::Utf8Str &aCurrentDirectory) +{ + RT_NOREF(aCurrentDirectory); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::setCurrentDirectory(const com::Utf8Str &aCurrentDirectory) +{ + RT_NOREF(aCurrentDirectory); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::getUserHome(com::Utf8Str &aUserHome) +{ + HRESULT hr = i_isStartedExternal(); + if (FAILED(hr)) + return hr; + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_pathUserHome(aUserHome, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_NOT_SUPPORTED: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, + tr("Getting the user's home path is not supported by installed Guest Additions")); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, + tr("Getting the user's home path failed on the guest: %Rrc"), rcGuest); + break; + } + break; + } + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's home path failed: %Rrc"), vrc); + break; + } + } + + return hr; +} + +HRESULT GuestSession::getUserDocuments(com::Utf8Str &aUserDocuments) +{ + HRESULT hr = i_isStartedExternal(); + if (FAILED(hr)) + return hr; + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_pathUserDocuments(aUserDocuments, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_NOT_SUPPORTED: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, + tr("Getting the user's documents path is not supported by installed Guest Additions")); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, + tr("Getting the user's documents path failed on the guest: %Rrc"), rcGuest); + break; + } + break; + } + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's documents path failed: %Rrc"), vrc); + break; + } + } + + return hr; +} + +HRESULT GuestSession::getDirectories(std::vector<ComPtr<IGuestDirectory> > &aDirectories) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aDirectories.resize(mData.mDirectories.size()); + size_t i = 0; + for (SessionDirectories::iterator it = mData.mDirectories.begin(); it != mData.mDirectories.end(); ++it, ++i) + { + it->second.queryInterfaceTo(aDirectories[i].asOutParam()); + } + + LogFlowFunc(("mDirectories=%zu\n", aDirectories.size())); + return S_OK; +} + +HRESULT GuestSession::getFiles(std::vector<ComPtr<IGuestFile> > &aFiles) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aFiles.resize(mData.mFiles.size()); + size_t i = 0; + for(SessionFiles::iterator it = mData.mFiles.begin(); it != mData.mFiles.end(); ++it, ++i) + it->second.queryInterfaceTo(aFiles[i].asOutParam()); + + LogFlowFunc(("mDirectories=%zu\n", aFiles.size())); + + return S_OK; +} + +HRESULT GuestSession::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + LogFlowThisFuncEnter(); + + // no need to lock - lifetime constant + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + LogFlowThisFuncLeave(); + return S_OK; +} + +// private methods +/////////////////////////////////////////////////////////////////////////////// + +/** + * Closes a guest session on the guest. + * + * @returns VBox status code. + * @param uFlags Guest session close flags. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the read lock. + */ +int GuestSession::i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("uFlags=%x, uTimeoutMS=%RU32\n", uFlags, uTimeoutMS)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Guest Additions < 4.3 don't support closing dedicated + guest sessions, skip. */ + if (mData.mProtocolVersion < 2) + { + LogFlowThisFunc(("Installed Guest Additions don't support closing dedicated sessions, skipping\n")); + return VINF_SUCCESS; + } + + /** @todo uFlags validation. */ + + if (mData.mStatus != GuestSessionStatus_Started) + { + LogFlowThisFunc(("Session ID=%RU32 not started (anymore), status now is: %RU32\n", + mData.mSession.mID, mData.mStatus)); + return VINF_SUCCESS; + } + + int vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); + + vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + LogFlowThisFunc(("Sending closing request to guest session ID=%RU32, uFlags=%x\n", + mData.mSession.mID, uFlags)); + + alock.release(); + + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], uFlags); + + vrc = i_sendMessage(HOST_MSG_SESSION_CLOSE, i, paParms, VBOX_GUESTCTRL_DST_BOTH); + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Terminate, uTimeoutMS, + NULL /* Session status */, prcGuest); + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Internal worker function for public APIs that handle copying elements from + * guest to the host. + * + * @return HRESULT + * @param SourceSet Source set specifying what to copy. + * @param strDestination Destination path on the host. Host path style. + * @param pProgress Progress object returned to the caller. + */ +HRESULT GuestSession::i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet, + const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress) +{ + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + /* Validate stuff. */ + if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */ + return setError(E_INVALIDARG, tr("No source(s) specified")); + if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No destination specified")); + + GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin(); + while (itSrc != SourceSet.end()) + { + LogRel2(("Guest Control: Copying '%s' from guest to '%s' on the host (type: %s, filter: %s)\n", + itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str())); + ++itSrc; + } + + /* Create a task and return the progress obejct for it. */ + GuestSessionTaskCopyFrom *pTask = NULL; + try + { + pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyFrom object")); + } + + try + { + hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str())); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + { + ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject(); + + /* Kick off the worker thread. Note! Consumes pTask. */ + hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + pTask = NULL; + if (SUCCEEDED(hrc)) + hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam()); + else + hrc = setError(hrc, tr("Starting thread for copying from guest to the host failed")); + } + else + { + hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyFrom object failed")); + delete pTask; + } + + LogFlowFunc(("Returning %Rhrc\n", hrc)); + return hrc; +} + +/** + * Internal worker function for public APIs that handle copying elements from + * host to the guest. + * + * @return HRESULT + * @param SourceSet Source set specifying what to copy. + * @param strDestination Destination path on the guest. Guest path style. + * @param pProgress Progress object returned to the caller. + */ +HRESULT GuestSession::i_copyToGuest(const GuestSessionFsSourceSet &SourceSet, + const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress) +{ + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin(); + while (itSrc != SourceSet.end()) + { + LogRel2(("Guest Control: Copying '%s' from host to '%s' on the guest (type: %s, filter: %s)\n", + itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str())); + ++itSrc; + } + + /* Create a task and return the progress object for it. */ + GuestSessionTaskCopyTo *pTask = NULL; + try + { + pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyTo object")); + } + + try + { + hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str())); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + { + ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject(); + + /* Kick off the worker thread. Note! Consumes pTask. */ + hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + pTask = NULL; + if (SUCCEEDED(hrc)) + hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam()); + else + hrc = setError(hrc, tr("Starting thread for copying from host to the guest failed")); + } + else + { + hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyTo object failed")); + delete pTask; + } + + LogFlowFunc(("Returning %Rhrc\n", hrc)); + return hrc; +} + +/** + * Validates and extracts directory copy flags from a comma-separated string. + * + * @return COM status, error set on failure + * @param strFlags String to extract flags from. + * @param fStrict Whether to set an error when an unknown / invalid flag is detected. + * @param pfFlags Where to store the extracted (and validated) flags. + */ +HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, DirectoryCopyFlag_T *pfFlags) +{ + unsigned fFlags = DirectoryCopyFlag_None; + + /* Validate and set flags. */ + if (strFlags.isNotEmpty()) + { + const char *pszNext = strFlags.c_str(); + for (;;) + { + /* Find the next keyword, ignoring all whitespace. */ + pszNext = RTStrStripL(pszNext); + + const char * const pszComma = strchr(pszNext, ','); + size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext); + while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1])) + cchKeyword--; + + if (cchKeyword > 0) + { + /* Convert keyword to flag. */ +#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \ + && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0) + if (MATCH_KEYWORD("CopyIntoExisting")) + fFlags |= (unsigned)DirectoryCopyFlag_CopyIntoExisting; + else if (MATCH_KEYWORD("Recursive")) + fFlags |= (unsigned)DirectoryCopyFlag_Recursive; + else if (MATCH_KEYWORD("FollowLinks")) + fFlags |= (unsigned)DirectoryCopyFlag_FollowLinks; + else if (fStrict) + return setError(E_INVALIDARG, tr("Invalid directory copy flag: %.*s"), (int)cchKeyword, pszNext); +#undef MATCH_KEYWORD + } + if (!pszComma) + break; + pszNext = pszComma + 1; + } + } + + if (pfFlags) + *pfFlags = (DirectoryCopyFlag_T)fFlags; + return S_OK; +} + +/** + * Creates a directory on the guest. + * + * @returns VBox status code. + * @param strPath Path on guest to directory to create. + * @param uMode Creation mode to use (octal, 0777 max). + * @param uFlags Directory creation flags to use. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestSession::i_directoryCreate(const Utf8Str &strPath, uint32_t uMode, + uint32_t uFlags, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, uMode=%x, uFlags=%x\n", strPath.c_str(), uMode, uFlags)); + + int vrc = VINF_SUCCESS; + + GuestProcessStartupInfo procInfo; + procInfo.mFlags = ProcessCreateFlag_Hidden; + procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKDIR); + + try + { + procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ + + /* Construct arguments. */ + if (uFlags) + { + if (uFlags & DirectoryCreateFlag_Parents) + procInfo.mArguments.push_back(Utf8Str("--parents")); /* We also want to create the parent directories. */ + else + vrc = VERR_INVALID_PARAMETER; + } + + if ( RT_SUCCESS(vrc) + && uMode) + { + procInfo.mArguments.push_back(Utf8Str("--mode")); /* Set the creation mode. */ + + char szMode[16]; + if (RTStrPrintf(szMode, sizeof(szMode), "%o", uMode)) + { + procInfo.mArguments.push_back(Utf8Str(szMode)); + } + else + vrc = VERR_BUFFER_OVERFLOW; + } + + procInfo.mArguments.push_back("--"); /* '--version' is a valid directory name. */ + procInfo.mArguments.push_back(strPath); /* The directory we want to create. */ + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(vrc)) + vrc = GuestProcessTool::run(this, procInfo, prcGuest); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Checks if a directory on the guest exists. + * + * @returns \c true if directory exists on the guest, \c false if not. + * @param strPath Path of directory to check. + */ +bool GuestSession::i_directoryExists(const Utf8Str &strPath) +{ + GuestFsObjData objDataIgnored; + int rcGuestIgnored; + int rc = i_directoryQueryInfo(strPath, true /* fFollowSymlinks */, objDataIgnored, &rcGuestIgnored); + + return RT_SUCCESS(rc); +} + +/** + * Checks if a directory object exists and optionally returns its object. + * + * @returns \c true if directory object exists, or \c false if not. + * @param uDirID ID of directory object to check. + * @param pDir Where to return the found directory object on success. + */ +inline bool GuestSession::i_directoryExists(uint32_t uDirID, ComObjPtr<GuestDirectory> *pDir) +{ + SessionDirectories::const_iterator it = mData.mDirectories.find(uDirID); + if (it != mData.mDirectories.end()) + { + if (pDir) + *pDir = it->second; + return true; + } + return false; +} + +/** + * Queries information about a directory on the guest. + * + * @returns VBox status code, or VERR_NOT_A_DIRECTORY if the file system object exists but is not a directory. + * @param strPath Path to directory to query information for. + * @param fFollowSymlinks Whether to follow symlinks or not. + * @param objData Where to store the information returned on success. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + */ +int GuestSession::i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, + GuestFsObjData &objData, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks)); + + int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); + if (RT_SUCCESS(vrc)) + { + vrc = objData.mType == FsObjType_Directory + ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Unregisters a directory object from a guest session. + * + * @returns VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore). + * @param pDirectory Directory object to unregister from session. + * + * @note Takes the write lock. + */ +int GuestSession::i_directoryUnregister(GuestDirectory *pDirectory) +{ + AssertPtrReturn(pDirectory, VERR_INVALID_POINTER); + + LogFlowThisFunc(("pDirectory=%p\n", pDirectory)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + const uint32_t idObject = pDirectory->getObjectID(); + + LogFlowFunc(("Removing directory (objectID=%RU32) ...\n", idObject)); + + int rc = i_objectUnregister(idObject); + if (RT_FAILURE(rc)) + return rc; + + SessionDirectories::iterator itDirs = mData.mDirectories.find(idObject); + AssertReturn(itDirs != mData.mDirectories.end(), VERR_NOT_FOUND); + + /* Make sure to consume the pointer before the one of the iterator gets released. */ + ComObjPtr<GuestDirectory> pDirConsumed = pDirectory; + + LogFlowFunc(("Removing directory ID=%RU32 (session %RU32, now total %zu directories)\n", + idObject, mData.mSession.mID, mData.mDirectories.size())); + + rc = pDirConsumed->i_onUnregister(); + AssertRCReturn(rc, rc); + + mData.mDirectories.erase(itDirs); + + alock.release(); /* Release lock before firing off event. */ + +// ::FireGuestDirectoryRegisteredEvent(mEventSource, this /* Session */, pDirConsumed, false /* Process unregistered */); + + pDirConsumed.setNull(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Removes a directory on the guest. + * + * @returns VBox status code. + * @param strPath Path of directory on guest to remove. + * @param fFlags Directory remove flags to use. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the read lock. + */ +int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t fFlags, int *prcGuest) +{ + AssertReturn(!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), fFlags)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + GuestWaitEvent *pEvent = NULL; + int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetPv(&paParms[i++], (void*)strPath.c_str(), + (ULONG)strPath.length() + 1); + HGCMSvcSetU32(&paParms[i++], fFlags); + + alock.release(); /* Drop lock before sending. */ + + vrc = i_sendMessage(HOST_MSG_DIR_REMOVE, i, paParms); + if (RT_SUCCESS(vrc)) + { + vrc = pEvent->Wait(30 * 1000); + if ( vrc == VERR_GSTCTL_GUEST_ERROR + && prcGuest) + *prcGuest = pEvent->GuestResult(); + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Creates a temporary directory / file on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param strTemplate Name template to use. + * \sa RTDirCreateTemp / RTDirCreateTempSecure. + * @param strPath Path where to create the temporary directory / file. + * @param fDirectory Whether to create a temporary directory or file. + * @param strName Where to return the created temporary name on success. + * @param fMode File mode to use for creation (octal, umask-style). + * Ignored when \a fSecure is specified. + * @param fSecure Whether to perform a secure creation or not. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + */ +int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName, + uint32_t fMode, bool fSecure, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + AssertReturn(fSecure || !(fMode & ~07777), VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool, fMode=%o, fSecure=%RTbool\n", + strTemplate.c_str(), strPath.c_str(), fDirectory, fMode, fSecure)); + + GuestProcessStartupInfo procInfo; + procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; + try + { + procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKTEMP); + procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ + procInfo.mArguments.push_back(Utf8Str("--machinereadable")); + if (fDirectory) + procInfo.mArguments.push_back(Utf8Str("-d")); + if (strPath.length()) /* Otherwise use /tmp or equivalent. */ + { + procInfo.mArguments.push_back(Utf8Str("-t")); + procInfo.mArguments.push_back(strPath); + } + /* Note: Secure flag and mode cannot be specified at the same time. */ + if (fSecure) + { + procInfo.mArguments.push_back(Utf8Str("--secure")); + } + else + { + procInfo.mArguments.push_back(Utf8Str("--mode")); + + /* Note: Pass the mode unmodified down to the guest. See @ticketref{21394}. */ + char szMode[16]; + int vrc2 = RTStrPrintf2(szMode, sizeof(szMode), "%d", fMode); + AssertRCReturn(vrc2, vrc2); + procInfo.mArguments.push_back(szMode); + } + procInfo.mArguments.push_back("--"); /* strTemplate could be '--help'. */ + procInfo.mArguments.push_back(strTemplate); + } + catch (std::bad_alloc &) + { + Log(("Out of memory!\n")); + return VERR_NO_MEMORY; + } + + /** @todo Use an internal HGCM command for this operation, since + * we now can run in a user-dedicated session. */ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + GuestCtrlStreamObjects stdOut; + int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest); + if (!GuestProcess::i_isGuestError(vrc)) + { + GuestFsObjData objData; + if (!stdOut.empty()) + { + vrc = objData.FromMkTemp(stdOut.at(0)); + if (RT_FAILURE(vrc)) + { + vrcGuest = vrc; + if (prcGuest) + *prcGuest = vrcGuest; + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + else + vrc = VERR_BROKEN_PIPE; + + if (RT_SUCCESS(vrc)) + strName = objData.mName; + } + else if (prcGuest) + *prcGuest = vrcGuest; + + LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); + return vrc; +} + +/** + * Open a directory on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param openInfo Open information to use. + * @param pDirectory Where to return the guest directory object on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestSession::i_directoryOpen(const GuestDirectoryOpenInfo &openInfo, + ComObjPtr<GuestDirectory> &pDirectory, int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, strPath=%s, uFlags=%x\n", + openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Create the directory object. */ + HRESULT hr = pDirectory.createObject(); + if (FAILED(hr)) + return Global::vboxStatusCodeFromCOM(hr); + + /* Register a new object ID. */ + uint32_t idObject; + int vrc = i_objectRegister(pDirectory, SESSIONOBJECTTYPE_DIRECTORY, &idObject); + if (RT_FAILURE(vrc)) + { + pDirectory.setNull(); + return vrc; + } + + /* We need to release the write lock first before initializing the directory object below, + * as we're starting a guest process as part of it. This in turn will try to acquire the session's + * write lock. */ + alock.release(); + + Console *pConsole = mParent->i_getConsole(); + AssertPtr(pConsole); + + vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo); + if (RT_FAILURE(vrc)) + { + /* Make sure to acquire the write lock again before unregistering the object. */ + alock.acquire(); + + int vrc2 = i_objectUnregister(idObject); + AssertRC(vrc2); + + pDirectory.setNull(); + } + else + { + /* Make sure to acquire the write lock again before continuing. */ + alock.acquire(); + + try + { + /* Add the created directory to our map. */ + mData.mDirectories[idObject] = pDirectory; + + LogFlowFunc(("Added new guest directory \"%s\" (Session: %RU32) (now total %zu directories)\n", + openInfo.mPath.c_str(), mData.mSession.mID, mData.mDirectories.size())); + + alock.release(); /* Release lock before firing off event. */ + + /** @todo Fire off a VBoxEventType_OnGuestDirectoryRegistered event? */ + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + /* Nothing further to do here yet. */ + if (prcGuest) + *prcGuest = VINF_SUCCESS; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Dispatches a host callback to its corresponding object. + * + * @return VBox status code. VERR_NOT_FOUND if no corresponding object was found. + * @param pCtxCb Host callback context. + * @param pSvcCb Service callback data. + * + * @note Takes the read lock. + */ +int GuestSession::i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb)); + + AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Find the object. + */ + int rc = VERR_NOT_FOUND; + const uint32_t idObject = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCtxCb->uContextID); + SessionObjects::const_iterator itObj = mData.mObjects.find(idObject); + if (itObj != mData.mObjects.end()) + { + /* Set protocol version so that pSvcCb can be interpreted right. */ + pCtxCb->uProtocol = mData.mProtocolVersion; + + switch (itObj->second.enmType) + { + /* Note: The session object is special, as it does not inherit from GuestObject we could call + * its dispatcher for -- so treat this separately and call it directly. */ + case SESSIONOBJECTTYPE_SESSION: + { + alock.release(); + + rc = i_dispatchToThis(pCtxCb, pSvcCb); + break; + } + case SESSIONOBJECTTYPE_DIRECTORY: + { + ComObjPtr<GuestDirectory> pObj((GuestDirectory *)itObj->second.pObject); + AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); + + alock.release(); + + rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); + break; + } + case SESSIONOBJECTTYPE_FILE: + { + ComObjPtr<GuestFile> pObj((GuestFile *)itObj->second.pObject); + AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); + + alock.release(); + + rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); + break; + } + case SESSIONOBJECTTYPE_PROCESS: + { + ComObjPtr<GuestProcess> pObj((GuestProcess *)itObj->second.pObject); + AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); + + alock.release(); + + rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); + break; + } + default: + AssertMsgFailed(("%d\n", itObj->second.enmType)); + rc = VERR_INTERNAL_ERROR_4; + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main handler for guest session messages from the guest. + * + * @returns VBox status code. + * @param pCbCtx Host callback context from HGCM service. + * @param pSvcCbData HGCM service callback data. + * + * @note No locking! + */ +int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n", + mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCbData)); + int rc; + switch (pCbCtx->uMessage) + { + case GUEST_MSG_DISCONNECTED: + /** @todo Handle closing all guest objects. */ + rc = VERR_INTERNAL_ERROR; + break; + + case GUEST_MSG_SESSION_NOTIFY: /* Guest Additions >= 4.3.0. */ + { + rc = i_onSessionStatusChange(pCbCtx, pSvcCbData); + break; + } + + default: + rc = dispatchGeneric(pCbCtx, pSvcCbData); + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Validates and extracts file copy flags from a comma-separated string. + * + * @return COM status, error set on failure + * @param strFlags String to extract flags from. + * @param fStrict Whether to set an error when an unknown / invalid flag is detected. + * @param pfFlags Where to store the extracted (and validated) flags. + */ +HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, FileCopyFlag_T *pfFlags) +{ + unsigned fFlags = (unsigned)FileCopyFlag_None; + + /* Validate and set flags. */ + if (strFlags.isNotEmpty()) + { + const char *pszNext = strFlags.c_str(); + for (;;) + { + /* Find the next keyword, ignoring all whitespace. */ + pszNext = RTStrStripL(pszNext); + + const char * const pszComma = strchr(pszNext, ','); + size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext); + while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1])) + cchKeyword--; + + if (cchKeyword > 0) + { + /* Convert keyword to flag. */ +#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \ + && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0) + if (MATCH_KEYWORD("NoReplace")) + fFlags |= (unsigned)FileCopyFlag_NoReplace; + else if (MATCH_KEYWORD("FollowLinks")) + fFlags |= (unsigned)FileCopyFlag_FollowLinks; + else if (MATCH_KEYWORD("Update")) + fFlags |= (unsigned)FileCopyFlag_Update; + else if (fStrict) + return setError(E_INVALIDARG, tr("Invalid file copy flag: %.*s"), (int)cchKeyword, pszNext); +#undef MATCH_KEYWORD + } + if (!pszComma) + break; + pszNext = pszComma + 1; + } + } + + if (pfFlags) + *pfFlags = (FileCopyFlag_T)fFlags; + return S_OK; +} + +/** + * Checks if a file object exists and optionally returns its object. + * + * @returns \c true if file object exists, or \c false if not. + * @param uFileID ID of file object to check. + * @param pFile Where to return the found file object on success. + */ +inline bool GuestSession::i_fileExists(uint32_t uFileID, ComObjPtr<GuestFile> *pFile) +{ + SessionFiles::const_iterator it = mData.mFiles.find(uFileID); + if (it != mData.mFiles.end()) + { + if (pFile) + *pFile = it->second; + return true; + } + return false; +} + +/** + * Unregisters a file object from a guest session. + * + * @returns VBox status code. VERR_NOT_FOUND if the file is not registered (anymore). + * @param pFile File object to unregister from session. + * + * @note Takes the write lock. + */ +int GuestSession::i_fileUnregister(GuestFile *pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + LogFlowThisFunc(("pFile=%p\n", pFile)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + const uint32_t idObject = pFile->getObjectID(); + + LogFlowFunc(("Removing file (objectID=%RU32) ...\n", idObject)); + + int rc = i_objectUnregister(idObject); + if (RT_FAILURE(rc)) + return rc; + + SessionFiles::iterator itFiles = mData.mFiles.find(idObject); + AssertReturn(itFiles != mData.mFiles.end(), VERR_NOT_FOUND); + + /* Make sure to consume the pointer before the one of the iterator gets released. */ + ComObjPtr<GuestFile> pFileConsumed = pFile; + + LogFlowFunc(("Removing file ID=%RU32 (session %RU32, now total %zu files)\n", + pFileConsumed->getObjectID(), mData.mSession.mID, mData.mFiles.size())); + + rc = pFileConsumed->i_onUnregister(); + AssertRCReturn(rc, rc); + + mData.mFiles.erase(itFiles); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestFileRegisteredEvent(mEventSource, this, pFileConsumed, false /* Unregistered */); + + pFileConsumed.setNull(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Removes a file from the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param strPath Path of file on guest to remove. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestSession::i_fileRemove(const Utf8Str &strPath, int *prcGuest) +{ + LogFlowThisFunc(("strPath=%s\n", strPath.c_str())); + + GuestProcessStartupInfo procInfo; + GuestProcessStream streamOut; + + procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; + procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_RM); + + try + { + procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ + procInfo.mArguments.push_back(Utf8Str("--machinereadable")); + procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */ + procInfo.mArguments.push_back(strPath); /* The file we want to remove. */ + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + GuestCtrlStreamObjects stdOut; + int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest); + if (GuestProcess::i_isGuestError(vrc)) + { + if (!stdOut.empty()) + { + GuestFsObjData objData; + vrc = objData.FromRm(stdOut.at(0)); + if (RT_FAILURE(vrc)) + { + vrcGuest = vrc; + if (prcGuest) + *prcGuest = vrcGuest; + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + else + vrc = VERR_BROKEN_PIPE; + } + else if (prcGuest) + *prcGuest = vrcGuest; + + LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); + return vrc; +} + +/** + * Opens a file on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param aPath File path on guest to open. + * @param aAccessMode Access mode to use. + * @param aOpenAction Open action to use. + * @param aSharingMode Sharing mode to use. + * @param aCreationMode Creation mode to use. + * @param aFlags Open flags to use. + * @param pFile Where to return the file object on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestSession::i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, + FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector<FileOpenExFlag_T> &aFlags, + ComObjPtr<GuestFile> &pFile, int *prcGuest) +{ + GuestFileOpenInfo openInfo; + openInfo.mFilename = aPath; + openInfo.mCreationMode = aCreationMode; + openInfo.mAccessMode = aAccessMode; + openInfo.mOpenAction = aOpenAction; + openInfo.mSharingMode = aSharingMode; + + /* Combine and validate flags. */ + for (size_t i = 0; i < aFlags.size(); i++) + openInfo.mfOpenEx |= aFlags[i]; + /* Validation is done in i_fileOpen(). */ + + return i_fileOpen(openInfo, pFile, prcGuest); +} + +/** + * Opens a file on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param openInfo Open information to use for opening the file. + * @param pFile Where to return the file object on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the write lock. + */ +int GuestSession::i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr<GuestFile> &pFile, int *prcGuest) +{ + LogFlowThisFunc(("strFile=%s, enmAccessMode=0x%x, enmOpenAction=0x%x, uCreationMode=%RU32, mfOpenEx=%RU32\n", + openInfo.mFilename.c_str(), openInfo.mAccessMode, openInfo.mOpenAction, openInfo.mCreationMode, + openInfo.mfOpenEx)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Guest Additions < 4.3 don't support handling guest files, skip. */ + if (mData.mProtocolVersion < 2) + { + if (prcGuest) + *prcGuest = VERR_NOT_SUPPORTED; + return VERR_GSTCTL_GUEST_ERROR; + } + + if (!openInfo.IsValid()) + return VERR_INVALID_PARAMETER; + + /* Create the directory object. */ + HRESULT hr = pFile.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + /* Register a new object ID. */ + uint32_t idObject; + int rc = i_objectRegister(pFile, SESSIONOBJECTTYPE_FILE, &idObject); + if (RT_FAILURE(rc)) + { + pFile.setNull(); + return rc; + } + + Console *pConsole = mParent->i_getConsole(); + AssertPtr(pConsole); + + rc = pFile->init(pConsole, this /* GuestSession */, idObject, openInfo); + if (RT_FAILURE(rc)) + return rc; + + /* + * Since this is a synchronous guest call we have to + * register the file object first, releasing the session's + * lock and then proceed with the actual opening command + * -- otherwise the file's opening callback would hang + * because the session's lock still is in place. + */ + try + { + /* Add the created file to our vector. */ + mData.mFiles[idObject] = pFile; + + LogFlowFunc(("Added new guest file \"%s\" (Session: %RU32) (now total %zu files)\n", + openInfo.mFilename.c_str(), mData.mSession.mID, mData.mFiles.size())); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestFileRegisteredEvent(mEventSource, this, pFile, true /* Registered */); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + rc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &rcGuest); + if ( rc == VERR_GSTCTL_GUEST_ERROR + && prcGuest) + { + *prcGuest = rcGuest; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Queries information from a file on the guest. + * + * @returns IPRT status code. VERR_NOT_A_FILE if the queried file system object on the guest is not a file, + * or VERR_GSTCTL_GUEST_ERROR if prcGuest contains more error information from the guest. + * @param strPath Absolute path of file to query information for. + * @param fFollowSymlinks Whether or not to follow symbolic links on the guest. + * @param objData Where to store the acquired information. + * @param prcGuest Where to store the guest rc. Optional. + */ +int GuestSession::i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest) +{ + LogFlowThisFunc(("strPath=%s fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks)); + + int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); + if (RT_SUCCESS(vrc)) + { + vrc = objData.mType == FsObjType_File + ? VINF_SUCCESS : VERR_NOT_A_FILE; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Queries the size of a file on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @retval VERR_ + * @param strPath Path of file on guest to query size for. + * @param fFollowSymlinks \c true when wanting to follow symbolic links, \c false if not. + * @param pllSize Where to return the queried file size on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestSession::i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *prcGuest) +{ + AssertPtrReturn(pllSize, VERR_INVALID_POINTER); + + GuestFsObjData objData; + int vrc = i_fileQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); + if (RT_SUCCESS(vrc)) + *pllSize = objData.mObjectSize; + + return vrc; +} + +/** + * Queries information of a file system object (file, directory, ...). + * + * @return IPRT status code. + * @param strPath Path to file system object to query information for. + * @param fFollowSymlinks Whether to follow symbolic links or not. + * @param objData Where to return the file system object data, if found. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + * Any other return code indicates some host side error. + */ +int GuestSession::i_fsQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest) +{ + LogFlowThisFunc(("strPath=%s\n", strPath.c_str())); + + /** @todo Merge this with IGuestFile::queryInfo(). */ + GuestProcessStartupInfo procInfo; + procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; + try + { + procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_STAT); + procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ + procInfo.mArguments.push_back(Utf8Str("--machinereadable")); + if (fFollowSymlinks) + procInfo.mArguments.push_back(Utf8Str("-L")); + procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */ + procInfo.mArguments.push_back(strPath); + } + catch (std::bad_alloc &) + { + Log(("Out of memory!\n")); + return VERR_NO_MEMORY; + } + + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + GuestCtrlStreamObjects stdOut; + int vrc = GuestProcessTool::runEx(this, procInfo, + &stdOut, 1 /* cStrmOutObjects */, + &vrcGuest); + if (!GuestProcess::i_isGuestError(vrc)) + { + if (!stdOut.empty()) + { + vrc = objData.FromStat(stdOut.at(0)); + if (RT_FAILURE(vrc)) + { + vrcGuest = vrc; + if (prcGuest) + *prcGuest = vrcGuest; + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + else + vrc = VERR_BROKEN_PIPE; + } + else if (prcGuest) + *prcGuest = vrcGuest; + + LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); + return vrc; +} + +/** + * Returns the guest credentials of a guest session. + * + * @returns Guest credentials. + */ +const GuestCredentials& GuestSession::i_getCredentials(void) +{ + return mData.mCredentials; +} + +/** + * Returns the guest session (friendly) name. + * + * @returns Guest session name. + */ +Utf8Str GuestSession::i_getName(void) +{ + return mData.mSession.mName; +} + +/** + * Returns a stringified error description for a given guest result code. + * + * @returns Stringified error description. + */ +/* static */ +Utf8Str GuestSession::i_guestErrorToString(int rcGuest) +{ + Utf8Str strError; + + /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ + switch (rcGuest) + { + case VERR_INVALID_VM_HANDLE: + strError.printf(tr("VMM device is not available (is the VM running?)")); + break; + + case VERR_HGCM_SERVICE_NOT_FOUND: + strError.printf(tr("The guest execution service is not available")); + break; + + case VERR_ACCOUNT_RESTRICTED: + strError.printf(tr("The specified user account on the guest is restricted and can't be used to logon")); + break; + + case VERR_AUTHENTICATION_FAILURE: + strError.printf(tr("The specified user was not able to logon on guest")); + break; + + case VERR_TIMEOUT: + strError.printf(tr("The guest did not respond within time")); + break; + + case VERR_CANCELLED: + strError.printf(tr("The session operation was canceled")); + break; + + case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED: + strError.printf(tr("Maximum number of concurrent guest processes has been reached")); + break; + + case VERR_NOT_FOUND: + strError.printf(tr("The guest execution service is not ready (yet)")); + break; + + default: + strError.printf("%Rrc", rcGuest); + break; + } + + return strError; +} + +/** + * Returns whether the session is in a started state or not. + * + * @returns \c true if in a started state, or \c false if not. + */ +bool GuestSession::i_isStarted(void) const +{ + return (mData.mStatus == GuestSessionStatus_Started); +} + +/** + * Checks if this session is ready state where it can handle + * all session-bound actions (like guest processes, guest files). + * Only used by official API methods. Will set an external + * error when not ready. + */ +HRESULT GuestSession::i_isStartedExternal(void) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /** @todo Be a bit more informative. */ + if (!i_isStarted()) + return setError(E_UNEXPECTED, tr("Session is not in started state")); + + return S_OK; +} + +/** + * Returns whether a guest session status implies a terminated state or not. + * + * @returns \c true if it's a terminated state, or \c false if not. + */ +/* static */ +bool GuestSession::i_isTerminated(GuestSessionStatus_T enmStatus) +{ + switch (enmStatus) + { + case GuestSessionStatus_Terminated: + RT_FALL_THROUGH(); + case GuestSessionStatus_TimedOutKilled: + RT_FALL_THROUGH(); + case GuestSessionStatus_TimedOutAbnormally: + RT_FALL_THROUGH(); + case GuestSessionStatus_Down: + RT_FALL_THROUGH(); + case GuestSessionStatus_Error: + return true; + + default: + break; + } + + return false; +} + +/** + * Returns whether the session is in a terminated state or not. + * + * @returns \c true if in a terminated state, or \c false if not. + */ +bool GuestSession::i_isTerminated(void) const +{ + return GuestSession::i_isTerminated(mData.mStatus); +} + +/** + * Called by IGuest right before this session gets removed from + * the public session list. + * + * @note Takes the write lock. + */ +int GuestSession::i_onRemove(void) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = i_objectsUnregister(); + + /* + * Note: The event source stuff holds references to this object, + * so make sure that this is cleaned up *before* calling uninit. + */ + if (!mEventSource.isNull()) + { + mEventSource->UnregisterListener(mLocalListener); + + mLocalListener.setNull(); + unconst(mEventSource).setNull(); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Handles guest session status changes from the guest. + * + * @returns VBox status code. + * @param pCbCtx Host callback context from HGCM service. + * @param pSvcCbData HGCM service callback data. + * + * @note Takes the read lock (for session ID lookup). + */ +int GuestSession::i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + /* pCallback is optional. */ + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + if (pSvcCbData->mParms < 3) + return VERR_INVALID_PARAMETER; + + CALLBACKDATA_SESSION_NOTIFY dataCb; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uType); + AssertRCReturn(vrc, vrc); + vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uResult); + AssertRCReturn(vrc, vrc); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("ID=%RU32, uType=%RU32, rcGuest=%Rrc\n", + mData.mSession.mID, dataCb.uType, dataCb.uResult)); + + GuestSessionStatus_T sessionStatus = GuestSessionStatus_Undefined; + + int rcGuest = dataCb.uResult; /** @todo uint32_t vs. int. */ + switch (dataCb.uType) + { + case GUEST_SESSION_NOTIFYTYPE_ERROR: + sessionStatus = GuestSessionStatus_Error; + LogRel(("Guest Control: Error starting Session '%s' (%Rrc) \n", mData.mSession.mName.c_str(), rcGuest)); + break; + + case GUEST_SESSION_NOTIFYTYPE_STARTED: + sessionStatus = GuestSessionStatus_Started; +#if 0 /** @todo If we get some environment stuff along with this kind notification: */ + const char *pszzEnvBlock = ...; + uint32_t cbEnvBlock = ...; + if (!mData.mpBaseEnvironment) + { + GuestEnvironment *pBaseEnv; + try { pBaseEnv = new GuestEnvironment(); } catch (std::bad_alloc &) { pBaseEnv = NULL; } + if (pBaseEnv) + { + int vrc = pBaseEnv->initNormal(Guest.i_isGuestInWindowsNtFamily() ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0); + if (RT_SUCCESS(vrc)) + vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock); + if (RT_SUCCESS(vrc)) + mData.mpBaseEnvironment = pBaseEnv; + else + pBaseEnv->release(); + } + } +#endif + LogRel(("Guest Control: Session '%s' was successfully started\n", mData.mSession.mName.c_str())); + break; + + case GUEST_SESSION_NOTIFYTYPE_TEN: + LogRel(("Guest Control: Session '%s' was terminated normally with exit code %#x\n", + mData.mSession.mName.c_str(), dataCb.uResult)); + sessionStatus = GuestSessionStatus_Terminated; + break; + + case GUEST_SESSION_NOTIFYTYPE_TEA: + LogRel(("Guest Control: Session '%s' was terminated abnormally\n", mData.mSession.mName.c_str())); + sessionStatus = GuestSessionStatus_Terminated; + /* dataCb.uResult is undefined. */ + break; + + case GUEST_SESSION_NOTIFYTYPE_TES: + LogRel(("Guest Control: Session '%s' was terminated via signal %#x\n", mData.mSession.mName.c_str(), dataCb.uResult)); + sessionStatus = GuestSessionStatus_Terminated; + break; + + case GUEST_SESSION_NOTIFYTYPE_TOK: + sessionStatus = GuestSessionStatus_TimedOutKilled; + LogRel(("Guest Control: Session '%s' timed out and was killed\n", mData.mSession.mName.c_str())); + break; + + case GUEST_SESSION_NOTIFYTYPE_TOA: + sessionStatus = GuestSessionStatus_TimedOutAbnormally; + LogRel(("Guest Control: Session '%s' timed out and was not killed successfully\n", mData.mSession.mName.c_str())); + break; + + case GUEST_SESSION_NOTIFYTYPE_DWN: + sessionStatus = GuestSessionStatus_Down; + LogRel(("Guest Control: Session '%s' got killed as guest service/OS is down\n", mData.mSession.mName.c_str())); + break; + + case GUEST_SESSION_NOTIFYTYPE_UNDEFINED: + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + + /* Leave the lock, as i_setSessionStatus() below will require a write lock for actually + * committing the session state. */ + alock.release(); + + if (RT_SUCCESS(vrc)) + { + if (RT_FAILURE(rcGuest)) + sessionStatus = GuestSessionStatus_Error; + } + + /* Set the session status. */ + if (RT_SUCCESS(vrc)) + vrc = i_setSessionStatus(sessionStatus, rcGuest); + + LogFlowThisFunc(("ID=%RU32, rcGuest=%Rrc\n", mData.mSession.mID, rcGuest)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Returns the path separation style used on the guest. + * + * @returns Separation style used on the guest. + */ +PathStyle_T GuestSession::i_getGuestPathStyle(void) +{ + PathStyle_T enmPathStyle; + + VBOXOSTYPE enmOsType = mParent->i_getGuestOSType(); + if (enmOsType < VBOXOSTYPE_DOS) + { + LogFlowFunc(("returns PathStyle_Unknown\n")); + enmPathStyle = PathStyle_Unknown; + } + else if (enmOsType < VBOXOSTYPE_Linux) + { + LogFlowFunc(("returns PathStyle_DOS\n")); + enmPathStyle = PathStyle_DOS; + } + else + { + LogFlowFunc(("returns PathStyle_UNIX\n")); + enmPathStyle = PathStyle_UNIX; + } + + return enmPathStyle; +} + +/** + * Returns the path separation style used on the host. + * + * @returns Separation style used on the host. + */ +/* static */ +PathStyle_T GuestSession::i_getHostPathStyle(void) +{ +#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS + return PathStyle_DOS; +#else + return PathStyle_UNIX; +#endif +} + +/** + * Starts the guest session on the guest. + * + * @returns VBox status code. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the read and write locks. + */ +int GuestSession::i_startSession(int *prcGuest) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mID=%RU32, mName=%s, uProtocolVersion=%RU32, openFlags=%x, openTimeoutMS=%RU32\n", + mData.mSession.mID, mData.mSession.mName.c_str(), mData.mProtocolVersion, + mData.mSession.mOpenFlags, mData.mSession.mOpenTimeoutMS)); + + /* Guest Additions < 4.3 don't support opening dedicated + guest sessions. Simply return success here. */ + if (mData.mProtocolVersion < 2) + { + alock.release(); /* Release lock before changing status. */ + + /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Started, VINF_SUCCESS); + LogFlowThisFunc(("Installed Guest Additions don't support opening dedicated sessions, skipping\n")); + return VINF_SUCCESS; + } + + if (mData.mStatus != GuestSessionStatus_Undefined) + return VINF_SUCCESS; + + /** @todo mData.mSession.uFlags validation. */ + + alock.release(); /* Release lock before changing status. */ + + /* Set current session status. */ + int vrc = i_setSessionStatus(GuestSessionStatus_Starting, VINF_SUCCESS); + if (RT_FAILURE(vrc)) + return vrc; + + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); + + vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + alock.acquire(); /* Re-acquire lock before accessing session attributes below. */ + + VBOXHGCMSVCPARM paParms[8]; + + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], mData.mProtocolVersion); + HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mUser.c_str(), + (ULONG)mData.mCredentials.mUser.length() + 1); + HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mPassword.c_str(), + (ULONG)mData.mCredentials.mPassword.length() + 1); + HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mDomain.c_str(), + (ULONG)mData.mCredentials.mDomain.length() + 1); + HGCMSvcSetU32(&paParms[i++], mData.mSession.mOpenFlags); + + alock.release(); /* Drop lock before sending. */ + + vrc = i_sendMessage(HOST_MSG_SESSION_CREATE, i, paParms, VBOX_GUESTCTRL_DST_ROOT_SVC); + if (RT_SUCCESS(vrc)) + { + vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Start, + 30 * 1000 /* 30s timeout */, + NULL /* Session status */, prcGuest); + } + else + { + /* + * Unable to start guest session - update its current state. + * Since there is no (official API) way to recover a failed guest session + * this also marks the end state. Internally just calling this + * same function again will work though. + */ + /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Error, vrc); + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Starts the guest session asynchronously in a separate worker thread. + * + * @returns IPRT status code. + */ +int GuestSession::i_startSessionAsync(void) +{ + LogFlowThisFuncEnter(); + + /* Create task: */ + GuestSessionTaskInternalStart *pTask = NULL; + try + { + pTask = new GuestSessionTaskInternalStart(this); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + if (pTask->isOk()) + { + /* Kick off the thread: */ + HRESULT hrc = pTask->createThread(); + pTask = NULL; /* Not valid anymore, not even on failure! */ + if (SUCCEEDED(hrc)) + { + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; + } + LogFlow(("GuestSession: Failed to create thread for GuestSessionTaskInternalOpen task.\n")); + } + else + LogFlow(("GuestSession: GuestSessionTaskInternalStart creation failed: %Rhrc.\n", pTask->rc())); + LogFlowFuncLeaveRC(VERR_GENERAL_FAILURE); + return VERR_GENERAL_FAILURE; +} + +/** + * Static function to start a guest session asynchronously. + * + * @returns IPRT status code. + * @param pTask Task object to use for starting the guest session. + */ +/* static */ +int GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask) +{ + LogFlowFunc(("pTask=%p\n", pTask)); + AssertPtr(pTask); + + const ComObjPtr<GuestSession> pSession(pTask->Session()); + Assert(!pSession.isNull()); + + AutoCaller autoCaller(pSession); + if (FAILED(autoCaller.rc())) + return VERR_COM_INVALID_OBJECT_STATE; + + int vrc = pSession->i_startSession(NULL /* Guest rc, ignored */); + /* Nothing to do here anymore. */ + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Registers an object with the session, i.e. allocates an object ID. + * + * @return VBox status code. + * @retval VERR_GSTCTL_MAX_OBJECTS_REACHED if the maximum of concurrent objects + * is reached. + * @param pObject Guest object to register (weak pointer). Optional. + * @param enmType Session object type to register. + * @param pidObject Where to return the object ID on success. Optional. + */ +int GuestSession::i_objectRegister(GuestObject *pObject, SESSIONOBJECTTYPE enmType, uint32_t *pidObject) +{ + /* pObject can be NULL. */ + /* pidObject is optional. */ + + /* + * Pick a random bit as starting point. If it's in use, search forward + * for a free one, wrapping around. We've reserved both the zero'th and + * max-1 IDs (see Data constructor). + */ + uint32_t idObject = RTRandU32Ex(1, VBOX_GUESTCTRL_MAX_OBJECTS - 2); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject)) + { /* likely */ } + else if (mData.mObjects.size() < VBOX_GUESTCTRL_MAX_OBJECTS - 2 /* First and last are not used */) + { + /* Forward search. */ + int iHit = ASMBitNextClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS, idObject); + if (iHit < 0) + iHit = ASMBitFirstClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS); + AssertLogRelMsgReturn(iHit >= 0, ("object count: %#zu\n", mData.mObjects.size()), VERR_GSTCTL_MAX_CID_OBJECTS_REACHED); + idObject = iHit; + AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2); + } + else + { + LogFunc(("Maximum number of objects reached (enmType=%RU32, %zu objects)\n", enmType, mData.mObjects.size())); + return VERR_GSTCTL_MAX_CID_OBJECTS_REACHED; + } + + Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size())); + + try + { + mData.mObjects[idObject].pObject = pObject; /* Can be NULL. */ + mData.mObjects[idObject].enmType = enmType; + mData.mObjects[idObject].msBirth = RTTimeMilliTS(); + } + catch (std::bad_alloc &) + { + ASMBitClear(&mData.bmObjectIds[0], idObject); + return VERR_NO_MEMORY; + } + + if (pidObject) + *pidObject = idObject; + + return VINF_SUCCESS; +} + +/** + * Unregisters an object from the session objects list. + * + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_FOUND if the object ID was not found. + * @param idObject Object ID to unregister. + * + * @note Takes the write lock. + */ +int GuestSession::i_objectUnregister(uint32_t idObject) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int rc = VINF_SUCCESS; + AssertMsgStmt(ASMBitTestAndClear(&mData.bmObjectIds, idObject), ("idObject=%#x\n", idObject), rc = VERR_NOT_FOUND); + + SessionObjects::iterator ItObj = mData.mObjects.find(idObject); + AssertMsgReturn(ItObj != mData.mObjects.end(), ("idObject=%#x\n", idObject), VERR_NOT_FOUND); + mData.mObjects.erase(ItObj); + + return rc; +} + +/** + * Unregisters all objects from the session list. + * + * @returns VBox status code. + * + * @note Takes the write lock. + */ +int GuestSession::i_objectsUnregister(void) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("Unregistering directories (%zu total)\n", mData.mDirectories.size())); + + SessionDirectories::iterator itDirs; + while ((itDirs = mData.mDirectories.begin()) != mData.mDirectories.end()) + { + alock.release(); + i_directoryUnregister(itDirs->second); + alock.acquire(); + } + + Assert(mData.mDirectories.size() == 0); + mData.mDirectories.clear(); + + LogFlowThisFunc(("Unregistering files (%zu total)\n", mData.mFiles.size())); + + SessionFiles::iterator itFiles; + while ((itFiles = mData.mFiles.begin()) != mData.mFiles.end()) + { + alock.release(); + i_fileUnregister(itFiles->second); + alock.acquire(); + } + + Assert(mData.mFiles.size() == 0); + mData.mFiles.clear(); + + LogFlowThisFunc(("Unregistering processes (%zu total)\n", mData.mProcesses.size())); + + SessionProcesses::iterator itProcs; + while ((itProcs = mData.mProcesses.begin()) != mData.mProcesses.end()) + { + alock.release(); + i_processUnregister(itProcs->second); + alock.acquire(); + } + + Assert(mData.mProcesses.size() == 0); + mData.mProcesses.clear(); + + return VINF_SUCCESS; +} + +/** + * Notifies all registered objects about a guest session status change. + * + * @returns VBox status code. + * @param enmSessionStatus Session status to notify objects about. + */ +int GuestSession::i_objectsNotifyAboutStatusChange(GuestSessionStatus_T enmSessionStatus) +{ + LogFlowThisFunc(("enmSessionStatus=%RU32\n", enmSessionStatus)); + + int vrc = VINF_SUCCESS; + + SessionObjects::iterator itObjs = mData.mObjects.begin(); + while (itObjs != mData.mObjects.end()) + { + GuestObject *pObj = itObjs->second.pObject; + if (pObj) /* pObject can be NULL (weak pointer). */ + { + int vrc2 = pObj->i_onSessionStatusChange(enmSessionStatus); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + /* If the session got terminated, make sure to cancel all wait events for + * the current object. */ + if (i_isTerminated()) + pObj->cancelWaitEvents(); + } + + ++itObjs; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Renames a path on the guest. + * + * @returns VBox status code. + * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. + * @param strSource Source path on guest to rename. + * @param strDest Destination path on guest to rename \a strSource to. + * @param uFlags Renaming flags. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * @note Takes the read lock. + */ +int GuestSession::i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *prcGuest) +{ + AssertReturn(!(uFlags & ~PATHRENAME_FLAG_VALID_MASK), VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("strSource=%s, strDest=%s, uFlags=0x%x\n", + strSource.c_str(), strDest.c_str(), uFlags)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + GuestWaitEvent *pEvent = NULL; + int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[8]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetPv(&paParms[i++], (void*)strSource.c_str(), + (ULONG)strSource.length() + 1); + HGCMSvcSetPv(&paParms[i++], (void*)strDest.c_str(), + (ULONG)strDest.length() + 1); + HGCMSvcSetU32(&paParms[i++], uFlags); + + alock.release(); /* Drop lock before sending. */ + + vrc = i_sendMessage(HOST_MSG_PATH_RENAME, i, paParms); + if (RT_SUCCESS(vrc)) + { + vrc = pEvent->Wait(30 * 1000); + if ( vrc == VERR_GSTCTL_GUEST_ERROR + && prcGuest) + *prcGuest = pEvent->GuestResult(); + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Returns the user's absolute documents path, if any. + * + * @returns VBox status code. + * @param strPath Where to store the user's document path. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + * Any other return code indicates some host side error. + * + * @note Takes the read lock. + */ +int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *prcGuest) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /** @todo Cache the user's document path? */ + + GuestWaitEvent *pEvent = NULL; + int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[2]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + + alock.release(); /* Drop lock before sending. */ + + vrc = i_sendMessage(HOST_MSG_PATH_USER_DOCUMENTS, i, paParms); + if (RT_SUCCESS(vrc)) + { + vrc = pEvent->Wait(30 * 1000); + if (RT_SUCCESS(vrc)) + { + strPath = pEvent->Payload().ToString(); + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + if (prcGuest) + *prcGuest = pEvent->GuestResult(); + } + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Returns the user's absolute home path, if any. + * + * @returns VBox status code. + * @param strPath Where to store the user's home path. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + * Any other return code indicates some host side error. + * + * @note Takes the read lock. + */ +int GuestSession::i_pathUserHome(Utf8Str &strPath, int *prcGuest) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /** @todo Cache the user's home path? */ + + GuestWaitEvent *pEvent = NULL; + int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[2]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + + alock.release(); /* Drop lock before sending. */ + + vrc = i_sendMessage(HOST_MSG_PATH_USER_HOME, i, paParms); + if (RT_SUCCESS(vrc)) + { + vrc = pEvent->Wait(30 * 1000); + if (RT_SUCCESS(vrc)) + { + strPath = pEvent->Payload().ToString(); + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + if (prcGuest) + *prcGuest = pEvent->GuestResult(); + } + } + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Unregisters a process object from a guest session. + * + * @returns VBox status code. VERR_NOT_FOUND if the process is not registered (anymore). + * @param pProcess Process object to unregister from session. + * + * @note Takes the write lock. + */ +int GuestSession::i_processUnregister(GuestProcess *pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + LogFlowThisFunc(("pProcess=%p\n", pProcess)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + const uint32_t idObject = pProcess->getObjectID(); + + LogFlowFunc(("Removing process (objectID=%RU32) ...\n", idObject)); + + int rc = i_objectUnregister(idObject); + if (RT_FAILURE(rc)) + return rc; + + SessionProcesses::iterator itProcs = mData.mProcesses.find(idObject); + AssertReturn(itProcs != mData.mProcesses.end(), VERR_NOT_FOUND); + + /* Make sure to consume the pointer before the one of the iterator gets released. */ + ComObjPtr<GuestProcess> pProc = pProcess; + + ULONG uPID; + HRESULT hr = pProc->COMGETTER(PID)(&uPID); + ComAssertComRC(hr); + + LogFlowFunc(("Removing process ID=%RU32 (session %RU32, guest PID %RU32, now total %zu processes)\n", + idObject, mData.mSession.mID, uPID, mData.mProcesses.size())); + + rc = pProcess->i_onUnregister(); + AssertRCReturn(rc, rc); + + mData.mProcesses.erase(itProcs); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProc, uPID, false /* Process unregistered */); + + pProc.setNull(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Creates but does *not* start the process yet. + * + * See GuestProcess::startProcess() or GuestProcess::startProcessAsync() for + * starting the process. + * + * @returns IPRT status code. + * @param procInfo Process startup info to use for starting the process. + * @param pProcess Where to return the created guest process object on success. + * + * @note Takes the write lock. + */ +int GuestSession::i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr<GuestProcess> &pProcess) +{ + LogFlowFunc(("mExe=%s, mFlags=%x, mTimeoutMS=%RU32\n", + procInfo.mExecutable.c_str(), procInfo.mFlags, procInfo.mTimeoutMS)); +#ifdef DEBUG + if (procInfo.mArguments.size()) + { + LogFlowFunc(("Arguments:")); + ProcessArguments::const_iterator it = procInfo.mArguments.begin(); + while (it != procInfo.mArguments.end()) + { + LogFlow((" %s", (*it).c_str())); + ++it; + } + LogFlow(("\n")); + } +#endif + + /* Validate flags. */ + if (procInfo.mFlags) + { + if ( !(procInfo.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses) + && !(procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly) + && !(procInfo.mFlags & ProcessCreateFlag_Hidden) + && !(procInfo.mFlags & ProcessCreateFlag_Profile) + && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdOut) + && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdErr)) + { + return VERR_INVALID_PARAMETER; + } + } + + if ( (procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly) + && ( (procInfo.mFlags & ProcessCreateFlag_WaitForStdOut) + || (procInfo.mFlags & ProcessCreateFlag_WaitForStdErr) + ) + ) + { + return VERR_INVALID_PARAMETER; + } + + if (procInfo.mPriority) + { + if (!(procInfo.mPriority & ProcessPriority_Default)) + return VERR_INVALID_PARAMETER; + } + + /* Adjust timeout. + * If set to 0, we define an infinite timeout (unlimited process run time). */ + if (procInfo.mTimeoutMS == 0) + procInfo.mTimeoutMS = UINT32_MAX; + + /** @todo Implement process priority + affinity. */ + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Create the process object. */ + HRESULT hr = pProcess.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + /* Register a new object ID. */ + uint32_t idObject; + int rc = i_objectRegister(pProcess, SESSIONOBJECTTYPE_PROCESS, &idObject); + if (RT_FAILURE(rc)) + { + pProcess.setNull(); + return rc; + } + + rc = pProcess->init(mParent->i_getConsole() /* Console */, this /* Session */, idObject, + procInfo, mData.mpBaseEnvironment); + if (RT_FAILURE(rc)) + return rc; + + /* Add the created process to our map. */ + try + { + mData.mProcesses[idObject] = pProcess; + + LogFlowFunc(("Added new process (Session: %RU32) with process ID=%RU32 (now total %zu processes)\n", + mData.mSession.mID, idObject, mData.mProcesses.size())); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProcess, 0 /* PID */, true /* Process registered */); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Checks if a process object exists and optionally returns its object. + * + * @returns \c true if process object exists, or \c false if not. + * @param uProcessID ID of process object to check. + * @param pProcess Where to return the found process object on success. + * + * @note No locking done! + */ +inline bool GuestSession::i_processExists(uint32_t uProcessID, ComObjPtr<GuestProcess> *pProcess) +{ + SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID); + if (it != mData.mProcesses.end()) + { + if (pProcess) + *pProcess = it->second; + return true; + } + return false; +} + +/** + * Returns the process object from a guest PID. + * + * @returns VBox status code. + * @param uPID Guest PID to get process object for. + * @param pProcess Where to return the process object on success. + * + * @note No locking done! + */ +inline int GuestSession::i_processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess) +{ + AssertReturn(uPID, false); + /* pProcess is optional. */ + + SessionProcesses::iterator itProcs = mData.mProcesses.begin(); + for (; itProcs != mData.mProcesses.end(); ++itProcs) + { + ComObjPtr<GuestProcess> pCurProc = itProcs->second; + AutoCaller procCaller(pCurProc); + if (!procCaller.isOk()) + return VERR_COM_INVALID_OBJECT_STATE; + + ULONG uCurPID; + HRESULT hr = pCurProc->COMGETTER(PID)(&uCurPID); + ComAssertComRC(hr); + + if (uCurPID == uPID) + { + if (pProcess) + *pProcess = pCurProc; + return VINF_SUCCESS; + } + } + + return VERR_NOT_FOUND; +} + +/** + * Sends a message to the HGCM host service. + * + * @returns VBox status code. + * @param uMessage Message ID to send. + * @param uParms Number of parameters in \a paParms to send. + * @param paParms Array of HGCM parameters to send. + * @param fDst Host message destination flags of type VBOX_GUESTCTRL_DST_XXX. + */ +int GuestSession::i_sendMessage(uint32_t uMessage, uint32_t uParms, PVBOXHGCMSVCPARM paParms, + uint64_t fDst /*= VBOX_GUESTCTRL_DST_SESSION*/) +{ + LogFlowThisFuncEnter(); + +#ifndef VBOX_GUESTCTRL_TEST_CASE + ComObjPtr<Console> pConsole = mParent->i_getConsole(); + Assert(!pConsole.isNull()); + + /* Forward the information to the VMM device. */ + VMMDev *pVMMDev = pConsole->i_getVMMDev(); + AssertPtr(pVMMDev); + + LogFlowThisFunc(("uMessage=%RU32 (%s), uParms=%RU32\n", uMessage, GstCtrlHostMsgtoStr((guestControl::eHostMsg)uMessage), uParms)); + + /* HACK ALERT! We extend the first parameter to 64-bit and use the + two topmost bits for call destination information. */ + Assert(fDst == VBOX_GUESTCTRL_DST_SESSION || fDst == VBOX_GUESTCTRL_DST_ROOT_SVC || fDst == VBOX_GUESTCTRL_DST_BOTH); + Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); + paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT; + paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | fDst; + + /* Make the call. */ + int vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, uParms, paParms); + if (RT_FAILURE(vrc)) + { + /** @todo What to do here? */ + } +#else + /* Not needed within testcases. */ + int vrc = VINF_SUCCESS; +#endif + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sets the guest session's current status. + * + * @returns VBox status code. + * @param sessionStatus Session status to set. + * @param sessionRc Session result to set (for error handling). + * + * @note Takes the write lock. + */ +int GuestSession::i_setSessionStatus(GuestSessionStatus_T sessionStatus, int sessionRc) +{ + LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, sessionRc=%Rrc\n", + mData.mStatus, sessionStatus, sessionRc)); + + if (sessionStatus == GuestSessionStatus_Error) + { + AssertMsg(RT_FAILURE(sessionRc), ("Guest rc must be an error (%Rrc)\n", sessionRc)); + /* Do not allow overwriting an already set error. If this happens + * this means we forgot some error checking/locking somewhere. */ + AssertMsg(RT_SUCCESS(mData.mRC), ("Guest rc already set (to %Rrc)\n", mData.mRC)); + } + else + AssertMsg(RT_SUCCESS(sessionRc), ("Guest rc must not be an error (%Rrc)\n", sessionRc)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = VINF_SUCCESS; + + if (mData.mStatus != sessionStatus) + { + mData.mStatus = sessionStatus; + mData.mRC = sessionRc; + + /* Make sure to notify all underlying objects first. */ + vrc = i_objectsNotifyAboutStatusChange(sessionStatus); + + ComObjPtr<VirtualBoxErrorInfo> errorInfo; + HRESULT hr = errorInfo.createObject(); + ComAssertComRC(hr); + int rc2 = errorInfo->initEx(VBOX_E_IPRT_ERROR, sessionRc, + COM_IIDOF(IGuestSession), getComponentName(), + i_guestErrorToString(sessionRc)); + AssertRC(rc2); + + alock.release(); /* Release lock before firing off event. */ + + ::FireGuestSessionStateChangedEvent(mEventSource, this, mData.mSession.mID, sessionStatus, errorInfo); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** @todo Unused --remove? */ +int GuestSession::i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */) +{ + RT_NOREF(enmWaitResult, rc); + + /*LogFlowThisFunc(("enmWaitResult=%d, rc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n", + enmWaitResult, rc, mData.mWaitCount, mData.mWaitEvent));*/ + + /* Note: No write locking here -- already done in the caller. */ + + int vrc = VINF_SUCCESS; + /*if (mData.mWaitEvent) + vrc = mData.mWaitEvent->Signal(enmWaitResult, rc);*/ + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Shuts down (and optionally powers off / reboots) the guest. + * Needs supported Guest Additions installed. + * + * @returns VBox status code. VERR_NOT_SUPPORTED if not supported by Guest Additions. + * @param fFlags Guest shutdown flags. + * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. + * Any other return code indicates some host side error. + * + * @note Takes the read lock. + */ +int GuestSession::i_shutdown(uint32_t fFlags, int *prcGuest) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertPtrReturn(mParent, VERR_INVALID_POINTER); + if (!(mParent->i_getGuestControlFeatures0() & VBOX_GUESTCTRL_GF_0_SHUTDOWN)) + return VERR_NOT_SUPPORTED; + + LogRel(("Guest Control: Shutting down guest (flags = %#x) ...\n", fFlags)); + + GuestWaitEvent *pEvent = NULL; + int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); + if (RT_FAILURE(vrc)) + return vrc; + + /* Prepare HGCM call. */ + VBOXHGCMSVCPARM paParms[2]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], fFlags); + + alock.release(); /* Drop lock before sending. */ + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + + vrc = i_sendMessage(HOST_MSG_SHUTDOWN, i, paParms); + if (RT_SUCCESS(vrc)) + { + vrc = pEvent->Wait(30 * 1000); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + rcGuest = pEvent->GuestResult(); + } + } + + if (RT_FAILURE(vrc)) + { + LogRel(("Guest Control: Shutting down guest failed, rc=%Rrc\n", + vrc == VERR_GSTCTL_GUEST_ERROR ? rcGuest : vrc)); + + if ( vrc == VERR_GSTCTL_GUEST_ERROR + && prcGuest) + *prcGuest = rcGuest; + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Determines the protocol version (sets mData.mProtocolVersion). + * + * This is called from the init method prior to to establishing a guest + * session. + * + * @returns VBox status code. + */ +int GuestSession::i_determineProtocolVersion(void) +{ + /* + * We currently do this based on the reported Guest Additions version, + * ASSUMING that VBoxService and VBoxDrv are at the same version. + */ + ComObjPtr<Guest> pGuest = mParent; + AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED); + uint32_t uGaVersion = pGuest->i_getAdditionsVersion(); + + /* Everyone supports version one, if they support anything at all. */ + mData.mProtocolVersion = 1; + + /* Guest control 2.0 was introduced with 4.3.0. */ + if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0)) + mData.mProtocolVersion = 2; /* Guest control 2.0. */ + + LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n", + VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), + VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); + + /* + * Inform the user about outdated Guest Additions (VM release log). + */ + if (mData.mProtocolVersion < 2) + LogRelMax(3, ("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n" + " Please upgrade GAs to the current version to get full guest control capabilities.\n", + VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), + VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); + + return VINF_SUCCESS; +} + +/** + * Waits for guest session events. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR on received guest error. + * @retval VERR_TIMEOUT when a timeout has occurred. + * @param fWaitFlags Wait flags to use. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param waitResult Where to return the wait result on success. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + * + * @note Takes the read lock. + */ +int GuestSession::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *prcGuest) +{ + LogFlowThisFuncEnter(); + + AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER); + + /*LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, prcGuest=%p\n", + fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, prcGuest));*/ + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Did some error occur before? Then skip waiting and return. */ + if (mData.mStatus == GuestSessionStatus_Error) + { + waitResult = GuestSessionWaitResult_Error; + AssertMsg(RT_FAILURE(mData.mRC), ("No error rc (%Rrc) set when guest session indicated an error\n", mData.mRC)); + if (prcGuest) + *prcGuest = mData.mRC; /* Return last set error. */ + return VERR_GSTCTL_GUEST_ERROR; + } + + /* Guest Additions < 4.3 don't support session handling, skip. */ + if (mData.mProtocolVersion < 2) + { + waitResult = GuestSessionWaitResult_WaitFlagNotSupported; + + LogFlowThisFunc(("Installed Guest Additions don't support waiting for dedicated sessions, skipping\n")); + return VINF_SUCCESS; + } + + waitResult = GuestSessionWaitResult_None; + if (fWaitFlags & GuestSessionWaitForFlag_Terminate) + { + switch (mData.mStatus) + { + case GuestSessionStatus_Terminated: + case GuestSessionStatus_Down: + waitResult = GuestSessionWaitResult_Terminate; + break; + + case GuestSessionStatus_TimedOutKilled: + case GuestSessionStatus_TimedOutAbnormally: + waitResult = GuestSessionWaitResult_Timeout; + break; + + case GuestSessionStatus_Error: + /* Handled above. */ + break; + + case GuestSessionStatus_Started: + waitResult = GuestSessionWaitResult_Start; + break; + + case GuestSessionStatus_Undefined: + case GuestSessionStatus_Starting: + /* Do the waiting below. */ + break; + + default: + AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus)); + return VERR_NOT_IMPLEMENTED; + } + } + else if (fWaitFlags & GuestSessionWaitForFlag_Start) + { + switch (mData.mStatus) + { + case GuestSessionStatus_Started: + case GuestSessionStatus_Terminating: + case GuestSessionStatus_Terminated: + case GuestSessionStatus_Down: + waitResult = GuestSessionWaitResult_Start; + break; + + case GuestSessionStatus_Error: + waitResult = GuestSessionWaitResult_Error; + break; + + case GuestSessionStatus_TimedOutKilled: + case GuestSessionStatus_TimedOutAbnormally: + waitResult = GuestSessionWaitResult_Timeout; + break; + + case GuestSessionStatus_Undefined: + case GuestSessionStatus_Starting: + /* Do the waiting below. */ + break; + + default: + AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus)); + return VERR_NOT_IMPLEMENTED; + } + } + + LogFlowThisFunc(("sessionStatus=%RU32, sessionRc=%Rrc, waitResult=%RU32\n", + mData.mStatus, mData.mRC, waitResult)); + + /* No waiting needed? Return immediately using the last set error. */ + if (waitResult != GuestSessionWaitResult_None) + { + if (prcGuest) + *prcGuest = mData.mRC; /* Return last set error (if any). */ + return RT_SUCCESS(mData.mRC) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR; + } + + int vrc = VINF_SUCCESS; + + uint64_t const tsStart = RTTimeMilliTS(); + uint64_t tsNow = tsStart; + + while (tsNow - tsStart < uTimeoutMS) + { + GuestWaitEvent *pEvent = NULL; + GuestEventTypes eventTypes; + try + { + eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); + + vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + return vrc; + + alock.release(); /* Release lock before waiting. */ + + GuestSessionStatus_T sessionStatus; + vrc = i_waitForStatusChange(pEvent, fWaitFlags, + uTimeoutMS - (tsNow - tsStart), &sessionStatus, prcGuest); + if (RT_SUCCESS(vrc)) + { + switch (sessionStatus) + { + case GuestSessionStatus_Started: + waitResult = GuestSessionWaitResult_Start; + break; + + case GuestSessionStatus_Starting: + RT_FALL_THROUGH(); + case GuestSessionStatus_Terminating: + if (fWaitFlags & GuestSessionWaitForFlag_Status) /* Any status wanted? */ + waitResult = GuestSessionWaitResult_Status; + /* else: Wait another round until we get the event(s) fWaitFlags defines. */ + break; + + case GuestSessionStatus_Terminated: + waitResult = GuestSessionWaitResult_Terminate; + break; + + case GuestSessionStatus_TimedOutKilled: + RT_FALL_THROUGH(); + case GuestSessionStatus_TimedOutAbnormally: + waitResult = GuestSessionWaitResult_Timeout; + break; + + case GuestSessionStatus_Down: + waitResult = GuestSessionWaitResult_Terminate; + break; + + case GuestSessionStatus_Error: + waitResult = GuestSessionWaitResult_Error; + break; + + default: + waitResult = GuestSessionWaitResult_Status; + break; + } + } + + unregisterWaitEvent(pEvent); + + /* Wait result not None, e.g. some result acquired or a wait error occurred? Bail out. */ + if ( waitResult != GuestSessionWaitResult_None + || RT_FAILURE(vrc)) + break; + + tsNow = RTTimeMilliTS(); + + alock.acquire(); /* Re-acquire lock before waiting for the next event. */ + } + + if (tsNow - tsStart >= uTimeoutMS) + { + waitResult = GuestSessionWaitResult_None; /* Paranoia. */ + vrc = VERR_TIMEOUT; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Waits for guest session status changes. + * + * @returns VBox status code. + * @retval VERR_GSTCTL_GUEST_ERROR on received guest error. + * @retval VERR_WRONG_ORDER when an unexpected event type has been received. + * @param pEvent Wait event to use for waiting. + * @param fWaitFlags Wait flags to use. + * @param uTimeoutMS Timeout (in ms) to wait. + * @param pSessionStatus Where to return the guest session status. + * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR + * was returned. Optional. + */ +int GuestSession::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS, + GuestSessionStatus_T *pSessionStatus, int *prcGuest) +{ + RT_NOREF(fWaitFlags); + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + VBoxEventType_T evtType; + ComPtr<IEvent> pIEvent; + int vrc = waitForEvent(pEvent, uTimeoutMS, &evtType, pIEvent.asOutParam()); + if (RT_SUCCESS(vrc)) + { + if (evtType == VBoxEventType_OnGuestSessionStateChanged) + { + ComPtr<IGuestSessionStateChangedEvent> pChangedEvent = pIEvent; + Assert(!pChangedEvent.isNull()); + + GuestSessionStatus_T sessionStatus; + pChangedEvent->COMGETTER(Status)(&sessionStatus); + if (pSessionStatus) + *pSessionStatus = sessionStatus; + + ComPtr<IVirtualBoxErrorInfo> errorInfo; + HRESULT hr = pChangedEvent->COMGETTER(Error)(errorInfo.asOutParam()); + ComAssertComRC(hr); + + LONG lGuestRc; + hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc); + ComAssertComRC(hr); + if (RT_FAILURE((int)lGuestRc)) + vrc = VERR_GSTCTL_GUEST_ERROR; + if (prcGuest) + *prcGuest = (int)lGuestRc; + + LogFlowThisFunc(("Status changed event for session ID=%RU32, new status is: %RU32 (%Rrc)\n", + mData.mSession.mID, sessionStatus, + RT_SUCCESS((int)lGuestRc) ? VINF_SUCCESS : (int)lGuestRc)); + } + else /** @todo Re-visit this. Can this happen more frequently? */ + AssertMsgFailedReturn(("Got unexpected event type %#x\n", evtType), VERR_WRONG_ORDER); + } + /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */ + else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) + *prcGuest = pEvent->GuestResult(); + Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestSession::close() +{ + LogFlowThisFuncEnter(); + + /* Note: Don't check if the session is ready via i_isStartedExternal() here; + * the session (already) could be in a stopped / aborted state. */ + + int vrc = VINF_SUCCESS; /* Shut up MSVC. */ + int rcGuest = VINF_SUCCESS; + + uint32_t msTimeout = RT_MS_10SEC; /* 10s timeout by default */ + for (int i = 0; i < 3; i++) + { + if (i) + { + LogRel(("Guest Control: Closing session '%s' timed out (%RU32s timeout, attempt %d/10), retrying ...\n", + mData.mSession.mName.c_str(), msTimeout / RT_MS_1SEC, i + 1)); + msTimeout += RT_MS_5SEC; /* Slightly increase the timeout. */ + } + + /* Close session on guest. */ + vrc = i_closeSession(0 /* Flags */, msTimeout, &rcGuest); + if ( RT_SUCCESS(vrc) + || vrc != VERR_TIMEOUT) /* If something else happened there is no point in retrying further. */ + break; + } + + /* On failure don't return here, instead do all the cleanup + * work first and then return an error. */ + + /* Destroy session + remove ourselves from the session list. */ + AssertPtr(mParent); + int vrc2 = mParent->i_sessionDestroy(mData.mSession.mID); + if (vrc2 == VERR_NOT_FOUND) /* Not finding the session anymore isn't critical. */ + vrc2 = VINF_SUCCESS; + + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowThisFunc(("Returning rc=%Rrc, rcGuest=%Rrc\n", vrc, rcGuest)); + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str()); + return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Closing guest session failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session \"%s\" failed with %Rrc"), + mData.mSession.mName.c_str(), vrc); + } + + return S_OK; +} + +HRESULT GuestSession::fileCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aSource, aDestination, aFlags, aProgress); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::fileCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<FileCopyFlag_T> &aFlags, + ComPtr<IProgress> &aProgress) +{ + uint32_t fFlags = FileCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update; + if (fFlags & ~fValidFlags) + return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_File; + source.enmPathStyle = i_getGuestPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + source.fDirCopyFlags = DirectoryCopyFlag_None; + source.fFileCopyFlags = (FileCopyFlag_T)fFlags; + + SourceSet.push_back(source); + + return i_copyFromGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::fileCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + uint32_t fFlags = FileCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update; + if (fFlags & ~fValidFlags) + return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_File; + source.enmPathStyle = GuestSession::i_getHostPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + source.fDirCopyFlags = DirectoryCopyFlag_None; + source.fFileCopyFlags = (FileCopyFlag_T)fFlags; + + SourceSet.push_back(source); + + return i_copyToGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::copyFromGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters, + const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination, + ComPtr<IProgress> &aProgress) +{ + const size_t cSources = aSources.size(); + if ( (aFilters.size() && aFilters.size() != cSources) + || (aFlags.size() && aFlags.size() != cSources)) + { + return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified")); + } + + GuestSessionFsSourceSet SourceSet; + + std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin(); + std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin(); + std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin(); + + const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */ + const bool fFollowSymlinks = true; /** @todo Ditto. */ + + while (itSource != aSources.end()) + { + GuestFsObjData objData; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &rcGuest); + if ( RT_FAILURE(vrc) + && !fContinueOnErrors) + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, (*itSource).c_str()); + return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying type for guest source failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + return setError(E_FAIL, tr("Querying type for guest source \"%s\" failed: %Rrc"), (*itSource).c_str(), vrc); + } + + Utf8Str strFlags; + if (itFlags != aFlags.end()) + { + strFlags = *itFlags; + ++itFlags; + } + + Utf8Str strFilter; + if (itFilter != aFilters.end()) + { + strFilter = *itFilter; + ++itFilter; + } + + GuestSessionFsSourceSpec source; + source.strSource = *itSource; + source.strFilter = strFilter; + source.enmType = objData.mType; + source.enmPathStyle = i_getGuestPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + + /* Check both flag groups here, as copying a directory also could mean to explicitly + * *not* replacing any existing files (or just copy files which are newer, for instance). */ + GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags); + GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags); + + SourceSet.push_back(source); + + ++itSource; + } + + return i_copyFromGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::copyToGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters, + const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination, + ComPtr<IProgress> &aProgress) +{ + const size_t cSources = aSources.size(); + if ( (aFilters.size() && aFilters.size() != cSources) + || (aFlags.size() && aFlags.size() != cSources)) + { + return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified")); + } + + GuestSessionFsSourceSet SourceSet; + + std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin(); + std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin(); + std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin(); + + const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */ + + while (itSource != aSources.end()) + { + RTFSOBJINFO objInfo; + int vrc = RTPathQueryInfo((*itSource).c_str(), &objInfo, RTFSOBJATTRADD_NOTHING); + if ( RT_FAILURE(vrc) + && !fContinueOnErrors) + { + return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc); + } + + Utf8Str strFlags; + if (itFlags != aFlags.end()) + { + strFlags = *itFlags; + ++itFlags; + } + + Utf8Str strFilter; + if (itFilter != aFilters.end()) + { + strFilter = *itFilter; + ++itFilter; + } + + GuestSessionFsSourceSpec source; + source.strSource = *itSource; + source.strFilter = strFilter; + source.enmType = GuestBase::fileModeToFsObjType(objInfo.Attr.fMode); + source.enmPathStyle = GuestSession::i_getHostPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + + GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags); + GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags); + + SourceSet.push_back(source); + + ++itSource; + } + + /* (Re-)Validate stuff. */ + if (RT_UNLIKELY(SourceSet.size() == 0)) /* At least one source must be present. */ + return setError(E_INVALIDARG, tr("No sources specified")); + if (RT_UNLIKELY(SourceSet[0].strSource.isEmpty())) + return setError(E_INVALIDARG, tr("First source entry is empty")); + if (RT_UNLIKELY(aDestination.isEmpty())) + return setError(E_INVALIDARG, tr("No destination specified")); + + return i_copyToGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::directoryCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aSource, aDestination, aFlags, aProgress); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::directoryCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + uint32_t fFlags = DirectoryCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive + | DirectoryCopyFlag_FollowLinks; + if (fFlags & ~fValidFlags) + return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_Directory; + source.enmPathStyle = i_getGuestPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags; + source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */ + + SourceSet.push_back(source); + + return i_copyFromGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::directoryCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + uint32_t fFlags = DirectoryCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive + | DirectoryCopyFlag_FollowLinks; + if (fFlags & ~fValidFlags) + return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_Directory; + source.enmPathStyle = GuestSession::i_getHostPathStyle(); + source.fDryRun = false; /** @todo Implement support for a dry run. */ + source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags; + source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */ + + SourceSet.push_back(source); + + return i_copyToGuest(SourceSet, aDestination, aProgress); +} + +HRESULT GuestSession::directoryCreate(const com::Utf8Str &aPath, ULONG aMode, + const std::vector<DirectoryCreateFlag_T> &aFlags) +{ + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory to create specified")); + + uint32_t fFlags = DirectoryCreateFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + if (fFlags & ~DirectoryCreateFlag_Parents) + return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags); + } + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + ComObjPtr <GuestDirectory> pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &rcGuest); + if (RT_FAILURE(vrc)) + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); + return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Guest directory creation failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + switch (vrc) + { + case VERR_INVALID_PARAMETER: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Invalid parameters given")); + break; + + case VERR_BROKEN_PIPE: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Unexpectedly aborted")); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: %Rrc"), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, + BOOL aSecure, com::Utf8Str &aDirectory) +{ + if (RT_UNLIKELY((aTemplateName.c_str()) == NULL || *(aTemplateName.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No template specified")); + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory name specified")); + if (!aSecure) /* Ignore what mode is specified when a secure temp thing needs to be created. */ + if (RT_UNLIKELY(aMode & ~07777)) + return setError(E_INVALIDARG, tr("Mode invalid (must be specified in octal mode)")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, aMode, RT_BOOL(aSecure), &rcGuest); + if (!RT_SUCCESS(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolMkTemp, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Temporary guest directory creation failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary guest directory creation \"%s\" with template \"%s\" failed: %Rrc"), + aPath.c_str(), aTemplateName.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) +{ + if (RT_UNLIKELY(aPath.isEmpty())) + return setError(E_INVALIDARG, tr("Empty path")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestFsObjData objData; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + + int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); + if (RT_SUCCESS(vrc)) + *aExists = TRUE; + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_PATH_NOT_FOUND: + *aExists = FALSE; + break; + default: + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying directory existence failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + } + break; + } + + case VERR_NOT_A_DIRECTORY: + { + *aExists = FALSE; + break; + } + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying directory existence \"%s\" failed: %Rrc"), + aPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryOpen(const com::Utf8Str &aPath, const com::Utf8Str &aFilter, + const std::vector<DirectoryOpenFlag_T> &aFlags, ComPtr<IGuestDirectory> &aDirectory) +{ + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory to open specified")); + if (RT_UNLIKELY((aFilter.c_str()) != NULL && *(aFilter.c_str()) != '\0')) + return setError(E_INVALIDARG, tr("Directory filters are not implemented yet")); + + uint32_t fFlags = DirectoryOpenFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + + if (fFlags) + return setError(E_INVALIDARG, tr("Open flags (%#x) not implemented yet"), fFlags); + } + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestDirectoryOpenInfo openInfo; + openInfo.mPath = aPath; + openInfo.mFilter = aFilter; + openInfo.mFlags = fFlags; + + ComObjPtr<GuestDirectory> pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_directoryOpen(openInfo, pDirectory, &rcGuest); + if (RT_SUCCESS(vrc)) + { + /* Return directory object to the caller. */ + hrc = pDirectory.queryInterfaceTo(aDirectory.asOutParam()); + } + else + { + switch (vrc) + { + case VERR_INVALID_PARAMETER: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed; invalid parameters given"), + aPath.c_str()); + break; + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest directory failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath) +{ + if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0')) + return setError(E_INVALIDARG, tr("No directory to remove specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + /* No flags; only remove the directory when empty. */ + uint32_t fFlags = DIRREMOVEREC_FLAG_NONE; + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_directoryRemove(aPath, fFlags, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_NOT_SUPPORTED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Handling removing guest directories not supported by installed Guest Additions")); + break; + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest directory failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryRemoveRecursive(const com::Utf8Str &aPath, const std::vector<DirectoryRemoveRecFlag_T> &aFlags, + ComPtr<IProgress> &aProgress) +{ + if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0')) + return setError(E_INVALIDARG, tr("No directory to remove recursively specified")); + + /* By defautl remove recursively as the function name implies. */ + uint32_t fFlags = DIRREMOVEREC_FLAG_RECURSIVE; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + { + switch (aFlags[i]) + { + case DirectoryRemoveRecFlag_None: /* Skip. */ + continue; + + case DirectoryRemoveRecFlag_ContentAndDir: + fFlags |= DIRREMOVEREC_FLAG_CONTENT_AND_DIR; + break; + + case DirectoryRemoveRecFlag_ContentOnly: + fFlags |= DIRREMOVEREC_FLAG_CONTENT_ONLY; + break; + + default: + return setError(E_INVALIDARG, tr("Invalid flags specified")); + } + } + } + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + ComObjPtr<Progress> pProgress; + hrc = pProgress.createObject(); + if (SUCCEEDED(hrc)) + hrc = pProgress->init(static_cast<IGuestSession *>(this), + Bstr(tr("Removing guest directory")).raw(), + TRUE /*aCancelable*/); + if (FAILED(hrc)) + return hrc; + + /* Note: At the moment we don't supply progress information while + * deleting a guest directory recursively. So just complete + * the progress object right now. */ + /** @todo Implement progress reporting on guest directory deletion! */ + hrc = pProgress->i_notifyComplete(S_OK); + if (FAILED(hrc)) + return hrc; + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_directoryRemove(aPath, fFlags, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_NOT_SUPPORTED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Handling removing guest directories recursively not supported by installed Guest Additions")); + break; + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Recursively removing guest directory failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recursively removing guest directory \"%s\" failed: %Rrc"), + aPath.c_str(), vrc); + break; + } + } + else + { + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + + return hrc; +} + +HRESULT GuestSession::environmentScheduleSet(const com::Utf8Str &aName, const com::Utf8Str &aValue) +{ + LogFlowThisFuncEnter(); + int vrc; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + vrc = mData.mEnvironmentChanges.setVariable(aName, aValue); + } + HRESULT hrc; + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else if (vrc == VERR_ENV_INVALID_VAR_NAME) + hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); + else + hrc = setErrorVrc(vrc, tr("Failed to schedule setting environment variable '%s' to '%s'"), aName.c_str(), aValue.c_str()); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName) +{ + LogFlowThisFuncEnter(); + int vrc; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + vrc = mData.mEnvironmentChanges.unsetVariable(aName); + } + HRESULT hrc; + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else if (vrc == VERR_ENV_INVALID_VAR_NAME) + hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); + else + hrc = setErrorVrc(vrc, tr("Failed to schedule unsetting environment variable '%s'"), aName.c_str()); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue) +{ + LogFlowThisFuncEnter(); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc; + if (mData.mpBaseEnvironment) + { + int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else if (vrc == VERR_ENV_INVALID_VAR_NAME) + hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); + else + hrc = setErrorVrc(vrc); + } + else if (mData.mProtocolVersion < 99999) + hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); + else + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists) +{ + LogFlowThisFuncEnter(); + *aExists = FALSE; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc; + if (mData.mpBaseEnvironment) + { + hrc = S_OK; + *aExists = mData.mpBaseEnvironment->doesVariableExist(aName); + } + else if (mData.mProtocolVersion < 99999) + hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); + else + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::fileCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure, + ComPtr<IGuestFile> &aFile) +{ + RT_NOREF(aTemplateName, aMode, aPath, aSecure, aFile); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::fileExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) +{ + /* By default we return non-existent. */ + *aExists = FALSE; + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return S_OK; + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fileQueryInfo(aPath, RT_BOOL(aFollowSymlinks), objData, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aExists = TRUE; + return S_OK; + } + + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_PATH_NOT_FOUND: + RT_FALL_THROUGH(); + case VERR_FILE_NOT_FOUND: + break; + + default: + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + } + + break; + } + + case VERR_NOT_A_FILE: + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), + aPath.c_str(), vrc); + break; + } + + return hrc; +} + +HRESULT GuestSession::fileOpen(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, + ULONG aCreationMode, ComPtr<IGuestFile> &aFile) +{ + LogFlowThisFuncEnter(); + + const std::vector<FileOpenExFlag_T> EmptyFlags; + return fileOpenEx(aPath, aAccessMode, aOpenAction, FileSharingMode_All, aCreationMode, EmptyFlags, aFile); +} + +HRESULT GuestSession::fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, + FileSharingMode_T aSharingMode, ULONG aCreationMode, + const std::vector<FileOpenExFlag_T> &aFlags, ComPtr<IGuestFile> &aFile) +{ + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No file to open specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + /* Validate aAccessMode. */ + switch (aAccessMode) + { + case FileAccessMode_ReadOnly: + RT_FALL_THRU(); + case FileAccessMode_WriteOnly: + RT_FALL_THRU(); + case FileAccessMode_ReadWrite: + break; + case FileAccessMode_AppendOnly: + RT_FALL_THRU(); + case FileAccessMode_AppendRead: + return setError(E_NOTIMPL, tr("Append access modes are not yet implemented")); + default: + return setError(E_INVALIDARG, tr("Unknown FileAccessMode value %u (%#x)"), aAccessMode, aAccessMode); + } + + /* Validate aOpenAction to the old format. */ + switch (aOpenAction) + { + case FileOpenAction_OpenExisting: + RT_FALL_THRU(); + case FileOpenAction_OpenOrCreate: + RT_FALL_THRU(); + case FileOpenAction_CreateNew: + RT_FALL_THRU(); + case FileOpenAction_CreateOrReplace: + RT_FALL_THRU(); + case FileOpenAction_OpenExistingTruncated: + RT_FALL_THRU(); + case FileOpenAction_AppendOrCreate: + break; + default: + return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode); + } + + /* Validate aSharingMode. */ + switch (aSharingMode) + { + case FileSharingMode_All: + break; + case FileSharingMode_Read: + case FileSharingMode_Write: + case FileSharingMode_ReadWrite: + case FileSharingMode_Delete: + case FileSharingMode_ReadDelete: + case FileSharingMode_WriteDelete: + return setError(E_NOTIMPL, tr("Only FileSharingMode_All is currently implemented")); + + default: + return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode); + } + + /* Combine and validate flags. */ + uint32_t fOpenEx = 0; + for (size_t i = 0; i < aFlags.size(); i++) + fOpenEx |= aFlags[i]; + if (fOpenEx) + return setError(E_INVALIDARG, tr("Unsupported FileOpenExFlag value(s) in aFlags (%#x)"), fOpenEx); + + ComObjPtr <GuestFile> pFile; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fileOpenEx(aPath, aAccessMode, aOpenAction, aSharingMode, aCreationMode, aFlags, pFile, &rcGuest); + if (RT_SUCCESS(vrc)) + /* Return directory object to the caller. */ + hrc = pFile.queryInterfaceTo(aFile.asOutParam()); + else + { + switch (vrc) + { + case VERR_NOT_SUPPORTED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Handling guest files not supported by installed Guest Additions")); + break; + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_File, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest file failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::fileQuerySize(const com::Utf8Str &aPath, BOOL aFollowSymlinks, LONG64 *aSize) +{ + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + int64_t llSize; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aSize = llSize; + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file size failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file size of \"%s\" failed: %Rrc"), + vrc, aPath.c_str()); + } + + return hrc; +} + +HRESULT GuestSession::fsQueryFreeSpace(const com::Utf8Str &aPath, LONG64 *aFreeSpace) +{ + RT_NOREF(aPath, aFreeSpace); + + return E_NOTIMPL; +} + +HRESULT GuestSession::fsQueryInfo(const com::Utf8Str &aPath, ComPtr<IGuestFsInfo> &aInfo) +{ + RT_NOREF(aPath, aInfo); + + return E_NOTIMPL; +} + +HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) +{ + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); + + *aExists = false; + + GuestFsObjData objData; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aExists = TRUE; + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + { + if ( rcGuest == VERR_NOT_A_FILE + || rcGuest == VERR_PATH_NOT_FOUND + || rcGuest == VERR_FILE_NOT_FOUND + || rcGuest == VERR_INVALID_NAME) + { + hrc = S_OK; /* Ignore these vrc values. */ + } + else + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence information failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + } + else + hrc = setErrorVrc(vrc, tr("Querying guest file existence information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr<IGuestFsObjInfo> &aInfo) +{ + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); + + GuestFsObjData Info; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, Info, &rcGuest); + if (RT_SUCCESS(vrc)) + { + ComObjPtr<GuestFsObjInfo> ptrFsObjInfo; + hrc = ptrFsObjInfo.createObject(); + if (SUCCEEDED(hrc)) + { + vrc = ptrFsObjInfo->init(Info); + if (RT_SUCCESS(vrc)) + hrc = ptrFsObjInfo.queryInterfaceTo(aInfo.asOutParam()); + else + hrc = setErrorVrc(vrc); + } + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file information failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + else + hrc = setErrorVrc(vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath) +{ + if (RT_UNLIKELY(aPath.isEmpty())) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s\n", aPath.c_str())); + + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_fileRemove(aPath, &rcGuest); + if (RT_FAILURE(vrc)) + { + if (GuestProcess::i_isGuestError(vrc)) + { + GuestErrorInfo ge(GuestErrorInfo::Type_ToolRm, rcGuest, aPath.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest file failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + } + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths, ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aPaths, aProgress); + return E_NOTIMPL; +} + +HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource, + const com::Utf8Str &aDestination, + const std::vector<FsObjRenameFlag_T> &aFlags) +{ + if (RT_UNLIKELY(aSource.isEmpty())) + return setError(E_INVALIDARG, tr("No source path specified")); + + if (RT_UNLIKELY(aDestination.isEmpty())) + return setError(E_INVALIDARG, tr("No destination path specified")); + + HRESULT hrc = i_isStartedExternal(); + if (FAILED(hrc)) + return hrc; + + /* Combine, validate and convert flags. */ + uint32_t fApiFlags = 0; + for (size_t i = 0; i < aFlags.size(); i++) + fApiFlags |= aFlags[i]; + if (fApiFlags & ~((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) + return setError(E_INVALIDARG, tr("Unknown rename flag: %#x"), fApiFlags); + + LogFlowThisFunc(("aSource=%s, aDestination=%s\n", aSource.c_str(), aDestination.c_str())); + + AssertCompile(FsObjRenameFlag_NoReplace == 0); + AssertCompile(FsObjRenameFlag_Replace != 0); + uint32_t fBackend; + if ((fApiFlags & ((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) == FsObjRenameFlag_Replace) + fBackend = PATHRENAME_FLAG_REPLACE; + else + fBackend = PATHRENAME_FLAG_NO_REPLACE; + + /* Call worker to do the job. */ + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = i_pathRename(aSource, aDestination, fBackend, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_NOT_SUPPORTED: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Handling renaming guest paths not supported by installed Guest Additions")); + break; + + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, aSource.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Renaming guest path failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest path \"%s\" failed: %Rrc"), + aSource.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::fsObjMove(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, + const std::vector<FsObjMoveFlag_T> &aFlags, ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aSource, aDestination, aFlags, aProgress); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::fsObjMoveArray(const std::vector<com::Utf8Str> &aSource, + const com::Utf8Str &aDestination, + const std::vector<FsObjMoveFlag_T> &aFlags, + ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aSource, aDestination, aFlags, aProgress); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::fsObjCopyArray(const std::vector<com::Utf8Str> &aSource, + const com::Utf8Str &aDestination, + const std::vector<FileCopyFlag_T> &aFlags, + ComPtr<IProgress> &aProgress) +{ + RT_NOREF(aSource, aDestination, aFlags, aProgress); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::fsObjSetACL(const com::Utf8Str &aPath, BOOL aFollowSymlinks, const com::Utf8Str &aAcl, ULONG aMode) +{ + RT_NOREF(aPath, aFollowSymlinks, aAcl, aMode); + ReturnComNotImplemented(); +} + + +HRESULT GuestSession::processCreate(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments, + const std::vector<com::Utf8Str> &aEnvironment, + const std::vector<ProcessCreateFlag_T> &aFlags, + ULONG aTimeoutMS, ComPtr<IGuestProcess> &aGuestProcess) +{ + LogFlowThisFuncEnter(); + + std::vector<LONG> affinityIgnored; + return processCreateEx(aExecutable, aArguments, aEnvironment, aFlags, aTimeoutMS, ProcessPriority_Default, + affinityIgnored, aGuestProcess); +} + +HRESULT GuestSession::processCreateEx(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments, + const std::vector<com::Utf8Str> &aEnvironment, + const std::vector<ProcessCreateFlag_T> &aFlags, ULONG aTimeoutMS, + ProcessPriority_T aPriority, const std::vector<LONG> &aAffinity, + ComPtr<IGuestProcess> &aGuestProcess) +{ + HRESULT hr = i_isStartedExternal(); + if (FAILED(hr)) + return hr; + + /* + * Must have an executable to execute. If none is given, we try use the + * zero'th argument. + */ + const char *pszExecutable = aExecutable.c_str(); + if (RT_UNLIKELY(pszExecutable == NULL || *pszExecutable == '\0')) + { + if (aArguments.size() > 0) + pszExecutable = aArguments[0].c_str(); + if (pszExecutable == NULL || *pszExecutable == '\0') + return setError(E_INVALIDARG, tr("No command to execute specified")); + } + + /* The rest of the input is being validated in i_processCreateEx(). */ + + LogFlowThisFuncEnter(); + + /* + * Build the process startup info. + */ + GuestProcessStartupInfo procInfo; + + /* Executable and arguments. */ + procInfo.mExecutable = pszExecutable; + if (aArguments.size()) + { + for (size_t i = 0; i < aArguments.size(); i++) + procInfo.mArguments.push_back(aArguments[i]); + } + else /* If no arguments were given, add the executable as argv[0] by default. */ + procInfo.mArguments.push_back(procInfo.mExecutable); + + /* Combine the environment changes associated with the ones passed in by + the caller, giving priority to the latter. The changes are putenv style + and will be applied to the standard environment for the guest user. */ + int vrc = procInfo.mEnvironmentChanges.copy(mData.mEnvironmentChanges); + if (RT_SUCCESS(vrc)) + { + size_t idxError = ~(size_t)0; + vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment, &idxError); + if (RT_SUCCESS(vrc)) + { + /* Convert the flag array into a mask. */ + if (aFlags.size()) + for (size_t i = 0; i < aFlags.size(); i++) + procInfo.mFlags |= aFlags[i]; + + procInfo.mTimeoutMS = aTimeoutMS; + + /** @todo use RTCPUSET instead of archaic 64-bit variables! */ + if (aAffinity.size()) + for (size_t i = 0; i < aAffinity.size(); i++) + if (aAffinity[i]) + procInfo.mAffinity |= (uint64_t)1 << i; + + procInfo.mPriority = aPriority; + + /* + * Create a guest process object. + */ + ComObjPtr<GuestProcess> pProcess; + vrc = i_processCreateEx(procInfo, pProcess); + if (RT_SUCCESS(vrc)) + { + ComPtr<IGuestProcess> pIProcess; + hr = pProcess.queryInterfaceTo(pIProcess.asOutParam()); + if (SUCCEEDED(hr)) + { + /* + * Start the process. + */ + vrc = pProcess->i_startProcessAsync(); + if (RT_SUCCESS(vrc)) + { + aGuestProcess = pIProcess; + + LogFlowFuncLeaveRC(vrc); + return S_OK; + } + + hr = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc); + } + } + else if (vrc == VERR_GSTCTL_MAX_CID_OBJECTS_REACHED) + hr = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"), + VBOX_GUESTCTRL_MAX_OBJECTS); + else + hr = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc); + } + else + hr = setErrorBoth(vrc == VERR_ENV_INVALID_VAR_NAME ? E_INVALIDARG : Global::vboxStatusCodeToCOM(vrc), vrc, + tr("Failed to apply environment variable '%s', index %u (%Rrc)'"), + aEnvironment[idxError].c_str(), idxError, vrc); + } + else + hr = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc); + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +HRESULT GuestSession::processGet(ULONG aPid, ComPtr<IGuestProcess> &aGuestProcess) + +{ + if (aPid == 0) + return setError(E_INVALIDARG, tr("No valid process ID (PID) specified")); + + LogFlowThisFunc(("PID=%RU32\n", aPid)); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hr = S_OK; + + ComObjPtr<GuestProcess> pProcess; + int rc = i_processGetByPID(aPid, &pProcess); + if (RT_FAILURE(rc)) + hr = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPid); + + /* This will set (*aProcess) to NULL if pProgress is NULL. */ + HRESULT hr2 = pProcess.queryInterfaceTo(aGuestProcess.asOutParam()); + if (SUCCEEDED(hr)) + hr = hr2; + + LogFlowThisFunc(("aProcess=%p, hr=%Rhrc\n", (IGuestProcess*)aGuestProcess, hr)); + return hr; +} + +HRESULT GuestSession::symlinkCreate(const com::Utf8Str &aSource, const com::Utf8Str &aTarget, SymlinkType_T aType) +{ + RT_NOREF(aSource, aTarget, aType); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::symlinkExists(const com::Utf8Str &aSymlink, BOOL *aExists) + +{ + RT_NOREF(aSymlink, aExists); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::symlinkRead(const com::Utf8Str &aSymlink, const std::vector<SymlinkReadFlag_T> &aFlags, + com::Utf8Str &aTarget) +{ + RT_NOREF(aSymlink, aFlags, aTarget); + ReturnComNotImplemented(); +} + +HRESULT GuestSession::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason) +{ + /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */ + + LogFlowThisFuncEnter(); + + HRESULT hrc = S_OK; + + /* + * Note: Do not hold any locks here while waiting! + */ + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestSessionWaitResult_T waitResult; + int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &rcGuest); + if (RT_SUCCESS(vrc)) + *aReason = waitResult; + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str()); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Waiting for guest process failed: %s"), + GuestBase::getErrorAsString(ge).c_str()); + break; + } + case VERR_TIMEOUT: + *aReason = GuestSessionWaitResult_Timeout; + break; + + default: + { + const char *pszSessionName = mData.mSession.mName.c_str(); + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Waiting for guest session \"%s\" failed: %Rrc"), + pszSessionName ? pszSessionName : tr("Unnamed"), vrc); + break; + } + } + } + + LogFlowFuncLeaveRC(vrc); + return hrc; +} + +HRESULT GuestSession::waitForArray(const std::vector<GuestSessionWaitForFlag_T> &aWaitFor, ULONG aTimeoutMS, + GuestSessionWaitResult_T *aReason) +{ + /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */ + + LogFlowThisFuncEnter(); + + /* + * Note: Do not hold any locks here while waiting! + */ + uint32_t fWaitFor = GuestSessionWaitForFlag_None; + for (size_t i = 0; i < aWaitFor.size(); i++) + fWaitFor |= aWaitFor[i]; + + return WaitFor(fWaitFor, aTimeoutMS, aReason); +} diff --git a/src/VBox/Main/src-client/GuestSessionImplTasks.cpp b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp new file mode 100644 index 00000000..d3112ff0 --- /dev/null +++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp @@ -0,0 +1,3307 @@ +/* $Id: GuestSessionImplTasks.cpp $ */ +/** @file + * VirtualBox Main - Guest session tasks. + */ + +/* + * Copyright (C) 2012-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION +#include "LoggingNew.h" + +#include "GuestImpl.h" +#ifndef VBOX_WITH_GUEST_CONTROL +# error "VBOX_WITH_GUEST_CONTROL must defined in this file" +#endif +#include "GuestSessionImpl.h" +#include "GuestSessionImplTasks.h" +#include "GuestCtrlImplPrivate.h" + +#include "Global.h" +#include "AutoCaller.h" +#include "ConsoleImpl.h" +#include "ProgressImpl.h" + +#include <memory> /* For auto_ptr. */ + +#include <iprt/env.h> +#include <iprt/file.h> /* For CopyTo/From. */ +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/fsvfs.h> + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +/** + * (Guest Additions) ISO file flags. + * Needed for handling Guest Additions updates. + */ +#define ISOFILE_FLAG_NONE 0 +/** Copy over the file from host to the + * guest. */ +#define ISOFILE_FLAG_COPY_FROM_ISO RT_BIT(0) +/** Execute file on the guest after it has + * been successfully transferred. */ +#define ISOFILE_FLAG_EXECUTE RT_BIT(7) +/** File is optional, does not have to be + * existent on the .ISO. */ +#define ISOFILE_FLAG_OPTIONAL RT_BIT(8) + + +// session task classes +///////////////////////////////////////////////////////////////////////////// + +GuestSessionTask::GuestSessionTask(GuestSession *pSession) + : ThreadTask("GenericGuestSessionTask") +{ + mSession = pSession; + + switch (mSession->i_getGuestPathStyle()) + { + case PathStyle_DOS: + mstrGuestPathStyle = "\\"; + break; + + default: + mstrGuestPathStyle = "/"; + break; + } +} + +GuestSessionTask::~GuestSessionTask(void) +{ +} + +/** + * Creates (and initializes / sets) the progress objects of a guest session task. + * + * @returns VBox status code. + * @param cOperations Number of operation the task wants to perform. + */ +int GuestSessionTask::createAndSetProgressObject(ULONG cOperations /* = 1 */) +{ + LogFlowThisFunc(("cOperations=%ld\n", cOperations)); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hr = pProgress.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + hr = pProgress->init(static_cast<IGuestSession*>(mSession), + Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw()); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + mProgress = pProgress; + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +#if 0 /* unused */ +/** @note The task object is owned by the thread after this returns, regardless of the result. */ +int GuestSessionTask::RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress) +{ + LogFlowThisFunc(("strDesc=%s\n", strDesc.c_str())); + + mDesc = strDesc; + mProgress = pProgress; + HRESULT hrc = createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + + LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc)); + return Global::vboxStatusCodeToCOM(hrc); +} +#endif + +/** + * Gets a guest property from the VM. + * + * @returns VBox status code. + * @param pGuest Guest object of VM to get guest property from. + * @param strPath Guest property to path to get. + * @param strValue Where to store the guest property value on success. + */ +int GuestSessionTask::getGuestProperty(const ComObjPtr<Guest> &pGuest, + const Utf8Str &strPath, Utf8Str &strValue) +{ + ComObjPtr<Console> pConsole = pGuest->i_getConsole(); + const ComPtr<IMachine> pMachine = pConsole->i_machine(); + + Assert(!pMachine.isNull()); + Bstr strTemp, strFlags; + LONG64 i64Timestamp; + HRESULT hr = pMachine->GetGuestProperty(Bstr(strPath).raw(), + strTemp.asOutParam(), + &i64Timestamp, strFlags.asOutParam()); + if (SUCCEEDED(hr)) + { + strValue = strTemp; + return VINF_SUCCESS; + } + return VERR_NOT_FOUND; +} + +/** + * Sets the percentage of a guest session task progress. + * + * @returns VBox status code. + * @param uPercent Percentage (0-100) to set. + */ +int GuestSessionTask::setProgress(ULONG uPercent) +{ + if (mProgress.isNull()) /* Progress is optional. */ + return VINF_SUCCESS; + + BOOL fCanceled; + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + return VERR_CANCELLED; + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && fCompleted) + { + AssertMsgFailed(("Setting value of an already completed progress\n")); + return VINF_SUCCESS; + } + HRESULT hr = mProgress->SetCurrentOperationProgress(uPercent); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + return VINF_SUCCESS; +} + +/** + * Sets the task's progress object to succeeded. + * + * @returns VBox status code. + */ +int GuestSessionTask::setProgressSuccess(void) +{ + if (mProgress.isNull()) /* Progress is optional. */ + return VINF_SUCCESS; + + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && !fCompleted) + { +#ifdef VBOX_STRICT + ULONG uCurOp; mProgress->COMGETTER(Operation(&uCurOp)); + ULONG cOps; mProgress->COMGETTER(OperationCount(&cOps)); + AssertMsg(uCurOp + 1 /* Zero-based */ == cOps, ("Not all operations done yet (%u/%u)\n", uCurOp + 1, cOps)); +#endif + HRESULT hr = mProgress->i_notifyComplete(S_OK); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; /** @todo Find a better rc. */ + } + + return VINF_SUCCESS; +} + +/** + * Sets the task's progress object to an error using a string message. + * + * @returns Returns \a hr for convenience. + * @param hr Progress operation result to set. + * @param strMsg Message to set. + */ +HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg) +{ + LogFlowFunc(("hr=%Rhrc, strMsg=%s\n", hr, strMsg.c_str())); + + if (mProgress.isNull()) /* Progress is optional. */ + return hr; /* Return original rc. */ + + BOOL fCanceled; + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && !fCanceled + && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && !fCompleted) + { + HRESULT hr2 = mProgress->i_notifyComplete(hr, + COM_IIDOF(IGuestSession), + GuestSession::getStaticComponentName(), + /* Make sure to hand-in the message via format string to avoid problems + * with (file) paths which e.g. contain "%s" and friends. Can happen with + * randomly generated Validation Kit stuff. */ + "%s", strMsg.c_str()); + if (FAILED(hr2)) + return hr2; + } + return hr; /* Return original rc. */ +} + +/** + * Sets the task's progress object to an error using a string message and a guest error info object. + * + * @returns Returns \a hr for convenience. + * @param hr Progress operation result to set. + * @param strMsg Message to set. + * @param guestErrorInfo Guest error info to use. + */ +HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo) +{ + return setProgressErrorMsg(hr, strMsg + Utf8Str(": ") + GuestBase::getErrorAsString(guestErrorInfo)); +} + +/** + * Creates a directory on the guest. + * + * @return VBox status code. + * VINF_ALREADY_EXISTS if directory on the guest already exists (\a fCanExist is \c true). + * VWRN_ALREADY_EXISTS if directory on the guest already exists but must not exist (\a fCanExist is \c false). + * @param strPath Absolute path to directory on the guest (guest style path) to create. + * @param fMode Directory mode to use for creation. + * @param enmDirectoryCreateFlags Directory creation flags. + * @param fFollowSymlinks Whether to follow symlinks on the guest or not. + * @param fCanExist Whether the directory to create is allowed to exist already. + */ +int GuestSessionTask::directoryCreateOnGuest(const com::Utf8Str &strPath, + uint32_t fMode, DirectoryCreateFlag_T enmDirectoryCreateFlags, + bool fFollowSymlinks, bool fCanExist) +{ + LogFlowFunc(("strPath=%s, enmDirectoryCreateFlags=0x%x, fMode=%RU32, fFollowSymlinks=%RTbool, fCanExist=%RTbool\n", + strPath.c_str(), enmDirectoryCreateFlags, fMode, fFollowSymlinks, fCanExist)); + + GuestFsObjData objData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (!fCanExist) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest directory \"%s\" already exists"), strPath.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + else + vrc = VWRN_ALREADY_EXISTS; + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (vrcGuest) + { + case VERR_FILE_NOT_FOUND: + RT_FALL_THROUGH(); + case VERR_PATH_NOT_FOUND: + vrc = mSession->i_directoryCreate(strPath.c_str(), fMode, enmDirectoryCreateFlags, &vrcGuest); + break; + default: + break; + } + + if (RT_FAILURE(vrc)) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest error creating directory \"%s\" on the guest: %Rrc"), + strPath.c_str(), vrcGuest)); + break; + } + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host error creating directory \"%s\" on the guest: %Rrc"), + strPath.c_str(), vrc)); + break; + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Creates a directory on the host. + * + * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists. + * @param strPath Absolute path to directory on the host (host style path) to create. + * @param fMode Directory mode to use for creation. + * @param fCreate Directory creation flags. + * @param fCanExist Whether the directory to create is allowed to exist already. + */ +int GuestSessionTask::directoryCreateOnHost(const com::Utf8Str &strPath, uint32_t fMode, uint32_t fCreate, bool fCanExist) +{ + LogFlowFunc(("strPath=%s, fMode=%RU32, fCreate=0x%x, fCanExist=%RTbool\n", strPath.c_str(), fMode, fCreate, fCanExist)); + + LogRel2(("Guest Control: Creating host directory \"%s\" ...\n", strPath.c_str())); + + int vrc = RTDirCreate(strPath.c_str(), fMode, fCreate); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_ALREADY_EXISTS) + { + if (!fCanExist) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host directory \"%s\" already exists"), strPath.c_str())); + } + else + vrc = VINF_SUCCESS; + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Could not create host directory \"%s\": %Rrc"), + strPath.c_str(), vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Main function for copying a file from guest to the host. + * + * @return VBox status code. + * @param strSrcFile Full path of source file on the host to copy. + * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already. + * @param strDstFile Full destination path and file name (guest style) to copy file to. + * @param phDstFile Pointer to host file handle (destination) to copy to. Must be in opened and ready state already. + * @param fFileCopyFlags File copy flags. + * @param offCopy Offset (in bytes) where to start copying the source file. + * @param cbSize Size (in bytes) to copy from the source file. + */ +int GuestSessionTask::fileCopyFromGuestInner(const Utf8Str &strSrcFile, ComObjPtr<GuestFile> &srcFile, + const Utf8Str &strDstFile, PRTFILE phDstFile, + FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */ + return VINF_SUCCESS; + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int vrc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + vrc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Seeking to offset %RU64 of guest file \"%s\" failed: %Rrc"), + offCopy, strSrcFile.c_str(), vrc)); + return vrc; + } + } + + BYTE byBuf[_64K]; /** @todo Can we do better here? */ + while (cbToRead) + { + uint32_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + vrc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from guest \"%s\" failed: %Rrc", "", cbChunk), + cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc)); + break; + } + + vrc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing %RU32 bytes to host file \"%s\" failed: %Rrc", "", cbRead), + cbRead, strDstFile.c_str(), vrc)); + break; + } + + AssertBreak(cbToRead >= cbRead); + cbToRead -= cbRead; + + /* Update total bytes written to the guest. */ + cbWrittenTotal += cbRead; + AssertBreak(cbWrittenTotal <= cbSize); + + /* Did the user cancel the operation above? */ + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + break; + + AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR); + vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100); + if (RT_FAILURE(vrc)) + break; + } + + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + return VINF_SUCCESS; + + if (RT_FAILURE(vrc)) + return vrc; + + /* + * Even if we succeeded until here make sure to check whether we really transferred + * everything. + */ + if (cbWrittenTotal == 0) + { + /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write + * to the destination -> access denied. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing guest file \"%s\" to host file \"%s\" failed: Access denied"), + strSrcFile.c_str(), strDstFile.c_str())); + vrc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Copying guest file \"%s\" to host file \"%s\" failed (%RU64/%RU64 bytes transferred)"), + strSrcFile.c_str(), strDstFile.c_str(), cbWrittenTotal, cbSize)); + vrc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Closes a formerly opened guest file. + * + * @returns VBox status code. + * @param file Guest file to close. + * + * @note Set a progress error message on error. + */ +int GuestSessionTask::fileClose(const ComObjPtr<GuestFile> &file) +{ + int vrcGuest; + int vrc = file->i_closeFile(&vrcGuest); + if (RT_FAILURE(vrc)) + { + Utf8Str strFilename; + HRESULT const hrc = file->getFilename(strFilename); + AssertComRCReturn(hrc, VERR_OBJECT_DESTROYED); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Error closing guest file \"%s\": %Rrc"), + strFilename.c_str(), vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc)); + if (RT_SUCCESS(vrc)) + vrc = vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc; + } + + return vrc; +} + +/** + * Copies a file from the guest to the host. + * + * @return VBox status code. + * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified, + * *or * the file at the destination has the same (or newer) modification time + * and FileCopyFlag_Update is specified. + * @param strSrc Full path of source file on the guest to copy. + * @param strDst Full destination path and file name (host style) to copy file to. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags)); + + GuestFileOpenInfo srcOpenInfo; + srcOpenInfo.mFilename = strSrc; + srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting; + srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly; + srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> srcFile; + + GuestFsObjData srcObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = mSession->i_fsQueryInfo(strSrc, TRUE /* fFollowSymlinks */, srcObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file lookup failed"), + GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file lookup for \"%s\" failed: %Rrc"), strSrc.c_str(), vrc)); + } + else + { + switch (srcObjData.mType) + { + case FsObjType_File: + break; + + case FsObjType_Symlink: + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" is a symbolic link"), + strSrc.c_str())); + vrc = VERR_IS_A_SYMLINK; + } + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest object \"%s\" is not a file (is type %#x)"), + strSrc.c_str(), srcObjData.mType)); + vrc = VERR_NOT_A_FILE; + break; + } + } + + if (RT_FAILURE(vrc)) + return vrc; + + vrc = mSession->i_fileOpen(srcOpenInfo, srcFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file could not be opened"), + GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strSrc.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), strSrc.c_str(), vrc)); + } + + if (RT_FAILURE(vrc)) + return vrc; + + RTFSOBJINFO dstObjInfo; + RT_ZERO(dstObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfo(strDst.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + LogRel2(("Guest Control: Host file \"%s\" already exists, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + + if ( !fSkip + && fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC srcModificationTimeTS; + RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime); + if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0) + { + LogRel2(("Guest Control: Host file \"%s\" has same or newer modification date, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + } + } + else + { + if (vrc == VERR_PATH_NOT_FOUND) /* Destination file does not exist (yet)? */ + vrc = VERR_FILE_NOT_FOUND; /* Needed in next block further down. */ + else if (vrc != VERR_FILE_NOT_FOUND) /* Ditto. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host file lookup for \"%s\" failed: %Rrc"), strDst.c_str(), vrc)); + } + } + + if (fSkip) + { + int vrc2 = fileClose(srcFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; + } + + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_FILE(dstObjInfo.Attr.fMode)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host file \"%s\" already exists"), strDst.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + } + else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a directory"), strDst.c_str())); + vrc = VERR_IS_A_DIRECTORY; + } + else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode)) + { + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a symbolic link"), strDst.c_str())); + vrc = VERR_IS_A_SYMLINK; + } + } + else + { + LogFlowThisFunc(("Host file system type %#x not supported\n", dstObjInfo.Attr.fMode & RTFS_TYPE_MASK)); + vrc = VERR_NOT_SUPPORTED; + } + } + + LogFlowFunc(("vrc=%Rrc, dstFsType=%#x, pszDstFile=%s\n", vrc, dstObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDst.c_str())); + + if ( RT_SUCCESS(vrc) + || vrc == VERR_FILE_NOT_FOUND) + { + LogRel2(("Guest Control: Copying file \"%s\" from guest to \"%s\" on host ...\n", strSrc.c_str(), strDst.c_str())); + + RTFILE hDstFile; + vrc = RTFileOpen(&hDstFile, strDst.c_str(), + RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */ + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n", + strSrc.c_str(), strDst.c_str(), srcObjData.mObjectSize)); + + vrc = fileCopyFromGuestInner(strSrc, srcFile, strDst, &hDstFile, fFileCopyFlags, + 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize); + + int vrc2 = RTFileClose(hDstFile); + AssertRC(vrc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Opening/creating host file \"%s\" failed: %Rrc"), strDst.c_str(), vrc)); + } + + int vrc2 = fileClose(srcFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Main function for copying a file from host to the guest. + * + * @return VBox status code. + * @param strSrcFile Full path of source file on the host to copy. + * @param hVfsFile The VFS file handle to read from. + * @param strDstFile Full destination path and file name (guest style) to copy file to. + * @param fileDst Guest file (destination) to copy to the guest. Must be in opened and ready state already. + * @param fFileCopyFlags File copy flags. + * @param offCopy Offset (in bytes) where to start copying the source file. + * @param cbSize Size (in bytes) to copy from the source file. + */ +int GuestSessionTask::fileCopyToGuestInner(const Utf8Str &strSrcFile, RTVFSFILE hVfsFile, + const Utf8Str &strDstFile, ComObjPtr<GuestFile> &fileDst, + FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */ + return VINF_SUCCESS; + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int vrc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + vrc = RTVfsFileSeek(hVfsFile, offCopy, RTFILE_SEEK_END, &offActual); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Seeking to offset %RU64 of host file \"%s\" failed: %Rrc"), + offCopy, strSrcFile.c_str(), vrc)); + return vrc; + } + } + + BYTE byBuf[_64K]; + while (cbToRead) + { + size_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + vrc = RTVfsFileRead(hVfsFile, byBuf, cbChunk, &cbRead); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from host file \"%s\" failed: %Rrc"), + cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc)); + break; + } + + vrc = fileDst->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc"), + cbRead, strDstFile.c_str(), vrc)); + break; + } + + Assert(cbToRead >= cbRead); + cbToRead -= cbRead; + + /* Update total bytes written to the guest. */ + cbWrittenTotal += cbRead; + Assert(cbWrittenTotal <= cbSize); + + /* Did the user cancel the operation above? */ + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + break; + + AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR); + vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100); + if (RT_FAILURE(vrc)) + break; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* + * Even if we succeeded until here make sure to check whether we really transferred + * everything. + */ + if (cbWrittenTotal == 0) + { + /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write + * to the destination -> access denied. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing to guest file \"%s\" failed: Access denied"), + strDstFile.c_str())); + vrc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Copying to guest file \"%s\" failed (%RU64/%RU64 bytes transferred)"), + strDstFile.c_str(), cbWrittenTotal, cbSize)); + vrc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Copies a file from the host to the guest. + * + * @return VBox status code. + * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified, + * *or * the file at the destination has the same (or newer) modification time + * and FileCopyFlag_Update is specified. + * @param strSrc Full path of source file on the host. + * @param strDst Full destination path and file name (guest style) to copy file to. Guest-path style. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDst=%s, fFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags)); + + GuestFileOpenInfo dstOpenInfo; + dstOpenInfo.mFilename = strDst; + if (fFileCopyFlags & FileCopyFlag_NoReplace) + dstOpenInfo.mOpenAction = FileOpenAction_CreateNew; + else + dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace; + dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly; + dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> dstFile; + int vrcGuest; + int vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced"), strDst.c_str()), + GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strDst.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced: %Rrc"), strDst.c_str(), vrc)); + return vrc; + } + + char szSrcReal[RTPATH_MAX]; + + RTFSOBJINFO srcObjInfo; + RT_ZERO(srcObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(vrc)) + { + vrc = RTPathReal(strSrc.c_str(), szSrcReal, sizeof(szSrcReal)); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host path lookup for file \"%s\" failed: %Rrc"), + strSrc.c_str(), vrc)); + } + else + { + vrc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + /* Only perform a remote file query when needed. */ + if ( (fFileCopyFlags & FileCopyFlag_Update) + || (fFileCopyFlags & FileCopyFlag_NoReplace)) + { + GuestFsObjData dstObjData; + vrc = mSession->i_fileQueryInfo(strDst, RT_BOOL(fFileCopyFlags & FileCopyFlag_FollowLinks), dstObjData, + &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + LogRel2(("Guest Control: Guest file \"%s\" already exists, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + + if ( !fSkip + && fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC dstModificationTimeTS; + RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime); + if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0) + { + LogRel2(("Guest Control: Guest file \"%s\" has same or newer modification date, skipping\n", + strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + } + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + switch (vrcGuest) + { + case VERR_FILE_NOT_FOUND: + vrc = VINF_SUCCESS; + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest error while determining object data for guest file \"%s\": %Rrc"), + strDst.c_str(), vrcGuest)); + break; + } + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host error while determining object data for guest file \"%s\": %Rrc"), + strDst.c_str(), vrc)); + } + } + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host source file lookup for \"%s\" failed: %Rrc"), + szSrcReal, vrc)); + } + } + } + + if (fSkip) + { + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; + } + + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Copying file \"%s\" from host to \"%s\" on guest ...\n", strSrc.c_str(), strDst.c_str())); + + RTVFSFILE hSrcFile; + vrc = RTVfsFileOpenNormal(szSrcReal, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hSrcFile); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n", + szSrcReal, strDst.c_str(), srcObjInfo.cbObject)); + + vrc = fileCopyToGuestInner(szSrcReal, hSrcFile, strDst, dstFile, + fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject); + + int vrc2 = RTVfsFileRelease(hSrcFile); + AssertRC(vrc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Opening host file \"%s\" failed: %Rrc"), + szSrcReal, vrc)); + } + + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Adds a guest file system entry to a given list. + * + * @return VBox status code. + * @param strFile Path to file system entry to add. + * @param fsObjData Guest file system information of entry to add. + */ +int FsList::AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData) +{ + LogFlowFunc(("Adding \"%s\"\n", strFile.c_str())); + + FsEntry *pEntry = NULL; + try + { + pEntry = new FsEntry(); + pEntry->fMode = fsObjData.GetFileMode(); + pEntry->strPath = strFile; + + mVecEntries.push_back(pEntry); + } + catch (std::bad_alloc &) + { + if (pEntry) + delete pEntry; + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +/** + * Adds a host file system entry to a given list. + * + * @return VBox status code. + * @param strFile Path to file system entry to add. + * @param pcObjInfo File system information of entry to add. + */ +int FsList::AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo) +{ + LogFlowFunc(("Adding \"%s\"\n", strFile.c_str())); + + FsEntry *pEntry = NULL; + try + { + pEntry = new FsEntry(); + pEntry->fMode = pcObjInfo->Attr.fMode; + pEntry->strPath = strFile; + + mVecEntries.push_back(pEntry); + } + catch (std::bad_alloc &) + { + if (pEntry) + delete pEntry; + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +FsList::FsList(const GuestSessionTask &Task) + : mTask(Task) +{ +} + +FsList::~FsList() +{ + Destroy(); +} + +/** + * Initializes a file list. + * + * @return VBox status code. + * @param strSrcRootAbs Source root path (absolute) for this file list. + * @param strDstRootAbs Destination root path (absolute) for this file list. + * @param SourceSpec Source specification to use. + */ +int FsList::Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs, + const GuestSessionFsSourceSpec &SourceSpec) +{ + mSrcRootAbs = strSrcRootAbs; + mDstRootAbs = strDstRootAbs; + mSourceSpec = SourceSpec; + + /* Note: Leave the source and dest roots unmodified -- how paths will be treated + * will be done directly when working on those. See @bugref{10139}. */ + + LogFlowFunc(("mSrcRootAbs=%s, mDstRootAbs=%s, fDirCopyFlags=%#x, fFileCopyFlags=%#x\n", + mSrcRootAbs.c_str(), mDstRootAbs.c_str(), mSourceSpec.fDirCopyFlags, mSourceSpec.fFileCopyFlags)); + + return VINF_SUCCESS; +} + +/** + * Destroys a file list. + */ +void FsList::Destroy(void) +{ + LogFlowFuncEnter(); + + FsEntries::iterator itEntry = mVecEntries.begin(); + while (itEntry != mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + delete pEntry; + mVecEntries.erase(itEntry); + itEntry = mVecEntries.begin(); + } + + Assert(mVecEntries.empty()); + + LogFlowFuncLeave(); +} + +#ifdef DEBUG +/** + * Dumps a FsList to the debug log. + */ +void FsList::DumpToLog(void) +{ + LogFlowFunc(("strSrcRootAbs=%s, strDstRootAbs=%s\n", mSrcRootAbs.c_str(), mDstRootAbs.c_str())); + + FsEntries::iterator itEntry = mVecEntries.begin(); + while (itEntry != mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + LogFlowFunc(("\tstrPath=%s (fMode %#x)\n", pEntry->strPath.c_str(), pEntry->fMode)); + ++itEntry; + } + + LogFlowFuncLeave(); +} +#endif /* DEBUG */ + +/** + * Builds a guest file list from a given path (and optional filter). + * + * @return VBox status code. + * @param strPath Directory on the guest to build list from. + * @param strSubDir Current sub directory path; needed for recursion. + * Set to an empty path. + */ +int FsList::AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir /* = "" */) +{ + Utf8Str strPathAbs = strPath; + if (!strPathAbs.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle))) + strPathAbs += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle); + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle))) + strPathSub += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle); + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str())); + + LogRel2(("Guest Control: Handling directory \"%s\" on guest ...\n", strPathAbs.c_str())); + + GuestDirectoryOpenInfo dirOpenInfo; + dirOpenInfo.mFilter = ""; + dirOpenInfo.mPath = strPathAbs; + dirOpenInfo.mFlags = 0; /** @todo Handle flags? */ + + const ComObjPtr<GuestSession> &pSession = mTask.GetSession(); + + ComObjPtr <GuestDirectory> pDir; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = pSession->i_directoryOpen(dirOpenInfo, pDir, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_INVALID_PARAMETER: + break; + + case VERR_GSTCTL_GUEST_ERROR: + break; + + default: + break; + } + + return vrc; + } + + if (strPathSub.isNotEmpty()) + { + GuestFsObjData fsObjData; + fsObjData.mType = FsObjType_Directory; + + vrc = AddEntryFromGuest(strPathSub, fsObjData); + } + + if (RT_SUCCESS(vrc)) + { + ComObjPtr<GuestFsObjInfo> fsObjInfo; + while (RT_SUCCESS(vrc = pDir->i_read(fsObjInfo, &vrcGuest))) + { + FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */ + HRESULT hrc2 = fsObjInfo->COMGETTER(Type)(&enmObjType); + AssertComRC(hrc2); + + com::Bstr bstrName; + hrc2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam()); + AssertComRC(hrc2); + + Utf8Str strEntry = strPathSub + Utf8Str(bstrName); + + LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str())); + + switch (enmObjType) + { + case FsObjType_Directory: + { + if ( bstrName.equals(".") + || bstrName.equals("..")) + { + break; + } + + LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str())); + + if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive)) + break; + + vrc = AddDirFromGuest(strPath, strEntry); + break; + } + + case FsObjType_Symlink: + { + if ( mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks + || mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks) + { + /** @todo Symlink handling from guest is not implemented yet. + * See IGuestSession::symlinkRead(). */ + LogRel2(("Guest Control: Warning: Symlink support on guest side not available, skipping \"%s\"\n", + strEntry.c_str())); + } + break; + } + + case FsObjType_File: + { + LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str())); + + vrc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData()); + break; + } + + default: + break; + } + } + + if (vrc == VERR_NO_MORE_FILES) /* End of listing reached? */ + vrc = VINF_SUCCESS; + } + + int vrc2 = pDir->i_closeInternal(&vrcGuest); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; +} + +/** + * Builds a host file list from a given path. + * + * @return VBox status code. + * @param strPath Directory on the host to build list from. + * @param strSubDir Current sub directory path; needed for recursion. + * Set to an empty path. + * @param pszPathReal Scratch buffer for holding the resolved real path. + * Needed for recursion. + * @param cbPathReal Size (in bytes) of \a pszPathReal. + * @param pDirEntry Where to store looked up directory information for handled paths. + * Needed for recursion. + */ +int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir, + char *pszPathReal, size_t cbPathReal, PRTDIRENTRYEX pDirEntry) +{ + Utf8Str strPathAbs = strPath; + if (!strPathAbs.endsWith(RTPATH_SLASH_STR)) + strPathAbs += RTPATH_SLASH_STR; + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith(RTPATH_SLASH_STR)) + strPathSub += RTPATH_SLASH_STR; + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str())); + + LogRel2(("Guest Control: Handling directory \"%s\" on host ...\n", strPathAbs.c_str())); + + RTFSOBJINFO objInfo; + int vrc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + if (strPathSub.isNotEmpty()) + vrc = AddEntryFromHost(strPathSub, &objInfo); + + if (RT_SUCCESS(vrc)) + { + RTDIR hDir; + vrc = RTDirOpen(&hDir, strPathAbs.c_str()); + if (RT_SUCCESS(vrc)) + { + do + { + /* Retrieve the next directory entry. */ + vrc = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_NO_MORE_FILES) + vrc = VINF_SUCCESS; + break; + } + + Utf8Str strEntry = strPathSub + Utf8Str(pDirEntry->szName); + + LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str())); + + switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + /* Skip "." and ".." entries. */ + if (RTDirEntryExIsStdDotLink(pDirEntry)) + break; + + LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str())); + + if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive)) + break; + + vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry); + break; + } + + case RTFS_TYPE_FILE: + { + LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str())); + + vrc = AddEntryFromHost(strEntry, &pDirEntry->Info); + break; + } + + case RTFS_TYPE_SYMLINK: + { + Utf8Str strEntryAbs = strPathAbs + (const char *)pDirEntry->szName; + + vrc = RTPathReal(strEntryAbs.c_str(), pszPathReal, cbPathReal); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfo(pszPathReal, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (directory)\n", + strEntryAbs.c_str(), pszPathReal)); + if (mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks) + vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry); + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + { + LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (file)\n", + strEntryAbs.c_str(), pszPathReal)); + if (mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks) + vrc = AddEntryFromHost(strEntry, &objInfo); + } + else + vrc = VERR_NOT_SUPPORTED; + } + + if (RT_FAILURE(vrc)) + LogRel2(("Guest Control: Unable to query symbolic link info for \"%s\", rc=%Rrc\n", + pszPathReal, vrc)); + } + else + { + LogRel2(("Guest Control: Unable to resolve symlink for \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc)); + if (vrc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */ + vrc = VINF_SUCCESS; + } + break; + } + + default: + break; + } + + } while (RT_SUCCESS(vrc)); + + RTDirClose(hDir); + } + } + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + vrc = VERR_IS_A_FILE; + else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode)) + vrc = VERR_IS_A_SYMLINK; + else + vrc = VERR_NOT_SUPPORTED; + } + else + LogFlowFunc(("Unable to query \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS) + : GuestSessionTask(pSession) + , mFlags(uFlags) + , mTimeoutMS(uTimeoutMS) +{ + m_strTaskName = "gctlSesOpen"; +} + +GuestSessionTaskOpen::~GuestSessionTaskOpen(void) +{ + +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskOpen::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = mSession->i_startSession(NULL /*pvrcGuest*/); + /* Nothing to do here anymore. */ + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionCopyTask::GuestSessionCopyTask(GuestSession *pSession) + : GuestSessionTask(pSession) +{ +} + +GuestSessionCopyTask::~GuestSessionCopyTask() +{ + FsLists::iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pFsList = (*itList); + pFsList->Destroy(); + delete pFsList; + mVecLists.erase(itList); + itList = mVecLists.begin(); + } + + Assert(mVecLists.empty()); +} + +GuestSessionTaskCopyFrom::GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, + const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyFrm"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void) +{ +} + +/** + * Initializes a copy-from-guest task. + * + * @returns HRESULT + * @param strTaskDesc Friendly task description. + */ +HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc) +{ + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hrc = pProgress.createObject(); + if (FAILED(hrc)) + return hrc; + + mProgress = pProgress; + + int vrc = VINF_SUCCESS; + + ULONG cOperations = 0; + Utf8Str strErrorInfo; + + /** + * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyFrom::Run + * because the caller expects a ready-for-operation progress object on return. + * The progress object will have a variable operation count, based on the elements to + * be processed. + */ + + if (mSources.empty()) + { + strErrorInfo.printf(tr("No guest sources specified")); + vrc = VERR_INVALID_PARAMETER; + } + else if (mDest.isEmpty()) + { + strErrorInfo.printf(tr("Host destination must not be empty")); + vrc = VERR_INVALID_PARAMETER; + } + else + { + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + bool fFollowSymlinks; + + if (strSrc.isEmpty()) + { + strErrorInfo.printf(tr("Guest source entry must not be empty")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + if (itSrc->enmType == FsObjType_Directory) + { + fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks; + } + else + { + fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks); + } + + LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s, fFollowSymlinks=%RTbool\n", + strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str(), fFollowSymlinks)); + + GuestFsObjData srcObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + vrc = mSession->i_fsQueryInfo(strSrc, fFollowSymlinks, srcObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + strErrorInfo = GuestBase::getErrorAsString(tr("Guest source lookup failed"), + GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str())); + else + strErrorInfo.printf(tr("Guest source lookup for \"%s\" failed: %Rrc"), + strSrc.c_str(), vrc); + break; + } + + if (srcObjData.mType == FsObjType_Directory) + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo.printf(tr("Guest source is not a file: %s"), strSrc.c_str()); + vrc = VERR_NOT_A_FILE; + break; + } + } + else + { + if (itSrc->enmType != FsObjType_File) + { + strErrorInfo.printf(tr("Guest source is not a directory: %s"), strSrc.c_str()); + vrc = VERR_NOT_A_DIRECTORY; + break; + } + } + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + vrc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(vrc)) + { + switch (itSrc->enmType) + { + case FsObjType_Directory: + { + vrc = pFsList->AddDirFromGuest(strSrc); + break; + } + + case FsObjType_File: + /* The file name is already part of the actual list's source root (strSrc). */ + break; + + default: + LogRel2(("Guest Control: Warning: Unknown guest file system type %#x for source \"%s\", skipping\n", + itSrc->enmType, strSrc.c_str())); + break; + } + } + + if (RT_FAILURE(vrc)) + { + delete pFsList; + strErrorInfo.printf(tr("Error adding guest source \"%s\" to list: %Rrc"), + strSrc.c_str(), vrc); + break; + } +#ifdef DEBUG + pFsList->DumpToLog(); +#endif + mVecLists.push_back(pFsList); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + } + + if (RT_SUCCESS(vrc)) + { + /* When there are no entries in the first source list, this means the source only contains a single file + * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */ + Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0 + ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs; + + /* Now that we know how many objects we're handling, tweak the progress description so that it + * reflects more accurately what the progress is actually doing. */ + if (cOperations > 1) + { + mDesc.printf(tr("Copying \"%s\" [and %zu %s] from guest to \"%s\" on the host ..."), + strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str()); + } + else + mDesc.printf(tr("Copying \"%s\" from guest to \"%s\" on the host ..."), strFirstOp.c_str(), mDest.c_str()); + + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, Bstr(strFirstOp).raw()); + } + else /* On error we go with an "empty" progress object when will be used for error handling. */ + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */ + return hrc; + + if (RT_FAILURE(vrc)) + { + if (strErrorInfo.isEmpty()) + strErrorInfo.printf(tr("Failed with %Rrc"), vrc); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc)); + return hrc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskCopyFrom::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + Utf8Str strSrcRootAbs = pList->mSrcRootAbs; + Utf8Str strDstRootAbs = pList->mDstRootAbs; + + vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, mSession->i_getGuestPathStyle() /* Source */, + strDstRootAbs, PATH_STYLE_NATIVE /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Building host destination root path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + bool fCopyIntoExisting; + bool fFollowSymlinks; + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks); + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks); + } + else + AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED); + + uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + uint32_t fDirCreate = 0; + + bool fDstExists = true; + + RTFSOBJINFO dstFsObjInfo; + RT_ZERO(dstFsObjInfo); + vrc = RTPathQueryInfoEx(strDstRootAbs.c_str(), &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */); + if (RT_SUCCESS(vrc)) + { + char szPathReal[RTPATH_MAX]; + vrc = RTPathReal(strDstRootAbs.c_str(), szPathReal, sizeof(szPathReal)); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfoEx(szPathReal, &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK /* fFlags */); + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Host destination is a symbolic link \"%s\" -> \"%s\" (%s)\n", + strDstRootAbs.c_str(), szPathReal, + GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + } + + strDstRootAbs = szPathReal; + } + } + else + { + if ( vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + { + fDstExists = false; + vrc = VINF_SUCCESS; + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host path lookup for \"%s\" failed: %Rrc"), strDstRootAbs.c_str(), vrc)); + break; + } + } + + /* Create the root directory. */ + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + + if (fDstExists) + { + switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host root directory \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + break; + } + break; + } + + case RTFS_TYPE_FILE: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a file"), strDstRootAbs.c_str())); + vrc = VERR_IS_A_FILE; + break; + } + + default: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""), + dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(vrc)) + break; + + /* Make sure the destination root directory exists. */ + if (pList->mSourceSpec.fDryRun == false) + { + vrc = directoryCreateOnHost(strDstRootAbs, fDirMode, 0 /* fCreate */, true /* fCanExist */); + if (RT_FAILURE(vrc)) + break; + } + + AssertBreakStmt(pList->mSourceSpec.enmType == FsObjType_Directory, vrc = VERR_NOT_SUPPORTED); + + /* Walk the entries. */ + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while (itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = strSrcRootAbs; + Utf8Str strDstAbs = strDstRootAbs; + + strSrcAbs += PATH_STYLE_SEP_STR(pList->mSourceSpec.enmPathStyle); + strSrcAbs += pEntry->strPath; + + strDstAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE); + strDstAbs += pEntry->strPath; + + /* Clean up the final guest source path. */ + vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */, + pList->mSourceSpec.enmPathStyle /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest source path \"%s\" failed: %Rrc"), + strSrcAbs.c_str(), vrc)); + break; + } + + /* Translate the final host desitnation path. */ + vrc = GuestPath::Translate(strDstAbs, mSession->i_getGuestPathStyle() /* Source */, PATH_STYLE_NATIVE /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"), + strDstAbs.c_str(), vrc)); + break; + } + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + if (!pList->mSourceSpec.fDryRun) + vrc = directoryCreateOnHost(strDstAbs, fDirMode, fDirCreate, fCopyIntoExisting); + break; + + case RTFS_TYPE_FILE: + RT_FALL_THROUGH(); + case RTFS_TYPE_SYMLINK: + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyFromGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags); + break; + + default: + AssertFailed(); /* Should never happen (we already have a filtered list). */ + break; + } + + if (RT_FAILURE(vrc)) + break; + + ++itEntry; + } + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + + if (fDstExists) + { + switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a directory"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_DIRECTORY; + break; + } + + case RTFS_TYPE_FILE: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host file \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + default: + { + /** @todo Resolve symlinks? */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""), + dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* Translate the final host destination file path. */ + vrc = GuestPath::Translate(strDstRootAbs, + mSession->i_getGuestPathStyle() /* Dest */, PATH_STYLE_NATIVE /* Source */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyFromGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags); + } + } + else + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + + if (RT_FAILURE(vrc)) + break; + + ++itList; + } + + if (RT_SUCCESS(vrc)) + vrc = setProgressSuccess(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, + const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyTo"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void) +{ +} + +/** + * Initializes a copy-to-guest task. + * + * @returns HRESULT + * @param strTaskDesc Friendly task description. + */ +HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc) +{ + LogFlowFuncEnter(); + + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hrc = pProgress.createObject(); + if (FAILED(hrc)) + return hrc; + + mProgress = pProgress; + + int vrc = VINF_SUCCESS; + + ULONG cOperations = 0; + Utf8Str strErrorInfo; + + /* + * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyTo::Run + * because the caller expects a ready-for-operation progress object on return. + * The progress object will have a variable operation count, based on the elements to + * be processed. + */ + + if (mSources.empty()) + { + strErrorInfo.printf(tr("No host sources specified")); + vrc = VERR_INVALID_PARAMETER; + } + else if (mDest.isEmpty()) + { + strErrorInfo.printf(tr("Guest destination must not be empty")); + vrc = VERR_INVALID_PARAMETER; + } + else + { + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + bool fFollowSymlinks; + + if (strSrc.isEmpty()) + { + strErrorInfo.printf(tr("Host source entry must not be empty")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + if (itSrc->enmType == FsObjType_Directory) + { + fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks; + } + else + { + fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks); + } + + LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s\n", + strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str())); + + RTFSOBJINFO srcFsObjInfo; + vrc = RTPathQueryInfoEx(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */); + if (RT_FAILURE(vrc)) + { + strErrorInfo.printf(tr("No such host file/directory: %s"), strSrc.c_str()); + break; + } + + switch (srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo.printf(tr("Host source \"%s\" is not a file (is a directory)"), strSrc.c_str()); + vrc = VERR_NOT_A_FILE; + } + break; + } + + case RTFS_TYPE_FILE: + { + if (itSrc->enmType == FsObjType_Directory) + { + strErrorInfo.printf(tr("Host source \"%s\" is not a directory (is a file)"), strSrc.c_str()); + vrc = VERR_NOT_A_DIRECTORY; + } + break; + } + + case RTFS_TYPE_SYMLINK: + { + if (!fFollowSymlinks) + { + strErrorInfo.printf(tr("Host source \"%s\" is a symbolic link"), strSrc.c_str()); + vrc = VERR_IS_A_SYMLINK; + break; + } + + char szPathReal[RTPATH_MAX]; + vrc = RTPathReal(strSrc.c_str(), szPathReal, sizeof(szPathReal)); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfoEx(szPathReal, &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Host source is a symbolic link \"%s\" -> \"%s\" (%s)\n", + strSrc.c_str(), szPathReal, + GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode)))); + + /* We want to keep the symbolic link name of the source instead of the target pointing to, + * so don't touch the source's name here. */ + itSrc->enmType = GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode); + } + else + { + strErrorInfo.printf(tr("Querying symbolic link info for host source \"%s\" failed"), strSrc.c_str()); + break; + } + } + else + { + strErrorInfo.printf(tr("Resolving symbolic link for host source \"%s\" failed"), strSrc.c_str()); + break; + } + break; + } + + default: + LogRel2(("Guest Control: Warning: Unknown host file system type %#x for source \"%s\", skipping\n", + srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strSrc.c_str())); + break; + } + + if (RT_FAILURE(vrc)) + break; + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + vrc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(vrc)) + { + switch (itSrc->enmType) + { + case FsObjType_Directory: + { + char szPathReal[RTPATH_MAX]; + RTDIRENTRYEX DirEntry; + vrc = pFsList->AddDirFromHost(strSrc /* strPath */, "" /* strSubDir */, + szPathReal, sizeof(szPathReal), &DirEntry); + break; + } + + case FsObjType_File: + /* The file name is already part of the actual list's source root (strSrc). */ + break; + + case FsObjType_Symlink: + AssertFailed(); /* Should never get here, as we do the resolving above. */ + break; + + default: + LogRel2(("Guest Control: Warning: Unknown source type %#x for host source \"%s\", skipping\n", + itSrc->enmType, strSrc.c_str())); + break; + } + } + + if (RT_FAILURE(vrc)) + { + delete pFsList; + strErrorInfo.printf(tr("Error adding host source \"%s\" to list: %Rrc"), + strSrc.c_str(), vrc); + break; + } +#ifdef DEBUG + pFsList->DumpToLog(); +#endif + mVecLists.push_back(pFsList); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + } + + if (RT_SUCCESS(vrc)) + { + /* When there are no entries in the first source list, this means the source only contains a single file + * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */ + Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0 + ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs; + + /* Now that we know how many objects we're handling, tweak the progress description so that it + * reflects more accurately what the progress is actually doing. */ + if (cOperations > 1) + { + mDesc.printf(tr("Copying \"%s\" [and %zu %s] from host to \"%s\" on the guest ..."), + strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str()); + } + else + mDesc.printf(tr("Copying \"%s\" from host to \"%s\" on the guest ..."), strFirstOp.c_str(), mDest.c_str()); + + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1/* Number of operations */, + Bstr(strFirstOp).raw()); + } + else /* On error we go with an "empty" progress object when will be used for error handling. */ + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */ + return hrc; + + if (RT_FAILURE(vrc)) + { + if (strErrorInfo.isEmpty()) + strErrorInfo.printf(tr("Failed with %Rrc"), vrc); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc)); + return hrc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskCopyTo::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + Utf8Str strSrcRootAbs = pList->mSrcRootAbs; + Utf8Str strDstRootAbs = pList->mDstRootAbs; + + vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, PATH_STYLE_NATIVE /* Source */, + strDstRootAbs, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Building guest destination root path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + bool fCopyIntoExisting; + bool fFollowSymlinks; + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks); + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks); + } + else + AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED); + + uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + + bool fDstExists = true; + + GuestFsObjData dstObjData; + int vrcGuest; + vrc = mSession->i_fsQueryInfo(strDstRootAbs, fFollowSymlinks, dstObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + switch (vrcGuest) + { + case VERR_PATH_NOT_FOUND: + RT_FALL_THROUGH(); + case VERR_FILE_NOT_FOUND: + { + fDstExists = false; + vrc = VINF_SUCCESS; + break; + } + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrcGuest)); + break; + } + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + } + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType))); + + if (fDstExists) + { + switch (dstObjData.mType) + { + case FsObjType_Directory: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest root directory \"%s\" already exists"), + strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + case FsObjType_File: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on guest already exists and is a file"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_FILE; + } + + case FsObjType_Symlink: + /** @todo Resolve symlinks? */ + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on guest for \"%s\""), + dstObjData.mType, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(vrc)) + break; + + /* Make sure the destination root directory exists. */ + if (pList->mSourceSpec.fDryRun == false) + { + vrc = directoryCreateOnGuest(strDstRootAbs, fDirMode, DirectoryCreateFlag_None, + fFollowSymlinks, fCopyIntoExisting); + if (RT_FAILURE(vrc)) + break; + } + + /* Walk the entries. */ + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while ( RT_SUCCESS(vrc) + && itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = strSrcRootAbs; + Utf8Str strDstAbs = strDstRootAbs; + + strSrcAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE); + strSrcAbs += pEntry->strPath; + + strDstAbs += PATH_STYLE_SEP_STR(mSession->i_getGuestPathStyle()); + strDstAbs += pEntry->strPath; + + /* Clean up the final host source path. */ + vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */, + pList->mSourceSpec.enmPathStyle /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host source path\"%s\" failed: %Rrc"), + strSrcAbs.c_str(), vrc)); + break; + } + + /* Translate final guest destination path. */ + vrc = GuestPath::Translate(strDstAbs, + PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"), + strDstAbs.c_str(), vrc)); + break; + } + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + LogRel2(("Guest Control: Copying directory \"%s\" from host to \"%s\" on guest ...\n", + strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + vrc = directoryCreateOnGuest(strDstAbs, fDirMode, DirectoryCreateFlag_None, + fFollowSymlinks, fCopyIntoExisting); + break; + } + + case RTFS_TYPE_FILE: + { + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyToGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags); + break; + } + + default: + LogRel2(("Guest Control: Warning: Host file system type 0x%x for \"%s\" is not supported, skipping\n", + pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str())); + break; + } + + if (RT_FAILURE(vrc)) + break; + + ++itEntry; + } + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType))); + + if (fDstExists) + { + switch (dstObjData.mType) + { + case FsObjType_Directory: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the guest already exists and is a directory"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_DIRECTORY; + break; + } + + case FsObjType_File: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + default: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unsupported guest file system type (%#x) for \"%s\""), + dstObjData.mType, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* Translate the final guest destination file path. */ + vrc = GuestPath::Translate(strDstRootAbs, + PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyToGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags); + } + } + else + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + + if (RT_FAILURE(vrc)) + break; + + ++itList; + } + + if (RT_SUCCESS(vrc)) + vrc = setProgressSuccess(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskUpdateAdditions::GuestSessionTaskUpdateAdditions(GuestSession *pSession, + const Utf8Str &strSource, + const ProcessArguments &aArguments, + uint32_t fFlags) + : GuestSessionTask(pSession) +{ + m_strTaskName = "gctlUpGA"; + + mSource = strSource; + mArguments = aArguments; + mFlags = fFlags; +} + +GuestSessionTaskUpdateAdditions::~GuestSessionTaskUpdateAdditions(void) +{ + +} + +/** + * Adds arguments to existing process arguments. + * Identical / already existing arguments will be filtered out. + * + * @returns VBox status code. + * @param aArgumentsDest Destination to add arguments to. + * @param aArgumentsSource Arguments to add. + */ +int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource) +{ + try + { + /* Filter out arguments which already are in the destination to + * not end up having them specified twice. Not the fastest method on the + * planet but does the job. */ + ProcessArguments::const_iterator itSource = aArgumentsSource.begin(); + while (itSource != aArgumentsSource.end()) + { + bool fFound = false; + ProcessArguments::iterator itDest = aArgumentsDest.begin(); + while (itDest != aArgumentsDest.end()) + { + if ((*itDest).equalsIgnoreCase((*itSource))) + { + fFound = true; + break; + } + ++itDest; + } + + if (!fFound) + aArgumentsDest.push_back((*itSource)); + + ++itSource; + } + } + catch(std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +/** + * Helper function to copy a file from a VISO to the guest. + * + * @returns VBox status code. + * @param pSession Guest session to use. + * @param hVfsIso VISO handle to use. + * @param strFileSrc Source file path on VISO to copy. + * @param strFileDst Destination file path on guest. + * @param fOptional When set to \c true, the file is optional, i.e. can be skipped + * when not found, \c false if not. + */ +int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso, + Utf8Str const &strFileSrc, const Utf8Str &strFileDst, bool fOptional) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertReturn(hVfsIso != NIL_RTVFS, VERR_INVALID_POINTER); + + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + int vrc = RTVfsFileOpen(hVfsIso, strFileSrc.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + uint64_t cbSrcSize = 0; + vrc = RTVfsFileQuerySize(hVfsFile, &cbSrcSize); + if (RT_SUCCESS(vrc)) + { + LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n", + strFileSrc.c_str(), strFileDst.c_str())); + + GuestFileOpenInfo dstOpenInfo; + dstOpenInfo.mFilename = strFileDst; + dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace; + dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly; + dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> dstFile; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(vrcGuest, strFileDst.c_str())); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), + strFileDst.c_str(), vrc)); + break; + } + } + else + { + vrc = fileCopyToGuestInner(strFileSrc, hVfsFile, strFileDst, dstFile, FileCopyFlag_None, 0 /*offCopy*/, cbSrcSize); + + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + } + + RTVfsFileRelease(hVfsFile); + } + else if (fOptional) + vrc = VINF_SUCCESS; + + return vrc; +} + +/** + * Helper function to run (start) a file on the guest. + * + * @returns VBox status code. + * @param pSession Guest session to use. + * @param procInfo Guest process startup info to use. + */ +int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + LogRel(("Running %s ...\n", procInfo.mName.c_str())); + + GuestProcessTool procTool; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = procTool.init(pSession, procInfo, false /* Async */, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (RT_SUCCESS(vrcGuest)) + vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &vrcGuest); + if (RT_SUCCESS(vrc)) + vrc = procTool.getTerminationStatus(); + } + + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_PROCESS_EXIT_CODE: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Running update file \"%s\" on guest failed: %Rrc"), + procInfo.mExecutable.c_str(), procTool.getRc())); + break; + + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Running update file on guest failed"), + GuestErrorInfo(GuestErrorInfo::Type_Process, vrcGuest, procInfo.mExecutable.c_str())); + break; + + case VERR_INVALID_STATE: /** @todo Special guest control rc needed! */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Update file \"%s\" reported invalid running state"), + procInfo.mExecutable.c_str())); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Error while running update file \"%s\" on guest: %Rrc"), + procInfo.mExecutable.c_str(), vrc)); + break; + } + } + + return vrc; +} + +/** + * Helper function which waits until Guest Additions services started. + * + * @returns 0 on success or VERR_TIMEOUT if guest services were not + * started on time. + * @param pGuest Guest interface to use. + */ +int GuestSessionTaskUpdateAdditions::waitForGuestSession(ComObjPtr<Guest> pGuest) +{ + int vrc = VERR_GSTCTL_GUEST_ERROR; + int rc = VERR_TIMEOUT; + + uint64_t tsStart = RTTimeSystemMilliTS(); + const uint64_t timeoutMs = 600 * 1000; + + AssertReturn(!pGuest.isNull(), VERR_TIMEOUT); + + do + { + ComObjPtr<GuestSession> pSession; + GuestCredentials guestCreds; + GuestSessionStartupInfo startupInfo; + + startupInfo.mName = "Guest Additions connection checker"; + startupInfo.mOpenTimeoutMS = 100; + + vrc = pGuest->i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_SUCCESS(vrc)) + { + int vrcGuest = VERR_GSTCTL_GUEST_ERROR; /* unused. */ + + Assert(!pSession.isNull()); + + vrc = pSession->i_startSession(&vrcGuest); + if (RT_SUCCESS(vrc)) + { + GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None; + int rcGuest = 0; /* unused. */ + + /* Wait for VBoxService to start. */ + vrc = pSession->i_waitFor(GuestSessionWaitForFlag_Start, 100 /* timeout, ms */, enmWaitResult, &rcGuest); + if (RT_SUCCESS(vrc)) + { + vrc = pSession->Close(); + rc = 0; + break; + } + } + + vrc = pSession->Close(); + } + + RTThreadSleep(100); + + } while ((RTTimeSystemMilliTS() - tsStart) < timeoutMs); + + return rc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskUpdateAdditions::Run(void) +{ + LogFlowThisFuncEnter(); + + ComObjPtr<GuestSession> pSession = mSession; + Assert(!pSession.isNull()); + + AutoCaller autoCaller(pSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = setProgress(10); + if (RT_FAILURE(vrc)) + return vrc; + + HRESULT hrc = S_OK; + + LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", mSource.c_str())); + + ComObjPtr<Guest> pGuest(mSession->i_getParent()); +#if 0 + /* + * Wait for the guest being ready within 30 seconds. + */ + AdditionsRunLevelType_T addsRunLevel; + uint64_t tsStart = RTTimeSystemMilliTS(); + while ( SUCCEEDED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + && ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000) + { + vrc = VERR_TIMEOUT; + break; + } + + RTThreadSleep(100); /* Wait a bit. */ + } + + if (FAILED(hrc)) vrc = VERR_TIMEOUT; + if (vrc == VERR_TIMEOUT) + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions were not ready within time, giving up"))); +#else + /* + * For use with the GUI we don't want to wait, just return so that the manual .ISO mounting + * can continue. + */ + AdditionsRunLevelType_T addsRunLevel; + if ( FAILED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + || ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if (addsRunLevel == AdditionsRunLevelType_System) + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions are installed but not fully loaded yet, aborting automatic update"))); + else + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions not installed or ready, aborting automatic update"))); + vrc = VERR_NOT_SUPPORTED; + } +#endif + + if (RT_SUCCESS(vrc)) + { + /* + * Determine if we are able to update automatically. This only works + * if there are recent Guest Additions installed already. + */ + Utf8Str strAddsVer; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"), + strAddsVer.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + + Utf8Str strOSVer; + eOSType osType = eOSType_Unknown; + if (RT_SUCCESS(vrc)) + { + /* + * Determine guest OS type and the required installer image. + */ + Utf8Str strOSType; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType); + if (RT_SUCCESS(vrc)) + { + if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive) + || strOSType.contains("Windows", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Windows; + + /* + * Determine guest OS version. + */ + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Unable to detected guest OS version, please update manually"))); + vrc = VERR_NOT_SUPPORTED; + } + + /* Because Windows 2000 + XP and is bitching with WHQL popups even if we have signed drivers we + * can't do automated updates here. */ + /* Windows XP 64-bit (5.2) is a Windows 2003 Server actually, so skip this here. */ + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0) + { + if ( strOSVer.startsWith("5.0") /* Exclude the build number. */ + || strOSVer.startsWith("5.1") /* Exclude the build number. */) + { + /* If we don't have AdditionsUpdateFlag_WaitForUpdateStartOnly set we can't continue + * because the Windows Guest Additions installer will fail because of WHQL popups. If the + * flag is set this update routine ends successfully as soon as the installer was started + * (and the user has to deal with it in the guest). */ + if (!(mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually"))); + vrc = VERR_NOT_SUPPORTED; + } + } + } + else + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("%s (%s) not supported for automatic updating, please update manually"), + strOSType.c_str(), strOSVer.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Solaris; + } + else /* Everything else hopefully means Linux :-). */ + osType = eOSType_Linux; + + if ( RT_SUCCESS(vrc) + && ( osType != eOSType_Windows + && osType != eOSType_Linux)) + /** @todo Support Solaris. */ + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"), + strOSType.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* + * Try to open the .ISO file to extract all needed files. + */ + RTVFSFILE hVfsFileIso; + vrc = RTVfsFileOpenNormal(mSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFileIso); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"), + mSource.c_str(), vrc)); + } + else + { + RTVFS hVfsIso; + vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, NULL); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unable to open file as ISO 9660 file system volume: %Rrc"), vrc)); + } + else + { + Utf8Str strUpdateDir; + + vrc = setProgress(5); + if (RT_SUCCESS(vrc)) + { + /* Try getting the installed Guest Additions version to know whether we + * can install our temporary Guest Addition data into the original installation + * directory. + * + * Because versions prior to 4.2 had bugs wrt spaces in paths we have to choose + * a different location then. + */ + bool fUseInstallDir = false; + + Utf8Str strAddsVer; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0) + { + fUseInstallDir = true; + } + + if (fUseInstallDir) + { + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir); + if (RT_SUCCESS(vrc)) + { + if (strUpdateDir.isNotEmpty()) + { + if (osType == eOSType_Windows) + { + strUpdateDir.findReplace('/', '\\'); + strUpdateDir.append("\\Update\\"); + } + else + strUpdateDir.append("/update/"); + } + /* else Older Guest Additions might not handle this property correctly. */ + } + /* Ditto. */ + } + + /** @todo Set fallback installation directory. Make this a *lot* smarter. Later. */ + if (strUpdateDir.isEmpty()) + { + if (osType == eOSType_Windows) + strUpdateDir = "C:\\Temp\\"; + else + strUpdateDir = "/tmp/"; + } + } + + /* Create the installation directory. */ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + if (RT_SUCCESS(vrc)) + { + LogRel(("Guest Additions update directory is: %s\n", strUpdateDir.c_str())); + + vrc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Creating installation directory on guest failed"), + GuestErrorInfo(GuestErrorInfo::Type_Directory, vrcGuest, strUpdateDir.c_str())); + break; + + default: + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Creating installation directory \"%s\" on guest failed: %Rrc"), + strUpdateDir.c_str(), vrc)); + break; + } + } + } + + if (RT_SUCCESS(vrc)) + vrc = setProgress(10); + + if (RT_SUCCESS(vrc)) + { + /* Prepare the file(s) we want to copy over to the guest and + * (maybe) want to run. */ + switch (osType) + { + case eOSType_Windows: + { + /* Do we need to install our certificates? We do this for W2K and up. */ + bool fInstallCert = false; + + /* Only Windows 2000 and up need certificates to be installed. */ + if (RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0) + { + fInstallCert = true; + LogRel(("Certificates for auto updating WHQL drivers will be installed\n")); + } + else + LogRel(("Skipping installation of certificates for WHQL drivers\n")); + + if (fInstallCert) + { + static struct { const char *pszDst, *pszIso; } const s_aCertFiles[] = + { + { "vbox.cer", "/CERT/VBOX.CER" }, + { "vbox-sha1.cer", "/CERT/VBOX-SHA1.CER" }, + { "vbox-sha256.cer", "/CERT/VBOX-SHA256.CER" }, + { "vbox-sha256-r3.cer", "/CERT/VBOX-SHA256-R3.CER" }, + { "oracle-vbox.cer", "/CERT/ORACLE-VBOX.CER" }, + }; + uint32_t fCopyCertUtil = ISOFILE_FLAG_COPY_FROM_ISO; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aCertFiles); i++) + { + /* Skip if not present on the ISO. */ + RTFSOBJINFO ObjInfo; + vrc = RTVfsQueryPathInfo(hVfsIso, s_aCertFiles[i].pszIso, &ObjInfo, RTFSOBJATTRADD_NOTHING, + RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + continue; + + /* Copy the certificate certificate. */ + Utf8Str const strDstCert(strUpdateDir + s_aCertFiles[i].pszDst); + mFiles.push_back(ISOFile(s_aCertFiles[i].pszIso, + strDstCert, + ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_OPTIONAL)); + + /* Out certificate installation utility. */ + /* First pass: Copy over the file (first time only) + execute it to remove any + * existing VBox certificates. */ + GuestProcessStartupInfo siCertUtilRem; + siCertUtilRem.mName = "VirtualBox Certificate Utility, removing old VirtualBox certificates"; + /* The argv[0] should contain full path to the executable module */ + siCertUtilRem.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe"); + siCertUtilRem.mArguments.push_back(Utf8Str("remove-trusted-publisher")); + siCertUtilRem.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */ + siCertUtilRem.mArguments.push_back(strDstCert); + siCertUtilRem.mArguments.push_back(strDstCert); + mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE", + strUpdateDir + "VBoxCertUtil.exe", + fCopyCertUtil | ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL, + siCertUtilRem)); + fCopyCertUtil = 0; + /* Second pass: Only execute (but don't copy) again, this time installng the + * recent certificates just copied over. */ + GuestProcessStartupInfo siCertUtilAdd; + siCertUtilAdd.mName = "VirtualBox Certificate Utility, installing VirtualBox certificates"; + /* The argv[0] should contain full path to the executable module */ + siCertUtilAdd.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe"); + siCertUtilAdd.mArguments.push_back(Utf8Str("add-trusted-publisher")); + siCertUtilAdd.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */ + siCertUtilAdd.mArguments.push_back(strDstCert); + siCertUtilAdd.mArguments.push_back(strDstCert); + mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE", + strUpdateDir + "VBoxCertUtil.exe", + ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL, + siCertUtilAdd)); + } + } + /* The installers in different flavors, as we don't know (and can't assume) + * the guest's bitness. */ + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-X86.EXE", + strUpdateDir + "VBoxWindowsAdditions-x86.exe", + ISOFILE_FLAG_COPY_FROM_ISO)); + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-AMD64.EXE", + strUpdateDir + "VBoxWindowsAdditions-amd64.exe", + ISOFILE_FLAG_COPY_FROM_ISO)); + /* The stub loader which decides which flavor to run. */ + GuestProcessStartupInfo siInstaller; + siInstaller.mName = "VirtualBox Windows Guest Additions Installer"; + /* Set a running timeout of 5 minutes -- the Windows Guest Additions + * setup can take quite a while, so be on the safe side. */ + siInstaller.mTimeoutMS = 5 * 60 * 1000; + + /* The argv[0] should contain full path to the executable module */ + siInstaller.mArguments.push_back(strUpdateDir + "VBoxWindowsAdditions.exe"); + siInstaller.mArguments.push_back(Utf8Str("/S")); /* We want to install in silent mode. */ + siInstaller.mArguments.push_back(Utf8Str("/l")); /* ... and logging enabled. */ + /* Don't quit VBoxService during upgrade because it still is used for this + * piece of code we're in right now (that is, here!) ... */ + siInstaller.mArguments.push_back(Utf8Str("/no_vboxservice_exit")); + /* Tell the installer to report its current installation status + * using a running VBoxTray instance via balloon messages in the + * Windows taskbar. */ + siInstaller.mArguments.push_back(Utf8Str("/post_installstatus")); + /* Add optional installer command line arguments from the API to the + * installer's startup info. */ + vrc = addProcessArguments(siInstaller.mArguments, mArguments); + AssertRC(vrc); + /* If the caller does not want to wait for out guest update process to end, + * complete the progress object now so that the caller can do other work. */ + if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly) + siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly; + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS.EXE", + strUpdateDir + "VBoxWindowsAdditions.exe", + ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_EXECUTE, siInstaller)); + break; + } + case eOSType_Linux: + { + /* Copy over the installer to the guest but don't execute it. + * Execution will be done by the shell instead. */ + mFiles.push_back(ISOFile("VBOXLINUXADDITIONS.RUN", + strUpdateDir + "VBoxLinuxAdditions.run", ISOFILE_FLAG_COPY_FROM_ISO)); + + GuestProcessStartupInfo siInstaller; + siInstaller.mName = "VirtualBox Linux Guest Additions Installer"; + /* Set a running timeout of 5 minutes -- compiling modules and stuff for the Linux Guest Additions + * setup can take quite a while, so be on the safe side. */ + siInstaller.mTimeoutMS = 5 * 60 * 1000; + /* The argv[0] should contain full path to the shell we're using to execute the installer. */ + siInstaller.mArguments.push_back("/bin/sh"); + /* Now add the stuff we need in order to execute the installer. */ + siInstaller.mArguments.push_back(strUpdateDir + "VBoxLinuxAdditions.run"); + /* Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking xterm + * window spawned when doing any unattended Linux GA installations. */ + siInstaller.mArguments.push_back("--nox11"); + siInstaller.mArguments.push_back("--"); + /* Force the upgrade. Needed in order to skip the confirmation dialog about warning to upgrade. */ + siInstaller.mArguments.push_back("--force"); /** @todo We might want a dedicated "--silent" switch here. */ + /* If the caller does not want to wait for out guest update process to end, + * complete the progress object now so that the caller can do other work. */ + if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly) + siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly; + mFiles.push_back(ISOFile("/bin/sh" /* Source */, "/bin/sh" /* Dest */, + ISOFILE_FLAG_EXECUTE, siInstaller)); + break; + } + case eOSType_Solaris: + /** @todo Add Solaris support. */ + break; + default: + AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType)); + break; + } + } + + if (RT_SUCCESS(vrc)) + { + /* We want to spend 40% total for all copying operations. So roughly + * calculate the specific percentage step of each copied file. */ + uint8_t uOffset = 20; /* Start at 20%. */ + uint8_t uStep = 40 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10); + + LogRel(("Copying over Guest Additions update files to the guest ...\n")); + + std::vector<ISOFile>::const_iterator itFiles = mFiles.begin(); + while (itFiles != mFiles.end()) + { + if (itFiles->fFlags & ISOFILE_FLAG_COPY_FROM_ISO) + { + bool fOptional = false; + if (itFiles->fFlags & ISOFILE_FLAG_OPTIONAL) + fOptional = true; + vrc = copyFileToGuest(pSession, hVfsIso, itFiles->strSource, itFiles->strDest, fOptional); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"), + itFiles->strSource.c_str(), itFiles->strDest.c_str(), vrc)); + break; + } + } + + vrc = setProgress(uOffset); + if (RT_FAILURE(vrc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + /* Done copying, close .ISO file. */ + RTVfsRelease(hVfsIso); + + if (RT_SUCCESS(vrc)) + { + /* We want to spend 35% total for all copying operations. So roughly + * calculate the specific percentage step of each copied file. */ + uint8_t uOffset = 60; /* Start at 60%. */ + uint8_t uStep = 35 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10); + + LogRel(("Executing Guest Additions update files ...\n")); + + std::vector<ISOFile>::iterator itFiles = mFiles.begin(); + while (itFiles != mFiles.end()) + { + if (itFiles->fFlags & ISOFILE_FLAG_EXECUTE) + { + vrc = runFileOnGuest(pSession, itFiles->mProcInfo); + if (RT_FAILURE(vrc)) + break; + } + + vrc = setProgress(uOffset); + if (RT_FAILURE(vrc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + if (RT_SUCCESS(vrc)) + { + /* Linux Guest Additions will restart VBoxService during installation process. + * In this case, connection to the guest will be temporary lost until new + * kernel modules will be rebuilt, loaded and new VBoxService restarted. + * Handle this case here: check if old connection was terminated and + * new one has started. */ + if (osType == eOSType_Linux) + { + if (pSession->i_isTerminated()) + { + LogRel(("Old guest session has terminated, waiting updated guest services to start\n")); + + /* Wait for VBoxService to restart. */ + vrc = waitForGuestSession(pSession->i_getParent()); + if (RT_FAILURE(vrc)) + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Automatic update of Guest Additions has failed: " + "guest services were not restarted, please reinstall Guest Additions"))); + } + else + { + vrc = VERR_TRY_AGAIN; + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Old guest session is still active, guest services were not restarted " + "after installation, please reinstall Guest Additions"))); + } + } + + if (RT_SUCCESS(vrc)) + { + LogRel(("Automatic update of Guest Additions succeeded\n")); + hrc = setProgressSuccess(); + } + } + } + + RTVfsFileRelease(hVfsFileIso); + } + } + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_CANCELLED) + { + LogRel(("Automatic update of Guest Additions was canceled\n")); + + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Installation was canceled"))); + } + else if (vrc == VERR_TIMEOUT) + { + LogRel(("Automatic update of Guest Additions has timed out\n")); + + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Installation has timed out"))); + } + else + { + Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", vrc); + if (!mProgress.isNull()) /* Progress object is optional. */ + { +#ifdef VBOX_STRICT + /* If we forgot to set the progress object accordingly, let us know. */ + LONG rcProgress; + AssertMsg( SUCCEEDED(mProgress->COMGETTER(ResultCode(&rcProgress))) + && FAILED(rcProgress), ("Task indicated an error (%Rrc), but progress did not indicate this (%Rhrc)\n", + vrc, rcProgress)); +#endif + com::ProgressErrorInfo errorInfo(mProgress); + if ( errorInfo.isFullAvailable() + || errorInfo.isBasicAvailable()) + { + strError = errorInfo.getText(); + } + } + + LogRel(("Automatic update of Guest Additions failed: %s (%Rhrc)\n", + strError.c_str(), hrc)); + } + + LogRel(("Please install Guest Additions manually\n")); + } + + /** @todo Clean up copied / left over installation files. */ + + LogFlowFuncLeaveRC(vrc); + return vrc; +} diff --git a/src/VBox/Main/src-client/HGCM.cpp b/src/VBox/Main/src-client/HGCM.cpp new file mode 100644 index 00000000..4595f9bd --- /dev/null +++ b/src/VBox/Main/src-client/HGCM.cpp @@ -0,0 +1,3040 @@ +/* $Id: HGCM.cpp $ */ +/** @file + * HGCM (Host-Guest Communication Manager) + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_HGCM +#include "LoggingNew.h" + +#include "HGCM.h" +#include "HGCMThread.h" + +#include <VBox/err.h> +#include <VBox/hgcmsvc.h> +#include <VBox/vmm/ssm.h> +#include <VBox/vmm/stam.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/sup.h> +#include <VBox/AssertGuest.h> + +#include <iprt/alloc.h> +#include <iprt/avl.h> +#include <iprt/critsect.h> +#include <iprt/asm.h> +#include <iprt/ldr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + +#include <VBox/VMMDev.h> +#include <new> + +/** + * A service gets one thread, which synchronously delivers messages to + * the service. This is good for serialization. + * + * Some services may want to process messages asynchronously, and will want + * a next message to be delivered, while a previous message is still being + * processed. + * + * The dedicated service thread delivers a next message when service + * returns after fetching a previous one. The service will call a message + * completion callback when message is actually processed. So returning + * from the service call means only that the service is processing message. + * + * 'Message processed' condition is indicated by service, which call the + * callback, even if the callback is called synchronously in the dedicated + * thread. + * + * This message completion callback is only valid for Call requests. + * Connect and Disconnect are processed synchronously by the service. + */ + + +/* The maximum allowed size of a service name in bytes. */ +#define VBOX_HGCM_SVC_NAME_MAX_BYTES 1024 + +struct _HGCMSVCEXTHANDLEDATA +{ + char *pszServiceName; + /* The service name follows. */ +}; + +class HGCMClient; + +/** Internal helper service object. HGCM code would use it to + * hold information about services and communicate with services. + * The HGCMService is an (in future) abstract class that implements + * common functionality. There will be derived classes for specific + * service types. + */ + +class HGCMService +{ + private: + VBOXHGCMSVCHELPERS m_svcHelpers; + + static HGCMService *sm_pSvcListHead; + static HGCMService *sm_pSvcListTail; + + static int sm_cServices; + + HGCMThread *m_pThread; + friend DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser); + + uint32_t volatile m_u32RefCnt; + + HGCMService *m_pSvcNext; + HGCMService *m_pSvcPrev; + + char *m_pszSvcName; + char *m_pszSvcLibrary; + + RTLDRMOD m_hLdrMod; + PFNVBOXHGCMSVCLOAD m_pfnLoad; + + VBOXHGCMSVCFNTABLE m_fntable; + + /** Set if servicing SVC_MSG_CONNECT or SVC_MSG_DISCONNECT. + * Used for context checking pfnDisconnectClient calls, as it can only + * safely be made when the main HGCM thread is waiting on the service to + * process those messages. */ + bool m_fInConnectOrDisconnect; + + uint32_t m_acClients[HGCM_CLIENT_CATEGORY_MAX]; /**< Clients per category. */ + uint32_t m_cClients; + uint32_t m_cClientsAllocated; + + uint32_t *m_paClientIds; + + HGCMSVCEXTHANDLE m_hExtension; + + PUVM m_pUVM; + PCVMMR3VTABLE m_pVMM; + PPDMIHGCMPORT m_pHgcmPort; + + /** @name Statistics + * @{ */ + STAMPROFILE m_StatHandleMsg; + STAMCOUNTER m_StatTooManyClients; + STAMCOUNTER m_StatTooManyCalls; + /** @} */ + + int loadServiceDLL(void); + void unloadServiceDLL(void); + + /* + * Main HGCM thread methods. + */ + int instanceCreate(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort); + void registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM); + void instanceDestroy(void); + + int saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM); + int loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion); + + HGCMService(); + ~HGCMService() {}; + + static DECLCALLBACK(int) svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc); + static DECLCALLBACK(int) svcHlpDisconnectClient(void *pvInstance, uint32_t idClient); + static DECLCALLBACK(bool) svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle); + static DECLCALLBACK(bool) svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle); + static DECLCALLBACK(int) svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, + STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, + const char *pszName, va_list va); + static DECLCALLBACK(int) svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va); + static DECLCALLBACK(int) svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser); + static DECLCALLBACK(int) svcHlpInfoDeregister(void *pvInstance, const char *pszName); + static DECLCALLBACK(uint32_t) svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall); + static DECLCALLBACK(uint64_t) svcHlpGetVMMDevSessionId(void *pvInstance); + + public: + + /* + * Main HGCM thread methods. + */ + static int LoadService(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort); + void UnloadService(bool fUvmIsInvalid); + + static void UnloadAll(bool fUvmIsInvalid); + + static int ResolveService(HGCMService **ppsvc, const char *pszServiceName); + void ReferenceService(void); + void ReleaseService(void); + + static void Reset(void); + + static int SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM); + static int LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion); + + int CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring); + int DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient); + + int HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms); + static void BroadcastNotify(HGCMNOTIFYEVENT enmEvent); + void Notify(HGCMNOTIFYEVENT enmEvent); + + uint32_t SizeOfClient(void) { return m_fntable.cbClient; }; + + int RegisterExtension(HGCMSVCEXTHANDLE handle, PFNHGCMSVCEXT pfnExtension, void *pvExtension); + void UnregisterExtension(HGCMSVCEXTHANDLE handle); + + /* + * The service thread methods. + */ + + int GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], uint64_t tsArrival); + void GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient); +}; + + +class HGCMClient: public HGCMObject +{ + public: + HGCMClient(uint32_t a_fRequestor, uint32_t a_idxCategory) + : HGCMObject(HGCMOBJ_CLIENT) + , pService(NULL) + , pvData(NULL) + , fRequestor(a_fRequestor) + , idxCategory(a_idxCategory) + , cPendingCalls(0) + , m_fGuestAccessible(false) + { + Assert(idxCategory < HGCM_CLIENT_CATEGORY_MAX); + } + ~HGCMClient(); + + int Init(HGCMService *pSvc); + + /** Lookups a client object by its handle. */ + static HGCMClient *ReferenceByHandle(uint32_t idClient) + { + return (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT); + } + + /** Lookups a client object by its handle and makes sure that it's accessible to the guest. */ + static HGCMClient *ReferenceByHandleForGuest(uint32_t idClient) + { + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT); + if (pClient) + { + if (RT_LIKELY(pClient->m_fGuestAccessible)) + return pClient; + pClient->Dereference(); + } + return NULL; + } + + /** Make the client object accessible to the guest. */ + void makeAccessibleToGuest() + { + ASMAtomicWriteBool(&m_fGuestAccessible, true); + } + + /** Service that the client is connected to. */ + HGCMService *pService; + + /** Client specific data. */ + void *pvData; + + /** The requestor flags this client was created with. + * @sa VMMDevRequestHeader::fRequestor */ + uint32_t fRequestor; + + /** The client category (HGCM_CLIENT_CATEGORY_XXX). */ + uint32_t idxCategory; + + /** Number of pending calls. */ + uint32_t volatile cPendingCalls; + + protected: + /** Set if the client is accessible to the guest, clear if not. */ + bool volatile m_fGuestAccessible; + + private: /* none of this: */ + HGCMClient(); + HGCMClient(HGCMClient const &); + HGCMClient &operator=(HGCMClient const &); +}; + +HGCMClient::~HGCMClient() +{ + if (pService->SizeOfClient() > 0) + { + RTMemFree(pvData); + pvData = NULL; + } +} + + +int HGCMClient::Init(HGCMService *pSvc) +{ + pService = pSvc; + + if (pService->SizeOfClient() > 0) + { + pvData = RTMemAllocZ(pService->SizeOfClient()); + + if (!pvData) + { + return VERR_NO_MEMORY; + } + } + + return VINF_SUCCESS; +} + + +#define HGCM_CLIENT_DATA(pService, pClient)(pClient->pvData) + + + +HGCMService *HGCMService::sm_pSvcListHead = NULL; +HGCMService *HGCMService::sm_pSvcListTail = NULL; +int HGCMService::sm_cServices = 0; + +HGCMService::HGCMService() + : + m_pThread (NULL), + m_u32RefCnt (0), + m_pSvcNext (NULL), + m_pSvcPrev (NULL), + m_pszSvcName (NULL), + m_pszSvcLibrary (NULL), + m_hLdrMod (NIL_RTLDRMOD), + m_pfnLoad (NULL), + m_fInConnectOrDisconnect(false), + m_cClients (0), + m_cClientsAllocated (0), + m_paClientIds (NULL), + m_hExtension (NULL), + m_pUVM (NULL), + m_pVMM (NULL), + m_pHgcmPort (NULL) +{ + RT_ZERO(m_acClients); + RT_ZERO(m_fntable); +} + + +static bool g_fResetting = false; +static bool g_fSaveState = false; + + +/** Helper function to load a local service DLL. + * + * @return VBox code + */ +int HGCMService::loadServiceDLL(void) +{ + LogFlowFunc(("m_pszSvcLibrary = %s\n", m_pszSvcLibrary)); + + if (m_pszSvcLibrary == NULL) + { + return VERR_INVALID_PARAMETER; + } + + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + int vrc; + + if (RTPathHasPath(m_pszSvcLibrary)) + vrc = SUPR3HardenedLdrLoadPlugIn(m_pszSvcLibrary, &m_hLdrMod, &ErrInfo.Core); + else + vrc = SUPR3HardenedLdrLoadAppPriv(m_pszSvcLibrary, &m_hLdrMod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core); + + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("successfully loaded the library.\n")); + + m_pfnLoad = NULL; + + vrc = RTLdrGetSymbol(m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad); + + if (RT_FAILURE(vrc) || !m_pfnLoad) + { + Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, vrc = %Rrc, m_pfnLoad = %p\n", + VBOX_HGCM_SVCLOAD_NAME, vrc, m_pfnLoad)); + + if (RT_SUCCESS(vrc)) + { + /* m_pfnLoad was NULL */ + vrc = VERR_SYMBOL_NOT_FOUND; + } + } + + if (RT_SUCCESS(vrc)) + { + RT_ZERO(m_fntable); + + m_fntable.cbSize = sizeof(m_fntable); + m_fntable.u32Version = VBOX_HGCM_SVC_VERSION; + m_fntable.pHelpers = &m_svcHelpers; + + /* Total max calls: (2048 + 1024 + 1024) * 8192 = 33 554 432 */ + m_fntable.idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL; + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = _2K; + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT] = _1K; + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER] = _1K; + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] = _8K; + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] = _4K; + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER] = _2K; + /** @todo provide way to configure different values via extra data. */ + + vrc = m_pfnLoad(&m_fntable); + + LogFlowFunc(("m_pfnLoad vrc = %Rrc\n", vrc)); + + if (RT_SUCCESS(vrc)) + { + if ( m_fntable.pfnUnload != NULL + && m_fntable.pfnConnect != NULL + && m_fntable.pfnDisconnect != NULL + && m_fntable.pfnCall != NULL + ) + { + Assert(m_fntable.idxLegacyClientCategory < RT_ELEMENTS(m_fntable.acMaxClients)); + LogRel2(("HGCMService::loadServiceDLL: acMaxClients={%u,%u,%u} acMaxCallsPerClient={%u,%u,%u} => %RU64 calls; idxLegacyClientCategory=%d; %s\n", + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL], + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT], + m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER], + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL], + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT], + m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER], + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] + * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] + + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT] + * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] + + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER] + * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER], + m_fntable.idxLegacyClientCategory, m_pszSvcName)); + } + else + { + Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n")); + + vrc = VERR_INVALID_PARAMETER; + + if (m_fntable.pfnUnload) + { + m_fntable.pfnUnload(m_fntable.pvService); + } + } + } + } + } + else + { + LogRel(("HGCM: Failed to load the service library: [%s], vrc = %Rrc - %s. The service will be not available.\n", + m_pszSvcLibrary, vrc, ErrInfo.Core.pszMsg)); + m_hLdrMod = NIL_RTLDRMOD; + } + + if (RT_FAILURE(vrc)) + { + unloadServiceDLL(); + } + + return vrc; +} + +/** Helper function to free a local service DLL. + * + * @return VBox code + */ +void HGCMService::unloadServiceDLL(void) +{ + if (m_hLdrMod) + { + RTLdrClose(m_hLdrMod); + } + + RT_ZERO(m_fntable); + m_pfnLoad = NULL; + m_hLdrMod = NIL_RTLDRMOD; +} + +/* + * Messages processed by service threads. These threads only call the service entry points. + */ + +#define SVC_MSG_LOAD (0) /**< Load the service library and call VBOX_HGCM_SVCLOAD_NAME entry point. */ +#define SVC_MSG_UNLOAD (1) /**< call pfnUnload and unload the service library. */ +#define SVC_MSG_CONNECT (2) /**< pfnConnect */ +#define SVC_MSG_DISCONNECT (3) /**< pfnDisconnect */ +#define SVC_MSG_GUESTCALL (4) /**< pfnGuestCall */ +#define SVC_MSG_HOSTCALL (5) /**< pfnHostCall */ +#define SVC_MSG_LOADSTATE (6) /**< pfnLoadState. */ +#define SVC_MSG_SAVESTATE (7) /**< pfnSaveState. */ +#define SVC_MSG_QUIT (8) /**< Terminate the thread. */ +#define SVC_MSG_REGEXT (9) /**< pfnRegisterExtension */ +#define SVC_MSG_UNREGEXT (10) /**< pfnRegisterExtension */ +#define SVC_MSG_NOTIFY (11) /**< pfnNotify */ +#define SVC_MSG_GUESTCANCELLED (12) /**< pfnCancelled */ + +class HGCMMsgSvcLoad: public HGCMMsgCore +{ + public: + HGCMMsgSvcLoad() : HGCMMsgCore(), pUVM() {} + + /** The user mode VM handle (for statistics and such). */ + PUVM pUVM; +}; + +class HGCMMsgSvcUnload: public HGCMMsgCore +{ +}; + +class HGCMMsgSvcConnect: public HGCMMsgCore +{ + public: + /** client identifier */ + uint32_t u32ClientId; + /** Requestor flags. */ + uint32_t fRequestor; + /** Set if restoring. */ + bool fRestoring; +}; + +class HGCMMsgSvcDisconnect: public HGCMMsgCore +{ + public: + /** client identifier */ + uint32_t u32ClientId; + /** The client instance. */ + HGCMClient *pClient; +}; + +class HGCMMsgHeader: public HGCMMsgCore +{ + public: + HGCMMsgHeader() : pCmd(NULL), pHGCMPort(NULL) {}; + + /* Command pointer/identifier. */ + PVBOXHGCMCMD pCmd; + + /* Port to be informed on message completion. */ + PPDMIHGCMPORT pHGCMPort; +}; + +class HGCMMsgCall: public HGCMMsgHeader +{ + public: + HGCMMsgCall() : pcCounter(NULL) + { } + + HGCMMsgCall(HGCMThread *pThread) + : pcCounter(NULL) + { + InitializeCore(SVC_MSG_GUESTCALL, pThread); + Initialize(); + } + ~HGCMMsgCall() + { + Log(("~HGCMMsgCall %p\n", this)); + Assert(!pcCounter); + } + + /** Points to HGCMClient::cPendingCalls if it needs to be decremented. */ + uint32_t volatile *pcCounter; + + /* client identifier */ + uint32_t u32ClientId; + + /* function number */ + uint32_t u32Function; + + /* number of parameters */ + uint32_t cParms; + + VBOXHGCMSVCPARM *paParms; + + /** The STAM_GET_TS() value when the request arrived. */ + uint64_t tsArrival; +}; + +class HGCMMsgCancelled: public HGCMMsgHeader +{ + public: + HGCMMsgCancelled() {} + + HGCMMsgCancelled(HGCMThread *pThread) + { + InitializeCore(SVC_MSG_GUESTCANCELLED, pThread); + Initialize(); + } + ~HGCMMsgCancelled() { Log(("~HGCMMsgCancelled %p\n", this)); } + + /** The client identifier. */ + uint32_t idClient; +}; + +class HGCMMsgLoadSaveStateClient: public HGCMMsgCore +{ + public: + PSSMHANDLE pSSM; + PCVMMR3VTABLE pVMM; + uint32_t uVersion; + uint32_t u32ClientId; +}; + +class HGCMMsgHostCallSvc: public HGCMMsgCore +{ + public: + /* function number */ + uint32_t u32Function; + + /* number of parameters */ + uint32_t cParms; + + VBOXHGCMSVCPARM *paParms; +}; + +class HGCMMsgSvcRegisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the extension to be registered. */ + HGCMSVCEXTHANDLE handle; + /* The extension entry point. */ + PFNHGCMSVCEXT pfnExtension; + /* The extension pointer. */ + void *pvExtension; +}; + +class HGCMMsgSvcUnregisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the registered extension. */ + HGCMSVCEXTHANDLE handle; +}; + +class HGCMMsgNotify: public HGCMMsgCore +{ + public: + /** The event. */ + HGCMNOTIFYEVENT enmEvent; +}; + +static HGCMMsgCore *hgcmMessageAllocSvc(uint32_t u32MsgId) +{ + switch (u32MsgId) + { + case SVC_MSG_LOAD: return new HGCMMsgSvcLoad(); + case SVC_MSG_UNLOAD: return new HGCMMsgSvcUnload(); + case SVC_MSG_CONNECT: return new HGCMMsgSvcConnect(); + case SVC_MSG_DISCONNECT: return new HGCMMsgSvcDisconnect(); + case SVC_MSG_HOSTCALL: return new HGCMMsgHostCallSvc(); + case SVC_MSG_GUESTCALL: return new HGCMMsgCall(); + case SVC_MSG_LOADSTATE: + case SVC_MSG_SAVESTATE: return new HGCMMsgLoadSaveStateClient(); + case SVC_MSG_REGEXT: return new HGCMMsgSvcRegisterExtension(); + case SVC_MSG_UNREGEXT: return new HGCMMsgSvcUnregisterExtension(); + case SVC_MSG_NOTIFY: return new HGCMMsgNotify(); + case SVC_MSG_GUESTCANCELLED: return new HGCMMsgCancelled(); + default: + AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId)); + } + + return NULL; +} + +/* + * The service thread. Loads the service library and calls the service entry points. + */ +DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser) +{ + HGCMService *pSvc = (HGCMService *)pvUser; + AssertRelease(pSvc != NULL); + + bool fQuit = false; + + while (!fQuit) + { + HGCMMsgCore *pMsgCore; + int vrc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(vrc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", vrc)); + break; + } + + STAM_REL_PROFILE_START(&pSvc->m_StatHandleMsg, a); + + /* Cache required information to avoid unnecessary pMsgCore access. */ + uint32_t u32MsgId = pMsgCore->MsgId(); + + switch (u32MsgId) + { + case SVC_MSG_LOAD: + { + LogFlowFunc(("SVC_MSG_LOAD\n")); + vrc = pSvc->loadServiceDLL(); + } break; + + case SVC_MSG_UNLOAD: + { + LogFlowFunc(("SVC_MSG_UNLOAD\n")); + if (pSvc->m_fntable.pfnUnload) + { + pSvc->m_fntable.pfnUnload(pSvc->m_fntable.pvService); + } + + pSvc->unloadServiceDLL(); + fQuit = true; + } break; + + case SVC_MSG_CONNECT: + { + HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pMsgCore; + + LogFlowFunc(("SVC_MSG_CONNECT u32ClientId = %d\n", pMsg->u32ClientId)); + + HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId); + + if (pClient) + { + pSvc->m_fInConnectOrDisconnect = true; + vrc = pSvc->m_fntable.pfnConnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), + pMsg->fRequestor, pMsg->fRestoring); + pSvc->m_fInConnectOrDisconnect = false; + + hgcmObjDereference(pClient); + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_DISCONNECT: + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore; + + LogFlowFunc(("SVC_MSG_DISCONNECT u32ClientId = %d, pClient = %p\n", pMsg->u32ClientId, pMsg->pClient)); + + if (pMsg->pClient) + { + pSvc->m_fInConnectOrDisconnect = true; + vrc = pSvc->m_fntable.pfnDisconnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pMsg->pClient)); + pSvc->m_fInConnectOrDisconnect = false; + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_GUESTCALL: + { + HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore; + + LogFlowFunc(("SVC_MSG_GUESTCALL u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n", + pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->u32ClientId); + + if (pClient) + { + pSvc->m_fntable.pfnCall(pSvc->m_fntable.pvService, (VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function, + pMsg->cParms, pMsg->paParms, pMsg->tsArrival); + + hgcmObjDereference(pClient); + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_GUESTCANCELLED: + { + HGCMMsgCancelled *pMsg = (HGCMMsgCancelled *)pMsgCore; + + LogFlowFunc(("SVC_MSG_GUESTCANCELLED idClient = %d\n", pMsg->idClient)); + + HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->idClient); + + if (pClient) + { + pSvc->m_fntable.pfnCancelled(pSvc->m_fntable.pvService, pMsg->idClient, HGCM_CLIENT_DATA(pSvc, pClient)); + + hgcmObjDereference(pClient); + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_HOSTCALL: + { + HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pMsgCore; + + LogFlowFunc(("SVC_MSG_HOSTCALL u32Function = %d, cParms = %d, paParms = %p\n", + pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + vrc = pSvc->m_fntable.pfnHostCall(pSvc->m_fntable.pvService, pMsg->u32Function, pMsg->cParms, pMsg->paParms); + } break; + + case SVC_MSG_LOADSTATE: + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore; + + LogFlowFunc(("SVC_MSG_LOADSTATE\n")); + + HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId); + + if (pClient) + { + /* fRequestor: Restored by the message sender already. */ + bool fHaveClientState = pSvc->m_fntable.pfnLoadState != NULL; + if (pMsg->uVersion > HGCM_SAVED_STATE_VERSION_V2) + vrc = pMsg->pVMM->pfnSSMR3GetBool(pMsg->pSSM, &fHaveClientState); + else + vrc = VINF_SUCCESS; + if (RT_SUCCESS(vrc) ) + { + if (pSvc->m_fntable.pfnLoadState) + vrc = pSvc->m_fntable.pfnLoadState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM, + fHaveClientState ? pMsg->uVersion : 0); + else + AssertLogRelStmt(!fHaveClientState, vrc = VERR_INTERNAL_ERROR_5); + } + hgcmObjDereference(pClient); + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_SAVESTATE: + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore; + + LogFlowFunc(("SVC_MSG_SAVESTATE\n")); + + HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId); + + vrc = VINF_SUCCESS; + + if (pClient) + { + pMsg->pVMM->pfnSSMR3PutU32(pMsg->pSSM, pClient->fRequestor); /* Quicker to save this here than in the message sender. */ + vrc = pMsg->pVMM->pfnSSMR3PutBool(pMsg->pSSM, pSvc->m_fntable.pfnSaveState != NULL); + if (RT_SUCCESS(vrc) && pSvc->m_fntable.pfnSaveState) + { + g_fSaveState = true; + vrc = pSvc->m_fntable.pfnSaveState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM); + g_fSaveState = false; + } + + hgcmObjDereference(pClient); + } + else + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_REGEXT: + { + HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pMsgCore; + + LogFlowFunc(("SVC_MSG_REGEXT handle = %p, pfn = %p\n", pMsg->handle, pMsg->pfnExtension)); + + if (pSvc->m_hExtension) + { + vrc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, pMsg->pfnExtension, + pMsg->pvExtension); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + if (RT_SUCCESS(vrc)) + { + pSvc->m_hExtension = pMsg->handle; + } + } + } break; + + case SVC_MSG_UNREGEXT: + { + HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pMsgCore; + + LogFlowFunc(("SVC_MSG_UNREGEXT handle = %p\n", pMsg->handle)); + + if (pSvc->m_hExtension != pMsg->handle) + { + vrc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, NULL, NULL); + } + else + { + vrc = VERR_NOT_SUPPORTED; + } + + pSvc->m_hExtension = NULL; + } + } break; + + case SVC_MSG_NOTIFY: + { + HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pMsgCore; + + LogFlowFunc(("SVC_MSG_NOTIFY enmEvent = %d\n", pMsg->enmEvent)); + + pSvc->m_fntable.pfnNotify(pSvc->m_fntable.pvService, pMsg->enmEvent); + } break; + + default: + { + AssertMsgFailed(("hgcmServiceThread::Unsupported message number %08X\n", u32MsgId)); + vrc = VERR_NOT_SUPPORTED; + } break; + } + + if (u32MsgId != SVC_MSG_GUESTCALL) + { + /* For SVC_MSG_GUESTCALL the service calls the completion helper. + * Other messages have to be completed here. + */ + hgcmMsgComplete (pMsgCore, vrc); + } + STAM_REL_PROFILE_STOP(&pSvc->m_StatHandleMsg, a); + } +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnCallComplete} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + HGCMMsgCore *pMsgCore = (HGCMMsgCore *)callHandle; + + /* Only call the completion for these messages. The helper + * is called by the service, and the service does not get + * any other messages. + */ + AssertMsgReturn(pMsgCore->MsgId() == SVC_MSG_GUESTCALL, ("%d\n", pMsgCore->MsgId()), VERR_WRONG_TYPE); + return hgcmMsgComplete(pMsgCore, rc); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnDisconnectClient} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpDisconnectClient(void *pvInstance, uint32_t idClient) +{ + HGCMService *pService = static_cast <HGCMService *> (pvInstance); + AssertReturn(pService, VERR_INVALID_HANDLE); + + /* Only safe to call when the main HGCM thread is waiting on the service + to handle a SVC_MSG_CONNECT or SVC_MSG_DISCONNECT message. Otherwise + we'd risk racing it and corrupt data structures. */ + AssertReturn(pService->m_fInConnectOrDisconnect, VERR_INVALID_CONTEXT); + + /* Resolve the client ID and verify that it belongs to this service before + trying to disconnect it. */ + int vrc = VERR_NOT_FOUND; + HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient); + if (pClient) + { + if (pClient->pService == pService) + vrc = pService->DisconnectClient(idClient, true, pClient); + hgcmObjDereference(pClient); + } + return vrc; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallRestored} + */ +/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle; + AssertPtrReturn(pMsgHdr, false); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, false); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, false); + + return pHgcmPort->pfnIsCmdRestored(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallCancelled} + */ +/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle; + AssertPtrReturn(pMsgHdr, false); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, false); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, false); + + return pHgcmPort->pfnIsCmdCancelled(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamRegisterV} + */ +/* static */ DECLCALLBACK(int) +HGCMService::svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, + STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + if (pService->m_pUVM) + return pService->m_pVMM->pfnSTAMR3RegisterVU(pService->m_pUVM, pvSample, enmType, enmVisibility, + enmUnit, pszDesc, pszName, va); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamDeregisterV} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + if (pService->m_pUVM) + return pService->m_pVMM->pfnSTAMR3DeregisterV(pService->m_pUVM, pszPatFmt, va); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoRegister} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + if (pService->m_pUVM) + return pService->m_pVMM->pfnDBGFR3InfoRegisterExternal(pService->m_pUVM, pszName, pszDesc, pfnHandler, pvUser); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoDeregister} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoDeregister(void *pvInstance, const char *pszName) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + if (pService->m_pUVM) + return pService->m_pVMM->pfnDBGFR3InfoDeregisterExternal(pService->m_pUVM, pszName); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetRequestor} + */ +/* static */ DECLCALLBACK(uint32_t) HGCMService::svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)(hCall); + AssertPtrReturn(pMsgHdr, VMMDEV_REQUESTOR_LOWEST); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, VMMDEV_REQUESTOR_LOWEST); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, VMMDEV_REQUESTOR_LOWEST); + + return pHgcmPort->pfnGetRequestor(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetVMMDevSessionId} + */ +/* static */ DECLCALLBACK(uint64_t) HGCMService::svcHlpGetVMMDevSessionId(void *pvInstance) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, UINT64_MAX); + + PPDMIHGCMPORT pHgcmPort = pService->m_pHgcmPort; + AssertPtrReturn(pHgcmPort, UINT64_MAX); + + return pHgcmPort->pfnGetVMMDevSessionId(pHgcmPort); +} + + +static DECLCALLBACK(int) hgcmMsgCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore) +{ + /* Call the VMMDev port interface to issue IRQ notification. */ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)pMsgCore; + + LogFlow(("MAIN::hgcmMsgCompletionCallback: message %p\n", pMsgCore)); + + if (pMsgHdr->pHGCMPort) + { + if (!g_fResetting) + return pMsgHdr->pHGCMPort->pfnCompleted(pMsgHdr->pHGCMPort, + g_fSaveState ? VINF_HGCM_SAVE_STATE : result, pMsgHdr->pCmd); + return VERR_ALREADY_RESET; /* best I could find. */ + } + return VERR_NOT_AVAILABLE; +} + +/* + * The main HGCM methods of the service. + */ + +int HGCMService::instanceCreate(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("name %s, lib %s\n", pszServiceName, pszServiceLibrary)); + + /* The maximum length of the thread name, allowed by the RT is 15. */ + char szThreadName[16]; + if (!strncmp(pszServiceName, RT_STR_TUPLE("VBoxShared"))) + RTStrPrintf(szThreadName, sizeof(szThreadName), "Sh%s", pszServiceName + 10); + else if (!strncmp(pszServiceName, RT_STR_TUPLE("VBox"))) + RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName + 4); + else + RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName); + + int vrc = hgcmThreadCreate(&m_pThread, szThreadName, hgcmServiceThread, this, pszServiceName, pUVM, pVMM); + if (RT_SUCCESS(vrc)) + { + m_pszSvcName = RTStrDup(pszServiceName); + m_pszSvcLibrary = RTStrDup(pszServiceLibrary); + + if (!m_pszSvcName || !m_pszSvcLibrary) + { + RTStrFree(m_pszSvcLibrary); + m_pszSvcLibrary = NULL; + + RTStrFree(m_pszSvcName); + m_pszSvcName = NULL; + + vrc = VERR_NO_MEMORY; + } + else + { + m_pUVM = pUVM; + m_pVMM = pVMM; + m_pHgcmPort = pHgcmPort; + + registerStatistics(pszServiceName, pUVM, pVMM); + + /* Initialize service helpers table. */ + m_svcHelpers.pfnCallComplete = svcHlpCallComplete; + m_svcHelpers.pvInstance = this; + m_svcHelpers.pfnDisconnectClient = svcHlpDisconnectClient; + m_svcHelpers.pfnIsCallRestored = svcHlpIsCallRestored; + m_svcHelpers.pfnIsCallCancelled = svcHlpIsCallCancelled; + m_svcHelpers.pfnStamRegisterV = svcHlpStamRegisterV; + m_svcHelpers.pfnStamDeregisterV = svcHlpStamDeregisterV; + m_svcHelpers.pfnInfoRegister = svcHlpInfoRegister; + m_svcHelpers.pfnInfoDeregister = svcHlpInfoDeregister; + m_svcHelpers.pfnGetRequestor = svcHlpGetRequestor; + m_svcHelpers.pfnGetVMMDevSessionId = svcHlpGetVMMDevSessionId; + + /* Execute the load request on the service thread. */ + HGCMMsgCore *pCoreMsg; + vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgSvcLoad *pMsg = (HGCMMsgSvcLoad *)pCoreMsg; + + pMsg->pUVM = pUVM; + + vrc = hgcmMsgSend(pMsg); + } + } + } + + if (RT_FAILURE(vrc)) + { + instanceDestroy(); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Called by HGCMService::instanceCreate to register statistics. */ +void HGCMService::registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatHandleMsg, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_OCCURENCE, + "Message handling", "/HGCM/%s/Msg", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyCalls, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + "Too many calls (per client)", "/HGCM/%s/TooManyCalls", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyClients, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + "Too many clients", "/HGCM/%s/TooManyClients", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_cClients, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + "Number of clients", "/HGCM/%s/Clients", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Number of kernel clients", "/HGCM/%s/Clients/Kernel", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Number of root/admin clients", "/HGCM/%s/Clients/Root", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Number of regular user clients", "/HGCM/%s/Clients/User", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of kernel clients", "/HGCM/%s/Clients/KernelMax", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of root clients", "/HGCM/%s/Clients/RootMax", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of user clients", "/HGCM/%s/Clients/UserMax", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.idxLegacyClientCategory, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Legacy client mapping", "/HGCM/%s/Clients/LegacyClientMapping", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of call per kernel client", "/HGCM/%s/MaxCallsKernelClient", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of call per root client", "/HGCM/%s/MaxCallsRootClient", pszServiceName); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS, + STAMUNIT_OCCURENCES, "Max number of call per user client", "/HGCM/%s/MaxCallsUserClient", pszServiceName); +} + +void HGCMService::instanceDestroy(void) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pMsg, SVC_MSG_UNLOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + vrc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(vrc)) + hgcmThreadWait(m_pThread); + } + + if (m_pszSvcName && m_pUVM) + m_pVMM->pfnSTAMR3DeregisterF(m_pUVM, "/HGCM/%s/*", m_pszSvcName); + m_pUVM = NULL; + m_pHgcmPort = NULL; + + RTStrFree(m_pszSvcLibrary); + m_pszSvcLibrary = NULL; + + RTStrFree(m_pszSvcName); + m_pszSvcName = NULL; + + if (m_paClientIds) + { + RTMemFree(m_paClientIds); + m_paClientIds = NULL; + } +} + +int HGCMService::saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_SAVESTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + pMsg->pSSM = pSSM; + pMsg->pVMM = pVMM; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int HGCMService::loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOADSTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->pSSM = pSSM; + pMsg->pVMM = pVMM; + pMsg->uVersion = uVersion; + pMsg->u32ClientId = u32ClientId; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + + +/** The method creates a service and references it. + * + * @param pszServiceLibrary The library to be loaded. + * @param pszServiceName The name of the service. + * @param pUVM The user mode VM handle (for statistics and such). + * @param pVMM The VMM vtable (for statistics and such). + * @param pHgcmPort The VMMDev HGCM port interface. + * + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadService(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("lib %s, name = %s, pUVM = %p\n", pszServiceLibrary, pszServiceName, pUVM)); + + /* Look at already loaded services to avoid double loading. */ + + HGCMService *pSvc; + int vrc = HGCMService::ResolveService(&pSvc, pszServiceName); + + if (RT_SUCCESS(vrc)) + { + /* The service is already loaded. */ + pSvc->ReleaseService(); + vrc = VERR_HGCM_SERVICE_EXISTS; + } + else + { + /* Create the new service. */ + pSvc = new (std::nothrow) HGCMService(); + + if (!pSvc) + { + vrc = VERR_NO_MEMORY; + } + else + { + /* Load the library and call the initialization entry point. */ + vrc = pSvc->instanceCreate(pszServiceLibrary, pszServiceName, pUVM, pVMM, pHgcmPort); + if (RT_SUCCESS(vrc)) + { + /* Insert the just created service to list for future references. */ + pSvc->m_pSvcNext = sm_pSvcListHead; + pSvc->m_pSvcPrev = NULL; + + if (sm_pSvcListHead) + sm_pSvcListHead->m_pSvcPrev = pSvc; + else + sm_pSvcListTail = pSvc; + + sm_pSvcListHead = pSvc; + + sm_cServices++; + + /* Reference the service (for first time) until it is unloaded on HGCM termination. */ + AssertRelease(pSvc->m_u32RefCnt == 0); + pSvc->ReferenceService(); + + LogFlowFunc(("service %p\n", pSvc)); + } + } + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** The method unloads a service. + * + * @thread main HGCM + */ +void HGCMService::UnloadService(bool fUvmIsInvalid) +{ + LogFlowFunc(("name = %s\n", m_pszSvcName)); + + if (fUvmIsInvalid) + { + m_pUVM = NULL; + m_pHgcmPort = NULL; + } + + /* Remove the service from the list. */ + if (m_pSvcNext) + { + m_pSvcNext->m_pSvcPrev = m_pSvcPrev; + } + else + { + sm_pSvcListTail = m_pSvcPrev; + } + + if (m_pSvcPrev) + { + m_pSvcPrev->m_pSvcNext = m_pSvcNext; + } + else + { + sm_pSvcListHead = m_pSvcNext; + } + + sm_cServices--; + + /* The service must be unloaded only if all clients were disconnected. */ + LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt)); + AssertRelease(m_u32RefCnt == 1); + + /* Now the service can be released. */ + ReleaseService(); +} + +/** The method unloads all services. + * + * @thread main HGCM + */ +/* static */ void HGCMService::UnloadAll(bool fUvmIsInvalid) +{ + while (sm_pSvcListHead) + { + sm_pSvcListHead->UnloadService(fUvmIsInvalid); + } +} + +/** The method obtains a referenced pointer to the service with + * specified name. The caller must call ReleaseService when + * the pointer is no longer needed. + * + * @param ppSvc Where to store the pointer to the service. + * @param pszServiceName The name of the service. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::ResolveService(HGCMService **ppSvc, const char *pszServiceName) +{ + LogFlowFunc(("ppSvc = %p name = %s\n", + ppSvc, pszServiceName)); + + if (!ppSvc || !pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + if (strcmp(pSvc->m_pszSvcName, pszServiceName) == 0) + { + break; + } + + pSvc = pSvc->m_pSvcNext; + } + + LogFlowFunc(("lookup in the list is %p\n", pSvc)); + + if (pSvc == NULL) + { + *ppSvc = NULL; + return VERR_HGCM_SERVICE_NOT_FOUND; + } + + pSvc->ReferenceService(); + + *ppSvc = pSvc; + + return VINF_SUCCESS; +} + +/** The method increases reference counter. + * + * @thread main HGCM + */ +void HGCMService::ReferenceService(void) +{ + ASMAtomicIncU32(&m_u32RefCnt); + LogFlowFunc(("[%s] m_u32RefCnt = %d\n", m_pszSvcName, m_u32RefCnt)); +} + +/** The method dereferences a service and deletes it when no more refs. + * + * @thread main HGCM + */ +void HGCMService::ReleaseService(void) +{ + LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt)); + uint32_t u32RefCnt = ASMAtomicDecU32(&m_u32RefCnt); + AssertRelease(u32RefCnt != ~0U); + + LogFlowFunc(("u32RefCnt = %d, name %s\n", u32RefCnt, m_pszSvcName)); + + if (u32RefCnt == 0) + { + instanceDestroy(); + delete this; + } +} + +/** The method is called when the VM is being reset or terminated + * and disconnects all clients from all services. + * + * @thread main HGCM + */ +/* static */ void HGCMService::Reset(void) +{ + g_fResetting = true; + + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + while (pSvc->m_cClients && pSvc->m_paClientIds) + { + uint32_t const idClient = pSvc->m_paClientIds[0]; + HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient); + Assert(pClient); + LogFlowFunc(("handle %d/%p\n", pSvc->m_paClientIds[0], pClient)); + + pSvc->DisconnectClient(pSvc->m_paClientIds[0], false, pClient); + + hgcmObjDereference(pClient); + } + + pSvc = pSvc->m_pSvcNext; + } + + g_fResetting = false; +} + +/** The method saves the HGCM state. + * + * @param pSSM The saved state context. + * @param pVMM The VMM vtable. + * @return VBox status code. + * @thread main HGCM + */ +/* static */ int HGCMService::SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + /* Save the current handle count and restore afterwards to avoid client id conflicts. */ + int vrc = pVMM->pfnSSMR3PutU32(pSSM, hgcmObjQueryHandleCount()); + AssertRCReturn(vrc, vrc); + + LogFlowFunc(("%d services to be saved:\n", sm_cServices)); + + /* Save number of services. */ + vrc = pVMM->pfnSSMR3PutU32(pSSM, sm_cServices); + AssertRCReturn(vrc, vrc); + + /* Save every service. */ + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + LogFlowFunc(("Saving service [%s]\n", pSvc->m_pszSvcName)); + + /* Save the length of the service name. */ + vrc = pVMM->pfnSSMR3PutU32(pSSM, (uint32_t) strlen(pSvc->m_pszSvcName) + 1); + AssertRCReturn(vrc, vrc); + + /* Save the name of the service. */ + vrc = pVMM->pfnSSMR3PutStrZ(pSSM, pSvc->m_pszSvcName); + AssertRCReturn(vrc, vrc); + + /* Save the number of clients. */ + vrc = pVMM->pfnSSMR3PutU32(pSSM, pSvc->m_cClients); + AssertRCReturn(vrc, vrc); + + /* Call the service for every client. Normally a service must not have + * a global state to be saved: only per client info is relevant. + * The global state of a service is configured during VM startup. + */ + uint32_t i; + + for (i = 0; i < pSvc->m_cClients; i++) + { + uint32_t u32ClientId = pSvc->m_paClientIds[i]; + + Log(("client id 0x%08X\n", u32ClientId)); + + /* Save the client id. (fRequestor is saved via SVC_MSG_SAVESTATE for convenience.) */ + vrc = pVMM->pfnSSMR3PutU32(pSSM, u32ClientId); + AssertRCReturn(vrc, vrc); + + /* Call the service, so the operation is executed by the service thread. */ + vrc = pSvc->saveClientState(u32ClientId, pSSM, pVMM); + AssertRCReturn(vrc, vrc); + } + + pSvc = pSvc->m_pSvcNext; + } + + return VINF_SUCCESS; +} + +/** The method loads saved HGCM state. + * + * @param pSSM The saved state handle. + * @param pVMM The VMM vtable. + * @param uVersion The state version being loaded. + * @return VBox status code. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + /* Restore handle count to avoid client id conflicts. */ + uint32_t u32; + + int vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32); + AssertRCReturn(vrc, vrc); + + hgcmObjSetHandleCount(u32); + + /* Get the number of services. */ + uint32_t cServices; + + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cServices); + AssertRCReturn(vrc, vrc); + + LogFlowFunc(("%d services to be restored:\n", cServices)); + + while (cServices--) + { + /* Get the length of the service name. */ + vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32); + AssertRCReturn(vrc, vrc); + AssertReturn(u32 <= VBOX_HGCM_SVC_NAME_MAX_BYTES, VERR_SSM_UNEXPECTED_DATA); + + /* Get the service name. */ + char szServiceName[VBOX_HGCM_SVC_NAME_MAX_BYTES]; + vrc = pVMM->pfnSSMR3GetStrZ(pSSM, szServiceName, u32); + AssertRCReturn(vrc, vrc); + + LogRel(("HGCM: Restoring [%s]\n", szServiceName)); + + /* Resolve the service instance. */ + HGCMService *pSvc; + vrc = ResolveService(&pSvc, szServiceName); + AssertLogRelMsgReturn(pSvc, ("vrc=%Rrc, %s\n", vrc, szServiceName), VERR_SSM_UNEXPECTED_DATA); + + /* Get the number of clients. */ + uint32_t cClients; + vrc = pVMM->pfnSSMR3GetU32(pSSM, &cClients); + if (RT_FAILURE(vrc)) + { + pSvc->ReleaseService(); + AssertFailed(); + return vrc; + } + + while (cClients--) + { + /* Get the client ID and fRequest (convieniently save via SVC_MSG_SAVESTATE + but restored here in time for calling CreateAndConnectClient). */ + uint32_t u32ClientId; + vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32ClientId); + uint32_t fRequestor = VMMDEV_REQUESTOR_LEGACY; + if (RT_SUCCESS(vrc) && uVersion > HGCM_SAVED_STATE_VERSION_V2) + vrc = pVMM->pfnSSMR3GetU32(pSSM, &fRequestor); + AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc); + + /* Connect the client. */ + vrc = pSvc->CreateAndConnectClient(NULL, u32ClientId, fRequestor, true /*fRestoring*/); + AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc); + + /* Call the service, so the operation is executed by the service thread. */ + vrc = pSvc->loadClientState(u32ClientId, pSSM, pVMM, uVersion); + AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc); + } + + pSvc->ReleaseService(); + } + + return VINF_SUCCESS; +} + +/* Create a new client instance and connect it to the service. + * + * @param pu32ClientIdOut If not NULL, then the method must generate a new handle for the client. + * If NULL, use the given 'u32ClientIdIn' handle. + * @param u32ClientIdIn The handle for the client, when 'pu32ClientIdOut' is NULL. + * @param fRequestor The requestor flags, VMMDEV_REQUESTOR_LEGACY if not available. + * @param fRestoring Set if we're restoring a saved state. + * @return VBox status code. + */ +int HGCMService::CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring) +{ + LogFlowFunc(("pu32ClientIdOut = %p, u32ClientIdIn = %d, fRequestor = %#x, fRestoring = %d\n", + pu32ClientIdOut, u32ClientIdIn, fRequestor, fRestoring)); + + /* + * Categorize the client (compress VMMDEV_REQUESTOR_USR_MASK) + * and check the respective client limit. + */ + uint32_t idxClientCategory; + if (fRequestor == VMMDEV_REQUESTOR_LEGACY) + { + idxClientCategory = m_fntable.idxLegacyClientCategory; + AssertStmt(idxClientCategory < RT_ELEMENTS(m_acClients), idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL); + } + else + switch (fRequestor & VMMDEV_REQUESTOR_USR_MASK) + { + case VMMDEV_REQUESTOR_USR_DRV: + case VMMDEV_REQUESTOR_USR_DRV_OTHER: + idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL; + break; + case VMMDEV_REQUESTOR_USR_ROOT: + case VMMDEV_REQUESTOR_USR_SYSTEM: + idxClientCategory = HGCM_CLIENT_CATEGORY_ROOT; + break; + default: + idxClientCategory = HGCM_CLIENT_CATEGORY_USER; + break; + } + + if ( m_acClients[idxClientCategory] < m_fntable.acMaxClients[idxClientCategory] + || fRestoring) + { } + else + { + LogRel2(("Too many concurrenct clients for HGCM service '%s': %u, max %u; category %u\n", + m_pszSvcName, m_cClients, m_fntable.acMaxClients[idxClientCategory], idxClientCategory)); + STAM_REL_COUNTER_INC(&m_StatTooManyClients); + return VERR_HGCM_TOO_MANY_CLIENTS; + } + + /* Allocate a client information structure. */ + HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor, idxClientCategory); + + if (!pClient) + { + Log1WarningFunc(("Could not allocate HGCMClient!!!\n")); + return VERR_NO_MEMORY; + } + + uint32_t handle; + + if (pu32ClientIdOut != NULL) + { + handle = hgcmObjGenerateHandle(pClient); + } + else + { + handle = hgcmObjAssignHandle(pClient, u32ClientIdIn); + } + + LogFlowFunc(("client id = %d\n", handle)); + + AssertRelease(handle); + + /* Initialize the HGCM part of the client. */ + int vrc = pClient->Init(this); + + if (RT_SUCCESS(vrc)) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg; + + pMsg->u32ClientId = handle; + pMsg->fRequestor = fRequestor; + pMsg->fRestoring = fRestoring; + + vrc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(vrc)) + { + /* Add the client Id to the array. */ + if (m_cClients == m_cClientsAllocated) + { + const uint32_t cDelta = 64; + + /* Guards against integer overflow on 32bit arch and also limits size of m_paClientIds array to 4GB*/ + if (m_cClientsAllocated < UINT32_MAX / sizeof(m_paClientIds[0]) - cDelta) + { + uint32_t *paClientIdsNew; + + paClientIdsNew = (uint32_t *)RTMemRealloc(m_paClientIds, + (m_cClientsAllocated + cDelta) * sizeof(m_paClientIds[0])); + Assert(paClientIdsNew); + + if (paClientIdsNew) + { + m_paClientIds = paClientIdsNew; + m_cClientsAllocated += cDelta; + } + else + { + vrc = VERR_NO_MEMORY; + } + } + else + { + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + m_paClientIds[m_cClients] = handle; + m_cClients++; + m_acClients[idxClientCategory]++; + LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s\n", + handle, m_cClients, idxClientCategory, m_acClients[idxClientCategory], m_pszSvcName)); + } + } + } + } + + if (RT_SUCCESS(vrc)) + { + if (pu32ClientIdOut != NULL) + { + *pu32ClientIdOut = handle; + } + + ReferenceService(); + + /* The guest may now use this client object. */ + pClient->makeAccessibleToGuest(); + } + else + { + hgcmObjDeleteHandle(handle); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** + * Disconnect the client from the service and delete the client handle. + * + * @param u32ClientId The handle of the client. + * @param fFromService Set if called by the service via + * svcHlpDisconnectClient(). + * @param pClient The client disconnecting. + * @return VBox status code. + */ +int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient) +{ + AssertPtr(pClient); + LogFlowFunc(("client id = %d, fFromService = %d, pClient = %p\n", u32ClientId, fFromService, pClient)); + + /* + * Destroy the client handle prior to the disconnecting to avoid creating + * a race with other messages from the same client. See @bugref{10038} + * for further details. + */ + Assert(pClient->idxCategory < HGCM_CLIENT_CATEGORY_MAX); + Assert(m_acClients[pClient->idxCategory] > 0); + + bool fReleaseService = false; + int vrc = VERR_NOT_FOUND; + for (uint32_t i = 0; i < m_cClients; i++) + { + if (m_paClientIds[i] == u32ClientId) + { + if (m_acClients[pClient->idxCategory] > 0) + m_acClients[pClient->idxCategory]--; + + m_cClients--; + + if (m_cClients > i) + memmove(&m_paClientIds[i], &m_paClientIds[i + 1], sizeof(m_paClientIds[0]) * (m_cClients - i)); + + /* Delete the client handle. */ + hgcmObjDeleteHandle(u32ClientId); + fReleaseService = true; + + vrc = VINF_SUCCESS; + break; + } + } + + /* Some paranoia wrt to not trusting the client ID array. */ + Assert(vrc == VINF_SUCCESS || fFromService); + if (vrc == VERR_NOT_FOUND && !fFromService) + { + if (m_acClients[pClient->idxCategory] > 0) + m_acClients[pClient->idxCategory]--; + + hgcmObjDeleteHandle(u32ClientId); + fReleaseService = true; + } + + LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s (cPendingCalls=%u) rc=%Rrc\n", u32ClientId, m_cClients, + pClient->idxCategory, m_acClients[pClient->idxCategory], m_pszSvcName, pClient->cPendingCalls, vrc)); + + /* + * Call the service. + */ + if (!fFromService) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_DISCONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + pMsg->pClient = pClient; + + vrc = hgcmMsgSend(pMsg); + } + else + { + LogRel(("(%d, %d) [%s] hgcmMsgAlloc(%p, SVC_MSG_DISCONNECT) failed %Rrc\n", + u32ClientId, fFromService, RT_VALID_PTR(m_pszSvcName)? m_pszSvcName: "", m_pThread, vrc)); + } + } + + + /* + * Release the pClient->pService reference. + */ + if (fReleaseService) + ReleaseService(); + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int HGCMService::RegisterExtension(HGCMSVCEXTHANDLE handle, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) +{ + LogFlowFunc(("%s\n", handle->pszServiceName)); + + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_REGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pCoreMsg; + + pMsg->handle = handle; + pMsg->pfnExtension = pfnExtension; + pMsg->pvExtension = pvExtension; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +void HGCMService::UnregisterExtension(HGCMSVCEXTHANDLE handle) +{ + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_UNREGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); +} + +/** @callback_method_impl{FNHGCMMSGCALLBACK} */ +static DECLCALLBACK(int) hgcmMsgCallCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore) +{ + /* + * Do common message completion then decrement the call counter + * for the client if necessary. + */ + int vrc = hgcmMsgCompletionCallback(result, pMsgCore); + + HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore; + if (pMsg->pcCounter) + { + uint32_t cCalls = ASMAtomicDecU32(pMsg->pcCounter); + AssertStmt(cCalls < UINT32_MAX / 2, ASMAtomicWriteU32(pMsg->pcCounter, 0)); + pMsg->pcCounter = NULL; + Log3Func(("pMsg=%p cPendingCalls=%u / %u (fun %u, %u parms)\n", + pMsg, cCalls, pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms)); + } + + return vrc; +} + +/** Perform a guest call to the service. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle to be disconnected and deleted. + * @param pClient The client data. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @param tsArrival The STAM_GET_TS() value when the request arrived. + * @return VBox rc. + * @retval VINF_HGCM_ASYNC_EXECUTE on success. + */ +int HGCMService::GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + LogFlow(("MAIN::HGCMService::GuestCall\n")); + + int vrc; + HGCMMsgCall *pMsg = new(std::nothrow) HGCMMsgCall(m_pThread); + if (pMsg) + { + pMsg->Reference(); /** @todo starts out with zero references. */ + + uint32_t cCalls = ASMAtomicIncU32(&pClient->cPendingCalls); + Assert(pClient->idxCategory < RT_ELEMENTS(m_fntable.acMaxCallsPerClient)); + if (cCalls < m_fntable.acMaxCallsPerClient[pClient->idxCategory]) + { + pMsg->pcCounter = &pClient->cPendingCalls; + Log3(("MAIN::HGCMService::GuestCall: pMsg=%p cPendingCalls=%u / %u / %s (fun %u, %u parms)\n", + pMsg, cCalls, u32ClientId, m_pszSvcName, u32Function, cParms)); + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + pMsg->tsArrival = tsArrival; + + vrc = hgcmMsgPost(pMsg, hgcmMsgCallCompletionCallback); + + if (RT_SUCCESS(vrc)) + { /* Reference donated on success. */ } + else + { + ASMAtomicDecU32(&pClient->cPendingCalls); + pMsg->pcCounter = NULL; + Log(("MAIN::HGCMService::GuestCall: hgcmMsgPost failed: %Rrc\n", vrc)); + pMsg->Dereference(); + } + } + else + { + ASMAtomicDecU32(&pClient->cPendingCalls); + LogRel2(("HGCM: Too many calls to '%s' from client %u: %u, max %u; category %u\n", m_pszSvcName, u32ClientId, + cCalls, m_fntable.acMaxCallsPerClient[pClient->idxCategory], pClient->idxCategory)); + pMsg->Dereference(); + STAM_REL_COUNTER_INC(&m_StatTooManyCalls); + vrc = VERR_HGCM_TOO_MANY_CLIENT_CALLS; + } + } + else + { + Log(("MAIN::HGCMService::GuestCall: Message allocation failed\n")); + vrc = VERR_NO_MEMORY; + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Guest cancelled a request (call, connection attempt, disconnect attempt). + * + * @param pHGCMPort The port to be used for completion confirmation + * @param pCmd The VBox HGCM context. + * @param idClient The client handle to be disconnected and deleted. + * @return VBox rc. + */ +void HGCMService::GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient) +{ + LogFlow(("MAIN::HGCMService::GuestCancelled\n")); + + if (m_fntable.pfnCancelled) + { + HGCMMsgCancelled *pMsg = new (std::nothrow) HGCMMsgCancelled(m_pThread); + if (pMsg) + { + pMsg->Reference(); /** @todo starts out with zero references. */ + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->idClient = idClient; + + hgcmMsgPost(pMsg, NULL); + } + else + Log(("MAIN::HGCMService::GuestCancelled: Message allocation failed\n")); + } +} + +/** Perform a host call the service. + * + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @return VBox rc. + */ +int HGCMService::HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + LogFlowFunc(("%s u32Function = %d, cParms = %d, paParms = %p\n", + m_pszSvcName, u32Function, cParms, paParms)); + + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTCALL, hgcmMessageAllocSvc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pCoreMsg; + + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Posts a broadcast notification event to all interested services. + * + * @param enmEvent The notification event. + */ +/*static*/ void HGCMService::BroadcastNotify(HGCMNOTIFYEVENT enmEvent) +{ + for (HGCMService *pService = sm_pSvcListHead; pService != NULL; pService = pService->m_pSvcNext) + { + pService->Notify(enmEvent); + } +} + +/** Posts a broadcast notification event to the service. + * + * @param enmEvent The notification event. + */ +void HGCMService::Notify(HGCMNOTIFYEVENT enmEvent) +{ + LogFlowFunc(("%s enmEvent=%d pfnNotify=%p\n", m_pszSvcName, enmEvent, m_fntable.pfnNotify)); + if (m_fntable.pfnNotify) + { + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_NOTIFY, hgcmMessageAllocSvc); + if (RT_SUCCESS(vrc)) + { + HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pCoreMsg; + pMsg->enmEvent = enmEvent; + + vrc = hgcmMsgPost(pMsg, NULL); + AssertRC(vrc); + } + } +} + +/* + * Main HGCM thread that manages services. + */ + +/* Messages processed by the main HGCM thread. */ +#define HGCM_MSG_CONNECT (10) /**< Connect a client to a service. */ +#define HGCM_MSG_DISCONNECT (11) /**< Disconnect the specified client id. */ +#define HGCM_MSG_LOAD (12) /**< Load the service. */ +#define HGCM_MSG_HOSTCALL (13) /**< Call the service. */ +#define HGCM_MSG_LOADSTATE (14) /**< Load saved state for the specified service. */ +#define HGCM_MSG_SAVESTATE (15) /**< Save state for the specified service. */ +#define HGCM_MSG_RESET (16) /**< Disconnect all clients from the specified service. */ +#define HGCM_MSG_QUIT (17) /**< Unload all services and terminate the thread. */ +#define HGCM_MSG_REGEXT (18) /**< Register a service extension. */ +#define HGCM_MSG_UNREGEXT (19) /**< Unregister a service extension. */ +#define HGCM_MSG_BRD_NOTIFY (20) /**< Broadcast notification event (VM state change). */ + +class HGCMMsgMainConnect: public HGCMMsgHeader +{ + public: + /* Service name. */ + const char *pszServiceName; + /* Where to store the client handle. */ + uint32_t *pu32ClientId; +}; + +class HGCMMsgMainDisconnect: public HGCMMsgHeader +{ + public: + /* Handle of the client to be disconnected. */ + uint32_t u32ClientId; +}; + +class HGCMMsgMainLoad: public HGCMMsgCore +{ + public: + /* Name of the library to be loaded. */ + const char *pszServiceLibrary; + /* Name to be assigned to the service. */ + const char *pszServiceName; + /** The user mode VM handle (for statistics and such). */ + PUVM pUVM; + /** The VMM vtable (for statistics and such). */ + PCVMMR3VTABLE pVMM; + /** The HGCM port on the VMMDev device (for session ID and such). */ + PPDMIHGCMPORT pHgcmPort; +}; + +class HGCMMsgMainHostCall: public HGCMMsgCore +{ + public: + /* Which service to call. */ + const char *pszServiceName; + /* Function number. */ + uint32_t u32Function; + /* Number of the function parameters. */ + uint32_t cParms; + /* Pointer to array of the function parameters. */ + VBOXHGCMSVCPARM *paParms; +}; + +class HGCMMsgMainLoadSaveState: public HGCMMsgCore +{ + public: + /** Saved state handle. */ + PSSMHANDLE pSSM; + /** The VMM vtable. */ + PCVMMR3VTABLE pVMM; + /** The HGCM saved state version being loaded (ignore for save). */ + uint32_t uVersion; +}; + +class HGCMMsgMainReset: public HGCMMsgCore +{ + public: + /** Set if this is actually a shutdown and not a VM reset. */ + bool fForShutdown; +}; + +class HGCMMsgMainQuit: public HGCMMsgCore +{ + public: + /** Whether UVM has gone invalid already or not. */ + bool fUvmIsInvalid; +}; + +class HGCMMsgMainRegisterExtension: public HGCMMsgCore +{ + public: + /** Returned handle to be used in HGCMMsgMainUnregisterExtension. */ + HGCMSVCEXTHANDLE *pHandle; + /** Name of the service. */ + const char *pszServiceName; + /** The extension entry point. */ + PFNHGCMSVCEXT pfnExtension; + /** The extension pointer. */ + void *pvExtension; +}; + +class HGCMMsgMainUnregisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the registered extension. */ + HGCMSVCEXTHANDLE handle; +}; + +class HGCMMsgMainBroadcastNotify: public HGCMMsgCore +{ + public: + /** The notification event. */ + HGCMNOTIFYEVENT enmEvent; +}; + + +static HGCMMsgCore *hgcmMainMessageAlloc (uint32_t u32MsgId) +{ + switch (u32MsgId) + { + case HGCM_MSG_CONNECT: return new HGCMMsgMainConnect(); + case HGCM_MSG_DISCONNECT: return new HGCMMsgMainDisconnect(); + case HGCM_MSG_LOAD: return new HGCMMsgMainLoad(); + case HGCM_MSG_HOSTCALL: return new HGCMMsgMainHostCall(); + case HGCM_MSG_LOADSTATE: + case HGCM_MSG_SAVESTATE: return new HGCMMsgMainLoadSaveState(); + case HGCM_MSG_RESET: return new HGCMMsgMainReset(); + case HGCM_MSG_QUIT: return new HGCMMsgMainQuit(); + case HGCM_MSG_REGEXT: return new HGCMMsgMainRegisterExtension(); + case HGCM_MSG_UNREGEXT: return new HGCMMsgMainUnregisterExtension(); + case HGCM_MSG_BRD_NOTIFY: return new HGCMMsgMainBroadcastNotify(); + + default: + AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId)); + } + + return NULL; +} + + +/* The main HGCM thread handler. */ +static DECLCALLBACK(void) hgcmThread(HGCMThread *pThread, void *pvUser) +{ + LogFlowFunc(("pThread = %p, pvUser = %p\n", pThread, pvUser)); + + NOREF(pvUser); + + bool fQuit = false; + + while (!fQuit) + { + HGCMMsgCore *pMsgCore; + int vrc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(vrc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", vrc)); + break; + } + + uint32_t u32MsgId = pMsgCore->MsgId(); + + switch (u32MsgId) + { + case HGCM_MSG_CONNECT: + { + HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_CONNECT pszServiceName %s, pu32ClientId %p\n", + pMsg->pszServiceName, pMsg->pu32ClientId)); + + /* Resolve the service name to the pointer to service instance. + */ + HGCMService *pService; + vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(vrc)) + { + /* Call the service instance method. */ + vrc = pService->CreateAndConnectClient(pMsg->pu32ClientId, + 0, + pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd), + pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd)); + + /* Release the service after resolve. */ + pService->ReleaseService(); + } + } break; + + case HGCM_MSG_DISCONNECT: + { + HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_DISCONNECT u32ClientId = %d\n", + pMsg->u32ClientId)); + + HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId); + + if (!pClient) + { + vrc = VERR_HGCM_INVALID_CLIENT_ID; + break; + } + + /* The service the client belongs to. */ + HGCMService *pService = pClient->pService; + + /* Call the service instance to disconnect the client. */ + vrc = pService->DisconnectClient(pMsg->u32ClientId, false, pClient); + + hgcmObjDereference(pClient); + } break; + + case HGCM_MSG_LOAD: + { + HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_LOAD pszServiceName = %s, pMsg->pszServiceLibrary = %s, pMsg->pUVM = %p\n", + pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pUVM)); + + vrc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName, + pMsg->pUVM, pMsg->pVMM, pMsg->pHgcmPort); + } break; + + case HGCM_MSG_HOSTCALL: + { + HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_HOSTCALL pszServiceName %s, u32Function %d, cParms %d, paParms %p\n", + pMsg->pszServiceName, pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + /* Resolve the service name to the pointer to service instance. */ + HGCMService *pService; + vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(vrc)) + { + vrc = pService->HostCall(pMsg->u32Function, pMsg->cParms, pMsg->paParms); + + pService->ReleaseService(); + } + } break; + + case HGCM_MSG_BRD_NOTIFY: + { + HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_BRD_NOTIFY enmEvent=%d\n", pMsg->enmEvent)); + + HGCMService::BroadcastNotify(pMsg->enmEvent); + } break; + + case HGCM_MSG_RESET: + { + LogFlowFunc(("HGCM_MSG_RESET\n")); + + HGCMService::Reset(); + + HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore; + if (!pMsg->fForShutdown) + HGCMService::BroadcastNotify(HGCMNOTIFYEVENT_RESET); + } break; + + case HGCM_MSG_LOADSTATE: + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_LOADSTATE\n")); + + vrc = HGCMService::LoadState(pMsg->pSSM, pMsg->pVMM, pMsg->uVersion); + } break; + + case HGCM_MSG_SAVESTATE: + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_SAVESTATE\n")); + + vrc = HGCMService::SaveState(pMsg->pSSM, pMsg->pVMM); + } break; + + case HGCM_MSG_QUIT: + { + HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore; + LogFlowFunc(("HGCM_MSG_QUIT\n")); + + HGCMService::UnloadAll(pMsg->fUvmIsInvalid); + + fQuit = true; + } break; + + case HGCM_MSG_REGEXT: + { + HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_REGEXT\n")); + + /* Allocate the handle data. */ + HGCMSVCEXTHANDLE handle = (HGCMSVCEXTHANDLE)RTMemAllocZ(sizeof(struct _HGCMSVCEXTHANDLEDATA) + + strlen(pMsg->pszServiceName) + + sizeof(char)); + + if (handle == NULL) + { + vrc = VERR_NO_MEMORY; + } + else + { + handle->pszServiceName = (char *)((uint8_t *)handle + sizeof(struct _HGCMSVCEXTHANDLEDATA)); + strcpy(handle->pszServiceName, pMsg->pszServiceName); + + HGCMService *pService; + vrc = HGCMService::ResolveService(&pService, handle->pszServiceName); + + if (RT_SUCCESS(vrc)) + { + pService->RegisterExtension(handle, pMsg->pfnExtension, pMsg->pvExtension); + + pService->ReleaseService(); + } + + if (RT_FAILURE(vrc)) + { + RTMemFree(handle); + } + else + { + *pMsg->pHandle = handle; + } + } + } break; + + case HGCM_MSG_UNREGEXT: + { + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_UNREGEXT\n")); + + HGCMService *pService; + vrc = HGCMService::ResolveService(&pService, pMsg->handle->pszServiceName); + + if (RT_SUCCESS(vrc)) + { + pService->UnregisterExtension(pMsg->handle); + + pService->ReleaseService(); + } + + RTMemFree(pMsg->handle); + } break; + + default: + { + AssertMsgFailed(("hgcmThread: Unsupported message number %08X!!!\n", u32MsgId)); + vrc = VERR_NOT_SUPPORTED; + } break; + } + + /* Complete the message processing. */ + hgcmMsgComplete(pMsgCore, vrc); + + LogFlowFunc(("message processed %Rrc\n", vrc)); + } +} + + +/* + * The HGCM API. + */ + +/** The main hgcm thread. */ +static HGCMThread *g_pHgcmThread = 0; + +/* + * Public HGCM functions. + * + * hgcmGuest* - called as a result of the guest HGCM requests. + * hgcmHost* - called by the host. + */ + +/* Load a HGCM service from the specified library. + * Assign the specified name to the service. + * + * @param pszServiceLibrary The library to be loaded. + * @param pszServiceName The name to be assigned to the service. + * @param pUVM The user mode VM handle (for statistics and such). + * @param pVMM The VMM vtable (for statistics and such). + * @param pHgcmPort The HGCM port on the VMMDev device (for session ID and such). + * @return VBox rc. + */ +int HGCMHostLoad(const char *pszServiceLibrary, + const char *pszServiceName, + PUVM pUVM, + PCVMMR3VTABLE pVMM, + PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("lib = %s, name = %s\n", pszServiceLibrary, pszServiceName)); + + if (!pszServiceLibrary || !pszServiceName) + return VERR_INVALID_PARAMETER; + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_LOAD, hgcmMainMessageAlloc); + if (RT_SUCCESS(vrc)) + { + /* Initialize the message. Since the message is synchronous, use the supplied pointers. */ + HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pCoreMsg; + + pMsg->pszServiceLibrary = pszServiceLibrary; + pMsg->pszServiceName = pszServiceName; + pMsg->pUVM = pUVM; + pMsg->pVMM = pVMM; + pMsg->pHgcmPort = pHgcmPort; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/* Register a HGCM service extension. + * + * @param pHandle Returned handle for the registered extension. + * @param pszServiceName The name of the service. + * @param pfnExtension The extension entry point (callback). + * @param pvExtension The extension pointer. + * @return VBox rc. + */ +int HGCMHostRegisterServiceExtension(HGCMSVCEXTHANDLE *pHandle, + const char *pszServiceName, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) +{ + LogFlowFunc(("pHandle = %p, name = %s, pfn = %p, rv = %p\n", pHandle, pszServiceName, pfnExtension, pvExtension)); + + if (!pHandle || !pszServiceName || !pfnExtension) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_REGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + /* Initialize the message. Since the message is synchronous, use the supplied pointers. */ + HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pCoreMsg; + + pMsg->pHandle = pHandle; + pMsg->pszServiceName = pszServiceName; + pMsg->pfnExtension = pfnExtension; + pMsg->pvExtension = pvExtension; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("*pHandle = %p, vrc = %Rrc\n", *pHandle, vrc)); + return vrc; +} + +void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle) +{ + LogFlowFunc(("handle = %p\n", handle)); + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_UNREGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + /* Initialize the message. */ + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return; +} + +/* Find a service and inform it about a client connection, create a client handle. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param pszServiceName The name of the service to be connected to. + * @param pu32ClientId Where the store the created client handle. + * @return VBox rc. + */ +int HGCMGuestConnect(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + const char *pszServiceName, + uint32_t *pu32ClientId) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, name = %s, pu32ClientId = %p\n", + pHGCMPort, pCmd, pszServiceName, pu32ClientId)); + + if (pHGCMPort == NULL || pCmd == NULL || pszServiceName == NULL || pu32ClientId == NULL) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_CONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + /* Initialize the message. Since 'pszServiceName' and 'pu32ClientId' + * will not be deallocated by the caller until the message is completed, + * use the supplied pointers. + */ + HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pCoreMsg; + + pMsg->pHGCMPort = pHGCMPort; + pMsg->pCmd = pCmd; + pMsg->pszServiceName = pszServiceName; + pMsg->pu32ClientId = pu32ClientId; + + vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", vrc)); + return vrc; +} + +/* Tell a service that the client is disconnecting, destroy the client handle. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle to be disconnected and deleted. + * @return VBox rc. + */ +int HGCMGuestDisconnect(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + uint32_t u32ClientId) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d\n", + pHGCMPort, pCmd, u32ClientId)); + + if (!pHGCMPort || !pCmd || !u32ClientId) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_DISCONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + /* Initialize the message. */ + HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pCoreMsg; + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + + vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Helper to send either HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE messages to the main HGCM thread. + * + * @param pSSM The SSM handle. + * @param pVMM The VMM vtable. + * @param idMsg The message to be sent: HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE. + * @param uVersion The state version being loaded. + * @return VBox rc. + */ +static int hgcmHostLoadSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t idMsg, uint32_t uVersion) +{ + LogFlowFunc(("pSSM = %p, pVMM = %p, idMsg = %d, uVersion = %#x\n", pSSM, pVMM, idMsg, uVersion)); + + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, idMsg, hgcmMainMessageAlloc); + if (RT_SUCCESS(vrc)) + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pCoreMsg; + AssertRelease(pMsg); + + pMsg->pSSM = pSSM; + pMsg->pVMM = pVMM; + pMsg->uVersion = uVersion; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Save the state of services. + * + * @param pSSM The SSM handle. + * @param pVMM The VMM vtable. + * @return VBox status code. + */ +int HGCMHostSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM) +{ + return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_SAVESTATE, HGCM_SAVED_STATE_VERSION); +} + +/** Load the state of services. + * + * @param pSSM The SSM handle. + * @param pVMM The VMM vtable. + * @param uVersion The state version being loaded. + * @return VBox status code. + */ +int HGCMHostLoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion) +{ + return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_LOADSTATE, uVersion); +} + +/** The guest calls the service. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @param tsArrival The STAM_GET_TS() value when the request arrived. + * @return VBox rc. + */ +int HGCMGuestCall(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + uint32_t u32ClientId, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM *paParms, + uint64_t tsArrival) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n", + pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms)); + + if (!pHGCMPort || !pCmd || u32ClientId == 0) + { + return VERR_INVALID_PARAMETER; + } + + int vrc = VERR_HGCM_INVALID_CLIENT_ID; + + /* Resolve the client handle to the client instance pointer. */ + HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(u32ClientId); + + if (pClient) + { + AssertRelease(pClient->pService); + + /* Forward the message to the service thread. */ + vrc = pClient->pService->GuestCall(pHGCMPort, pCmd, u32ClientId, pClient, u32Function, cParms, paParms, tsArrival); + + hgcmObjDereference(pClient); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** The guest cancelled a request (call, connect, disconnect) + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param idClient The client handle. + */ +void HGCMGuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, idClient = %d\n", pHGCMPort, pCmd, idClient)); + AssertReturnVoid(pHGCMPort); + AssertReturnVoid(pCmd); + AssertReturnVoid(idClient != 0); + + /* Resolve the client handle to the client instance pointer. */ + HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(idClient); + + if (pClient) + { + AssertRelease(pClient->pService); + + /* Forward the message to the service thread. */ + pClient->pService->GuestCancelled(pHGCMPort, pCmd, idClient); + + hgcmObjDereference(pClient); + } + + LogFlowFunc(("returns\n")); +} + +/** The host calls the service. + * + * @param pszServiceName The service name to be called. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @return VBox rc. + */ +int HGCMHostCall(const char *pszServiceName, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM *paParms) +{ + LogFlowFunc(("name = %s, u32Function = %d, cParms = %d, paParms = %p\n", + pszServiceName, u32Function, cParms, paParms)); + + if (!pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + /* Host calls go to main HGCM thread that resolves the service name to the + * service instance pointer and then, using the service pointer, forwards + * the message to the service thread. + * So it is slow but host calls are intended mostly for configuration and + * other non-time-critical functions. + */ + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_HOSTCALL, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pCoreMsg; + + pMsg->pszServiceName = (char *)pszServiceName; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +/** Posts a notification event to all services. + * + * @param enmEvent The notification event. + * @return VBox rc. + */ +int HGCMBroadcastEvent(HGCMNOTIFYEVENT enmEvent) +{ + LogFlowFunc(("enmEvent=%d\n", enmEvent)); + + HGCMMsgCore *pCoreMsg; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_BRD_NOTIFY, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pCoreMsg; + + pMsg->enmEvent = enmEvent; + + vrc = hgcmMsgPost(pMsg, NULL); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + + +int HGCMHostReset(bool fForShutdown) +{ + LogFlowFunc(("\n")); + + /* Disconnect all clients. + */ + + HGCMMsgCore *pMsgCore; + int vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_RESET, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore; + + pMsg->fForShutdown = fForShutdown; + + vrc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int HGCMHostInit(void) +{ + LogFlowFunc(("\n")); + + int vrc = hgcmThreadInit(); + + if (RT_SUCCESS(vrc)) + { + /* + * Start main HGCM thread. + */ + + vrc = hgcmThreadCreate(&g_pHgcmThread, "MainHGCMthread", hgcmThread, NULL /*pvUser*/, + NULL /*pszStatsSubDir*/, NULL /*pUVM*/, NULL /*pVMM*/); + + if (RT_FAILURE(vrc)) + LogRel(("Failed to start HGCM thread. HGCM services will be unavailable!!! vrc = %Rrc\n", vrc)); + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int HGCMHostShutdown(bool fUvmIsInvalid /*= false*/) +{ + LogFlowFunc(("\n")); + + /* + * Do HGCMReset and then unload all services. + */ + + int vrc = HGCMHostReset(true /*fForShutdown*/); + + if (RT_SUCCESS(vrc)) + { + /* Send the quit message to the main hgcmThread. */ + HGCMMsgCore *pMsgCore; + vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_QUIT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(vrc)) + { + HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore; + pMsg->fUvmIsInvalid = fUvmIsInvalid; + + vrc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(vrc)) + { + /* Wait for the thread termination. */ + hgcmThreadWait(g_pHgcmThread); + g_pHgcmThread = NULL; + + hgcmThreadUninit(); + } + } + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + diff --git a/src/VBox/Main/src-client/HGCMObjects.cpp b/src/VBox/Main/src-client/HGCMObjects.cpp new file mode 100644 index 00000000..0b668618 --- /dev/null +++ b/src/VBox/Main/src-client/HGCMObjects.cpp @@ -0,0 +1,286 @@ +/* $Id: HGCMObjects.cpp $ */ +/** @file + * HGCMObjects - Host-Guest Communication Manager objects + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_HGCM +#include "LoggingNew.h" + +#include "HGCMObjects.h" + +#include <iprt/string.h> +#include <iprt/errcore.h> + + +static RTCRITSECT g_critsect; + +/* There are internal handles, which are not saved, + * and client handles, which are saved. + * They use different range of values: + * 1..7FFFFFFF for clients, + * 0x80000001..0xFFFFFFFF for other handles. + */ +static uint32_t volatile g_u32InternalHandleCount; +static uint32_t volatile g_u32ClientHandleCount; + +static PAVLU32NODECORE g_pTree; + + +DECLINLINE(int) hgcmObjEnter(void) +{ + return RTCritSectEnter(&g_critsect); +} + +DECLINLINE(void) hgcmObjLeave(void) +{ + RTCritSectLeave(&g_critsect); +} + +int hgcmObjInit(void) +{ + LogFlow(("MAIN::hgcmObjInit\n")); + + g_u32InternalHandleCount = 0x80000000; + g_u32ClientHandleCount = 0; + g_pTree = NULL; + + int vrc = RTCritSectInit(&g_critsect); + + LogFlow(("MAIN::hgcmObjInit: vrc = %Rrc\n", vrc)); + + return vrc; +} + +void hgcmObjUninit(void) +{ + if (RTCritSectIsInitialized(&g_critsect)) + RTCritSectDelete(&g_critsect); +} + +uint32_t hgcmObjMake(HGCMObject *pObject, uint32_t u32HandleIn) +{ + int handle = 0; + + LogFlow(("MAIN::hgcmObjGenerateHandle: pObject %p\n", pObject)); + + int vrc = hgcmObjEnter(); + + if (RT_SUCCESS(vrc)) + { + ObjectAVLCore *pCore = &pObject->m_core; + + /* Generate a new handle value. */ + + uint32_t volatile *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT? + &g_u32ClientHandleCount: + &g_u32InternalHandleCount; + + uint32_t u32Start = *pu32HandleCountSource; + + for (;;) + { + uint32_t Key; + + if (u32HandleIn == 0) + { + Key = ASMAtomicIncU32(pu32HandleCountSource); + + if (Key == u32Start) + { + /* Rollover. Something is wrong. */ + AssertReleaseFailed(); + break; + } + + /* 0 and 0x80000000 are not valid handles. */ + if ((Key & 0x7FFFFFFF) == 0) + { + /* Over the invalid value, reinitialize the source. */ + *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT? + 0: + UINT32_C(0x80000000); + continue; + } + } + else + { + Key = u32HandleIn; + } + + /* Insert object to AVL tree. */ + pCore->AvlCore.Key = Key; + + bool fRC = RTAvlU32Insert(&g_pTree, &pCore->AvlCore); + + /* Could not insert a handle. */ + if (!fRC) + { + if (u32HandleIn == 0) + { + /* Try another generated handle. */ + continue; + } + /* Could not use the specified handle. */ + break; + } + + /* Initialize backlink. */ + pCore->pSelf = pObject; + + /* Reference the object for time while it resides in the tree. */ + pObject->Reference(); + + /* Store returned handle. */ + handle = Key; + + Log(("Object key inserted 0x%08X\n", Key)); + + break; + } + + hgcmObjLeave(); + } + else + { + AssertReleaseMsgFailed(("MAIN::hgcmObjGenerateHandle: Failed to acquire object pool semaphore")); + } + + LogFlow(("MAIN::hgcmObjGenerateHandle: handle = 0x%08X, vrc = %Rrc, return void\n", handle, vrc)); + + return handle; +} + +uint32_t hgcmObjGenerateHandle(HGCMObject *pObject) +{ + return hgcmObjMake(pObject, 0); +} + +uint32_t hgcmObjAssignHandle(HGCMObject *pObject, uint32_t u32Handle) +{ + return hgcmObjMake(pObject, u32Handle); +} + +void hgcmObjDeleteHandle(uint32_t handle) +{ + int vrc = VINF_SUCCESS; + + LogFlow(("MAIN::hgcmObjDeleteHandle: handle 0x%08X\n", handle)); + + if (handle) + { + vrc = hgcmObjEnter(); + + if (RT_SUCCESS(vrc)) + { + ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Remove(&g_pTree, handle); + + if (pCore) + { + AssertRelease(pCore->pSelf); + + pCore->pSelf->Dereference(); + } + + hgcmObjLeave(); + } + else + { + AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc)); + } + } + + LogFlow(("MAIN::hgcmObjDeleteHandle: vrc = %Rrc, return void\n", vrc)); +} + +HGCMObject *hgcmObjReference (uint32_t handle, HGCMOBJ_TYPE enmObjType) +{ + LogFlow(("MAIN::hgcmObjReference: handle 0x%08X\n", handle)); + + HGCMObject *pObject = NULL; + + if ((handle & UINT32_C(0x7FFFFFFF)) == 0) + { + return pObject; + } + + int vrc = hgcmObjEnter(); + + if (RT_SUCCESS(vrc)) + { + ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Get(&g_pTree, handle); + + Assert(!pCore || (pCore->pSelf && pCore->pSelf->Type() == enmObjType)); + if ( pCore + && pCore->pSelf + && pCore->pSelf->Type() == enmObjType) + { + pObject = pCore->pSelf; + + AssertRelease(pObject); + + pObject->Reference(); + } + + hgcmObjLeave(); + } + else + { + AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc)); + } + + LogFlow(("MAIN::hgcmObjReference: return pObject %p\n", pObject)); + + return pObject; +} + +void hgcmObjDereference(HGCMObject *pObject) +{ + LogFlow(("MAIN::hgcmObjDereference: pObject %p\n", pObject)); + + AssertRelease(pObject); + + pObject->Dereference(); + + LogFlow(("MAIN::hgcmObjDereference: return\n")); +} + +uint32_t hgcmObjQueryHandleCount() +{ + return g_u32ClientHandleCount; +} + +void hgcmObjSetHandleCount(uint32_t u32ClientHandleCount) +{ + Assert(g_u32ClientHandleCount <= u32ClientHandleCount); + + int vrc = hgcmObjEnter(); + + if (RT_SUCCESS(vrc)) + { + if (g_u32ClientHandleCount <= u32ClientHandleCount) + g_u32ClientHandleCount = u32ClientHandleCount; + hgcmObjLeave(); + } +} diff --git a/src/VBox/Main/src-client/HGCMThread.cpp b/src/VBox/Main/src-client/HGCMThread.cpp new file mode 100644 index 00000000..8ea01bab --- /dev/null +++ b/src/VBox/Main/src-client/HGCMThread.cpp @@ -0,0 +1,790 @@ +/* $Id: HGCMThread.cpp $ */ +/** @file + * HGCMThread - Host-Guest Communication Manager Threads + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_HGCM +#include "LoggingNew.h" + +#include "HGCMThread.h" + +#include <VBox/err.h> +#include <VBox/vmm/stam.h> +#include <VBox/vmm/vmmr3vtable.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/string.h> + +#include <new> /* for std:nothrow */ + + +/* HGCM uses worker threads, which process messages from other threads. + * A message consists of the message header and message specific data. + * Message header is opaque for callers, but message data is defined + * and used by them. + * + * Messages are distinguished by message identifier and worker thread + * they are allocated for. + * + * Messages are allocated for a worker thread and belong to + * the thread. A worker thread holds the queue of messages. + * + * The calling thread creates a message, specifying which worker thread + * the message is created for, then, optionally, initializes message + * specific data and, also optionally, references the message. + * + * Message then is posted or sent to worker thread by inserting + * it to the worker thread message queue and referencing the message. + * Worker thread then again may fetch next message. + * + * Upon processing the message the worker thread dereferences it. + * Dereferencing also automatically deletes message from the thread + * queue and frees memory allocated for the message, if no more + * references left. If there are references, the message remains + * in the queue. + * + */ + +/* Version of HGCM message header */ +#define HGCMMSG_VERSION (1) + +/* Thread is initializing. */ +#define HGCMMSG_TF_INITIALIZING (0x00000001) +/* Thread must be terminated. */ +#define HGCMMSG_TF_TERMINATE (0x00000002) +/* Thread has been terminated. */ +#define HGCMMSG_TF_TERMINATED (0x00000004) + +/** @todo consider use of RTReq */ + +static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser); + +class HGCMThread : public HGCMReferencedObject +{ + private: + friend DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser); + + /* Worker thread function. */ + PFNHGCMTHREAD m_pfnThread; + + /* A user supplied thread parameter. */ + void *m_pvUser; + + /* The thread runtime handle. */ + RTTHREAD m_hThread; + + /** Event the thread waits for, signalled when a message to process is posted to + * the thread, automatically reset. */ + RTSEMEVENT m_eventThread; + + /* A caller thread waits for completion of a SENT message on this event. */ + RTSEMEVENTMULTI m_eventSend; + int32_t volatile m_i32MessagesProcessed; + + /* Critical section for accessing the thread data, mostly for message queues. */ + RTCRITSECT m_critsect; + + /* thread state/operation flags */ + uint32_t m_fu32ThreadFlags; + + /* Message queue variables. Messages are inserted at tail of message + * queue. They are consumed by worker thread sequentially. If a message was + * consumed, it is removed from message queue. + */ + + /* Head of message queue. */ + HGCMMsgCore *m_pMsgInputQueueHead; + /* Message which another message will be inserted after. */ + HGCMMsgCore *m_pMsgInputQueueTail; + + /* Head of messages being processed queue. */ + HGCMMsgCore *m_pMsgInProcessHead; + /* Message which another message will be inserted after. */ + HGCMMsgCore *m_pMsgInProcessTail; + + /* Head of free message structures list. */ + HGCMMsgCore *m_pFreeHead; + /* Tail of free message structures list. */ + HGCMMsgCore *m_pFreeTail; + + /** @name Statistics + * @{ */ + STAMCOUNTER m_StatPostMsgNoPending; + STAMCOUNTER m_StatPostMsgOnePending; + STAMCOUNTER m_StatPostMsgTwoPending; + STAMCOUNTER m_StatPostMsgThreePending; + STAMCOUNTER m_StatPostMsgManyPending; + /** @} */ + + inline int Enter(void); + inline void Leave(void); + + HGCMMsgCore *FetchFreeListHead(void); + + protected: + virtual ~HGCMThread(void); + + public: + + HGCMThread (); + + int WaitForTermination (void); + + int Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser, + const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM); + + int MsgAlloc(HGCMMsgCore **pMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage); + int MsgGet(HGCMMsgCore **ppMsg); + int MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool bWait); + int MsgComplete(HGCMMsgCore *pMsg, int32_t result); +}; + + +/* + * HGCMMsgCore implementation. + */ + +#define HGCM_MSG_F_PROCESSED (0x00000001) +#define HGCM_MSG_F_WAIT (0x00000002) +#define HGCM_MSG_F_IN_PROCESS (0x00000004) + +void HGCMMsgCore::InitializeCore(uint32_t u32MsgId, HGCMThread *pThread) +{ + m_u32Version = HGCMMSG_VERSION; + m_u32Msg = u32MsgId; + m_pfnCallback = NULL; + m_pNext = NULL; + m_pPrev = NULL; + m_fu32Flags = 0; + m_rcSend = VINF_SUCCESS; + m_pThread = pThread; + pThread->Reference(); +} + +/* virtual */ HGCMMsgCore::~HGCMMsgCore() +{ + if (m_pThread) + { + m_pThread->Dereference(); + m_pThread = NULL; + } +} + +/* + * HGCMThread implementation. + */ + +static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD hThreadSelf, void *pvUser) +{ + HGCMThread *pThread = (HGCMThread *)pvUser; + + LogFlow(("MAIN::hgcmWorkerThreadFunc: starting HGCM thread %p\n", pThread)); + + AssertRelease(pThread); + + pThread->m_hThread = hThreadSelf; + pThread->m_fu32ThreadFlags &= ~HGCMMSG_TF_INITIALIZING; + int vrc = RTThreadUserSignal(hThreadSelf); + AssertRC(vrc); + + pThread->m_pfnThread(pThread, pThread->m_pvUser); + + pThread->m_fu32ThreadFlags |= HGCMMSG_TF_TERMINATED; + + LogFlow(("MAIN::hgcmWorkerThreadFunc: completed HGCM thread %p\n", pThread)); + + return vrc; +} + +HGCMThread::HGCMThread() + : + HGCMReferencedObject(HGCMOBJ_THREAD), + m_pfnThread(NULL), + m_pvUser(NULL), + m_hThread(NIL_RTTHREAD), + m_eventThread(NIL_RTSEMEVENT), + m_eventSend(NIL_RTSEMEVENTMULTI), + m_i32MessagesProcessed(0), + m_fu32ThreadFlags(0), + m_pMsgInputQueueHead(NULL), + m_pMsgInputQueueTail(NULL), + m_pMsgInProcessHead(NULL), + m_pMsgInProcessTail(NULL), + m_pFreeHead(NULL), + m_pFreeTail(NULL) +{ + RT_ZERO(m_critsect); +} + +HGCMThread::~HGCMThread() +{ + /* + * Free resources allocated for the thread. + */ + + Assert(m_fu32ThreadFlags & HGCMMSG_TF_TERMINATED); + + if (RTCritSectIsInitialized(&m_critsect)) + RTCritSectDelete(&m_critsect); + + if (m_eventSend != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(m_eventSend); + m_eventSend = NIL_RTSEMEVENTMULTI; + } + + if (m_eventThread != NIL_RTSEMEVENT) + { + RTSemEventDestroy(m_eventThread); + m_eventThread = NIL_RTSEMEVENT; + } +} + +int HGCMThread::WaitForTermination(void) +{ + int vrc = VINF_SUCCESS; + LogFlowFunc(("\n")); + + if (m_hThread != NIL_RTTHREAD) + { + vrc = RTThreadWait(m_hThread, 5000, NULL); + m_hThread = NIL_RTTHREAD; + } + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int HGCMThread::Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser, + const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + int vrc = RTSemEventCreate(&m_eventThread); + + if (RT_SUCCESS(vrc)) + { + vrc = RTSemEventMultiCreate(&m_eventSend); + + if (RT_SUCCESS(vrc)) + { + vrc = RTCritSectInit(&m_critsect); + + if (RT_SUCCESS(vrc)) + { + m_pfnThread = pfnThread; + m_pvUser = pvUser; + + m_fu32ThreadFlags = HGCMMSG_TF_INITIALIZING; + + RTTHREAD hThread; + vrc = RTThreadCreate(&hThread, hgcmWorkerThreadFunc, this, 0, /* default stack size; some services + may need quite a bit */ + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + pszThreadName); + + if (RT_SUCCESS(vrc)) + { + /* Register statistics while the thread starts. */ + if (pUVM) + { + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgNoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, "Times a message was appended to an empty input queue.", + "/HGCM/%s/PostMsg0Pending", pszStatsSubDir); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgOnePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, + "Times a message was appended to input queue with only one pending message.", + "/HGCM/%s/PostMsg1Pending", pszStatsSubDir); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgTwoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, + "Times a message was appended to input queue with only one pending message.", + "/HGCM/%s/PostMsg2Pending", pszStatsSubDir); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgThreePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, + "Times a message was appended to input queue with only one pending message.", + "/HGCM/%s/PostMsg3Pending", pszStatsSubDir); + pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgManyPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, + STAMUNIT_COUNT, + "Times a message was appended to input queue with only one pending message.", + "/HGCM/%s/PostMsgManyPending", pszStatsSubDir); + } + + + /* Wait until the thread is ready. */ + vrc = RTThreadUserWait(hThread, 30000); + AssertRC(vrc); + Assert(!(m_fu32ThreadFlags & HGCMMSG_TF_INITIALIZING) || RT_FAILURE(vrc)); + } + else + { + m_hThread = NIL_RTTHREAD; + Log(("hgcmThreadCreate: FAILURE: Can't start worker thread.\n")); + } + } + else + { + Log(("hgcmThreadCreate: FAILURE: Can't init a critical section for a hgcm worker thread.\n")); + RT_ZERO(m_critsect); + } + } + else + { + Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a sent messages.\n")); + m_eventSend = NIL_RTSEMEVENTMULTI; + } + } + else + { + Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a hgcm worker thread.\n")); + m_eventThread = NIL_RTSEMEVENT; + } + + return vrc; +} + +inline int HGCMThread::Enter(void) +{ + int vrc = RTCritSectEnter(&m_critsect); + +#ifdef LOG_ENABLED + if (RT_FAILURE(vrc)) + Log(("HGCMThread::MsgPost: FAILURE: could not obtain worker thread mutex, vrc = %Rrc!!!\n", vrc)); +#endif + + return vrc; +} + +inline void HGCMThread::Leave(void) +{ + RTCritSectLeave(&m_critsect); +} + + +int HGCMThread::MsgAlloc(HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage) +{ + /** @todo Implement this free list / cache thingy. */ + HGCMMsgCore *pmsg = NULL; + + bool fFromFreeList = false; + + if (!pmsg) + { + /* We have to allocate a new memory block. */ + pmsg = pfnNewMessage(u32MsgId); + if (pmsg != NULL) + pmsg->Reference(); /* (it's created with zero references) */ + else + return VERR_NO_MEMORY; + } + + /* Initialize just allocated message core */ + pmsg->InitializeCore(u32MsgId, this); + + /* and the message specific data. */ + pmsg->Initialize(); + + LogFlow(("MAIN::hgcmMsgAlloc: allocated message %p\n", pmsg)); + + *ppMsg = pmsg; + + if (fFromFreeList) + { + /* Message was referenced in the free list, now dereference it. */ + pmsg->Dereference(); + } + + return VINF_SUCCESS; +} + +int HGCMThread::MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait) +{ + LogFlow(("HGCMThread::MsgPost: thread = %p, pMsg = %p, pfnCallback = %p\n", this, pMsg, pfnCallback)); + + int vrc = Enter(); + + if (RT_SUCCESS(vrc)) + { + pMsg->m_pfnCallback = pfnCallback; + + if (fWait) + pMsg->m_fu32Flags |= HGCM_MSG_F_WAIT; + + /* Insert the message to the queue tail. */ + pMsg->m_pNext = NULL; + HGCMMsgCore * const pPrev = m_pMsgInputQueueTail; + pMsg->m_pPrev = pPrev; + + if (pPrev) + { + pPrev->m_pNext = pMsg; + if (!pPrev->m_pPrev) + STAM_REL_COUNTER_INC(&m_StatPostMsgOnePending); + else if (!pPrev->m_pPrev) + STAM_REL_COUNTER_INC(&m_StatPostMsgTwoPending); + else if (!pPrev->m_pPrev->m_pPrev) + STAM_REL_COUNTER_INC(&m_StatPostMsgThreePending); + else + STAM_REL_COUNTER_INC(&m_StatPostMsgManyPending); + } + else + { + m_pMsgInputQueueHead = pMsg; + STAM_REL_COUNTER_INC(&m_StatPostMsgNoPending); + } + + m_pMsgInputQueueTail = pMsg; + + Leave(); + + LogFlow(("HGCMThread::MsgPost: going to inform the thread %p about message, fWait = %d\n", this, fWait)); + + /* Inform the worker thread that there is a message. */ + RTSemEventSignal(m_eventThread); + + LogFlow(("HGCMThread::MsgPost: event signalled\n")); + + if (fWait) + { + /* Immediately check if the message has been processed. */ + while ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0) + { + /* Poll infrequently to make sure no completed message has been missed. */ + RTSemEventMultiWait(m_eventSend, 1000); + + LogFlow(("HGCMThread::MsgPost: wait completed flags = %08X\n", pMsg->m_fu32Flags)); + + if ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0) + RTThreadYield(); + } + + /* 'Our' message has been processed, so should reset the semaphore. + * There is still possible that another message has been processed + * and the semaphore has been signalled again. + * Reset only if there are no other messages completed. + */ + int32_t c = ASMAtomicDecS32(&m_i32MessagesProcessed); + Assert(c >= 0); + if (c == 0) + RTSemEventMultiReset(m_eventSend); + + vrc = pMsg->m_rcSend; + } + } + + LogFlow(("HGCMThread::MsgPost: vrc = %Rrc\n", vrc)); + return vrc; +} + + +int HGCMThread::MsgGet(HGCMMsgCore **ppMsg) +{ + int vrc = VINF_SUCCESS; + + LogFlow(("HGCMThread::MsgGet: thread = %p, ppMsg = %p\n", this, ppMsg)); + + for (;;) + { + if (m_fu32ThreadFlags & HGCMMSG_TF_TERMINATE) + { + vrc = VERR_INTERRUPTED; + break; + } + + LogFlow(("MAIN::hgcmMsgGet: m_pMsgInputQueueHead = %p\n", m_pMsgInputQueueHead)); + + if (m_pMsgInputQueueHead) + { + /* Move the message to the m_pMsgInProcessHead list */ + vrc = Enter(); + + if (RT_FAILURE(vrc)) + { + break; + } + + HGCMMsgCore *pMsg = m_pMsgInputQueueHead; + + /* Remove the message from the head of Queue list. */ + Assert(m_pMsgInputQueueHead->m_pPrev == NULL); + + if (m_pMsgInputQueueHead->m_pNext) + { + m_pMsgInputQueueHead = m_pMsgInputQueueHead->m_pNext; + m_pMsgInputQueueHead->m_pPrev = NULL; + } + else + { + Assert(m_pMsgInputQueueHead == m_pMsgInputQueueTail); + + m_pMsgInputQueueHead = NULL; + m_pMsgInputQueueTail = NULL; + } + + /* Insert the message to the tail of the m_pMsgInProcessHead list. */ + pMsg->m_pNext = NULL; + pMsg->m_pPrev = m_pMsgInProcessTail; + + if (m_pMsgInProcessTail) + m_pMsgInProcessTail->m_pNext = pMsg; + else + m_pMsgInProcessHead = pMsg; + + m_pMsgInProcessTail = pMsg; + + pMsg->m_fu32Flags |= HGCM_MSG_F_IN_PROCESS; + + Leave(); + + /* Return the message to the caller. */ + *ppMsg = pMsg; + + LogFlow(("MAIN::hgcmMsgGet: got message %p\n", *ppMsg)); + + break; + } + + /* Wait for an event. */ + RTSemEventWait(m_eventThread, RT_INDEFINITE_WAIT); + } + + LogFlow(("HGCMThread::MsgGet: *ppMsg = %p, return vrc = %Rrc\n", *ppMsg, vrc)); + return vrc; +} + +int HGCMThread::MsgComplete(HGCMMsgCore *pMsg, int32_t result) +{ + LogFlow(("HGCMThread::MsgComplete: thread = %p, pMsg = %p, result = %Rrc (%d)\n", this, pMsg, result, result)); + + AssertRelease(pMsg->m_pThread == this); + AssertReleaseMsg((pMsg->m_fu32Flags & HGCM_MSG_F_IN_PROCESS) != 0, ("%p %x\n", pMsg, pMsg->m_fu32Flags)); + + int vrcRet = VINF_SUCCESS; + if (pMsg->m_pfnCallback) + { + /** @todo call callback with error code in MsgPost in case of errors */ + + vrcRet = pMsg->m_pfnCallback(result, pMsg); + + LogFlow(("HGCMThread::MsgComplete: callback executed. pMsg = %p, thread = %p, rcRet = %Rrc\n", pMsg, this, vrcRet)); + } + + /* Message processing has been completed. */ + + int vrc = Enter(); + + if (RT_SUCCESS(vrc)) + { + /* Remove the message from the InProcess queue. */ + + if (pMsg->m_pNext) + pMsg->m_pNext->m_pPrev = pMsg->m_pPrev; + else + m_pMsgInProcessTail = pMsg->m_pPrev; + + if (pMsg->m_pPrev) + pMsg->m_pPrev->m_pNext = pMsg->m_pNext; + else + m_pMsgInProcessHead = pMsg->m_pNext; + + pMsg->m_pNext = NULL; + pMsg->m_pPrev = NULL; + + bool fWaited = ((pMsg->m_fu32Flags & HGCM_MSG_F_WAIT) != 0); + + if (fWaited) + { + ASMAtomicIncS32(&m_i32MessagesProcessed); + + /* This should be done before setting the HGCM_MSG_F_PROCESSED flag. */ + pMsg->m_rcSend = result; + } + + /* The message is now completed. */ + pMsg->m_fu32Flags &= ~HGCM_MSG_F_IN_PROCESS; + pMsg->m_fu32Flags &= ~HGCM_MSG_F_WAIT; + pMsg->m_fu32Flags |= HGCM_MSG_F_PROCESSED; + + pMsg->Dereference(); + + Leave(); + + if (fWaited) + { + /* Wake up all waiters. so they can decide if their message has been processed. */ + RTSemEventMultiSignal(m_eventSend); + } + } + + return vrcRet; +} + +/* + * Thread API. Public interface. + */ + +int hgcmThreadCreate(HGCMThread **ppThread, const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser, + const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM) +{ + LogFlow(("MAIN::hgcmThreadCreate\n")); + int vrc; + + /* Allocate memory for a new thread object. */ + HGCMThread *pThread = new (std::nothrow) HGCMThread(); + + if (pThread) + { + pThread->Reference(); /* (it's created with zero references) */ + + /* Initialize the object. */ + vrc = pThread->Initialize(pszThreadName, pfnThread, pvUser, pszStatsSubDir, pUVM, pVMM); + if (RT_SUCCESS(vrc)) + { + *ppThread = pThread; + LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc)); + return vrc; + } + + Log(("hgcmThreadCreate: FAILURE: Initialize failed: vrc = %Rrc\n", vrc)); + + pThread->Dereference(); + } + else + { + Log(("hgcmThreadCreate: FAILURE: Can't allocate memory for a hgcm worker thread.\n")); + vrc = VERR_NO_MEMORY; + } + *ppThread = NULL; + + LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc)); + return vrc; +} + +int hgcmThreadWait(HGCMThread *pThread) +{ + LogFlowFunc(("%p\n", pThread)); + + int vrc; + if (pThread) + { + vrc = pThread->WaitForTermination(); + + pThread->Dereference(); + } + else + vrc = VERR_INVALID_HANDLE; + + LogFlowFunc(("vrc = %Rrc\n", vrc)); + return vrc; +} + +int hgcmMsgAlloc(HGCMThread *pThread, HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage) +{ + LogFlow(("hgcmMsgAlloc: pThread = %p, ppMsg = %p, sizeof (HGCMMsgCore) = %d\n", pThread, ppMsg, sizeof(HGCMMsgCore))); + + AssertReturn(pThread, VERR_INVALID_HANDLE); + AssertReturn(ppMsg, VERR_INVALID_PARAMETER); + + int vrc = pThread->MsgAlloc(ppMsg, u32MsgId, pfnNewMessage); + + LogFlow(("MAIN::hgcmMsgAlloc: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc)); + return vrc; +} + +DECLINLINE(int) hgcmMsgPostInternal(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait) +{ + LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait)); + Assert(pMsg); + + pMsg->Reference(); /* paranoia? */ + + int vrc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait); + + pMsg->Dereference(); + + LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, vrc = %Rrc\n", pMsg, vrc)); + return vrc; +} + +int hgcmMsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback) +{ + int vrc = hgcmMsgPostInternal(pMsg, pfnCallback, false); + + if (RT_SUCCESS(vrc)) + vrc = VINF_HGCM_ASYNC_EXECUTE; + + return vrc; +} + +int hgcmMsgSend(HGCMMsgCore *pMsg) +{ + return hgcmMsgPostInternal(pMsg, NULL, true); +} + +int hgcmMsgGet(HGCMThread *pThread, HGCMMsgCore **ppMsg) +{ + LogFlow(("MAIN::hgcmMsgGet: pThread = %p, ppMsg = %p\n", pThread, ppMsg)); + + AssertReturn(pThread, VERR_INVALID_HANDLE); + AssertReturn(ppMsg, VERR_INVALID_PARAMETER); + + pThread->Reference(); /* paranoia */ + + int vrc = pThread->MsgGet(ppMsg); + + pThread->Dereference(); + + LogFlow(("MAIN::hgcmMsgGet: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc)); + return vrc; +} + +int hgcmMsgComplete(HGCMMsgCore *pMsg, int32_t rcMsg) +{ + LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg = %Rrc (%d)\n", pMsg, rcMsg, rcMsg)); + + int vrc; + if (pMsg) + vrc = pMsg->Thread()->MsgComplete(pMsg, rcMsg); + else + vrc = VINF_SUCCESS; + + LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg =%Rrc (%d), returns vrc = %Rrc\n", pMsg, rcMsg, rcMsg, vrc)); + return vrc; +} + +int hgcmThreadInit(void) +{ + LogFlow(("MAIN::hgcmThreadInit\n")); + + /** @todo error processing. */ + + int vrc = hgcmObjInit(); + + LogFlow(("MAIN::hgcmThreadInit: vrc = %Rrc\n", vrc)); + return vrc; +} + +void hgcmThreadUninit(void) +{ + hgcmObjUninit(); +} + diff --git a/src/VBox/Main/src-client/KeyboardImpl.cpp b/src/VBox/Main/src-client/KeyboardImpl.cpp new file mode 100644 index 00000000..3682bc3f --- /dev/null +++ b/src/VBox/Main/src-client/KeyboardImpl.cpp @@ -0,0 +1,548 @@ +/* $Id: KeyboardImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_KEYBOARD +#include "LoggingNew.h" + +#include "KeyboardImpl.h" +#include "ConsoleImpl.h" + +#include "AutoCaller.h" +#include "VBoxEvents.h" + +#include <VBox/com/array.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/err.h> + +#include <iprt/cpp/utils.h> + + +// defines +//////////////////////////////////////////////////////////////////////////////// + +// globals +//////////////////////////////////////////////////////////////////////////////// + +/** + * Keyboard device capabilities bitfield. + */ +enum +{ + /** The keyboard device does not wish to receive keystrokes. */ + KEYBOARD_DEVCAP_DISABLED = 0, + /** The keyboard device does wishes to receive keystrokes. */ + KEYBOARD_DEVCAP_ENABLED = 1 +}; + +/** + * Keyboard driver instance data. + */ +typedef struct DRVMAINKEYBOARD +{ + /** Pointer to the keyboard object. */ + Keyboard *pKeyboard; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the keyboard port interface of the driver/device above us. */ + PPDMIKEYBOARDPORT pUpPort; + /** Our keyboard connector interface. */ + PDMIKEYBOARDCONNECTOR IConnector; + /** The capabilities of this device. */ + uint32_t u32DevCaps; +} DRVMAINKEYBOARD, *PDRVMAINKEYBOARD; + + +// constructor / destructor +//////////////////////////////////////////////////////////////////////////////// + +Keyboard::Keyboard() + : mParent(NULL) +{ +} + +Keyboard::~Keyboard() +{ +} + +HRESULT Keyboard::FinalConstruct() +{ + RT_ZERO(mpDrv); + menmLeds = PDMKEYBLEDS_NONE; + return BaseFinalConstruct(); +} + +void Keyboard::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public methods +//////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the keyboard object. + * + * @returns COM result indicator + * @param aParent handle of our parent object + */ +HRESULT Keyboard::init(Console *aParent) +{ + LogFlowThisFunc(("aParent=%p\n", aParent)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + unconst(mEventSource).createObject(); + HRESULT hrc = mEventSource->init(); + AssertComRCReturnRC(hrc); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void Keyboard::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + for (unsigned i = 0; i < KEYBOARD_MAX_DEVICES; ++i) + { + if (mpDrv[i]) + mpDrv[i]->pKeyboard = NULL; + mpDrv[i] = NULL; + } + + menmLeds = PDMKEYBLEDS_NONE; + + unconst(mParent) = NULL; + unconst(mEventSource).setNull(); +} + +/** + * Sends a scancode to the keyboard. + * + * @returns COM status code + * @param aScancode The scancode to send + */ +HRESULT Keyboard::putScancode(LONG aScancode) +{ + std::vector<LONG> scancodes; + scancodes.resize(1); + scancodes[0] = aScancode; + return putScancodes(scancodes, NULL); +} + +/** + * Sends a list of scancodes to the keyboard. + * + * @returns COM status code + * @param aScancodes Pointer to the first scancode + * @param aCodesStored Address of variable to store the number + * of scancodes that were sent to the keyboard. + This value can be NULL. + */ +HRESULT Keyboard::putScancodes(const std::vector<LONG> &aScancodes, + ULONG *aCodesStored) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_CONSOLE_DRV(mpDrv[0]); + + /* Send input to the last enabled device. Relies on the fact that + * the USB keyboard is always initialized after the PS/2 keyboard. + */ + PPDMIKEYBOARDPORT pUpPort = NULL; + for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i) + { + if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED)) + { + pUpPort = mpDrv[i]->pUpPort; + break; + } + } + + /* No enabled keyboard - throw the input away. */ + if (!pUpPort) + { + if (aCodesStored) + *aCodesStored = (uint32_t)aScancodes.size(); + return S_OK; + } + + int vrc = VINF_SUCCESS; + + uint32_t sent; + for (sent = 0; (sent < aScancodes.size()) && RT_SUCCESS(vrc); ++sent) + vrc = pUpPort->pfnPutEventScan(pUpPort, (uint8_t)aScancodes[sent]); + + if (aCodesStored) + *aCodesStored = sent; + + com::SafeArray<LONG> keys(aScancodes.size()); + for (size_t i = 0; i < aScancodes.size(); ++i) + keys[i] = aScancodes[i]; + + ::FireGuestKeyboardEvent(mEventSource, ComSafeArrayAsInParam(keys)); + + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send all scan codes to the virtual keyboard (%Rrc)"), + vrc); + + return S_OK; +} + +/** + * Sends a HID usage code and page to the keyboard. + * + * @returns COM status code + * @param aUsageCode The HID usage code to send + * @param aUsagePage The HID usage page corresponding to the code + * @param fKeyRelease The key release flag + */ +HRESULT Keyboard::putUsageCode(LONG aUsageCode, LONG aUsagePage, BOOL fKeyRelease) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_CONSOLE_DRV(mpDrv[0]); + + /* Send input to the last enabled device. Relies on the fact that + * the USB keyboard is always initialized after the PS/2 keyboard. + */ + PPDMIKEYBOARDPORT pUpPort = NULL; + for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i) + { + if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED)) + { + pUpPort = mpDrv[i]->pUpPort; + break; + } + } + + /* No enabled keyboard - throw the input away. */ + if (!pUpPort) + return S_OK; + + uint32_t idUsage = (uint16_t)aUsageCode | ((uint32_t)(uint8_t)aUsagePage << 16) | (fKeyRelease ? UINT32_C(0x80000000) : 0); + int vrc = pUpPort->pfnPutEventHid(pUpPort, idUsage); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send usage code to the virtual keyboard (%Rrc)"), + vrc); + + return S_OK; +} + +/** + * Sends Control-Alt-Delete to the keyboard. This could be done otherwise + * but it's so common that we'll be nice and supply a convenience API. + * + * @returns COM status code + * + */ +HRESULT Keyboard::putCAD() +{ + std::vector<LONG> cadSequence; + cadSequence.resize(8); + + cadSequence[0] = 0x1d; // Ctrl down + cadSequence[1] = 0x38; // Alt down + cadSequence[2] = 0xe0; // Del down 1 + cadSequence[3] = 0x53; // Del down 2 + cadSequence[4] = 0xe0; // Del up 1 + cadSequence[5] = 0xd3; // Del up 2 + cadSequence[6] = 0xb8; // Alt up + cadSequence[7] = 0x9d; // Ctrl up + + return putScancodes(cadSequence, NULL); +} + +/** + * Releases all currently held keys in the virtual keyboard. + * + * @returns COM status code + * + */ +HRESULT Keyboard::releaseKeys() +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Release all keys on the active keyboard in order to start with a clean slate. + * Note that this should mirror the logic in Keyboard::putScancodes() when choosing + * which keyboard to send the release event to. + */ + PPDMIKEYBOARDPORT pUpPort = NULL; + for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i) + { + if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED)) + { + pUpPort = mpDrv[i]->pUpPort; + break; + } + } + + if (pUpPort) + { + int vrc = pUpPort->pfnReleaseKeys(pUpPort); + if (RT_FAILURE(vrc)) + AssertMsgFailed(("Failed to release keys on all keyboards! vrc=%Rrc\n", vrc)); + } + + return S_OK; +} + +HRESULT Keyboard::getKeyboardLEDs(std::vector<KeyboardLED_T> &aKeyboardLEDs) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aKeyboardLEDs.resize(0); + + if (menmLeds & PDMKEYBLEDS_NUMLOCK) aKeyboardLEDs.push_back(KeyboardLED_NumLock); + if (menmLeds & PDMKEYBLEDS_CAPSLOCK) aKeyboardLEDs.push_back(KeyboardLED_CapsLock); + if (menmLeds & PDMKEYBLEDS_SCROLLLOCK) aKeyboardLEDs.push_back(KeyboardLED_ScrollLock); + + return S_OK; +} + +HRESULT Keyboard::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + // No need to lock - lifetime constant + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + + return S_OK; +} + +// +// private methods +// +void Keyboard::onKeyboardLedsChange(PDMKEYBLEDS enmLeds) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Save the current status. */ + menmLeds = enmLeds; + + alock.release(); + + i_getParent()->i_onKeyboardLedsChange(RT_BOOL(enmLeds & PDMKEYBLEDS_NUMLOCK), + RT_BOOL(enmLeds & PDMKEYBLEDS_CAPSLOCK), + RT_BOOL(enmLeds & PDMKEYBLEDS_SCROLLLOCK)); +} + +DECLCALLBACK(void) Keyboard::i_keyboardLedStatusChange(PPDMIKEYBOARDCONNECTOR pInterface, PDMKEYBLEDS enmLeds) +{ + PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector); + pDrv->pKeyboard->onKeyboardLedsChange(enmLeds); +} + +/** + * @interface_method_impl{PDMIKEYBOARDCONNECTOR,pfnSetActive} + */ +DECLCALLBACK(void) Keyboard::i_keyboardSetActive(PPDMIKEYBOARDCONNECTOR pInterface, bool fActive) +{ + PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector); + + // Before activating a different keyboard, release all keys on the currently active one. + if (fActive) + pDrv->pKeyboard->releaseKeys(); + + if (fActive) + pDrv->u32DevCaps |= KEYBOARD_DEVCAP_ENABLED; + else + pDrv->u32DevCaps &= ~KEYBOARD_DEVCAP_ENABLED; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) Keyboard::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINKEYBOARD pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDCONNECTOR, &pDrv->IConnector); + return NULL; +} + + +/** + * Destruct a keyboard driver instance. + * + * @returns VBox status code. + * @param pDrvIns The driver instance data. + */ +DECLCALLBACK(void) Keyboard::i_drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD); + LogFlow(("Keyboard::drvDestruct: iInstance=%d\n", pDrvIns->iInstance)); + + if (pThis->pKeyboard) + { + AutoWriteLock kbdLock(pThis->pKeyboard COMMA_LOCKVAL_SRC_POS); + for (unsigned cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev) + if (pThis->pKeyboard->mpDrv[cDev] == pThis) + { + pThis->pKeyboard->mpDrv[cDev] = NULL; + break; + } + } +} + +/** + * Construct a keyboard driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +DECLCALLBACK(int) Keyboard::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + RT_NOREF(fFlags, pCfg); + PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD); + LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * IBase. + */ + pDrvIns->IBase.pfnQueryInterface = Keyboard::i_drvQueryInterface; + + pThis->IConnector.pfnLedStatusChange = i_keyboardLedStatusChange; + pThis->IConnector.pfnSetActive = Keyboard::i_keyboardSetActive; + + /* + * Get the IKeyboardPort interface of the above driver/device. + */ + pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIKEYBOARDPORT); + if (!pThis->pUpPort) + { + AssertMsgFailed(("Configuration error: No keyboard port interface above!\n")); + return VERR_PDM_MISSING_INTERFACE_ABOVE; + } + + /* + * Get the Keyboard object pointer and update the mpDrv member. + */ + com::Guid uuid(COM_IIDOF(IKeyboard)); + IKeyboard *pIKeyboard = (IKeyboard *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw()); + if (!pIKeyboard) + { + AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n")); + return VERR_NOT_FOUND; + } + pThis->pKeyboard = static_cast<Keyboard *>(pIKeyboard); + + unsigned cDev; + for (cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev) + if (!pThis->pKeyboard->mpDrv[cDev]) + { + pThis->pKeyboard->mpDrv[cDev] = pThis; + break; + } + if (cDev == KEYBOARD_MAX_DEVICES) + return VERR_NO_MORE_HANDLES; + + return VINF_SUCCESS; +} + + +/** + * Keyboard driver registration record. + */ +const PDMDRVREG Keyboard::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "MainKeyboard", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main keyboard driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_KEYBOARD, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINKEYBOARD), + /* pfnConstruct */ + Keyboard::i_drvConstruct, + /* pfnDestruct */ + Keyboard::i_drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/MachineDebuggerImpl.cpp b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp new file mode 100644 index 00000000..c4646646 --- /dev/null +++ b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp @@ -0,0 +1,1560 @@ +/* $Id: MachineDebuggerImpl.cpp $ */ +/** @file + * VBox IMachineDebugger COM class implementation (VBoxC). + */ + +/* + * Copyright (C) 2006-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_MACHINEDEBUGGER +#include "LoggingNew.h" + +#include "MachineDebuggerImpl.h" + +#include "Global.h" +#include "ConsoleImpl.h" +#include "ProgressImpl.h" + +#include "AutoCaller.h" + +#include <VBox/vmm/vmmr3vtable.h> +#include <VBox/vmm/em.h> +#include <VBox/vmm/uvm.h> +#include <VBox/vmm/tm.h> +#include <VBox/vmm/hm.h> +#include <VBox/err.h> +#include <iprt/cpp/utils.h> + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +MachineDebugger::MachineDebugger() + : mParent(NULL) +{ +} + +MachineDebugger::~MachineDebugger() +{ +} + +HRESULT MachineDebugger::FinalConstruct() +{ + unconst(mParent) = NULL; + return BaseFinalConstruct(); +} + +void MachineDebugger::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the machine debugger object. + * + * @returns COM result indicator + * @param aParent handle of our parent object + */ +HRESULT MachineDebugger::init(Console *aParent) +{ + LogFlowThisFunc(("aParent=%p\n", aParent)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = aParent; + + for (unsigned i = 0; i < RT_ELEMENTS(maiQueuedEmExecPolicyParams); i++) + maiQueuedEmExecPolicyParams[i] = UINT8_MAX; + mSingleStepQueued = -1; + mLogEnabledQueued = -1; + mVirtualTimeRateQueued = UINT32_MAX; + mFlushMode = false; + + m_hSampleReport = NULL; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void MachineDebugger::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unconst(mParent) = NULL; + mFlushMode = false; +} + +/** + * @callback_method_impl{FNDBGFPROGRESS} + */ +/*static*/ DECLCALLBACK(int) MachineDebugger::i_dbgfProgressCallback(void *pvUser, unsigned uPercentage) +{ + MachineDebugger *pThis = (MachineDebugger *)pvUser; + + int vrc = pThis->m_Progress->i_iprtProgressCallback(uPercentage, static_cast<Progress *>(pThis->m_Progress)); + if ( RT_SUCCESS(vrc) + && uPercentage == 100) + { + PCVMMR3VTABLE const pVMM = pThis->mParent->i_getVMMVTable(); + AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3); + + vrc = pVMM->pfnDBGFR3SampleReportDumpToFile(pThis->m_hSampleReport, pThis->m_strFilename.c_str()); + pVMM->pfnDBGFR3SampleReportRelease(pThis->m_hSampleReport); + pThis->m_hSampleReport = NULL; + if (RT_SUCCESS(vrc)) + pThis->m_Progress->i_notifyComplete(S_OK); + else + { + HRESULT hrc = pThis->setError(VBOX_E_IPRT_ERROR, + tr("Writing the sample report to '%s' failed with %Rrc"), + pThis->m_strFilename.c_str(), vrc); + pThis->m_Progress->i_notifyComplete(hrc); + } + pThis->m_Progress.setNull(); + } + else if (vrc == VERR_CANCELLED) + vrc = VERR_DBGF_CANCELLED; + + return vrc; +} + +// IMachineDebugger properties +///////////////////////////////////////////////////////////////////////////// + +/** + * Returns the current singlestepping flag. + * + * @returns COM status code + * @param aSingleStep Where to store the result. + */ +HRESULT MachineDebugger::getSingleStep(BOOL *aSingleStep) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + RT_NOREF(aSingleStep); /** @todo */ + ReturnComNotImplemented(); + } + return hrc; +} + +/** + * Sets the singlestepping flag. + * + * @returns COM status code + * @param aSingleStep The new state. + */ +HRESULT MachineDebugger::setSingleStep(BOOL aSingleStep) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + NOREF(aSingleStep); /** @todo */ + ReturnComNotImplemented(); + } + return hrc; +} + +/** + * Internal worker for getting an EM executable policy setting. + * + * @returns COM status code. + * @param enmPolicy Which EM policy. + * @param pfEnforced Where to return the policy setting. + */ +HRESULT MachineDebugger::i_getEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL *pfEnforced) +{ + CheckComArgOutPointerValid(pfEnforced); + + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (i_queueSettings()) + *pfEnforced = maiQueuedEmExecPolicyParams[enmPolicy] == 1; + else + { + bool fEnforced = false; + Console::SafeVMPtrQuiet ptrVM(mParent); + hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + ptrVM.vtable()->pfnEMR3QueryExecutionPolicy(ptrVM.rawUVM(), enmPolicy, &fEnforced); + *pfEnforced = fEnforced; + } + } + return hrc; +} + +/** + * Internal worker for setting an EM executable policy. + * + * @returns COM status code. + * @param enmPolicy Which policy to change. + * @param fEnforce Whether to enforce the policy or not. + */ +HRESULT MachineDebugger::i_setEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL fEnforce) +{ + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (i_queueSettings()) + maiQueuedEmExecPolicyParams[enmPolicy] = fEnforce ? 1 : 0; + else + { + Console::SafeVMPtrQuiet ptrVM(mParent); + hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnEMR3SetExecutionPolicy(ptrVM.rawUVM(), enmPolicy, fEnforce != FALSE); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("EMR3SetExecutionPolicy failed with %Rrc"), vrc); + } + } + } + return hrc; +} + +/** + * Returns the current execute-all-in-IEM setting. + * + * @returns COM status code + * @param aExecuteAllInIEM Address of result variable. + */ +HRESULT MachineDebugger::getExecuteAllInIEM(BOOL *aExecuteAllInIEM) +{ + return i_getEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM); +} + +/** + * Changes the execute-all-in-IEM setting. + * + * @returns COM status code + * @param aExecuteAllInIEM New setting. + */ +HRESULT MachineDebugger::setExecuteAllInIEM(BOOL aExecuteAllInIEM) +{ + LogFlowThisFunc(("enable=%d\n", aExecuteAllInIEM)); + return i_setEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM); +} + +/** + * Returns the log enabled / disabled status. + * + * @returns COM status code + * @param aLogEnabled address of result variable + */ +HRESULT MachineDebugger::getLogEnabled(BOOL *aLogEnabled) +{ +#ifdef LOG_ENABLED + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + const PRTLOGGER pLogInstance = RTLogDefaultInstance(); + *aLogEnabled = pLogInstance && !(RTLogGetFlags(pLogInstance) & RTLOGFLAGS_DISABLED); +#else + *aLogEnabled = false; +#endif + + return S_OK; +} + +/** + * Enables or disables logging. + * + * @returns COM status code + * @param aLogEnabled The new code log state. + */ +HRESULT MachineDebugger::setLogEnabled(BOOL aLogEnabled) +{ + LogFlowThisFunc(("aLogEnabled=%d\n", aLogEnabled)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (i_queueSettings()) + { + // queue the request + mLogEnabledQueued = aLogEnabled; + return S_OK; + } + + Console::SafeVMPtr ptrVM(mParent); + if (FAILED(ptrVM.rc())) return ptrVM.rc(); + +#ifdef LOG_ENABLED + int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aLogEnabled ? "enabled" : "disabled"); + if (RT_FAILURE(vrc)) + { + /** @todo handle error code. */ + } +#endif + + return S_OK; +} + +HRESULT MachineDebugger::i_logStringProps(PRTLOGGER pLogger, PFNLOGGETSTR pfnLogGetStr, + const char *pszLogGetStr, Utf8Str *pstrSettings) +{ + /* Make sure the VM is powered up. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (FAILED(hrc)) + return hrc; + + /* Make sure we've got a logger. */ + if (!pLogger) + { + *pstrSettings = ""; + return S_OK; + } + + /* Do the job. */ + size_t cbBuf = _1K; + for (;;) + { + char *pszBuf = (char *)RTMemTmpAlloc(cbBuf); + AssertReturn(pszBuf, E_OUTOFMEMORY); + int vrc = pstrSettings->reserveNoThrow(cbBuf); + if (RT_SUCCESS(vrc)) + { + vrc = pfnLogGetStr(pLogger, pstrSettings->mutableRaw(), cbBuf); + if (RT_SUCCESS(vrc)) + { + pstrSettings->jolt(); + return S_OK; + } + *pstrSettings = ""; + AssertReturn(vrc == VERR_BUFFER_OVERFLOW, + setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("%s returned %Rrc"), pszLogGetStr, vrc)); + } + else + return E_OUTOFMEMORY; + + /* try again with a bigger buffer. */ + cbBuf *= 2; + AssertReturn(cbBuf <= _256K, setError(E_FAIL, tr("%s returns too much data"), pszLogGetStr)); + } +} + +HRESULT MachineDebugger::getLogDbgFlags(com::Utf8Str &aLogDbgFlags) +{ + return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogDbgFlags); +} + +HRESULT MachineDebugger::getLogDbgGroups(com::Utf8Str &aLogDbgGroups) +{ + return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogDbgGroups); +} + +HRESULT MachineDebugger::getLogDbgDestinations(com::Utf8Str &aLogDbgDestinations) +{ + return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogDbgDestinations); +} + +HRESULT MachineDebugger::getLogRelFlags(com::Utf8Str &aLogRelFlags) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogRelFlags); +} + +HRESULT MachineDebugger::getLogRelGroups(com::Utf8Str &aLogRelGroups) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogRelGroups); +} + +HRESULT MachineDebugger::getLogRelDestinations(com::Utf8Str &aLogRelDestinations) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogRelDestinations); +} + +/** + * Return the main execution engine of the VM. + * + * @returns COM status code + * @param apenmEngine Address of the result variable. + */ +HRESULT MachineDebugger::getExecutionEngine(VMExecutionEngine_T *apenmEngine) +{ + *apenmEngine = VMExecutionEngine_NotSet; + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + uint8_t bEngine = UINT8_MAX; + int vrc = ptrVM.vtable()->pfnEMR3QueryMainExecutionEngine(ptrVM.rawUVM(), &bEngine); + if (RT_SUCCESS(vrc)) + switch (bEngine) + { + case VM_EXEC_ENGINE_NOT_SET: *apenmEngine = VMExecutionEngine_NotSet; break; + case VM_EXEC_ENGINE_IEM: *apenmEngine = VMExecutionEngine_Emulated; break; + case VM_EXEC_ENGINE_HW_VIRT: *apenmEngine = VMExecutionEngine_HwVirt; break; + case VM_EXEC_ENGINE_NATIVE_API: *apenmEngine = VMExecutionEngine_NativeApi; break; + default: AssertMsgFailed(("bEngine=%d\n", bEngine)); + } + } + + return S_OK; +} + +/** + * Returns the current nested paging flag. + * + * @returns COM status code + * @param aHWVirtExNestedPagingEnabled address of result variable + */ +HRESULT MachineDebugger::getHWVirtExNestedPagingEnabled(BOOL *aHWVirtExNestedPagingEnabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + *aHWVirtExNestedPagingEnabled = ptrVM.vtable()->pfnHMR3IsNestedPagingActive(ptrVM.rawUVM()); + else + *aHWVirtExNestedPagingEnabled = false; + + return S_OK; +} + +/** + * Returns the current VPID flag. + * + * @returns COM status code + * @param aHWVirtExVPIDEnabled address of result variable + */ +HRESULT MachineDebugger::getHWVirtExVPIDEnabled(BOOL *aHWVirtExVPIDEnabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + *aHWVirtExVPIDEnabled = ptrVM.vtable()->pfnHMR3IsVpidActive(ptrVM.rawUVM()); + else + *aHWVirtExVPIDEnabled = false; + + return S_OK; +} + +/** + * Returns the current unrestricted execution setting. + * + * @returns COM status code + * @param aHWVirtExUXEnabled address of result variable + */ +HRESULT MachineDebugger::getHWVirtExUXEnabled(BOOL *aHWVirtExUXEnabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + *aHWVirtExUXEnabled = ptrVM.vtable()->pfnHMR3IsUXActive(ptrVM.rawUVM()); + else + *aHWVirtExUXEnabled = false; + + return S_OK; +} + +HRESULT MachineDebugger::getOSName(com::Utf8Str &aOSName) +{ + LogFlowThisFunc(("\n")); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Do the job and try convert the name. + */ + char szName[64]; + int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), szName, sizeof(szName), NULL, 0); + if (RT_SUCCESS(vrc)) + hrc = aOSName.assignEx(szName); + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::getOSVersion(com::Utf8Str &aOSVersion) +{ + LogFlowThisFunc(("\n")); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Do the job and try convert the name. + */ + char szVersion[256]; + int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), NULL, 0, szVersion, sizeof(szVersion)); + if (RT_SUCCESS(vrc)) + hrc = aOSVersion.assignEx(szVersion); + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc); + } + return hrc; +} + +/** + * Returns the current PAE flag. + * + * @returns COM status code + * @param aPAEEnabled address of result variable. + */ +HRESULT MachineDebugger::getPAEEnabled(BOOL *aPAEEnabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + uint32_t cr4; + int vrc = ptrVM.vtable()->pfnDBGFR3RegCpuQueryU32(ptrVM.rawUVM(), 0 /*idCpu*/, DBGFREG_CR4, &cr4); AssertRC(vrc); + *aPAEEnabled = RT_BOOL(cr4 & X86_CR4_PAE); + } + else + *aPAEEnabled = false; + + return S_OK; +} + +/** + * Returns the current virtual time rate. + * + * @returns COM status code. + * @param aVirtualTimeRate Where to store the rate. + */ +HRESULT MachineDebugger::getVirtualTimeRate(ULONG *aVirtualTimeRate) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + *aVirtualTimeRate = ptrVM.vtable()->pfnTMR3GetWarpDrive(ptrVM.rawUVM()); + + return hrc; +} + +/** + * Set the virtual time rate. + * + * @returns COM status code. + * @param aVirtualTimeRate The new rate. + */ +HRESULT MachineDebugger::setVirtualTimeRate(ULONG aVirtualTimeRate) +{ + HRESULT hrc = S_OK; + + if (aVirtualTimeRate < 2 || aVirtualTimeRate > 20000) + return setError(E_INVALIDARG, tr("%u is out of range [2..20000]"), aVirtualTimeRate); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (i_queueSettings()) + mVirtualTimeRateQueued = aVirtualTimeRate; + else + { + Console::SafeVMPtr ptrVM(mParent); + hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnTMR3SetWarpDrive(ptrVM.rawUVM(), aVirtualTimeRate); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("TMR3SetWarpDrive(, %u) failed with rc=%Rrc"), aVirtualTimeRate, vrc); + } + } + + return hrc; +} + +/** + * Get the VM uptime in milliseconds. + * + * @returns COM status code + * @param aUptime Where to store the uptime. + */ +HRESULT MachineDebugger::getUptime(LONG64 *aUptime) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + *aUptime = (int64_t)ptrVM.vtable()->pfnTMR3TimeVirtGetMilli(ptrVM.rawUVM()); + + return hrc; +} + +// IMachineDebugger methods +///////////////////////////////////////////////////////////////////////////// + +HRESULT MachineDebugger::dumpGuestCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression) +{ + if (aCompression.length()) + return setError(E_INVALIDARG, tr("The compression parameter must be empty")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnDBGFR3CoreWrite(ptrVM.rawUVM(), aFilename.c_str(), false /*fReplaceFile*/); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3CoreWrite failed with %Rrc"), vrc); + } + + return hrc; +} + +HRESULT MachineDebugger::dumpHostProcessCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression) +{ + RT_NOREF(aFilename, aCompression); + ReturnComNotImplemented(); +} + +/** + * Debug info string buffer formatter. + */ +typedef struct MACHINEDEBUGGERINOFHLP +{ + /** The core info helper structure. */ + DBGFINFOHLP Core; + /** Pointer to the buffer. */ + char *pszBuf; + /** The size of the buffer. */ + size_t cbBuf; + /** The offset into the buffer */ + size_t offBuf; + /** Indicates an out-of-memory condition. */ + bool fOutOfMemory; +} MACHINEDEBUGGERINOFHLP; +/** Pointer to a Debug info string buffer formatter. */ +typedef MACHINEDEBUGGERINOFHLP *PMACHINEDEBUGGERINOFHLP; + + +/** + * @callback_method_impl{FNRTSTROUTPUT} + */ +static DECLCALLBACK(size_t) MachineDebuggerInfoOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + PMACHINEDEBUGGERINOFHLP pHlp = (PMACHINEDEBUGGERINOFHLP)pvArg; + + /* + * Grow the buffer if required. + */ + size_t const cbRequired = cbChars + pHlp->offBuf + 1; + if (cbRequired > pHlp->cbBuf) + { + if (RT_UNLIKELY(pHlp->fOutOfMemory)) + return 0; + + size_t cbBufNew = pHlp->cbBuf * 2; + if (cbRequired > cbBufNew) + cbBufNew = RT_ALIGN_Z(cbRequired, 256); + void *pvBufNew = RTMemRealloc(pHlp->pszBuf, cbBufNew); + if (RT_UNLIKELY(!pvBufNew)) + { + pHlp->fOutOfMemory = true; + RTMemFree(pHlp->pszBuf); + pHlp->pszBuf = NULL; + pHlp->cbBuf = 0; + pHlp->offBuf = 0; + return 0; + } + + pHlp->pszBuf = (char *)pvBufNew; + pHlp->cbBuf = cbBufNew; + } + + /* + * Copy the bytes into the buffer and terminate it. + */ + if (cbChars) + { + memcpy(&pHlp->pszBuf[pHlp->offBuf], pachChars, cbChars); + pHlp->offBuf += cbChars; + } + pHlp->pszBuf[pHlp->offBuf] = '\0'; + Assert(pHlp->offBuf < pHlp->cbBuf); + return cbChars; +} + +/** + * @interface_method_impl{DBGFINFOHLP,pfnPrintfV} + */ +static DECLCALLBACK(void) MachineDebuggerInfoPrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list args) +{ + RTStrFormatV(MachineDebuggerInfoOutput, (void *)pHlp, NULL, NULL, pszFormat, args); +} + +/** + * @interface_method_impl{DBGFINFOHLP,pfnPrintf} + */ +static DECLCALLBACK(void) MachineDebuggerInfoPrintf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + MachineDebuggerInfoPrintfV(pHlp, pszFormat, va); + va_end(va); +} + +/** + * Initializes the debug info string buffer formatter + * + * @param pHlp The help structure to init. + * @param pVMM The VMM vtable. + */ +static void MachineDebuggerInfoInit(PMACHINEDEBUGGERINOFHLP pHlp, PCVMMR3VTABLE pVMM) +{ + pHlp->Core.pfnPrintf = MachineDebuggerInfoPrintf; + pHlp->Core.pfnPrintfV = MachineDebuggerInfoPrintfV; + pHlp->Core.pfnGetOptError = pVMM->pfnDBGFR3InfoGenericGetOptError; + pHlp->pszBuf = NULL; + pHlp->cbBuf = 0; + pHlp->offBuf = 0; + pHlp->fOutOfMemory = false; +} + +/** + * Deletes the debug info string buffer formatter. + * @param pHlp The helper structure to delete. + */ +static void MachineDebuggerInfoDelete(PMACHINEDEBUGGERINOFHLP pHlp) +{ + RTMemFree(pHlp->pszBuf); + pHlp->pszBuf = NULL; +} + +HRESULT MachineDebugger::info(const com::Utf8Str &aName, const com::Utf8Str &aArgs, com::Utf8Str &aInfo) +{ + LogFlowThisFunc(("\n")); + + /* + * Do the autocaller and lock bits. + */ + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Create a helper and call DBGFR3Info. + */ + MACHINEDEBUGGERINOFHLP Hlp; + MachineDebuggerInfoInit(&Hlp, ptrVM.vtable()); + int vrc = ptrVM.vtable()->pfnDBGFR3Info(ptrVM.rawUVM(), aName.c_str(), aArgs.c_str(), &Hlp.Core); + if (RT_SUCCESS(vrc)) + { + if (!Hlp.fOutOfMemory) + hrc = aInfo.assignEx(Hlp.pszBuf); + else + hrc = E_OUTOFMEMORY; + } + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3Info failed with %Rrc"), vrc); + MachineDebuggerInfoDelete(&Hlp); + } + } + return hrc; +} + +HRESULT MachineDebugger::injectNMI() +{ + LogFlowThisFunc(("\n")); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnDBGFR3InjectNMI(ptrVM.rawUVM(), 0); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3InjectNMI failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::modifyLogFlags(const com::Utf8Str &aSettings) +{ + LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str())); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aSettings.c_str()); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyFlags failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::modifyLogGroups(const com::Utf8Str &aSettings) +{ + LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str())); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyGroups(ptrVM.rawUVM(), aSettings.c_str()); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyGroups failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::modifyLogDestinations(const com::Utf8Str &aSettings) +{ + LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str())); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyDestinations(ptrVM.rawUVM(), aSettings.c_str()); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyDestinations failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::readPhysicalMemory(LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes) +{ + RT_NOREF(aAddress, aSize, aBytes); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::writePhysicalMemory(LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes) +{ + RT_NOREF(aAddress, aSize, aBytes); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::readVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes) +{ + RT_NOREF(aCpuId, aAddress, aSize, aBytes); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::writeVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes) +{ + RT_NOREF(aCpuId, aAddress, aSize, aBytes); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::loadPlugIn(const com::Utf8Str &aName, com::Utf8Str &aPlugInName) +{ + /* + * Lock the debugger and get the VM pointer + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Do the job and try convert the name. + */ + if (aName.equals("all")) + { + ptrVM.vtable()->pfnDBGFR3PlugInLoadAll(ptrVM.rawUVM()); + hrc = aPlugInName.assignEx("all"); + } + else + { + RTERRINFOSTATIC ErrInfo; + char szName[80]; + int vrc = ptrVM.vtable()->pfnDBGFR3PlugInLoad(ptrVM.rawUVM(), aName.c_str(), szName, sizeof(szName), RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(vrc)) + hrc = aPlugInName.assignEx(szName); + else + hrc = setErrorVrc(vrc, "%s", ErrInfo.szMsg); + } + } + return hrc; + +} + +HRESULT MachineDebugger::unloadPlugIn(const com::Utf8Str &aName) +{ + /* + * Lock the debugger and get the VM pointer + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Do the job and try convert the name. + */ + if (aName.equals("all")) + { + ptrVM.vtable()->pfnDBGFR3PlugInUnloadAll(ptrVM.rawUVM()); + hrc = S_OK; + } + else + { + int vrc = ptrVM.vtable()->pfnDBGFR3PlugInUnload(ptrVM.rawUVM(), aName.c_str()); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else if (vrc == VERR_NOT_FOUND) + hrc = setErrorBoth(E_FAIL, vrc, tr("Plug-in '%s' was not found"), aName.c_str()); + else + hrc = setErrorVrc(vrc, tr("Error unloading '%s': %Rrc"), aName.c_str(), vrc); + } + } + return hrc; + +} + +HRESULT MachineDebugger::detectOS(com::Utf8Str &aOs) +{ + LogFlowThisFunc(("\n")); + + /* + * Lock the debugger and get the VM pointer + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Do the job. + */ + char szName[64]; + int vrc = ptrVM.vtable()->pfnDBGFR3OSDetect(ptrVM.rawUVM(), szName, sizeof(szName)); + if (RT_SUCCESS(vrc) && vrc != VINF_DBGF_OS_NOT_DETCTED) + hrc = aOs.assignEx(szName); + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSDetect failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::queryOSKernelLog(ULONG aMaxMessages, com::Utf8Str &aDmesg) +{ + /* + * Lock the debugger and get the VM pointer + */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + PDBGFOSIDMESG pDmesg = (PDBGFOSIDMESG)ptrVM.vtable()->pfnDBGFR3OSQueryInterface(ptrVM.rawUVM(), DBGFOSINTERFACE_DMESG); + if (pDmesg) + { + size_t cbActual; + size_t cbBuf = _512K; + int vrc = aDmesg.reserveNoThrow(cbBuf); + if (RT_SUCCESS(vrc)) + { + uint32_t cMessages = aMaxMessages == 0 ? UINT32_MAX : aMaxMessages; + vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages, + aDmesg.mutableRaw(), cbBuf, &cbActual); + + uint32_t cTries = 10; + while (vrc == VERR_BUFFER_OVERFLOW && cbBuf < 16*_1M && cTries-- > 0) + { + cbBuf = RT_ALIGN_Z(cbActual + _4K, _4K); + vrc = aDmesg.reserveNoThrow(cbBuf); + if (RT_SUCCESS(vrc)) + vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages, + aDmesg.mutableRaw(), cbBuf, &cbActual); + } + if (RT_SUCCESS(vrc)) + aDmesg.jolt(); + else if (vrc == VERR_BUFFER_OVERFLOW) + hrc = setError(E_FAIL, tr("Too much log available, must use the maxMessages parameter to restrict.")); + else + hrc = setErrorVrc(vrc); + } + else + hrc = setErrorBoth(E_OUTOFMEMORY, vrc); + } + else + hrc = setError(E_FAIL, tr("The dmesg interface isn't implemented by guest OS digger, or detectOS() has not been called.")); + } + return hrc; +} + +HRESULT MachineDebugger::getRegister(ULONG aCpuId, const com::Utf8Str &aName, com::Utf8Str &aValue) +{ + /* + * The prologue. + */ + LogFlowThisFunc(("\n")); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Real work. + */ + DBGFREGVAL Value; + DBGFREGVALTYPE enmType; + int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQuery(ptrVM.rawUVM(), aCpuId, aName.c_str(), &Value, &enmType); + if (RT_SUCCESS(vrc)) + { + char szHex[160]; + ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &Value, enmType, true /*fSpecial*/); + if (cch > 0) + hrc = aValue.assignEx(szHex); + else + hrc = E_UNEXPECTED; + } + else if (vrc == VERR_DBGF_REGISTER_NOT_FOUND) + hrc = setErrorBoth(E_FAIL, vrc, tr("Register '%s' was not found"), aName.c_str()); + else if (vrc == VERR_INVALID_CPU_ID) + hrc = setErrorBoth(E_FAIL, vrc, tr("Invalid CPU ID: %u"), aCpuId); + else + hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, + tr("DBGFR3RegNmQuery failed with rc=%Rrc querying register '%s' with default cpu set to %u"), + vrc, aName.c_str(), aCpuId); + } + + return hrc; +} + +HRESULT MachineDebugger::getRegisters(ULONG aCpuId, std::vector<com::Utf8Str> &aNames, std::vector<com::Utf8Str> &aValues) +{ + RT_NOREF(aCpuId); /** @todo fix missing aCpuId usage! */ + + /* + * The prologue. + */ + LogFlowThisFunc(("\n")); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * Real work. + */ + size_t cRegs; + int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAllCount(ptrVM.rawUVM(), &cRegs); + if (RT_SUCCESS(vrc)) + { + PDBGFREGENTRYNM paRegs = (PDBGFREGENTRYNM)RTMemAllocZ(sizeof(paRegs[0]) * cRegs); + if (paRegs) + { + vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAll(ptrVM.rawUVM(), paRegs, cRegs); + if (RT_SUCCESS(vrc)) + { + try + { + aValues.resize(cRegs); + aNames.resize(cRegs); + for (uint32_t iReg = 0; iReg < cRegs; iReg++) + { + char szHex[160]; + szHex[159] = szHex[0] = '\0'; + ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &paRegs[iReg].Val, + paRegs[iReg].enmType, true /*fSpecial*/); + Assert(cch > 0); NOREF(cch); + aNames[iReg] = paRegs[iReg].pszName; + aValues[iReg] = szHex; + } + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAll failed with %Rrc"), vrc); + + RTMemFree(paRegs); + } + else + hrc = E_OUTOFMEMORY; + } + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAllCount failed with %Rrc"), vrc); + } + return hrc; +} + +HRESULT MachineDebugger::setRegister(ULONG aCpuId, const com::Utf8Str &aName, const com::Utf8Str &aValue) +{ + RT_NOREF(aCpuId, aName, aValue); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::setRegisters(ULONG aCpuId, const std::vector<com::Utf8Str> &aNames, + const std::vector<com::Utf8Str> &aValues) +{ + RT_NOREF(aCpuId, aNames, aValues); + ReturnComNotImplemented(); +} + +HRESULT MachineDebugger::dumpGuestStack(ULONG aCpuId, com::Utf8Str &aStack) +{ + /* + * The prologue. + */ + LogFlowThisFunc(("\n")); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + /* + * There is currently a problem with the windows diggers and SMP, where + * guest driver memory is being read from CPU zero in order to ensure that + * we've got a consisten virtual memory view. If one of the other CPUs + * initiates a rendezvous while we're unwinding the stack and trying to + * read guest driver memory, we will deadlock. + * + * So, check the VM state and maybe suspend the VM before we continue. + */ + int vrc = VINF_SUCCESS; + bool fPaused = false; + if (aCpuId != 0) + { + VMSTATE enmVmState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM()); + if ( enmVmState == VMSTATE_RUNNING + || enmVmState == VMSTATE_RUNNING_LS) + { + alock.release(); + vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), VMSUSPENDREASON_USER); + alock.acquire(); + fPaused = RT_SUCCESS(vrc); + } + } + if (RT_SUCCESS(vrc)) + { + PCDBGFSTACKFRAME pFirstFrame; + vrc = ptrVM.vtable()->pfnDBGFR3StackWalkBegin(ptrVM.rawUVM(), aCpuId, DBGFCODETYPE_GUEST, &pFirstFrame); + if (RT_SUCCESS(vrc)) + { + /* + * Print header. + */ + try + { + uint32_t fBitFlags = 0; + for (PCDBGFSTACKFRAME pFrame = pFirstFrame; + pFrame; + pFrame = ptrVM.vtable()->pfnDBGFR3StackWalkNext(pFrame)) + { + uint32_t const fCurBitFlags = pFrame->fFlags & (DBGFSTACKFRAME_FLAGS_16BIT | DBGFSTACKFRAME_FLAGS_32BIT | DBGFSTACKFRAME_FLAGS_64BIT); + if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_16BIT) + { + if (fCurBitFlags != fBitFlags) + aStack.append("SS:BP Ret SS:BP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n"); + aStack.appendPrintf("%04RX16:%04RX16 %04RX16:%04RX16 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32", + pFrame->AddrFrame.Sel, + (uint16_t)pFrame->AddrFrame.off, + pFrame->AddrReturnFrame.Sel, + (uint16_t)pFrame->AddrReturnFrame.off, + (uint32_t)pFrame->AddrReturnPC.Sel, + (uint32_t)pFrame->AddrReturnPC.off, + pFrame->Args.au32[0], + pFrame->Args.au32[1], + pFrame->Args.au32[2], + pFrame->Args.au32[3]); + } + else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT) + { + if (fCurBitFlags != fBitFlags) + aStack.append("EBP Ret EBP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n"); + aStack.appendPrintf("%08RX32 %08RX32 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32", + (uint32_t)pFrame->AddrFrame.off, + (uint32_t)pFrame->AddrReturnFrame.off, + (uint32_t)pFrame->AddrReturnPC.Sel, + (uint32_t)pFrame->AddrReturnPC.off, + pFrame->Args.au32[0], + pFrame->Args.au32[1], + pFrame->Args.au32[2], + pFrame->Args.au32[3]); + } + else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT) + { + if (fCurBitFlags != fBitFlags) + aStack.append("RBP Ret SS:RBP Ret RIP CS:RIP / Symbol [line]\n"); + aStack.appendPrintf("%016RX64 %04RX16:%016RX64 %016RX64", + (uint64_t)pFrame->AddrFrame.off, + pFrame->AddrReturnFrame.Sel, + (uint64_t)pFrame->AddrReturnFrame.off, + (uint64_t)pFrame->AddrReturnPC.off); + } + + if (!pFrame->pSymPC) + aStack.appendPrintf(fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT + ? " %RTsel:%016RGv" + : fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT + ? " %RTsel:%08RGv" + : " %RTsel:%04RGv" + , pFrame->AddrPC.Sel, pFrame->AddrPC.off); + else + { + RTGCINTPTR offDisp = pFrame->AddrPC.FlatPtr - pFrame->pSymPC->Value; /** @todo this isn't 100% correct for segmented stuff. */ + if (offDisp > 0) + aStack.appendPrintf(" %s+%llx", pFrame->pSymPC->szName, (int64_t)offDisp); + else if (offDisp < 0) + aStack.appendPrintf(" %s-%llx", pFrame->pSymPC->szName, -(int64_t)offDisp); + else + aStack.appendPrintf(" %s", pFrame->pSymPC->szName); + } + if (pFrame->pLinePC) + aStack.appendPrintf(" [%s @ 0i%d]", pFrame->pLinePC->szFilename, pFrame->pLinePC->uLineNo); + aStack.append("\n"); + + fBitFlags = fCurBitFlags; + } + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + + ptrVM.vtable()->pfnDBGFR3StackWalkEnd(pFirstFrame); + } + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3StackWalkBegin failed with %Rrc"), vrc); + + /* + * Resume the VM if we suspended it. + */ + if (fPaused) + { + alock.release(); + ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_USER); + } + } + else + hrc = setErrorBoth(E_FAIL, vrc, tr("Suspending the VM failed with %Rrc\n"), vrc); + } + + return hrc; +} + +/** + * Resets VM statistics. + * + * @returns COM status code. + * @param aPattern The selection pattern. A bit similar to filename globbing. + */ +HRESULT MachineDebugger::resetStats(const com::Utf8Str &aPattern) +{ + Console::SafeVMPtrQuiet ptrVM(mParent); + if (!ptrVM.isOk()) + return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running")); + + ptrVM.vtable()->pfnSTAMR3Reset(ptrVM.rawUVM(), aPattern.c_str()); + + return S_OK; +} + +/** + * Dumps VM statistics to the log. + * + * @returns COM status code. + * @param aPattern The selection pattern. A bit similar to filename globbing. + */ +HRESULT MachineDebugger::dumpStats(const com::Utf8Str &aPattern) +{ + Console::SafeVMPtrQuiet ptrVM(mParent); + if (!ptrVM.isOk()) + return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running")); + + ptrVM.vtable()->pfnSTAMR3Dump(ptrVM.rawUVM(), aPattern.c_str()); + + return S_OK; +} + +/** + * Get the VM statistics in an XML format. + * + * @returns COM status code. + * @param aPattern The selection pattern. A bit similar to filename globbing. + * @param aWithDescriptions Whether to include the descriptions. + * @param aStats The XML document containing the statistics. + */ +HRESULT MachineDebugger::getStats(const com::Utf8Str &aPattern, BOOL aWithDescriptions, com::Utf8Str &aStats) +{ + Console::SafeVMPtrQuiet ptrVM(mParent); + if (!ptrVM.isOk()) + return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running")); + + char *pszSnapshot; + int vrc = ptrVM.vtable()->pfnSTAMR3Snapshot(ptrVM.rawUVM(), aPattern.c_str(), &pszSnapshot, NULL, !!aWithDescriptions); + if (RT_FAILURE(vrc)) + return vrc == VERR_NO_MEMORY ? E_OUTOFMEMORY : E_FAIL; + + /** @todo this is horribly inefficient! And it's kinda difficult to tell whether it failed... + * Must use UTF-8 or ASCII here and completely avoid these two extra copy operations. + * Until that's done, this method is kind of useless for debugger statistics GUI because + * of the amount statistics in a debug build. */ + HRESULT hrc = aStats.assignEx(pszSnapshot); + ptrVM.vtable()->pfnSTAMR3SnapshotFree(ptrVM.rawUVM(), pszSnapshot); + + return hrc; +} + + +/** Wrapper around TMR3GetCpuLoadPercents. */ +HRESULT MachineDebugger::getCPULoad(ULONG aCpuId, ULONG *aPctExecuting, ULONG *aPctHalted, ULONG *aPctOther, LONG64 *aMsInterval) +{ + HRESULT hrc; + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + uint8_t uPctExecuting = 0; + uint8_t uPctHalted = 0; + uint8_t uPctOther = 0; + uint64_t msInterval = 0; + int vrc = ptrVM.vtable()->pfnTMR3GetCpuLoadPercents(ptrVM.rawUVM(), aCpuId >= UINT32_MAX / 2 ? VMCPUID_ALL : aCpuId, + &msInterval, &uPctExecuting, &uPctHalted, &uPctOther); + if (RT_SUCCESS(vrc)) + { + *aPctExecuting = uPctExecuting; + *aPctHalted = uPctHalted; + *aPctOther = uPctOther; + *aMsInterval = msInterval; + hrc = S_OK; + } + else + hrc = setErrorVrc(vrc); + } + else + hrc = setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running")); + return hrc; +} + + +HRESULT MachineDebugger::takeGuestSample(const com::Utf8Str &aFilename, ULONG aUsInterval, LONG64 aUsSampleTime, ComPtr<IProgress> &pProgress) +{ + /* + * The prologue. + */ + LogFlowThisFunc(("\n")); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + if (!m_hSampleReport) + { + m_strFilename = aFilename; + + int vrc = ptrVM.vtable()->pfnDBGFR3SampleReportCreate(ptrVM.rawUVM(), aUsInterval, + DBGF_SAMPLE_REPORT_F_STACK_REVERSE, &m_hSampleReport); + if (RT_SUCCESS(vrc)) + { + hrc = m_Progress.createObject(); + if (SUCCEEDED(hrc)) + { + hrc = m_Progress->init(static_cast<IMachineDebugger*>(this), + tr("Creating guest sample report..."), + TRUE /* aCancelable */); + if (SUCCEEDED(hrc)) + { + vrc = ptrVM.vtable()->pfnDBGFR3SampleReportStart(m_hSampleReport, aUsSampleTime, i_dbgfProgressCallback, + static_cast<MachineDebugger*>(this)); + if (RT_SUCCESS(vrc)) + hrc = m_Progress.queryInterfaceTo(pProgress.asOutParam()); + else + hrc = setErrorVrc(vrc); + } + } + + if (FAILED(hrc)) + { + ptrVM.vtable()->pfnDBGFR3SampleReportRelease(m_hSampleReport); + m_hSampleReport = NULL; + } + } + else + hrc = setErrorVrc(vrc); + } + else + hrc = setError(VBOX_E_INVALID_VM_STATE, tr("A sample report is already in progress")); + } + + return hrc; +} + +/** + * Hack for getting the user mode VM handle (UVM) and VMM function table. + * + * @returns COM status code + * @param aMagicVersion The VMMR3VTABLE_MAGIC_VERSION value of the + * caller so we can check that the function table + * is compatible. (Otherwise, the caller can't + * safely release the UVM reference.) + * @param aUVM Where to store the vm handle. Since there is no + * uintptr_t in COM, we're using the max integer. (No, + * ULONG is not pointer sized!) + * @param aVMMFunctionTable Where to store the vm handle. + * + * @remarks The returned handle must be passed to VMR3ReleaseUVM()! + */ +HRESULT MachineDebugger::getUVMAndVMMFunctionTable(LONG64 aMagicVersion, LONG64 *aVMMFunctionTable, LONG64 *aUVM) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Make sure it is a local call. + */ + RTTHREAD hThread = RTThreadSelf(); + if (hThread != NIL_RTTHREAD) + { + const char *pszName = RTThreadGetName(hThread); + if ( !RTStrStartsWith(pszName, "ALIEN-") /* COM worker threads are aliens */ + && !RTStrStartsWith(pszName, "nspr-") /* XPCOM worker threads are nspr-X */ ) + { + /* + * Use safe VM pointer to get both the UVM and VMM function table. + */ + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + if (VMMR3VTABLE_IS_COMPATIBLE_EX(ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion)) + { + ptrVM.vtable()->pfnVMR3RetainUVM(ptrVM.rawUVM()); + *aUVM = (intptr_t)ptrVM.rawUVM(); + *aVMMFunctionTable = (intptr_t)ptrVM.vtable(); + hrc = S_OK; + } + else + hrc = setError(E_FAIL, tr("Incompatible VMM function table: %RX64 vs %RX64 (caller)"), + ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion); + } + return hrc; + } + } + + return setError(E_ACCESSDENIED, tr("The method getUVMAndVMMFunctionTable is only for local calls")); +} + + + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +void MachineDebugger::i_flushQueuedSettings() +{ + mFlushMode = true; + if (mSingleStepQueued != -1) + { + COMSETTER(SingleStep)(mSingleStepQueued); + mSingleStepQueued = -1; + } + for (unsigned i = 0; i < EMEXECPOLICY_END; i++) + if (maiQueuedEmExecPolicyParams[i] != UINT8_MAX) + { + i_setEmExecPolicyProperty((EMEXECPOLICY)i, RT_BOOL(maiQueuedEmExecPolicyParams[i])); + maiQueuedEmExecPolicyParams[i] = UINT8_MAX; + } + if (mLogEnabledQueued != -1) + { + COMSETTER(LogEnabled)(mLogEnabledQueued); + mLogEnabledQueued = -1; + } + if (mVirtualTimeRateQueued != UINT32_MAX) + { + COMSETTER(VirtualTimeRate)(mVirtualTimeRateQueued); + mVirtualTimeRateQueued = UINT32_MAX; + } + mFlushMode = false; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +bool MachineDebugger::i_queueSettings() const +{ + if (!mFlushMode) + { + // check if the machine is running + MachineState_T machineState; + mParent->COMGETTER(State)(&machineState); + switch (machineState) + { + // queue the request + default: + return true; + + case MachineState_Running: + case MachineState_Paused: + case MachineState_Stuck: + case MachineState_LiveSnapshotting: + case MachineState_Teleporting: + break; + } + } + return false; +} +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/Makefile.kup b/src/VBox/Main/src-client/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Main/src-client/Makefile.kup diff --git a/src/VBox/Main/src-client/MouseImpl.cpp b/src/VBox/Main/src-client/MouseImpl.cpp new file mode 100644 index 00000000..fb39e093 --- /dev/null +++ b/src/VBox/Main/src-client/MouseImpl.cpp @@ -0,0 +1,1404 @@ +/* $Id: MouseImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_MOUSE +#include "LoggingNew.h" + +#include <iprt/cpp/utils.h> + +#include "MouseImpl.h" +#include "DisplayImpl.h" +#include "VMMDev.h" +#include "MousePointerShapeWrap.h" +#include "VBoxEvents.h" + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/VMMDev.h> +#include <VBox/err.h> + + +class ATL_NO_VTABLE MousePointerShape: + public MousePointerShapeWrap +{ +public: + + DECLARE_COMMON_CLASS_METHODS(MousePointerShape) + + HRESULT FinalConstruct(); + void FinalRelease(); + + /* Public initializer/uninitializer for internal purposes only. */ + HRESULT init(ComObjPtr<Mouse> pMouse, + bool fVisible, bool fAlpha, + uint32_t hotX, uint32_t hotY, + uint32_t width, uint32_t height, + const uint8_t *pu8Shape, uint32_t cbShape); + void uninit(); + +private: + // wrapped IMousePointerShape properties + virtual HRESULT getVisible(BOOL *aVisible); + virtual HRESULT getAlpha(BOOL *aAlpha); + virtual HRESULT getHotX(ULONG *aHotX); + virtual HRESULT getHotY(ULONG *aHotY); + virtual HRESULT getWidth(ULONG *aWidth); + virtual HRESULT getHeight(ULONG *aHeight); + virtual HRESULT getShape(std::vector<BYTE> &aShape); + + struct Data + { + ComObjPtr<Mouse> pMouse; + bool fVisible; + bool fAlpha; + uint32_t hotX; + uint32_t hotY; + uint32_t width; + uint32_t height; + std::vector<BYTE> shape; + }; + + Data m; +}; + +/* + * MousePointerShape implementation. + */ +DEFINE_EMPTY_CTOR_DTOR(MousePointerShape) + +HRESULT MousePointerShape::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void MousePointerShape::FinalRelease() +{ + uninit(); + + BaseFinalRelease(); +} + +HRESULT MousePointerShape::init(ComObjPtr<Mouse> pMouse, + bool fVisible, bool fAlpha, + uint32_t hotX, uint32_t hotY, + uint32_t width, uint32_t height, + const uint8_t *pu8Shape, uint32_t cbShape) +{ + LogFlowThisFunc(("v %d, a %d, h %d,%d, %dx%d, cb %d\n", + fVisible, fAlpha, hotX, hotY, width, height, cbShape)); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m.pMouse = pMouse; + m.fVisible = fVisible; + m.fAlpha = fAlpha; + m.hotX = hotX; + m.hotY = hotY; + m.width = width; + m.height = height; + m.shape.resize(cbShape); + if (cbShape) + { + memcpy(&m.shape.front(), pu8Shape, cbShape); + } + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +void MousePointerShape::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + m.pMouse.setNull(); +} + +HRESULT MousePointerShape::getVisible(BOOL *aVisible) +{ + *aVisible = m.fVisible; + return S_OK; +} + +HRESULT MousePointerShape::getAlpha(BOOL *aAlpha) +{ + *aAlpha = m.fAlpha; + return S_OK; +} + +HRESULT MousePointerShape::getHotX(ULONG *aHotX) +{ + *aHotX = m.hotX; + return S_OK; +} + +HRESULT MousePointerShape::getHotY(ULONG *aHotY) +{ + *aHotY = m.hotY; + return S_OK; +} + +HRESULT MousePointerShape::getWidth(ULONG *aWidth) +{ + *aWidth = m.width; + return S_OK; +} + +HRESULT MousePointerShape::getHeight(ULONG *aHeight) +{ + *aHeight = m.height; + return S_OK; +} + +HRESULT MousePointerShape::getShape(std::vector<BYTE> &aShape) +{ + aShape.resize(m.shape.size()); + if (m.shape.size()) + memcpy(&aShape.front(), &m.shape.front(), aShape.size()); + return S_OK; +} + + +/** @name Mouse device capabilities bitfield + * @{ */ +enum +{ + /** The mouse device can do relative reporting */ + MOUSE_DEVCAP_RELATIVE = 1, + /** The mouse device can do absolute reporting */ + MOUSE_DEVCAP_ABSOLUTE = 2, + /** The mouse device can do absolute multi-touch reporting */ + MOUSE_DEVCAP_MT_ABSOLUTE = 4, + /** The mouse device can do relative multi-touch reporting */ + MOUSE_DEVCAP_MT_RELATIVE = 8, +}; +/** @} */ + + +/** + * Mouse driver instance data. + */ +struct DRVMAINMOUSE +{ + /** Pointer to the mouse object. */ + Mouse *pMouse; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the mouse port interface of the driver/device above us. */ + PPDMIMOUSEPORT pUpPort; + /** Our mouse connector interface. */ + PDMIMOUSECONNECTOR IConnector; + /** The capabilities of this device. */ + uint32_t u32DevCaps; +}; + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +Mouse::Mouse() + : mParent(NULL) +{ +} + +Mouse::~Mouse() +{ +} + + +HRESULT Mouse::FinalConstruct() +{ + RT_ZERO(mpDrv); + RT_ZERO(mPointerData); + mcLastX = 0x8000; + mcLastY = 0x8000; + mfLastButtons = 0; + mfVMMDevGuestCaps = 0; + return BaseFinalConstruct(); +} + +void Mouse::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the mouse object. + * + * @returns COM result indicator + * @param parent handle of our parent object + */ +HRESULT Mouse::init (ConsoleMouseInterface *parent) +{ + LogFlowThisFunc(("\n")); + + ComAssertRet(parent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mParent) = parent; + + unconst(mEventSource).createObject(); + HRESULT hrc = mEventSource->init(); + AssertComRCReturnRC(hrc); + + ComPtr<IEvent> ptrEvent; + hrc = ::CreateGuestMouseEvent(ptrEvent.asOutParam(), mEventSource, + (GuestMouseEventMode_T)0, 0 /*x*/, 0 /*y*/, 0 /*z*/, 0 /*w*/, 0 /*buttons*/); + AssertComRCReturnRC(hrc); + mMouseEvent.init(ptrEvent, mEventSource); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void Mouse::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i) + { + if (mpDrv[i]) + mpDrv[i]->pMouse = NULL; + mpDrv[i] = NULL; + } + + mPointerShape.setNull(); + + RTMemFree(mPointerData.pu8Shape); + mPointerData.pu8Shape = NULL; + mPointerData.cbShape = 0; + + mMouseEvent.uninit(); + unconst(mEventSource).setNull(); + unconst(mParent) = NULL; +} + +void Mouse::updateMousePointerShape(bool fVisible, bool fAlpha, + uint32_t hotX, uint32_t hotY, + uint32_t width, uint32_t height, + const uint8_t *pu8Shape, uint32_t cbShape) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + RTMemFree(mPointerData.pu8Shape); + mPointerData.pu8Shape = NULL; + mPointerData.cbShape = 0; + + mPointerData.fVisible = fVisible; + mPointerData.fAlpha = fAlpha; + mPointerData.hotX = hotX; + mPointerData.hotY = hotY; + mPointerData.width = width; + mPointerData.height = height; + if (cbShape) + { + mPointerData.pu8Shape = (uint8_t *)RTMemDup(pu8Shape, cbShape); + if (mPointerData.pu8Shape) + { + mPointerData.cbShape = cbShape; + } + } + + mPointerShape.setNull(); +} + +// IMouse properties +///////////////////////////////////////////////////////////////////////////// + +/** Report the front-end's mouse handling capabilities to the VMM device and + * thus to the guest. + * @note all calls out of this object are made with no locks held! */ +HRESULT Mouse::i_updateVMMDevMouseCaps(uint32_t fCapsAdded, + uint32_t fCapsRemoved) +{ + VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface(); + if (!pVMMDev) + return E_FAIL; /* No assertion, as the front-ends can send events + * at all sorts of inconvenient times. */ + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + if (pDisplay == NULL) + return E_FAIL; + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + if (!pVMMDevPort) + return E_FAIL; /* same here */ + + int vrc = pVMMDevPort->pfnUpdateMouseCapabilities(pVMMDevPort, fCapsAdded, + fCapsRemoved); + if (RT_FAILURE(vrc)) + return E_FAIL; + return pDisplay->i_reportHostCursorCapabilities(fCapsAdded, fCapsRemoved); +} + +/** + * Returns whether the currently active device portfolio can accept absolute + * mouse events. + * + * @returns COM status code + * @param aAbsoluteSupported address of result variable + */ +HRESULT Mouse::getAbsoluteSupported(BOOL *aAbsoluteSupported) +{ + *aAbsoluteSupported = i_supportsAbs(); + return S_OK; +} + +/** + * Returns whether the currently active device portfolio can accept relative + * mouse events. + * + * @returns COM status code + * @param aRelativeSupported address of result variable + */ +HRESULT Mouse::getRelativeSupported(BOOL *aRelativeSupported) +{ + *aRelativeSupported = i_supportsRel(); + return S_OK; +} + +/** + * Returns whether the currently active device portfolio can accept multi-touch + * touchscreen events. + * + * @returns COM status code + * @param aTouchScreenSupported address of result variable + */ +HRESULT Mouse::getTouchScreenSupported(BOOL *aTouchScreenSupported) +{ + *aTouchScreenSupported = i_supportsTS(); + return S_OK; +} + +/** + * Returns whether the currently active device portfolio can accept multi-touch + * touchpad events. + * + * @returns COM status code + * @param aTouchPadSupported address of result variable + */ +HRESULT Mouse::getTouchPadSupported(BOOL *aTouchPadSupported) +{ + *aTouchPadSupported = i_supportsTP(); + return S_OK; +} + +/** + * Returns whether the guest can currently switch to drawing the mouse cursor + * itself if it is asked to by the front-end. + * + * @returns COM status code + * @param aNeedsHostCursor address of result variable + */ +HRESULT Mouse::getNeedsHostCursor(BOOL *aNeedsHostCursor) +{ + *aNeedsHostCursor = i_guestNeedsHostCursor(); + return S_OK; +} + +HRESULT Mouse::getPointerShape(ComPtr<IMousePointerShape> &aPointerShape) +{ + HRESULT hr = S_OK; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mPointerShape.isNull()) + { + ComObjPtr<MousePointerShape> obj; + hr = obj.createObject(); + if (SUCCEEDED(hr)) + { + hr = obj->init(this, mPointerData.fVisible, mPointerData.fAlpha, + mPointerData.hotX, mPointerData.hotY, + mPointerData.width, mPointerData.height, + mPointerData.pu8Shape, mPointerData.cbShape); + } + + if (SUCCEEDED(hr)) + { + mPointerShape = obj; + } + } + + if (SUCCEEDED(hr)) + { + aPointerShape = mPointerShape; + } + + return hr; +} + +// IMouse methods +///////////////////////////////////////////////////////////////////////////// + +/** Converts a bitfield containing information about mouse buttons currently + * held down from the format used by the front-end to the format used by PDM + * and the emulated pointing devices. */ +static uint32_t i_mouseButtonsToPDM(LONG buttonState) +{ + uint32_t fButtons = 0; + if (buttonState & MouseButtonState_LeftButton) + fButtons |= PDMIMOUSEPORT_BUTTON_LEFT; + if (buttonState & MouseButtonState_RightButton) + fButtons |= PDMIMOUSEPORT_BUTTON_RIGHT; + if (buttonState & MouseButtonState_MiddleButton) + fButtons |= PDMIMOUSEPORT_BUTTON_MIDDLE; + if (buttonState & MouseButtonState_XButton1) + fButtons |= PDMIMOUSEPORT_BUTTON_X1; + if (buttonState & MouseButtonState_XButton2) + fButtons |= PDMIMOUSEPORT_BUTTON_X2; + return fButtons; +} + +HRESULT Mouse::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + // no need to lock - lifetime constant + mEventSource.queryInterfaceTo(aEventSource.asOutParam()); + return S_OK; +} + +/** + * Send a relative pointer event to the relative device we deem most + * appropriate. + * + * @returns COM status code + */ +HRESULT Mouse::i_reportRelEventToMouseDev(int32_t dx, int32_t dy, int32_t dz, + int32_t dw, uint32_t fButtons) +{ + if (dx || dy || dz || dw || fButtons != mfLastButtons) + { + PPDMIMOUSEPORT pUpPort = NULL; + { + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i) + { + if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_RELATIVE)) + pUpPort = mpDrv[i]->pUpPort; + } + } + if (!pUpPort) + return S_OK; + + int vrc = pUpPort->pfnPutEvent(pUpPort, dx, dy, dz, dw, fButtons); + + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send the mouse event to the virtual mouse (%Rrc)"), + vrc); + mfLastButtons = fButtons; + } + return S_OK; +} + + +/** + * Send an absolute pointer event to the emulated absolute device we deem most + * appropriate. + * + * @returns COM status code + */ +HRESULT Mouse::i_reportAbsEventToMouseDev(int32_t x, int32_t y, + int32_t dz, int32_t dw, uint32_t fButtons) +{ + if ( x < VMMDEV_MOUSE_RANGE_MIN + || x > VMMDEV_MOUSE_RANGE_MAX) + return S_OK; + if ( y < VMMDEV_MOUSE_RANGE_MIN + || y > VMMDEV_MOUSE_RANGE_MAX) + return S_OK; + if ( x != mcLastX || y != mcLastY + || dz || dw || fButtons != mfLastButtons) + { + PPDMIMOUSEPORT pUpPort = NULL; + { + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i) + { + if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_ABSOLUTE)) + pUpPort = mpDrv[i]->pUpPort; + } + } + if (!pUpPort) + return S_OK; + + int vrc = pUpPort->pfnPutEventAbs(pUpPort, x, y, dz, + dw, fButtons); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send the mouse event to the virtual mouse (%Rrc)"), + vrc); + mfLastButtons = fButtons; + + } + return S_OK; +} + +HRESULT Mouse::i_reportMultiTouchEventToDevice(uint8_t cContacts, + const uint64_t *pau64Contacts, + bool fTouchScreen, + uint32_t u32ScanTime) +{ + HRESULT hrc = S_OK; + + int match = fTouchScreen ? MOUSE_DEVCAP_MT_ABSOLUTE : MOUSE_DEVCAP_MT_RELATIVE; + PPDMIMOUSEPORT pUpPort = NULL; + { + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + unsigned i; + for (i = 0; i < MOUSE_MAX_DEVICES; ++i) + { + if ( mpDrv[i] + && (mpDrv[i]->u32DevCaps & match)) + { + pUpPort = mpDrv[i]->pUpPort; + break; + } + } + } + + if (pUpPort) + { + int vrc = pUpPort->pfnPutEventTouchScreen(pUpPort, cContacts, pau64Contacts, u32ScanTime); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send the multi-touch event to the virtual device (%Rrc)"), + vrc); + } + else + { + hrc = E_UNEXPECTED; + } + + return hrc; +} + + +/** + * Send an absolute position event to the VMM device. + * @note all calls out of this object are made with no locks held! + * + * @returns COM status code + */ +HRESULT Mouse::i_reportAbsEventToVMMDev(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons) +{ + VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface(); + ComAssertRet(pVMMDev, E_FAIL); + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + ComAssertRet(pVMMDevPort, E_FAIL); + + if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons) + { + int vrc = pVMMDevPort->pfnSetAbsoluteMouse(pVMMDevPort, x, y, dz, dw, fButtons); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not send the mouse event to the virtual mouse (%Rrc)"), + vrc); + } + return S_OK; +} + + +/** + * Send an absolute pointer event to a pointing device (the VMM device if + * possible or whatever emulated absolute device seems best to us if not). + * + * @returns COM status code + */ +HRESULT Mouse::i_reportAbsEventToInputDevices(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons, + bool fUsesVMMDevEvent) +{ + HRESULT hrc = S_OK; + /** If we are using the VMMDev to report absolute position but without + * VMMDev IRQ support then we need to send a small "jiggle" to the emulated + * relative mouse device to alert the guest to changes. */ + LONG cJiggle = 0; + + if (i_vmmdevCanAbs()) + { + /* + * Send the absolute mouse position to the VMM device. + */ + if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons) + { + hrc = i_reportAbsEventToVMMDev(x, y, dz, dw, fButtons); + cJiggle = !fUsesVMMDevEvent; + } + + /* If guest cannot yet read full mouse state from DevVMM (i.e., + * only 'x' and 'y' coordinates will be read) we need to pass buttons + * state as well as horizontal and vertical wheel movement over ever-present PS/2 + * emulated mouse device. */ + if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL)) + hrc = i_reportRelEventToMouseDev(cJiggle, 0, dz, dw, fButtons); + } + else + hrc = i_reportAbsEventToMouseDev(x, y, dz, dw, fButtons); + + mcLastX = x; + mcLastY = y; + mfLastButtons = fButtons; + return hrc; +} + + +/** + * Send an absolute position event to the display device. + * @note all calls out of this object are made with no locks held! + * @param x Cursor X position in pixels relative to the first screen, where + * (1, 1) is the upper left corner. + * @param y Cursor Y position in pixels relative to the first screen, where + * (1, 1) is the upper left corner. + */ +HRESULT Mouse::i_reportAbsEventToDisplayDevice(int32_t x, int32_t y) +{ + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + ComAssertRet(pDisplay, E_FAIL); + + if (x != mcLastX || y != mcLastY) + { + pDisplay->i_reportHostCursorPosition(x - 1, y - 1, false); + } + return S_OK; +} + + +void Mouse::i_fireMouseEvent(bool fAbsolute, LONG x, LONG y, LONG dz, LONG dw, + LONG fButtons) +{ + /* If mouse button is pressed, we generate new event, to avoid reusable events coalescing and thus + dropping key press events */ + GuestMouseEventMode_T mode; + if (fAbsolute) + mode = GuestMouseEventMode_Absolute; + else + mode = GuestMouseEventMode_Relative; + + if (fButtons != 0) + ::FireGuestMouseEvent(mEventSource, mode, x, y, dz, dw, fButtons); + else + { + ComPtr<IEvent> ptrEvent; + mMouseEvent.getEvent(ptrEvent.asOutParam()); + ReinitGuestMouseEvent(ptrEvent, mode, x, y, dz, dw, fButtons); + mMouseEvent.fire(0); + } +} + +void Mouse::i_fireMultiTouchEvent(uint8_t cContacts, + const LONG64 *paContacts, + bool fTouchScreen, + uint32_t u32ScanTime) +{ + com::SafeArray<SHORT> xPositions(cContacts); + com::SafeArray<SHORT> yPositions(cContacts); + com::SafeArray<USHORT> contactIds(cContacts); + com::SafeArray<USHORT> contactFlags(cContacts); + + uint8_t i; + for (i = 0; i < cContacts; i++) + { + uint32_t u32Lo = RT_LO_U32(paContacts[i]); + uint32_t u32Hi = RT_HI_U32(paContacts[i]); + xPositions[i] = (int16_t)u32Lo; + yPositions[i] = (int16_t)(u32Lo >> 16); + contactIds[i] = RT_BYTE1(u32Hi); + contactFlags[i] = RT_BYTE2(u32Hi); + } + + ::FireGuestMultiTouchEvent(mEventSource, cContacts, ComSafeArrayAsInParam(xPositions), ComSafeArrayAsInParam(yPositions), + ComSafeArrayAsInParam(contactIds), ComSafeArrayAsInParam(contactFlags), fTouchScreen, u32ScanTime); +} + +/** + * Send a relative mouse event to the guest. + * @note the VMMDev capability change is so that the guest knows we are sending + * real events over the PS/2 device and not dummy events to signal the + * arrival of new absolute pointer data + * + * @returns COM status code + * @param dx X movement. + * @param dy Y movement. + * @param dz Z movement. + * @param dw Mouse wheel movement. + * @param aButtonState The mouse button state. + */ +HRESULT Mouse::putMouseEvent(LONG dx, LONG dy, LONG dz, LONG dw, + LONG aButtonState) +{ + LogRel3(("%s: dx=%d, dy=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__, + dx, dy, dz, dw)); + + uint32_t fButtonsAdj = i_mouseButtonsToPDM(aButtonState); + /* Make sure that the guest knows that we are sending real movement + * events to the PS/2 device and not just dummy wake-up ones. */ + i_updateVMMDevMouseCaps(0, VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE); + HRESULT hrc = i_reportRelEventToMouseDev(dx, dy, dz, dw, fButtonsAdj); + + i_fireMouseEvent(false, dx, dy, dz, dw, aButtonState); + + return hrc; +} + +/** + * Convert an (X, Y) value pair in screen co-ordinates (starting from 1) to a + * value from VMMDEV_MOUSE_RANGE_MIN to VMMDEV_MOUSE_RANGE_MAX. Sets the + * optional validity value to false if the pair is not on an active screen and + * to true otherwise. + * @note since guests with recent versions of X.Org use a different method + * to everyone else to map the valuator value to a screen pixel (they + * multiply by the screen dimension, do a floating point divide by + * the valuator maximum and round the result, while everyone else + * does truncating integer operations) we adjust the value we send + * so that it maps to the right pixel both when the result is rounded + * and when it is truncated. + * + * @returns COM status value + */ +HRESULT Mouse::i_convertDisplayRes(LONG x, LONG y, int32_t *pxAdj, int32_t *pyAdj, + bool *pfValid) +{ + AssertPtrReturn(pxAdj, E_POINTER); + AssertPtrReturn(pyAdj, E_POINTER); + AssertPtrNullReturn(pfValid, E_POINTER); + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + ComAssertRet(pDisplay, E_FAIL); + /** The amount to add to the result (multiplied by the screen width/height) + * to compensate for differences in guest methods for mapping back to + * pixels */ + enum { ADJUST_RANGE = - 3 * VMMDEV_MOUSE_RANGE / 4 }; + + if (pfValid) + *pfValid = true; + if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL) && !pDisplay->i_isInputMappingSet()) + { + ULONG displayWidth, displayHeight; + ULONG ulDummy; + LONG lDummy; + /* Takes the display lock */ + HRESULT hrc = pDisplay->i_getScreenResolution(0, &displayWidth, + &displayHeight, &ulDummy, &lDummy, &lDummy); + if (FAILED(hrc)) + return hrc; + + *pxAdj = displayWidth ? (x * VMMDEV_MOUSE_RANGE + ADJUST_RANGE) + / (LONG) displayWidth: 0; + *pyAdj = displayHeight ? (y * VMMDEV_MOUSE_RANGE + ADJUST_RANGE) + / (LONG) displayHeight: 0; + } + else + { + int32_t x1, y1, x2, y2; + /* Takes the display lock */ + pDisplay->i_getFramebufferDimensions(&x1, &y1, &x2, &y2); + *pxAdj = x1 < x2 ? ((x - x1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE) + / (x2 - x1) : 0; + *pyAdj = y1 < y2 ? ((y - y1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE) + / (y2 - y1) : 0; + if ( *pxAdj < VMMDEV_MOUSE_RANGE_MIN + || *pxAdj > VMMDEV_MOUSE_RANGE_MAX + || *pyAdj < VMMDEV_MOUSE_RANGE_MIN + || *pyAdj > VMMDEV_MOUSE_RANGE_MAX) + if (pfValid) + *pfValid = false; + } + return S_OK; +} + + +/** + * Send an absolute mouse event to the VM. This requires either VirtualBox- + * specific drivers installed in the guest or absolute pointing device + * emulation. + * @note the VMMDev capability change is so that the guest knows we are sending + * dummy events over the PS/2 device to signal the arrival of new + * absolute pointer data, and not pointer real movement data + * @note all calls out of this object are made with no locks held! + * + * @returns COM status code + * @param x X position (pixel), starting from 1 + * @param y Y position (pixel), starting from 1 + * @param dz Z movement + * @param dw mouse wheel movement + * @param aButtonState The mouse button state + */ +HRESULT Mouse::putMouseEventAbsolute(LONG x, LONG y, LONG dz, LONG dw, + LONG aButtonState) +{ + LogRel3(("%s: x=%d, y=%d, dz=%d, dw=%d, fButtons=0x%x\n", + __PRETTY_FUNCTION__, x, y, dz, dw, aButtonState)); + + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + ComAssertRet(pDisplay, E_FAIL); + int32_t xAdj, yAdj; + uint32_t fButtonsAdj; + bool fValid; + + /* If we are doing old-style (IRQ-less) absolute reporting to the VMM + * device then make sure the guest is aware of it, so that it knows to + * ignore relative movement on the PS/2 device. */ + i_updateVMMDevMouseCaps(VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE, 0); + /* Detect out-of-range. */ + if (x == 0x7FFFFFFF && y == 0x7FFFFFFF) + { + pDisplay->i_reportHostCursorPosition(0, 0, true); + return S_OK; + } + /* Detect "report-only" (-1, -1). This is not ideal, as in theory the + * front-end could be sending negative values relative to the primary + * screen. */ + if (x == -1 && y == -1) + return S_OK; + /** @todo the front end should do this conversion to avoid races */ + /** @note Or maybe not... races are pretty inherent in everything done in + * this object and not really bad as far as I can see. */ + HRESULT hrc = i_convertDisplayRes(x, y, &xAdj, &yAdj, &fValid); + if (FAILED(hrc)) return hrc; + + fButtonsAdj = i_mouseButtonsToPDM(aButtonState); + if (fValid) + { + hrc = i_reportAbsEventToInputDevices(xAdj, yAdj, dz, dw, fButtonsAdj, + RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL)); + if (FAILED(hrc)) return hrc; + + i_fireMouseEvent(true, x, y, dz, dw, aButtonState); + } + hrc = i_reportAbsEventToDisplayDevice(x, y); + + return hrc; +} + +/** + * Send a multi-touch event. This requires multi-touch pointing device emulation. + * @note all calls out of this object are made with no locks held! + * + * @returns COM status code. + * @param aCount Number of contacts. + * @param aContacts Information about each contact. + * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events. + * @param aScanTime Timestamp. + */ +HRESULT Mouse::putEventMultiTouch(LONG aCount, + const std::vector<LONG64> &aContacts, + BOOL aIsTouchscreen, + ULONG aScanTime) +{ + LogRel3(("%s: aCount %d(actual %d), aScanTime %u\n", + __FUNCTION__, aCount, aContacts.size(), aScanTime)); + + HRESULT hrc = S_OK; + + if ((LONG)aContacts.size() >= aCount) + { + const LONG64 *paContacts = aCount > 0? &aContacts.front(): NULL; + + hrc = i_putEventMultiTouch(aCount, paContacts, aIsTouchscreen, aScanTime); + } + else + { + hrc = E_INVALIDARG; + } + + return hrc; +} + +/** + * Send a multi-touch event. Version for scripting languages. + * + * @returns COM status code. + * @param aCount Number of contacts. + * @param aContacts Information about each contact. + * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events. + * @param aScanTime Timestamp. + */ +HRESULT Mouse::putEventMultiTouchString(LONG aCount, + const com::Utf8Str &aContacts, + BOOL aIsTouchscreen, + ULONG aScanTime) +{ + /** @todo implement: convert the string to LONG64 array and call putEventMultiTouch. */ + NOREF(aCount); + NOREF(aContacts); + NOREF(aIsTouchscreen); + NOREF(aScanTime); + return E_NOTIMPL; +} + + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/* Used by PutEventMultiTouch and PutEventMultiTouchString. */ +HRESULT Mouse::i_putEventMultiTouch(LONG aCount, + const LONG64 *paContacts, + BOOL aIsTouchscreen, + ULONG aScanTime) +{ + if (aCount >= 256) + return E_INVALIDARG; + + HRESULT hrc = S_OK; + + /* Touch events in the touchscreen variant are currently mapped to the + * primary monitor, because the emulated USB touchscreen device is + * associated with one (normally the primary) screen in the guest. + * In the future this could/should be extended to multi-screen support. */ + ULONG uScreenId = 0; + + ULONG cWidth = 0; + ULONG cHeight = 0; + LONG xOrigin = 0; + LONG yOrigin = 0; + + if (aIsTouchscreen) + { + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + ComAssertRet(pDisplay, E_FAIL); + ULONG cBPP = 0; + hrc = pDisplay->i_getScreenResolution(uScreenId, &cWidth, &cHeight, &cBPP, &xOrigin, &yOrigin); + NOREF(cBPP); + ComAssertComRCRetRC(hrc); + } + + uint64_t* pau64Contacts = NULL; + uint8_t cContacts = 0; + + /* Deliver 0 contacts too, touch device may use this to reset the state. */ + if (aCount > 0) + { + /* Create a copy with converted coords. */ + pau64Contacts = (uint64_t *)RTMemTmpAlloc(aCount * sizeof(uint64_t)); + if (pau64Contacts) + { + if (aIsTouchscreen) + { + int32_t x1 = xOrigin; + int32_t y1 = yOrigin; + int32_t x2 = x1 + cWidth; + int32_t y2 = y1 + cHeight; + + LogRel3(("%s: screen [%d] %d,%d %d,%d\n", + __FUNCTION__, uScreenId, x1, y1, x2, y2)); + + LONG i; + for (i = 0; i < aCount; i++) + { + uint32_t u32Lo = RT_LO_U32(paContacts[i]); + uint32_t u32Hi = RT_HI_U32(paContacts[i]); + int32_t x = (int16_t)u32Lo; + int32_t y = (int16_t)(u32Lo >> 16); + uint8_t contactId = RT_BYTE1(u32Hi); + bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0; + bool fInRange = (RT_BYTE2(u32Hi) & 0x2) != 0; + + LogRel3(("%s: touchscreen [%d] %d,%d id %d, inContact %d, inRange %d\n", + __FUNCTION__, i, x, y, contactId, fInContact, fInRange)); + + /* x1,y1 are inclusive and x2,y2 are exclusive, + * while x,y start from 1 and are inclusive. + */ + if (x <= x1 || x > x2 || y <= y1 || y > y2) + { + /* Out of range. Skip the contact. */ + continue; + } + + int32_t xAdj = x1 < x2? ((x - 1 - x1) * VMMDEV_MOUSE_RANGE) / (x2 - x1) : 0; + int32_t yAdj = y1 < y2? ((y - 1 - y1) * VMMDEV_MOUSE_RANGE) / (y2 - y1) : 0; + + bool fValid = ( xAdj >= VMMDEV_MOUSE_RANGE_MIN + && xAdj <= VMMDEV_MOUSE_RANGE_MAX + && yAdj >= VMMDEV_MOUSE_RANGE_MIN + && yAdj <= VMMDEV_MOUSE_RANGE_MAX); + + if (fValid) + { + uint8_t fu8 = (uint8_t)( (fInContact? 0x01: 0x00) + | (fInRange? 0x02: 0x00)); + pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16((uint16_t)xAdj, + (uint16_t)yAdj, + RT_MAKE_U16(contactId, fu8), + 0); + cContacts++; + } + } + } + else + { + LONG i; + for (i = 0; i < aCount; i++) + { + uint32_t u32Lo = RT_LO_U32(paContacts[i]); + uint32_t u32Hi = RT_HI_U32(paContacts[i]); + uint16_t x = (uint16_t)u32Lo; + uint16_t y = (uint16_t)(u32Lo >> 16); + uint8_t contactId = RT_BYTE1(u32Hi); + bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0; + + LogRel3(("%s: touchpad [%d] %#04x,%#04x id %d, inContact %d\n", + __FUNCTION__, i, x, y, contactId, fInContact)); + + uint8_t fu8 = (uint8_t)(fInContact? 0x01: 0x00); + + pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16(x, y, + RT_MAKE_U16(contactId, fu8), + 0); + cContacts++; + } + } + } + else + { + hrc = E_OUTOFMEMORY; + } + } + + if (SUCCEEDED(hrc)) + { + hrc = i_reportMultiTouchEventToDevice(cContacts, cContacts? pau64Contacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime); + + /* Send the original contact information. */ + i_fireMultiTouchEvent(cContacts, cContacts? paContacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime); + } + + RTMemTmpFree(pau64Contacts); + + return hrc; +} + + +/** Does the guest currently rely on the host to draw the mouse cursor or + * can it switch to doing it itself in software? */ +bool Mouse::i_guestNeedsHostCursor(void) +{ + return RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR); +} + + +/** + * Gets the combined capabilities of all currently enabled devices. + * + * @returns Combination of MOUSE_DEVCAP_XXX + */ +uint32_t Mouse::i_getDeviceCaps(void) +{ + uint32_t fCaps = 0; + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i) + if (mpDrv[i]) + fCaps |= mpDrv[i]->u32DevCaps; + return fCaps; +} + + +/** Does the VMM device currently support absolute reporting? */ +bool Mouse::i_vmmdevCanAbs(void) +{ + /* This requires the VMMDev cap and a relative device, which supposedly + consumes these. As seen in @bugref{10285} this isn't quite as clear cut. */ + return (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE) + && (i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE); +} + + +/** Does any device currently support absolute reporting w/o help from VMMDev? */ +bool Mouse::i_deviceCanAbs(void) +{ + return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_ABSOLUTE); +} + + +/** Can we currently send relative events to the guest? */ +bool Mouse::i_supportsRel(void) +{ + return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE); +} + + +/** Can we currently send absolute events to the guest (including via VMMDev)? */ +bool Mouse::i_supportsAbs(uint32_t fCaps) const +{ + return (fCaps & MOUSE_DEVCAP_ABSOLUTE) + || /* inlined i_vmmdevCanAbs() to avoid unnecessary i_getDeviceCaps call: */ + ( (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE) + && (fCaps & MOUSE_DEVCAP_RELATIVE)); +} + + +/** Can we currently send absolute events to the guest? */ +bool Mouse::i_supportsAbs(void) +{ + return Mouse::i_supportsAbs(i_getDeviceCaps()); +} + + +/** Can we currently send multi-touch events (touchscreen variant) to the guest? */ +bool Mouse::i_supportsTS(void) +{ + return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_ABSOLUTE); +} + + +/** Can we currently send multi-touch events (touchpad variant) to the guest? */ +bool Mouse::i_supportsTP(void) +{ + return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_RELATIVE); +} + + +/** Check what sort of reporting can be done using the devices currently + * enabled (including the VMM device) and notify the guest and the front-end. + */ +void Mouse::i_sendMouseCapsNotifications(void) +{ + bool fRelDev, fTSDev, fTPDev, fCanAbs, fNeedsHostCursor; + { + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + uint32_t const fCaps = i_getDeviceCaps(); + fRelDev = RT_BOOL(fCaps & MOUSE_DEVCAP_RELATIVE); + fTSDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_ABSOLUTE); + fTPDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_RELATIVE); + fCanAbs = i_supportsAbs(fCaps); + fNeedsHostCursor = i_guestNeedsHostCursor(); + } + mParent->i_onMouseCapabilityChange(fCanAbs, fRelDev, fTSDev, fTPDev, fNeedsHostCursor); +} + + +/** + * @interface_method_impl{PDMIMOUSECONNECTOR,pfnReportModes} + */ +DECLCALLBACK(void) Mouse::i_mouseReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRelative, + bool fAbsolute, bool fMTAbsolute, bool fMTRelative) +{ + PDRVMAINMOUSE pDrv = RT_FROM_MEMBER(pInterface, DRVMAINMOUSE, IConnector); + if (fRelative) + pDrv->u32DevCaps |= MOUSE_DEVCAP_RELATIVE; + else + pDrv->u32DevCaps &= ~MOUSE_DEVCAP_RELATIVE; + if (fAbsolute) + pDrv->u32DevCaps |= MOUSE_DEVCAP_ABSOLUTE; + else + pDrv->u32DevCaps &= ~MOUSE_DEVCAP_ABSOLUTE; + if (fMTAbsolute) + pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_ABSOLUTE; + else + pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_ABSOLUTE; + if (fMTRelative) + pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_RELATIVE; + else + pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_RELATIVE; + + pDrv->pMouse->i_sendMouseCapsNotifications(); +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) Mouse::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINMOUSE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pDrv->IConnector); + return NULL; +} + + +/** + * Destruct a mouse driver instance. + * + * @returns VBox status code. + * @param pDrvIns The driver instance data. + */ +DECLCALLBACK(void) Mouse::i_drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE); + LogFlow(("Mouse::drvDestruct: iInstance=%d\n", pDrvIns->iInstance)); + + if (pThis->pMouse) + { + AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS); + for (unsigned cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev) + if (pThis->pMouse->mpDrv[cDev] == pThis) + { + pThis->pMouse->mpDrv[cDev] = NULL; + break; + } + } +} + + +/** + * Construct a mouse driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +DECLCALLBACK(int) Mouse::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + RT_NOREF(fFlags, pCfg); + PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE); + LogFlow(("drvMainMouse_Construct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * IBase. + */ + pDrvIns->IBase.pfnQueryInterface = Mouse::i_drvQueryInterface; + + pThis->IConnector.pfnReportModes = Mouse::i_mouseReportModes; + + /* + * Get the IMousePort interface of the above driver/device. + */ + pThis->pUpPort = (PPDMIMOUSEPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMIMOUSEPORT_IID); + if (!pThis->pUpPort) + { + AssertMsgFailed(("Configuration error: No mouse port interface above!\n")); + return VERR_PDM_MISSING_INTERFACE_ABOVE; + } + + /* + * Get the Mouse object pointer and update the mpDrv member. + */ + com::Guid uuid(COM_IIDOF(IMouse)); + IMouse *pIMouse = (IMouse *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw()); + if (!pIMouse) + { + AssertMsgFailed(("Configuration error: No/bad Mouse object!\n")); + return VERR_NOT_FOUND; + } + pThis->pMouse = static_cast<Mouse *>(pIMouse); + + unsigned cDev; + { + AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS); + + for (cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev) + if (!pThis->pMouse->mpDrv[cDev]) + { + pThis->pMouse->mpDrv[cDev] = pThis; + break; + } + } + if (cDev == MOUSE_MAX_DEVICES) + return VERR_NO_MORE_HANDLES; + + return VINF_SUCCESS; +} + + +/** + * Main mouse driver registration record. + */ +const PDMDRVREG Mouse::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "MainMouse", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main mouse driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_MOUSE, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINMOUSE), + /* pfnConstruct */ + Mouse::i_drvConstruct, + /* pfnDestruct */ + Mouse::i_drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/PCIRawDevImpl.cpp b/src/VBox/Main/src-client/PCIRawDevImpl.cpp new file mode 100644 index 00000000..acf22bb0 --- /dev/null +++ b/src/VBox/Main/src-client/PCIRawDevImpl.cpp @@ -0,0 +1,230 @@ +/* $Id: PCIRawDevImpl.cpp $ */ +/** @file + * VirtualBox Driver Interface to raw PCI device. + */ + +/* + * Copyright (C) 2010-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW +#include "LoggingNew.h" + +#include "PCIRawDevImpl.h" +#include "PCIDeviceAttachmentImpl.h" +#include "ConsoleImpl.h" + +// generated header for events +#include "VBoxEvents.h" + +#include <VBox/err.h> + + +/** + * PCI raw driver instance data. + */ +typedef struct DRVMAINPCIRAWDEV +{ + /** Pointer to the real PCI raw object. */ + PCIRawDev *pPCIRawDev; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Our PCI device connector interface. */ + PDMIPCIRAWCONNECTOR IConnector; + +} DRVMAINPCIRAWDEV, *PDRVMAINPCIRAWDEV; + +// +// constructor / destructor +// +PCIRawDev::PCIRawDev(Console *console) + : mParent(console), + mpDrv(NULL) +{ +} + +PCIRawDev::~PCIRawDev() +{ +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) PCIRawDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIPCIRAWCONNECTOR, &pThis->IConnector); + + return NULL; +} + + +/** + * @interface_method_impl{PDMIPCIRAWCONNECTOR,pfnDeviceConstructComplete} + */ +DECLCALLBACK(int) PCIRawDev::drvDeviceConstructComplete(PPDMIPCIRAWCONNECTOR pInterface, const char *pcszName, + uint32_t uHostPCIAddress, uint32_t uGuestPCIAddress, + int rc) +{ + PDRVMAINPCIRAWDEV pThis = RT_FROM_CPP_MEMBER(pInterface, DRVMAINPCIRAWDEV, IConnector); + Console *pConsole = pThis->pPCIRawDev->getParent(); + const ComPtr<IMachine>& machine = pConsole->i_machine(); + ComPtr<IVirtualBox> vbox; + + HRESULT hrc = machine->COMGETTER(Parent)(vbox.asOutParam()); + Assert(SUCCEEDED(hrc)); NOREF(hrc); + + ComPtr<IEventSource> es; + hrc = vbox->COMGETTER(EventSource)(es.asOutParam()); + Assert(SUCCEEDED(hrc)); + + Bstr bstrId; + hrc = machine->COMGETTER(Id)(bstrId.asOutParam()); + Assert(SUCCEEDED(hrc)); + + ComObjPtr<PCIDeviceAttachment> pda; + BstrFmt bstrName(pcszName); + pda.createObject(); + pda->init(machine, bstrName, uHostPCIAddress, uGuestPCIAddress, TRUE); + + Bstr msg(""); + if (RT_FAILURE(rc)) + msg = BstrFmt("runtime error %Rrc", rc); + + ::FireHostPCIDevicePlugEvent(es, bstrId.raw(), true /* plugged */, RT_SUCCESS_NP(rc) /* success */, pda, msg.raw()); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnReset} + */ +DECLCALLBACK(void) PCIRawDev::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV); + + if (pThis->pPCIRawDev) + pThis->pPCIRawDev->mpDrv = NULL; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnConstruct} + */ +DECLCALLBACK(int) PCIRawDev::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfgHandle, "Object\0")) + return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES; + + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * IBase. + */ + pDrvIns->IBase.pfnQueryInterface = PCIRawDev::drvQueryInterface; + + /* + * IConnector. + */ + pThis->IConnector.pfnDeviceConstructComplete = PCIRawDev::drvDeviceConstructComplete; + + /* + * Get the object pointer and update the mpDrv member. + */ + void *pv; + int rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No \"Object\" value! rc=%Rrc\n", rc)); + return rc; + } + + pThis->pPCIRawDev = (PCIRawDev *)pv; + pThis->pPCIRawDev->mpDrv = pThis; + + return VINF_SUCCESS; +} + +/** + * Main raw PCI driver registration record. + */ +const PDMDRVREG PCIRawDev::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "MainPciRaw", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main PCI raw driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_PCIRAW, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINPCIRAWDEV), + /* pfnConstruct */ + PCIRawDev::drvConstruct, + /* pfnDestruct */ + PCIRawDev::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Main/src-client/README.testing b/src/VBox/Main/src-client/README.testing new file mode 100644 index 00000000..6edd1083 --- /dev/null +++ b/src/VBox/Main/src-client/README.testing @@ -0,0 +1,16 @@ +This file contains some notes about things to try out to give the client-side +code in Main a reasonably thorough test. We will add cases of things which +have been known to fail in the past to this file as we discover them. + +*Linux guests*: changes should be tested against the following guests, or +equivalent vintages, preferably in 32-bit and 64-bit versions, with no +Additions and with 4.2 and 4.3 Additions and with single and dual screens: +CentOS 3, CentOS 5, CentOS 6, Current Ubuntu. + +Mouse interface changes: + * Test that mouse integration works. + * Test that disabling mouse integration on the fly works. + +Display interface changed: + * Test that dynamic resizing works. + * Test that switching to seamless mode and back works. diff --git a/src/VBox/Main/src-client/Recording.cpp b/src/VBox/Main/src-client/Recording.cpp new file mode 100644 index 00000000..907a5226 --- /dev/null +++ b/src/VBox/Main/src-client/Recording.cpp @@ -0,0 +1,945 @@ +/* $Id: Recording.cpp $ */ +/** @file + * Recording context code. + * + * This code employs a separate encoding thread per recording context + * to keep time spent in EMT as short as possible. Each configured VM display + * is represented by an own recording stream, which in turn has its own rendering + * queue. Common recording data across all recording streams is kept in a + * separate queue in the recording context to minimize data duplication and + * multiplexing overhead in EMT. + */ + +/* + * Copyright (C) 2012-2022 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 + */ + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_RECORDING +#include "LoggingNew.h" + +#include <stdexcept> +#include <vector> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include <VBox/err.h> +#include <VBox/com/VirtualBox.h> + +#include "ConsoleImpl.h" +#include "Recording.h" +#include "RecordingInternals.h" +#include "RecordingStream.h" +#include "RecordingUtils.h" +#include "WebMWriter.h" + +using namespace com; + +#ifdef DEBUG_andy +/** Enables dumping audio / video data for debugging reasons. */ +//# define VBOX_RECORDING_DUMP +#endif + + +/** + * Recording context constructor. + * + * @note Will throw rc when unable to create. + */ +RecordingContext::RecordingContext(void) + : m_pConsole(NULL) + , m_enmState(RECORDINGSTS_UNINITIALIZED) + , m_cStreamsEnabled(0) +{ + int vrc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(vrc)) + throw vrc; +} + +/** + * Recording context constructor. + * + * @param ptrConsole Pointer to console object this context is bound to (weak pointer). + * @param Settings Reference to recording settings to use for creation. + * + * @note Will throw rc when unable to create. + */ +RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings) + : m_pConsole(NULL) + , m_enmState(RECORDINGSTS_UNINITIALIZED) + , m_cStreamsEnabled(0) +{ + int vrc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = RecordingContext::createInternal(ptrConsole, Settings); + if (RT_FAILURE(vrc)) + throw vrc; +} + +RecordingContext::~RecordingContext(void) +{ + destroyInternal(); + + if (RTCritSectIsInitialized(&m_CritSect)) + RTCritSectDelete(&m_CritSect); +} + +/** + * Worker thread for all streams of a recording context. + * + * For video frames, this also does the RGB/YUV conversion and encoding. + */ +DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser) +{ + RecordingContext *pThis = (RecordingContext *)pvUser; + + /* Signal that we're up and rockin'. */ + RTThreadUserSignal(hThreadSelf); + + LogRel2(("Recording: Thread started\n")); + + for (;;) + { + int vrc = RTSemEventWait(pThis->m_WaitEvent, RT_INDEFINITE_WAIT); + AssertRCBreak(vrc); + + Log2Func(("Processing %zu streams\n", pThis->m_vecStreams.size())); + + /* Process common raw blocks (data which not has been encoded yet). */ + vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */); + + /** @todo r=andy This is inefficient -- as we already wake up this thread + * for every screen from Main, we here go again (on every wake up) through + * all screens. */ + RecordingStreams::iterator itStream = pThis->m_vecStreams.begin(); + while (itStream != pThis->m_vecStreams.end()) + { + RecordingStream *pStream = (*itStream); + + /* Hand-in common encoded blocks. */ + vrc = pStream->Process(pThis->m_mapBlocksEncoded); + if (RT_FAILURE(vrc)) + { + LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc)); + break; + } + + ++itStream; + } + + if (RT_FAILURE(vrc)) + LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc)); + + /* Keep going in case of errors. */ + + if (ASMAtomicReadBool(&pThis->m_fShutdown)) + { + LogFunc(("Thread is shutting down ...\n")); + break; + } + + } /* for */ + + LogRel2(("Recording: Thread ended\n")); + return VINF_SUCCESS; +} + +/** + * Notifies a recording context's encoding thread. + * + * @returns VBox status code. + */ +int RecordingContext::threadNotify(void) +{ + return RTSemEventSignal(m_WaitEvent); +} + +/** + * Worker function for processing common block data. + * + * @returns VBox status code. + * @param mapCommon Common block map to handle. + * @param msTimeout Timeout to use for maximum time spending to process data. + * Use RT_INDEFINITE_WAIT for processing all data. + * + * @note Runs in recording thread. + */ +int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout) +{ + Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout)); + + int vrc = VINF_SUCCESS; + + uint64_t const msStart = RTTimeMilliTS(); + RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin(); + while (itCommonBlocks != mapCommon.end()) + { + RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin(); + while (itBlock != itCommonBlocks->second->List.end()) + { + RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock); + switch (pBlockCommon->enmType) + { +#ifdef VBOX_WITH_AUDIO_RECORDING + case RECORDINGBLOCKTYPE_AUDIO: + { + PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData; + + RECORDINGFRAME Frame; + Frame.msTimestamp = pBlockCommon->msTimestamp; + Frame.Audio.pvBuf = pAudioFrame->pvBuf; + Frame.Audio.cbBuf = pAudioFrame->cbBuf; + + vrc = recordingCodecEncode(&m_CodecAudio, &Frame, NULL, NULL); + break; + } +#endif /* VBOX_WITH_AUDIO_RECORDING */ + default: + /* Skip unknown stuff. */ + break; + } + + itCommonBlocks->second->List.erase(itBlock); + delete pBlockCommon; + itBlock = itCommonBlocks->second->List.begin(); + + if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout) + break; + } + + /* If no entries are left over in the block map, remove it altogether. */ + if (itCommonBlocks->second->List.empty()) + { + delete itCommonBlocks->second; + mapCommon.erase(itCommonBlocks); + itCommonBlocks = mapCommon.begin(); + } + else + ++itCommonBlocks; + + if (RT_FAILURE(vrc)) + break; + } + + return vrc; +} + +/** + * Writes common block data (i.e. shared / the same) in all streams. + * + * The multiplexing is needed to supply all recorded (enabled) screens with the same + * data at the same given point in time. + * + * Currently this only is being used for audio data. + * + * @returns VBox status code. + * @param mapCommon Common block map to write data to. + * @param pCodec Pointer to codec instance which has written the data. + * @param pvData Pointer to written data (encoded). + * @param cbData Size (in bytes) of \a pvData. + * @param msTimestamp Absolute PTS (in ms) of the written data. + * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX. + */ +int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, + uint64_t msTimestamp, uint32_t uFlags) +{ + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n", + pCodec, cbData, msTimestamp, uFlags)); + + /** @todo Optimize this! Three allocations in here! */ + + RECORDINGBLOCKTYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO + ? RECORDINGBLOCKTYPE_AUDIO : RECORDINGBLOCKTYPE_UNKNOWN; + + AssertReturn(enmType != RECORDINGBLOCKTYPE_UNKNOWN, VERR_NOT_SUPPORTED); + + RecordingBlock *pBlock = new RecordingBlock(); + + switch (enmType) + { + case RECORDINGBLOCKTYPE_AUDIO: + { + PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME)); + AssertPtrReturn(pFrame, VERR_NO_MEMORY); + + pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData); + AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY); + pFrame->cbBuf = cbData; + + memcpy(pFrame->pvBuf, pvData, cbData); + + pBlock->enmType = enmType; + pBlock->pvData = pFrame; + pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData; + pBlock->cRefs = m_cStreamsEnabled; + pBlock->msTimestamp = msTimestamp; + pBlock->uFlags = uFlags; + + break; + } + + default: + AssertFailed(); + break; + } + + lock(); + + int vrc; + + try + { + RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp); + if (itBlocks == mapCommon.end()) + { + RecordingBlocks *pRecordingBlocks = new RecordingBlocks(); + pRecordingBlocks->List.push_back(pBlock); + + mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks)); + } + else + itBlocks->second->List.push_back(pBlock); + + vrc = VINF_SUCCESS; + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + vrc = VERR_NO_MEMORY; + } + + unlock(); + + if (RT_SUCCESS(vrc)) + vrc = threadNotify(); + + return vrc; +} + +#ifdef VBOX_WITH_AUDIO_RECORDING +/** + * Callback function for writing encoded audio data into the common encoded block map. + * + * This is called by the audio codec when finishing encoding audio data. + * + * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData + */ +/* static */ +DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, + uint64_t msAbsPTS, uint32_t uFlags, void *pvUser) +{ + RecordingContext *pThis = (RecordingContext *)pvUser; + return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags); +} + +/** + * Initializes the audio codec for a (multiplexing) recording context. + * + * @returns VBox status code. + * @param screenSettings Reference to recording screen settings to use for initialization. + */ +int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings) +{ + RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec; + + if (enmCodec == RecordingAudioCodec_None) + { + LogRel2(("Recording: No audio codec configured, skipping audio init\n")); + return VINF_SUCCESS; + } + + RECORDINGCODECCALLBACKS Callbacks; + Callbacks.pvUser = this; + Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback; + + int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec); + if (RT_SUCCESS(vrc)) + vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings); + + return vrc; +} +#endif /* VBOX_WITH_AUDIO_RECORDING */ + +/** + * Creates a recording context. + * + * @returns VBox status code. + * @param ptrConsole Pointer to console object this context is bound to (weak pointer). + * @param Settings Reference to recording settings to use for creation. + */ +int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings) +{ + int vrc = VINF_SUCCESS; + + /* Copy the settings to our context. */ + m_Settings = Settings; + +#ifdef VBOX_WITH_AUDIO_RECORDING + settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin(); + AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER); + + /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */ + settings::RecordingScreenSettings const &screen0Settings = itScreen0->second; + + vrc = this->audioInit(screen0Settings); + if (RT_FAILURE(vrc)) + return vrc; +#endif + + m_pConsole = ptrConsole; + + settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin(); + while (itScreen != m_Settings.mapScreens.end()) + { + RecordingStream *pStream = NULL; + try + { + pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second); + m_vecStreams.push_back(pStream); + if (itScreen->second.fEnabled) + m_cStreamsEnabled++; + LogFlowFunc(("pStream=%p\n", pStream)); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + break; + } + catch (int vrc_thrown) /* Catch rc thrown by constructor. */ + { + vrc = vrc_thrown; + break; + } + + ++itScreen; + } + + if (RT_SUCCESS(vrc)) + { + m_tsStartMs = RTTimeMilliTS(); + m_enmState = RECORDINGSTS_CREATED; + m_fShutdown = false; + + vrc = RTSemEventCreate(&m_WaitEvent); + AssertRCReturn(vrc, vrc); + } + + if (RT_FAILURE(vrc)) + destroyInternal(); + + return vrc; +} + +/** + * Starts a recording context by creating its worker thread. + * + * @returns VBox status code. + */ +int RecordingContext::startInternal(void) +{ + if (m_enmState == RECORDINGSTS_STARTED) + return VINF_SUCCESS; + + Assert(m_enmState == RECORDINGSTS_CREATED); + + int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0, + RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record"); + + if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */ + vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */); + + if (RT_SUCCESS(vrc)) + { + LogRel(("Recording: Started\n")); + m_enmState = RECORDINGSTS_STARTED; + } + else + Log(("Recording: Failed to start (%Rrc)\n", vrc)); + + return vrc; +} + +/** + * Stops a recording context by telling the worker thread to stop and finalizing its operation. + * + * @returns VBox status code. + */ +int RecordingContext::stopInternal(void) +{ + if (m_enmState != RECORDINGSTS_STARTED) + return VINF_SUCCESS; + + LogThisFunc(("Shutting down thread ...\n")); + + /* Set shutdown indicator. */ + ASMAtomicWriteBool(&m_fShutdown, true); + + /* Signal the thread and wait for it to shut down. */ + int vrc = threadNotify(); + if (RT_SUCCESS(vrc)) + vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL); + + lock(); + + if (RT_SUCCESS(vrc)) + { + LogRel(("Recording: Stopped\n")); + m_enmState = RECORDINGSTS_CREATED; + } + else + Log(("Recording: Failed to stop (%Rrc)\n", vrc)); + + unlock(); + + LogFlowThisFunc(("%Rrc\n", vrc)); + return vrc; +} + +/** + * Destroys a recording context, internal version. + */ +void RecordingContext::destroyInternal(void) +{ + lock(); + + if (m_enmState == RECORDINGSTS_UNINITIALIZED) + { + unlock(); + return; + } + + int vrc = stopInternal(); + AssertRCReturnVoid(vrc); + + vrc = RTSemEventDestroy(m_WaitEvent); + AssertRCReturnVoid(vrc); + + m_WaitEvent = NIL_RTSEMEVENT; + + RecordingStreams::iterator it = m_vecStreams.begin(); + while (it != m_vecStreams.end()) + { + RecordingStream *pStream = (*it); + + vrc = pStream->Uninit(); + AssertRC(vrc); + + delete pStream; + pStream = NULL; + + m_vecStreams.erase(it); + it = m_vecStreams.begin(); + } + + /* Sanity. */ + Assert(m_vecStreams.empty()); + Assert(m_mapBlocksRaw.size() == 0); + Assert(m_mapBlocksEncoded.size() == 0); + + m_enmState = RECORDINGSTS_UNINITIALIZED; + + unlock(); +} + +/** + * Returns a recording context's current settings. + * + * @returns The recording context's current settings. + */ +const settings::RecordingSettings &RecordingContext::GetConfig(void) const +{ + return m_Settings; +} + +/** + * Returns the recording stream for a specific screen. + * + * @returns Recording stream for a specific screen, or NULL if not found. + * @param uScreen Screen ID to retrieve recording stream for. + */ +RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const +{ + RecordingStream *pStream; + + try + { + pStream = m_vecStreams.at(uScreen); + } + catch (std::out_of_range &) + { + pStream = NULL; + } + + return pStream; +} + +/** + * Locks the recording context for serializing access. + * + * @returns VBox status code. + */ +int RecordingContext::lock(void) +{ + int vrc = RTCritSectEnter(&m_CritSect); + AssertRC(vrc); + return vrc; +} + +/** + * Unlocks the recording context for serializing access. + * + * @returns VBox status code. + */ +int RecordingContext::unlock(void) +{ + int vrc = RTCritSectLeave(&m_CritSect); + AssertRC(vrc); + return vrc; +} + +/** + * Retrieves a specific recording stream of a recording context. + * + * @returns Pointer to recording stream if found, or NULL if not found. + * @param uScreen Screen number of recording stream to look up. + */ +RecordingStream *RecordingContext::GetStream(unsigned uScreen) const +{ + return getStreamInternal(uScreen); +} + +/** + * Returns the number of configured recording streams for a recording context. + * + * @returns Number of configured recording streams. + */ +size_t RecordingContext::GetStreamCount(void) const +{ + return m_vecStreams.size(); +} + +/** + * Creates a new recording context. + * + * @returns VBox status code. + * @param ptrConsole Pointer to console object this context is bound to (weak pointer). + * @param Settings Reference to recording settings to use for creation. + */ +int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings) +{ + return createInternal(ptrConsole, Settings); +} + +/** + * Destroys a recording context. + */ +void RecordingContext::Destroy(void) +{ + destroyInternal(); +} + +/** + * Starts a recording context. + * + * @returns VBox status code. + */ +int RecordingContext::Start(void) +{ + return startInternal(); +} + +/** + * Stops a recording context. + */ +int RecordingContext::Stop(void) +{ + return stopInternal(); +} + +/** + * Returns if a specific recoding feature is enabled for at least one of the attached + * recording streams or not. + * + * @returns @c true if at least one recording stream has this feature enabled, or @c false if + * no recording stream has this feature enabled. + * @param enmFeature Recording feature to check for. + */ +bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature) +{ + lock(); + + RecordingStreams::const_iterator itStream = m_vecStreams.begin(); + while (itStream != m_vecStreams.end()) + { + if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature)) + { + unlock(); + return true; + } + ++itStream; + } + + unlock(); + + return false; +} + +/** + * Returns if this recording context is ready to start recording. + * + * @returns @c true if recording context is ready, @c false if not. + */ +bool RecordingContext::IsReady(void) +{ + lock(); + + const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED; + + unlock(); + + return fIsReady; +} + +/** + * Returns if this recording context is ready to accept new recording data for a given screen. + * + * @returns @c true if the specified screen is ready, @c false if not. + * @param uScreen Screen ID. + * @param msTimestamp Timestamp (PTS, in ms). Currently not being used. + */ +bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp) +{ + RT_NOREF(msTimestamp); + + lock(); + + bool fIsReady = false; + + if (m_enmState != RECORDINGSTS_STARTED) + { + const RecordingStream *pStream = getStreamInternal(uScreen); + if (pStream) + fIsReady = pStream->IsReady(); + + /* Note: Do not check for other constraints like the video FPS rate here, + * as this check then also would affect other (non-FPS related) stuff + * like audio data. */ + } + + unlock(); + + return fIsReady; +} + +/** + * Returns whether a given recording context has been started or not. + * + * @returns true if active, false if not. + */ +bool RecordingContext::IsStarted(void) +{ + lock(); + + const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED; + + unlock(); + + return fIsStarted; +} + +/** + * Checks if a specified limit for recording has been reached. + * + * @returns true if any limit has been reached. + */ +bool RecordingContext::IsLimitReached(void) +{ + lock(); + + LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled)); + + const bool fLimitReached = m_cStreamsEnabled == 0; + + unlock(); + + return fLimitReached; +} + +/** + * Checks if a specified limit for recording has been reached. + * + * @returns true if any limit has been reached. + * @param uScreen Screen ID. + * @param msTimestamp Timestamp (PTS, in ms) to check for. + */ +bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp) +{ + lock(); + + bool fLimitReached = false; + + const RecordingStream *pStream = getStreamInternal(uScreen); + if ( !pStream + || pStream->IsLimitReached(msTimestamp)) + { + fLimitReached = true; + } + + unlock(); + + return fLimitReached; +} + +/** + * Returns if a specific screen needs to be fed with an update or not. + * + * @returns @c true if an update is needed, @c false if not. + * @param uScreen Screen ID to retrieve update stats for. + * @param msTimestamp Timestamp (PTS, in ms). + */ +bool RecordingContext::NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp) +{ + lock(); + + bool fNeedsUpdate = false; + + if (m_enmState == RECORDINGSTS_STARTED) + { +#ifdef VBOX_WITH_AUDIO_RECORDING + if ( recordingCodecIsInitialized(&m_CodecAudio) + && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0) + { + fNeedsUpdate = true; + } +#endif /* VBOX_WITH_AUDIO_RECORDING */ + + if (!fNeedsUpdate) + { + const RecordingStream *pStream = getStreamInternal(uScreen); + if (pStream) + fNeedsUpdate = pStream->NeedsUpdate(msTimestamp); + } + } + + unlock(); + + return fNeedsUpdate; +} + +DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc) +{ + RT_NOREF(uScreen, rc); + LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc)); + + lock(); + + Assert(m_cStreamsEnabled); + m_cStreamsEnabled--; + + LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled)); + + unlock(); + + return VINF_SUCCESS; +} + +/** + * Sends an audio frame to the recording thread. + * + * @returns VBox status code. + * @param pvData Audio frame data to send. + * @param cbData Size (in bytes) of (encoded) audio frame data. + * @param msTimestamp Timestamp (PTS, in ms) of audio playback. + */ +int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp) +{ +#ifdef VBOX_WITH_AUDIO_RECORDING + return writeCommonData(m_mapBlocksRaw, &m_CodecAudio, + pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY); +#else + RT_NOREF(pvData, cbData, msTimestamp); + return VERR_NOT_SUPPORTED; +#endif +} + +/** + * Sends a video frame to the recording thread. + * + * @thread EMT + * + * @returns VBox status code. + * @param uScreen Screen number to send video frame to. + * @param x Starting x coordinate of the video frame. + * @param y Starting y coordinate of the video frame. + * @param uPixelFormat Pixel format. + * @param uBPP Bits Per Pixel (BPP). + * @param uBytesPerLine Bytes per scanline. + * @param uSrcWidth Width of the video frame. + * @param uSrcHeight Height of the video frame. + * @param puSrcData Pointer to video frame data. + * @param msTimestamp Timestamp (PTS, in ms). + */ +int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y, + uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine, + uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, + uint64_t msTimestamp) +{ + AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER); + AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER); + AssertReturn(puSrcData, VERR_INVALID_POINTER); + + lock(); + + RecordingStream *pStream = getStreamInternal(uScreen); + if (!pStream) + { + unlock(); + + AssertFailed(); + return VERR_NOT_FOUND; + } + + int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp); + + unlock(); + + if ( RT_SUCCESS(vrc) + && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */ + { + threadNotify(); + } + + return vrc; +} + diff --git a/src/VBox/Main/src-client/RecordingCodec.cpp b/src/VBox/Main/src-client/RecordingCodec.cpp new file mode 100644 index 00000000..58da5d0d --- /dev/null +++ b/src/VBox/Main/src-client/RecordingCodec.cpp @@ -0,0 +1,894 @@ +/* $Id: RecordingCodec.cpp $ */ +/** @file + * Recording codec wrapper. + */ + +/* + * Copyright (C) 2022 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 + */ + +/* This code makes use of Vorbis (libvorbis): + * + * Copyright (c) 2002-2020 Xiph.org Foundation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Xiph.org Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_GROUP LOG_GROUP_RECORDING +#include "LoggingNew.h" + +#include <VBox/com/string.h> +#include <VBox/err.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmaudioinline.h> + +#include "RecordingInternals.h" +#include "RecordingUtils.h" +#include "WebMWriter.h" + +#include <math.h> + + +/********************************************************************************************************************************* +* VPX (VP8 / VP9) codec * +*********************************************************************************************************************************/ + +#ifdef VBOX_WITH_LIBVPX +/** @copydoc RECORDINGCODECOPS::pfnInit */ +static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec) +{ + pCodec->cbScratch = _4K; + pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch); + AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY); + + pCodec->Parms.csFrame = 0; + pCodec->Parms.cbFrame = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */; + pCodec->Parms.msFrame = 1; /* 1ms per frame. */ + +# ifdef VBOX_WITH_LIBVPX_VP9 + vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx(); +# else /* Default is using VP8. */ + vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx(); +# endif + PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX; + + vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */); + if (rcv != VPX_CODEC_OK) + { + LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv))); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + /* Target bitrate in kilobits per second. */ + pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate; + /* Frame width. */ + pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth; + /* Frame height. */ + pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight; + /* ms per frame. */ + pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame; + pVPX->Cfg.g_timebase.den = 1000; + /* Disable multithreading. */ + pVPX->Cfg.g_threads = 0; + + /* Initialize codec. */ + rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */); + if (rcv != VPX_CODEC_OK) + { + LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv))); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420, + pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1)) + { + LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight)); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + /* Save a pointer to the first raw YUV plane. */ + pVPX->pu8YuvBuf = pVPX->RawImage.planes[0]; + + return VINF_SUCCESS; +} + +/** @copydoc RECORDINGCODECOPS::pfnDestroy */ +static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec) +{ + PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX; + + vpx_img_free(&pVPX->RawImage); + pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */ + + vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx); + Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv); + + return VINF_SUCCESS; +} + +/** @copydoc RECORDINGCODECOPS::pfnParseOptions */ +static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions) +{ + size_t pos = 0; + com::Utf8Str key, value; + while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos) + { + if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0) + { + const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX; + + if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0) + pVPX->uEncoderDeadline = VPX_DL_REALTIME; + else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0) + { + AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); + pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS; + } + else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0) + pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY; + else + pVPX->uEncoderDeadline = value.toUInt32(); + } + else + LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str())); + } /* while */ + + return VINF_SUCCESS; +} + +/** @copydoc RECORDINGCODECOPS::pfnEncode */ +static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame, + size_t *pcEncoded, size_t *pcbEncoded) +{ + RT_NOREF(pcEncoded, pcbEncoded); + + AssertPtrReturn(pFrame, VERR_INVALID_POINTER); + + PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr; + + int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt, + /* Destination */ + pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight, + /* Source */ + pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight); + + PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX; + + /* Presentation TimeStamp (PTS). */ + vpx_codec_pts_t pts = pFrame->msTimestamp; + vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx, + &pVPX->RawImage, + pts /* Timestamp */, + pCodec->Parms.Video.uDelayMs /* How long to show this frame */, + 0 /* Flags */, + pVPX->uEncoderDeadline /* Quality setting */); + if (rcv != VPX_CODEC_OK) + { + if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */ + LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv))); + return VERR_RECORDING_ENCODING_FAILED; + } + + pCodec->State.cEncErrors = 0; + + vpx_codec_iter_t iter = NULL; + vrc = VERR_NO_DATA; + for (;;) + { + const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter); + if (!pPkt) + break; + + switch (pPkt->kind) + { + case VPX_CODEC_CX_FRAME_PKT: + { + /* Calculate the absolute PTS of this frame (in ms). */ + uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000 + * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den; + + const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY); + + uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE; + if (fKeyframe) + fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY; + if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) + fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE; + + vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz, + tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser); + break; + } + + default: + AssertFailed(); + LogFunc(("Unexpected video packet type %ld\n", pPkt->kind)); + break; + } + } + + return vrc; +} +#endif /* VBOX_WITH_LIBVPX */ + + +/********************************************************************************************************************************* +* Ogg Vorbis codec * +*********************************************************************************************************************************/ + +#ifdef VBOX_WITH_LIBVORBIS +/** @copydoc RECORDINGCODECOPS::pfnInit */ +static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec) +{ + pCodec->cbScratch = _4K; + pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch); + AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY); + + const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps; + + /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */ + oggpack_buffer b; + oggpack_writeinit(&b); + + vorbis_info_init(&pCodec->Audio.Vorbis.info); + + int vorbis_rc; + if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */ + vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info, + PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps), + (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */); + else + vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps), + -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */); + if (vorbis_rc) + { + LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n", + pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc)); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info); + if (vorbis_rc) + { + LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc)); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + /* Initialize the analysis state and encoding storage. */ + vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info); + if (vorbis_rc) + { + vorbis_info_clear(&pCodec->Audio.Vorbis.info); + LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc)); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur); + if (vorbis_rc) + { + vorbis_info_clear(&pCodec->Audio.Vorbis.info); + LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc)); + return VERR_RECORDING_CODEC_INIT_FAILED; + } + + if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */ + pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT; + + return VINF_SUCCESS; +} + +/** @copydoc RECORDINGCODECOPS::pfnDestroy */ +static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec) +{ + PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis; + + vorbis_block_clear(&pVorbis->block_cur); + vorbis_dsp_clear (&pVorbis->dsp_state); + vorbis_info_clear (&pVorbis->info); + + return VINF_SUCCESS; +} + +/** @copydoc RECORDINGCODECOPS::pfnEncode */ +static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec, + const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded) +{ + const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps; + + Assert (pCodec->Parms.cbFrame); + AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER); + Assert (pFrame->Audio.cbBuf); + AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER); + AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER); + + int vrc = VINF_SUCCESS; + + int const cbFrame = PDMAudioPropsFrameSize(pPCMProps); + int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame); + + /* Write non-interleaved frames. */ + float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames); + int16_t *puSrc = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc); + + /* Convert samples into floating point. */ + /** @todo This is sloooooooooooow! Optimize this! */ + uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps); + AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED); + + float const div = 1.0f / 32768.0f; + + for(int f = 0; f < cFrames; f++) + { + buffer[0][f] = (float)puSrc[0] * div; + buffer[1][f] = (float)puSrc[1] * div; + puSrc += cChannels; + } + + int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames); + if (vorbis_rc) + { + LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc)); + return VERR_RECORDING_ENCODING_FAILED; + } + + if (pcEncoded) + *pcEncoded = 0; + if (pcbEncoded) + *pcbEncoded = 0; + + size_t cBlocksEncoded = 0; + size_t cBytesEncoded = 0; + + uint8_t *puDst = (uint8_t *)pCodec->pvScratch; + + while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */) + { + vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL); + if (vorbis_rc < 0) + { + LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc)); + vorbis_rc = 0; /* Reset */ + vrc = VERR_RECORDING_ENCODING_FAILED; + break; + } + + vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur); + if (vorbis_rc < 0) + { + LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc)); + vorbis_rc = 0; /* Reset */ + vrc = VERR_RECORDING_ENCODING_FAILED; + break; + } + + /* Vorbis expects us to flush packets one at a time directly to the container. + * + * If we flush more than one packet in a row, players can't decode this then. */ + ogg_packet op; + while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0) + { + cBytesEncoded += op.bytes; + AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW); + cBlocksEncoded++; + + vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs, + RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */, + pCodec->Callbacks.pvUser); + } + + RT_NOREF(puDst); + + /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */ + if (vorbis_rc < 0) + { + LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc)); + vorbis_rc = 0; /* Reset */ + vrc = VERR_RECORDING_ENCODING_FAILED; + break; + } + } + + if (vorbis_rc < 0) + { + LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc)); + return VERR_RECORDING_ENCODING_FAILED; + } + + if (pcbEncoded) + *pcbEncoded = 0; + if (pcEncoded) + *pcEncoded = 0; + + if (RT_FAILURE(vrc)) + LogRel(("Recording: Encoding Vorbis audio data failed, rc=%Rrc\n", vrc)); + + Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n", + pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc)); + + return vrc; +} + +static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec) +{ + int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */); + if (vorbis_rc) + { + LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc)); + return VERR_RECORDING_ENCODING_FAILED; + } + + return VINF_SUCCESS; +} +#endif /* VBOX_WITH_LIBVORBIS */ + + +/********************************************************************************************************************************* +* Codec API * +*********************************************************************************************************************************/ + +/** + * Initializes an audio codec. + * + * @returns VBox status code. + * @param pCodec Codec instance to initialize. + * @param pCallbacks Codec callback table to use for the codec. + * @param Settings Screen settings to use for initialization. + */ +static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec, + const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings) +{ + AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER); + + com::Utf8Str strCodec; + settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec); + LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str())); + + const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps; + + PDMAudioPropsInit(pPCMProps, + Settings.Audio.cBits / 8, + true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz); + pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */ + + if (pCallbacks) + memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS)); + + int vrc = VINF_SUCCESS; + + if (pCodec->Ops.pfnParseOptions) + vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions); + + if (RT_SUCCESS(vrc)) + vrc = pCodec->Ops.pfnInit(pCodec); + + if (RT_SUCCESS(vrc)) + { + Assert(PDMAudioPropsAreValid(pPCMProps)); + + uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */ + + LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n", + PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps))); + LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate)); + + if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */ + pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */ + + pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame); + pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame); + + LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n", + PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate)); + } + else + LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc)); + + return vrc; +} + +/** + * Initializes a video codec. + * + * @returns VBox status code. + * @param pCodec Codec instance to initialize. + * @param pCallbacks Codec callback table to use for the codec. + * @param Settings Screen settings to use for initialization. + */ +static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec, + const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings) +{ + com::Utf8Str strTemp; + settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp); + LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str())); + + pCodec->Parms.uBitrate = Settings.Video.ulRate; + pCodec->Parms.Video.uFPS = Settings.Video.ulFPS; + pCodec->Parms.Video.uWidth = Settings.Video.ulWidth; + pCodec->Parms.Video.uHeight = Settings.Video.ulHeight; + pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS; + + if (pCallbacks) + memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS)); + + AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */ + AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */ + + AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER); + AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER); + AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER); + + int vrc = VINF_SUCCESS; + + if (pCodec->Ops.pfnParseOptions) + vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions); + + if ( RT_SUCCESS(vrc) + && pCodec->Ops.pfnInit) + vrc = pCodec->Ops.pfnInit(pCodec); + + if (RT_SUCCESS(vrc)) + { + pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO; + pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */ + } + else + LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc)); + + return vrc; +} + +#ifdef VBOX_WITH_AUDIO_RECORDING +/** + * Lets an audio codec parse advanced options given from a string. + * + * @returns VBox status code. + * @param pCodec Codec instance to parse options for. + * @param strOptions Options string to parse. + */ +static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions) +{ + AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER); + + size_t pos = 0; + com::Utf8Str key, value; + while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos) + { + if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0) + { + if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0) + { + PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */); + } + else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0) + { + /* Stay with the defaults. */ + } + else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0) + { + PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */); + } + } + else + LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str())); + + } /* while */ + + return VINF_SUCCESS; +} +#endif + +static void recordingCodecReset(PRECORDINGCODEC pCodec) +{ + pCodec->State.tsLastWrittenMs = 0; + + pCodec->State.cEncErrors = 0; +#ifdef VBOX_WITH_STATISTICS + pCodec->STAM.cEncBlocks = 0; + pCodec->STAM.msEncTotal = 0; +#endif +} + +/** + * Common code for codec creation. + * + * @param pCodec Codec instance to create. + */ +static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec) +{ + RT_ZERO(pCodec->Ops); + RT_ZERO(pCodec->Callbacks); +} + +/** + * Creates an audio codec. + * + * @returns VBox status code. + * @param pCodec Codec instance to create. + * @param enmAudioCodec Audio codec to create. + */ +int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec) +{ + int vrc; + + recordingCodecCreateCommon(pCodec); + + switch (enmAudioCodec) + { +# ifdef VBOX_WITH_LIBVORBIS + case RecordingAudioCodec_OggVorbis: + { + pCodec->Ops.pfnInit = recordingCodecVorbisInit; + pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy; + pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions; + pCodec->Ops.pfnEncode = recordingCodecVorbisEncode; + pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize; + + vrc = VINF_SUCCESS; + break; + } +# endif /* VBOX_WITH_LIBVORBIS */ + + default: + LogRel(("Recording: Selected codec is not supported!\n")); + vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(vrc)) + { + pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO; + pCodec->Parms.enmAudioCodec = enmAudioCodec; + } + + return vrc; +} + +/** + * Creates a video codec. + * + * @returns VBox status code. + * @param pCodec Codec instance to create. + * @param enmVideoCodec Video codec to create. + */ +int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec) +{ + int vrc; + + recordingCodecCreateCommon(pCodec); + + switch (enmVideoCodec) + { +# ifdef VBOX_WITH_LIBVPX + case RecordingVideoCodec_VP8: + { + pCodec->Ops.pfnInit = recordingCodecVPXInit; + pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy; + pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions; + pCodec->Ops.pfnEncode = recordingCodecVPXEncode; + + vrc = VINF_SUCCESS; + break; + } +# endif /* VBOX_WITH_LIBVPX */ + + default: + vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(vrc)) + { + pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO; + pCodec->Parms.enmVideoCodec = enmVideoCodec; + } + + return vrc; +} + +/** + * Initializes a codec. + * + * @returns VBox status code. + * @param pCodec Codec to initialize. + * @param pCallbacks Codec callback table to use. Optional and may be NULL. + * @param Settings Settings to use for initializing the codec. + */ +int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings) +{ + recordingCodecReset(pCodec); + + int vrc; + if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO) + vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings); + else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO) + vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings); + else + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + + return vrc; +} + +/** + * Destroys an audio codec. + * + * @returns VBox status code. + * @param pCodec Codec to destroy. + */ +static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec) +{ + AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER); + + return pCodec->Ops.pfnDestroy(pCodec); +} + +/** + * Destroys a video codec. + * + * @returns VBox status code. + * @param pCodec Codec to destroy. + */ +static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec) +{ + AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER); + + return pCodec->Ops.pfnDestroy(pCodec); +} + +/** + * Destroys the codec. + * + * @returns VBox status code. + * @param pCodec Codec to destroy. + */ +int recordingCodecDestroy(PRECORDINGCODEC pCodec) +{ + if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID) + return VINF_SUCCESS; + + int vrc; + + if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO) + vrc = recordingCodecDestroyAudio(pCodec); + else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO) + vrc =recordingCodecDestroyVideo(pCodec); + else + AssertFailedReturn(VERR_NOT_SUPPORTED); + + if (RT_SUCCESS(vrc)) + { + if (pCodec->pvScratch) + { + Assert(pCodec->cbScratch); + RTMemFree(pCodec->pvScratch); + pCodec->pvScratch = NULL; + pCodec->cbScratch = 0; + } + + pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID; + pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None; + } + + return vrc; +} + +/** + * Feeds the codec encoder with data to encode. + * + * @returns VBox status code. + * @param pCodec Codec to use. + * @param pFrame Pointer to frame data to encode. + * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional. + * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional. + */ +int recordingCodecEncode(PRECORDINGCODEC pCodec, + const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded) +{ + AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED); + + size_t cEncoded, cbEncoded; + int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded); + if (RT_SUCCESS(vrc)) + { + pCodec->State.tsLastWrittenMs = pFrame->msTimestamp; + +#ifdef VBOX_WITH_STATISTICS + pCodec->STAM.cEncBlocks += cEncoded; + pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded; +#endif + if (pcEncoded) + *pcEncoded = cEncoded; + if (pcbEncoded) + *pcbEncoded = cbEncoded; + } + + return vrc; +} + +/** + * Tells the codec that has to finalize the stream. + * + * @returns VBox status code. + * @param pCodec Codec to finalize stream for. + */ +int recordingCodecFinalize(PRECORDINGCODEC pCodec) +{ + if (pCodec->Ops.pfnFinalize) + return pCodec->Ops.pfnFinalize(pCodec); + return VINF_SUCCESS; +} + +/** + * Returns whether the codec has been initialized or not. + * + * @returns @c true if initialized, or @c false if not. + * @param pCodec Codec to return initialization status for. + */ +bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec) +{ + return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */ +} + +/** + * Returns the number of writable bytes for a given timestamp. + * + * This basically is a helper function to respect the set frames per second (FPS). + * + * @returns Number of writable bytes. + * @param pCodec Codec to return number of writable bytes for. + * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for. + */ +uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp) +{ + Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n", + msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs)); + + if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs) + return 0; /* Too early for writing (respect set FPS). */ + + /* For now we just return the complete frame space. */ + AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n")); + return pCodec->Parms.cbFrame; +} diff --git a/src/VBox/Main/src-client/RecordingInternals.cpp b/src/VBox/Main/src-client/RecordingInternals.cpp new file mode 100644 index 00000000..15f8168c --- /dev/null +++ b/src/VBox/Main/src-client/RecordingInternals.cpp @@ -0,0 +1,158 @@ +/* $Id: RecordingInternals.cpp $ */ +/** @file + * Recording internals code. + */ + +/* + * Copyright (C) 2012-2022 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 "RecordingInternals.h" + +#include <iprt/assert.h> +#include <iprt/mem.h> + +#ifdef VBOX_WITH_AUDIO_RECORDING +/** + * Initializes a recording frame. + * + * @param pFrame Pointer to video frame to initialize. + * @param w Width (in pixel) of video frame. + * @param h Height (in pixel) of video frame. + * @param uBPP Bits per pixel (BPP). + * @param enmPixelFmt Pixel format to use. + */ +int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt) +{ + /* Calculate bytes per pixel and set pixel format. */ + const unsigned uBytesPerPixel = uBPP / 8; + const size_t cbRGBBuf = w * h * uBytesPerPixel; + AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER); + + pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf); + AssertPtrReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY); + pFrame->cbRGBBuf = cbRGBBuf; + + pFrame->uX = 0; + pFrame->uY = 0; + pFrame->uWidth = w; + pFrame->uHeight = h; + pFrame->enmPixelFmt = enmPixelFmt; + pFrame->uBPP = uBPP; + pFrame->uBytesPerLine = w * uBytesPerPixel; + + return VINF_SUCCESS; +} + +/** + * Destroys a recording audio frame. + * + * @param pFrame Pointer to audio frame to destroy. + */ +static void recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame) +{ + if (pFrame->pvBuf) + { + Assert(pFrame->cbBuf); + RTMemFree(pFrame->pvBuf); + pFrame->cbBuf = 0; + } +} + +/** + * Frees a previously allocated recording audio frame. + * + * @param pFrame Audio frame to free. The pointer will be invalid after return. + */ +void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame) +{ + if (!pFrame) + return; + + recordingAudioFrameDestroy(pFrame); + + RTMemFree(pFrame); + pFrame = NULL; +} +#endif + +/** + * Destroys a recording video frame. + * + * @param pFrame Pointer to video frame to destroy. + */ +void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame) +{ + if (pFrame->pu8RGBBuf) + { + Assert(pFrame->cbRGBBuf); + RTMemFree(pFrame->pu8RGBBuf); + pFrame->cbRGBBuf = 0; + } +} + +/** + * Frees a recording video frame. + * + * @returns VBox status code. + * @param pFrame Pointer to video frame to free. The pointer will be invalid after return. + */ +void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame) +{ + if (!pFrame) + return; + + RecordingVideoFrameDestroy(pFrame); + + RTMemFree(pFrame); +} + +/** + * Frees a recording frame. + * + * @returns VBox status code. + * @param pFrame Pointer to recording frame to free. The pointer will be invalid after return. + */ +void RecordingFrameFree(PRECORDINGFRAME pFrame) +{ + if (!pFrame) + return; + + switch (pFrame->enmType) + { +#ifdef VBOX_WITH_AUDIO_RECORDING + case RECORDINGFRAME_TYPE_AUDIO: + recordingAudioFrameDestroy(&pFrame->Audio); + break; +#endif + case RECORDINGFRAME_TYPE_VIDEO: + RecordingVideoFrameDestroy(&pFrame->Video); + break; + + default: + AssertFailed(); + break; + } + + RTMemFree(pFrame); + pFrame = NULL; +} + diff --git a/src/VBox/Main/src-client/RecordingStream.cpp b/src/VBox/Main/src-client/RecordingStream.cpp new file mode 100644 index 00000000..d8d4b4f3 --- /dev/null +++ b/src/VBox/Main/src-client/RecordingStream.cpp @@ -0,0 +1,1039 @@ +/* $Id: RecordingStream.cpp $ */ +/** @file + * Recording stream code. + */ + +/* + * Copyright (C) 2012-2022 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 + */ + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_RECORDING +#include "LoggingNew.h" + +#include <iprt/path.h> + +#ifdef VBOX_RECORDING_DUMP +# include <iprt/formats/bmp.h> +#endif + +#ifdef VBOX_WITH_AUDIO_RECORDING +# include <VBox/vmm/pdmaudioinline.h> +#endif + +#include "Recording.h" +#include "RecordingUtils.h" +#include "WebMWriter.h" + + +RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings) + : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED) +{ + int vrc2 = initInternal(a_pCtx, uScreen, Settings); + if (RT_FAILURE(vrc2)) + throw vrc2; +} + +RecordingStream::~RecordingStream(void) +{ + int vrc2 = uninitInternal(); + AssertRC(vrc2); +} + +/** + * Opens a recording stream. + * + * @returns VBox status code. + * @param screenSettings Recording settings to use. + */ +int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings) +{ + /* Sanity. */ + Assert(screenSettings.enmDest != RecordingDestination_None); + + int vrc; + + switch (screenSettings.enmDest) + { + case RecordingDestination_File: + { + Assert(screenSettings.File.strName.isNotEmpty()); + + const char *pszFile = screenSettings.File.strName.c_str(); + + RTFILE hFile = NIL_RTFILE; + vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(vrc)) + { + LogRel2(("Recording: Opened file '%s'\n", pszFile)); + + try + { + Assert(File.m_pWEBM == NULL); + File.m_pWEBM = new WebMWriter(); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(vrc)) + { + this->File.m_hFile = hFile; + m_ScreenSettings.File.strName = pszFile; + } + } + else + LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n", + pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc)); + + if (RT_FAILURE(vrc)) + { + if (hFile != NIL_RTFILE) + RTFileClose(hFile); + } + + break; + } + + default: + vrc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Returns the recording stream's used configuration. + * + * @returns The recording stream's used configuration. + */ +const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const +{ + return m_ScreenSettings; +} + +/** + * Checks if a specified limit for a recording stream has been reached, internal version. + * + * @returns @c true if any limit has been reached, @c false if not. + * @param msTimestamp Timestamp (PTS, in ms) to check for. + */ +bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const +{ + LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n", + msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs)); + + if ( m_ScreenSettings.ulMaxTimeS + && msTimestamp >= m_tsStartMs + (m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC)) + { + LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n", + m_uScreenID, m_ScreenSettings.ulMaxTimeS)); + return true; + } + + if (m_ScreenSettings.enmDest == RecordingDestination_File) + { + if (m_ScreenSettings.File.ulMaxSizeMB) + { + uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M; + if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB) + { + LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n", + m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB)); + return true; + } + } + + /* Check for available free disk space */ + if ( this->File.m_pWEBM + && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */ + { + LogRel(("Recording: Not enough free storage space available, stopping recording\n")); + return true; + } + } + + return false; +} + +/** + * Internal iteration main loop. + * Does housekeeping and recording context notification. + * + * @returns VBox status code. + * @param msTimestamp Timestamp (PTS, in ms). + */ +int RecordingStream::iterateInternal(uint64_t msTimestamp) +{ + if (!m_fEnabled) + return VINF_SUCCESS; + + int vrc; + + if (isLimitReachedInternal(msTimestamp)) + { + vrc = VINF_RECORDING_LIMIT_REACHED; + } + else + vrc = VINF_SUCCESS; + + AssertPtr(m_pCtx); + + switch (vrc) + { + case VINF_RECORDING_LIMIT_REACHED: + { + m_fEnabled = false; + + int vrc2 = m_pCtx->OnLimitReached(m_uScreenID, VINF_SUCCESS /* rc */); + AssertRC(vrc2); + break; + } + + default: + break; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Checks if a specified limit for a recording stream has been reached. + * + * @returns @c true if any limit has been reached, @c false if not. + * @param msTimestamp Timestamp (PTS, in ms) to check for. + */ +bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const +{ + if (!IsReady()) + return true; + + return isLimitReachedInternal(msTimestamp); +} + +/** + * Returns whether a recording stream is ready (e.g. enabled and active) or not. + * + * @returns @c true if ready, @c false if not. + */ +bool RecordingStream::IsReady(void) const +{ + return m_fEnabled; +} + +/** + * Returns if a recording stream needs to be fed with an update or not. + * + * @returns @c true if an update is needed, @c false if not. + * @param msTimestamp Timestamp (PTS, in ms). + */ +bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const +{ + return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0; +} + +/** + * Processes a recording stream. + * This function takes care of the actual encoding and writing of a certain stream. + * As this can be very CPU intensive, this function usually is called from a separate thread. + * + * @returns VBox status code. + * @param mapBlocksCommon Map of common block to process for this stream. + * + * @note Runs in recording thread. + */ +int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon) +{ + LogFlowFuncEnter(); + + lock(); + + if (!m_ScreenSettings.fEnabled) + { + unlock(); + return VINF_SUCCESS; + } + + int vrc = VINF_SUCCESS; + + RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin(); + while (itStreamBlocks != m_Blocks.Map.end()) + { + uint64_t const msTimestamp = itStreamBlocks->first; + RecordingBlocks *pBlocks = itStreamBlocks->second; + + AssertPtr(pBlocks); + + while (!pBlocks->List.empty()) + { + RecordingBlock *pBlock = pBlocks->List.front(); + AssertPtr(pBlock); + + switch (pBlock->enmType) + { + case RECORDINGBLOCKTYPE_VIDEO: + { + RECORDINGFRAME Frame; + Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData; + Frame.msTimestamp = msTimestamp; + + int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + break; + } + + default: + /* Note: Audio data already is encoded. */ + break; + } + + pBlocks->List.pop_front(); + delete pBlock; + } + + Assert(pBlocks->List.empty()); + delete pBlocks; + + m_Blocks.Map.erase(itStreamBlocks); + itStreamBlocks = m_Blocks.Map.begin(); + } + +#ifdef VBOX_WITH_AUDIO_RECORDING + /* Do we need to multiplex the common audio data to this stream? */ + if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio)) + { + /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be + * written to the screen's assigned recording stream. */ + RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin(); + while (itCommonBlocks != mapBlocksCommon.end()) + { + RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin(); + while (itBlock != itCommonBlocks->second->List.end()) + { + RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock); + switch (pBlockCommon->enmType) + { + case RECORDINGBLOCKTYPE_AUDIO: + { + PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData; + AssertPtr(pAudioFrame); + AssertPtr(pAudioFrame->pvBuf); + Assert(pAudioFrame->cbBuf); + + AssertPtr(this->File.m_pWEBM); + int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + break; + } + + default: + AssertFailed(); + break; + } + + Assert(pBlockCommon->cRefs); + pBlockCommon->cRefs--; + if (pBlockCommon->cRefs == 0) + { + itCommonBlocks->second->List.erase(itBlock); + delete pBlockCommon; + itBlock = itCommonBlocks->second->List.begin(); + } + else + ++itBlock; + } + + /* If no entries are left over in the block map, remove it altogether. */ + if (itCommonBlocks->second->List.empty()) + { + delete itCommonBlocks->second; + mapBlocksCommon.erase(itCommonBlocks); + itCommonBlocks = mapBlocksCommon.begin(); + } + else + ++itCommonBlocks; + + LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size())); + } + } +#else + RT_NOREF(mapBlocksCommon); +#endif /* VBOX_WITH_AUDIO_RECORDING */ + + unlock(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Sends a raw (e.g. not yet encoded) audio frame to the recording stream. + * + * @returns VBox status code. + * @param pvData Pointer to audio data. + * @param cbData Size (in bytes) of \a pvData. + * @param msTimestamp Timestamp (PTS, in ms). + */ +int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp) +{ + AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER); + AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */ + + Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp)); + + /* As audio data is common across all streams, re-route this to the recording context, where + * the data is being encoded and stored in the common blocks queue. */ + return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp); +} + +/** + * Sends a raw (e.g. not yet encoded) video frame to the recording stream. + * + * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording + * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current + * FPS setting. + * @param x Upper left (X) coordinate where the video frame starts. + * @param y Upper left (Y) coordinate where the video frame starts. + * @param uPixelFormat Pixel format of the video frame. + * @param uBPP Bits per pixel (BPP) of the video frame. + * @param uBytesPerLine Bytes per line of the video frame. + * @param uSrcWidth Width (in pixels) of the video frame. + * @param uSrcHeight Height (in pixels) of the video frame. + * @param puSrcData Actual pixel data of the video frame. + * @param msTimestamp Timestamp (PTS, in ms). + */ +int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine, + uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp) +{ + AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER); + AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */ + + lock(); + + Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp)); + + PRECORDINGVIDEOFRAME pFrame = NULL; + + int vrc = iterateInternal(msTimestamp); + if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */ + { + unlock(); + return vrc; + } + + do + { + int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2; + uint32_t w = uSrcWidth; + if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */ + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t destX; + if ((int)x < -xDiff) + { + w += xDiff + x; + x = -xDiff; + destX = 0; + } + else + destX = x + xDiff; + + uint32_t h = uSrcHeight; + int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2; + if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */ + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t destY; + if ((int)y < -yDiff) + { + h += yDiff + (int)y; + y = -yDiff; + destY = 0; + } + else + destY = y + yDiff; + + if ( destX > m_ScreenSettings.Video.ulWidth + || destY > m_ScreenSettings.Video.ulHeight) + { + vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */ + break; + } + + if (destX + w > m_ScreenSettings.Video.ulWidth) + w = m_ScreenSettings.Video.ulWidth - destX; + + if (destY + h > m_ScreenSettings.Video.ulHeight) + h = m_ScreenSettings.Video.ulHeight - destY; + + pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME)); + AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY); + + /* Calculate bytes per pixel and set pixel format. */ + const unsigned uBytesPerPixel = uBPP / 8; + if (uPixelFormat == BitmapFormat_BGR) + { + switch (uBPP) + { + case 32: + pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32; + break; + case 24: + pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24; + break; + case 16: + pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565; + break; + default: + AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED); + break; + } + } + else + AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED); + + const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth + * m_ScreenSettings.Video.ulHeight + * uBytesPerPixel; + AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER); + + pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf); + AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY); + pFrame->cbRGBBuf = cbRGBBuf; + pFrame->uWidth = uSrcWidth; + pFrame->uHeight = uSrcHeight; + + /* If the current video frame is smaller than video resolution we're going to encode, + * clear the frame beforehand to prevent artifacts. */ + if ( uSrcWidth < m_ScreenSettings.Video.ulWidth + || uSrcHeight < m_ScreenSettings.Video.ulHeight) + { + RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf); + } + + /* Calculate start offset in source and destination buffers. */ + uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel; + uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel; + +#ifdef VBOX_RECORDING_DUMP + BMPFILEHDR fileHdr; + RT_ZERO(fileHdr); + + BMPWIN3XINFOHDR coreHdr; + RT_ZERO(coreHdr); + + fileHdr.uType = BMP_HDR_MAGIC; + fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel)); + fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR)); + + coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR); + coreHdr.uWidth = w; + coreHdr.uHeight = h; + coreHdr.cPlanes = 1; + coreHdr.cBits = uBPP; + coreHdr.uXPelsPerMeter = 5000; + coreHdr.uYPelsPerMeter = 5000; + + char szFileName[RTPATH_MAX]; + RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID); + + RTFILE fh; + int vrc2 = RTFileOpen(&fh, szFileName, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(vrc2)) + { + RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL); + RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL); + } +#endif + Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel); + + /* Do the copy. */ + for (unsigned int i = 0; i < h; i++) + { + /* Overflow check. */ + Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine); + Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel); + + memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel); + +#ifdef VBOX_RECORDING_DUMP + if (RT_SUCCESS(rc2)) + RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL); +#endif + offSrc += uBytesPerLine; + offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel; + } + +#ifdef VBOX_RECORDING_DUMP + if (RT_SUCCESS(vrc2)) + RTFileClose(fh); +#endif + + } while (0); + + if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */ + { + RecordingBlock *pBlock = new RecordingBlock(); + if (pBlock) + { + AssertPtr(pFrame); + + pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO; + pBlock->pvData = pFrame; + pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf; + + try + { + RecordingBlocks *pRecordingBlocks = new RecordingBlocks(); + pRecordingBlocks->List.push_back(pBlock); + + Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end()); + m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks)); + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + + delete pBlock; + vrc = VERR_NO_MEMORY; + } + } + else + vrc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(vrc)) + RecordingVideoFrameFree(pFrame); + + unlock(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Initializes a recording stream. + * + * @returns VBox status code. + * @param pCtx Pointer to recording context. + * @param uScreen Screen number to use for this recording stream. + * @param Settings Recording screen configuration to use for initialization. + */ +int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings) +{ + return initInternal(pCtx, uScreen, Settings); +} + +/** + * Initializes a recording stream, internal version. + * + * @returns VBox status code. + * @param pCtx Pointer to recording context. + * @param uScreen Screen number to use for this recording stream. + * @param screenSettings Recording screen configuration to use for initialization. + */ +int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen, + const settings::RecordingScreenSettings &screenSettings) +{ + AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER); + + m_pCtx = pCtx; + m_uTrackAudio = UINT8_MAX; + m_uTrackVideo = UINT8_MAX; + m_tsStartMs = 0; + m_uScreenID = uScreen; +#ifdef VBOX_WITH_AUDIO_RECORDING + /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */ + m_pCodecAudio = m_pCtx->GetCodecAudio(); +#endif + m_ScreenSettings = screenSettings; + + settings::RecordingScreenSettings *pSettings = &m_ScreenSettings; + + int vrc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + + this->File.m_pWEBM = NULL; + this->File.m_hFile = NIL_RTFILE; + + vrc = open(*pSettings); + if (RT_FAILURE(vrc)) + return vrc; + + const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video); + const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio); + + if (fVideoEnabled) + { + vrc = initVideo(*pSettings); + if (RT_FAILURE(vrc)) + return vrc; + } + + switch (pSettings->enmDest) + { + case RecordingDestination_File: + { + Assert(pSettings->File.strName.isNotEmpty()); + const char *pszFile = pSettings->File.strName.c_str(); + + AssertPtr(File.m_pWEBM); + vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile, + fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None, + fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None); + if (RT_FAILURE(vrc)) + { + LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc)); + break; + } + + if (fVideoEnabled) + { + vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo, + pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS, + &m_uTrackVideo); + if (RT_FAILURE(vrc)) + { + LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc)); + break; + } + + LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n", + m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight, + pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo)); + } + +#ifdef VBOX_WITH_AUDIO_RECORDING + if (fAudioEnabled) + { + AssertPtr(m_pCodecAudio); + vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio, + pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits, + &m_uTrackAudio); + if (RT_FAILURE(vrc)) + { + LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc)); + break; + } + + LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n", + m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels, + pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio)); + } +#endif + + if ( fVideoEnabled +#ifdef VBOX_WITH_AUDIO_RECORDING + || fAudioEnabled +#endif + ) + { + char szWhat[32] = { 0 }; + if (fVideoEnabled) + RTStrCat(szWhat, sizeof(szWhat), "video"); +#ifdef VBOX_WITH_AUDIO_RECORDING + if (fAudioEnabled) + { + if (fVideoEnabled) + RTStrCat(szWhat, sizeof(szWhat), " + "); + RTStrCat(szWhat, sizeof(szWhat), "audio"); + } +#endif + LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile)); + } + + break; + } + + default: + AssertFailed(); /* Should never happen. */ + vrc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(vrc)) + { + m_enmState = RECORDINGSTREAMSTATE_INITIALIZED; + m_fEnabled = true; + m_tsStartMs = RTTimeProgramMilliTS(); + + return VINF_SUCCESS; + } + + int vrc2 = uninitInternal(); + AssertRC(vrc2); + + LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc)); + return vrc; +} + +/** + * Closes a recording stream. + * Depending on the stream's recording destination, this function closes all associated handles + * and finalizes recording. + * + * @returns VBox status code. + */ +int RecordingStream::close(void) +{ + int vrc = VINF_SUCCESS; + + switch (m_ScreenSettings.enmDest) + { + case RecordingDestination_File: + { + if (this->File.m_pWEBM) + vrc = this->File.m_pWEBM->Close(); + break; + } + + default: + AssertFailed(); /* Should never happen. */ + break; + } + + m_Blocks.Clear(); + + LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID)); + + if (RT_FAILURE(vrc)) + { + LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc)); + return vrc; + } + + switch (m_ScreenSettings.enmDest) + { + case RecordingDestination_File: + { + if (RTFileIsValid(this->File.m_hFile)) + { + vrc = RTFileClose(this->File.m_hFile); + if (RT_SUCCESS(vrc)) + { + LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str())); + } + else + { + LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc)); + break; + } + } + + WebMWriter *pWebMWriter = this->File.m_pWEBM; + AssertPtr(pWebMWriter); + + if (pWebMWriter) + { + /* If no clusters (= data) was written, delete the file again. */ + if (pWebMWriter->GetClusters() == 0) + { + int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str()); + AssertRC(vrc2); /* Ignore rc on non-debug builds. */ + } + + delete pWebMWriter; + pWebMWriter = NULL; + + this->File.m_pWEBM = NULL; + } + break; + } + + default: + vrc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Uninitializes a recording stream. + * + * @returns VBox status code. + */ +int RecordingStream::Uninit(void) +{ + return uninitInternal(); +} + +/** + * Uninitializes a recording stream, internal version. + * + * @returns VBox status code. + */ +int RecordingStream::uninitInternal(void) +{ + if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED) + return VINF_SUCCESS; + + int vrc = close(); + if (RT_FAILURE(vrc)) + return vrc; + +#ifdef VBOX_WITH_AUDIO_RECORDING + m_pCodecAudio = NULL; +#endif + + if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video)) + { + vrc = recordingCodecFinalize(&m_CodecVideo); + if (RT_SUCCESS(vrc)) + vrc = recordingCodecDestroy(&m_CodecVideo); + } + + if (RT_SUCCESS(vrc)) + { + RTCritSectDelete(&m_CritSect); + + m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED; + m_fEnabled = false; + } + + return vrc; +} + +/** + * Writes encoded data to a WebM file instance. + * + * @returns VBox status code. + * @param pCodec Codec which has encoded the data. + * @param pvData Encoded data to write. + * @param cbData Size (in bytes) of \a pvData. + * @param msAbsPTS Absolute PTS (in ms) of written data. + * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX. + */ +int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, + uint64_t msAbsPTS, uint32_t uFlags) +{ + AssertPtr(this->File.m_pWEBM); + AssertPtr(pvData); + Assert (cbData); + + WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE; + if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE)) + { + /* All set. */ + } + else + { + if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY) + blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME; + if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE) + blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE; + } + + return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO + ? m_uTrackAudio : m_uTrackVideo, + pvData, cbData, msAbsPTS, blockFlags); +} + +/** + * Codec callback for writing encoded data to a recording stream. + * + * @returns VBox status code. + * @param pCodec Codec which has encoded the data. + * @param pvData Encoded data to write. + * @param cbData Size (in bytes) of \a pvData. + * @param msAbsPTS Absolute PTS (in ms) of written data. + * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX. + * @param pvUser User-supplied pointer. + */ +/* static */ +DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, + uint64_t msAbsPTS, uint32_t uFlags, void *pvUser) +{ + RecordingStream *pThis = (RecordingStream *)pvUser; + AssertPtr(pThis); + + /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */ + return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags); +} + +/** + * Initializes the video recording for a recording stream. + * + * @returns VBox status code. + * @param screenSettings Screen settings to use. + */ +int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings) +{ + /* Sanity. */ + AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER); + AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER); + AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER); + AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER); + + PRECORDINGCODEC pCodec = &m_CodecVideo; + + RECORDINGCODECCALLBACKS Callbacks; + Callbacks.pvUser = this; + Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback; + + int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec); + if (RT_SUCCESS(vrc)) + vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings); + + if (RT_FAILURE(vrc)) + LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc)); + + return vrc; +} + +/** + * Locks a recording stream. + */ +void RecordingStream::lock(void) +{ + int vrc = RTCritSectEnter(&m_CritSect); + AssertRC(vrc); +} + +/** + * Unlocks a locked recording stream. + */ +void RecordingStream::unlock(void) +{ + int vrc = RTCritSectLeave(&m_CritSect); + AssertRC(vrc); +} + diff --git a/src/VBox/Main/src-client/RecordingUtils.cpp b/src/VBox/Main/src-client/RecordingUtils.cpp new file mode 100644 index 00000000..b23c19dd --- /dev/null +++ b/src/VBox/Main/src-client/RecordingUtils.cpp @@ -0,0 +1,290 @@ +/* $Id: RecordingUtils.cpp $ */ +/** @file + * Recording utility code. + */ + +/* + * Copyright (C) 2012-2022 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 "Recording.h" +#include "RecordingUtils.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#ifdef DEBUG +#include <iprt/file.h> +#include <iprt/formats/bmp.h> +#endif + + +/** + * Convert an image to YUV420p format. + * + * @return \c true on success, \c false on failure. + * @param aDstBuf The destination image buffer. + * @param aDstWidth Width (in pixel) of destination buffer. + * @param aDstHeight Height (in pixel) of destination buffer. + * @param aSrcBuf The source image buffer. + * @param aSrcWidth Width (in pixel) of source buffer. + * @param aSrcHeight Height (in pixel) of source buffer. + */ +template <class T> +inline bool recordingUtilsColorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight, + uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight) +{ + RT_NOREF(aDstWidth, aDstHeight); + + AssertReturn(!(aSrcWidth & 1), false); + AssertReturn(!(aSrcHeight & 1), false); + + bool fRc = true; + T iter1(aSrcWidth, aSrcHeight, aSrcBuf); + T iter2 = iter1; + iter2.skip(aSrcWidth); + unsigned cPixels = aSrcWidth * aSrcHeight; + unsigned offY = 0; + unsigned offU = cPixels; + unsigned offV = cPixels + cPixels / 4; + unsigned const cyHalf = aSrcHeight / 2; + unsigned const cxHalf = aSrcWidth / 2; + for (unsigned i = 0; i < cyHalf && fRc; ++i) + { + for (unsigned j = 0; j < cxHalf; ++j) + { + unsigned red, green, blue; + fRc = iter1.getRGB(&red, &green, &blue); + AssertReturn(fRc, false); + aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16; + unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4; + unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4; + + fRc = iter1.getRGB(&red, &green, &blue); + AssertReturn(fRc, false); + aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16; + u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4; + v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4; + + fRc = iter2.getRGB(&red, &green, &blue); + AssertReturn(fRc, false); + aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16; + u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4; + v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4; + + fRc = iter2.getRGB(&red, &green, &blue); + AssertReturn(fRc, false); + aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16; + u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4; + v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4; + + aDstBuf[offU] = u; + aDstBuf[offV] = v; + offY += 2; + ++offU; + ++offV; + } + + iter1.skip(aSrcWidth); + iter2.skip(aSrcWidth); + offY += aSrcWidth; + } + + return true; +} + +/** + * Convert an image to RGB24 format. + * + * @returns true on success, false on failure. + * @param aWidth Width of image. + * @param aHeight Height of image. + * @param aDestBuf An allocated memory buffer large enough to hold the + * destination image (i.e. width * height * 12bits). + * @param aSrcBuf The source image as an array of bytes. + */ +template <class T> +inline bool RecordingUtilsColorConvWriteRGB24(unsigned aWidth, unsigned aHeight, + uint8_t *aDestBuf, uint8_t *aSrcBuf) +{ + enum { PIX_SIZE = 3 }; + bool fRc = true; + AssertReturn(0 == (aWidth & 1), false); + AssertReturn(0 == (aHeight & 1), false); + T iter(aWidth, aHeight, aSrcBuf); + unsigned cPixels = aWidth * aHeight; + for (unsigned i = 0; i < cPixels && fRc; ++i) + { + unsigned red, green, blue; + fRc = iter.getRGB(&red, &green, &blue); + if (fRc) + { + aDestBuf[i * PIX_SIZE ] = red; + aDestBuf[i * PIX_SIZE + 1] = green; + aDestBuf[i * PIX_SIZE + 2] = blue; + } + } + return fRc; +} + +/** + * Converts a RGB to YUV buffer. + * + * @returns IPRT status code. + * @param enmPixelFormat Pixel format to use for conversion. + * @param paDst Pointer to destination buffer. + * @param uDstWidth Width (X, in pixels) of destination buffer. + * @param uDstHeight Height (Y, in pixels) of destination buffer. + * @param paSrc Pointer to source buffer. + * @param uSrcWidth Width (X, in pixels) of source buffer. + * @param uSrcHeight Height (Y, in pixels) of source buffer. + */ +int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat, + uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight, + uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight) +{ + switch (enmPixelFormat) + { + case RECORDINGPIXELFMT_RGB32: + if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight, + paSrc, uSrcWidth, uSrcHeight)) + return VERR_INVALID_PARAMETER; + break; + case RECORDINGPIXELFMT_RGB24: + if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight, + paSrc, uSrcWidth, uSrcHeight)) + return VERR_INVALID_PARAMETER; + break; + case RECORDINGPIXELFMT_RGB565: + if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight, + paSrc, uSrcWidth, uSrcHeight)) + return VERR_INVALID_PARAMETER; + break; + default: + AssertFailed(); + return VERR_NOT_SUPPORTED; + } + return VINF_SUCCESS; +} + +#ifdef DEBUG +/** + * Dumps a video recording frame to a bitmap (BMP) file, extended version. + * + * @returns VBox status code. + * @param pu8RGBBuf Pointer to actual RGB frame data. + * @param cbRGBBuf Size (in bytes) of \a pu8RGBBuf. + * @param pszPath Absolute path to dump file to. Must exist. + * Specify NULL to use the system's temp directory. + * Existing frame files will be overwritten. + * @param pszPrefx Naming prefix to use. Optional and can be NULL. + * @param uWidth Width (in pixel) to write. + * @param uHeight Height (in pixel) to write. + * @param uBPP Bits in pixel. + */ +int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx, + uint16_t uWidth, uint32_t uHeight, uint8_t uBPP) +{ + RT_NOREF(cbRGBBuf); + + const uint8_t uBytesPerPixel = uBPP / 8 /* Bits */; + const size_t cbData = uWidth * uHeight * uBytesPerPixel; + + if (!cbData) /* No data to write? Bail out early. */ + return VINF_SUCCESS; + + BMPFILEHDR fileHdr; + RT_ZERO(fileHdr); + + BMPWIN3XINFOHDR coreHdr; + RT_ZERO(coreHdr); + + fileHdr.uType = BMP_HDR_MAGIC; + fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + cbData); + fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR)); + + coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR); + coreHdr.uWidth = uWidth ; + coreHdr.uHeight = uHeight; + coreHdr.cPlanes = 1; + coreHdr.cBits = uBPP; + coreHdr.uXPelsPerMeter = 5000; + coreHdr.uYPelsPerMeter = 5000; + + static uint64_t s_iCount = 0; + + char szPath[RTPATH_MAX]; + if (!pszPath) + RTPathTemp(szPath, sizeof(szPath)); + + char szFileName[RTPATH_MAX]; + if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s/RecDump-%04RU64-%s-w%RU16h%RU16.bmp", + pszPath ? pszPath : szPath, s_iCount, pszPrefx ? pszPrefx : "Frame", uWidth, uHeight) <= 0) + { + return VERR_BUFFER_OVERFLOW; + } + + s_iCount++; + + RTFILE fh; + int vrc = RTFileOpen(&fh, szFileName, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(vrc)) + { + RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL); + RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL); + + /* Bitmaps (DIBs) are stored upside-down (thanks, OS/2), so work from the bottom up. */ + uint32_t offSrc = (uHeight * uWidth * uBytesPerPixel) - uWidth * uBytesPerPixel; + + /* Do the copy. */ + for (unsigned int i = 0; i < uHeight; i++) + { + RTFileWrite(fh, pu8RGBBuf + offSrc, uWidth * uBytesPerPixel, NULL); + offSrc -= uWidth * uBytesPerPixel; + } + + RTFileClose(fh); + } + + return vrc; +} + +/** + * Dumps a video recording frame to a bitmap (BMP) file. + * + * @returns VBox status code. + * @param pFrame Video frame to dump. + */ +int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame) +{ + AssertReturn(pFrame->enmType == RECORDINGFRAME_TYPE_VIDEO, VERR_INVALID_PARAMETER); + return RecordingUtilsDbgDumpFrameEx(pFrame->Video.pu8RGBBuf, pFrame->Video.cbRGBBuf, + NULL /* Use temp directory */, NULL /* pszPrefix */, + pFrame->Video.uWidth, pFrame->Video.uHeight, pFrame->Video.uBPP); +} +#endif /* DEBUG */ + diff --git a/src/VBox/Main/src-client/RemoteUSBBackend.cpp b/src/VBox/Main/src-client/RemoteUSBBackend.cpp new file mode 100644 index 00000000..8a8077f8 --- /dev/null +++ b/src/VBox/Main/src-client/RemoteUSBBackend.cpp @@ -0,0 +1,1414 @@ +/* $Id: RemoteUSBBackend.cpp $ */ +/** @file + * VirtualBox Remote USB backend + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_USB_REMOTE +#include "LoggingNew.h" + +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" +#include "RemoteUSBBackend.h" +#include "RemoteUSBDeviceImpl.h" + +#include <VBox/RemoteDesktop/VRDE.h> +#include <VBox/vrdpusb.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include <VBox/vusb.h> + +#include <iprt/time.h> + +/** @page pg_vrdb_usb Async Remote USB + * + * + * USB backend functions are called in EMT so care must be taken to prevent + * delays in the functions execution. + * + * Among 11 backend functions 10 just return a success indicator. + * + * Such a function usually will check pending error code and if everything is ok, + * submit asynchronous RDP request and return success immediately. + * + * On actual completion of each request, the status will be saved as + * pending, so in case of an error all further functions will fail with + * device disconnected condition. + * @todo May be a device disconnect notification for console is required? + * + * The only remaining function that needs special processing is + * the reap_urb. It has a timeout parameter. + * Normally, the timeout is 0, as result of polling from VUSB frame timer. + * It is ok for async processing, the backend will periodically reap urbs from client. + * And already reaped URBs from client will be returned for the call. + * Exceptions: + * 1) during device initialization, when obtaining device descriptions + * the timeout is -1, and the request is expected to be processed synchronously. + * It looks like only 3 URBs with some information are retrieved that way. + * Probably, one can return this information in DEVICE_LIST together with the + * device description and when such request are submitted, just return + * the prefetched data. + * 2) during suspend timeout is non zero (10 or less milliseconds), + * and URB's are reaped for about 1 second. But here network delays + * will not affect the timeout, so it is ok. + * + * + * @section sub_vrdb_usb_dad Device attaching/detaching + * + * Devices are attached when client is connected or when a new device is connected to client. + * Devices are detached when client is disconnected (all devices) or a device is disconnected + * the client side. + * + * The backend polls the client for list of attached USB devices from RemoteUSBThread. + * + */ + +/* Queued URB submitted to VRDP client. */ +typedef struct _REMOTEUSBQURB +{ + struct _REMOTEUSBQURB *next; + struct _REMOTEUSBQURB *prev; + + PREMOTEUSBDEVICE pDevice; /* Device, the URB is queued for. */ + + uint32_t u32Handle; /* The handle of the URB. Generated by the Remote USB backend. */ + + void *pvData; /* Pointer to URB data allocated by VUSB. */ + void *pvURB; /* Pointer to URB known to VUSB. */ + + uint32_t u32Len; /* Data length returned by the VRDP client. */ + uint32_t u32Err; /* URB error code returned by the VRDP client. */ + + bool fCompleted; /* The URB has been returned back by VRDP client. */ + bool fInput; /* This URB receives data from the client. */ + + uint32_t u32TransferredLen; /* For VRDE_USB_DIRECTION_OUT URBs = bytes written. + * For VRDE_USB_DIRECTION_IN URBs = bytes received. + */ +} REMOTEUSBQURB; + +/* Remote USB device instance data. */ +typedef struct _REMOTEUSBDEVICE +{ + struct _REMOTEUSBDEVICE *prev; + struct _REMOTEUSBDEVICE *next; + + RemoteUSBBackend *pOwner; + + VRDEUSBDEVID id; /* The remote identifier, assigned by client. */ + + uint32_t u32ClientId; /* The identifier of the remote client. */ + + REMOTEUSBQURB *pHeadQURBs; /* List of URBs queued for the device. */ + REMOTEUSBQURB *pTailQURBs; + + volatile uint32_t hURB; /* Source for URB's handles. */ + bool fFailed; /* True if an operation has failed for the device. */ + RTCRITSECT critsect; /* Protects the queued urb list. */ + volatile bool fWokenUp; /* Flag whther the reaper was woken up. */ +} REMOTEUSBDEVICE; + + + +static void requestDevice(REMOTEUSBDEVICE *pDevice) +{ + int vrc = RTCritSectEnter(&pDevice->critsect); + AssertRC(vrc); +} + +static void releaseDevice(REMOTEUSBDEVICE *pDevice) +{ + RTCritSectLeave(&pDevice->critsect); +} + +static REMOTEUSBQURB *qurbAlloc(PREMOTEUSBDEVICE pDevice) +{ + /** @todo reuse URBs. */ + REMOTEUSBQURB *pQURB = (REMOTEUSBQURB *)RTMemAllocZ (sizeof (REMOTEUSBQURB)); + + if (pQURB) + { + pQURB->pDevice = pDevice; + } + + return pQURB; +} + +static void qurbFree (REMOTEUSBQURB *pQURB) +{ + RTMemFree (pQURB); + return; +} + + +/* Called by VRDP server when the client responds to a request on USB channel. */ +DECLCALLBACK(int) USBClientResponseCallback(void *pv, uint32_t u32ClientId, uint8_t code, const void *pvRet, uint32_t cbRet) +{ + RT_NOREF(u32ClientId); + int vrc = VINF_SUCCESS; + + LogFlow(("USBClientResponseCallback: id = %d, pv = %p, code = %d, pvRet = %p, cbRet = %d\n", + u32ClientId, pv, code, pvRet, cbRet)); + + RemoteUSBBackend *pThis = (RemoteUSBBackend *)pv; + + switch (code) + { + case VRDE_USB_REQ_DEVICE_LIST: + { + vrc = pThis->saveDeviceList(pvRet, cbRet); + } break; + + case VRDE_USB_REQ_NEGOTIATE: + { + if (pvRet && cbRet >= sizeof(VRDEUSBREQNEGOTIATERET)) + { + VRDEUSBREQNEGOTIATERET *pret = (VRDEUSBREQNEGOTIATERET *)pvRet; + + vrc = pThis->negotiateResponse(pret, cbRet); + } + else + { + Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n", + pvRet, cbRet, sizeof(VRDEUSBREQNEGOTIATERET))); + + vrc = VERR_INVALID_PARAMETER; + } + } break; + + case VRDE_USB_REQ_REAP_URB: + { + vrc = pThis->reapURB(pvRet, cbRet); + + LogFlow(("USBClientResponseCallback: reap URB, rc = %Rrc.\n", vrc)); + } break; + + case VRDE_USB_REQ_QUEUE_URB: + case VRDE_USB_REQ_CLOSE: + case VRDE_USB_REQ_CANCEL_URB: + { + /* Do nothing, actually this should not happen. */ + Log(("USBClientResponseCallback: WARNING: response to a request %d is not expected!!!\n", code)); + } break; + + case VRDE_USB_REQ_OPEN: + case VRDE_USB_REQ_RESET: + case VRDE_USB_REQ_SET_CONFIG: + case VRDE_USB_REQ_CLAIM_INTERFACE: + case VRDE_USB_REQ_RELEASE_INTERFACE: + case VRDE_USB_REQ_INTERFACE_SETTING: + case VRDE_USB_REQ_CLEAR_HALTED_EP: + { + /* + * Device specific responses with status codes. + */ + if (pvRet && cbRet >= sizeof(VRDEUSBREQRETHDR)) + { + VRDEUSBREQRETHDR *pret = (VRDEUSBREQRETHDR *)pvRet; + + if (pret->status != VRDE_USB_STATUS_SUCCESS) + { + REMOTEUSBDEVICE *pDevice = pThis->deviceFromId(pret->id); + + if (!pDevice) + { + Log(("USBClientResponseCallback: WARNING: invalid device id %08X.\n", pret->id)); + vrc = VERR_INVALID_PARAMETER; + } + else + { + Log(("USBClientResponseCallback: WARNING: the operation failed, status %d\n", pret->status)); + pDevice->fFailed = true; + } + } + } + else + { + Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n", + pvRet, cbRet, sizeof(VRDEUSBREQRETHDR))); + } + } break; + + default: + { + Log(("USBClientResponseCallback: WARNING: invalid code %d\n", code)); + } break; + } + + return vrc; +} + +/* + * Backend entry points. + */ +static DECLCALLBACK(int) iface_Open(PREMOTEUSBBACKEND pInstance, const char *pszAddress, + size_t cbAddress, PREMOTEUSBDEVICE *ppDevice) +{ + RT_NOREF(cbAddress); + int vrc = VINF_SUCCESS; + + RemoteUSBBackend *pThis = (RemoteUSBBackend *)pInstance; + + REMOTEUSBDEVICE *pDevice = (REMOTEUSBDEVICE *)RTMemAllocZ(sizeof(REMOTEUSBDEVICE)); + + if (!pDevice) + { + vrc = VERR_NO_MEMORY; + } + else + { + /* Parse given address string to find the device identifier. + * The format is "REMOTEUSB0xAAAABBBB&0xCCCCDDDD", where AAAABBBB is hex device identifier + * and CCCCDDDD is hex client id. + */ + if (strncmp(pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) != 0) + { + AssertFailed(); + vrc = VERR_INVALID_PARAMETER; + } + else + { + /* Initialize the device structure. */ + pDevice->pOwner = pThis; + pDevice->fWokenUp = false; + + vrc = RTCritSectInit(&pDevice->critsect); + AssertRC(vrc); + + if (RT_SUCCESS(vrc)) + { + pDevice->id = RTStrToUInt32(&pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN]); + + size_t l = strlen(pszAddress); + + if (l >= REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678&0x87654321")) + { + const char *p = &pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678")]; + if (*p == '&') + { + pDevice->u32ClientId = RTStrToUInt32(p + 1); + } + else + { + AssertFailed(); + vrc = VERR_INVALID_PARAMETER; + } + } + else + { + AssertFailed(); + vrc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(vrc)) + { + VRDE_USB_REQ_OPEN_PARM parm; + + parm.code = VRDE_USB_REQ_OPEN; + parm.id = pDevice->id; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + } + } + } + } + + if (RT_SUCCESS(vrc)) + { + *ppDevice = pDevice; + + pThis->addDevice(pDevice); + } + else + { + RTMemFree(pDevice); + } + + return vrc; +} + +static DECLCALLBACK(void) iface_Close(PREMOTEUSBDEVICE pDevice) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + VRDE_USB_REQ_CLOSE_PARM parm; + + parm.code = VRDE_USB_REQ_CLOSE; + parm.id = pDevice->id; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + pThis->removeDevice(pDevice); + + if (RTCritSectIsInitialized(&pDevice->critsect)) + { + RTCritSectDelete(&pDevice->critsect); + } + + RTMemFree(pDevice); + + return; +} + +static DECLCALLBACK(int) iface_Reset(PREMOTEUSBDEVICE pDevice) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_RESET_PARM parm; + + parm.code = VRDE_USB_REQ_RESET; + parm.id = pDevice->id; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) iface_SetConfig(PREMOTEUSBDEVICE pDevice, uint8_t u8Cfg) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_SET_CONFIG_PARM parm; + + parm.code = VRDE_USB_REQ_SET_CONFIG; + parm.id = pDevice->id; + parm.configuration = u8Cfg; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) iface_ClaimInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_CLAIM_INTERFACE_PARM parm; + + parm.code = VRDE_USB_REQ_CLAIM_INTERFACE; + parm.id = pDevice->id; + parm.iface = u8Ifnum; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) iface_ReleaseInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_RELEASE_INTERFACE_PARM parm; + + parm.code = VRDE_USB_REQ_RELEASE_INTERFACE; + parm.id = pDevice->id; + parm.iface = u8Ifnum; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) iface_InterfaceSetting(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum, uint8_t u8Setting) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_INTERFACE_SETTING_PARM parm; + + parm.code = VRDE_USB_REQ_INTERFACE_SETTING; + parm.id = pDevice->id; + parm.iface = u8Ifnum; + parm.setting = u8Setting; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) iface_ClearHaltedEP(PREMOTEUSBDEVICE pDevice, uint8_t u8Ep) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VRDE_USB_REQ_CLEAR_HALTED_EP_PARM parm; + + parm.code = VRDE_USB_REQ_CLEAR_HALTED_EP; + parm.id = pDevice->id; + parm.ep = u8Ep; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + return VINF_SUCCESS; +} + +static DECLCALLBACK(void) iface_CancelURB(PREMOTEUSBDEVICE pDevice, PREMOTEUSBQURB pRemoteURB) +{ + RemoteUSBBackend *pThis = pDevice->pOwner; + + VRDE_USB_REQ_CANCEL_URB_PARM parm; + + parm.code = VRDE_USB_REQ_CANCEL_URB; + parm.id = pDevice->id; + parm.handle = pRemoteURB->u32Handle; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + + requestDevice(pDevice); + + /* Remove this urb from the queue. It is safe because if + * client will return the URB, it will be just ignored + * in reapURB. + */ + if (pRemoteURB->prev) + { + pRemoteURB->prev->next = pRemoteURB->next; + } + else + { + pDevice->pHeadQURBs = pRemoteURB->next; + } + + if (pRemoteURB->next) + { + pRemoteURB->next->prev = pRemoteURB->prev; + } + else + { + pDevice->pTailQURBs = pRemoteURB->prev; + } + + qurbFree(pRemoteURB); + + releaseDevice(pDevice); + + return; +} + +static DECLCALLBACK(int) iface_QueueURB(PREMOTEUSBDEVICE pDevice, uint8_t u8Type, uint8_t u8Ep, uint8_t u8Direction, + uint32_t u32Len, void *pvData, void *pvURB, PREMOTEUSBQURB *ppRemoteURB) +{ + int vrc = VINF_SUCCESS; + +#ifdef DEBUG_sunlover + LogFlow(("RemoteUSBBackend::iface_QueueURB: u8Type = %d, u8Ep = %d, u8Direction = %d, data\n%.*Rhxd\n", + u8Type, u8Ep, u8Direction, u32Len, pvData)); +#endif /* DEBUG_sunlover */ + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + RemoteUSBBackend *pThis = pDevice->pOwner; + + VRDE_USB_REQ_QUEUE_URB_PARM parm; + uint32_t u32Handle = 0; + uint32_t u32DataLen = 0; + + REMOTEUSBQURB *qurb = qurbAlloc(pDevice); + + if (qurb == NULL) + { + vrc = VERR_NO_MEMORY; + goto l_leave; + } + + /* + * Compute length of data which need to be transferred to the client. + */ + switch(u8Direction) + { + case VUSB_DIRECTION_IN: + { + if (u8Type == VUSBXFERTYPE_MSG) + { + u32DataLen = 8; /* 8 byte header. */ + // u32DataLen = u32Len; /// @todo do messages need all information? + } + } break; + + case VUSB_DIRECTION_OUT: + { + u32DataLen = u32Len; + } break; + + default: + { + AssertFailed(); + vrc = VERR_INVALID_PARAMETER; + goto l_leave; + } + } + + parm.code = VRDE_USB_REQ_QUEUE_URB; + parm.id = pDevice->id; + + u32Handle = pDevice->hURB++; + if (u32Handle == 0) + { + u32Handle = pDevice->hURB++; + } + + LogFlow(("RemoteUSBBackend::iface_QueueURB: handle = %d\n", u32Handle)); + + parm.handle = u32Handle; + + switch(u8Type) + { + case VUSBXFERTYPE_CTRL: parm.type = VRDE_USB_TRANSFER_TYPE_CTRL; break; + case VUSBXFERTYPE_ISOC: parm.type = VRDE_USB_TRANSFER_TYPE_ISOC; break; + case VUSBXFERTYPE_BULK: parm.type = VRDE_USB_TRANSFER_TYPE_BULK; break; + case VUSBXFERTYPE_INTR: parm.type = VRDE_USB_TRANSFER_TYPE_INTR; break; + case VUSBXFERTYPE_MSG: parm.type = VRDE_USB_TRANSFER_TYPE_MSG; break; + default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave; + } + + parm.ep = u8Ep; + + switch(u8Direction) + { + case VUSB_DIRECTION_SETUP: AssertFailed(); parm.direction = VRDE_USB_DIRECTION_SETUP; break; + case VUSB_DIRECTION_IN: parm.direction = VRDE_USB_DIRECTION_IN; break; + case VUSB_DIRECTION_OUT: parm.direction = VRDE_USB_DIRECTION_OUT; break; + default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave; + } + + parm.urblen = u32Len; + parm.datalen = u32DataLen; + + if (u32DataLen) + { + parm.data = pvData; + } + + requestDevice (pDevice); + + /* Add at tail of queued urb list. */ + qurb->next = NULL; + qurb->prev = pDevice->pTailQURBs; + qurb->u32Err = VRDE_USB_XFER_OK; + qurb->u32Len = u32Len; + qurb->pvData = pvData; + qurb->pvURB = pvURB; + qurb->u32Handle = u32Handle; + qurb->fCompleted = false; + qurb->fInput = (u8Direction == VUSB_DIRECTION_IN); + qurb->u32TransferredLen = 0; + + if (pDevice->pTailQURBs) + { + Assert(pDevice->pTailQURBs->next == NULL); + pDevice->pTailQURBs->next = qurb; + } + else + { + /* This is the first URB to be added. */ + Assert(pDevice->pHeadQURBs == NULL); + pDevice->pHeadQURBs = qurb; + } + + pDevice->pTailQURBs = qurb; + + releaseDevice(pDevice); + + *ppRemoteURB = qurb; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + +l_leave: + if (RT_FAILURE(vrc)) + { + qurbFree(qurb); + } + + return vrc; +} + +/* The function checks the URB queue for completed URBs. Also if the client + * has requested URB polling, the function will send URB poll requests. + */ +static DECLCALLBACK(int) iface_ReapURB(PREMOTEUSBDEVICE pDevice, uint32_t u32Millies, void **ppvURB, + uint32_t *pu32Len, uint32_t *pu32Err) +{ + int vrc = VINF_SUCCESS; + + LogFlow(("RemoteUSBBackend::iface_ReapURB %d ms\n", u32Millies)); + + if (pDevice->fFailed) + { + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + RemoteUSBBackend *pThis = pDevice->pOwner; + + /* Wait for transaction completion. */ + uint64_t u64StartTime = RTTimeMilliTS(); + + if (pThis->pollingEnabledURB()) + { + VRDE_USB_REQ_REAP_URB_PARM parm; + + parm.code = VRDE_USB_REQ_REAP_URB; + + pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm)); + } + + REMOTEUSBQURB *qurb = NULL; + + for (;;) + { + uint32_t u32ClientId; + + if (ASMAtomicXchgBool(&pDevice->fWokenUp, false)) + break; + + /* Scan queued URBs, look for completed. */ + requestDevice(pDevice); + + u32ClientId = pDevice->u32ClientId; + + qurb = pDevice->pHeadQURBs; + + while (qurb) + { + if (qurb->fCompleted) + { + /* Remove this completed urb from the queue. */ + if (qurb->prev) + { + qurb->prev->next = qurb->next; + } + else + { + pDevice->pHeadQURBs = qurb->next; + } + + if (qurb->next) + { + qurb->next->prev = qurb->prev; + } + else + { + pDevice->pTailQURBs = qurb->prev; + } + + qurb->next = NULL; + qurb->prev = NULL; + + break; + } + + qurb = qurb->next; + } + + releaseDevice(pDevice); + + if ( qurb + || !pDevice->pHeadQURBs + || u32Millies == 0 + || pDevice->fFailed + || (RTTimeMilliTS() - u64StartTime >= (uint64_t)u32Millies)) + { + /* Got an URB or do not have to wait for an URB. */ + break; + } + + LogFlow(("RemoteUSBBackend::iface_ReapURB iteration.\n")); + + RTThreadSleep(10); + + if (pThis->pollingEnabledURB()) + { + VRDE_USB_REQ_REAP_URB_PARM parm; + + parm.code = VRDE_USB_REQ_REAP_URB; + + pThis->VRDPServer()->SendUSBRequest(u32ClientId, &parm, sizeof(parm)); + } + } + + LogFlow(("RemoteUSBBackend::iface_ReapURB completed in %lld ms, qurb = %p\n", RTTimeMilliTS () - u64StartTime, qurb)); + + if (!qurb) + { + *ppvURB = NULL; + *pu32Len = 0; + *pu32Err = VUSBSTATUS_OK; + } + else + { + *ppvURB = qurb->pvURB; + *pu32Len = qurb->u32Len; + *pu32Err = qurb->u32Err; + +#ifdef LOG_ENABLED + Log(("URB len = %d, data = %p\n", qurb->u32Len, qurb->pvURB)); + if (qurb->u32Len) + { + Log(("Received URB content:\n%.*Rhxd\n", qurb->u32Len, qurb->pvData)); + } +#endif + + qurbFree(qurb); + } + + return vrc; +} + +static DECLCALLBACK(int) iface_Wakeup(PREMOTEUSBDEVICE pDevice) +{ + ASMAtomicXchgBool(&pDevice->fWokenUp, true); + return VINF_SUCCESS; +} + +void RemoteUSBBackend::AddRef(void) +{ + cRefs++; +} + +void RemoteUSBBackend::Release(void) +{ + cRefs--; + + if (cRefs <= 0) + { + delete this; + } +} + +void RemoteUSBBackend::PollRemoteDevices(void) +{ + if ( mfWillBeDeleted + && menmPollRemoteDevicesStatus != PollRemoteDevicesStatus_Dereferenced) + { + /* Unmount all remote USB devices. */ + mConsole->i_processRemoteUSBDevices(mu32ClientId, NULL, 0, false); + + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_Dereferenced; + + Release(); + + return; + } + + switch(menmPollRemoteDevicesStatus) + { + case PollRemoteDevicesStatus_Negotiate: + { + VRDEUSBREQNEGOTIATEPARM parm; + + parm.code = VRDE_USB_REQ_NEGOTIATE; + parm.version = VRDE_USB_VERSION; + /* VRDE_USB_VERSION_3: support VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */ + parm.flags = VRDE_USB_SERVER_CAPS_PORT_VERSION; + + mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm)); + + /* Reference the object. When the client disconnects and + * the backend is about to be deleted, the method must be called + * to disconnect the USB devices (as stated above). + */ + AddRef(); + + /* Goto the disabled state. When a response will be received + * the state will be changed to the SendRequest. + */ + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitNegotiateResponse; + } break; + + case PollRemoteDevicesStatus_WaitNegotiateResponse: + { + LogFlow(("USB::PollRemoteDevices: WaitNegotiateResponse\n")); + /* Do nothing. */ + } break; + + case PollRemoteDevicesStatus_SendRequest: + { + LogFlow(("USB::PollRemoteDevices: SendRequest\n")); + + /* Send a request for device list. */ + VRDE_USB_REQ_DEVICE_LIST_PARM parm; + + parm.code = VRDE_USB_REQ_DEVICE_LIST; + + mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm)); + + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitResponse; + } break; + + case PollRemoteDevicesStatus_WaitResponse: + { + LogFlow(("USB::PollRemoteDevices: WaitResponse\n")); + + if (mfHasDeviceList) + { + mConsole->i_processRemoteUSBDevices(mu32ClientId, (VRDEUSBDEVICEDESC *)mpvDeviceList, mcbDeviceList, mfDescExt); + LogFlow(("USB::PollRemoteDevices: WaitResponse after process\n")); + + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest; + + mfHasDeviceList = false; + } + } break; + + case PollRemoteDevicesStatus_Dereferenced: + { + LogFlow(("USB::PollRemoteDevices: Dereferenced\n")); + /* Do nothing. */ + } break; + + default: + { + AssertFailed(); + } break; + } +} + +void RemoteUSBBackend::NotifyDelete(void) +{ + mfWillBeDeleted = true; +} + +/* + * The backend maintains a list of UUIDs of devices + * which are managed by the backend. + */ +bool RemoteUSBBackend::addUUID(const Guid *pUuid) +{ + unsigned i; + for (i = 0; i < RT_ELEMENTS(aGuids); i++) + { + if (aGuids[i].isZero()) + { + aGuids[i] = *pUuid; + return true; + } + } + + return false; +} + +bool RemoteUSBBackend::findUUID(const Guid *pUuid) +{ + unsigned i; + for (i = 0; i < RT_ELEMENTS(aGuids); i++) + { + if (aGuids[i] == *pUuid) + { + return true; + } + } + + return false; +} + +void RemoteUSBBackend::removeUUID(const Guid *pUuid) +{ + unsigned i; + for (i = 0; i < RT_ELEMENTS(aGuids); i++) + { + if (aGuids[i] == *pUuid) + { + aGuids[i].clear(); + break; + } + } +} + +RemoteUSBBackend::RemoteUSBBackend(Console *console, ConsoleVRDPServer *server, uint32_t u32ClientId) + : + mConsole(console), + mServer(server), + cRefs(0), + mu32ClientId(u32ClientId), + mfHasDeviceList(false), + mpvDeviceList(NULL), + mcbDeviceList(0), + menmPollRemoteDevicesStatus(PollRemoteDevicesStatus_Negotiate), + mfPollURB(true), + mpDevices(NULL), + mfWillBeDeleted(false), + mClientVersion(0), /* VRDE_USB_VERSION_2: the client version. */ + mfDescExt(false) /* VRDE_USB_VERSION_3: VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */ +{ + Assert(console); + Assert(server); + + int vrc = RTCritSectInit(&mCritsect); + + if (RT_FAILURE(vrc)) + { + AssertFailed(); + RT_ZERO(mCritsect); + } + + mCallback.pInstance = (PREMOTEUSBBACKEND)this; + mCallback.pfnOpen = iface_Open; + mCallback.pfnClose = iface_Close; + mCallback.pfnReset = iface_Reset; + mCallback.pfnSetConfig = iface_SetConfig; + mCallback.pfnClaimInterface = iface_ClaimInterface; + mCallback.pfnReleaseInterface = iface_ReleaseInterface; + mCallback.pfnInterfaceSetting = iface_InterfaceSetting; + mCallback.pfnQueueURB = iface_QueueURB; + mCallback.pfnReapURB = iface_ReapURB; + mCallback.pfnClearHaltedEP = iface_ClearHaltedEP; + mCallback.pfnCancelURB = iface_CancelURB; + mCallback.pfnWakeup = iface_Wakeup; +} + +RemoteUSBBackend::~RemoteUSBBackend() +{ + Assert(cRefs == 0); + + if (RTCritSectIsInitialized(&mCritsect)) + { + RTCritSectDelete(&mCritsect); + } + + RTMemFree(mpvDeviceList); + + mServer->usbBackendRemoveFromList(this); +} + +int RemoteUSBBackend::negotiateResponse(const VRDEUSBREQNEGOTIATERET *pret, uint32_t cbRet) +{ + int vrc = VINF_SUCCESS; + + Log(("RemoteUSBBackend::negotiateResponse: flags = %02X.\n", pret->flags)); + + LogRel(("Remote USB: Received negotiate response. Flags 0x%02X.\n", + pret->flags)); + + if (pret->flags & VRDE_USB_CAPS_FLAG_POLL) + { + Log(("RemoteUSBBackend::negotiateResponse: client requested URB polling.\n")); + mfPollURB = true; + } + else + { + mfPollURB = false; + } + + /* VRDE_USB_VERSION_2: check the client version. */ + if (pret->flags & VRDE_USB_CAPS2_FLAG_VERSION) + { + /* This could be a client version > 1. */ + if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_2)) + { + VRDEUSBREQNEGOTIATERET_2 *pret2 = (VRDEUSBREQNEGOTIATERET_2 *)pret; + + if (pret2->u32Version <= VRDE_USB_VERSION) + { + /* This is OK. The client wants a version supported by the server. */ + mClientVersion = pret2->u32Version; + } + else + { + LogRel(("VRDP: ERROR: unsupported remote USB protocol client version %d.\n", pret2->u32Version)); + vrc = VERR_NOT_SUPPORTED; + } + } + else + { + LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet)); + vrc = VERR_NOT_SUPPORTED; + } + } + else + { + /* This is a client version 1. */ + mClientVersion = VRDE_USB_VERSION_1; + } + + if (RT_SUCCESS(vrc)) + { + LogRel(("VRDP: remote USB protocol version %d.\n", mClientVersion)); + + /* VRDE_USB_VERSION_3: check the client capabilities: VRDE_USB_CLIENT_CAPS_*. */ + if (mClientVersion == VRDE_USB_VERSION_3) + { + if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_3)) + { + VRDEUSBREQNEGOTIATERET_3 *pret3 = (VRDEUSBREQNEGOTIATERET_3 *)pret; + + mfDescExt = (pret3->u32Flags & VRDE_USB_CLIENT_CAPS_PORT_VERSION) != 0; + } + else + { + LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet)); + vrc = VERR_NOT_SUPPORTED; + } + } + + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest; + } + + return vrc; +} + +int RemoteUSBBackend::saveDeviceList(const void *pvList, uint32_t cbList) +{ + Log(("RemoteUSBBackend::saveDeviceList: pvList = %p, cbList = %d\n", pvList, cbList)); + + if (!mfHasDeviceList) + { + RTMemFree(mpvDeviceList); + mpvDeviceList = NULL; + + mcbDeviceList = cbList; + + if (cbList > 0) + { + mpvDeviceList = RTMemAlloc(cbList); + memcpy(mpvDeviceList, pvList, cbList); + } + + mfHasDeviceList = true; + } + + return VINF_SUCCESS; +} + +void RemoteUSBBackend::request(void) +{ + int vrc = RTCritSectEnter(&mCritsect); + AssertRC(vrc); +} + +void RemoteUSBBackend::release(void) +{ + RTCritSectLeave(&mCritsect); +} + +PREMOTEUSBDEVICE RemoteUSBBackend::deviceFromId(VRDEUSBDEVID id) +{ + request(); + + REMOTEUSBDEVICE *pDevice = mpDevices; + + while (pDevice && pDevice->id != id) + { + pDevice = pDevice->next; + } + + release(); + + return pDevice; +} + +void RemoteUSBBackend::addDevice(PREMOTEUSBDEVICE pDevice) +{ + request(); + + pDevice->next = mpDevices; + + if (mpDevices) + { + mpDevices->prev = pDevice; + } + + mpDevices = pDevice; + + release(); +} + +void RemoteUSBBackend::removeDevice(PREMOTEUSBDEVICE pDevice) +{ + request(); + + if (pDevice->prev) + { + pDevice->prev->next = pDevice->next; + } + else + { + mpDevices = pDevice->next; + } + + if (pDevice->next) + { + pDevice->next->prev = pDevice->prev; + } + + release(); +} + +int RemoteUSBBackend::reapURB(const void *pvBody, uint32_t cbBody) +{ + int vrc = VINF_SUCCESS; + + LogFlow(("RemoteUSBBackend::reapURB: pvBody = %p, cbBody = %d\n", pvBody, cbBody)); + + VRDEUSBREQREAPURBBODY *pBody = (VRDEUSBREQREAPURBBODY *)pvBody; + + /* 'pvBody' memory buffer can contain multiple URBs. */ + while (cbBody >= sizeof(VRDEUSBREQREAPURBBODY)) + { + Log(("RemoteUSBBackend::reapURB: id = %d, flags = %02X, error = %d, handle %d, len = %d.\n", + pBody->id, pBody->flags, pBody->error, pBody->handle, pBody->len)); + + uint8_t fu8ReapValidFlags; + + if (mClientVersion == VRDE_USB_VERSION_1 || mClientVersion == VRDE_USB_VERSION_2) + { + fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS; + } + else + { + fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS_3; + } + + /* Verify client's data. */ + if ( (pBody->flags & ~fu8ReapValidFlags) != 0 + || pBody->handle == 0) + { + LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid reply data. Skipping the reply.\n")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + PREMOTEUSBDEVICE pDevice = deviceFromId(pBody->id); + + if (!pDevice) + { + LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid device id. Skipping the reply.\n")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t cbBodyData = 0; /* Data contained in the URB body structure for input URBs. i.e. beyond VRDEUSBREQREAPURBBODY. */ + + requestDevice(pDevice); + + /* Search the queued URB for given handle. */ + REMOTEUSBQURB *qurb = pDevice->pHeadQURBs; + + while (qurb && qurb->u32Handle != pBody->handle) + { + LogFlow(("RemoteUSBBackend::reapURB: searching: %p handle = %d.\n", qurb, qurb->u32Handle)); + qurb = qurb->next; + } + + if (!qurb) + { + LogFlow(("RemoteUSBBackend::reapURB: Queued URB not found, probably already canceled. Skipping the URB.\n")); + } + else + { + LogFlow(("RemoteUSBBackend::reapURB: qurb = %p, u32Err = %d\n", qurb, qurb->u32Err)); + + /* Update the URB error field, if it does not yet indicate an error. */ + if (qurb->u32Err == VUSBSTATUS_OK) + { + if (mClientVersion == VRDE_USB_VERSION_1) + { + switch(pBody->error) + { + case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break; + case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break; + case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break; + case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break; + default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error)); + qurb->u32Err = VUSBSTATUS_DNR; break; + } + } + else if ( mClientVersion == VRDE_USB_VERSION_2 + || mClientVersion == VRDE_USB_VERSION_3) + { + switch(pBody->error) + { + case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break; + case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break; + case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break; + case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break; + case VRDE_USB_XFER_DO: qurb->u32Err = VUSBSTATUS_DATA_OVERRUN; break; + case VRDE_USB_XFER_DU: qurb->u32Err = VUSBSTATUS_DATA_UNDERRUN; break; + + /* Unmapped errors. */ + case VRDE_USB_XFER_BS: + case VRDE_USB_XFER_DTM: + case VRDE_USB_XFER_PCF: + case VRDE_USB_XFER_UPID: + case VRDE_USB_XFER_BO: + case VRDE_USB_XFER_BU: + case VRDE_USB_XFER_ERR: + default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error)); + qurb->u32Err = VUSBSTATUS_DNR; break; + } + } + else + { + qurb->u32Err = VUSBSTATUS_DNR; + } + } + + /* Get the URB data. The URB is completed unless the client tells that this is a fragment of an IN URB. */ + bool fURBCompleted = true; + + if (qurb->fInput) + { + if (pBody->len <= cbBody - sizeof(VRDEUSBREQREAPURBBODY)) + cbBodyData = pBody->len; /* VRDE_USB_DIRECTION_IN URBs include some data. */ + else + { + cbBodyData = cbBody - sizeof(VRDEUSBREQREAPURBBODY); + qurb->u32Err = VUSBSTATUS_DNR; + } + + if (qurb->u32Err == VUSBSTATUS_OK) + { + LogFlow(("RemoteUSBBackend::reapURB: copying data %d bytes\n", pBody->len)); + if (pBody->len > qurb->u32Len - qurb->u32TransferredLen) + { + /* Received more data than expected for this URB. If there more fragments follow, + * they will be discarded because the URB handle will not be valid anymore. + */ + qurb->u32Err = VUSBSTATUS_DNR; + qurb->u32TransferredLen = qurb->u32Len; + } + else + { + memcpy ((uint8_t *)qurb->pvData + qurb->u32TransferredLen, &pBody[1], pBody->len); + qurb->u32TransferredLen += pBody->len; + } + + if ( qurb->u32Err == VUSBSTATUS_OK + && (pBody->flags & VRDE_USB_REAP_FLAG_FRAGMENT) != 0) + { + /* If the client sends fragmented packets, accumulate the URB data. */ + fURBCompleted = false; + } + } + } + else + qurb->u32TransferredLen += pBody->len; /* Update the value for OUT URBs. */ + + if (fURBCompleted) + { + /* Move the URB near the head of URB list, so that iface_ReapURB can + * find it faster. Note that the order of completion must be preserved! + */ + if (qurb->prev) + { + /* The URB is not in the head. Unlink it from its current position. */ + qurb->prev->next = qurb->next; + + if (qurb->next) + { + qurb->next->prev = qurb->prev; + } + else + { + pDevice->pTailQURBs = qurb->prev; + } + + /* And insert it to its new place. */ + if (pDevice->pHeadQURBs->fCompleted) + { + /* At least one other completed URB; insert after the + * last completed URB. + */ + REMOTEUSBQURB *prev_qurb = pDevice->pHeadQURBs; + while (prev_qurb->next && prev_qurb->next->fCompleted) + prev_qurb = prev_qurb->next; + + qurb->next = prev_qurb->next; + qurb->prev = prev_qurb; + + if (prev_qurb->next) + prev_qurb->next->prev = qurb; + else + pDevice->pTailQURBs = qurb; + prev_qurb->next = qurb; + } + else + { + /* No other completed URBs; insert at head. */ + qurb->next = pDevice->pHeadQURBs; + qurb->prev = NULL; + + pDevice->pHeadQURBs->prev = qurb; + pDevice->pHeadQURBs = qurb; + } + } + + qurb->u32Len = qurb->u32TransferredLen; /* Update the final length. */ + qurb->fCompleted = true; + } + } + + releaseDevice (pDevice); + + if (pBody->flags & VRDE_USB_REAP_FLAG_LAST) + { + break; + } + + /* There is probably a further URB body. */ + if (cbBodyData > cbBody - sizeof(VRDEUSBREQREAPURBBODY)) + { + vrc = VERR_INVALID_PARAMETER; + break; + } + + cbBody -= sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData; + pBody = (VRDEUSBREQREAPURBBODY *)((uint8_t *)pBody + sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData); + } + + LogFlow(("RemoteUSBBackend::reapURB: returns %Rrc\n", vrc)); + + return vrc; +} +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp new file mode 100644 index 00000000..96f7aa9e --- /dev/null +++ b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp @@ -0,0 +1,314 @@ +/* $Id: RemoteUSBDeviceImpl.cpp $ */ +/** @file + * VirtualBox IHostUSBDevice COM interface implementation for remote (VRDP) USB devices. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_HOSTUSBDEVICE +#include "LoggingNew.h" + +#include "RemoteUSBDeviceImpl.h" + +#include "AutoCaller.h" + +#include <iprt/cpp/utils.h> + +#include <iprt/errcore.h> + +#include <VBox/RemoteDesktop/VRDE.h> +#include <VBox/vrdpusb.h> + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(RemoteUSBDevice) + +HRESULT RemoteUSBDevice::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void RemoteUSBDevice::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** @todo (sunlover) REMOTE_USB Device states. */ + +/** + * Initializes the remote USB device object. + */ +HRESULT RemoteUSBDevice::init(uint32_t u32ClientId, VRDEUSBDEVICEDESC *pDevDesc, bool fDescExt) +{ + LogFlowThisFunc(("u32ClientId=%d,pDevDesc=%p\n", u32ClientId, pDevDesc)); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(mData.id).create(); + + unconst(mData.vendorId) = pDevDesc->idVendor; + unconst(mData.productId) = pDevDesc->idProduct; + unconst(mData.revision) = pDevDesc->bcdRev; + + unconst(mData.manufacturer) = pDevDesc->oManufacturer ? (char *)pDevDesc + pDevDesc->oManufacturer : ""; + unconst(mData.product) = pDevDesc->oProduct ? (char *)pDevDesc + pDevDesc->oProduct : ""; + unconst(mData.serialNumber) = pDevDesc->oSerialNumber ? (char *)pDevDesc + pDevDesc->oSerialNumber : ""; + + char id[64]; + RTStrPrintf(id, sizeof(id), REMOTE_USB_BACKEND_PREFIX_S "0x%08X&0x%08X", pDevDesc->id, u32ClientId); + unconst(mData.address) = id; + unconst(mData.backend) = "vrdp"; + + char port[16]; + RTStrPrintf(port, sizeof(port), "%u", pDevDesc->idPort); + unconst(mData.portPath) = port; + + unconst(mData.port) = pDevDesc->idPort; + unconst(mData.version) = (uint16_t)(pDevDesc->bcdUSB >> 8); + if (fDescExt) + { + VRDEUSBDEVICEDESCEXT *pDevDescExt = (VRDEUSBDEVICEDESCEXT *)pDevDesc; + switch (pDevDescExt->u16DeviceSpeed) + { + default: + case VRDE_USBDEVICESPEED_UNKNOWN: + case VRDE_USBDEVICESPEED_LOW: + case VRDE_USBDEVICESPEED_FULL: + unconst(mData.speed) = USBConnectionSpeed_Full; + break; + + case VRDE_USBDEVICESPEED_HIGH: + case VRDE_USBDEVICESPEED_VARIABLE: + unconst(mData.speed) = USBConnectionSpeed_High; + break; + + case VRDE_USBDEVICESPEED_SUPERSPEED: + unconst(mData.speed) = USBConnectionSpeed_Super; + break; + } + } + else + { + unconst(mData.speed) = mData.version == 3 ? USBConnectionSpeed_Super + : mData.version == 2 ? USBConnectionSpeed_High + : USBConnectionSpeed_Full; + } + + mData.state = USBDeviceState_Available; + + mData.dirty = false; + unconst(mData.devId) = (uint16_t)pDevDesc->id; + + unconst(mData.clientId) = u32ClientId; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void RemoteUSBDevice::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unconst(mData.id).clear(); + + unconst(mData.vendorId) = 0; + unconst(mData.productId) = 0; + unconst(mData.revision) = 0; + + unconst(mData.manufacturer).setNull(); + unconst(mData.product).setNull(); + unconst(mData.serialNumber).setNull(); + + unconst(mData.address).setNull(); + unconst(mData.backend).setNull(); + + unconst(mData.port) = 0; + unconst(mData.portPath).setNull(); + unconst(mData.version) = 1; + + unconst(mData.dirty) = FALSE; + + unconst(mData.devId) = 0; + unconst(mData.clientId) = 0; +} + +// IUSBDevice properties +///////////////////////////////////////////////////////////////////////////// + +HRESULT RemoteUSBDevice::getId(com::Guid &aId) +{ + aId = mData.id; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getVendorId(USHORT *aVendorId) +{ + /* this is const, no need to lock */ + *aVendorId = mData.vendorId; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getProductId(USHORT *aProductId) +{ + /* this is const, no need to lock */ + *aProductId = mData.productId; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getRevision(USHORT *aRevision) +{ + /* this is const, no need to lock */ + *aRevision = mData.revision; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getManufacturer(com::Utf8Str &aManufacturer) +{ + /* this is const, no need to lock */ + aManufacturer = mData.manufacturer; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getProduct(com::Utf8Str &aProduct) +{ + /* this is const, no need to lock */ + aProduct = mData.product; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber) +{ + /* this is const, no need to lock */ + aSerialNumber = mData.serialNumber; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getAddress(com::Utf8Str &aAddress) +{ + /* this is const, no need to lock */ + aAddress = mData.address; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getPort(USHORT *aPort) +{ + /* this is const, no need to lock */ + *aPort = mData.port; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getPortPath(com::Utf8Str &aPortPath) +{ + /* this is const, no need to lock */ + aPortPath = mData.portPath; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getVersion(USHORT *aVersion) +{ + /* this is const, no need to lock */ + *aVersion = mData.version; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed) +{ + /* this is const, no need to lock */ + *aSpeed = mData.speed; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getRemote(BOOL *aRemote) +{ + /* RemoteUSBDevice is always remote. */ + /* this is const, no need to lock */ + *aRemote = TRUE; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getBackend(com::Utf8Str &aBackend) +{ + /* this is const, no need to lock */ + aBackend = mData.backend; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo) +{ + /* this is const, no need to lock */ + aInfo.resize(2); + aInfo[0] = mData.manufacturer; + aInfo[1] = mData.product; + + return S_OK; +} + +// IHostUSBDevice properties +//////////////////////////////////////////////////////////////////////////////// + +HRESULT RemoteUSBDevice::getState(USBDeviceState_T *aState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aState = mData.state; + + return S_OK; +} + +// public methods only for internal purposes +//////////////////////////////////////////////////////////////////////////////// +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/SessionImpl.cpp b/src/VBox/Main/src-client/SessionImpl.cpp new file mode 100644 index 00000000..7fd7deaa --- /dev/null +++ b/src/VBox/Main/src-client/SessionImpl.cpp @@ -0,0 +1,1339 @@ +/* $Id: SessionImpl.cpp $ */ +/** @file + * VBox Client Session COM Class implementation in VBoxC. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_SESSION +#include "LoggingNew.h" + +#include "SessionImpl.h" +#include "ConsoleImpl.h" +#include "ClientTokenHolder.h" +#include "Global.h" +#include "StringifyEnums.h" + +#include "AutoCaller.h" + +#include <iprt/errcore.h> +#include <iprt/process.h> + + +/** + * Local macro to check whether the session is open and return an error if not. + * @note Don't forget to do |Auto[Reader]Lock alock (this);| before using this + * macro. + */ +#define CHECK_OPEN() \ + do { \ + if (mState != SessionState_Locked) \ + return setError(E_UNEXPECTED, Session::tr("The session is not locked (session state: %s)"), \ + Global::stringifySessionState(mState)); \ + } while (0) + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +Session::Session() +{ +} + +Session::~Session() +{ +} + +HRESULT Session::FinalConstruct() +{ + LogFlowThisFunc(("\n")); + + HRESULT hrc = init(); + + BaseFinalConstruct(); + + return hrc; +} + +void Session::FinalRelease() +{ + LogFlowThisFunc(("\n")); + + uninit(); + + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the Session object. + */ +HRESULT Session::init() +{ + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + LogFlowThisFuncEnter(); + + mState = SessionState_Unlocked; + mType = SessionType_Null; + + mClientTokenHolder = NULL; + + /* Confirm a successful initialization when it's the case */ + autoInitSpan.setSucceeded(); + + LogFlowThisFuncLeave(); + + return S_OK; +} + +/** + * Uninitializes the Session object. + * + * @note Locks this object for writing. + */ +void Session::uninit() +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + { + LogFlowThisFunc(("Already uninitialized.\n")); + LogFlowThisFuncLeave(); + return; + } + + /* close() needs write lock */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mState != SessionState_Unlocked) + { + Assert(mState == SessionState_Locked || + mState == SessionState_Spawning); + + HRESULT hrc = i_unlockMachine(true /* aFinalRelease */, false /* aFromServer */, alock); + AssertComRC(hrc); + } + + LogFlowThisFuncLeave(); +} + +// ISession properties +///////////////////////////////////////////////////////////////////////////// + +HRESULT Session::getState(SessionState_T *aState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aState = mState; + + return S_OK; +} + +HRESULT Session::getType(SessionType_T *aType) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_OPEN(); + + *aType = mType; + return S_OK; +} + +HRESULT Session::getName(com::Utf8Str &aName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aName = mName; + return S_OK; +} + +HRESULT Session::setName(const com::Utf8Str &aName) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mState != SessionState_Unlocked) + return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Trying to set name for a session which is not in state \"unlocked\"")); + + mName = aName; + return S_OK; +} + +HRESULT Session::getMachine(ComPtr<IMachine> &aMachine) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_OPEN(); + + HRESULT hrc; +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + hrc = mConsole->i_machine().queryInterfaceTo(aMachine.asOutParam()); + else +#endif + hrc = mRemoteMachine.queryInterfaceTo(aMachine.asOutParam()); + if (FAILED(hrc)) + { +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + setError(hrc, tr("Failed to query the session machine")); + else +#endif + if (FAILED_DEAD_INTERFACE(hrc)) + setError(hrc, tr("Peer process crashed")); + else + setError(hrc, tr("Failed to query the remote session machine")); + } + + return hrc; +} + +HRESULT Session::getConsole(ComPtr<IConsole> &aConsole) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_OPEN(); + + HRESULT hrc = S_OK; +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + hrc = mConsole.queryInterfaceTo(aConsole.asOutParam()); + else +#endif + hrc = mRemoteConsole.queryInterfaceTo(aConsole.asOutParam()); + + if (FAILED(hrc)) + { +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + setError(hrc, tr("Failed to query the console")); + else +#endif + if (FAILED_DEAD_INTERFACE(hrc)) + setError(hrc, tr("Peer process crashed")); + else + setError(hrc, tr("Failed to query the remote console")); + } + + return hrc; +} + +// ISession methods +///////////////////////////////////////////////////////////////////////////// +HRESULT Session::unlockMachine() +{ + LogFlowThisFunc(("mState=%d, mType=%d\n", mState, mType)); + + /* close() needs write lock */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_OPEN(); + return i_unlockMachine(false /* aFinalRelease */, false /* aFromServer */, alock); +} + +// IInternalSessionControl methods +///////////////////////////////////////////////////////////////////////////// +HRESULT Session::getPID(ULONG *aPid) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aPid = (ULONG)RTProcSelf(); + AssertCompile(sizeof(*aPid) == sizeof(RTPROCESS)); + + return S_OK; +} + +HRESULT Session::getRemoteConsole(ComPtr<IConsole> &aConsole) +{ + LogFlowThisFuncEnter(); +#ifndef VBOX_COM_INPROC_API_CLIENT + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mType == SessionType_WriteLock && !!mConsole) + { + /* return a failure if the session already transitioned to Closing + * but the server hasn't processed Machine::OnSessionEnd() yet. */ + if (mState == SessionState_Locked) + { + mConsole.queryInterfaceTo(aConsole.asOutParam()); + + LogFlowThisFuncLeave(); + return S_OK; + } + return VBOX_E_INVALID_VM_STATE; + } + return setError(VBOX_E_INVALID_OBJECT_STATE, "This is not a direct session"); + +#else /* VBOX_COM_INPROC_API_CLIENT */ + RT_NOREF(aConsole); + AssertFailed(); + return VBOX_E_INVALID_OBJECT_STATE; +#endif /* VBOX_COM_INPROC_API_CLIENT */ +} + +HRESULT Session::getNominalState(MachineState_T *aNominalState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_getNominalState(*aNominalState); +#else + RT_NOREF(aNominalState); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER +HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine, + LockType_T aLockType, + const com::Utf8Str &aTokenId) +#else +HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine, + LockType_T aLockType, + const ComPtr<IToken> &aToken) +#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */ +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturn(mState == SessionState_Unlocked, VBOX_E_INVALID_VM_STATE); + + if (!aMachine) + { + /* + * A special case: the server informs us that this session has been + * passed to IMachine::launchVMProcess() so this session will become + * remote (but not existing) when AssignRemoteMachine() is called. + */ + + AssertReturn(mType == SessionType_Null, VBOX_E_INVALID_OBJECT_STATE); + mType = SessionType_Remote; + mState = SessionState_Spawning; + + return S_OK; + } + + /* query IInternalMachineControl interface */ + mControl = aMachine; + AssertReturn(!!mControl, E_FAIL); + + HRESULT hrc = S_OK; +#ifndef VBOX_COM_INPROC_API_CLIENT + if (aLockType == LockType_VM) + { + /* This is what is special about VM processes: they have a Console + * object which is the root of all VM related activity. */ + hrc = mConsole.createObject(); + AssertComRCReturn(hrc, hrc); + + hrc = mConsole->initWithMachine(aMachine, mControl, aLockType); + AssertComRCReturn(hrc, hrc); + } + else + mRemoteMachine = aMachine; +#else + RT_NOREF(aLockType); + mRemoteMachine = aMachine; +#endif + +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + Utf8Str strTokenId(aTokenId); + Assert(!strTokenId.isEmpty()); +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + Assert(!aToken.isNull()); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + /* create the machine client token */ + try + { +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + mClientTokenHolder = new ClientTokenHolder(strTokenId); +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + mClientTokenHolder = new ClientTokenHolder(aToken); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + if (!mClientTokenHolder->isReady()) + { + delete mClientTokenHolder; + mClientTokenHolder = NULL; + hrc = E_FAIL; + } + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + + /* + * Reference the VirtualBox object to ensure the server is up + * until the session is closed + */ + if (SUCCEEDED(hrc)) + hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam()); + + if (SUCCEEDED(hrc)) + { + mType = SessionType_WriteLock; + mState = SessionState_Locked; + } + else + { + /* some cleanup */ + mControl.setNull(); +#ifndef VBOX_COM_INPROC_API_CLIENT + if (!mConsole.isNull()) + { + mConsole->uninit(); + mConsole.setNull(); + } +#endif + } + + return hrc; +} + +HRESULT Session::assignRemoteMachine(const ComPtr<IMachine> &aMachine, + const ComPtr<IConsole> &aConsole) + +{ + AssertReturn(aMachine, E_INVALIDARG); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturn(mState == SessionState_Unlocked || + mState == SessionState_Spawning, VBOX_E_INVALID_VM_STATE); + + HRESULT hrc = E_FAIL; + + /* query IInternalMachineControl interface */ + mControl = aMachine; + AssertReturn(!!mControl, E_FAIL); + + /// @todo (dmik) + // currently, the remote session returns the same machine and + // console objects as the direct session, thus giving the + // (remote) client full control over the direct session. For the + // console, it is the desired behavior (the ability to control + // VM execution is a must for the remote session). What about + // the machine object, we may want to prevent the remote client + // from modifying machine data. In this case, we must: + // 1) assign the Machine object (instead of the SessionMachine + // object that is passed to this method) to mRemoteMachine; + // 2) remove GetMachine() property from the IConsole interface + // because it always returns the SessionMachine object + // (alternatively, we can supply a separate IConsole + // implementation that will return the Machine object in + // response to GetMachine()). + + mRemoteMachine = aMachine; + mRemoteConsole = aConsole; + + /* + * Reference the VirtualBox object to ensure the server is up + * until the session is closed + */ + hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam()); + + if (SUCCEEDED(hrc)) + { + /* + * RemoteSession type can be already set by AssignMachine() when its + * argument is NULL (a special case) + */ + if (mType != SessionType_Remote) + mType = SessionType_Shared; + else + Assert(mState == SessionState_Spawning); + + mState = SessionState_Locked; + } + else + { + /* some cleanup */ + mControl.setNull(); + mRemoteMachine.setNull(); + mRemoteConsole.setNull(); + } + + LogFlowThisFunc(("hrc=%08X\n", hrc)); + LogFlowThisFuncLeave(); + + return hrc; +} + +HRESULT Session::updateMachineState(MachineState_T aMachineState) +{ + + if (getObjectState().getState() != ObjectState::Ready) + { + /* + * We might have already entered Session::uninit() at this point, so + * return silently (not interested in the state change during uninit) + */ + LogFlowThisFunc(("Already uninitialized.\n")); + return S_OK; + } + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (mState == SessionState_Unlocking) + { + LogFlowThisFunc(("Already being unlocked.\n")); + return S_OK; + } + + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + + AssertReturn(!mControl.isNull(), E_FAIL); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(!mConsole.isNull(), E_FAIL); + + return mConsole->i_updateMachineState(aMachineState); +#else + RT_NOREF(aMachineState); + return S_OK; +#endif +} + +HRESULT Session::uninitialize() +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(this); + + HRESULT hrc = S_OK; + + if (getObjectState().getState() == ObjectState::Ready) + { + /* close() needs write lock */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType)); + + if (mState == SessionState_Unlocking) + { + LogFlowThisFunc(("Already being unlocked.\n")); + return S_OK; + } + + if ( mState == SessionState_Locked + || mState == SessionState_Spawning) + { /* likely */ } + else + { +#ifndef DEBUG_bird /* bird: hitting this all the time running tdAddBaseic1.py. */ + AssertMsgFailed(("Session is in wrong state (%d), expected locked (%d) or spawning (%d)\n", + mState, SessionState_Locked, SessionState_Spawning)); +#endif + return VBOX_E_INVALID_VM_STATE; + } + + /* close ourselves */ + hrc = i_unlockMachine(false /* aFinalRelease */, true /* aFromServer */, alock); + } + else if (getObjectState().getState() == ObjectState::InUninit) + { + /* + * We might have already entered Session::uninit() at this point, + * return silently + */ + LogFlowThisFunc(("Already uninitialized.\n")); + } + else + { + Log1WarningThisFunc(("UNEXPECTED uninitialization!\n")); + hrc = autoCaller.rc(); + } + + LogFlowThisFunc(("hrc=%08X\n", hrc)); + LogFlowThisFuncLeave(); + + return hrc; +} + +HRESULT Session::onNetworkAdapterChange(const ComPtr<INetworkAdapter> &aNetworkAdapter, + BOOL aChangeAdapter) + +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onNetworkAdapterChange(aNetworkAdapter, aChangeAdapter); +#else + RT_NOREF(aNetworkAdapter, aChangeAdapter); + return S_OK; +#endif +} + +HRESULT Session::onAudioAdapterChange(const ComPtr<IAudioAdapter> &aAudioAdapter) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onAudioAdapterChange(aAudioAdapter); +#else + RT_NOREF(aAudioAdapter); + return S_OK; +#endif + +} + +HRESULT Session::onHostAudioDeviceChange(const ComPtr<IHostAudioDevice> &aDevice, + BOOL aNew, AudioDeviceState_T aState, + const ComPtr<IVirtualBoxErrorInfo> &aErrInfo) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onHostAudioDeviceChange(aDevice, aNew, aState, aErrInfo); +#else + RT_NOREF(aDevice, aNew, aState, aErrInfo); + return S_OK; +#endif +} + +HRESULT Session::onSerialPortChange(const ComPtr<ISerialPort> &aSerialPort) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onSerialPortChange(aSerialPort); +#else + RT_NOREF(aSerialPort); + return S_OK; +#endif +} + +HRESULT Session::onParallelPortChange(const ComPtr<IParallelPort> &aParallelPort) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onParallelPortChange(aParallelPort); +#else + RT_NOREF(aParallelPort); + return S_OK; +#endif +} + +HRESULT Session::onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onStorageControllerChange(aMachineId, aControllerName); +#else + NOREF(aMachineId); + NOREF(aControllerName); + return S_OK; +#endif +} + +HRESULT Session::onMediumChange(const ComPtr<IMediumAttachment> &aMediumAttachment, + BOOL aForce) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onMediumChange(aMediumAttachment, aForce); +#else + RT_NOREF(aMediumAttachment, aForce); + return S_OK; +#endif +} + +HRESULT Session::onVMProcessPriorityChange(VMProcPriority_T priority) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onVMProcessPriorityChange(priority); +#else + RT_NOREF(priority); + return S_OK; +#endif +} + +HRESULT Session::onCPUChange(ULONG aCpu, BOOL aAdd) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onCPUChange(aCpu, aAdd); +#else + RT_NOREF(aCpu, aAdd); + return S_OK; +#endif +} + +HRESULT Session::onCPUExecutionCapChange(ULONG aExecutionCap) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onCPUExecutionCapChange(aExecutionCap); +#else + RT_NOREF(aExecutionCap); + return S_OK; +#endif +} + +HRESULT Session::onVRDEServerChange(BOOL aRestart) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onVRDEServerChange(aRestart); +#else + RT_NOREF(aRestart); + return S_OK; +#endif +} + +HRESULT Session::onRecordingChange(BOOL aEnable) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onRecordingChange(aEnable); +#else + RT_NOREF(aEnable); + return S_OK; +#endif +} + +HRESULT Session::onUSBControllerChange() +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onUSBControllerChange(); +#else + return S_OK; +#endif +} + +HRESULT Session::onSharedFolderChange(BOOL aGlobal) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onSharedFolderChange(aGlobal); +#else + RT_NOREF(aGlobal); + return S_OK; +#endif +} + +HRESULT Session::onClipboardModeChange(ClipboardMode_T aClipboardMode) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onClipboardModeChange(aClipboardMode); +#else + RT_NOREF(aClipboardMode); + return S_OK; +#endif +} + +HRESULT Session::onClipboardFileTransferModeChange(BOOL aEnabled) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onClipboardFileTransferModeChange(RT_BOOL(aEnabled)); +#else + RT_NOREF(aEnabled); + return S_OK; +#endif +} + +HRESULT Session::onDnDModeChange(DnDMode_T aDndMode) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onDnDModeChange(aDndMode); +#else + RT_NOREF(aDndMode); + return S_OK; +#endif +} + +HRESULT Session::onGuestDebugControlChange(const ComPtr<IGuestDebugControl> &aGuestDebugControl) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onGuestDebugControlChange(aGuestDebugControl); +#else + RT_NOREF(aGuestDebugControl); + return S_OK; +#endif +} + +HRESULT Session::onUSBDeviceAttach(const ComPtr<IUSBDevice> &aDevice, + const ComPtr<IVirtualBoxErrorInfo> &aError, + ULONG aMaskedInterfaces, + const com::Utf8Str &aCaptureFilename) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onUSBDeviceAttach(aDevice, aError, aMaskedInterfaces, aCaptureFilename); +#else + RT_NOREF(aDevice, aError, aMaskedInterfaces, aCaptureFilename); + return S_OK; +#endif +} + +HRESULT Session::onUSBDeviceDetach(const com::Guid &aId, + const ComPtr<IVirtualBoxErrorInfo> &aError) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onUSBDeviceDetach(aId.toUtf16().raw(), aError); +#else + RT_NOREF(aId, aError); + return S_OK; +#endif +} + +HRESULT Session::onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); +#endif + + if (mState != SessionState_Locked) + { + /* the call from Machine issued when the session is open can arrive + * after the session starts closing or gets closed. Note that when + * aCheck is false, we return E_FAIL to indicate that aWinId we return + * is not valid */ + *aCanShow = FALSE; + *aWinId = 0; + return aCheck ? S_OK : E_FAIL; + } + +#ifndef VBOX_COM_INPROC_API_CLIENT + return mConsole->i_onShowWindow(aCheck, aCanShow, aWinId); +#else + return S_OK; +#endif +} + +HRESULT Session::onBandwidthGroupChange(const ComPtr<IBandwidthGroup> &aBandwidthGroup) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onBandwidthGroupChange(aBandwidthGroup); +#else + RT_NOREF(aBandwidthGroup); + return S_OK; +#endif +} + +HRESULT Session::onStorageDeviceChange(const ComPtr<IMediumAttachment> &aMediumAttachment, BOOL aRemove, BOOL aSilent) +{ + LogFlowThisFunc(("\n")); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onStorageDeviceChange(aMediumAttachment, aRemove, aSilent); +#else + RT_NOREF(aMediumAttachment, aRemove, aSilent); + return S_OK; +#endif +} + +HRESULT Session::accessGuestProperty(const com::Utf8Str &aName, const com::Utf8Str &aValue, const com::Utf8Str &aFlags, + ULONG aAccessMode, com::Utf8Str &aRetValue, LONG64 *aRetTimestamp, com::Utf8Str &aRetFlags) +{ +#ifdef VBOX_WITH_GUEST_PROPS +# ifndef VBOX_COM_INPROC_API_CLIENT + if (mState != SessionState_Locked) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Machine is not locked by session (session state: %s)."), + Global::stringifySessionState(mState)); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + if (aName.isEmpty()) + return E_INVALIDARG; + if (aAccessMode == 0 && !RT_VALID_PTR(aRetTimestamp)) + return E_POINTER; + + /* If this session is not in a VM process fend off the call. The caller + * handles this correctly, by doing the operation in VBoxSVC. */ + if (!mConsole) + return E_ACCESSDENIED; + + HRESULT hr; + if (aAccessMode == 2) + hr = mConsole->i_deleteGuestProperty(aName); + else if (aAccessMode == 1) + hr = mConsole->i_setGuestProperty(aName, aValue, aFlags); + else if (aAccessMode == 0) + hr = mConsole->i_getGuestProperty(aName, &aRetValue, aRetTimestamp, &aRetFlags); + else + hr = E_INVALIDARG; + + return hr; +# else /* VBOX_COM_INPROC_API_CLIENT */ + /** @todo This is nonsense, non-VM API users shouldn't need to deal with this + * method call, VBoxSVC should be clever enough to see that the + * session doesn't have a console! */ + RT_NOREF(aName, aValue, aFlags, aAccessMode, aRetValue, aRetTimestamp, aRetFlags); + return E_ACCESSDENIED; +# endif /* VBOX_COM_INPROC_API_CLIENT */ + +#else /* VBOX_WITH_GUEST_PROPS */ + ReturnComNotImplemented(); +#endif /* VBOX_WITH_GUEST_PROPS */ +} + +HRESULT Session::enumerateGuestProperties(const com::Utf8Str &aPatterns, + std::vector<com::Utf8Str> &aKeys, + std::vector<com::Utf8Str> &aValues, + std::vector<LONG64> &aTimestamps, + std::vector<com::Utf8Str> &aFlags) +{ +#if defined(VBOX_WITH_GUEST_PROPS) && !defined(VBOX_COM_INPROC_API_CLIENT) + if (mState != SessionState_Locked) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Machine is not locked by session (session state: %s)."), + Global::stringifySessionState(mState)); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + + /* If this session is not in a VM process fend off the call. The caller + * handles this correctly, by doing the operation in VBoxSVC. */ + if (!mConsole) + return E_ACCESSDENIED; + + return mConsole->i_enumerateGuestProperties(aPatterns, aKeys, aValues, aTimestamps, aFlags); + +#else /* VBOX_WITH_GUEST_PROPS not defined */ + RT_NOREF(aPatterns, aKeys, aValues, aTimestamps, aFlags); + ReturnComNotImplemented(); +#endif /* VBOX_WITH_GUEST_PROPS not defined */ +} + +HRESULT Session::onlineMergeMedium(const ComPtr<IMediumAttachment> &aMediumAttachment, ULONG aSourceIdx, + ULONG aTargetIdx, const ComPtr<IProgress> &aProgress) +{ + if (mState != SessionState_Locked) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Machine is not locked by session (session state: %s)."), + Global::stringifySessionState(mState)); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_onlineMergeMedium(aMediumAttachment, + aSourceIdx, aTargetIdx, + aProgress); +#else + RT_NOREF(aMediumAttachment, aSourceIdx, aTargetIdx, aProgress); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments) +{ + if (mState != SessionState_Locked) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Machine is not locked by session (session state: %s)."), + Global::stringifySessionState(mState)); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_reconfigureMediumAttachments(aAttachments); +#else + RT_NOREF(aAttachments); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::enableVMMStatistics(BOOL aEnable) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + mConsole->i_enableVMMStatistics(aEnable); + + return S_OK; +#else + RT_NOREF(aEnable); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::pauseWithReason(Reason_T aReason) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_pause(aReason); +#else + RT_NOREF(aReason); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::resumeWithReason(Reason_T aReason) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + AutoWriteLock dummyLock(mConsole COMMA_LOCKVAL_SRC_POS); + return mConsole->i_resume(aReason, dummyLock); +#else + RT_NOREF(aReason); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::saveStateWithReason(Reason_T aReason, + const ComPtr<IProgress> &aProgress, + const ComPtr<ISnapshot> &aSnapshot, + const Utf8Str &aStateFilePath, + BOOL aPauseVM, BOOL *aLeftPaused) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + bool fLeftPaused = false; + HRESULT hrc = mConsole->i_saveState(aReason, aProgress, aSnapshot, aStateFilePath, !!aPauseVM, fLeftPaused); + if (aLeftPaused) + *aLeftPaused = fLeftPaused; + return hrc; +#else + RT_NOREF(aReason, aProgress, aSnapshot, aStateFilePath, aPauseVM, aLeftPaused); + AssertFailed(); + return E_NOTIMPL; +#endif +} + +HRESULT Session::cancelSaveStateWithReason() +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE); + AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE); +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE); + + return mConsole->i_cancelSaveState(); +#else + AssertFailed(); + return E_NOTIMPL; +#endif +} + +// private methods +/////////////////////////////////////////////////////////////////////////////// + +/** + * Unlocks a machine associated with the current session. + * + * @param aFinalRelease called as a result of FinalRelease() + * @param aFromServer called as a result of Uninitialize() + * @param aLockW The write lock this object is protected with. + * Must be acquired already and will be released + * and later reacquired during the unlocking. + * + * @note To be called only from #uninit(), ISession::UnlockMachine() or + * ISession::Uninitialize(). + */ +HRESULT Session::i_unlockMachine(bool aFinalRelease, bool aFromServer, AutoWriteLock &aLockW) +{ + LogFlowThisFuncEnter(); + LogFlowThisFunc(("aFinalRelease=%d, isFromServer=%d\n", + aFinalRelease, aFromServer)); + + LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType)); + + Assert(aLockW.isWriteLockOnCurrentThread()); + + if (mState != SessionState_Locked) + { + Assert(mState == SessionState_Spawning); + + /* The session object is going to be uninitialized before it has been + * assigned a direct console of the machine the client requested to open + * a remote session to using IVirtualBox:: openRemoteSession(). It is OK + * only if this close request comes from the server (for example, it + * detected that the VM process it started terminated before opening a + * direct session). Otherwise, it means that the client is too fast and + * trying to close the session before waiting for the progress object it + * got from IVirtualBox:: openRemoteSession() to complete, so assert. */ + Assert(aFromServer); + + mState = SessionState_Unlocked; + mType = SessionType_Null; + + Assert(!mClientTokenHolder); + + LogFlowThisFuncLeave(); + return S_OK; + } + + /* go to the closing state */ + mState = SessionState_Unlocking; + + if (mType == SessionType_WriteLock) + { +#ifndef VBOX_COM_INPROC_API_CLIENT + if (!mConsole.isNull()) + { + mConsole->uninit(); + mConsole.setNull(); + } +#else + mRemoteMachine.setNull(); +#endif + } + else + { + mRemoteMachine.setNull(); + mRemoteConsole.setNull(); + } + + ComPtr<IProgress> progress; + + if (!aFinalRelease && !aFromServer) + { + /* + * We trigger OnSessionEnd() only when the session closes itself using + * Close(). Note that if isFinalRelease = TRUE here, this means that + * the client process has already initialized the termination procedure + * without issuing Close() and the IPC channel is no more operational -- + * so we cannot call the server's method (it will definitely fail). The + * server will instead simply detect the abnormal client death (since + * OnSessionEnd() is not called) and reset the machine state to Aborted. + */ + + /* + * while waiting for OnSessionEnd() to complete one of our methods + * can be called by the server (for example, Uninitialize(), if the + * direct session has initiated a closure just a bit before us) so + * we need to release the lock to avoid deadlocks. The state is already + * SessionState_Closing here, so it's safe. + */ + aLockW.release(); + + Assert(!aLockW.isWriteLockOnCurrentThread()); + + LogFlowThisFunc(("Calling mControl->OnSessionEnd()...\n")); + HRESULT hrc = mControl->OnSessionEnd(this, progress.asOutParam()); + LogFlowThisFunc(("mControl->OnSessionEnd()=%08X\n", hrc)); + + aLockW.acquire(); + + /* + * If we get E_UNEXPECTED this means that the direct session has already + * been closed, we're just too late with our notification and nothing more + * + * bird: Seems E_ACCESSDENIED is what gets returned these days; see + * ObjectState::addCaller. + */ + if (mType != SessionType_WriteLock && (hrc == E_UNEXPECTED || hrc == E_ACCESSDENIED)) + hrc = S_OK; + +#if !defined(DEBUG_bird) && !defined(DEBUG_andy) /* I don't want clients crashing on me just because VBoxSVC went belly up. */ + AssertComRC(hrc); +#endif + } + + mControl.setNull(); + + if (mType == SessionType_WriteLock) + { + if (mClientTokenHolder) + { + delete mClientTokenHolder; + mClientTokenHolder = NULL; + } + + if (!aFinalRelease && !aFromServer) + { + /* + * Wait for the server to grab the semaphore and destroy the session + * machine (allowing us to open a new session with the same machine + * once this method returns) + */ + Assert(!!progress); + if (progress) + progress->WaitForCompletion(-1); + } + } + + mState = SessionState_Unlocked; + mType = SessionType_Null; + + /* release the VirtualBox instance as the very last step */ + mVirtualBox.setNull(); + + LogFlowThisFuncLeave(); + return S_OK; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/USBDeviceImpl.cpp b/src/VBox/Main/src-client/USBDeviceImpl.cpp new file mode 100644 index 00000000..1e957514 --- /dev/null +++ b/src/VBox/Main/src-client/USBDeviceImpl.cpp @@ -0,0 +1,351 @@ +/* $Id: USBDeviceImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_USBDEVICE +#include "LoggingNew.h" + +#include "USBDeviceImpl.h" + +#include "AutoCaller.h" + +#include <iprt/cpp/utils.h> + + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR (OUSBDevice) + +HRESULT OUSBDevice::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void OUSBDevice::FinalRelease() +{ + uninit (); + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the USB device object. + * + * @returns COM result indicator + * @param aUSBDevice The USB device (interface) to clone. + */ +HRESULT OUSBDevice::init(IUSBDevice *aUSBDevice) +{ + LogFlowThisFunc(("aUSBDevice=%p\n", aUSBDevice)); + + ComAssertRet(aUSBDevice, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + HRESULT hrc = aUSBDevice->COMGETTER(VendorId)(&unconst(mData.vendorId)); + ComAssertComRCRet(hrc, hrc); + ComAssertRet(mData.vendorId, E_INVALIDARG); + + hrc = aUSBDevice->COMGETTER(ProductId)(&unconst(mData.productId)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(Revision)(&unconst(mData.revision)); + ComAssertComRCRet(hrc, hrc); + + Bstr bstr; + + hrc = aUSBDevice->COMGETTER(Manufacturer)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.manufacturer) = bstr; + + hrc = aUSBDevice->COMGETTER(Product)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.product) = bstr; + + hrc = aUSBDevice->COMGETTER(SerialNumber)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.serialNumber) = bstr; + + hrc = aUSBDevice->COMGETTER(Address)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.address) = bstr; + + hrc = aUSBDevice->COMGETTER(Backend)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.backend) = bstr; + + hrc = aUSBDevice->COMGETTER(Port)(&unconst(mData.port)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(PortPath)(bstr.asOutParam()); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(Version)(&unconst(mData.version)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(Speed)(&unconst(mData.speed)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(Remote)(&unconst(mData.remote)); + ComAssertComRCRet(hrc, hrc); + + Bstr uuid; + hrc = aUSBDevice->COMGETTER(Id)(uuid.asOutParam()); + ComAssertComRCRet(hrc, hrc); + unconst(mData.id) = Guid(uuid); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void OUSBDevice::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + unconst(mData.id).clear(); + + unconst(mData.vendorId) = 0; + unconst(mData.productId) = 0; + unconst(mData.revision) = 0; + + unconst(mData.manufacturer).setNull(); + unconst(mData.product).setNull(); + unconst(mData.serialNumber).setNull(); + + unconst(mData.address).setNull(); + unconst(mData.backend).setNull(); + + unconst(mData.port) = 0; + unconst(mData.portPath).setNull(); + unconst(mData.version) = 1; + + unconst(mData.remote) = FALSE; +} + +// IUSBDevice properties +///////////////////////////////////////////////////////////////////////////// + +/** + * Returns the GUID. + * + * @returns COM status code + * @param aId Address of result variable. + */ +HRESULT OUSBDevice::getId(com::Guid &aId) +{ + /* this is const, no need to lock */ + aId = mData.id; + + return S_OK; +} + + +/** + * Returns the vendor Id. + * + * @returns COM status code + * @param aVendorId Where to store the vendor id. + */ +HRESULT OUSBDevice::getVendorId(USHORT *aVendorId) +{ + /* this is const, no need to lock */ + *aVendorId = mData.vendorId; + + return S_OK; +} + + +/** + * Returns the product Id. + * + * @returns COM status code + * @param aProductId Where to store the product id. + */ +HRESULT OUSBDevice::getProductId(USHORT *aProductId) +{ + /* this is const, no need to lock */ + *aProductId = mData.productId; + + return S_OK; +} + + +/** + * Returns the revision BCD. + * + * @returns COM status code + * @param aRevision Where to store the revision BCD. + */ +HRESULT OUSBDevice::getRevision(USHORT *aRevision) +{ + /* this is const, no need to lock */ + *aRevision = mData.revision; + + return S_OK; +} + +/** + * Returns the manufacturer string. + * + * @returns COM status code + * @param aManufacturer Where to put the return string. + */ +HRESULT OUSBDevice::getManufacturer(com::Utf8Str &aManufacturer) +{ + /* this is const, no need to lock */ + aManufacturer = mData.manufacturer; + + return S_OK; +} + + +/** + * Returns the product string. + * + * @returns COM status code + * @param aProduct Where to put the return string. + */ +HRESULT OUSBDevice::getProduct(com::Utf8Str &aProduct) +{ + /* this is const, no need to lock */ + aProduct = mData.product; + + return S_OK; +} + + +/** + * Returns the serial number string. + * + * @returns COM status code + * @param aSerialNumber Where to put the return string. + */ +HRESULT OUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber) +{ + /* this is const, no need to lock */ + aSerialNumber = mData.serialNumber; + + return S_OK; +} + + +/** + * Returns the host specific device address. + * + * @returns COM status code + * @param aAddress Where to put the return string. + */ +HRESULT OUSBDevice::getAddress(com::Utf8Str &aAddress) +{ + /* this is const, no need to lock */ + aAddress = mData.address; + + return S_OK; +} + +HRESULT OUSBDevice::getPort(USHORT *aPort) +{ + /* this is const, no need to lock */ + *aPort = mData.port; + + return S_OK; +} + +HRESULT OUSBDevice::getPortPath(com::Utf8Str &aPortPath) +{ + /* this is const, no need to lock */ + aPortPath = mData.portPath; + + return S_OK; +} + +HRESULT OUSBDevice::getVersion(USHORT *aVersion) +{ + /* this is const, no need to lock */ + *aVersion = mData.version; + + return S_OK; +} + +HRESULT OUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed) +{ + /* this is const, no need to lock */ + *aSpeed = mData.speed; + + return S_OK; +} + +HRESULT OUSBDevice::getRemote(BOOL *aRemote) +{ + /* this is const, no need to lock */ + *aRemote = mData.remote; + + return S_OK; +} + +/** + * Returns the device specific backend. + * + * @returns COM status code + * @param aBackend Where to put the return string. + */ +HRESULT OUSBDevice::getBackend(com::Utf8Str &aBackend) +{ + /* this is const, no need to lock */ + aBackend = mData.backend; + + return S_OK; +} + +HRESULT OUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo) +{ + /* this is const, no need to lock */ + aInfo.resize(2); + aInfo[0] = mData.manufacturer; + aInfo[1] = mData.product; + + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/UsbCardReader.cpp b/src/VBox/Main/src-client/UsbCardReader.cpp new file mode 100644 index 00000000..9ffb9997 --- /dev/null +++ b/src/VBox/Main/src-client/UsbCardReader.cpp @@ -0,0 +1,1986 @@ +/* $Id: UsbCardReader.cpp $ */ +/** @file + * UsbCardReader - Driver Interface to USB Smart Card Reader emulation. + */ + +/* + * Copyright (C) 2011-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_USB_CARDREADER +#include "LoggingNew.h" + +#include "UsbCardReader.h" +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmcardreaderinfs.h> +#include <VBox/err.h> + +#include <iprt/req.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct USBCARDREADER USBCARDREADER; +typedef struct USBCARDREADER *PUSBCARDREADER; + +struct USBCARDREADER +{ + UsbCardReader *pUsbCardReader; + + PPDMDRVINS pDrvIns; + + PDMICARDREADERDOWN ICardReaderDown; + PPDMICARDREADERUP pICardReaderUp; + + /* Thread handling Cmd to card reader */ + PPDMTHREAD pThrCardReaderCmd; + /* Queue handling requests to cardreader */ + RTREQQUEUE hReqQCardReaderCmd; +}; + + +/* + * Command queue's callbacks. + */ + +static DECLCALLBACK(void) drvCardReaderCmdStatusChange(PUSBCARDREADER pThis, + void *pvUser, + uint32_t u32Timeout, + PDMICARDREADER_READERSTATE *paReaderStats, + uint32_t cReaderStats) +{ + LogFlowFunc(("ENTER: pvUser:%p, u32Timeout:%d\n", + pvUser, u32Timeout)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnSetStatusChange(pThis->pICardReaderUp, + pvUser, VRDE_SCARD_E_NO_SMARTCARD, + paReaderStats, cReaderStats); + } + else + { + pUsbCardReader->GetStatusChange(pThis, pvUser, u32Timeout, + paReaderStats, cReaderStats); + } + + LogFlowFuncLeave(); +} + + +static DECLCALLBACK(void) drvCardReaderCmdEstablishContext(PUSBCARDREADER pThis) +{ + LogFlowFunc(("\n")); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnEstablishContext(pThis->pICardReaderUp, + VRDE_SCARD_E_NO_SMARTCARD); + } + else + { + pUsbCardReader->EstablishContext(pThis); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdReleaseContext(PUSBCARDREADER pThis, + void *pvUser) +{ + LogFlowFunc(("ENTER: pvUser:%p\n", + pvUser)); + NOREF(pvUser); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + /* Do nothing. */ + } + else + { + pUsbCardReader->ReleaseContext(pThis); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdStatus(PUSBCARDREADER pThis, + void *pvUser) +{ + LogFlowFunc(("ENTER: pvUser:%p\n", + pvUser)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnStatus(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + /* pszReaderName */ NULL, + /* cchReaderName */ 0, + /* u32CardState */ 0, + /* u32Protocol */ 0, + /* pu8Atr */ 0, + /* cbAtr */ 0); + } + else + { + pUsbCardReader->Status(pThis, pvUser); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdConnect(PUSBCARDREADER pThis, + void *pvUser, + const char *pcszCardReaderName, + uint32_t u32ShareMode, + uint32_t u32PreferredProtocols) +{ + LogFlowFunc(("ENTER: pvUser:%p, pcszCardReaderName:%s, u32ShareMode:%RX32, u32PreferredProtocols:%RX32\n", + pvUser, pcszCardReaderName, u32ShareMode, u32PreferredProtocols)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnConnect(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + 0); + } + else + { + pUsbCardReader->Connect(pThis, pvUser, pcszCardReaderName, + u32ShareMode, u32PreferredProtocols); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdDisconnect(PUSBCARDREADER pThis, + void *pvUser, + uint32_t u32Disposition) +{ + LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n", + pvUser, u32Disposition)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnDisconnect(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD); + } + else + { + pUsbCardReader->Disconnect(pThis, pvUser, u32Disposition); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdTransmit(PUSBCARDREADER pThis, + void *pvUser, + PDMICARDREADER_IO_REQUEST *pioSendRequest, + uint8_t *pu8SendBuffer, + uint32_t cbSendBuffer, + uint32_t cbRecvBuffer) +{ + LogFlowFunc(("ENTER: pvUser:%p, pioSendRequest:%p, pu8SendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n", + pvUser, pioSendRequest, pu8SendBuffer, cbSendBuffer, cbRecvBuffer)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnTransmit(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + /* pioRecvPci */ NULL, + /* pu8RecvBuffer */ NULL, + /* cbRecvBuffer*/ 0); + } + else + { + pUsbCardReader->Transmit(pThis, pvUser, pioSendRequest, + pu8SendBuffer, cbSendBuffer, cbRecvBuffer); + } + + /* Clean up buffers allocated by driver */ + RTMemFree(pioSendRequest); + RTMemFree(pu8SendBuffer); + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdGetAttr(PUSBCARDREADER pThis, + void *pvUser, + uint32_t u32AttrId, + uint32_t cbAttrib) +{ + LogFlowFunc(("ENTER: pvUser:%p, u32AttrId:%RX32, cbAttrib:%d\n", + pvUser, u32AttrId, cbAttrib)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnGetAttrib(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + u32AttrId, + /* pvAttrib */ NULL, + /* cbAttrib */ 0); + } + else + { + pUsbCardReader->GetAttrib(pThis, pvUser, u32AttrId, cbAttrib); + } + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdSetAttr(PUSBCARDREADER pThis, + void *pvUser, + uint32_t u32AttrId, + void *pvAttrib, + uint32_t cbAttrib) +{ + LogFlowFunc(("ENTER: pvUser:%p, u32AttrId:%RX32, pvAttrib:%p, cbAttrib:%d\n", + pvUser, u32AttrId, pvAttrib, cbAttrib)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnSetAttrib(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + u32AttrId); + } + else + { + pUsbCardReader->SetAttrib(pThis, pvUser, u32AttrId, (uint8_t *)pvAttrib, cbAttrib); + } + + /* Clean up buffers allocated by driver */ + RTMemFree(pvAttrib); + + LogFlowFuncLeave(); +} + +static DECLCALLBACK(void) drvCardReaderCmdControl(PUSBCARDREADER pThis, + void *pvUser, + uint32_t u32ControlCode, + void *pvInBuffer, + uint32_t cbInBuffer, + uint32_t cbOutBuffer) +{ + LogFlowFunc(("ENTER: pvUser:%p, u32ControlCode:%RX32, pvInBuffer:%p, cbInBuffer:%d, cbOutBuffer:%d\n", + pvUser, u32ControlCode, pvInBuffer, cbInBuffer, cbOutBuffer)); + + UsbCardReader *pUsbCardReader = pThis->pUsbCardReader; + if (!pUsbCardReader) + { + pThis->pICardReaderUp->pfnControl(pThis->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + u32ControlCode, + /* pvOutBuffer */ NULL, + /* cbOutBuffer */ 0); + } + else + { + pUsbCardReader->Control(pThis, pvUser, u32ControlCode, + (uint8_t *)pvInBuffer, cbInBuffer, cbOutBuffer); + } + + /* Clean up buffers allocated by driver */ + RTMemFree(pvInBuffer); + + LogFlowFuncLeave(); +} + + +/* + * PDMICARDREADERDOWN - interface + */ + +static DECLCALLBACK(int) drvCardReaderDownConnect(PPDMICARDREADERDOWN pInterface, + void *pvUser, + const char *pcszCardReaderName, + uint32_t u32ShareMode, + uint32_t u32PreferredProtocols) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pcszCardReaderName:%s, pvUser:%p, u32ShareMode:%RX32, u32PreferredProtocols:%RX32\n", + pcszCardReaderName, pvUser, u32ShareMode, u32PreferredProtocols)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdConnect, 5, + pThis, pvUser, pcszCardReaderName, u32ShareMode, u32PreferredProtocols); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownDisconnect(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32Disposition) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n", + pvUser, u32Disposition)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdDisconnect, 3, + pThis, pvUser, u32Disposition); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownEstablishContext(PPDMICARDREADERDOWN pInterface) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER:\n")); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdEstablishContext, 1, + pThis); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownReleaseContext(PPDMICARDREADERDOWN pInterface, + void *pvUser) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p\n", + pvUser)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + /** @todo Device calls this when the driver already destroyed. */ + if (pThis->hReqQCardReaderCmd == NIL_RTREQQUEUE) + { + LogFlowFunc(("LEAVE: device already deleted.\n")); + return VINF_SUCCESS; + } + + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdReleaseContext, 2, + pThis, pvUser); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownStatus(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t cchReaderName, + uint32_t cbAtrLen) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, cchReaderName:%d, cbAtrLen:%d\n", + pvUser, cchReaderName, cbAtrLen)); + NOREF(cchReaderName); + NOREF(cbAtrLen); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdStatus, 2, + pThis, pvUser); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownGetStatusChange(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32Timeout, + PDMICARDREADER_READERSTATE *paReaderStats, + uint32_t cReaderStats) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32Timeout:%d, cReaderStats:%d\n", + pvUser, u32Timeout, cReaderStats)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdStatusChange, 5, + pThis, pvUser, u32Timeout, paReaderStats, cReaderStats); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownBeginTransaction(PPDMICARDREADERDOWN pInterface, + void *pvUser) +{ + RT_NOREF(pvUser); + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p\n", + pvUser)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); NOREF(pThis); + int rc = VERR_NOT_SUPPORTED; + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownEndTransaction(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32Disposition) +{ + RT_NOREF(pvUser, u32Disposition); + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32Disposition:%RX32\n", + pvUser, u32Disposition)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); NOREF(pThis); + int rc = VERR_NOT_SUPPORTED; + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownTransmit(PPDMICARDREADERDOWN pInterface, + void *pvUser, + const PDMICARDREADER_IO_REQUEST *pioSendRequest, + const uint8_t *pu8SendBuffer, + uint32_t cbSendBuffer, + uint32_t cbRecvBuffer) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, pioSendRequest:%p, pu8SendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n", + pvUser, pioSendRequest, pu8SendBuffer, cbSendBuffer, cbRecvBuffer)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + uint8_t *pu8SendBufferCopy = NULL; + if ( pu8SendBuffer + && cbSendBuffer) + { + pu8SendBufferCopy = (uint8_t *)RTMemDup(pu8SendBuffer, cbSendBuffer); + if (!pu8SendBufferCopy) + { + return VERR_NO_MEMORY; + } + } + PDMICARDREADER_IO_REQUEST *pioSendRequestCopy = NULL; + if (pioSendRequest) + { + pioSendRequestCopy = (PDMICARDREADER_IO_REQUEST *)RTMemDup(pioSendRequest, pioSendRequest->cbPciLength); + if (!pioSendRequestCopy) + { + RTMemFree(pu8SendBufferCopy); + return VERR_NO_MEMORY; + } + } + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0,RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdTransmit, 6, + pThis, pvUser, pioSendRequestCopy, pu8SendBufferCopy, cbSendBuffer, cbRecvBuffer); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownGetAttr(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32AttribId, + uint32_t cbAttrib) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32AttribId:%RX32, cbAttrib:%d\n", + pvUser, u32AttribId, cbAttrib)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdGetAttr, 4, + pThis, pvUser, u32AttribId, cbAttrib); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownSetAttr(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32AttribId, + const void *pvAttrib, + uint32_t cbAttrib) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32AttribId:%RX32, pvAttrib:%p, cbAttrib:%d\n", + pvUser, u32AttribId, pvAttrib, cbAttrib)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + void *pvAttribCopy = NULL; + if ( pvAttrib + && cbAttrib) + { + pvAttribCopy = RTMemDup(pvAttrib, cbAttrib); + AssertPtrReturn(pvAttribCopy, VERR_NO_MEMORY); + } + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdSetAttr, 5, + pThis, pvUser, u32AttribId, pvAttribCopy, cbAttrib); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderDownControl(PPDMICARDREADERDOWN pInterface, + void *pvUser, + uint32_t u32ControlCode, + const void *pvInBuffer, + uint32_t cbInBuffer, + uint32_t cbOutBuffer) +{ + AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER); + LogFlowFunc(("ENTER: pvUser:%p, u32ControlCode:%RX32 pvInBuffer:%p, cbInBuffer:%d, cbOutBuffer:%d\n", + pvUser, u32ControlCode, pvInBuffer, cbInBuffer, cbOutBuffer)); + PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown); + void *pvInBufferCopy = NULL; + if ( pvInBuffer + && cbInBuffer) + { + pvInBufferCopy = RTMemDup(pvInBuffer, cbInBuffer); + AssertReturn(pvInBufferCopy, VERR_NO_MEMORY); + } + int rc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)drvCardReaderCmdControl, 6, + pThis, pvUser, u32ControlCode, pvInBufferCopy, cbInBuffer, cbOutBuffer); + AssertRC(rc); + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + + +/* + * Cardreader driver thread routines + */ +static DECLCALLBACK(int) drvCardReaderThreadCmd(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + int rc = VINF_SUCCESS; + PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER); + + LogFlowFunc(("ENTER: pDrvIns:%d, state %d\n", pDrvIns->iInstance, pThread->enmState)); + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + { + LogFlowFunc(("LEAVE: INITIALIZING: VINF_SUCCESS\n")); + return VINF_SUCCESS; + } + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + rc = RTReqQueueProcess(pThis->hReqQCardReaderCmd, RT_INDEFINITE_WAIT); + + AssertMsg(rc == VWRN_STATE_CHANGED, + ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", + rc)); + } + + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +static DECLCALLBACK(int) drvCardReaderWakeupFunc(PUSBCARDREADER pThis) +{ + NOREF(pThis); + /* Returning a VINF_* will cause RTReqQueueProcess return. */ + return VWRN_STATE_CHANGED; +} + +static DECLCALLBACK(int) drvCardReaderThreadCmdWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pThread); + LogFlowFunc(("ENTER: pDrvIns:%i\n", pDrvIns->iInstance)); + PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER); + + AssertReturn(pThis->hReqQCardReaderCmd != NIL_RTREQQUEUE, VERR_INVALID_STATE); + + PRTREQ pReq; + int rc = RTReqQueueCall(pThis->hReqQCardReaderCmd, &pReq, 10000, (PFNRT)drvCardReaderWakeupFunc, 1, pThis); + AssertMsgRC(rc, ("Inserting request into queue failed rc=%Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + RTReqRelease(pReq); + /** @todo handle VERR_TIMEOUT */ + + return rc; +} + + +/* + * USB Card reader driver implementation. + */ + +UsbCardReader::UsbCardReader(Console *console) + : + mpDrv(NULL), + mParent(console), + m_pRemote(NULL) +{ + LogFlowFunc(("\n")); +} + +UsbCardReader::~UsbCardReader() +{ + LogFlowFunc(("mpDrv %p\n", mpDrv)); + if (mpDrv) + { + mpDrv->pUsbCardReader = NULL; + mpDrv = NULL; + } +} + +typedef struct UCRREMOTEREADER +{ + bool fAvailable; + char szReaderName[1024]; + + bool fHandle; + VRDESCARDHANDLE hCard; +} UCRREMOTEREADER; + +struct UCRREMOTE +{ + UsbCardReader *pUsbCardReader; + + /* The remote identifiers. */ + uint32_t u32ClientId; + uint32_t u32DeviceId; + + bool fContext; + VRDESCARDCONTEXT context; + + /* Possible a few readers. Currently only one. */ + UCRREMOTEREADER reader; +}; + +typedef struct UCRREQCTX +{ + UCRREMOTE *pRemote; + uint32_t u32Function; + void *pvUser; + union + { + struct + { + PDMICARDREADER_READERSTATE *paReaderStats; + uint32_t cReaderStats; + } GetStatusChange; + struct + { + uint32_t u32AttrId; + } GetAttrib; + struct + { + uint32_t u32AttrId; + } SetAttrib; + struct + { + uint32_t u32ControlCode; + } Control; + } u; +} UCRREQCTX; + +int UsbCardReader::vrdeSCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData) +{ + int rc = mParent->i_consoleVRDPServer()->SCardRequest(pvUser, u32Function, pvData, cbData); + LogFlowFunc(("%d %Rrc\n", u32Function, rc)); + return rc; +} + +int UsbCardReader::VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData) +{ + RT_NOREF(cbData); + int rc = VINF_SUCCESS; + + switch (u32Id) + { + case VRDE_SCARD_NOTIFY_ATTACH: + { + VRDESCARDNOTIFYATTACH *p = (VRDESCARDNOTIFYATTACH *)pvData; + Assert(cbData == sizeof(VRDESCARDNOTIFYATTACH)); + + LogFlowFunc(("[%d,%d]\n", p->u32ClientId, p->u32DeviceId)); + + /* Add this remote instance, which allow access to card readers attached to the client, to the list. + * @todo currently only one device is allowed. + */ + if (m_pRemote) + { + AssertFailed(); + rc = VERR_NOT_SUPPORTED; + break; + } + UCRREMOTE *pRemote = (UCRREMOTE *)RTMemAllocZ(sizeof(UCRREMOTE)); + if (pRemote == NULL) + { + rc = VERR_NO_MEMORY; + break; + } + + pRemote->pUsbCardReader = this; + pRemote->u32ClientId = p->u32ClientId; + pRemote->u32DeviceId = p->u32DeviceId; + + m_pRemote = pRemote; + + /* Try to establish a context. */ + VRDESCARDESTABLISHCONTEXTREQ req; + req.u32ClientId = m_pRemote->u32ClientId; + req.u32DeviceId = m_pRemote->u32DeviceId; + + rc = vrdeSCardRequest(m_pRemote, VRDE_SCARD_FN_ESTABLISHCONTEXT, &req, sizeof(req)); + + LogFlowFunc(("sent ESTABLISHCONTEXT\n")); + } break; + + case VRDE_SCARD_NOTIFY_DETACH: + { + VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; NOREF(p); + Assert(cbData == sizeof(VRDESCARDNOTIFYDETACH)); + + /** @todo Just free. There should be no pending requests, because VRDP cancels them. */ + RTMemFree(m_pRemote); + m_pRemote = NULL; + } break; + + default: + rc = VERR_INVALID_PARAMETER; + AssertFailed(); + break; + } + + return rc; +} + +int UsbCardReader::VRDEResponse(int rcRequest, void *pvUser, uint32_t u32Function, void *pvData, uint32_t cbData) +{ + RT_NOREF(cbData); + int rc = VINF_SUCCESS; + + LogFlowFunc(("%Rrc %p %u %p %u\n", + rcRequest, pvUser, u32Function, pvData, cbData)); + + switch (u32Function) + { + case VRDE_SCARD_FN_ESTABLISHCONTEXT: + { + Assert(cbData == sizeof(VRDESCARDESTABLISHCONTEXTRSP) || RT_FAILURE(rcRequest)); + VRDESCARDESTABLISHCONTEXTRSP *pRsp = (VRDESCARDESTABLISHCONTEXTRSP *)pvData; + UCRREMOTE *pRemote = (UCRREMOTE *)pvUser; + + /* Check if the context was created. */ + Assert(!pRemote->fContext); + if ( RT_SUCCESS(rcRequest) + && pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + pRemote->fContext = true; + pRemote->context = pRsp->Context; + + LogFlowFunc(("ESTABLISHCONTEXT success\n")); + + /* Now list readers attached to the remote client. */ + VRDESCARDLISTREADERSREQ req; + req.Context = pRemote->context; + + rc = vrdeSCardRequest(pRemote, VRDE_SCARD_FN_LISTREADERS, &req, sizeof(req)); + } + } break; + + case VRDE_SCARD_FN_LISTREADERS: + { + Assert(cbData == sizeof(VRDESCARDLISTREADERSRSP) || RT_FAILURE(rcRequest)); + VRDESCARDLISTREADERSRSP *pRsp = (VRDESCARDLISTREADERSRSP *)pvData; + UCRREMOTE *pRemote = (UCRREMOTE *)pvUser; + + Assert(pRemote->fContext); + if ( RT_SUCCESS(rcRequest) + && pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS + && pRemote->fContext) + { + LogFlowFunc(("LISTREADERS: cReaders %d\n", + pRsp->cReaders)); + + uint32_t i; + for (i = 0; i < pRsp->cReaders; i++) + { + LogFlowFunc(("LISTREADERS: [%d] [%s]\n", + i, pRsp->apszNames[i])); + + /** @todo only the first reader is supported. */ + if (i != 0) + { + continue; + } + + RTStrCopy(pRemote->reader.szReaderName, sizeof(pRemote->reader.szReaderName), pRsp->apszNames[i]); + pRemote->reader.fHandle = false; + pRemote->reader.fAvailable = true; + } + } + } break; + + case VRDE_SCARD_FN_RELEASECONTEXT: + { + Assert(cbData == sizeof(VRDESCARDRELEASECONTEXTRSP) || RT_FAILURE(rcRequest)); + VRDESCARDRELEASECONTEXTRSP *pRsp = (VRDESCARDRELEASECONTEXTRSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("RELEASECONTEXT completed\n")); + + /* No notification is expected here by the caller. */ + Assert(!m_pRemote->fContext); + } break; + + case VRDE_SCARD_FN_GETSTATUSCHANGE: + { + Assert(cbData == sizeof(VRDESCARDGETSTATUSCHANGERSP) || RT_FAILURE(rcRequest)); + VRDESCARDGETSTATUSCHANGERSP *pRsp = (VRDESCARDGETSTATUSCHANGERSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("GETSTATUSCHANGE\n")); + + uint32_t rcCard; + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + uint32_t i; + for (i = 0; i < pRsp->cReaders; i++) + { + LogFlowFunc(("GETSTATUSCHANGE: [%d] %RX32\n", + i, pRsp->aReaderStates[i].u32EventState)); + + /** @todo only the first reader is supported. */ + if (i != 0) + { + continue; + } + + if (i >= pCtx->u.GetStatusChange.cReaderStats) + { + continue; + } + + pCtx->u.GetStatusChange.paReaderStats[i].u32EventState = pRsp->aReaderStates[i].u32EventState; + pCtx->u.GetStatusChange.paReaderStats[i].cbAtr = pRsp->aReaderStates[i].u32AtrLength > 36? + 36: + pRsp->aReaderStates[i].u32AtrLength; + memcpy(pCtx->u.GetStatusChange.paReaderStats[i].au8Atr, + pRsp->aReaderStates[i].au8Atr, + pCtx->u.GetStatusChange.paReaderStats[i].cbAtr); + } + } + } + + mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pCtx->u.GetStatusChange.paReaderStats, + pCtx->u.GetStatusChange.cReaderStats); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_CANCEL: + { + Assert(cbData == sizeof(VRDESCARDCANCELRSP) || RT_FAILURE(rcRequest)); + VRDESCARDCANCELRSP *pRsp = (VRDESCARDCANCELRSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("CANCEL\n")); + } break; + + case VRDE_SCARD_FN_CONNECT: + { + Assert(cbData == sizeof(VRDESCARDCONNECTRSP) || RT_FAILURE(rcRequest)); + VRDESCARDCONNECTRSP *pRsp = (VRDESCARDCONNECTRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("CONNECT\n")); + + uint32_t u32ActiveProtocol = 0; + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + u32ActiveProtocol = pRsp->u32ActiveProtocol; + + Assert(!m_pRemote->reader.fHandle); + m_pRemote->reader.hCard = pRsp->hCard; + m_pRemote->reader.fHandle = true; + } + } + + mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + u32ActiveProtocol); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_RECONNECT: + { + Assert(cbData == sizeof(VRDESCARDRECONNECTRSP) || RT_FAILURE(rcRequest)); + VRDESCARDRECONNECTRSP *pRsp = (VRDESCARDRECONNECTRSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("RECONNECT\n")); + } break; + + case VRDE_SCARD_FN_DISCONNECT: + { + Assert(cbData == sizeof(VRDESCARDDISCONNECTRSP) || RT_FAILURE(rcRequest)); + VRDESCARDDISCONNECTRSP *pRsp = (VRDESCARDDISCONNECTRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("DISCONNECT\n")); + + Assert(!pCtx->pRemote->reader.fHandle); + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + } + + mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_BEGINTRANSACTION: + { + Assert(cbData == sizeof(VRDESCARDBEGINTRANSACTIONRSP) || RT_FAILURE(rcRequest)); + VRDESCARDBEGINTRANSACTIONRSP *pRsp = (VRDESCARDBEGINTRANSACTIONRSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("BEGINTRANSACTION\n")); + } break; + + case VRDE_SCARD_FN_ENDTRANSACTION: + { + Assert(cbData == sizeof(VRDESCARDENDTRANSACTIONRSP) || RT_FAILURE(rcRequest)); + VRDESCARDENDTRANSACTIONRSP *pRsp = (VRDESCARDENDTRANSACTIONRSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("ENDTRANSACTION\n")); + } break; + + case VRDE_SCARD_FN_STATE: + { + Assert(cbData == sizeof(VRDESCARDSTATERSP) || RT_FAILURE(rcRequest)); + VRDESCARDSTATERSP *pRsp = (VRDESCARDSTATERSP *)pvData; NOREF(pRsp); + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; NOREF(pCtx); + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("STATE\n")); + } break; + + case VRDE_SCARD_FN_STATUS: + { + Assert(cbData == sizeof(VRDESCARDSTATUSRSP) || RT_FAILURE(rcRequest)); + VRDESCARDSTATUSRSP *pRsp = (VRDESCARDSTATUSRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("STATUS\n")); + + char *pszReaderName = NULL; + uint32_t cchReaderName = 0; + uint32_t u32CardState = 0; + uint32_t u32Protocol = 0; + uint32_t u32AtrLength = 0; + uint8_t *pbAtr = NULL; + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + pszReaderName = pRsp->szReader; + cchReaderName = (uint32_t)strlen(pRsp->szReader) + 1; + u32CardState = pRsp->u32State; + u32Protocol = pRsp->u32Protocol; + u32AtrLength = pRsp->u32AtrLength; + pbAtr = &pRsp->au8Atr[0]; + } + } + + mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pszReaderName, + cchReaderName, + u32CardState, + u32Protocol, + pbAtr, + u32AtrLength); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_TRANSMIT: + { + Assert(cbData == sizeof(VRDESCARDTRANSMITRSP) || RT_FAILURE(rcRequest)); + VRDESCARDTRANSMITRSP *pRsp = (VRDESCARDTRANSMITRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("TRANSMIT\n")); + + PDMICARDREADER_IO_REQUEST *pioRecvPci = NULL; + uint8_t *pu8RecvBuffer = NULL; + uint32_t cbRecvBuffer = 0; + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + pu8RecvBuffer = pRsp->pu8RecvBuffer; + cbRecvBuffer = pRsp->u32RecvLength; + /** @todo pioRecvPci */ + } + } + + mpDrv->pICardReaderUp->pfnTransmit(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pioRecvPci, + pu8RecvBuffer, + cbRecvBuffer); + + RTMemFree(pioRecvPci); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_CONTROL: + { + Assert(cbData == sizeof(VRDESCARDCONTROLRSP) || RT_FAILURE(rcRequest)); + VRDESCARDCONTROLRSP *pRsp = (VRDESCARDCONTROLRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("CONTROL\n")); + + uint8_t *pu8OutBuffer = NULL; + uint32_t cbOutBuffer = 0; + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + pu8OutBuffer = pRsp->pu8OutBuffer; + cbOutBuffer = pRsp->u32OutBufferSize; + } + } + + mpDrv->pICardReaderUp->pfnControl(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pCtx->u.Control.u32ControlCode, + pu8OutBuffer, + cbOutBuffer); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_GETATTRIB: + { + Assert(cbData == sizeof(VRDESCARDGETATTRIBRSP) || RT_FAILURE(rcRequest)); + VRDESCARDGETATTRIBRSP *pRsp = (VRDESCARDGETATTRIBRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("GETATTRIB\n")); + + uint8_t *pu8Attrib = NULL; + uint32_t cbAttrib = 0; + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + + if (pRsp->u32ReturnCode == VRDE_SCARD_S_SUCCESS) + { + pu8Attrib = pRsp->pu8Attr; + cbAttrib = pRsp->u32AttrLength; + } + } + + mpDrv->pICardReaderUp->pfnGetAttrib(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pCtx->u.GetAttrib.u32AttrId, + pu8Attrib, + cbAttrib); + + RTMemFree(pCtx); + } break; + + case VRDE_SCARD_FN_SETATTRIB: + { + Assert(cbData == sizeof(VRDESCARDSETATTRIBRSP) || RT_FAILURE(rcRequest)); + VRDESCARDSETATTRIBRSP *pRsp = (VRDESCARDSETATTRIBRSP *)pvData; + UCRREQCTX *pCtx = (UCRREQCTX *)pvUser; + + Assert(pCtx->u32Function == u32Function); + + LogFlowFunc(("SETATTRIB\n")); + + uint32_t rcCard; + + if (RT_FAILURE(rcRequest)) + { + rcCard = VRDE_SCARD_E_NO_SMARTCARD; + } + else + { + rcCard = pRsp->u32ReturnCode; + } + + mpDrv->pICardReaderUp->pfnSetAttrib(mpDrv->pICardReaderUp, + pCtx->pvUser, + rcCard, + pCtx->u.SetAttrib.u32AttrId); + + RTMemFree(pCtx); + } break; + + default: + AssertFailed(); + rc = VERR_INVALID_PARAMETER; + break; + } + + return rc; +} + +int UsbCardReader::EstablishContext(struct USBCARDREADER *pDrv) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + /* The context here is a not a real device context. + * The device can be detached at the moment, for example the VRDP client did not connect yet. + */ + + return mpDrv->pICardReaderUp->pfnEstablishContext(mpDrv->pICardReaderUp, + VRDE_SCARD_S_SUCCESS); +} + +int UsbCardReader::ReleaseContext(struct USBCARDREADER *pDrv) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext) + { + /* Do nothing. */ + } + else + { + UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + /* Do nothing. */ + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_RELEASECONTEXT; + pCtx->pvUser = NULL; + + VRDESCARDRELEASECONTEXTREQ req; + req.Context = m_pRemote->context; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_RELEASECONTEXT, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + else + { + m_pRemote->fContext = false; + } + } + } + + return rc; +} + +int UsbCardReader::GetStatusChange(struct USBCARDREADER *pDrv, + void *pvUser, + uint32_t u32Timeout, + PDMICARDREADER_READERSTATE *paReaderStats, + uint32_t cReaderStats) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable) + { + rc = mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + paReaderStats, + cReaderStats); + } + else + { + UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rc = mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_MEMORY, + paReaderStats, + cReaderStats); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_GETSTATUSCHANGE; + pCtx->pvUser = pvUser; + pCtx->u.GetStatusChange.paReaderStats = paReaderStats; + pCtx->u.GetStatusChange.cReaderStats = cReaderStats; + + VRDESCARDGETSTATUSCHANGEREQ req; + req.Context = m_pRemote->context; + req.u32Timeout = u32Timeout; + req.cReaders = 1; + req.aReaderStates[0].pszReader = &m_pRemote->reader.szReaderName[0]; + req.aReaderStates[0].u32CurrentState = paReaderStats[0].u32CurrentState; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETSTATUSCHANGE, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + } + + return rc; +} + +int UsbCardReader::Connect(struct USBCARDREADER *pDrv, + void *pvUser, + const char *pszReaderName, + uint32_t u32ShareMode, + uint32_t u32PreferredProtocols) +{ + RT_NOREF(pszReaderName); + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable) + { + rc = mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + VRDE_SCARD_PROTOCOL_T0); + } + else + { + UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rc = mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_MEMORY, + VRDE_SCARD_PROTOCOL_T0); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_CONNECT; + pCtx->pvUser = pvUser; + + VRDESCARDCONNECTREQ req; + req.Context = m_pRemote->context; + req.pszReader = &m_pRemote->reader.szReaderName[0]; + req.u32ShareMode = u32ShareMode; + req.u32PreferredProtocols = u32PreferredProtocols; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONNECT, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + } + + return rc; +} + +int UsbCardReader::Disconnect(struct USBCARDREADER *pDrv, + void *pvUser, + uint32_t u32Mode) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rc = mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD); + } + else + { + UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rc = mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_MEMORY); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_DISCONNECT; + pCtx->pvUser = pvUser; + + VRDESCARDDISCONNECTREQ req; + req.hCard = m_pRemote->reader.hCard; + req.u32Disposition = u32Mode; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_DISCONNECT, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + else + { + m_pRemote->reader.fHandle = false; + } + } + } + + return rc; +} + +int UsbCardReader::Status(struct USBCARDREADER *pDrv, + void *pvUser) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rc = mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_SMARTCARD, + /* pszReaderName */ NULL, + /* cchReaderName */ 0, + /* u32CardState */ 0, + /* u32Protocol */ 0, + /* pu8Atr */ 0, + /* cbAtr */ 0); + } + else + { + UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rc = mpDrv->pICardReaderUp->pfnStatus(mpDrv->pICardReaderUp, + pvUser, + VRDE_SCARD_E_NO_MEMORY, + /* pszReaderName */ NULL, + /* cchReaderName */ 0, + /* u32CardState */ 0, + /* u32Protocol */ 0, + /* pu8Atr */ 0, + /* cbAtr */ 0); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_STATUS; + pCtx->pvUser = pvUser; + + VRDESCARDSTATUSREQ req; + req.hCard = m_pRemote->reader.hCard; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_STATUS, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + } + + return rc; +} + +int UsbCardReader::Transmit(struct USBCARDREADER *pDrv, + void *pvUser, + PDMICARDREADER_IO_REQUEST *pioSendRequest, + uint8_t *pu8SendBuffer, + uint32_t cbSendBuffer, + uint32_t cbRecvBuffer) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + UCRREQCTX *pCtx = NULL; + uint32_t rcSCard = VRDE_SCARD_S_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rcSCard = VRDE_SCARD_E_NO_SMARTCARD; + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + if ( !pioSendRequest + || ( pioSendRequest->cbPciLength < 2 * sizeof(uint32_t) + || pioSendRequest->cbPciLength > 2 * sizeof(uint32_t) + VRDE_SCARD_MAX_PCI_DATA) + ) + { + AssertFailed(); + rcSCard = VRDE_SCARD_E_INVALID_PARAMETER; + } + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rcSCard = VRDE_SCARD_E_NO_MEMORY; + } + } + + if (rcSCard != VRDE_SCARD_S_SUCCESS) + { + Assert(pCtx == NULL); + + rc = pDrv->pICardReaderUp->pfnTransmit(pDrv->pICardReaderUp, + pvUser, + rcSCard, + /* pioRecvPci */ NULL, + /* pu8RecvBuffer */ NULL, + /* cbRecvBuffer*/ 0); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_TRANSMIT; + pCtx->pvUser = pvUser; + + VRDESCARDTRANSMITREQ req; + req.hCard = m_pRemote->reader.hCard; + + req.ioSendPci.u32Protocol = pioSendRequest->u32Protocol; + req.ioSendPci.u32PciLength = pioSendRequest->cbPciLength < 2 * sizeof(uint32_t)? + (uint32_t)(2 * sizeof(uint32_t)): + pioSendRequest->cbPciLength; + Assert(pioSendRequest->cbPciLength <= VRDE_SCARD_MAX_PCI_DATA + 2 * sizeof(uint32_t)); + memcpy(req.ioSendPci.au8PciData, + (uint8_t *)pioSendRequest + 2 * sizeof(uint32_t), + req.ioSendPci.u32PciLength - 2 * sizeof(uint32_t)); + + req.u32SendLength = cbSendBuffer; + req.pu8SendBuffer = pu8SendBuffer; + req.u32RecvLength = cbRecvBuffer; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_TRANSMIT, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + + return rc; +} + +int UsbCardReader::Control(struct USBCARDREADER *pDrv, + void *pvUser, + uint32_t u32ControlCode, + uint8_t *pu8InBuffer, + uint32_t cbInBuffer, + uint32_t cbOutBuffer) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + UCRREQCTX *pCtx = NULL; + uint32_t rcSCard = VRDE_SCARD_S_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rcSCard = VRDE_SCARD_E_NO_SMARTCARD; + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + if ( cbInBuffer > _128K + || cbOutBuffer > _128K) + { + AssertFailed(); + rcSCard = VRDE_SCARD_E_INVALID_PARAMETER; + } + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rcSCard = VRDE_SCARD_E_NO_MEMORY; + } + } + + if (rcSCard != VRDE_SCARD_S_SUCCESS) + { + Assert(pCtx == NULL); + + rc = pDrv->pICardReaderUp->pfnControl(pDrv->pICardReaderUp, + pvUser, + rcSCard, + u32ControlCode, + /* pvOutBuffer */ NULL, + /* cbOutBuffer*/ 0); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_CONTROL; + pCtx->pvUser = pvUser; + pCtx->u.Control.u32ControlCode = u32ControlCode; + + VRDESCARDCONTROLREQ req; + req.hCard = m_pRemote->reader.hCard; + req.u32ControlCode = u32ControlCode; + req.u32InBufferSize = cbInBuffer; + req.pu8InBuffer = pu8InBuffer; + req.u32OutBufferSize = cbOutBuffer; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONTROL, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + + return rc; +} + +int UsbCardReader::GetAttrib(struct USBCARDREADER *pDrv, + void *pvUser, + uint32_t u32AttrId, + uint32_t cbAttrib) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + UCRREQCTX *pCtx = NULL; + uint32_t rcSCard = VRDE_SCARD_S_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rcSCard = VRDE_SCARD_E_NO_SMARTCARD; + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + if (cbAttrib > _128K) + { + AssertFailed(); + rcSCard = VRDE_SCARD_E_INVALID_PARAMETER; + } + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rcSCard = VRDE_SCARD_E_NO_MEMORY; + } + } + + if (rcSCard != VRDE_SCARD_S_SUCCESS) + { + Assert(pCtx == NULL); + + pDrv->pICardReaderUp->pfnGetAttrib(pDrv->pICardReaderUp, + pvUser, + rcSCard, + u32AttrId, + /* pvAttrib */ NULL, + /* cbAttrib */ 0); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_GETATTRIB; + pCtx->pvUser = pvUser; + pCtx->u.GetAttrib.u32AttrId = u32AttrId; + + VRDESCARDGETATTRIBREQ req; + req.hCard = m_pRemote->reader.hCard; + req.u32AttrId = u32AttrId; + req.u32AttrLen = cbAttrib; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETATTRIB, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + + return rc; +} + +int UsbCardReader::SetAttrib(struct USBCARDREADER *pDrv, + void *pvUser, + uint32_t u32AttrId, + uint8_t *pu8Attrib, + uint32_t cbAttrib) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int rc = VINF_SUCCESS; + + UCRREQCTX *pCtx = NULL; + uint32_t rcSCard = VRDE_SCARD_S_SUCCESS; + + if ( !m_pRemote + || !m_pRemote->fContext + || !m_pRemote->reader.fAvailable + || !m_pRemote->reader.fHandle) + { + rcSCard = VRDE_SCARD_E_NO_SMARTCARD; + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + if (cbAttrib > _128K) + { + AssertFailed(); + rcSCard = VRDE_SCARD_E_INVALID_PARAMETER; + } + } + + if (rcSCard == VRDE_SCARD_S_SUCCESS) + { + pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX)); + if (!pCtx) + { + rcSCard = VRDE_SCARD_E_NO_MEMORY; + } + } + + if (rcSCard != VRDE_SCARD_S_SUCCESS) + { + Assert(pCtx == NULL); + + pDrv->pICardReaderUp->pfnSetAttrib(pDrv->pICardReaderUp, + pvUser, + rcSCard, + u32AttrId); + } + else + { + pCtx->pRemote = m_pRemote; + pCtx->u32Function = VRDE_SCARD_FN_SETATTRIB; + pCtx->pvUser = pvUser; + pCtx->u.SetAttrib.u32AttrId = u32AttrId; + + VRDESCARDSETATTRIBREQ req; + req.hCard = m_pRemote->reader.hCard; + req.u32AttrId = u32AttrId; + req.u32AttrLen = cbAttrib; + req.pu8Attr = pu8Attrib; + + rc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_SETATTRIB, &req, sizeof(req)); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + + return rc; +} + + +/* + * PDMDRVINS + */ + +/* static */ DECLCALLBACK(void *) UsbCardReader::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + LogFlowFunc(("pInterface:%p, pszIID:%s\n", pInterface, pszIID)); + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMICARDREADERDOWN, &pThis->ICardReaderDown); + return NULL; +} + +/* static */ DECLCALLBACK(void) UsbCardReader::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + LogFlowFunc(("iInstance/%d\n",pDrvIns->iInstance)); + PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER); + + /** @todo The driver is destroyed before the device. + * So device calls ReleaseContext when there is no more driver. + * Notify the device here so it can do cleanup or + * do a cleanup now in the driver. + */ + if (pThis->hReqQCardReaderCmd != NIL_RTREQQUEUE) + { + int rc = RTReqQueueDestroy(pThis->hReqQCardReaderCmd); + AssertRC(rc); + pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE; + } + + pThis->pUsbCardReader->mpDrv = NULL; + pThis->pUsbCardReader = NULL; + LogFlowFuncLeave(); +} + +/* static */ DECLCALLBACK(int) UsbCardReader::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags, pCfg); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + LogFlowFunc(("iInstance/%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags)); + PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER); + + pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE; + + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + com::Guid uuid(USBCARDREADER_OID); + pThis->pUsbCardReader = (UsbCardReader *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw()); + AssertMsgReturn(RT_VALID_PTR(pThis->pUsbCardReader), ("Configuration error: No/bad USB card reader object value!\n"), VERR_NOT_FOUND); + + pThis->pUsbCardReader->mpDrv = pThis; + pThis->pDrvIns = pDrvIns; + + pDrvIns->IBase.pfnQueryInterface = UsbCardReader::drvQueryInterface; + + pThis->ICardReaderDown.pfnEstablishContext = drvCardReaderDownEstablishContext; + pThis->ICardReaderDown.pfnReleaseContext = drvCardReaderDownReleaseContext; + pThis->ICardReaderDown.pfnConnect = drvCardReaderDownConnect; + pThis->ICardReaderDown.pfnDisconnect = drvCardReaderDownDisconnect; + pThis->ICardReaderDown.pfnStatus = drvCardReaderDownStatus; + pThis->ICardReaderDown.pfnGetStatusChange = drvCardReaderDownGetStatusChange; + pThis->ICardReaderDown.pfnBeginTransaction = drvCardReaderDownBeginTransaction; + pThis->ICardReaderDown.pfnEndTransaction = drvCardReaderDownEndTransaction; + pThis->ICardReaderDown.pfnTransmit = drvCardReaderDownTransmit; + pThis->ICardReaderDown.pfnGetAttr = drvCardReaderDownGetAttr; + pThis->ICardReaderDown.pfnSetAttr = drvCardReaderDownSetAttr; + pThis->ICardReaderDown.pfnControl = drvCardReaderDownControl; + + pThis->pICardReaderUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMICARDREADERUP); + AssertReturn(pThis->pICardReaderUp, VERR_PDM_MISSING_INTERFACE); + + /* Command Thread Synchronization primitives */ + int rc = RTReqQueueCreate(&pThis->hReqQCardReaderCmd); + AssertLogRelRCReturn(rc, rc); + + rc = PDMDrvHlpThreadCreate(pDrvIns, + &pThis->pThrCardReaderCmd, + pThis, + drvCardReaderThreadCmd /* worker routine */, + drvCardReaderThreadCmdWakeup /* wakeup routine */, + 128 * _1K, RTTHREADTYPE_IO, "UCRCMD"); + if (RT_FAILURE(rc)) + { + RTReqQueueDestroy(pThis->hReqQCardReaderCmd); + pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE; + } + + LogFlowFunc(("LEAVE: %Rrc\n", rc)); + return rc; +} + +/* static */ const PDMDRVREG UsbCardReader::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName[32] */ + "UsbCardReader", + /* szRCMod[32] */ + "", + /* szR0Mod[32] */ + "", + /* pszDescription */ + "Main Driver communicating with VRDE", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass */ + PDM_DRVREG_CLASS_USB, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(USBCARDREADER), + /* pfnConstruct */ + UsbCardReader::drvConstruct, + /* pfnDestruct */ + UsbCardReader::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DRVREG_VERSION +}; +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/UsbWebcamInterface.cpp b/src/VBox/Main/src-client/UsbWebcamInterface.cpp new file mode 100644 index 00000000..8fe5427a --- /dev/null +++ b/src/VBox/Main/src-client/UsbWebcamInterface.cpp @@ -0,0 +1,492 @@ +/* $Id: UsbWebcamInterface.cpp $ */ +/** @file + * UsbWebcamInterface - Driver Interface for USB Webcam emulation. + */ + +/* + * Copyright (C) 2011-2022 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 + */ + + +#define LOG_GROUP LOG_GROUP_USB_WEBCAM +#include "LoggingNew.h" + +#include "UsbWebcamInterface.h" +#include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" +#include "EmulatedUSBImpl.h" + +#include <VBox/vmm/pdmwebcaminfs.h> +#include <VBox/err.h> + + +typedef struct EMWEBCAMREMOTE +{ + EmWebcam *pEmWebcam; + + VRDEVIDEOINDEVICEHANDLE deviceHandle; /* The remote identifier. */ + + /* Received from the remote client. */ + uint32_t u32Version; /* VRDE_VIDEOIN_NEGOTIATE_VERSION */ + uint32_t fu32Capabilities; /* VRDE_VIDEOIN_NEGOTIATE_CAP_* */ + VRDEVIDEOINDEVICEDESC *pDeviceDesc; + uint32_t cbDeviceDesc; + + /* The device identifier for the PDM device.*/ + uint64_t u64DeviceId; +} EMWEBCAMREMOTE; + +typedef struct EMWEBCAMDRV +{ + EMWEBCAMREMOTE *pRemote; + PPDMIWEBCAMDEV pIWebcamUp; + PDMIWEBCAMDRV IWebcamDrv; +} EMWEBCAMDRV, *PEMWEBCAMDRV; + +typedef struct EMWEBCAMREQCTX +{ + EMWEBCAMREMOTE *pRemote; + void *pvUser; +} EMWEBCAMREQCTX; + + +static DECLCALLBACK(void) drvEmWebcamReady(PPDMIWEBCAMDRV pInterface, + bool fReady) +{ + NOREF(fReady); + + PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv); + EMWEBCAMREMOTE *pRemote = pThis->pRemote; + + LogFlowFunc(("pRemote:%p\n", pThis->pRemote)); + + if (pThis->pIWebcamUp) + { + pThis->pIWebcamUp->pfnAttached(pThis->pIWebcamUp, + pRemote->u64DeviceId, + pRemote->pDeviceDesc, + pRemote->cbDeviceDesc, + pRemote->u32Version, + pRemote->fu32Capabilities); + } +} + +static DECLCALLBACK(int) drvEmWebcamControl(PPDMIWEBCAMDRV pInterface, + void *pvUser, + uint64_t u64DeviceId, + const struct VRDEVIDEOINCTRLHDR *pCtrl, + uint32_t cbCtrl) +{ + PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv); + EMWEBCAMREMOTE *pRemote = pThis->pRemote; + + LogFlowFunc(("pRemote:%p, u64DeviceId %lld\n", pRemote, u64DeviceId)); + + return pRemote->pEmWebcam->SendControl(pThis, pvUser, u64DeviceId, pCtrl, cbCtrl); +} + + +EmWebcam::EmWebcam(ConsoleVRDPServer *pServer) + : + mParent(pServer), + mpDrv(NULL), + mpRemote(NULL), + mu64DeviceIdSrc(0) +{ +} + +EmWebcam::~EmWebcam() +{ + if (mpDrv) + { + mpDrv->pRemote = NULL; + mpDrv = NULL; + } +} + +void EmWebcam::EmWebcamConstruct(EMWEBCAMDRV *pDrv) +{ + AssertReturnVoid(mpDrv == NULL); + + mpDrv = pDrv; +} + +void EmWebcam::EmWebcamDestruct(EMWEBCAMDRV *pDrv) +{ + AssertReturnVoid(pDrv == mpDrv); + + if (mpRemote) + { + mParent->VideoInDeviceDetach(&mpRemote->deviceHandle); + + RTMemFree(mpRemote->pDeviceDesc); + mpRemote->pDeviceDesc = NULL; + mpRemote->cbDeviceDesc = 0; + + RTMemFree(mpRemote); + mpRemote = NULL; + } + + mpDrv->pRemote = NULL; + mpDrv = NULL; +} + +void EmWebcam::EmWebcamCbNotify(uint32_t u32Id, const void *pvData, uint32_t cbData) +{ + int vrc = VINF_SUCCESS; + + switch (u32Id) + { + case VRDE_VIDEOIN_NOTIFY_ID_ATTACH: + { + VRDEVIDEOINNOTIFYATTACH *p = (VRDEVIDEOINNOTIFYATTACH *)pvData; + + /* Older versions did not report u32Version and fu32Capabilities. */ + uint32_t u32Version = 1; + uint32_t fu32Capabilities = VRDE_VIDEOIN_NEGOTIATE_CAP_VOID; + + if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, u32Version) + sizeof(p->u32Version)) + u32Version = p->u32Version; + + if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, fu32Capabilities) + sizeof(p->fu32Capabilities)) + fu32Capabilities = p->fu32Capabilities; + + LogFlowFunc(("ATTACH[%d,%d] version %d, caps 0x%08X\n", + p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId, + u32Version, fu32Capabilities)); + + /* Currently only one device is allowed. */ + if (mpRemote) + { + AssertFailed(); + vrc = VERR_NOT_SUPPORTED; + break; + } + + EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)RTMemAllocZ(sizeof(EMWEBCAMREMOTE)); + if (pRemote == NULL) + { + vrc = VERR_NO_MEMORY; + break; + } + + pRemote->pEmWebcam = this; + pRemote->deviceHandle = p->deviceHandle; + pRemote->u32Version = u32Version; + pRemote->fu32Capabilities = fu32Capabilities; + pRemote->pDeviceDesc = NULL; + pRemote->cbDeviceDesc = 0; + pRemote->u64DeviceId = ASMAtomicIncU64(&mu64DeviceIdSrc); + + mpRemote = pRemote; + + /* Tell the server that this webcam will be used. */ + vrc = mParent->VideoInDeviceAttach(&mpRemote->deviceHandle, mpRemote); + if (RT_FAILURE(vrc)) + { + RTMemFree(mpRemote); + mpRemote = NULL; + break; + } + + /* Get the device description. */ + vrc = mParent->VideoInGetDeviceDesc(NULL, &mpRemote->deviceHandle); + + if (RT_FAILURE(vrc)) + { + mParent->VideoInDeviceDetach(&mpRemote->deviceHandle); + RTMemFree(mpRemote); + mpRemote = NULL; + break; + } + + LogFlowFunc(("sent DeviceDesc\n")); + } break; + + case VRDE_VIDEOIN_NOTIFY_ID_DETACH: + { + VRDEVIDEOINNOTIFYDETACH *p = (VRDEVIDEOINNOTIFYDETACH *)pvData; NOREF(p); + Assert(cbData == sizeof(VRDEVIDEOINNOTIFYDETACH)); + + LogFlowFunc(("DETACH[%d,%d]\n", p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId)); + + /** @todo */ + if (mpRemote) + { + if (mpDrv && mpDrv->pIWebcamUp) + mpDrv->pIWebcamUp->pfnDetached(mpDrv->pIWebcamUp, mpRemote->u64DeviceId); + /* mpRemote is deallocated in EmWebcamDestruct */ + } + } break; + + default: + vrc = VERR_INVALID_PARAMETER; + AssertFailed(); + break; + } + + return; +} + +void EmWebcam::EmWebcamCbDeviceDesc(int rcRequest, void *pDeviceCtx, void *pvUser, + const VRDEVIDEOINDEVICEDESC *pDeviceDesc, uint32_t cbDeviceDesc) +{ + RT_NOREF(pvUser); + EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx; + Assert(pRemote == mpRemote); + + LogFlowFunc(("mpDrv %p, rcRequest %Rrc %p %p %p %d\n", + mpDrv, rcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDeviceDesc)); + + if (RT_SUCCESS(rcRequest)) + { + /* Save device description. */ + Assert(pRemote->pDeviceDesc == NULL); + pRemote->pDeviceDesc = (VRDEVIDEOINDEVICEDESC *)RTMemDup(pDeviceDesc, cbDeviceDesc); + pRemote->cbDeviceDesc = cbDeviceDesc; + + /* Try to attach the device. */ + EmulatedUSB *pEUSB = mParent->getConsole()->i_getEmulatedUSB(); + pEUSB->i_webcamAttachInternal("", "", "EmWebcam", pRemote); + } + else + { + mParent->VideoInDeviceDetach(&mpRemote->deviceHandle); + RTMemFree(mpRemote); + mpRemote = NULL; + } +} + +void EmWebcam::EmWebcamCbControl(int rcRequest, void *pDeviceCtx, void *pvUser, + const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl) +{ + RT_NOREF(rcRequest); + EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx; NOREF(pRemote); + Assert(pRemote == mpRemote); + + LogFlowFunc(("rcRequest %Rrc %p %p %p %d\n", + rcRequest, pDeviceCtx, pvUser, pControl, cbControl)); + + bool fResponse = (pvUser != NULL); + + if (mpDrv && mpDrv->pIWebcamUp) + { + mpDrv->pIWebcamUp->pfnControl(mpDrv->pIWebcamUp, + fResponse, + pvUser, + mpRemote->u64DeviceId, + pControl, + cbControl); + } + + RTMemFree(pvUser); +} + +void EmWebcam::EmWebcamCbFrame(int rcRequest, void *pDeviceCtx, + const VRDEVIDEOINPAYLOADHDR *pFrame, uint32_t cbFrame) +{ + RT_NOREF(rcRequest, pDeviceCtx); + LogFlowFunc(("rcRequest %Rrc %p %p %d\n", + rcRequest, pDeviceCtx, pFrame, cbFrame)); + + if (mpDrv && mpDrv->pIWebcamUp) + { + if ( cbFrame >= sizeof(VRDEVIDEOINPAYLOADHDR) + && cbFrame >= pFrame->u8HeaderLength) + { + uint32_t cbImage = cbFrame - pFrame->u8HeaderLength; + const uint8_t *pu8Image = cbImage > 0? (const uint8_t *)pFrame + pFrame->u8HeaderLength: NULL; + + mpDrv->pIWebcamUp->pfnFrame(mpDrv->pIWebcamUp, + mpRemote->u64DeviceId, + pFrame, + pFrame->u8HeaderLength, + pu8Image, + cbImage); + } + } +} + +int EmWebcam::SendControl(EMWEBCAMDRV *pDrv, void *pvUser, uint64_t u64DeviceId, + const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl) +{ + AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED); + + int vrc = VINF_SUCCESS; + + EMWEBCAMREQCTX *pCtx = NULL; + + /* Verify that there is a remote device. */ + if ( !mpRemote + || mpRemote->u64DeviceId != u64DeviceId) + { + vrc = VERR_NOT_SUPPORTED; + } + + if (RT_SUCCESS(vrc)) + { + pCtx = (EMWEBCAMREQCTX *)RTMemAlloc(sizeof(EMWEBCAMREQCTX)); + if (!pCtx) + { + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + pCtx->pRemote = mpRemote; + pCtx->pvUser = pvUser; + + vrc = mParent->VideoInControl(pCtx, &mpRemote->deviceHandle, pControl, cbControl); + + if (RT_FAILURE(vrc)) + { + RTMemFree(pCtx); + } + } + + return vrc; +} + +/* static */ DECLCALLBACK(void *) EmWebcam::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV); + + LogFlowFunc(("pszIID:%s\n", pszIID)); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIWEBCAMDRV, &pThis->IWebcamDrv); + return NULL; +} + +/* static */ DECLCALLBACK(void) EmWebcam::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV); + EMWEBCAMREMOTE *pRemote = pThis->pRemote; + + LogFlowFunc(("iInstance %d, pRemote %p, pIWebcamUp %p\n", + pDrvIns->iInstance, pRemote, pThis->pIWebcamUp)); + + if (pRemote && pRemote->pEmWebcam) + { + pRemote->pEmWebcam->EmWebcamDestruct(pThis); + } +} + +/* static */ DECLCALLBACK(int) EmWebcam::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + LogFlowFunc(("iInstance:%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags)); + + PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV); + + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* Check early that there is a device. No need to init anything if there is no device. */ + pThis->pIWebcamUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIWEBCAMDEV); + if (pThis->pIWebcamUp == NULL) + { + LogRel(("USBWEBCAM: Emulated webcam device does not exist.\n")); + return VERR_PDM_MISSING_INTERFACE; + } + + char *pszId = NULL; + int vrc = pDrvIns->pHlpR3->pfnCFGMQueryStringAlloc(pCfg, "Id", &pszId); + if (RT_SUCCESS(vrc)) + { + RTUUID UuidEmulatedUsbIf; + vrc = RTUuidFromStr(&UuidEmulatedUsbIf, EMULATEDUSBIF_OID); AssertRC(vrc); + + PEMULATEDUSBIF pEmulatedUsbIf = (PEMULATEDUSBIF)PDMDrvHlpQueryGenericUserObject(pDrvIns, &UuidEmulatedUsbIf); + AssertPtrReturn(pEmulatedUsbIf, VERR_INVALID_PARAMETER); + + vrc = pEmulatedUsbIf->pfnQueryEmulatedUsbDataById(pEmulatedUsbIf->pvUser, pszId, + NULL /*ppvEmUsbCb*/, NULL /*ppvEmUsbCbData*/, (void **)&pThis->pRemote); + pDrvIns->pHlpR3->pfnMMHeapFree(pDrvIns, pszId); + AssertRCReturn(vrc, vrc); + } + else + return vrc; + + /* Everything ok. Initialize. */ + pThis->pRemote->pEmWebcam->EmWebcamConstruct(pThis); + + pDrvIns->IBase.pfnQueryInterface = drvQueryInterface; + + pThis->IWebcamDrv.pfnReady = drvEmWebcamReady; + pThis->IWebcamDrv.pfnControl = drvEmWebcamControl; + + return VINF_SUCCESS; +} + +/* static */ const PDMDRVREG EmWebcam::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName[32] */ + "EmWebcam", + /* szRCMod[32] */ + "", + /* szR0Mod[32] */ + "", + /* pszDescription */ + "Main Driver communicating with VRDE", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass */ + PDM_DRVREG_CLASS_USB, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(EMWEBCAMDRV), + /* pfnConstruct */ + EmWebcam::drvConstruct, + /* pfnDestruct */ + EmWebcam::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DRVREG_VERSION +}; +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/VBoxDriversRegister.cpp b/src/VBox/Main/src-client/VBoxDriversRegister.cpp new file mode 100644 index 00000000..e9badd13 --- /dev/null +++ b/src/VBox/Main/src-client/VBoxDriversRegister.cpp @@ -0,0 +1,124 @@ +/* $Id: VBoxDriversRegister.cpp $ */ +/** @file + * + * Main driver registration. + */ + +/* + * Copyright (C) 2006-2022 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN +#include "LoggingNew.h" + +#include "MouseImpl.h" +#include "KeyboardImpl.h" +#include "DisplayImpl.h" +#include "VMMDev.h" +#include "NvramStoreImpl.h" +#ifdef VBOX_WITH_AUDIO_VRDE +# include "DrvAudioVRDE.h" +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING +# include "DrvAudioRec.h" +#endif +#include "UsbWebcamInterface.h" +#ifdef VBOX_WITH_USB_CARDREADER +# include "UsbCardReader.h" +#endif +#include "ConsoleImpl.h" +#ifdef VBOX_WITH_PCI_PASSTHROUGH +# include "PCIRawDevImpl.h" +#endif + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/version.h> + + +/** + * Register the main drivers. + * + * @returns VBox status code. + * @param pCallbacks Pointer to the callback table. + * @param u32Version VBox version number. + */ +extern "C" DECLEXPORT(int) VBoxDriversRegister(PCPDMDRVREGCB pCallbacks, uint32_t u32Version) +{ + LogFlow(("VBoxDriversRegister: u32Version=%#x\n", u32Version)); + AssertReleaseMsg(u32Version == VBOX_VERSION, ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION)); + + int rc = pCallbacks->pfnRegister(pCallbacks, &Mouse::DrvReg); + if (RT_FAILURE(rc)) + return rc; + + rc = pCallbacks->pfnRegister(pCallbacks, &Keyboard::DrvReg); + if (RT_FAILURE(rc)) + return rc; + + rc = pCallbacks->pfnRegister(pCallbacks, &Display::DrvReg); + if (RT_FAILURE(rc)) + return rc; + + rc = pCallbacks->pfnRegister(pCallbacks, &VMMDev::DrvReg); + if (RT_FAILURE(rc)) + return rc; +#ifdef VBOX_WITH_AUDIO_VRDE + rc = pCallbacks->pfnRegister(pCallbacks, &AudioVRDE::DrvReg); + if (RT_FAILURE(rc)) + return rc; +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING + rc = pCallbacks->pfnRegister(pCallbacks, &AudioVideoRec::DrvReg); + if (RT_FAILURE(rc)) + return rc; +#endif + + rc = pCallbacks->pfnRegister(pCallbacks, &EmWebcam::DrvReg); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_USB_CARDREADER + rc = pCallbacks->pfnRegister(pCallbacks, &UsbCardReader::DrvReg); + if (RT_FAILURE(rc)) + return rc; +#endif + + rc = pCallbacks->pfnRegister(pCallbacks, &Console::DrvStatusReg); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_PCI_PASSTHROUGH + rc = pCallbacks->pfnRegister(pCallbacks, &PCIRawDev::DrvReg); + if (RT_FAILURE(rc)) + return rc; +#endif + + rc = pCallbacks->pfnRegister(pCallbacks, &NvramStore::DrvReg); + if (RT_FAILURE(rc)) + return rc; + + return VINF_SUCCESS; +} +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/VMMDevInterface.cpp b/src/VBox/Main/src-client/VMMDevInterface.cpp new file mode 100644 index 00000000..e8c0011d --- /dev/null +++ b/src/VBox/Main/src-client/VMMDevInterface.cpp @@ -0,0 +1,1265 @@ +/* $Id: VMMDevInterface.cpp $ */ +/** @file + * VirtualBox Driver Interface to VMM device. + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_VMMDEVINTERFACES +#include "LoggingNew.h" + +#include "VMMDev.h" +#include "ConsoleImpl.h" +#include "DisplayImpl.h" +#include "GuestImpl.h" +#include "MouseImpl.h" + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/VMMDev.h> +#include <VBox/shflsvc.h> +#include <iprt/asm.h> + +#ifdef VBOX_WITH_HGCM +# include "HGCM.h" +# include "HGCMObjects.h" +#endif + +// +// defines +// + +#ifdef RT_OS_OS2 +# define VBOXSHAREDFOLDERS_DLL "VBoxSFld" +#else +# define VBOXSHAREDFOLDERS_DLL "VBoxSharedFolders" +#endif + +// +// globals +// + + +/** + * VMMDev driver instance data. + */ +typedef struct DRVMAINVMMDEV +{ + /** Pointer to the VMMDev object. */ + VMMDev *pVMMDev; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the VMMDev port interface of the driver/device above us. */ + PPDMIVMMDEVPORT pUpPort; + /** Our VMM device connector interface. */ + PDMIVMMDEVCONNECTOR Connector; + +#ifdef VBOX_WITH_HGCM + /** Pointer to the HGCM port interface of the driver/device above us. */ + PPDMIHGCMPORT pHGCMPort; + /** Our HGCM connector interface. */ + PDMIHGCMCONNECTOR HGCMConnector; +#endif + +#ifdef VBOX_WITH_GUEST_PROPS + HGCMSVCEXTHANDLE hHgcmSvcExtGstProps; +#endif +#ifdef VBOX_WITH_GUEST_CONTROL + HGCMSVCEXTHANDLE hHgcmSvcExtGstCtrl; +#endif +} DRVMAINVMMDEV, *PDRVMAINVMMDEV; + +// +// constructor / destructor +// +VMMDev::VMMDev(Console *console) + : mpDrv(NULL) + , mParent(console) +{ + int vrc = RTSemEventCreate(&mCredentialsEvent); + AssertRC(vrc); +#ifdef VBOX_WITH_HGCM + vrc = HGCMHostInit(); + AssertRC(vrc); + m_fHGCMActive = true; +#endif /* VBOX_WITH_HGCM */ + mu32CredentialsFlags = 0; +} + +VMMDev::~VMMDev() +{ +#ifdef VBOX_WITH_HGCM + if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true)) + HGCMHostShutdown(true /*fUvmIsInvalid*/); +#endif + RTSemEventDestroy(mCredentialsEvent); + if (mpDrv) + mpDrv->pVMMDev = NULL; + mpDrv = NULL; +} + +PPDMIVMMDEVPORT VMMDev::getVMMDevPort() +{ + if (!mpDrv) + return NULL; + return mpDrv->pUpPort; +} + + + +// +// public methods +// + +/** + * Wait on event semaphore for guest credential judgement result. + */ +int VMMDev::WaitCredentialsJudgement(uint32_t u32Timeout, uint32_t *pu32CredentialsFlags) +{ + if (u32Timeout == 0) + { + u32Timeout = 5000; + } + + int vrc = RTSemEventWait(mCredentialsEvent, u32Timeout); + + if (RT_SUCCESS(vrc)) + { + *pu32CredentialsFlags = mu32CredentialsFlags; + } + + return vrc; +} + +int VMMDev::SetCredentialsJudgementResult(uint32_t u32Flags) +{ + mu32CredentialsFlags = u32Flags; + + int vrc = RTSemEventSignal(mCredentialsEvent); + AssertRC(vrc); + + return vrc; +} + + +/** + * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestStatus} + */ +DECLCALLBACK(void) vmmdevUpdateGuestStatus(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFacility, uint16_t uStatus, + uint32_t fFlags, PCRTTIMESPEC pTimeSpecTS) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* Store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturnVoid(guest); + + guest->i_setAdditionsStatus((VBoxGuestFacilityType)uFacility, (VBoxGuestFacilityStatus)uStatus, fFlags, pTimeSpecTS); + pConsole->i_onAdditionsStateChange(); +} + + +/** + * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestUserState} + */ +DECLCALLBACK(void) vmmdevUpdateGuestUserState(PPDMIVMMDEVCONNECTOR pInterface, + const char *pszUser, const char *pszDomain, + uint32_t uState, + const uint8_t *pabDetails, uint32_t cbDetails) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + AssertPtr(pDrv); + Console *pConsole = pDrv->pVMMDev->getParent(); + AssertPtr(pConsole); + + /* Store that information in IGuest. */ + Guest* pGuest = pConsole->i_getGuest(); + AssertPtrReturnVoid(pGuest); + + pGuest->i_onUserStateChanged(Utf8Str(pszUser), Utf8Str(pszDomain), (VBoxGuestUserState)uState, pabDetails, cbDetails); +} + + +/** + * Reports Guest Additions API and OS version. + * + * Called whenever the Additions issue a guest version report request or the VM + * is reset. + * + * @param pInterface Pointer to this interface. + * @param guestInfo Pointer to guest information structure. + * @thread The emulation thread. + */ +DECLCALLBACK(void) vmmdevUpdateGuestInfo(PPDMIVMMDEVCONNECTOR pInterface, const VBoxGuestInfo *guestInfo) +{ + AssertPtrReturnVoid(guestInfo); + + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* Store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturnVoid(guest); + + if (guestInfo->interfaceVersion != 0) + { + char version[16]; + RTStrPrintf(version, sizeof(version), "%d", guestInfo->interfaceVersion); + guest->i_setAdditionsInfo(Bstr(version), guestInfo->osType); + + /* + * Tell the console interface about the event + * so that it can notify its consumers. + */ + pConsole->i_onAdditionsStateChange(); + + if (guestInfo->interfaceVersion < VMMDEV_VERSION) + pConsole->i_onAdditionsOutdated(); + } + else + { + /* + * The Guest Additions was disabled because of a reset + * or driver unload. + */ + guest->i_setAdditionsInfo(Bstr(), guestInfo->osType); /* Clear interface version + OS type. */ + /** @todo Would be better if GuestImpl.cpp did all this in the above method call + * while holding down the. */ + guest->i_setAdditionsInfo2(0, "", 0, 0); /* Clear Guest Additions version. */ + RTTIMESPEC TimeSpecTS; + RTTimeNow(&TimeSpecTS); + guest->i_setAdditionsStatus(VBoxGuestFacilityType_All, VBoxGuestFacilityStatus_Inactive, 0 /*fFlags*/, &TimeSpecTS); + pConsole->i_onAdditionsStateChange(); + } +} + +/** + * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestInfo2} + */ +DECLCALLBACK(void) vmmdevUpdateGuestInfo2(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFullVersion, + const char *pszName, uint32_t uRevision, uint32_t fFeatures) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + AssertPtr(pszName); + Assert(uFullVersion); + + /* Store that information in IGuest. */ + Guest *pGuest = pDrv->pVMMDev->getParent()->i_getGuest(); + AssertPtrReturnVoid(pGuest); + + /* Just pass it on... */ + pGuest->i_setAdditionsInfo2(uFullVersion, pszName, uRevision, fFeatures); + + /* + * No need to tell the console interface about the update; + * vmmdevUpdateGuestInfo takes care of that when called as the + * last event in the chain. + */ +} + +/** + * Update the Guest Additions capabilities. + * This is called when the Guest Additions capabilities change. The new capabilities + * are given and the connector should update its internal state. + * + * @param pInterface Pointer to this interface. + * @param newCapabilities New capabilities. + * @thread The emulation thread. + */ +DECLCALLBACK(void) vmmdevUpdateGuestCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t newCapabilities) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + AssertPtr(pDrv); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* store that information in IGuest */ + Guest* pGuest = pConsole->i_getGuest(); + AssertPtrReturnVoid(pGuest); + + /* + * Report our current capabilities (and assume none is active yet). + */ + pGuest->i_setSupportedFeatures(newCapabilities); + + /* + * Tell the Display, so that it can update the "supports graphics" + * capability if the graphics card has not asserted it. + */ + Display* pDisplay = pConsole->i_getDisplay(); + AssertPtrReturnVoid(pDisplay); + pDisplay->i_handleUpdateVMMDevSupportsGraphics(RT_BOOL(newCapabilities & VMMDEV_GUEST_SUPPORTS_GRAPHICS)); + + /* + * Tell the console interface about the event + * so that it can notify its consumers. + */ + pConsole->i_onAdditionsStateChange(); +} + +/** + * Update the mouse capabilities. + * This is called when the mouse capabilities change. The new capabilities + * are given and the connector should update its internal state. + * + * @param pInterface Pointer to this interface. + * @param fNewCaps New capabilities. + * @thread The emulation thread. + */ +DECLCALLBACK(void) vmmdevUpdateMouseCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t fNewCaps) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* + * Tell the console interface about the event + * so that it can notify its consumers. + */ + Mouse *pMouse = pConsole->i_getMouse(); + if (pMouse) /** @todo and if not? Can that actually happen? */ + pMouse->i_onVMMDevGuestCapsChange(fNewCaps & VMMDEV_MOUSE_GUEST_MASK); +} + +/** + * Update the pointer shape or visibility. + * + * This is called when the mouse pointer shape changes or pointer is hidden/displaying. + * The new shape is passed as a caller allocated buffer that will be freed after returning. + * + * @param pInterface Pointer to this interface. + * @param fVisible Whether the pointer is visible or not. + * @param fAlpha Alpha channel information is present. + * @param xHot Horizontal coordinate of the pointer hot spot. + * @param yHot Vertical coordinate of the pointer hot spot. + * @param width Pointer width in pixels. + * @param height Pointer height in pixels. + * @param pShape The shape buffer. If NULL, then only pointer visibility is being changed. + * @thread The emulation thread. + */ +DECLCALLBACK(void) vmmdevUpdatePointerShape(PPDMIVMMDEVCONNECTOR pInterface, bool fVisible, bool fAlpha, + uint32_t xHot, uint32_t yHot, + uint32_t width, uint32_t height, + void *pShape) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* tell the console about it */ + uint32_t cbShape = 0; + if (pShape) + { + cbShape = (width + 7) / 8 * height; /* size of the AND mask */ + cbShape = ((cbShape + 3) & ~3) + width * 4 * height; /* + gap + size of the XOR mask */ + } + pConsole->i_onMousePointerShapeChange(fVisible, fAlpha, xHot, yHot, width, height, (uint8_t *)pShape, cbShape); +} + +DECLCALLBACK(int) iface_VideoAccelEnable(PPDMIVMMDEVCONNECTOR pInterface, bool fEnable, VBVAMEMORY *pVbvaMemory) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + Display *display = pConsole->i_getDisplay(); + + if (display) + { + Log9(("MAIN::VMMDevInterface::iface_VideoAccelEnable: %d, %p\n", fEnable, pVbvaMemory)); + return display->VideoAccelEnableVMMDev(fEnable, pVbvaMemory); + } + + return VERR_NOT_SUPPORTED; +} +DECLCALLBACK(void) iface_VideoAccelFlush(PPDMIVMMDEVCONNECTOR pInterface) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + Display *display = pConsole->i_getDisplay(); + + if (display) + { + Log9(("MAIN::VMMDevInterface::iface_VideoAccelFlush\n")); + display->VideoAccelFlushVMMDev(); + } +} + +DECLCALLBACK(int) vmmdevVideoModeSupported(PPDMIVMMDEVCONNECTOR pInterface, uint32_t display, uint32_t width, uint32_t height, + uint32_t bpp, bool *fSupported) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + if (!fSupported) + return VERR_INVALID_PARAMETER; +#ifdef DEBUG_sunlover + Log(("vmmdevVideoModeSupported: [%d]: %dx%dx%d\n", display, width, height, bpp)); +#endif + IFramebuffer *framebuffer = NULL; + HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(display, &framebuffer); + if (SUCCEEDED(hrc) && framebuffer) + { + framebuffer->VideoModeSupported(width, height, bpp, (BOOL*)fSupported); + framebuffer->Release(); + } + else + { +#ifdef DEBUG_sunlover + Log(("vmmdevVideoModeSupported: hrc %x, framebuffer %p!!!\n", hrc, framebuffer)); +#endif + *fSupported = true; + } + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vmmdevGetHeightReduction(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *heightReduction) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + if (!heightReduction) + return VERR_INVALID_PARAMETER; + IFramebuffer *framebuffer = NULL; + HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(0, &framebuffer); + if (SUCCEEDED(hrc) && framebuffer) + { + framebuffer->COMGETTER(HeightReduction)((ULONG*)heightReduction); + framebuffer->Release(); + } + else + *heightReduction = 0; + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vmmdevSetCredentialsJudgementResult(PPDMIVMMDEVCONNECTOR pInterface, uint32_t u32Flags) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + + if (pDrv->pVMMDev) + return pDrv->pVMMDev->SetCredentialsJudgementResult(u32Flags); + + return VERR_GENERAL_FAILURE; +} + +DECLCALLBACK(int) vmmdevSetVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cRect, PRTRECT pRect) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* Forward to Display, which calls corresponding framebuffers. */ + pConsole->i_getDisplay()->i_handleSetVisibleRegion(cRect, pRect); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateMonitorPositions} + */ +static DECLCALLBACK(int) vmmdevUpdateMonitorPositions(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cPositions, PCRTPOINT paPositions) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + pConsole->i_getDisplay()->i_handleUpdateMonitorPositions(cPositions, paPositions); + + return VINF_SUCCESS; +} + +DECLCALLBACK(int) vmmdevQueryVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcRects, PRTRECT paRects) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + /* Forward to Display, which calls corresponding framebuffers. */ + pConsole->i_getDisplay()->i_handleQueryVisibleRegion(pcRects, paRects); + + return VINF_SUCCESS; +} + +/** + * Request the statistics interval + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pulInterval Pointer to interval in seconds + * @thread The emulation thread. + */ +DECLCALLBACK(int) vmmdevQueryStatisticsInterval(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pulInterval) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + ULONG val = 0; + + if (!pulInterval) + return VERR_INVALID_POINTER; + + /* store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturn(guest, VERR_GENERAL_FAILURE); + + guest->COMGETTER(StatisticsUpdateInterval)(&val); + *pulInterval = val; + return VINF_SUCCESS; +} + +/** + * Query the current balloon size + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pcbBalloon Balloon size + * @thread The emulation thread. + */ +DECLCALLBACK(int) vmmdevQueryBalloonSize(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcbBalloon) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + ULONG val = 0; + + if (!pcbBalloon) + return VERR_INVALID_POINTER; + + /* store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturn(guest, VERR_GENERAL_FAILURE); + + guest->COMGETTER(MemoryBalloonSize)(&val); + *pcbBalloon = val; + return VINF_SUCCESS; +} + +/** + * Query the current page fusion setting + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pfPageFusionEnabled Pointer to boolean + * @thread The emulation thread. + */ +DECLCALLBACK(int) vmmdevIsPageFusionEnabled(PPDMIVMMDEVCONNECTOR pInterface, bool *pfPageFusionEnabled) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + if (!pfPageFusionEnabled) + return VERR_INVALID_POINTER; + + /* store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturn(guest, VERR_GENERAL_FAILURE); + + *pfPageFusionEnabled = !!guest->i_isPageFusionEnabled(); + return VINF_SUCCESS; +} + +/** + * Report new guest statistics + * + * @returns VBox status code. + * @param pInterface Pointer to this interface. + * @param pGuestStats Guest statistics + * @thread The emulation thread. + */ +DECLCALLBACK(int) vmmdevReportStatistics(PPDMIVMMDEVCONNECTOR pInterface, VBoxGuestStatistics *pGuestStats) +{ + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector); + Console *pConsole = pDrv->pVMMDev->getParent(); + + AssertPtrReturn(pGuestStats, VERR_INVALID_POINTER); + + /* store that information in IGuest */ + Guest* guest = pConsole->i_getGuest(); + AssertPtrReturn(guest, VERR_GENERAL_FAILURE); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_IDLE) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUIDLE, pGuestStats->u32CpuLoad_Idle); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_KERNEL) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUKERNEL, pGuestStats->u32CpuLoad_Kernel); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_USER) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUUSER, pGuestStats->u32CpuLoad_User); + + + /** @todo r=bird: Convert from 4KB to 1KB units? + * CollectorGuestHAL::i_getGuestMemLoad says it returns KB units to + * preCollect(). I might be wrong ofc, this is convoluted code... */ + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_TOTAL) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMTOTAL, pGuestStats->u32PhysMemTotal); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_AVAIL) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMFREE, pGuestStats->u32PhysMemAvail); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_BALLOON) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMBALLOON, pGuestStats->u32PhysMemBalloon); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_MEM_SYSTEM_CACHE) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMCACHE, pGuestStats->u32MemSystemCache); + + if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PAGE_FILE_SIZE) + guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_PAGETOTAL, pGuestStats->u32PageFileSize); + + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_HGCM + +/* HGCM connector interface */ + +static DECLCALLBACK(int) iface_hgcmConnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, + PHGCMSERVICELOCATION pServiceLocation, + uint32_t *pu32ClientID) +{ + Log9(("Enter\n")); + + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector); + + if ( !pServiceLocation + || ( pServiceLocation->type != VMMDevHGCMLoc_LocalHost + && pServiceLocation->type != VMMDevHGCMLoc_LocalHost_Existing)) + { + return VERR_INVALID_PARAMETER; + } + + /* Check if service name is a string terminated by zero*/ + size_t cchInfo = 0; + if (RTStrNLenEx(pServiceLocation->u.host.achName, sizeof(pServiceLocation->u.host.achName), &cchInfo) != VINF_SUCCESS) + { + return VERR_INVALID_PARAMETER; + } + + if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive()) + return VERR_INVALID_STATE; + return HGCMGuestConnect(pDrv->pHGCMPort, pCmd, pServiceLocation->u.host.achName, pu32ClientID); +} + +static DECLCALLBACK(int) iface_hgcmDisconnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID) +{ + Log9(("Enter\n")); + + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector); + + if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive()) + return VERR_INVALID_STATE; + + return HGCMGuestDisconnect(pDrv->pHGCMPort, pCmd, u32ClientID); +} + +static DECLCALLBACK(int) iface_hgcmCall(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, + uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms, uint64_t tsArrival) +{ + Log9(("Enter\n")); + + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector); + + if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive()) + return VERR_INVALID_STATE; + + return HGCMGuestCall(pDrv->pHGCMPort, pCmd, u32ClientID, u32Function, cParms, paParms, tsArrival); +} + +static DECLCALLBACK(void) iface_hgcmCancelled(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t idClient) +{ + Log9(("Enter\n")); + + PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector); + if ( pDrv->pVMMDev + && pDrv->pVMMDev->hgcmIsActive()) + return HGCMGuestCancelled(pDrv->pHGCMPort, pCmd, idClient); +} + +/** + * Execute state save operation. + * + * @returns VBox status code. + * @param pDrvIns Driver instance of the driver which registered the data unit. + * @param pSSM SSM operation handle. + */ +/*static*/ DECLCALLBACK(int) VMMDev::hgcmSave(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + Log9(("Enter\n")); + + AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2); + Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent); + AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3); + return HGCMHostSaveState(pSSM, ptrVM.vtable()); +} + + +/** + * Execute state load operation. + * + * @returns VBox status code. + * @param pDrvIns Driver instance of the driver which registered the data unit. + * @param pSSM SSM operation handle. + * @param uVersion Data layout version. + * @param uPass The data pass. + */ +/*static*/ DECLCALLBACK(int) VMMDev::hgcmLoad(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + LogFlowFunc(("Enter\n")); + + if ( uVersion != HGCM_SAVED_STATE_VERSION + && uVersion != HGCM_SAVED_STATE_VERSION_V2) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2); + Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent); + AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3); + return HGCMHostLoadState(pSSM, ptrVM.vtable(), uVersion); +} + +int VMMDev::hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName) +{ + if (!hgcmIsActive()) + return VERR_INVALID_STATE; + + /** @todo Construct all the services in the VMMDev::drvConstruct()!! */ + Assert( (mpDrv && mpDrv->pHGCMPort) + || !strcmp(pszServiceLibrary, "VBoxHostChannel") + || !strcmp(pszServiceLibrary, "VBoxSharedClipboard") + || !strcmp(pszServiceLibrary, "VBoxDragAndDropSvc") + || !strcmp(pszServiceLibrary, "VBoxGuestPropSvc") + || !strcmp(pszServiceLibrary, "VBoxSharedCrOpenGL") + ); + Console::SafeVMPtrQuiet ptrVM(mParent); + return HGCMHostLoad(pszServiceLibrary, pszServiceName, ptrVM.rawUVM(), ptrVM.vtable(), mpDrv ? mpDrv->pHGCMPort : NULL); +} + +int VMMDev::hgcmHostCall(const char *pszServiceName, uint32_t u32Function, + uint32_t cParms, PVBOXHGCMSVCPARM paParms) +{ + if (!hgcmIsActive()) + return VERR_INVALID_STATE; + return HGCMHostCall(pszServiceName, u32Function, cParms, paParms); +} + +/** + * Used by Console::i_powerDown to shut down the services before the VM is destroyed. + */ +void VMMDev::hgcmShutdown(bool fUvmIsInvalid /*= false*/) +{ +#ifdef VBOX_WITH_GUEST_PROPS + if (mpDrv && mpDrv->hHgcmSvcExtGstProps) + { + HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstProps); + mpDrv->hHgcmSvcExtGstProps = NULL; + } +#endif + +#ifdef VBOX_WITH_GUEST_CONTROL + if (mpDrv && mpDrv->hHgcmSvcExtGstCtrl) + { + HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstCtrl); + mpDrv->hHgcmSvcExtGstCtrl = NULL; + } +#endif + + if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true)) + HGCMHostShutdown(fUvmIsInvalid); +} + +#endif /* HGCM */ + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) VMMDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVMAINVMMDEV pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIVMMDEVCONNECTOR, &pDrv->Connector); +#ifdef VBOX_WITH_HGCM + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHGCMCONNECTOR, &pDrv->HGCMConnector); +#endif + return NULL; +} + +/** + * @interface_method_impl{PDMDRVREG,pfnSuspend} + */ +/*static*/ DECLCALLBACK(void) VMMDev::drvSuspend(PPDMDRVINS pDrvIns) +{ + RT_NOREF(pDrvIns); +#ifdef VBOX_WITH_HGCM + HGCMBroadcastEvent(HGCMNOTIFYEVENT_SUSPEND); +#endif +} + +/** + * @interface_method_impl{PDMDRVREG,pfnResume} + */ +/*static*/ DECLCALLBACK(void) VMMDev::drvResume(PPDMDRVINS pDrvIns) +{ + RT_NOREF(pDrvIns); +#ifdef VBOX_WITH_HGCM + HGCMBroadcastEvent(HGCMNOTIFYEVENT_RESUME); +#endif +} + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOff(PPDMDRVINS pDrvIns) +{ + RT_NOREF(pDrvIns); +#ifdef VBOX_WITH_HGCM + HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON); +#endif +} + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOn} + */ +/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOn(PPDMDRVINS pDrvIns) +{ + RT_NOREF(pDrvIns); +#ifdef VBOX_WITH_HGCM + HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON); +#endif +} + +/** + * @interface_method_impl{PDMDRVREG,pfnReset} + */ +DECLCALLBACK(void) VMMDev::drvReset(PPDMDRVINS pDrvIns) +{ + RT_NOREF(pDrvIns); + LogFlow(("VMMDev::drvReset: iInstance=%d\n", pDrvIns->iInstance)); +#ifdef VBOX_WITH_HGCM + HGCMHostReset(false /*fForShutdown*/); +#endif +} + +/** + * @interface_method_impl{PDMDRVREG,pfnDestruct} + */ +DECLCALLBACK(void) VMMDev::drvDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + LogFlow(("VMMDev::drvDestruct: iInstance=%d\n", pDrvIns->iInstance)); + +#ifdef VBOX_WITH_GUEST_PROPS + if (pThis->hHgcmSvcExtGstProps) + { + HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstProps); + pThis->hHgcmSvcExtGstProps = NULL; + } +#endif + +#ifdef VBOX_WITH_GUEST_CONTROL + if (pThis->hHgcmSvcExtGstCtrl) + { + HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstCtrl); + pThis->hHgcmSvcExtGstCtrl = NULL; + } +#endif + + if (pThis->pVMMDev) + { +#ifdef VBOX_WITH_HGCM + /* When VM construction goes wrong, we prefer shutting down HGCM here + while pUVM is still valid, rather than in ~VMMDev. */ + if (ASMAtomicCmpXchgBool(&pThis->pVMMDev->m_fHGCMActive, false, true)) + HGCMHostShutdown(); +#endif + pThis->pVMMDev->mpDrv = NULL; + } +} + +#ifdef VBOX_WITH_GUEST_PROPS + +/** + * Set an array of guest properties + */ +void VMMDev::i_guestPropSetMultiple(void *names, void *values, void *timestamps, void *flags) +{ + VBOXHGCMSVCPARM parms[4]; + + parms[0].type = VBOX_HGCM_SVC_PARM_PTR; + parms[0].u.pointer.addr = names; + parms[0].u.pointer.size = 0; /* We don't actually care. */ + parms[1].type = VBOX_HGCM_SVC_PARM_PTR; + parms[1].u.pointer.addr = values; + parms[1].u.pointer.size = 0; /* We don't actually care. */ + parms[2].type = VBOX_HGCM_SVC_PARM_PTR; + parms[2].u.pointer.addr = timestamps; + parms[2].u.pointer.size = 0; /* We don't actually care. */ + parms[3].type = VBOX_HGCM_SVC_PARM_PTR; + parms[3].u.pointer.addr = flags; + parms[3].u.pointer.size = 0; /* We don't actually care. */ + + hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROPS, 4, &parms[0]); +} + +/** + * Set a single guest property + */ +void VMMDev::i_guestPropSet(const char *pszName, const char *pszValue, const char *pszFlags) +{ + VBOXHGCMSVCPARM parms[4]; + + AssertPtrReturnVoid(pszName); + AssertPtrReturnVoid(pszValue); + AssertPtrReturnVoid(pszFlags); + parms[0].type = VBOX_HGCM_SVC_PARM_PTR; + parms[0].u.pointer.addr = (void *)pszName; + parms[0].u.pointer.size = (uint32_t)strlen(pszName) + 1; + parms[1].type = VBOX_HGCM_SVC_PARM_PTR; + parms[1].u.pointer.addr = (void *)pszValue; + parms[1].u.pointer.size = (uint32_t)strlen(pszValue) + 1; + parms[2].type = VBOX_HGCM_SVC_PARM_PTR; + parms[2].u.pointer.addr = (void *)pszFlags; + parms[2].u.pointer.size = (uint32_t)strlen(pszFlags) + 1; + hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP, 3, &parms[0]); +} + +/** + * Set the global flags value by calling the service + * @returns the status returned by the call to the service + * + * @param pTable the service instance handle + * @param eFlags the flags to set + */ +int VMMDev::i_guestPropSetGlobalPropertyFlags(uint32_t fFlags) +{ + VBOXHGCMSVCPARM parm; + HGCMSvcSetU32(&parm, fFlags); + int vrc = hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS, 1, &parm); + if (RT_FAILURE(vrc)) + { + char szFlags[GUEST_PROP_MAX_FLAGS_LEN]; + if (RT_FAILURE(GuestPropWriteFlags(fFlags, szFlags))) + Log(("Failed to set the global flags.\n")); + else + Log(("Failed to set the global flags \"%s\".\n", szFlags)); + } + return vrc; +} + + +/** + * Set up the Guest Property service, populate it with properties read from + * the machine XML and set a couple of initial properties. + */ +int VMMDev::i_guestPropLoadAndConfigure() +{ + Assert(mpDrv); + ComObjPtr<Console> ptrConsole = this->mParent; + AssertReturn(ptrConsole.isNotNull(), VERR_INVALID_POINTER); + + /* + * Load the service + */ + int vrc = hgcmLoadService("VBoxGuestPropSvc", "VBoxGuestPropSvc"); + if (RT_FAILURE(vrc)) + { + LogRel(("VBoxGuestPropSvc is not available. vrc = %Rrc\n", vrc)); + return VINF_SUCCESS; /* That is not a fatal failure. */ + } + + /* + * Pull over the properties from the server. + */ + SafeArray<BSTR> namesOut; + SafeArray<BSTR> valuesOut; + SafeArray<LONG64> timestampsOut; + SafeArray<BSTR> flagsOut; + HRESULT hrc = ptrConsole->i_pullGuestProperties(ComSafeArrayAsOutParam(namesOut), + ComSafeArrayAsOutParam(valuesOut), + ComSafeArrayAsOutParam(timestampsOut), + ComSafeArrayAsOutParam(flagsOut)); + AssertLogRelMsgReturn(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR); + size_t const cProps = namesOut.size(); + size_t const cAlloc = cProps + 1; + AssertLogRelReturn(valuesOut.size() == cProps, VERR_INTERNAL_ERROR_2); + AssertLogRelReturn(timestampsOut.size() == cProps, VERR_INTERNAL_ERROR_3); + AssertLogRelReturn(flagsOut.size() == cProps, VERR_INTERNAL_ERROR_4); + + char szEmpty[] = ""; + char **papszNames = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc); + char **papszValues = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc); + LONG64 *pai64Timestamps = (LONG64 *)RTMemTmpAllocZ(sizeof(LONG64) * cAlloc); + char **papszFlags = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc); + if (papszNames && papszValues && pai64Timestamps && papszFlags) + { + for (unsigned i = 0; RT_SUCCESS(vrc) && i < cProps; ++i) + { + AssertPtrBreakStmt(namesOut[i], vrc = VERR_INVALID_PARAMETER); + vrc = RTUtf16ToUtf8(namesOut[i], &papszNames[i]); + if (RT_FAILURE(vrc)) + break; + if (valuesOut[i]) + vrc = RTUtf16ToUtf8(valuesOut[i], &papszValues[i]); + else + papszValues[i] = szEmpty; + if (RT_FAILURE(vrc)) + break; + pai64Timestamps[i] = timestampsOut[i]; + if (flagsOut[i]) + vrc = RTUtf16ToUtf8(flagsOut[i], &papszFlags[i]); + else + papszFlags[i] = szEmpty; + } + if (RT_SUCCESS(vrc)) + i_guestPropSetMultiple((void *)papszNames, (void *)papszValues, (void *)pai64Timestamps, (void *)papszFlags); + for (unsigned i = 0; i < cProps; ++i) + { + RTStrFree(papszNames[i]); + if (valuesOut[i]) + RTStrFree(papszValues[i]); + if (flagsOut[i]) + RTStrFree(papszFlags[i]); + } + } + else + vrc = VERR_NO_MEMORY; + RTMemTmpFree(papszNames); + RTMemTmpFree(papszValues); + RTMemTmpFree(pai64Timestamps); + RTMemTmpFree(papszFlags); + AssertRCReturn(vrc, vrc); + + /* + * Register the host notification callback + */ + HGCMHostRegisterServiceExtension(&mpDrv->hHgcmSvcExtGstProps, "VBoxGuestPropSvc", Console::i_doGuestPropNotification, ptrConsole.m_p); + +# ifdef VBOX_WITH_GUEST_PROPS_RDONLY_GUEST + vrc = i_guestPropSetGlobalPropertyFlags(GUEST_PROP_F_RDONLYGUEST); + AssertRCReturn(vrc, vrc); +# endif + + Log(("Set VBoxGuestPropSvc property store\n")); + return VINF_SUCCESS; +} + +#endif /* VBOX_WITH_GUEST_PROPS */ + +/** + * @interface_method_impl{PDMDRVREG,pfnConstruct} + */ +DECLCALLBACK(int) VMMDev::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + RT_NOREF(fFlags, pCfg); + PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", ""); + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * IBase. + */ + pDrvIns->IBase.pfnQueryInterface = VMMDev::drvQueryInterface; + + pThis->Connector.pfnUpdateGuestStatus = vmmdevUpdateGuestStatus; + pThis->Connector.pfnUpdateGuestUserState = vmmdevUpdateGuestUserState; + pThis->Connector.pfnUpdateGuestInfo = vmmdevUpdateGuestInfo; + pThis->Connector.pfnUpdateGuestInfo2 = vmmdevUpdateGuestInfo2; + pThis->Connector.pfnUpdateGuestCapabilities = vmmdevUpdateGuestCapabilities; + pThis->Connector.pfnUpdateMouseCapabilities = vmmdevUpdateMouseCapabilities; + pThis->Connector.pfnUpdatePointerShape = vmmdevUpdatePointerShape; + pThis->Connector.pfnVideoAccelEnable = iface_VideoAccelEnable; + pThis->Connector.pfnVideoAccelFlush = iface_VideoAccelFlush; + pThis->Connector.pfnVideoModeSupported = vmmdevVideoModeSupported; + pThis->Connector.pfnGetHeightReduction = vmmdevGetHeightReduction; + pThis->Connector.pfnSetCredentialsJudgementResult = vmmdevSetCredentialsJudgementResult; + pThis->Connector.pfnSetVisibleRegion = vmmdevSetVisibleRegion; + pThis->Connector.pfnUpdateMonitorPositions = vmmdevUpdateMonitorPositions; + pThis->Connector.pfnQueryVisibleRegion = vmmdevQueryVisibleRegion; + pThis->Connector.pfnReportStatistics = vmmdevReportStatistics; + pThis->Connector.pfnQueryStatisticsInterval = vmmdevQueryStatisticsInterval; + pThis->Connector.pfnQueryBalloonSize = vmmdevQueryBalloonSize; + pThis->Connector.pfnIsPageFusionEnabled = vmmdevIsPageFusionEnabled; + +#ifdef VBOX_WITH_HGCM + pThis->HGCMConnector.pfnConnect = iface_hgcmConnect; + pThis->HGCMConnector.pfnDisconnect = iface_hgcmDisconnect; + pThis->HGCMConnector.pfnCall = iface_hgcmCall; + pThis->HGCMConnector.pfnCancelled = iface_hgcmCancelled; +#endif + + /* + * Get the IVMMDevPort interface of the above driver/device. + */ + pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIVMMDEVPORT); + AssertMsgReturn(pThis->pUpPort, ("Configuration error: No VMMDev port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + +#ifdef VBOX_WITH_HGCM + pThis->pHGCMPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHGCMPORT); + AssertMsgReturn(pThis->pHGCMPort, ("Configuration error: No HGCM port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); +#endif + + /* + * Get the Console object pointer and update the mpDrv member. + */ + com::Guid uuid(VMMDEV_OID); + pThis->pVMMDev = (VMMDev *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw()); + if (!pThis->pVMMDev) + { + AssertMsgFailed(("Configuration error: No/bad VMMDev object!\n")); + return VERR_NOT_FOUND; + } + pThis->pVMMDev->mpDrv = pThis; + + int vrc = VINF_SUCCESS; +#ifdef VBOX_WITH_HGCM + /* + * Load & configure the shared folders service. + */ + vrc = pThis->pVMMDev->hgcmLoadService(VBOXSHAREDFOLDERS_DLL, "VBoxSharedFolders"); + pThis->pVMMDev->fSharedFolderActive = RT_SUCCESS(vrc); + if (RT_SUCCESS(vrc)) + { + PPDMLED pLed; + PPDMILEDPORTS pLedPort; + + LogRel(("Shared Folders service loaded\n")); + pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS); + AssertMsgReturn(pLedPort, ("Configuration error: No LED port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + vrc = pLedPort->pfnQueryStatusLed(pLedPort, 0, &pLed); + if (RT_SUCCESS(vrc) && pLed) + { + VBOXHGCMSVCPARM parm; + + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = pLed; + parm.u.pointer.size = sizeof(*pLed); + + vrc = HGCMHostCall("VBoxSharedFolders", SHFL_FN_SET_STATUS_LED, 1, &parm); + } + else + AssertMsgFailed(("pfnQueryStatusLed failed with %Rrc (pLed=%x)\n", vrc, pLed)); + } + else + LogRel(("Failed to load Shared Folders service %Rrc\n", vrc)); + + + /* + * Load and configure the guest control service. + */ +# ifdef VBOX_WITH_GUEST_CONTROL + vrc = pThis->pVMMDev->hgcmLoadService("VBoxGuestControlSvc", "VBoxGuestControlSvc"); + if (RT_SUCCESS(vrc)) + { + vrc = HGCMHostRegisterServiceExtension(&pThis->hHgcmSvcExtGstCtrl, "VBoxGuestControlSvc", + &Guest::i_notifyCtrlDispatcher, + pThis->pVMMDev->mParent->i_getGuest()); + if (RT_SUCCESS(vrc)) + LogRel(("Guest Control service loaded\n")); + else + LogRel(("Warning: Cannot register VBoxGuestControlSvc extension! vrc=%Rrc\n", vrc)); + } + else + LogRel(("Warning!: Failed to load the Guest Control Service! %Rrc\n", vrc)); +# endif /* VBOX_WITH_GUEST_CONTROL */ + + + /* + * Load and configure the guest properties service. + */ +# ifdef VBOX_WITH_GUEST_PROPS + vrc = pThis->pVMMDev->i_guestPropLoadAndConfigure(); + AssertLogRelRCReturn(vrc, vrc); +# endif + + + /* + * The HGCM saved state. + */ + vrc = PDMDrvHlpSSMRegisterEx(pDrvIns, HGCM_SAVED_STATE_VERSION, 4096 /* bad guess */, + NULL, NULL, NULL, + NULL, VMMDev::hgcmSave, NULL, + NULL, VMMDev::hgcmLoad, NULL); + if (RT_FAILURE(vrc)) + return vrc; + +#endif /* VBOX_WITH_HGCM */ + + return VINF_SUCCESS; +} + + +/** + * VMMDevice driver registration record. + */ +const PDMDRVREG VMMDev::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "HGCM", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Main VMMDev driver (Main as in the API).", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_VMMDEV, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVMAINVMMDEV), + /* pfnConstruct */ + VMMDev::drvConstruct, + /* pfnDestruct */ + VMMDev::drvDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + VMMDev::drvPowerOn, + /* pfnReset */ + VMMDev::drvReset, + /* pfnSuspend */ + VMMDev::drvSuspend, + /* pfnResume */ + VMMDev::drvResume, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + VMMDev::drvPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp new file mode 100644 index 00000000..bf72790f --- /dev/null +++ b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp @@ -0,0 +1,833 @@ +/* $Id: VirtualBoxClientImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2010-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXCLIENT +#include "LoggingNew.h" + +#include "VirtualBoxClientImpl.h" + +#include "AutoCaller.h" +#include "VBoxEvents.h" +#include "VBox/com/ErrorInfo.h" +#include "VBox/com/listeners.h" + +#include <iprt/asm.h> +#include <iprt/thread.h> +#include <iprt/critsect.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/cpp/utils.h> +#include <iprt/utf16.h> +#ifdef RT_OS_WINDOWS +# include <iprt/err.h> +# include <iprt/ldr.h> +# include <msi.h> +# include <WbemIdl.h> +#endif + +#include <new> + + +/** Waiting time between probing whether VBoxSVC is alive. */ +#define VBOXCLIENT_DEFAULT_INTERVAL 30000 + + +/** Initialize instance counter class variable */ +uint32_t VirtualBoxClient::g_cInstances = 0; + +LONG VirtualBoxClient::s_cUnnecessaryAtlModuleLocks = 0; + +#ifdef VBOX_WITH_MAIN_NLS + +/* listener class for language updates */ +class VBoxEventListener +{ +public: + VBoxEventListener() + {} + + + HRESULT init(void *) + { + return S_OK; + } + + HRESULT init() + { + return S_OK; + } + + void uninit() + { + } + + virtual ~VBoxEventListener() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) + { + switch(aType) + { + case VBoxEventType_OnLanguageChanged: + { + /* + * Proceed with uttmost care as we might be racing com::Shutdown() + * and have the ground open up beneath us. + */ + LogFunc(("VBoxEventType_OnLanguageChanged\n")); + VirtualBoxTranslator *pTranslator = VirtualBoxTranslator::tryInstance(); + if (pTranslator) + { + ComPtr<ILanguageChangedEvent> pEvent = aEvent; + Assert(pEvent); + + /* This call may fail if we're racing COM shutdown. */ + com::Bstr bstrLanguageId; + HRESULT hrc = pEvent->COMGETTER(LanguageId)(bstrLanguageId.asOutParam()); + if (SUCCEEDED(hrc)) + { + try + { + com::Utf8Str strLanguageId(bstrLanguageId); + LogFunc(("New language ID: %s\n", strLanguageId.c_str())); + pTranslator->i_loadLanguage(strLanguageId.c_str()); + } + catch (std::bad_alloc &) + { + LogFunc(("Caught bad_alloc")); + } + } + else + LogFunc(("Failed to get new language ID: %Rhrc\n", hrc)); + + pTranslator->release(); + } + break; + } + + default: + AssertFailed(); + } + + return S_OK; + } +}; + +typedef ListenerImpl<VBoxEventListener> VBoxEventListenerImpl; + +VBOX_LISTENER_DECLARE(VBoxEventListenerImpl) + +#endif /* VBOX_WITH_MAIN_NLS */ + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +/** @relates VirtualBoxClient::FinalConstruct() */ +HRESULT VirtualBoxClient::FinalConstruct() +{ + HRESULT hrc = init(); + BaseFinalConstruct(); + return hrc; +} + +void VirtualBoxClient::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the VirtualBoxClient object. + * + * @returns COM result indicator + */ +HRESULT VirtualBoxClient::init() +{ + LogFlowThisFuncEnter(); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + /* Important: DO NOT USE any kind of "early return" (except the single + * one above, checking the init span success) in this method. It is vital + * for correct error handling that it has only one point of return, which + * does all the magic on COM to signal object creation success and + * reporting the error later for every API method. COM translates any + * unsuccessful object creation to REGDB_E_CLASSNOTREG errors or similar + * unhelpful ones which cause us a lot of grief with troubleshooting. */ + + HRESULT hrc = S_OK; + try + { + if (ASMAtomicIncU32(&g_cInstances) != 1) + AssertFailedStmt(throw setError(E_FAIL, "Attempted to create more than one VirtualBoxClient instance")); + + mData.m_ThreadWatcher = NIL_RTTHREAD; + mData.m_SemEvWatcher = NIL_RTSEMEVENT; + + hrc = mData.m_pVirtualBox.createLocalObject(CLSID_VirtualBox); + if (FAILED(hrc)) +#ifdef RT_OS_WINDOWS + throw i_investigateVirtualBoxObjectCreationFailure(hrc); +#else + throw hrc; +#endif + + /* VirtualBox error return is postponed to method calls, fetch it. */ + ULONG rev; + hrc = mData.m_pVirtualBox->COMGETTER(Revision)(&rev); + if (FAILED(hrc)) + throw hrc; + + hrc = unconst(mData.m_pEventSource).createObject(); + AssertComRCThrow(hrc, setError(hrc, "Could not create EventSource for VirtualBoxClient")); + hrc = mData.m_pEventSource->init(); + AssertComRCThrow(hrc, setError(hrc, "Could not initialize EventSource for VirtualBoxClient")); + + /* HACK ALERT! This is for DllCanUnloadNow(). */ + s_cUnnecessaryAtlModuleLocks++; + AssertMsg(s_cUnnecessaryAtlModuleLocks == 1, ("%d\n", s_cUnnecessaryAtlModuleLocks)); + + int vrc; +#ifdef VBOX_WITH_MAIN_NLS + /* Create the translator singelton (must work) and try load translations (non-fatal). */ + mData.m_pVBoxTranslator = VirtualBoxTranslator::instance(); + if (mData.m_pVBoxTranslator == NULL) + throw setError(VBOX_E_IPRT_ERROR, "Failed to create translator instance"); + + char szNlsPath[RTPATH_MAX]; + vrc = RTPathAppPrivateNoArch(szNlsPath, sizeof(szNlsPath)); + if (RT_SUCCESS(vrc)) + vrc = RTPathAppend(szNlsPath, sizeof(szNlsPath), "nls" RTPATH_SLASH_STR "VirtualBoxAPI"); + + if (RT_SUCCESS(vrc)) + { + vrc = mData.m_pVBoxTranslator->registerTranslation(szNlsPath, true, &mData.m_pTrComponent); + if (RT_SUCCESS(vrc)) + { + hrc = i_reloadApiLanguage(); + if (SUCCEEDED(hrc)) + i_registerEventListener(); /* for updates */ + else + LogRelFunc(("i_reloadApiLanguage failed: %Rhrc\n", hrc)); + } + else + LogRelFunc(("Register translation failed: %Rrc\n", vrc)); + } + else + LogRelFunc(("Path constructing failed: %Rrc\n", vrc)); +#endif + /* Setting up the VBoxSVC watcher thread. If anything goes wrong here it + * is not considered important enough to cause any sort of visible + * failure. The monitoring will not be done, but that's all. */ + vrc = RTSemEventCreate(&mData.m_SemEvWatcher); + if (RT_FAILURE(vrc)) + { + mData.m_SemEvWatcher = NIL_RTSEMEVENT; + AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create semaphore (vrc=%Rrc)"), vrc)); + } + + vrc = RTThreadCreate(&mData.m_ThreadWatcher, SVCWatcherThread, this, 0, + RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "VBoxSVCWatcher"); + if (RT_FAILURE(vrc)) + { + RTSemEventDestroy(mData.m_SemEvWatcher); + mData.m_SemEvWatcher = NIL_RTSEMEVENT; + AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create watcher thread (vrc=%Rrc)"), vrc)); + } + } + catch (HRESULT err) + { + /* we assume that error info is set by the thrower */ + hrc = err; + } + catch (...) + { + hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS); + } + + /* Confirm a successful initialization when it's the case. Must be last, + * as on failure it will uninitialize the object. */ + if (SUCCEEDED(hrc)) + autoInitSpan.setSucceeded(); + else + autoInitSpan.setFailed(hrc); + + LogFlowThisFunc(("hrc=%Rhrc\n", hrc)); + LogFlowThisFuncLeave(); + /* Unconditionally return success, because the error return is delayed to + * the attribute/method calls through the InitFailed object state. */ + return S_OK; +} + +#ifdef RT_OS_WINDOWS + +/** + * Looks into why we failed to create the VirtualBox object. + * + * @returns hrcCaller thru setError. + * @param hrcCaller The failure status code. + */ +HRESULT VirtualBoxClient::i_investigateVirtualBoxObjectCreationFailure(HRESULT hrcCaller) +{ + HRESULT hrc; + +# ifdef VBOX_WITH_SDS + /* + * Check that the VBoxSDS service is configured to run as LocalSystem and is enabled. + */ + WCHAR wszBuffer[256]; + uint32_t uStartType; + int vrc = i_getServiceAccountAndStartType(L"VBoxSDS", wszBuffer, RT_ELEMENTS(wszBuffer), &uStartType); + if (RT_SUCCESS(vrc)) + { + LogRelFunc(("VBoxSDS service is running under the '%ls' account with start type %u.\n", wszBuffer, uStartType)); + if (RTUtf16Cmp(wszBuffer, L"LocalSystem") != 0) + return setError(hrcCaller, + tr("VBoxSDS is misconfigured to run under the '%ls' account instead of the SYSTEM one.\n" + "Reinstall VirtualBox to fix it. Alternatively you can fix it using the Windows Service Control " + "Manager or by running 'sc config VBoxSDS obj=LocalSystem' on a command line."), wszBuffer); + if (uStartType == SERVICE_DISABLED) + return setError(hrcCaller, + tr("The VBoxSDS windows service is disabled.\n" + "Reinstall VirtualBox to fix it. Alternatively try reenable the service by setting it to " + " 'Manual' startup type in the Windows Service management console, or by runing " + "'sc config VBoxSDS start=demand' on the command line.")); + } + else if (vrc == VERR_NOT_FOUND) + return setError(hrcCaller, + tr("The VBoxSDS windows service was not found.\n" + "Reinstall VirtualBox to fix it. Alternatively you can try start VirtualBox as Administrator, this " + "should automatically reinstall the service, or you can run " + "'VBoxSDS.exe --regservice' command from an elevated Administrator command line.")); + else + LogRelFunc(("VirtualBoxClient::i_getServiceAccount failed: %Rrc\n", vrc)); +# endif + + /* + * First step is to try get an IUnknown interface of the VirtualBox object. + * + * This will succeed even when oleaut32.msm (see @bugref{8016}, @ticketref{12087}) + * is accidentally installed and messes up COM. It may also succeed when the COM + * registration is partially broken (though that's unlikely to happen these days). + */ + IUnknown *pUnknown = NULL; + hrc = CoCreateInstance(CLSID_VirtualBox, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnknown); + if (FAILED(hrc)) + { + if (hrc == hrcCaller) + return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc"), hrcCaller); + return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc & %Rhrc"), hrcCaller, hrc); + } + + /* + * Try query the IVirtualBox interface (should fail), if it succeed we return + * straight away so we have more columns to spend on long messages below. + */ + IVirtualBox *pVirtualBox; + hrc = pUnknown->QueryInterface(IID_IVirtualBox, (void **)&pVirtualBox); + if (SUCCEEDED(hrc)) + { + pVirtualBox->Release(); + pUnknown->Release(); + return setError(hrcCaller, + tr("Failed to instantiate CLSID_VirtualBox the first time, but worked when checking out why ... weird")); + } + + /* + * Check for oleaut32.msm traces in the registry. + */ + HKEY hKey; + LSTATUS lrc = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"CLSID\\{00020420-0000-0000-C000-000000000046}\\InprocServer32", + 0 /*fFlags*/, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | STANDARD_RIGHTS_READ, &hKey); + if (lrc == ERROR_SUCCESS) + { + wchar_t wszBuf[8192]; + DWORD cbBuf = sizeof(wszBuf) - sizeof(wchar_t); + DWORD dwType = 0; + lrc = RegQueryValueExW(hKey, L"InprocServer32", NULL /*pvReserved*/, &dwType, (BYTE *)&wszBuf[0], &cbBuf); + if (lrc == ERROR_SUCCESS) + { + wszBuf[cbBuf / sizeof(wchar_t)] = '\0'; + bool fSetError = false; + + /* + * Try decode the string and improve the message. + */ + typedef UINT (WINAPI *PFNMSIDECOMPOSEDESCRIPTORW)(PCWSTR pwszDescriptor, + LPWSTR pwszProductCode /*[40]*/, + LPWSTR pwszFeatureId /*[40]*/, + LPWSTR pwszComponentCode /*[40]*/, + DWORD *poffArguments); + PFNMSIDECOMPOSEDESCRIPTORW pfnMsiDecomposeDescriptorW; + pfnMsiDecomposeDescriptorW = (PFNMSIDECOMPOSEDESCRIPTORW)RTLdrGetSystemSymbol("msi.dll", "MsiDecomposeDescriptorW"); + if ( pfnMsiDecomposeDescriptorW + && ( dwType == REG_SZ + || dwType == REG_MULTI_SZ)) + { + wchar_t wszProductCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 }; + wchar_t wszFeatureId[RTUUID_STR_LENGTH + 2 + 16] = { 0 }; + wchar_t wszComponentCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 }; + DWORD offArguments = ~(DWORD)0; + UINT uRc = pfnMsiDecomposeDescriptorW(wszBuf, wszProductCode, wszFeatureId, wszComponentCode, &offArguments); + if (uRc == 0) + { + /* + * Can we resolve the product code into a name? + */ + typedef UINT (WINAPI *PFNMSIOPENPRODUCTW)(PCWSTR, MSIHANDLE *); + PFNMSIOPENPRODUCTW pfnMsiOpenProductW; + pfnMsiOpenProductW = (PFNMSIOPENPRODUCTW)RTLdrGetSystemSymbol("msi.dll", "MsiOpenProductW"); + + typedef UINT (WINAPI *PFNMSICLOSEHANDLE)(MSIHANDLE); + PFNMSICLOSEHANDLE pfnMsiCloseHandle; + pfnMsiCloseHandle = (PFNMSICLOSEHANDLE)RTLdrGetSystemSymbol("msi.dll", "MsiCloseHandle"); + + typedef UINT (WINAPI *PFNGETPRODUCTPROPERTYW)(MSIHANDLE, PCWSTR, PWSTR, PDWORD); + PFNGETPRODUCTPROPERTYW pfnMsiGetProductPropertyW; + pfnMsiGetProductPropertyW = (PFNGETPRODUCTPROPERTYW)RTLdrGetSystemSymbol("msi.dll", "MsiGetProductPropertyW"); + if ( pfnMsiGetProductPropertyW + && pfnMsiCloseHandle + && pfnMsiOpenProductW) + { + MSIHANDLE hMsi = 0; + uRc = pfnMsiOpenProductW(wszProductCode, &hMsi); + if (uRc == 0) + { + static wchar_t const * const s_apwszProps[] = + { + INSTALLPROPERTY_INSTALLEDPRODUCTNAME, + INSTALLPROPERTY_PRODUCTNAME, + INSTALLPROPERTY_PACKAGENAME, + }; + + wchar_t wszProductName[1024]; + DWORD cwcProductName; + unsigned i = 0; + do + { + cwcProductName = RT_ELEMENTS(wszProductName) - 1; + uRc = pfnMsiGetProductPropertyW(hMsi, s_apwszProps[i], wszProductName, &cwcProductName); + } + while ( ++i < RT_ELEMENTS(s_apwszProps) + && ( uRc != 0 + || cwcProductName < 2 + || cwcProductName >= RT_ELEMENTS(wszProductName)) ); + uRc = pfnMsiCloseHandle(hMsi); + if (uRc == 0 && cwcProductName >= 2) + { + wszProductName[RT_MIN(cwcProductName, RT_ELEMENTS(wszProductName) - 1)] = '\0'; + setError(hrcCaller, + tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n" + "PSDispatch looks broken by the '%ls' (%ls) program, suspecting that it features the broken oleaut32.msm module as component %ls.\n" + "\n" + "We suggest you try uninstall '%ls'.\n" + "\n" + "See also https://support.microsoft.com/en-us/kb/316911 "), + wszProductName, wszProductCode, wszComponentCode, wszProductName); + fSetError = true; + } + } + } + + /* MSI uses COM and may mess up our stuff. So, we wait with the fallback till afterwards in this case. */ + if (!fSetError) + { + setError(hrcCaller, + tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n" + "PSDispatch looks broken by installer %ls featuring the broken oleaut32.msm module as component %ls.\n" + "\n" + "See also https://support.microsoft.com/en-us/kb/316911 "), + wszProductCode, wszComponentCode); + fSetError = true; + } + } + } + if (!fSetError) + setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n" + "PSDispatch looks broken by some installer featuring the broken oleaut32.msm module as a component.\n" + "\n" + "See also https://support.microsoft.com/en-us/kb/316911 ")); + } + else if (lrc == ERROR_FILE_NOT_FOUND) + setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n" + "PSDispatch looks fine. Weird")); + else + setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n" + "Checking out PSDispatch registration ended with error: %u (%#x)"), lrc, lrc); + RegCloseKey(hKey); + } + + pUnknown->Release(); + return hrcCaller; +} + +# ifdef VBOX_WITH_SDS +/** + * Gets the service account name and start type for the given service. + * + * @returns IPRT status code (for some reason). + * @param pwszServiceName The name of the service. + * @param pwszAccountName Where to return the account name. + * @param cwcAccountName The length of the account name buffer (in WCHARs). + * @param puStartType Where to return the start type. + */ +int VirtualBoxClient::i_getServiceAccountAndStartType(const wchar_t *pwszServiceName, + wchar_t *pwszAccountName, size_t cwcAccountName, uint32_t *puStartType) +{ + AssertPtr(pwszServiceName); + AssertPtr(pwszAccountName); + Assert(cwcAccountName); + *pwszAccountName = '\0'; + *puStartType = SERVICE_DEMAND_START; + + int vrc; + + // Get a handle to the SCM database. + SC_HANDLE hSCManager = OpenSCManagerW(NULL /*pwszMachineName*/, NULL /*pwszDatabaseName*/, SC_MANAGER_CONNECT); + if (hSCManager != NULL) + { + SC_HANDLE hService = OpenServiceW(hSCManager, pwszServiceName, SERVICE_QUERY_CONFIG); + if (hService != NULL) + { + DWORD cbNeeded = sizeof(QUERY_SERVICE_CONFIGW) + _1K; + if (!QueryServiceConfigW(hService, NULL, 0, &cbNeeded)) + { + Assert(GetLastError() == ERROR_INSUFFICIENT_BUFFER); + LPQUERY_SERVICE_CONFIGW pSc = (LPQUERY_SERVICE_CONFIGW)RTMemTmpAllocZ(cbNeeded + _1K); + if (pSc) + { + DWORD cbNeeded2 = 0; + if (QueryServiceConfigW(hService, pSc, cbNeeded + _1K, &cbNeeded2)) + { + *puStartType = pSc->dwStartType; + vrc = RTUtf16Copy(pwszAccountName, cwcAccountName, pSc->lpServiceStartName); + if (RT_FAILURE(vrc)) + LogRel(("Error: SDS service name is too long (%Rrc): %ls\n", vrc, pSc->lpServiceStartName)); + } + else + { + int dwError = GetLastError(); + vrc = RTErrConvertFromWin32(dwError); + LogRel(("Error: Failed querying '%ls' service config: %Rwc (%u) -> %Rrc; cbNeeded=%d cbNeeded2=%d\n", + pwszServiceName, dwError, dwError, vrc, cbNeeded, cbNeeded2)); + } + RTMemTmpFree(pSc); + } + else + { + LogRel(("Error: Failed allocating %#x bytes of memory for service config!\n", cbNeeded + _1K)); + vrc = VERR_NO_TMP_MEMORY; + } + } + else + { + AssertLogRelMsgFailed(("Error: QueryServiceConfigW returns success with zero buffer!\n")); + vrc = VERR_IPE_UNEXPECTED_STATUS; + } + CloseServiceHandle(hService); + } + else + { + int dwError = GetLastError(); + vrc = RTErrConvertFromWin32(dwError); + LogRel(("Error: Could not open service '%ls': %Rwc (%u) -> %Rrc\n", pwszServiceName, dwError, dwError, vrc)); + } + CloseServiceHandle(hSCManager); + } + else + { + int dwError = GetLastError(); + vrc = RTErrConvertFromWin32(dwError); + LogRel(("Error: Could not open SCM: %Rwc (%u) -> %Rrc\n", dwError, dwError, vrc)); + } + return vrc; +} +# endif /* VBOX_WITH_SDS */ + +#endif /* RT_OS_WINDOWS */ + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void VirtualBoxClient::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + { + LogFlowThisFunc(("already done\n")); + return; + } + +#ifdef VBOX_WITH_MAIN_NLS + i_unregisterEventListener(); +#endif + + if (mData.m_ThreadWatcher != NIL_RTTHREAD) + { + /* Signal the event semaphore and wait for the thread to terminate. + * if it hangs for some reason exit anyway, this can cause a crash + * though as the object will no longer be available. */ + RTSemEventSignal(mData.m_SemEvWatcher); + RTThreadWait(mData.m_ThreadWatcher, 30000, NULL); + mData.m_ThreadWatcher = NIL_RTTHREAD; + RTSemEventDestroy(mData.m_SemEvWatcher); + mData.m_SemEvWatcher = NIL_RTSEMEVENT; + } +#ifdef VBOX_WITH_MAIN_NLS + if (mData.m_pVBoxTranslator != NULL) + { + mData.m_pVBoxTranslator->release(); + mData.m_pVBoxTranslator = NULL; + mData.m_pTrComponent = NULL; + } +#endif + mData.m_pToken.setNull(); + mData.m_pVirtualBox.setNull(); + + ASMAtomicDecU32(&g_cInstances); + + LogFlowThisFunc(("returns\n")); +} + +// IVirtualBoxClient properties +///////////////////////////////////////////////////////////////////////////// + +/** + * Returns a reference to the VirtualBox object. + * + * @returns COM status code + * @param aVirtualBox Address of result variable. + */ +HRESULT VirtualBoxClient::getVirtualBox(ComPtr<IVirtualBox> &aVirtualBox) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aVirtualBox = mData.m_pVirtualBox; + return S_OK; +} + +/** + * Create a new Session object and return a reference to it. + * + * @returns COM status code + * @param aSession Address of result variable. + */ +HRESULT VirtualBoxClient::getSession(ComPtr<ISession> &aSession) +{ + /* this is not stored in this object, no need to lock */ + ComPtr<ISession> pSession; + HRESULT hrc = pSession.createInprocObject(CLSID_Session); + if (SUCCEEDED(hrc)) + aSession = pSession; + return hrc; +} + +/** + * Return reference to the EventSource associated with this object. + * + * @returns COM status code + * @param aEventSource Address of result variable. + */ +HRESULT VirtualBoxClient::getEventSource(ComPtr<IEventSource> &aEventSource) +{ + /* this is const, no need to lock */ + aEventSource = mData.m_pEventSource; + return aEventSource.isNull() ? E_FAIL : S_OK; +} + +// IVirtualBoxClient methods +///////////////////////////////////////////////////////////////////////////// + +/** + * Checks a Machine object for any pending errors. + * + * @returns COM status code + * @param aMachine Machine object to check. + */ +HRESULT VirtualBoxClient::checkMachineError(const ComPtr<IMachine> &aMachine) +{ + BOOL fAccessible = FALSE; + HRESULT hrc = aMachine->COMGETTER(Accessible)(&fAccessible); + if (FAILED(hrc)) + return setError(hrc, tr("Could not check the accessibility status of the VM")); + else if (!fAccessible) + { + ComPtr<IVirtualBoxErrorInfo> pAccessError; + hrc = aMachine->COMGETTER(AccessError)(pAccessError.asOutParam()); + if (FAILED(hrc)) + return setError(hrc, tr("Could not get the access error message of the VM")); + else + { + ErrorInfo info(pAccessError); + ErrorInfoKeeper eik(info); + return info.getResultCode(); + } + } + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + + +/// @todo AM Add pinging of VBoxSDS +/*static*/ +DECLCALLBACK(int) VirtualBoxClient::SVCWatcherThread(RTTHREAD ThreadSelf, + void *pvUser) +{ + NOREF(ThreadSelf); + Assert(pvUser); + VirtualBoxClient *pThis = (VirtualBoxClient *)pvUser; + RTSEMEVENT sem = pThis->mData.m_SemEvWatcher; + RTMSINTERVAL cMillies = VBOXCLIENT_DEFAULT_INTERVAL; + + /* The likelihood of early crashes are high, so start with a short wait. */ + int vrc = RTSemEventWait(sem, cMillies / 2); + + /* As long as the waiting times out keep retrying the wait. */ + while (RT_FAILURE(vrc)) + { + { + HRESULT hrc = S_OK; + ComPtr<IVirtualBox> pV; + { + AutoReadLock alock(pThis COMMA_LOCKVAL_SRC_POS); + pV = pThis->mData.m_pVirtualBox; + } + if (!pV.isNull()) + { + ULONG rev; + hrc = pV->COMGETTER(Revision)(&rev); + if (FAILED_DEAD_INTERFACE(hrc)) + { + LogRel(("VirtualBoxClient: detected unresponsive VBoxSVC (hrc=%Rhrc)\n", hrc)); + { + AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS); + /* Throw away the VirtualBox reference, it's no longer + * usable as VBoxSVC terminated in the mean time. */ + pThis->mData.m_pVirtualBox.setNull(); + } + ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, FALSE); + } + } + else + { + /* Try to get a new VirtualBox reference straight away, and if + * this fails use an increased waiting time as very frequent + * restart attempts in some wedged config can cause high CPU + * and disk load. */ + ComPtr<IVirtualBox> pVirtualBox; + ComPtr<IToken> pToken; + hrc = pVirtualBox.createLocalObject(CLSID_VirtualBox); + if (FAILED(hrc)) + cMillies = 3 * VBOXCLIENT_DEFAULT_INTERVAL; + else + { + LogRel(("VirtualBoxClient: detected working VBoxSVC (hrc=%Rhrc)\n", hrc)); + { + AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS); + /* Update the VirtualBox reference, there's a working + * VBoxSVC again from now on. */ + pThis->mData.m_pVirtualBox = pVirtualBox; + pThis->mData.m_pToken = pToken; +#ifdef VBOX_WITH_MAIN_NLS + /* update language using new instance of IVirtualBox in case the language settings was changed */ + pThis->i_reloadApiLanguage(); + pThis->i_registerEventListener(); +#endif + } + ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, TRUE); + cMillies = VBOXCLIENT_DEFAULT_INTERVAL; + } + } + } + vrc = RTSemEventWait(sem, cMillies); + } + return 0; +} + +#ifdef VBOX_WITH_MAIN_NLS + +HRESULT VirtualBoxClient::i_reloadApiLanguage() +{ + if (mData.m_pVBoxTranslator == NULL) + return S_OK; + + HRESULT hrc = mData.m_pVBoxTranslator->loadLanguage(mData.m_pVirtualBox); + if (FAILED(hrc)) + setError(hrc, tr("Failed to load user language instance")); + return hrc; +} + +HRESULT VirtualBoxClient::i_registerEventListener() +{ + HRESULT hrc = mData.m_pVirtualBox->COMGETTER(EventSource)(mData.m_pVBoxEventSource.asOutParam()); + if (SUCCEEDED(hrc)) + { + ComObjPtr<VBoxEventListenerImpl> pVBoxListener; + pVBoxListener.createObject(); + pVBoxListener->init(new VBoxEventListener()); + mData.m_pVBoxEventListener = pVBoxListener; + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnLanguageChanged); + hrc = mData.m_pVBoxEventSource->RegisterListener(pVBoxListener, ComSafeArrayAsInParam(eventTypes), true); + if (FAILED(hrc)) + { + hrc = setError(hrc, tr("Failed to register listener")); + mData.m_pVBoxEventListener.setNull(); + mData.m_pVBoxEventSource.setNull(); + } + } + else + hrc = setError(hrc, tr("Failed to get event source from VirtualBox")); + return hrc; +} + +void VirtualBoxClient::i_unregisterEventListener() +{ + if (mData.m_pVBoxEventListener.isNotNull()) + { + if (mData.m_pVBoxEventSource.isNotNull()) + mData.m_pVBoxEventSource->UnregisterListener(mData.m_pVBoxEventListener); + mData.m_pVBoxEventListener.setNull(); + } + mData.m_pVBoxEventSource.setNull(); +} + +#endif /* VBOX_WITH_MAIN_NLS */ + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/WebMWriter.cpp b/src/VBox/Main/src-client/WebMWriter.cpp new file mode 100644 index 00000000..6361f13e --- /dev/null +++ b/src/VBox/Main/src-client/WebMWriter.cpp @@ -0,0 +1,881 @@ +/* $Id: WebMWriter.cpp $ */ +/** @file + * WebMWriter.cpp - WebM container handling. + */ + +/* + * Copyright (C) 2013-2022 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 + */ + +/** + * For more information, see: + * - https://w3c.github.io/media-source/webm-byte-stream-format.html + * - https://www.webmproject.org/docs/container/#muxer-guidelines + */ + +#define LOG_GROUP LOG_GROUP_RECORDING +#include "LoggingNew.h" + +#include <iprt/buildconfig.h> +#include <iprt/errcore.h> + +#include <VBox/version.h> + +#include "RecordingInternals.h" +#include "WebMWriter.h" + + +WebMWriter::WebMWriter(void) +{ + /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */ + m_cbTimecode = 2; + m_uTimecodeMax = UINT16_MAX; + + m_fInTracksSection = false; +} + +WebMWriter::~WebMWriter(void) +{ + Close(); +} + +/** + * Opens (creates) an output file using an already open file handle. + * + * @returns VBox status code. + * @param a_pszFilename Name of the file the file handle points at. + * @param a_phFile Pointer to open file handle to use. + * @param a_enmAudioCodec Audio codec to use. + * @param a_enmVideoCodec Video codec to use. + */ +int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile, + RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec) +{ + try + { + LogFunc(("Creating '%s'\n", a_pszFilename)); + + int vrc = createEx(a_pszFilename, a_phFile); + if (RT_SUCCESS(vrc)) + { + vrc = init(a_enmAudioCodec, a_enmVideoCodec); + if (RT_SUCCESS(vrc)) + vrc = writeHeader(); + } + } + catch(int vrc) + { + return vrc; + } + return VINF_SUCCESS; +} + +/** + * Opens an output file. + * + * @returns VBox status code. + * @param a_pszFilename Name of the file to create. + * @param a_fOpen File open mode of type RTFILE_O_. + * @param a_enmAudioCodec Audio codec to use. + * @param a_enmVideoCodec Video codec to use. + */ +int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen, + RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec) +{ + try + { + LogFunc(("Creating '%s'\n", a_pszFilename)); + + int vrc = create(a_pszFilename, a_fOpen); + if (RT_SUCCESS(vrc)) + { + vrc = init(a_enmAudioCodec, a_enmVideoCodec); + if (RT_SUCCESS(vrc)) + vrc = writeHeader(); + } + } + catch(int vrc) + { + return vrc; + } + return VINF_SUCCESS; +} + +/** + * Closes the WebM file and drains all queues. + * + * @returns VBox status code. + */ +int WebMWriter::Close(void) +{ + LogFlowFuncEnter(); + + if (!isOpen()) + return VINF_SUCCESS; + + /* Make sure to drain all queues. */ + processQueue(&m_CurSeg.m_queueBlocks, true /* fForce */); + + writeFooter(); + + WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin(); + while (itTrack != m_CurSeg.m_mapTracks.end()) + { + WebMTrack *pTrack = itTrack->second; + if (pTrack) /* Paranoia. */ + delete pTrack; + + m_CurSeg.m_mapTracks.erase(itTrack); + + itTrack = m_CurSeg.m_mapTracks.begin(); + } + + Assert(m_CurSeg.m_queueBlocks.Map.size() == 0); + Assert(m_CurSeg.m_mapTracks.size() == 0); + + com::Utf8Str strFileName = getFileName().c_str(); + + close(); + + return VINF_SUCCESS; +} + +/** + * Adds an audio track. + * + * @returns VBox status code. + * @param pCodec Audio codec to use. + * @param uHz Input sampling rate. + * Must be supported by the selected audio codec. + * @param cChannels Number of input audio channels. + * @param cBits Number of input bits per channel. + * @param puTrack Track number on successful creation. Optional. + */ +int WebMWriter::AddAudioTrack(PRECORDINGCODEC pCodec, uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack) +{ + AssertReturn(uHz, VERR_INVALID_PARAMETER); + AssertReturn(cBits, VERR_INVALID_PARAMETER); + AssertReturn(cChannels, VERR_INVALID_PARAMETER); + + /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1. + * Using a track number 0 will show those files as being corrupted. */ + const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1; + + subStart(MkvElem_TrackEntry); + + serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); + serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); + serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); + + int vrc = VINF_SUCCESS; + + WebMTrack *pTrack = NULL; + try + { + pTrack = new WebMTrack(WebMTrackType_Audio, pCodec, uTrack, RTFileTell(getFile())); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(vrc)) + { + serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4) + .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */); + + switch (m_enmAudioCodec) + { +# ifdef VBOX_WITH_LIBVORBIS + case RecordingAudioCodec_OggVorbis: + { + pTrack->Audio.msPerBlock = 0; /** @todo */ + if (!pTrack->Audio.msPerBlock) /* No ms per frame defined? Use default. */ + pTrack->Audio.msPerBlock = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT; + + vorbis_comment vc; + vorbis_comment_init(&vc); + vorbis_comment_add_tag(&vc,"ENCODER", vorbis_version_string()); + + ogg_packet pkt_ident; + ogg_packet pkt_comments; + ogg_packet pkt_setup; + vorbis_analysis_headerout(&pCodec->Audio.Vorbis.dsp_state, &vc, &pkt_ident, &pkt_comments, &pkt_setup); + AssertMsgBreakStmt(pkt_ident.bytes <= 255 && pkt_comments.bytes <= 255, + ("Too long header / comment packets\n"), vrc = VERR_INVALID_PARAMETER); + + WEBMOGGVORBISPRIVDATA vorbisPrivData(pkt_ident.bytes, pkt_comments.bytes, pkt_setup.bytes); + + uint8_t *pabHdr = &vorbisPrivData.abHdr[0]; + memcpy(pabHdr, pkt_ident.packet, pkt_ident.bytes); + pabHdr += pkt_ident.bytes; + memcpy(pabHdr, pkt_comments.packet, pkt_comments.bytes); + pabHdr += pkt_comments.bytes; + memcpy(pabHdr, pkt_setup.packet, pkt_setup.bytes); + pabHdr += pkt_setup.bytes; + + vorbis_comment_clear(&vc); + + size_t const offHeaders = RT_OFFSETOF(WEBMOGGVORBISPRIVDATA, abHdr); + + serializeString(MkvElem_CodecID, "A_VORBIS"); + serializeData(MkvElem_CodecPrivate, &vorbisPrivData, + offHeaders + pkt_ident.bytes + pkt_comments.bytes + pkt_setup.bytes); + break; + } +# endif /* VBOX_WITH_LIBVORBIS */ + default: + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); /* Shouldn't ever happen (tm). */ + break; + } + + if (RT_SUCCESS(vrc)) + { + serializeUnsignedInteger(MkvElem_CodecDelay, 0) + .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */ + .subStart(MkvElem_Audio) + .serializeFloat(MkvElem_SamplingFrequency, (float)uHz) + .serializeUnsignedInteger(MkvElem_Channels, cChannels) + .serializeUnsignedInteger(MkvElem_BitDepth, cBits) + .subEnd(MkvElem_Audio) + .subEnd(MkvElem_TrackEntry); + + pTrack->Audio.uHz = uHz; + pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock); + + LogRel2(("Recording: WebM track #%RU8: Audio codec @ %RU16Hz (%RU16ms, %RU16 frames per block)\n", + pTrack->uTrack, pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock)); + + m_CurSeg.m_mapTracks[uTrack] = pTrack; + + if (puTrack) + *puTrack = uTrack; + + return VINF_SUCCESS; + } + } + + if (pTrack) + delete pTrack; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Adds a video track. + * + * @returns VBox status code. + * @param pCodec Codec data to use. + * @param uWidth Width (in pixels) of the video track. + * @param uHeight Height (in pixels) of the video track. + * @param uFPS FPS (Frames Per Second) of the video track. + * @param puTrack Track number of the added video track on success. Optional. + */ +int WebMWriter::AddVideoTrack(PRECORDINGCODEC pCodec, uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack) +{ +#ifdef VBOX_WITH_LIBVPX + /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1. + * Using a track number 0 will show those files as being corrupted. */ + const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1; + + subStart(MkvElem_TrackEntry); + + serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); + serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); + serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); + + WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, pCodec, uTrack, RTFileTell(getFile())); + + /** @todo Resolve codec type. */ + serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4) + .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */) + .serializeString(MkvElem_CodecID, "V_VP8") + .subStart(MkvElem_Video) + .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth) + .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight) + /* Some players rely on the FPS rate for timing calculations. + * So make sure to *always* include that. */ + .serializeFloat (MkvElem_FrameRate, (float)uFPS) + .subEnd(MkvElem_Video); + + subEnd(MkvElem_TrackEntry); + + LogRel2(("Recording: WebM track #%RU8: Video\n", pTrack->uTrack)); + + m_CurSeg.m_mapTracks[uTrack] = pTrack; + + if (puTrack) + *puTrack = uTrack; + + return VINF_SUCCESS; +#else + RT_NOREF(pCodec, uWidth, uHeight, uFPS, puTrack); + return VERR_NOT_SUPPORTED; +#endif +} + +/** + * Gets file name. + * + * @returns File name as UTF-8 string. + */ +const com::Utf8Str& WebMWriter::GetFileName(void) +{ + return getFileName(); +} + +/** + * Gets current output file size. + * + * @returns File size in bytes. + */ +uint64_t WebMWriter::GetFileSize(void) +{ + return getFileSize(); +} + +/** + * Gets current free storage space available for the file. + * + * @returns Available storage free space. + */ +uint64_t WebMWriter::GetAvailableSpace(void) +{ + return getAvailableSpace(); +} + +/** + * Takes care of the initialization of the instance. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if a given codec is not supported. + * @param enmAudioCodec Audio codec to use. + * @param enmVideoCodec Video codec to use. + */ +int WebMWriter::init(RecordingAudioCodec_T enmAudioCodec, RecordingVideoCodec_T enmVideoCodec) +{ +#ifndef VBOX_WITH_LIBVORBIS + AssertReturn(enmAudioCodec != RecordingAudioCodec_OggVorbis, VERR_NOT_SUPPORTED); +#endif + AssertReturn( enmVideoCodec == RecordingVideoCodec_None + || enmVideoCodec == RecordingVideoCodec_VP8, VERR_NOT_SUPPORTED); + + m_enmAudioCodec = enmAudioCodec; + m_enmVideoCodec = enmVideoCodec; + + return m_CurSeg.init(); +} + +/** + * Takes care of the destruction of the instance. + */ +void WebMWriter::destroy(void) +{ + m_CurSeg.uninit(); +} + +/** + * Writes the WebM file header. + * + * @returns VBox status code. + */ +int WebMWriter::writeHeader(void) +{ + LogFunc(("Header @ %RU64\n", RTFileTell(getFile()))); + + subStart(MkvElem_EBML) + .serializeUnsignedInteger(MkvElem_EBMLVersion, 1) + .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1) + .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4) + .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8) + .serializeString(MkvElem_DocType, "webm") + .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2) + .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2) + .subEnd(MkvElem_EBML); + + subStart(MkvElem_Segment); + + /* Save offset of current segment. */ + m_CurSeg.m_offStart = RTFileTell(getFile()); + + writeSeekHeader(); + + /* Save offset of upcoming tracks segment. */ + m_CurSeg.m_offTracks = RTFileTell(getFile()); + + /* The tracks segment starts right after this header. */ + subStart(MkvElem_Tracks); + m_fInTracksSection = true; + + return VINF_SUCCESS; +} + +/** + * Writes a simple block into the EBML structure. + * + * @returns VBox status code. + * @param a_pTrack Track the simple block is assigned to. + * @param a_pBlock Simple block to write. + */ +int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock) +{ +#ifdef LOG_ENABLED + WebMCluster &Cluster = m_CurSeg.m_CurCluster; + + Log3Func(("[T%RU8C%RU64] Off=%RU64, AbsPTSMs=%RU64, RelToClusterMs=%RU16, %zu bytes\n", + a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()), + a_pBlock->Data.tcAbsPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb)); +#endif + /* + * Write a "Simple Block". + */ + writeClassId(MkvElem_SimpleBlock); + /* Block size. */ + writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */ + + m_cbTimecode /* Timecode size .*/ + + 1 /* Flags size. */ + + a_pBlock->Data.cb /* Actual frame data size. */), 4); + /* Track number. */ + writeSize(a_pTrack->uTrack); + /* Timecode (relative to cluster opening timecode). */ + writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode); + /* Flags. */ + writeUnsignedInteger(a_pBlock->Data.fFlags, 1); + /* Frame data. */ + write(a_pBlock->Data.pv, a_pBlock->Data.cb); + + return VINF_SUCCESS; +} + +/** + * Writes a simple block and enqueues it into the segment's render queue. + * + * @returns VBox status code. + * @param a_pTrack Track the simple block is assigned to. + * @param a_pBlock Simple block to write and enqueue. + */ +int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock) +{ + RT_NOREF(a_pTrack); + + int vrc = VINF_SUCCESS; + + try + { + const WebMTimecodeAbs tcAbsPTS = a_pBlock->Data.tcAbsPTSMs; + + /* See if we already have an entry for the specified timecode in our queue. */ + WebMBlockMap::iterator itQueue = m_CurSeg.m_queueBlocks.Map.find(tcAbsPTS); + if (itQueue != m_CurSeg.m_queueBlocks.Map.end()) /* Use existing queue. */ + { + WebMTimecodeBlocks &Blocks = itQueue->second; + Blocks.Enqueue(a_pBlock); + } + else /* Create a new timecode entry. */ + { + WebMTimecodeBlocks Blocks; + Blocks.Enqueue(a_pBlock); + + m_CurSeg.m_queueBlocks.Map[tcAbsPTS] = Blocks; + } + + vrc = processQueue(&m_CurSeg.m_queueBlocks, false /* fForce */); + } + catch(...) + { + delete a_pBlock; + a_pBlock = NULL; + + vrc = VERR_NO_MEMORY; + } + + return vrc; +} + +/** + * Writes a data block to the specified track. + * + * @returns VBox status code. + * @param uTrack Track ID to write data to. + * @param pvData Pointer to data block to write. + * @param cbData Size (in bytes) of data block to write. + * @param tcAbsPTSMs Absolute PTS of simple data block. + * @param uFlags WebM block flags to use for this block. + */ +int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs, WebMBlockFlags uFlags) +{ + int vrc = RTCritSectEnter(&m_CurSeg.m_CritSect); + AssertRC(vrc); + + WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.find(uTrack); + if (itTrack == m_CurSeg.m_mapTracks.end()) + { + RTCritSectLeave(&m_CurSeg.m_CritSect); + return VERR_NOT_FOUND; + } + + WebMTrack *pTrack = itTrack->second; + AssertPtr(pTrack); + + if (m_fInTracksSection) + { + subEnd(MkvElem_Tracks); + m_fInTracksSection = false; + } + + try + { + vrc = writeSimpleBlockQueued(pTrack, + new WebMSimpleBlock(pTrack, + tcAbsPTSMs, pvData, cbData, uFlags)); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + + int vrc2 = RTCritSectLeave(&m_CurSeg.m_CritSect); + AssertRC(vrc2); + + return vrc; +} + +/** + * Processes a render queue. + * + * @returns VBox status code. + * @param pQueue Queue to process. + * @param fForce Whether forcing to process the render queue or not. + * Needed to drain the queues when terminating. + */ +int WebMWriter::processQueue(WebMQueue *pQueue, bool fForce) +{ + if (pQueue->tsLastProcessedMs == 0) + pQueue->tsLastProcessedMs = RTTimeMilliTS(); + + if (!fForce) + { + /* Only process when we reached a certain threshold. */ + if (RTTimeMilliTS() - pQueue->tsLastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */) + return VINF_SUCCESS; + } + + WebMCluster &Cluster = m_CurSeg.m_CurCluster; + + /* Iterate through the block map. */ + WebMBlockMap::iterator it = pQueue->Map.begin(); + while (it != m_CurSeg.m_queueBlocks.Map.end()) + { + WebMTimecodeAbs mapAbsPTSMs = it->first; + WebMTimecodeBlocks mapBlocks = it->second; + + /* Whether to start a new cluster or not. */ + bool fClusterStart = false; + + /* If the current segment does not have any clusters (yet), + * take the first absolute PTS as the starting point for that segment. */ + if (m_CurSeg.m_cClusters == 0) + { + m_CurSeg.m_tcAbsStartMs = mapAbsPTSMs; + fClusterStart = true; + } + + /* Determine if we need to start a new cluster. */ + /* No blocks written yet? Start a new cluster. */ + if ( Cluster.cBlocks == 0 + /* Did we reach the maximum a cluster can hold? Use a new cluster then. */ + || mapAbsPTSMs - Cluster.tcAbsStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS + /* If the block map indicates that a cluster is needed for this timecode, create one. */ + || mapBlocks.fClusterNeeded) + { + fClusterStart = true; + } + + if ( fClusterStart + && !mapBlocks.fClusterStarted) + { + /* Last written timecode of the current cluster. */ + uint64_t tcAbsClusterLastWrittenMs; + + if (Cluster.fOpen) /* Close current cluster first. */ + { + Log2Func(("[C%RU64] End @ %RU64ms (duration = %RU64ms)\n", + Cluster.uID, Cluster.tcAbsLastWrittenMs, Cluster.tcAbsLastWrittenMs - Cluster.tcAbsStartMs)); + + /* Make sure that the current cluster contained some data. */ + Assert(Cluster.offStart); + Assert(Cluster.cBlocks); + + /* Save the last written timecode of the current cluster before closing it. */ + tcAbsClusterLastWrittenMs = Cluster.tcAbsLastWrittenMs; + + subEnd(MkvElem_Cluster); + Cluster.fOpen = false; + } + else /* First cluster ever? Use the segment's starting timecode. */ + tcAbsClusterLastWrittenMs = m_CurSeg.m_tcAbsStartMs; + + Cluster.fOpen = true; + Cluster.uID = m_CurSeg.m_cClusters; + /* Use the block map's currently processed TC as the cluster's starting TC. */ + Cluster.tcAbsStartMs = mapAbsPTSMs; + Cluster.tcAbsLastWrittenMs = Cluster.tcAbsStartMs; + Cluster.offStart = RTFileTell(getFile()); + Cluster.cBlocks = 0; + + AssertMsg(Cluster.tcAbsStartMs <= mapAbsPTSMs, + ("Cluster #%RU64 start TC (%RU64) must not bigger than the block map's currently processed TC (%RU64)\n", + Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs)); + + Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n", + Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs, Cluster.offStart)); + + /* Insert cue points for all tracks if a new cluster has been started. */ + WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsStartMs); + + WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin(); + while (itTrack != m_CurSeg.m_mapTracks.end()) + { + pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart); + ++itTrack; + } + + m_CurSeg.m_lstCuePoints.push_back(pCuePoint); + + subStart(MkvElem_Cluster) + .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs - m_CurSeg.m_tcAbsStartMs); + + m_CurSeg.m_cClusters++; + + mapBlocks.fClusterStarted = true; + } + + Log2Func(("[C%RU64] SegTcAbsStartMs=%RU64, ClusterTcAbsStartMs=%RU64, ClusterTcAbsLastWrittenMs=%RU64, mapAbsPTSMs=%RU64\n", + Cluster.uID, m_CurSeg.m_tcAbsStartMs, Cluster.tcAbsStartMs, Cluster.tcAbsLastWrittenMs, mapAbsPTSMs)); + + /* Iterate through all blocks related to the current timecode. */ + while (!mapBlocks.Queue.empty()) + { + WebMSimpleBlock *pBlock = mapBlocks.Queue.front(); + AssertPtr(pBlock); + + WebMTrack *pTrack = pBlock->pTrack; + AssertPtr(pTrack); + + /* Calculate the block's relative time code to the current cluster's starting time code. */ + Assert(pBlock->Data.tcAbsPTSMs >= Cluster.tcAbsStartMs); + pBlock->Data.tcRelToClusterMs = pBlock->Data.tcAbsPTSMs - Cluster.tcAbsStartMs; + + int vrc2 = writeSimpleBlockEBML(pTrack, pBlock); + AssertRC(vrc2); + + Cluster.cBlocks++; + Cluster.tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs; + + pTrack->cTotalBlocks++; + pTrack->tcAbsLastWrittenMs = Cluster.tcAbsLastWrittenMs; + + if (m_CurSeg.m_tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs) + m_CurSeg.m_tcAbsLastWrittenMs = pTrack->tcAbsLastWrittenMs; + + /* Save a cue point if this is a keyframe (if no new cluster has been started, + * as this implies that a cue point already is present. */ + if ( !fClusterStart + && (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)) + { + /* Insert cue points for all tracks if a new cluster has been started. */ + WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsLastWrittenMs); + + WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin(); + while (itTrack != m_CurSeg.m_mapTracks.end()) + { + pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart); + ++itTrack; + } + + m_CurSeg.m_lstCuePoints.push_back(pCuePoint); + } + + delete pBlock; + pBlock = NULL; + + mapBlocks.Queue.pop(); + } + + Assert(mapBlocks.Queue.empty()); + + m_CurSeg.m_queueBlocks.Map.erase(it); + + it = m_CurSeg.m_queueBlocks.Map.begin(); + } + + Assert(m_CurSeg.m_queueBlocks.Map.empty()); + + pQueue->tsLastProcessedMs = RTTimeMilliTS(); + + return VINF_SUCCESS; +} + +/** + * Writes the WebM footer. + * + * @returns VBox status code. + */ +int WebMWriter::writeFooter(void) +{ + AssertReturn(isOpen(), VERR_WRONG_ORDER); + + if (m_fInTracksSection) + { + subEnd(MkvElem_Tracks); + m_fInTracksSection = false; + } + + if (m_CurSeg.m_CurCluster.fOpen) + { + subEnd(MkvElem_Cluster); + m_CurSeg.m_CurCluster.fOpen = false; + } + + /* + * Write Cues element. + */ + m_CurSeg.m_offCues = RTFileTell(getFile()); + LogFunc(("Cues @ %RU64\n", m_CurSeg.m_offCues)); + + subStart(MkvElem_Cues); + + WebMCuePointList::iterator itCuePoint = m_CurSeg.m_lstCuePoints.begin(); + while (itCuePoint != m_CurSeg.m_lstCuePoints.end()) + { + WebMCuePoint *pCuePoint = (*itCuePoint); + AssertPtr(pCuePoint); + + LogFunc(("CuePoint @ %RU64: %zu tracks, tcAbs=%RU64)\n", + RTFileTell(getFile()), pCuePoint->Pos.size(), pCuePoint->tcAbs)); + + subStart(MkvElem_CuePoint) + .serializeUnsignedInteger(MkvElem_CueTime, pCuePoint->tcAbs); + + WebMCueTrackPosMap::iterator itTrackPos = pCuePoint->Pos.begin(); + while (itTrackPos != pCuePoint->Pos.end()) + { + WebMCueTrackPosEntry *pTrackPos = itTrackPos->second; + AssertPtr(pTrackPos); + + LogFunc(("TrackPos (track #%RU32) @ %RU64, offCluster=%RU64)\n", + itTrackPos->first, RTFileTell(getFile()), pTrackPos->offCluster)); + + subStart(MkvElem_CueTrackPositions) + .serializeUnsignedInteger(MkvElem_CueTrack, itTrackPos->first) + .serializeUnsignedInteger(MkvElem_CueClusterPosition, pTrackPos->offCluster - m_CurSeg.m_offStart, 8) + .subEnd(MkvElem_CueTrackPositions); + + ++itTrackPos; + } + + subEnd(MkvElem_CuePoint); + + ++itCuePoint; + } + + subEnd(MkvElem_Cues); + subEnd(MkvElem_Segment); + + /* + * Re-Update seek header with final information. + */ + + writeSeekHeader(); + + return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL); +} + +/** + * Writes the segment's seek header. + */ +void WebMWriter::writeSeekHeader(void) +{ + if (m_CurSeg.m_offSeekInfo) + RTFileSeek(getFile(), m_CurSeg.m_offSeekInfo, RTFILE_SEEK_BEGIN, NULL); + else + m_CurSeg.m_offSeekInfo = RTFileTell(getFile()); + + LogFunc(("Seek Header @ %RU64\n", m_CurSeg.m_offSeekInfo)); + + subStart(MkvElem_SeekHead); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks) + .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offTracks - m_CurSeg.m_offStart, 8) + .subEnd(MkvElem_Seek); + + if (m_CurSeg.m_offCues) + LogFunc(("Updating Cues @ %RU64\n", m_CurSeg.m_offCues)); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues) + .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offCues - m_CurSeg.m_offStart, 8) + .subEnd(MkvElem_Seek); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info) + .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offInfo - m_CurSeg.m_offStart, 8) + .subEnd(MkvElem_Seek); + + subEnd(MkvElem_SeekHead); + + /* + * Write the segment's info element. + */ + + /* Save offset of the segment's info element. */ + m_CurSeg.m_offInfo = RTFileTell(getFile()); + + LogFunc(("Info @ %RU64\n", m_CurSeg.m_offInfo)); + + char szMux[64]; + RTStrPrintf(szMux, sizeof(szMux), +#ifdef VBOX_WITH_LIBVPX + "vpxenc%s", vpx_codec_version_str() +#else + "unknown" +#endif + ); + char szApp[64]; + RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision()); + + const WebMTimecodeAbs tcAbsDurationMs = m_CurSeg.m_tcAbsLastWrittenMs - m_CurSeg.m_tcAbsStartMs; + + if (!m_CurSeg.m_lstCuePoints.empty()) + { + LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs)); + AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n")); + } + + subStart(MkvElem_Info) + .serializeUnsignedInteger(MkvElem_TimecodeScale, m_CurSeg.m_uTimecodeScaleFactor) + .serializeFloat(MkvElem_Segment_Duration, tcAbsDurationMs) + .serializeString(MkvElem_MuxingApp, szMux) + .serializeString(MkvElem_WritingApp, szApp) + .subEnd(MkvElem_Info); +} diff --git a/src/VBox/Main/src-client/win/Makefile.kup b/src/VBox/Main/src-client/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Main/src-client/win/Makefile.kup diff --git a/src/VBox/Main/src-client/win/VBoxC.def b/src/VBox/Main/src-client/win/VBoxC.def new file mode 100644 index 00000000..b19f0c32 --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxC.def @@ -0,0 +1,37 @@ +;; @file +; +; VBoxC DLL Definition File. +; + +; +; Copyright (C) 2006-2022 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 +; + +LIBRARY VBoxC.dll + +EXPORTS + ; COM entry points + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + ; private entry points + VBoxDriversRegister PRIVATE diff --git a/src/VBox/Main/src-client/win/VBoxC.rc b/src/VBox/Main/src-client/win/VBoxC.rc new file mode 100644 index 00000000..2937636e --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxC.rc @@ -0,0 +1,72 @@ +/* $Id: VBoxC.rc $ */ +/** @file + * VBoxC - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2022 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> + +#include "win/resource.h" + +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_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual + BEGIN + VALUE "FileDescription", "VirtualBox Interface\0" + VALUE "InternalName", "VBoxC\0" + VALUE "OriginalFilename", "VBoxC.dll\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 + + VALUE "OLESelfRegister", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +//IDR_VIRTUALBOX REGISTRY "VBoxC.rgs" + +1 TYPELIB "VirtualBox.tlb" diff --git a/src/VBox/Main/src-client/win/VBoxClient-x86.def b/src/VBox/Main/src-client/win/VBoxClient-x86.def new file mode 100644 index 00000000..602cd16a --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxClient-x86.def @@ -0,0 +1,36 @@ +; $Id: VBoxClient-x86.def $ +;; @file +; VBoxClient-x86 DLL Definition File. +; + +; +; Copyright (C) 2006-2022 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 +; + +LIBRARY VBoxClient-x86.dll + +EXPORTS + ; COM entry points + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + diff --git a/src/VBox/Main/src-client/win/VBoxClient-x86.rc b/src/VBox/Main/src-client/win/VBoxClient-x86.rc new file mode 100644 index 00000000..ede348eb --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxClient-x86.rc @@ -0,0 +1,73 @@ +/* $Id: VBoxClient-x86.rc $ */ +/** @file + * VBoxC - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2022 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> + +#include "win/resource.h" + +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_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual + BEGIN + VALUE "FileDescription", "VirtualBox Interface (32-bit)\0" + VALUE "InternalName", "VBoxClient-x86\0" + VALUE "OriginalFilename", "VBoxClient-x86.dll\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 + + VALUE "OLESelfRegister", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +IDR_VIRTUALBOX REGISTRY "VBoxClient-x86.rgs" + +1 TYPELIB "VirtualBox-x86.tlb" + diff --git a/src/VBox/Main/src-client/win/dllmain.cpp b/src/VBox/Main/src-client/win/dllmain.cpp new file mode 100644 index 00000000..3c681291 --- /dev/null +++ b/src/VBox/Main/src-client/win/dllmain.cpp @@ -0,0 +1,176 @@ +/* $Id: dllmain.cpp $ */ +/** @file + * VBoxC - COM DLL exports and DLL init/term. + */ + +/* + * Copyright (C) 2006-2022 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/defs.h" + +#include <SessionImpl.h> +#include <VirtualBoxClientImpl.h> + +#include <iprt/initterm.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static ATL::CComModule *g_pAtlComModule; + +BEGIN_OBJECT_MAP(ObjectMap) + OBJECT_ENTRY(CLSID_Session, Session) + OBJECT_ENTRY(CLSID_VirtualBoxClient, VirtualBoxClient) +END_OBJECT_MAP() + + +///////////////////////////////////////////////////////////////////////////// +// DLL Entry Point + +extern "C" +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + // idempotent, so doesn't harm, and needed for COM embedding scenario + RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE); + + g_pAtlComModule = new(ATL::CComModule); + if (!g_pAtlComModule) + return FALSE; + + g_pAtlComModule->Init(ObjectMap, hInstance, &LIBID_VirtualBox); + DisableThreadLibraryCalls(hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + { + if (g_pAtlComModule) + { + g_pAtlComModule->Term(); + delete g_pAtlComModule; + g_pAtlComModule = NULL; + } + } + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// Used to determine whether the DLL can be unloaded by OLE + +STDAPI DllCanUnloadNow(void) +{ + AssertReturn(g_pAtlComModule, S_OK); + LONG const cLocks = g_pAtlComModule->GetLockCount(); + Assert(cLocks >= VirtualBoxClient::s_cUnnecessaryAtlModuleLocks); + return cLocks <= VirtualBoxClient::s_cUnnecessaryAtlModuleLocks ? S_OK : S_FALSE; +} + +///////////////////////////////////////////////////////////////////////////// +// Returns a class factory to create an object of the requested type + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + AssertReturn(g_pAtlComModule, E_UNEXPECTED); + return g_pAtlComModule->GetClassObject(rclsid, riid, ppv); +} + +///////////////////////////////////////////////////////////////////////////// +// DllRegisterServer - Adds entries to the system registry + +STDAPI DllRegisterServer(void) +{ +#ifndef VBOX_WITH_MIDL_PROXY_STUB + // registers object, typelib and all interfaces in typelib + AssertReturn(g_pAtlComModule, E_UNEXPECTED); + return g_pAtlComModule->RegisterServer(TRUE); +#else + return S_OK; /* VBoxProxyStub does all the work, no need to duplicate it here. */ +#endif +} + +///////////////////////////////////////////////////////////////////////////// +// DllUnregisterServer - Removes entries from the system registry + +STDAPI DllUnregisterServer(void) +{ +#ifndef VBOX_WITH_MIDL_PROXY_STUB + AssertReturn(g_pAtlComModule, E_UNEXPECTED); + HRESULT hrc = g_pAtlComModule->UnregisterServer(TRUE); + return hrc; +#else + return S_OK; /* VBoxProxyStub does all the work, no need to duplicate it here. */ +#endif +} + + +#ifdef RT_OS_WINDOWS +/* + * HACK ALERT! Really ugly trick to make the VirtualBoxClient object go away + * when nobody uses it anymore. This is to prevent its uninit() + * method from accessing IVirtualBox and similar proxy stubs after + * COM has been officially shut down. + * + * It is simply TOO LATE to destroy the client object from DllMain/detach! + * + * This hack ASSUMES ObjectMap order. + * This hack is subject to a re-instantiation race. + */ +ULONG VirtualBoxClient::InternalRelease() +{ + ULONG cRefs = VirtualBoxClientWrap::InternalRelease(); +# ifdef DEBUG_bird + char szMsg[64]; + RTStrPrintf(szMsg, sizeof(szMsg), "VirtualBoxClient: cRefs=%d\n", cRefs); + OutputDebugStringA(szMsg); +# endif +# if 1 /* enable ugly hack */ + if (cRefs == 1) + { + /* Make the factory to drop its reference. */ + if (ObjectMap[1].pCF) + { + InternalAddRef(); + + CMyComClassFactorySingleton<VirtualBoxClient> *pFactory; + pFactory = dynamic_cast<CMyComClassFactorySingleton<VirtualBoxClient> *>(ObjectMap[1].pCF); + Assert(pFactory); + if (pFactory) + { + IUnknown *pUnknown = pFactory->m_spObj; + pFactory->m_spObj = NULL; + if (pUnknown) + pUnknown->Release(); + } + + cRefs = VirtualBoxClientWrap::InternalRelease(); + } + } +# endif + return cRefs; +} +#endif + diff --git a/src/VBox/Main/src-client/win/precomp_vcc.h b/src/VBox/Main/src-client/win/precomp_vcc.h new file mode 100644 index 00000000..f77c87c2 --- /dev/null +++ b/src/VBox/Main/src-client/win/precomp_vcc.h @@ -0,0 +1,49 @@ +/* $Id: precomp_vcc.h $ */ +/** @file + * VirtualBox COM - Visual C++ precompiled header for VBoxC. + */ + +/* + * Copyright (C) 2006-2022 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 <iprt/cdefs.h> +#include <iprt/win/winsock2.h> +#include <iprt/win/windows.h> +#include <VBox/cdefs.h> +#include <iprt/types.h> +#include <iprt/cpp/list.h> +#include <iprt/cpp/meta.h> +#include <iprt/cpp/ministring.h> +#include <VBox/com/microatl.h> +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/Guid.h> +#include <VBox/com/string.h> + +#include "VBox/com/VirtualBox.h" + +#if defined(Log) || defined(LogIsEnabled) +# error "Log() from iprt/log.h cannot be defined in the precompiled header!" +#endif + diff --git a/src/VBox/Main/src-client/xpcom/Makefile.kup b/src/VBox/Main/src-client/xpcom/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Main/src-client/xpcom/Makefile.kup diff --git a/src/VBox/Main/src-client/xpcom/module.cpp b/src/VBox/Main/src-client/xpcom/module.cpp new file mode 100644 index 00000000..5e0748d2 --- /dev/null +++ b/src/VBox/Main/src-client/xpcom/module.cpp @@ -0,0 +1,159 @@ +/* $Id: module.cpp $ */ +/** @file + * XPCOM module implementation functions + */ + +/* + * Copyright (C) 2006-2022 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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN + +/* Make sure all the stdint.h macros are included - must come first! */ +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS +#endif + +#include <nsIGenericFactory.h> + +// generated file +#include <VBox/com/VirtualBox.h> + +#include "SessionImpl.h" +#include "VirtualBoxClientImpl.h" +#include "RemoteUSBDeviceImpl.h" +#include "USBDeviceImpl.h" + +// XPCOM glue code unfolding + +/* + * Declare extern variables here to tell the compiler that + * NS_DECL_CLASSINFO(SessionWrap) + * already exists in the VBoxAPIWrap library. + */ +NS_DECL_CI_INTERFACE_GETTER(SessionWrap) +extern nsIClassInfo *NS_CLASSINFO_NAME(SessionWrap); + +/* + * Declare extern variables here to tell the compiler that + * NS_DECL_CLASSINFO(VirtualBoxClientWrap) + * already exists in the VBoxAPIWrap library. + */ +NS_DECL_CI_INTERFACE_GETTER(VirtualBoxClientWrap) +extern nsIClassInfo *NS_CLASSINFO_NAME(VirtualBoxClientWrap); + +/** + * Singleton class factory that holds a reference to the created instance + * (preventing it from being destroyed) until the module is explicitly + * unloaded by the XPCOM shutdown code. + * + * Suitable for IN-PROC components. + */ +class VirtualBoxClientClassFactory : public VirtualBoxClient +{ +public: + virtual ~VirtualBoxClientClassFactory() + { + FinalRelease(); + instance = 0; + } + + static nsresult GetInstance(VirtualBoxClient **inst) + { + int rv = NS_OK; + if (instance == 0) + { + instance = new VirtualBoxClientClassFactory(); + if (instance) + { + instance->AddRef(); // protect FinalConstruct() + rv = instance->FinalConstruct(); + if (NS_FAILED(rv)) + instance->Release(); + else + instance->AddRef(); // self-reference + } + else + { + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + else + { + instance->AddRef(); + } + *inst = instance; + return rv; + } + + static nsresult FactoryDestructor() + { + if (instance) + instance->Release(); + return NS_OK; + } + +private: + static VirtualBoxClient *instance; +}; + +VirtualBoxClient *VirtualBoxClientClassFactory::instance = nsnull; + + +NS_GENERIC_FACTORY_CONSTRUCTOR_WITH_RC(Session) + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR_WITH_RC(VirtualBoxClient, VirtualBoxClientClassFactory::GetInstance) + +/** + * Component definition table. + * Lists all components defined in this module. + */ +static const nsModuleComponentInfo components[] = +{ + { + "Session component", // description + NS_SESSION_CID, NS_SESSION_CONTRACTID, // CID/ContractID + SessionConstructor, // constructor function + NULL, // registration function + NULL, // deregistration function + NULL, // destructor function + NS_CI_INTERFACE_GETTER_NAME(SessionWrap), // interfaces function + NULL, // language helper + &NS_CLASSINFO_NAME(SessionWrap) // global class info & flags + }, + { + "VirtualBoxClient component", // description + NS_VIRTUALBOXCLIENT_CID, NS_VIRTUALBOXCLIENT_CONTRACTID, // CID/ContractID + VirtualBoxClientConstructor, // constructor function + NULL, // registration function + NULL, // deregistration function + VirtualBoxClientClassFactory::FactoryDestructor, // destructor function + NS_CI_INTERFACE_GETTER_NAME(VirtualBoxClientWrap), // interfaces function + NULL, // language helper + &NS_CLASSINFO_NAME(VirtualBoxClientWrap) // global class info & flags + }, +}; + +NS_IMPL_NSGETMODULE (VirtualBox_Client_Module, components) +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ diff --git a/src/VBox/Main/src-client/xpcom/precomp_gcc.h b/src/VBox/Main/src-client/xpcom/precomp_gcc.h new file mode 100644 index 00000000..ce01deb8 --- /dev/null +++ b/src/VBox/Main/src-client/xpcom/precomp_gcc.h @@ -0,0 +1,53 @@ +/* $Id: precomp_gcc.h $ */ +/** @file + * VirtualBox COM - GNU C++ precompiled header for VBoxC. + */ + +/* + * Copyright (C) 2006-2022 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 <iprt/cdefs.h> +#include <VBox/cdefs.h> +#include <iprt/types.h> +#include <iprt/cpp/list.h> +#include <iprt/cpp/meta.h> +#include <iprt/cpp/ministring.h> +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/Guid.h> +#include <VBox/com/string.h> +#include <VBox/com/VirtualBox.h> + +#if 1 +# include "VirtualBoxBase.h" +# include <new> +# include <list> +# include <map> +# include <array> +# include <errno.h> +#endif + +#if defined(Log) || defined(LogIsEnabled) +# error "Log() from iprt/log.h cannot be defined in the precompiled header!" +#endif + |