diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Main/src-client | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-client')
61 files changed, 71690 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..2ad50e7e --- /dev/null +++ b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp @@ -0,0 +1,219 @@ +/* $Id: AdditionsFacilityImpl.cpp $ */ +/** @file + * + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_ADDITIONSFACILITY +#include "LoggingNew.h" + +#include "AdditionsFacilityImpl.h" +#include "Global.h" + +#include "AutoCaller.h" + + +/* static */ +const AdditionsFacility::FacilityInfo AdditionsFacility::s_aFacilityInfo[8] = +{ + /* NOTE: We assume that unknown is always the first entry! */ + { "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); + LogFlowThisFunc(("a_pParent=%p\n", a_pParent)); + + /* Enclose the state transition NotReady->InInit->Ready. */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + FacilityState state; + state.mStatus = a_enmStatus; + state.mTimestamp = *a_pTimeSpecTS; + NOREF(a_fFlags); + + Assert(mData.mStates.size() == 0); + mData.mStates.push_back(state); + mData.mType = a_enmFacility; + /** @todo mClass is not initialized here. */ + NOREF(a_fFlags); + + /* 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; + + mData.mStates.clear(); +} + +HRESULT AdditionsFacility::getClassType(AdditionsFacilityClass_T *aClassType) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aClassType = i_getClass(); + + return S_OK; +} + +HRESULT AdditionsFacility::getName(com::Utf8Str &aName) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aName = i_getName(); + + return S_OK; +} + +HRESULT AdditionsFacility::getLastUpdated(LONG64 *aLastUpdated) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aLastUpdated = i_getLastUpdated(); + + return S_OK; +} + +HRESULT AdditionsFacility::getStatus(AdditionsFacilityStatus_T *aStatus) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aStatus = i_getStatus(); + + return S_OK; +} + +HRESULT AdditionsFacility::getType(AdditionsFacilityType_T *aType) +{ + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aType = i_getType(); + + return S_OK; +} + +const AdditionsFacility::FacilityInfo &AdditionsFacility::i_typeToInfo(AdditionsFacilityType_T aType) +{ + for (size_t i = 0; i < RT_ELEMENTS(s_aFacilityInfo); ++i) + { + if (s_aFacilityInfo[i].mType == aType) + return s_aFacilityInfo[i]; + } + return s_aFacilityInfo[0]; /* Return unknown type. */ +} + +AdditionsFacilityClass_T AdditionsFacility::i_getClass() const +{ + return AdditionsFacility::i_typeToInfo(mData.mType).mClass; +} + +com::Utf8Str AdditionsFacility::i_getName() const +{ + return AdditionsFacility::i_typeToInfo(mData.mType).mName; +} + +LONG64 AdditionsFacility::i_getLastUpdated() const +{ + if (mData.mStates.size()) + return RTTimeSpecGetMilli(&mData.mStates.front().mTimestamp); + + AssertMsgFailed(("Unknown timestamp of facility!\n")); + return 0; /* Should never happen! */ +} + +AdditionsFacilityStatus_T AdditionsFacility::i_getStatus() const +{ + if (mData.mStates.size()) + return mData.mStates.back().mStatus; + + AssertMsgFailed(("Unknown status of facility!\n")); + return AdditionsFacilityStatus_Unknown; /* Should never happen! */ +} + +AdditionsFacilityType_T AdditionsFacility::i_getType() const +{ + return mData.mType; +} + +/** + * Method used by IGuest::facilityUpdate to make updates. + */ +void AdditionsFacility::i_update(AdditionsFacilityStatus_T a_enmStatus, uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + FacilityState state; + state.mStatus = a_enmStatus; + state.mTimestamp = *a_pTimeSpecTS; + NOREF(a_fFlags); + + mData.mStates.push_back(state); + if (mData.mStates.size() > 10) /* Only keep the last 10 states. */ + mData.mStates.erase(mData.mStates.begin()); +} + diff --git a/src/VBox/Main/src-client/AudioDriver.cpp b/src/VBox/Main/src-client/AudioDriver.cpp new file mode 100644 index 00000000..2e2dd563 --- /dev/null +++ b/src/VBox/Main/src-client/AudioDriver.cpp @@ -0,0 +1,315 @@ +/* $Id: AudioDriver.cpp $ */ +/** @file + * VirtualBox audio base class for Main audio drivers. + */ + +/* + * Copyright (C) 2018-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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 "AudioDriver.h" +#include "ConsoleImpl.h" + +AudioDriver::AudioDriver(Console *pConsole) + : mpConsole(pConsole) + , mfAttached(false) +{ +} + +AudioDriver::~AudioDriver(void) +{ +} + + +/** + * 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 IPRT status code. + * @param pUVM The user mode VM handle for talking to EMT. + * @param pAutoLock The callers auto lock instance. Can be NULL if + * not locked. + */ +int AudioDriver::doAttachDriverViaEmt(PUVM pUVM, util::AutoWriteLock *pAutoLock) +{ + if (!isConfigured()) + return VINF_SUCCESS; + + PVMREQ pReq; + int vrc = VMR3ReqCallU(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 = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + + if (pAutoLock) + pAutoLock->acquire(); + } + + AssertRC(vrc); + VMR3ReqFree(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 rc = PDMR3DeviceDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + rc = pThis->configure(pCfg->uLUN, true /* Attach */); + if (RT_SUCCESS(rc)) + rc = PDMR3DriverAttach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, 0 /* fFlags */, + NULL /* ppBase */); + } + + if (RT_SUCCESS(rc)) + { + 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(), rc)); + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * Detatches the driver via EMT, if configured. + * + * @returns IPRT status code. + * @param pUVM The user mode VM handle for talking to EMT. + * @param pAutoLock The callers auto lock instance. Can be NULL if + * not locked. + */ +int AudioDriver::doDetachDriverViaEmt(PUVM pUVM, util::AutoWriteLock *pAutoLock) +{ + if (!isConfigured()) + return VINF_SUCCESS; + + PVMREQ pReq; + int vrc = VMR3ReqCallU(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 = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + + if (pAutoLock) + pAutoLock->acquire(); + } + + AssertRC(vrc); + VMR3ReqFree(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 rc = PDMR3DriverDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, + "AUDIO", 0 /* iOccurrence */, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = pThis->configure(pCfg->uLUN, false /* Detach */); + + if (RT_SUCCESS(rc)) + { + pThis->mfAttached = false; + LogRel2(("%s: Driver detached\n", pCfg->strName.c_str())); + } + else + LogRel(("%s: Failed to detach audio driver, rc=%Rrc\n", pCfg->strName.c_str(), rc)); + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + +/** + * 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); + Assert(ptrVM.isOk()); + + PUVM pUVM = ptrVM.rawUVM(); + AssertPtr(pUVM); + + PCFGMNODE pRoot = CFGMR3GetRootU(pUVM); + AssertPtr(pRoot); + PCFGMNODE pDev0 = CFGMR3GetChildF(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 rc = VINF_SUCCESS; + + PCFGMNODE pDevLun = CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN); + + if (fAttach) + { + do + { + AssertMsgBreakStmt(pDevLun, ("%s: Device LUN #%u not found\n", mCfg.strName.c_str(), uLUN), rc = VERR_NOT_FOUND); + + LogRel2(("%s: Configuring audio driver (to LUN #%u)\n", mCfg.strName.c_str(), uLUN)); + + CFGMR3RemoveNode(pDevLun); /* Remove LUN completely first. */ + + /* Insert new LUN configuration and build up the new driver chain. */ + rc = CFGMR3InsertNodeF(pDev0, &pDevLun, "LUN#%u/", uLUN); AssertRCBreak(rc); + rc = CFGMR3InsertString(pDevLun, "Driver", "AUDIO"); AssertRCBreak(rc); + + PCFGMNODE pLunCfg; + rc = CFGMR3InsertNode(pDevLun, "Config", &pLunCfg); AssertRCBreak(rc); + + rc = CFGMR3InsertStringF(pLunCfg, "DriverName", "%s", mCfg.strName.c_str()); AssertRCBreak(rc); + + rc = CFGMR3InsertInteger(pLunCfg, "InputEnabled", 0); /* Play safe by default. */ AssertRCBreak(rc); + rc = CFGMR3InsertInteger(pLunCfg, "OutputEnabled", 1); AssertRCBreak(rc); + + PCFGMNODE pAttachedDriver, pAttachedDriverCfg; + rc = CFGMR3InsertNode(pDevLun, "AttachedDriver", &pAttachedDriver); AssertRCBreak(rc); + rc = CFGMR3InsertStringF(pAttachedDriver, "Driver", "%s", mCfg.strName.c_str()); AssertRCBreak(rc); + rc = CFGMR3InsertNode(pAttachedDriver, "Config", &pAttachedDriverCfg); AssertRCBreak(rc); + + /* Call the (virtual) method for driver-specific configuration. */ + rc = configureDriver(pAttachedDriverCfg); AssertRCBreak(rc); + + } while (0); + } + else /* Detach */ + { + LogRel2(("%s: Unconfiguring audio driver\n", mCfg.strName.c_str())); + } + +#ifdef LOG_ENABLED + LogFunc(("%s: fAttach=%RTbool\n", mCfg.strName.c_str(), fAttach)); + CFGMR3Dump(pDevLun); +#endif + + if (RT_FAILURE(rc)) + LogRel(("%s: %s audio driver failed with rc=%Rrc\n", + mCfg.strName.c_str(), fAttach ? "Configuring" : "Unconfiguring", rc)); + + LogFunc(("Returning %Rrc\n", rc)); + return rc; +} + diff --git a/src/VBox/Main/src-client/BusAssignmentManager.cpp b/src/VBox/Main/src-client/BusAssignmentManager.cpp new file mode 100644 index 00000000..eb94c526 --- /dev/null +++ b/src/VBox/Main/src-client/BusAssignmentManager.cpp @@ -0,0 +1,577 @@ +/* $Id: BusAssignmentManager.cpp $ */ +/** @file + * VirtualBox bus slots assignment manager + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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/com/array.h> + +#include <map> +#include <vector> +#include <algorithm> + +struct DeviceAssignmentRule +{ + const char *pszName; + int iBus; + int iDevice; + int iFn; + int iPriority; +}; + +struct DeviceAliasRule +{ + const char *pszDevName; + const char *pszDevAlias; +}; + +/* 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 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 */ + {"lsilogic", 0, 20, 0, 1}, + {"buslogic", 0, 21, 0, 1}, + {"lsilogicsas", 0, 22, 0, 1}, + {"nvme", 0, 14, 0, 1}, + + /* USB controllers */ + {"usb-ohci", 0, 6, 0, 0}, + {"usb-ehci", 0, 11, 0, 0}, + {"usb-xhci", 0, 12, 0, 0}, + + /* ACPI controller */ + {"acpi", 0, 7, 0, 0}, + + /* 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 aPiix3Rules[] = +{ + {"piix3ide", 0, 1, 1, 0}, + {"ahci", 0, 13, 0, 1}, + {"pcibridge", 0, 24, 0, 0}, + {"pcibridge", 0, 25, 0, 0}, + { NULL, -1, -1, -1, 0} +}; + + +/* ICH9 chipset rules */ +static const DeviceAssignmentRule 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) */ + {"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}, + + { NULL, -1, -1, -1, 0} +}; + +/* Aliasing rules */ +static const DeviceAliasRule aDeviceAliases[] = +{ + {"e1000", "nic"}, + {"pcnet", "nic"}, + {"virtio-net", "nic"}, + {"ahci", "storage"}, + {"lsilogic", "storage"}, + {"buslogic", "storage"}, + {"lsilogicsas", "storage"}, + {"nvme", "storage"} +}; + +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; + PCIMap mPCIMap; + ReversePCIMap mReversePCIMap; + + State() + : cRefCnt(1), mChipsetType(ChipsetType_Null), mpszBridgeName("unknownbridge") + {} + ~State() + {} + + HRESULT init(ChipsetType_T chipsetType); + + 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(ChipsetType_T chipsetType) +{ + mChipsetType = chipsetType; + 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[2] = {aGenericRules, NULL}; + + switch (mChipsetType) + { + case ChipsetType_PIIX3: + aArrays[1] = aPiix3Rules; + break; + case ChipsetType_ICH9: + aArrays[1] = aIch9Rules; + 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(aDeviceAliases); iAlias++) + { + if (strcmp(pszDev, aDeviceAliases[iAlias].pszDevName) == 0) + return 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(ChipsetType_T chipsetType) +{ + BusAssignmentManager *pInstance = new BusAssignmentManager(); + pInstance->pState->init(chipsetType); + Assert(pInstance); + return pInstance; +} + +void BusAssignmentManager::AddRef() +{ + ASMAtomicIncS32(&pState->cRefCnt); +} +void BusAssignmentManager::Release() +{ + if (ASMAtomicDecS32(&pState->cRefCnt) == 0) + delete this; +} + +DECLINLINE(HRESULT) InsertConfigInteger(PCFGMNODE pCfg, const char *pszName, uint64_t u64) +{ + int vrc = CFGMR3InsertInteger(pCfg, pszName, u64); + if (RT_FAILURE(vrc)) + return E_INVALIDARG; + + return S_OK; +} + +DECLINLINE(HRESULT) InsertConfigNode(PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild) +{ + int vrc = CFGMR3InsertNode(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 rc = S_OK; + + if (!GuestAddress.valid()) + rc = pState->autoAssign(pszDevName, GuestAddress); + else + { + bool fAvailable = pState->checkAvailable(GuestAddress); + + if (!fAvailable) + { + if (fGuestAddressRequired) + rc = E_ACCESSDENIED; + else + rc = pState->autoAssign(pszDevName, GuestAddress); + } + } + + if (FAILED(rc)) + return rc; + + Assert(GuestAddress.valid() && pState->checkAvailable(GuestAddress)); + + rc = pState->record(pszDevName, GuestAddress, HostAddress); + if (FAILED(rc)) + return rc; + + rc = InsertConfigInteger(pCfg, "PCIBusNo", GuestAddress.miBus); + if (FAILED(rc)) + return rc; + rc = InsertConfigInteger(pCfg, "PCIDeviceNo", GuestAddress.miDevice); + if (FAILED(rc)) + return rc; + rc = InsertConfigInteger(pCfg, "PCIFunctionNo", GuestAddress.miFn); + if (FAILED(rc)) + return rc; + + /* 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 = CFGMR3GetParent(CFGMR3GetParent(pCfg)); + AssertLogRelMsgReturn(pDevices, ("BusAssignmentManager: cannot find base device configuration\n"), E_UNEXPECTED); + PCFGMNODE pBridges = CFGMR3GetChild(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; + rc = pState->autoAssign(pState->mpszBridgeName, BridgeGuestAddress); + if (FAILED(rc)) + return rc; + 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(pBridges, Utf8StrFmt("%d", iBridge).c_str(), &pInst); + InsertConfigInteger(pInst, "Trusted", 1); + rc = assignPCIDevice(pState->mpszBridgeName, pInst); + if (FAILED(rc)) + return rc; + } + } + } + + 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..818e7fdd --- /dev/null +++ b/src/VBox/Main/src-client/ClientTokenHolder.cpp @@ -0,0 +1,339 @@ +/* $Id: ClientTokenHolder.cpp $ */ +/** @file + * + * VirtualBox API client session token holder (in the client process) + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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; + + Bstr bstrTokenId(strTokenId); + + /* + * 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*)(BSTR)bstrTokenId.raw(); + 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) + Bstr bstrTokenId(strTokenId); + + /* + * 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*)bstrTokenId.raw(); + 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) + BSTR sessionId = (BSTR)data[0]; + HANDLE initDoneSem = (HANDLE)data[1]; + + HANDLE mutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, sessionId); + AssertMsg(mutex, ("cannot open token, err=%d\n", ::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 sessionId = (BSTR)data[0]; + RTSEMEVENT finishSem = (RTSEMEVENT)data[1]; + + LogFlowFunc(("sessionId='%s', finishSem=%p\n", sessionId.raw(), finishSem)); + + HMTX mutex = NULLHANDLE; + APIRET arc = ::DosOpenMutexSem((PSZ)sessionId.raw(), &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/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp new file mode 100644 index 00000000..c0835c3d --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImpl.cpp @@ -0,0 +1,11112 @@ +/* $Id: ConsoleImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation + */ + +/* + * Copyright (C) 2005-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 +#include "Nvram.h" +#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 "VBoxEvents.h" +#include "AutoCaller.h" +#include "ThreadTask.h" + +#ifdef VBOX_WITH_RECORDING +# include "Recording.h" +#endif + +#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/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/ftm.h> +#include <VBox/vmm/ssm.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/vusb.h> + +#include <VBox/VMMDev.h> + +#include <VBox/HostServices/VBoxClipboardSvc.h> +#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 */), + mConfigConstructor(NULL), + mStartPaused(false), + mTeleporterEnabled(FALSE), + mEnmFaultToleranceState(FaultToleranceState_Inactive) + { + m_strTaskName = "VMPwrUp"; + } + + PFNCFGMCONSTRUCTOR mConfigConstructor; + Utf8Str mSavedStateFile; + Console::SharedFolderDataMap mSharedFolders; + bool mStartPaused; + BOOL mTeleporterEnabled; + FaultToleranceState_T mEnmFaultToleranceState; + + /* 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: + { + Bstr id; + ComPtr<IMachine> pMachine = mConsole->i_machine(); + ComPtr<INATRedirectEvent> pNREv = aEvent; + HRESULT rc = E_FAIL; + Assert(pNREv); + + rc = pNREv->COMGETTER(MachineId)(id.asOutParam()); + AssertComRC(rc); + if (id != mConsole->i_getId()) + break; + /* now we can operate with redirects */ + NATProtocol_T proto; + pNREv->COMGETTER(Proto)(&proto); + BOOL fRemove; + pNREv->COMGETTER(Remove)(&fRemove); + Bstr hostIp, guestIp; + LONG hostPort, guestPort; + pNREv->COMGETTER(HostIP)(hostIp.asOutParam()); + pNREv->COMGETTER(HostPort)(&hostPort); + pNREv->COMGETTER(GuestIP)(guestIp.asOutParam()); + pNREv->COMGETTER(GuestPort)(&guestPort); + ULONG ulSlot; + rc = pNREv->COMGETTER(Slot)(&ulSlot); + AssertComRC(rc); + if (FAILED(rc)) + break; + mConsole->i_onNATRedirectRuleChange(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; + Bstr strKey; + Bstr strVal; + HRESULT hrc = S_OK; + + hrc = pEDCEv->COMGETTER(MachineId)(strMachineId.asOutParam()); + if (FAILED(hrc)) break; + + hrc = pEDCEv->COMGETTER(Key)(strKey.asOutParam()); + if (FAILED(hrc)) break; + + 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) + , mpUVM(NULL) + , mVMCallers(0) + , mVMZeroCallersSem(NIL_RTSEMEVENT) + , mVMDestroying(false) + , mVMPoweredOff(false) + , mVMIsAlreadyPoweringOff(false) + , mfSnapshotFolderSizeWarningShown(false) + , mfSnapshotFolderExt4WarningShown(false) + , mfSnapshotFolderDiskTypeShown(false) + , mfVMHasUsbController(false) + , mfPowerOffCausedByReset(false) + , mpVmm2UserMethods(NULL) + , m_pVMMDev(NULL) + , mAudioVRDE(NULL) + , mNvram(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) +{ +} + +Console::~Console() +{} + +HRESULT Console::FinalConstruct() +{ + LogFlowThisFunc(("\n")); + + RT_ZERO(mapStorageLeds); + RT_ZERO(mapNetworkLeds); + RT_ZERO(mapUSBLed); + RT_ZERO(mapSharedFolderLed); + RT_ZERO(mapCrOglLed); + + for (unsigned i = 0; i < RT_ELEMENTS(maStorageDevType); ++i) + maStorageDevType[i] = DeviceType_Null; + + 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; + + return BaseFinalConstruct(); +} + +void Console::FinalRelease() +{ + LogFlowThisFunc(("\n")); + + uninit(); + + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +HRESULT Console::init(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 */ + if (aLockType == LockType_VM) + { + 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); + + /* 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(Recording.mAudioRec) = new AudioVideoRec(this); + AssertReturn(Recording.mAudioRec, E_FAIL); +#endif + FirmwareType_T enmFirmwareType; + mMachine->COMGETTER(FirmwareType)(&enmFirmwareType); + if ( enmFirmwareType == FirmwareType_EFI + || enmFirmwareType == FirmwareType_EFI32 + || enmFirmwareType == FirmwareType_EFI64 + || enmFirmwareType == FirmwareType_EFIDUAL) + { + unconst(mNvram) = new Nvram(this); + AssertReturn(mNvram, E_FAIL); + } + +#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; + } + + if (mNvram) + { + delete mNvram; + unconst(mNvram) = 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 (Recording.mAudioRec) + { + delete Recording.mAudioRec; + unconst(Recording.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; + } + + 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 + + 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 */ + +bool Console::i_isResetTurnedIntoPowerOff(void) +{ + Bstr value; + HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/TurnResetIntoPowerOff").raw(), + value.asOutParam()); + if ( hrc == S_OK + && value == "1") + return true; + return false; +} + +#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_VideoAccelVRDP(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_VideoAccelVRDP(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: + 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 + 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 || mSavedStateDataLoaded) + return S_OK; + + Bstr savedStateFile; + HRESULT rc = mMachine->COMGETTER(StateFilePath)(savedStateFile.asOutParam()); + if (FAILED(rc)) + return rc; + + PSSMHANDLE ssm; + int vrc = SSMR3Open(Utf8Str(savedStateFile).c_str(), 0, &ssm); + if (RT_SUCCESS(vrc)) + { + uint32_t version = 0; + vrc = SSMR3Seek(ssm, sSSMConsoleUnit, 0 /* iInstance */, &version); + if (SSM_VERSION_MAJOR(version) == SSM_VERSION_MAJOR(CONSOLE_SAVED_STATE_VERSION)) + { + if (RT_SUCCESS(vrc)) + vrc = i_loadStateFileExecInternal(ssm, version); + else if (vrc == VERR_SSM_UNIT_NOT_FOUND) + vrc = VINF_SUCCESS; + } + else + vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + SSMR3Close(ssm); + } + + if (RT_FAILURE(vrc)) + rc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("The saved state file '%ls' is invalid (%Rrc). Delete the saved state and try again"), + savedStateFile.raw(), vrc); + + mSavedStateDataLoaded = true; + + return rc; +} + +/** + * Callback handler to save various console data to the state file, + * called when the user saves the VM state. + * + * @param pSSM SSM handle. + * @param pvUser pointer to Console + * + * @note Locks the Console object for reading. + */ +//static +DECLCALLBACK(void) Console::i_saveStateFileExec(PSSMHANDLE pSSM, void *pvUser) +{ + LogFlowFunc(("\n")); + + Console *that = static_cast<Console *>(pvUser); + AssertReturnVoid(that); + + AutoCaller autoCaller(that); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoReadLock alock(that COMMA_LOCKVAL_SRC_POS); + + SSMR3PutU32(pSSM, (uint32_t)that->m_mapSharedFolders.size()); + + for (SharedFolderMap::const_iterator it = that->m_mapSharedFolders.begin(); + it != that->m_mapSharedFolders.end(); + ++it) + { + SharedFolder *pSF = (*it).second; + AutoCaller sfCaller(pSF); + AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS); + + const Utf8Str &name = pSF->i_getName(); + SSMR3PutU32(pSSM, (uint32_t)name.length() + 1 /* term. 0 */); + SSMR3PutStrZ(pSSM, name.c_str()); + + const Utf8Str &hostPath = pSF->i_getHostPath(); + SSMR3PutU32(pSSM, (uint32_t)hostPath.length() + 1 /* term. 0 */); + SSMR3PutStrZ(pSSM, hostPath.c_str()); + + SSMR3PutBool(pSSM, !!pSF->i_isWritable()); + SSMR3PutBool(pSSM, !!pSF->i_isAutoMounted()); + + const Utf8Str &rStrAutoMountPoint = pSF->i_getAutoMountPoint(); + SSMR3PutU32(pSSM, (uint32_t)rStrAutoMountPoint.length() + 1 /* term. 0 */); + SSMR3PutStrZ(pSSM, rStrAutoMountPoint.c_str()); + } +} + +/** + * Callback handler to load various console data from the state file. + * Called when the VM is being restored from the saved state. + * + * @param pSSM SSM handle. + * @param pvUser pointer to Console + * @param uVersion Console unit version. + * Should match sSSMConsoleVer. + * @param uPass The data pass. + * + * @note Should locks the Console object for writing, if necessary. + */ +//static +DECLCALLBACK(int) +Console::i_loadStateFileExec(PSSMHANDLE pSSM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + LogFlowFunc(("\n")); + + if (SSM_VERSION_MAJOR_CHANGED(uVersion, CONSOLE_SAVED_STATE_VERSION)) + return VERR_VERSION_MISMATCH; + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + Console *that = static_cast<Console *>(pvUser); + AssertReturn(that, VERR_INVALID_PARAMETER); + + /* Currently, nothing to do when we've been called from VMR3Load*. */ + return SSMR3SkipToEndOfUnit(pSSM); +} + +/** + * Method to load various console data from the state file. + * Called from #i_loadDataFromSavedState. + * + * @param pSSM SSM handle. + * @param u32Version Console unit version. + * Should match sSSMConsoleVer. + * + * @note Locks the Console object for writing. + */ +int Console::i_loadStateFileExecInternal(PSSMHANDLE pSSM, 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 = SSMR3GetU32(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 = SSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + buf = new char[cbStr]; + vrc = SSMR3GetStrZ(pSSM, buf, cbStr); + AssertRC(vrc); + strName = buf; + delete[] buf; + + vrc = SSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + buf = new char[cbStr]; + vrc = SSMR3GetStrZ(pSSM, buf, cbStr); + AssertRC(vrc); + strHostPath = buf; + delete[] buf; + + if (u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT) + SSMR3GetBool(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). */ + && SSMR3HandleRevision(pSSM) >= 63916 +#endif + ) + SSMR3GetBool(pSSM, &autoMount); + + Utf8Str strAutoMountPoint; + if (u32Version >= CONSOLE_SAVED_STATE_VERSION) + { + vrc = SSMR3GetU32(pSSM, &cbStr); + AssertRCReturn(vrc, vrc); + vrc = strAutoMountPoint.reserveNoThrow(cbStr); + AssertRCReturn(vrc, vrc); + vrc = SSMR3GetStrZ(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); + ComObjPtr<Console> pConsole = reinterpret_cast<Console *>(pvExtension); + HRESULT hrc = pConsole->mControl->PushGuestProperty(name.raw(), + value.raw(), + pCBData->u64Timestamp, + flags.raw()); + if (SUCCEEDED(hrc)) + { + fireGuestPropertyChangedEvent(pConsole->mEventSource, pConsole->i_getId().raw(), name.raw(), value.raw(), flags.raw()); + 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) +{ + mfUseHostClipboard = !!aUseHostClipboard; + + 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")); + + /* Try cancel the FT sync. */ + case MachineState_FaultTolerantSyncing: + 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 fault tolerant sync")); + + /* extra nice error message for a common case */ + case MachineState_Saved: + 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; + + HRESULT rc = S_OK; + bool fBeganPowerDown = false; + VMPowerDownTask* task = NULL; + + do + { + ComPtr<IProgress> pProgress; + +#ifdef VBOX_WITH_GUEST_PROPS + alock.release(); + + if (i_isResetTurnedIntoPowerOff()) + { + 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) + */ + rc = mControl->BeginPoweringDown(pProgress.asOutParam()); + if (FAILED(rc)) + break; + + fBeganPowerDown = true; + + /* sync the state with the server */ + i_setMachineStateLocally(MachineState_Stopping); + try + { + task = new VMPowerDownTask(this, pProgress); + if (!task->isOk()) + { + throw E_FAIL; + } + } + catch(...) + { + delete task; + rc = setError(E_FAIL, "Could not create VMPowerDownTask object\n"); + break; + } + + rc = task->createThread(); + + /* pass the progress to the caller */ + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + while (0); + + if (FAILED(rc)) + { + /* preserve existing error info */ + ErrorInfoKeeper eik; + + if (fBeganPowerDown) + { + /* + * cancel the requested power down procedure. + * This will reset the machine state to the state it had right + * before calling mControl->BeginPoweringDown(). + */ + mControl->EndPoweringDown(eik.getResultCode(), eik.getText().raw()); } + + i_setMachineStateLocally(lastMachineState); + } + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + + return rc; +} + +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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + int vrc = VMR3Reset(ptrVM.rawUVM()); + + HRESULT rc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not reset the machine (%Rrc)"), vrc); + + LogFlowThisFunc(("mMachineState=%d, rc=%Rhrc\n", mMachineState, rc)); + LogFlowThisFuncLeave(); + return rc; +} + +/*static*/ DECLCALLBACK(int) Console::i_unplugCpu(Console *pThis, PUVM pUVM, VMCPUID idCpu) +{ + LogFlowFunc(("pThis=%p pVM=%p idCpu=%u\n", pThis, pUVM, idCpu)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + int vrc = PDMR3DeviceDetach(pUVM, "acpi", 0, idCpu, 0); + Log(("UnplugCpu: rc=%Rrc\n", vrc)); + + return vrc; +} + +HRESULT Console::i_doCPURemove(ULONG aCpu, PUVM pUVM) +{ + 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 = PDMR3QueryDeviceLun(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 = VMR3GetCpuCoreAndPackageIdFromCpuId(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 in EMT, that's faster and safer than doing everything + * using VMR3ReqCall. + */ + PVMREQ pReq; + vrc = VMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_unplugCpu, 3, + this, pUVM, (VMCPUID)aCpu); + + if (vrc == VERR_TIMEOUT) + vrc = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + VMR3ReqFree(pReq); + + if (RT_SUCCESS(vrc)) + { + /* Detach it from the VM */ + vrc = VMR3HotUnplugCpu(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, VMCPUID idCpu) +{ + LogFlowFunc(("pThis=%p uCpu=%u\n", pThis, idCpu)); + + AssertReturn(pThis, VERR_INVALID_PARAMETER); + + int rc = VMR3HotPlugCpu(pUVM, idCpu); + AssertRC(rc); + + PCFGMNODE pInst = CFGMR3GetChild(CFGMR3GetRootU(pUVM), "Devices/acpi/0/"); + AssertRelease(pInst); + /* nuke anything which might have been left behind. */ + CFGMR3RemoveNode(CFGMR3GetChildF(pInst, "LUN#%u", idCpu)); + +#define RC_CHECK() do { if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; } } while (0) + + PCFGMNODE pLunL0; + PCFGMNODE pCfg; + rc = CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%u", idCpu); RC_CHECK(); + rc = CFGMR3InsertString(pLunL0, "Driver", "ACPICpu"); RC_CHECK(); + rc = CFGMR3InsertNode(pLunL0, "Config", &pCfg); RC_CHECK(); + + /* + * Attach the driver. + */ + PPDMIBASE pBase; + rc = PDMR3DeviceAttach(pUVM, "acpi", 0, idCpu, 0, &pBase); RC_CHECK(); + + Log(("PlugCpu: rc=%Rrc\n", rc)); + + CFGMR3Dump(pInst); + +#undef RC_CHECK + + return VINF_SUCCESS; +} + +HRESULT Console::i_doCPUAdd(ULONG aCpu, PUVM pUVM) +{ + 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 in EMT, 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 = VMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_plugCpu, 3, + this, pUVM, aCpu); + + /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */ + alock.release(); + + if (vrc == VERR_TIMEOUT) + vrc = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + VMR3ReqFree(pReq); + + if (RT_SUCCESS(vrc)) + { + /* Notify the guest if possible. */ + uint32_t idCpuCore, idCpuPackage; + vrc = VMR3GetCpuCoreAndPackageIdFromCpuId(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); + 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 button. */ + PPDMIBASE pBase; + int vrc = PDMR3QueryDeviceLun(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; + } + + HRESULT rc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Controlled power off failed (%Rrc)"), vrc); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + // 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 = PDMR3QueryDeviceLun(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; + } + + HRESULT rc = 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(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + // 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 = PDMR3QueryDeviceLun(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 S_OK; +} + +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); + 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 = PDMR3QueryDeviceLun(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; + } + + HRESULT rc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending sleep button event failed (%Rrc)"), vrc); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +/** read the value of a LED. */ +inline 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. + */ + + aActivity.resize(aType.size()); + + size_t iType; + for (iType = 0; iType < aType.size(); ++iType) + { + /* Get LED array to read */ + PDMLEDCORE SumLed = {0}; + switch (aType[iType]) + { + case DeviceType_Floppy: + case DeviceType_DVD: + case DeviceType_HardDisk: + { + for (unsigned i = 0; i < RT_ELEMENTS(mapStorageLeds); ++i) + if (maStorageDevType[i] == aType[iType]) + SumLed.u32 |= readAndClearLed(mapStorageLeds[i]); + break; + } + + case DeviceType_Network: + { + for (unsigned i = 0; i < RT_ELEMENTS(mapNetworkLeds); ++i) + SumLed.u32 |= readAndClearLed(mapNetworkLeds[i]); + break; + } + + case DeviceType_USB: + { + for (unsigned i = 0; i < RT_ELEMENTS(mapUSBLed); ++i) + SumLed.u32 |= readAndClearLed(mapUSBLed[i]); + break; + } + + case DeviceType_SharedFolder: + { + SumLed.u32 |= readAndClearLed(mapSharedFolderLed); + break; + } + + case DeviceType_Graphics3D: + { + SumLed.u32 |= readAndClearLed(mapCrOglLed); + break; + } + + default: + return setError(E_INVALIDARG, tr("Invalid device type: %d"), aType[iType]); + } + + /* Compose the result */ + switch (SumLed.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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* Don't proceed unless we have a USB controller. */ + if (!mfVMHasUsbController) + return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller")); + + /* release the lock because the USB Proxy service may call us back + * (via onUSBDeviceAttach()) */ + alock.release(); + + /* Request the device capture */ + return mControl->CaptureUSBDevice(Bstr(aId.toString()).raw(), Bstr(aCaptureFilename).raw()); + +#else /* !VBOX_WITH_USB */ + 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. */ + ComObjPtr<OUSBDevice> pUSBDevice; + USBDeviceList::iterator it = mUSBDevices.begin(); + while (it != mUSBDevices.end()) + { + if ((*it)->i_id() == aId) + { + pUSBDevice = *it; + break; + } + ++it; + } + + if (!pUSBDevice) + return setError(E_INVALIDARG, tr("USB device with UUID {%RTuuid} is not attached to this machine"), aId.raw()); + + /* 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 rc = mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), false /* aDone */); + if (FAILED(rc)) + { + /* Re-add the device to the collection */ + alock.acquire(); + mUSBDevices.push_back(pUSBDevice); + return rc; + } + + /* Request the PDM to detach the USB device. */ + rc = i_detachUSBDevice(pUSBDevice); + if (SUCCEEDED(rc)) + { + /* Request the device release. Even if it fails, the device will + * remain as held by proxy, which is OK for us (the VM process). */ + rc = mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), true /* aDone */); + } + else + { + /* Re-add the device to the collection */ + alock.acquire(); + mUSBDevices.push_back(pUSBDevice); + } + + return rc; + + +#else /* !VBOX_WITH_USB */ + 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 address; + rc = devsvec[i]->COMGETTER(Address)(address.asOutParam()); + if (FAILED(rc)) return rc; + if (address == Bstr(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 */ + 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; + + for (size_t i = 0; i < devsvec.size(); ++i) + { + Bstr id; + rc = devsvec[i]->COMGETTER(Id)(id.asOutParam()); + if (FAILED(rc)) return rc; + if (Utf8Str(id) == aId.toString()) + { + 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}"), Guid(aId).raw()); + +#else /* !VBOX_WITH_USB */ + 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) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot create a transient shared folder on the machine in the saved state")); + 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) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Cannot remove a transient shared folder from the machine in the saved state")); + 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::addDiskEncryptionPassword(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)) + { + unsigned cDisksConfigured = 0; + + hrc = i_configureEncryptionForDisk(aId, &cDisksConfigured); + if (SUCCEEDED(hrc)) + { + SecretKey *pKey = NULL; + vrc = m_pKeyStore->retainSecretKey(aId, &pKey); + AssertRCReturn(vrc, E_FAIL); + + pKey->setUsers(cDisksConfigured); + pKey->setRemoveOnSuspend(!!aClearOnSuspend); + m_pKeyStore->releaseSecretKey(aId); + 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 = VMR3Resume(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); + } + } + } + else if (vrc == VERR_ALREADY_EXISTS) + hrc = setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password with the given ID already exists")); + 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::addDiskEncryptionPasswords(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); + + /* 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")); + } + } + + for (unsigned i = 0; i < aIds.size(); i++) + { + hrc = addDiskEncryptionPassword(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]); + removeDiskEncryptionPassword(aIds[ii]); + } + + break; + } + } + + return hrc; +} + +HRESULT Console::removeDiskEncryptionPassword(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); + } + 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::clearAllDiskEncryptionPasswords() +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + 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 = setErrorInternal(aResultCode, + getStaticClassIID(), + getStaticComponentName(), + Utf8Str(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 = setErrorInternal(aResultCode, + getStaticClassIID(), + getStaticComponentName(), + Utf8Str(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)); +} + + +/* 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"; + 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: + { + 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 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, AutoWriteLock *pAlock, bool *pfResume) +{ + *pfResume = false; + VMSTATE enmVMState = VMR3GetStateU(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 = VMR3Suspend(pUVM, VMSUSPENDREASON_RECONFIG); + if (pAlock) + pAlock->acquire(); + mVMStateChangeCallbackDisabled = false; + if (RT_FAILURE(vrc)) + return setErrorInternal(VBOX_E_INVALID_VM_STATE, + COM_IIDOF(IConsole), + getStaticComponentName(), + Utf8StrFmt("Could suspend VM for medium change (%Rrc)", vrc), + false /*aWarning*/, + true /*aLogIt*/, + vrc); + *pfResume = true; + break; + } + case VMSTATE_SUSPENDED: + break; + default: + return setErrorInternal(VBOX_E_INVALID_VM_STATE, + COM_IIDOF(IConsole), + getStaticComponentName(), + Utf8StrFmt("Invalid state '%s' for changing medium", + VMR3GetStateName(enmVMState)), + false /*aWarning*/, + true /*aLogIt*/); + } + + 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. + */ +void Console::i_resumeAfterConfigChange(PUVM pUVM) +{ + LogFlowFunc(("Resuming the VM...\n")); + /* disable the callback to prevent Console-level state change */ + mVMStateChangeCallbackDisabled = true; + int rc = VMR3Resume(pUVM, VMRESUMEREASON_RECONFIG); + mVMStateChangeCallbackDisabled = false; + AssertRC(rc); + if (RT_FAILURE(rc)) + { + VMSTATE enmVMState = VMR3GetStateU(pUVM); + if (enmVMState == VMSTATE_SUSPENDED) + { + /* too bad, we failed. try to sync the console state with the VMM state */ + i_vmstateChangeCallback(pUVM, 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. + * + * @note Locks this object for writing. + */ +HRESULT Console::i_doMediumChange(IMediumAttachment *aMediumAttachment, bool fForce, PUVM pUVM) +{ + 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, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker in EMT, 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 = VMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_changeRemovableMedium, 8, + this, pUVM, 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 = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + VMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM); + + 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 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, + 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 = VMR3GetStateU(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, + 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 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, 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, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker in EMT, 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 = VMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_attachStorageDevice, 8, + this, pUVM, 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 = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + VMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM); + + 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 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, + 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 = VMR3GetStateU(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, + 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 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, 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, &alock, &fResume); + if (FAILED(rc)) + return rc; + + /* + * Call worker in EMT, 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 = VMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS, + (PFNRT)i_detachStorageDevice, 7, + this, pUVM, 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 = VMR3ReqWait(pReq, RT_INDEFINITE_WAIT); + AssertRC(vrc); + if (RT_SUCCESS(vrc)) + vrc = pReq->iStatus; + VMR3ReqFree(pReq); + + if (fResume) + i_resumeAfterConfigChange(pUVM); + + 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 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, + 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 = VMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + /* Determine the base path for the device instance. */ + PCFGMNODE pCtlInst; + pCtlInst = CFGMR3GetChildF(CFGMR3GetRootU(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 = CFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + if (pLunL0) + { + uint32_t fFlags = 0; + + if (fSilent) + fFlags |= PDM_TACH_FLAGS_NOT_HOT_PLUG; + + rc = PDMR3DeviceDetach(pUVM, pcszDevice, uInstance, uLUN, fFlags); + if (rc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + rc = VINF_SUCCESS; + AssertRCReturn(rc, rc); + CFGMR3RemoveNode(pLunL0); + + Utf8Str devicePath = Utf8StrFmt("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN); + pThis->mapMediumAttachments.erase(devicePath); + + } + else + AssertFailedReturn(VERR_INTERNAL_ERROR); + + CFGMR3Dump(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 = PDMR3UsbDetachDevice(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; + int vrc = PDMR3QueryDeviceLun(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 = VMR3GetStateU(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(), 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_onNATRedirectRuleChange(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 = PDMR3QueryLun(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(), "pcnet", ulInstanceMax); + notifyNatDnsChange(ptrVM.rawUVM(), "e1000", ulInstanceMax); + notifyNatDnsChange(ptrVM.rawUVM(), "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, 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 = PDMR3QueryDriverOnLun(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); + + /* Find the correct attachment. */ + for (unsigned i = 0; i < sfaAttachments.size(); i++) + { + const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i]; + /* + * 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 = PDMR3QueryDriverOnLun(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; +} + +/** + * 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 = PDMR3QueryDriverOnLun(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 = PDMR3QueryDriverOnLun(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 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, + 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, 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 = VMR3ReqCallWaitU(pUVM, 0 /*idDstCpu*/, + (PFNRT)i_changeNetworkAttachment, 6, + this, pUVM, pszDevice, uInstance, uLun, aNetworkAdapter); + + if (fResume) + i_resumeAfterConfigChange(pUVM); + + 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 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, + 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. + */ + VMSTATE enmVMState = VMR3GetStateU(pUVM); + AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE); + + PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */ + PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */ + PCFGMNODE pInst = CFGMR3GetChildF(CFGMR3GetRootU(pUVM), "Devices/%s/%d/", pszDevice, uInstance); + AssertRelease(pInst); + + int rc = pThis->i_configNetwork(pszDevice, uInstance, uLun, aNetworkAdapter, pCfg, pLunL0, pInst, + true /*fAttachDetach*/, false /*fIgnoreConnectFailure*/); + + 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 = PDMR3QueryDriverOnLun(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; +} + + +/** + * 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 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, + 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 = VMR3GetStateU(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 = CFGMR3GetChildF(CFGMR3GetRootU(pUVM), "Devices/serial/%d/", ulSlot); + AssertRelease(pInst); + + /* Remove old driver. */ + if (pThis->m_aeSerialPortMode[ulSlot] != PortMode_Disconnected) + { + rc = PDMR3DeviceDetach(pUVM, "serial", ulSlot, 0, 0); + PCFGMNODE pLunL0 = CFGMR3GetChildF(pInst, "LUN#0"); + CFGMR3RemoveNode(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 = PDMR3DeviceAttach(pUVM, "serial", ulSlot, 0, 0, &pBase); + + CFGMR3Dump(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(), NULL, &fResume); + if (FAILED(hr)) + return hr; + + /* + * Call worker in EMT, that's faster and safer than doing everything + * using VM3ReqCallWait. + */ + int rc = VMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /*idDstCpu*/, + (PFNRT)i_changeSerialPortAttachment, 6, + this, ptrVM.rawUVM(), aSerialPort); + + if (fResume) + i_resumeAfterConfigChange(ptrVM.rawUVM()); + 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() +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + fireStorageControllerChangedEvent(mEventSource); + + 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.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()); + else + rc = i_doCPUAdd(aCPU, ptrVM.rawUVM()); + 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 = VMR3SetCpuExecutionCap(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) + i_changeClipboardMode(aClipboardMode); + 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::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("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(mpUVM, NULL /*alock is not held*/); +#endif + mConsoleVRDPServer->EnableConnections(); + } + } + else + { + mConsoleVRDPServer->Stop(); +#ifdef VBOX_WITH_AUDIO_VRDE + mAudioVRDE->doDetachDriverViaEmt(mpUVM, 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 = PDMR3QueryDeviceLun(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 IPRT status code. Will return 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) + { + const bool fIsEnabled = Recording.mpCtx + && Recording.mpCtx->IsStarted(); + + if (RT_BOOL(fEnable) != fIsEnabled) + { + LogRel(("Recording: %s\n", fEnable ? "Enabling" : "Disabling")); + + if (fEnable) + { + vrc = i_recordingCreate(); + if (RT_SUCCESS(vrc)) + { +# ifdef VBOX_WITH_AUDIO_RECORDING + /* Attach the video recording audio driver if required. */ + if ( Recording.mpCtx->IsFeatureEnabled(RecordingFeature_Audio) + && Recording.mAudioRec) + { + vrc = Recording.mAudioRec->applyConfiguration(Recording.mpCtx->GetConfig()); + if (RT_SUCCESS(vrc)) + vrc = Recording.mAudioRec->doAttachDriverViaEmt(mpUVM, pAutoLock); + } +# endif + if ( RT_SUCCESS(vrc) + && Recording.mpCtx->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)) + LogRel(("Recording: Failed to enable with %Rrc\n", vrc)); + } + else + { + i_recordingStop(pAutoLock); +# ifdef VBOX_WITH_AUDIO_RECORDING + if (Recording.mAudioRec) + Recording.mAudioRec->doDetachDriverViaEmt(mpUVM, pAutoLock); +# endif + i_recordingDestroy(); + } + + if (RT_FAILURE(vrc)) + LogRel(("Recording: %s failed with %Rrc\n", fEnable ? "Enabling" : "Disabling", vrc)); + } + else /* Should not happen. */ + vrc = VERR_NO_CHANGE; + } + + 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); + } + + ptrVM.release(); + } +#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::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 (!PDMR3UsbHasHub(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 */ + 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 */ + 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 strName; + rc = aBandwidthGroup->COMGETTER(Name)(strName.asOutParam()); + if (SUCCEEDED(rc)) + { + 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 = PDMR3AsyncCompletionBwMgrSetMaxForFile(ptrVM.rawUVM(), Utf8Str(strName).c_str(), (uint32_t)cMax); +#ifdef VBOX_WITH_NETSHAPER + else if (enmType == BandwidthGroupType_Network) + vrc = PDMR3NsBwGroupSetLimit(ptrVM.rawUVM(), Utf8Str(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(), RT_BOOL(aSilent)); + else + rc = i_doStorageDeviceAttach(aMediumAttachment, ptrVM.rawUVM(), 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(IN_BSTR aMachineId, IN_BSTR aKey, IN_BSTR aVal) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + if (!aMachineId) + return S_OK; + + HRESULT hrc = S_OK; + Bstr idMachine(aMachineId); + if ( FAILED(hrc) + || idMachine != i_getId()) + return hrc; + + /* don't do anything if the VM isn't running */ + SafeVMPtrQuiet ptrVM(this); + if (ptrVM.isOk()) + { + Bstr strKey(aKey); + Bstr strVal(aVal); + + if (strKey == "VBoxInternal2/TurnResetIntoPowerOff") + { + int vrc = VMR3SetPowerOffInsteadOfReset(ptrVM.rawUVM(), strVal == "1"); + AssertRC(vrc); + } + + ptrVM.release(); + } + + /* notify console callbacks on success */ + if (SUCCEEDED(hrc)) + fireExtraDataChangedEvent(mEventSource, aMachineId, aKey, aVal); + + LogFlowThisFunc(("Leaving hrc=%#x\n", hrc)); + return hrc; +} + +/** + * @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(), &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 = VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 14, + this, ptrVM.rawUVM(), 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()); + + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("%Rrc"), vrc); + if (FAILED(rc)) + return rc; + + PPDMIBASE pIBase = NULL; + PPDMIMEDIA pIMedium = NULL; + vrc = PDMR3QueryDriverOnLun(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(), &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 = VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 14, + this, ptrVM.rawUVM(), 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()); + + if (RT_FAILURE(vrc)) + return setErrorBoth(E_FAIL, vrc, tr("%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 = VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, + (PFNRT)i_reconfigureMediumAttachment, 14, + this, ptrVM.rawUVM(), pcszDevice, lInstance, enmBus, fUseHostIOCache, + fBuiltinIOCache, fInsertDiskIntegrityDrv, + false /* fSetupMerge */, 0 /* uMergeSource */, 0 /* uMergeTarget */, + pAttachment, mMachineState, &rc); + if (RT_FAILURE(vrc)) + throw setErrorBoth(E_FAIL, vrc, tr("%Rrc"), vrc); + if (FAILED(rc)) + throw rc; + + alock.acquire(); + } + + 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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + /* 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", Global::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 = VMR3Suspend(ptrVM.rawUVM(), enmReason); + + HRESULT hrc = S_OK; + 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", Global::stringifyReason(aReason))); + + int vrc; + if (VMR3GetStateU(ptrVM.rawUVM()) == VMSTATE_CREATED) + { +#ifdef VBOX_WITH_EXTPACK + vrc = mptrExtPackManager->i_callAllVmPowerOnHooks(this, VMR3GetVM(ptrVM.rawUVM())); +#else + vrc = VINF_SUCCESS; +#endif + if (RT_SUCCESS(vrc)) + vrc = VMR3PowerOn(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 (VMR3GetStateU(ptrVM.rawUVM()) != VMSTATE_SUSPENDED) + { + LogRel(("Ignoring VM resume request, VM is currently not suspended\n")); + return S_OK; + } + if (VMR3GetSuspendReason(ptrVM.rawUVM()) != VMSUSPENDREASON_HOST_SUSPEND) + { + LogRel(("Ignoring VM resume request, VM was not suspended due to host-suspend\n")); + 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 ( VMR3GetStateU(ptrVM.rawUVM()) == VMSTATE_SUSPENDED + && VMR3GetSuspendReason(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 = VMR3Resume(ptrVM.rawUVM(), enmReason); + if (aReason == Reason_Snapshot) + mVMStateChangeCallbackDisabled = false; + } + + HRESULT rc = RT_SUCCESS(vrc) ? S_OK + : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + LogFlowThisFuncLeave(); + return rc; +} + +/** + * 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", Global::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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + 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 = VMR3Suspend(ptrVM.rawUVM(), enmReason); + alock.acquire(); + + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc); + fPaused = true; + } + + LogFlowFunc(("Saving the state to '%s'...\n", aStateFilePath.c_str())); + + mpVmm2UserMethods->pISnapshot = aSnapshot; + mptrCancelableProgress = aProgress; + alock.release(); + int vrc = VMR3Save(ptrVM.rawUVM(), + aStateFilePath.c_str(), + fContinueAfterwards, + Console::i_stateProgressCallback, + static_cast<IProgress *>(aProgress), + &aLeftPaused); + alock.acquire(); + mpVmm2UserMethods->pISnapshot = NULL; + mptrCancelableProgress.setNull(); + if (RT_FAILURE(vrc)) + { + if (fPaused) + { + alock.release(); + VMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_STATE_RESTORED); + alock.acquire(); + } + return setErrorBoth(E_FAIL, vrc, tr("Failed to save the machine state to '%s' (%Rrc)"), aStateFilePath.c_str(), 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; + } + + 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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + SSMR3Cancel(ptrVM.rawUVM()); + + LogFlowFuncLeave(); + return S_OK; +} + +#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 (!Recording.mpCtx) + return S_OK; + + if ( Recording.mpCtx->IsStarted() + && Recording.mpCtx->IsFeatureEnabled(RecordingFeature_Audio)) + { + return Recording.mpCtx->SendAudioFrame(pvData, cbData, uTimestampMs); + } + + return S_OK; +} +#endif /* VBOX_WITH_AUDIO_RECORDING */ + +#ifdef VBOX_WITH_RECORDING +int Console::i_recordingGetSettings(settings::RecordingSettings &Settings) +{ + Assert(mMachine.isNotNull()); + + Settings.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); + Settings.fEnabled = RT_BOOL(fTemp); + + SafeIfaceArray<IRecordingScreenSettings> paRecordingScreens; + hrc = pRecordSettings->COMGETTER(Screens)(ComSafeArrayAsOutParam(paRecordingScreens)); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + + for (unsigned long i = 0; i < (unsigned long)paRecordingScreens.size(); ++i) + { + settings::RecordingScreenSettings RecordScreenSettings; + ComPtr<IRecordingScreenSettings> pRecordScreenSettings = paRecordingScreens[i]; + + hrc = pRecordScreenSettings->COMGETTER(Enabled)(&fTemp); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + RecordScreenSettings.fEnabled = RT_BOOL(fTemp); + hrc = pRecordScreenSettings->COMGETTER(MaxTime)((ULONG *)&RecordScreenSettings.ulMaxTimeS); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecordScreenSettings->COMGETTER(MaxFileSize)((ULONG *)&RecordScreenSettings.File.ulMaxSizeMB); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + Bstr bstrTemp; + hrc = pRecordScreenSettings->COMGETTER(Filename)(bstrTemp.asOutParam()); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + RecordScreenSettings.File.strName = bstrTemp; + hrc = pRecordScreenSettings->COMGETTER(Options)(bstrTemp.asOutParam()); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + RecordScreenSettings.strOptions = bstrTemp; + hrc = pRecordScreenSettings->COMGETTER(VideoWidth)((ULONG *)&RecordScreenSettings.Video.ulWidth); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecordScreenSettings->COMGETTER(VideoHeight)((ULONG *)&RecordScreenSettings.Video.ulHeight); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecordScreenSettings->COMGETTER(VideoRate)((ULONG *)&RecordScreenSettings.Video.ulRate); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + hrc = pRecordScreenSettings->COMGETTER(VideoFPS)((ULONG *)&RecordScreenSettings.Video.ulFPS); + AssertComRCReturn(hrc, VERR_INVALID_PARAMETER); + + Settings.mapScreens[i] = RecordScreenSettings; + } + + Assert(Settings.mapScreens.size() == paRecordingScreens.size()); + + return VINF_SUCCESS; +} + +/** + * Creates the recording context. + * + * @returns IPRT status code. + */ +int Console::i_recordingCreate(void) +{ + AssertReturn(Recording.mpCtx == NULL, VERR_WRONG_ORDER); + + settings::RecordingSettings recordingSettings; + int rc = i_recordingGetSettings(recordingSettings); + if (RT_SUCCESS(rc)) + { + try + { + Recording.mpCtx = new RecordingContext(this /* pConsole */, recordingSettings); + } + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + catch (int &rc2) + { + return rc2; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destroys the recording context. + */ +void Console::i_recordingDestroy(void) +{ + if (Recording.mpCtx) + { + delete Recording.mpCtx; + Recording.mpCtx = NULL; + } + + LogFlowThisFuncLeave(); +} + +/** + * Starts recording. Does nothing if recording is already active. + * + * @returns IPRT status code. + */ +int Console::i_recordingStart(util::AutoWriteLock *pAutoLock /* = NULL */) +{ + RT_NOREF(pAutoLock); + AssertPtrReturn(Recording.mpCtx, VERR_WRONG_ORDER); + + if (Recording.mpCtx->IsStarted()) + return VINF_SUCCESS; + + LogRel(("Recording: Starting ...\n")); + + int rc = Recording.mpCtx->Start(); + if (RT_SUCCESS(rc)) + { + for (unsigned uScreen = 0; uScreen < Recording.mpCtx->GetStreamCount(); uScreen++) + mDisplay->i_recordingScreenChanged(uScreen); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Stops recording. Does nothing if recording is not active. + */ +int Console::i_recordingStop(util::AutoWriteLock *pAutoLock /* = NULL */) +{ + if ( !Recording.mpCtx + || !Recording.mpCtx->IsStarted()) + return VINF_SUCCESS; + + LogRel(("Recording: Stopping ...\n")); + + int rc = Recording.mpCtx->Stop(); + if (RT_SUCCESS(rc)) + { + const size_t cStreams = Recording.mpCtx->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(rc); + return rc; +} +#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 = VMR3GetStateU(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_RUNNING_FT: + 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", VMR3GetStateName(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 supportsMT, BOOL needsHostCursor) +{ + LogFlowThisFunc(("supportsAbsolute=%d supportsRelative=%d needsHostCursor=%d\n", + supportsAbsolute, supportsRelative, needsHostCursor)); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + fireMouseCapabilityChangedEvent(mEventSource, supportsAbsolute, supportsRelative, supportsMT, 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()); + + VBoxEventDesc evDesc; + if (aCheck) + { + evDesc.init(mEventSource, VBoxEventType_OnCanShowWindow); + BOOL fDelivered = evDesc.fire(5000); /* Wait up to 5 secs for delivery */ + //Assert(fDelivered); + if (fDelivered) + { + ComPtr<IEvent> pEvent; + evDesc.getEvent(pEvent.asOutParam()); + // bit clumsy + ComPtr<ICanShowWindowEvent> pCanShowEvent = pEvent; + if (pCanShowEvent) + { + BOOL fVetoed = FALSE; + BOOL fApproved = FALSE; + pCanShowEvent->IsVetoed(&fVetoed); + pCanShowEvent->IsApproved(&fApproved); + *aCanShow = fApproved || !fVetoed; + } + else + { + AssertFailed(); + *aCanShow = TRUE; + } + } + else + *aCanShow = TRUE; + } + else + { + evDesc.init(mEventSource, VBoxEventType_OnShowWindow, INT64_C(0)); + BOOL fDelivered = evDesc.fire(5000); /* Wait up to 5 secs for delivery */ + //Assert(fDelivered); + if (fDelivered) + { + ComPtr<IEvent> pEvent; + evDesc.getEvent(pEvent.asOutParam()); + ComPtr<IShowWindowEvent> pShowEvent = pEvent; + if (pShowEvent) + { + LONG64 iEvWinId = 0; + pShowEvent->COMGETTER(WinId)(&iEvWinId); + if (iEvWinId != 0 && *aWinId == 0) + *aWinId = iEvWinId; + } + else + AssertFailed(); + } + } + + return S_OK; +} + +// private methods +//////////////////////////////////////////////////////////////////////////////// + +/** + * 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); + } +} + + +HRESULT Console::i_safeVMPtrRetainer(PUVM *a_ppUVM, bool a_Quiet) +{ + *a_ppUVM = 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 pUVM = mpUVM; + if (!pUVM) + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("The virtual machine is powered off")); + + /* + * Retain a reference to the user mode VM handle and get the global handle. + */ + uint32_t cRefs = VMR3RetainUVM(pUVM); + if (cRefs == UINT32_MAX) + return a_Quiet + ? E_ACCESSDENIED + : setError(E_ACCESSDENIED, tr("The virtual machine is powered off")); + + /* done */ + *a_ppUVM = pUVM; + return S_OK; +} + +void Console::i_safeVMPtrReleaser(PUVM *a_ppUVM) +{ + if (*a_ppUVM) + VMR3ReleaseUVM(*a_ppUVM); + *a_ppUVM = NULL; +} + + +/** + * 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) +{ + HRESULT hrc = S_OK; + + Bstr logFolder; + hrc = aMachine->COMGETTER(LogFolder)(logFolder.asOutParam()); + if (FAILED(hrc)) + return hrc; + + Utf8Str logDir = logFolder; + + /* make sure the Logs folder exists */ + Assert(logDir.length()); + if (!RTDirExists(logDir.c_str())) + RTDirCreateFullPath(logDir.c_str(), 0700); + + Utf8Str logFile = Utf8StrFmt("%s%cVBox.log", + logDir.c_str(), RTPATH_DELIMITER); + Utf8Str pngFile = Utf8StrFmt("%s%cVBox.png", + logDir.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 = Utf8StrFmt("%s.%d", files[j]->c_str(), i); + else + oldName = *files[j]; + newName = Utf8StrFmt("%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()); + } + } + } + + RTERRINFOSTATIC ErrInfo; + int vrc = com::VBoxLogRelCreate("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 */, 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(logDir.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); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mMachineState=%d\n", mMachineState)); + HRESULT rc = S_OK; + ComObjPtr<Progress> pPowerupProgress; + bool fBeganPoweringUp = false; + + LONG cOperations = 1; + LONG ulTotalOperationsWeight = 1; + VMPowerUpTask* task = NULL; + + try + { + if (Global::IsOnlineOrTransient(mMachineState)) + throw 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)) + throw 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)) + throw 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)) + throw rc; + } +#endif + + /* test the FaultToleranceState property */ + FaultToleranceState_T enmFaultToleranceState; + rc = mMachine->COMGETTER(FaultToleranceState)(&enmFaultToleranceState); + if (FAILED(rc)) + throw rc; + BOOL fFaultToleranceSyncEnabled = (enmFaultToleranceState == FaultToleranceState_Standby); + + /* 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) + progressDesc = tr("Restoring virtual machine"); + else if (fTeleporterEnabled) + progressDesc = tr("Teleporting virtual machine"); + else if (fFaultToleranceSyncEnabled) + progressDesc = tr("Fault Tolerance syncing of remote virtual machine"); + else + progressDesc = tr("Starting virtual machine"); + + Bstr savedStateFile; + + /* + * Saved VMs will have to prove that their saved states seem kosher. + */ + if (mMachineState == MachineState_Saved) + { + rc = mMachine->COMGETTER(StateFilePath)(savedStateFile.asOutParam()); + if (FAILED(rc)) + throw rc; + ComAssertRet(!savedStateFile.isEmpty(), E_FAIL); + int vrc = SSMR3ValidateFile(Utf8Str(savedStateFile).c_str(), false /* fChecksumIt */); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("VM cannot start because the saved state file '%ls' is invalid (%Rrc). Delete the saved state prior to starting the VM"), + savedStateFile.raw(), vrc); + } + + /* 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); + if (!task->isOk()) + { + throw E_FAIL; + } + } + catch(...) + { + delete task; + rc = setError(E_FAIL, "Could not create VMPowerUpTask object \n"); + throw rc; + } + + task->mConfigConstructor = i_configConstructor; + task->mSharedFolders = sharedFolders; + task->mStartPaused = aPaused; + if (mMachineState == MachineState_Saved) + task->mSavedStateFile = savedStateFile; + task->mTeleporterEnabled = fTeleporterEnabled; + task->mEnmFaultToleranceState = enmFaultToleranceState; + + /* 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 (savedStateFile.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, "Failed to setup CoreDumper. Couldn't create dump directory '%s' (%Rrc)\n", + pszDumpDir, vrc); + } + + vrc = RTCoreDumperSetup(pszDumpDir, fCoreFlags); + if (RT_FAILURE(vrc)) + throw setErrorBoth(E_FAIL, vrc, "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, + Bstr(tr("Starting Hard Disk operations")).raw(), + 1); + AssertComRCReturnRC(rc); + } + else if ( mMachineState == MachineState_Saved + || (!fTeleporterEnabled && !fFaultToleranceSyncEnabled)) + { + 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 */, + Bstr(tr("Teleporting virtual machine")).raw(), + 1 /* ulFirstOperationWeight */); + } + else if (fFaultToleranceSyncEnabled) + { + rc = pPowerupProgress->init(static_cast<IConsole *>(this), + progressDesc.raw(), + TRUE /* aCancelable */, + 3 /* cOperations */, + 10 /* ulTotalOperationsWeight */, + Bstr(tr("Fault Tolerance syncing of remote virtual machine")).raw(), + 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); + } + + rc = task->createThread(); + + 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) + i_setMachineState(MachineState_Restoring); + else if (fTeleporterEnabled) + i_setMachineState(MachineState_TeleportingIn); + else if (enmFaultToleranceState == FaultToleranceState_Standby) + i_setMachineState(MachineState_FaultTolerantSyncing); + 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); + } + + 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); + + PUVM pUVM = mpUVM; Assert(pUVM != NULL); + uint32_t cRefs = VMR3RetainUVM(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_FaultTolerantSyncing + || mMachineState == MachineState_TeleportingIn + , ("Invalid machine state: %s\n", Global::stringifyMachineState(mMachineState))); + + LogRel(("Console::powerDown(): A request to power off the VM has been issued (mMachineState=%s, InUninit=%d)\n", + Global::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_FaultTolerantSyncing + || 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 + && mMachineState != MachineState_FaultTolerantSyncing + ) + 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 = VMR3PowerOff(pUVM); +#ifdef VBOX_WITH_EXTPACK + mptrExtPackManager->i_callAllVmPowerOffHooks(this, VMR3GetVM(pUVM)); +#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(); + + 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.) */ + VMR3ReleaseUVM(mpUVM); + mpUVM = NULL; + + LogFlowThisFunc(("Destroying the VM...\n")); + + alock.release(); + + vrc = VMR3Destroy(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) + VMR3ReleaseUVM(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", + Global::stringifyMachineState(mMachineState), Global::stringifyMachineState(aMachineState), aUpdateServer)); + LogRel(("Console: Machine state changed to '%s'\n", Global::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 = RTPathAbsEx(NULL, 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()); + if (aData.m_strAutoMountPoint.length() >= RTPATH_MAX) + return setError(E_INVALIDARG, tr("Shared folder mountp point too long: %zu bytes"), 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; +} + +/** @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. + */ +DECLCALLBACK(void) Console::i_vmstateChangeCallback(PUVM pUVM, VMSTATE enmState, VMSTATE enmOldState, void *pvUser) +{ + LogFlowFunc(("Changing state from %s to %s (pUVM=%p)\n", + VMR3GetStateName(enmOldState), VMR3GetStateName(enmState), pUVM)); + + 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->i_isResetTurnedIntoPowerOff()) + { + 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_FaultTolerantSyncing + && 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* task = NULL; + try + { + task = new VMPowerDownTask(that, pProgress); + /* 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 (!task->isOk()) + { + LogFlowFunc(("Console is already being uninitialized. \n")); + throw E_FAIL; + } + } + catch(...) + { + delete task; + LogFlowFunc(("Problem with creating VMPowerDownTask object. \n")); + } + + rc = task->createThread(); + + if (FAILED(rc)) + { + LogFlowFunc(("Problem with creating thread for VMPowerDownTask. \n")); + } + + } + 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; + + /* 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 + * back to Saved (to preserve the saved state file) */ + that->i_setMachineState(MachineState_Saved); + 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; + case MachineState_FaultTolerantSyncing: + /* Fault tolerant sync failed or was canceled. Back to powered off. */ + that->i_setMachineState(MachineState_PoweredOff); + 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_FaultTolerantSyncing: + 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", Global::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", Global::stringifyMachineState(that->mMachineState), + VMR3GetStateName(enmOldState), VMR3GetStateName(enmState) )); + that->i_setMachineState(MachineState_Paused); + break; + } + break; + } + + case VMSTATE_RUNNING: + { + if ( enmOldState == VMSTATE_POWERING_ON + || enmOldState == VMSTATE_RESUMING + || enmOldState == VMSTATE_RUNNING_FT) + { + 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->mMachineState == MachineState_FaultTolerantSyncing + && enmOldState == VMSTATE_RUNNING_FT)); + + that->i_setMachineState(MachineState_Running); + } + + break; + } + + case VMSTATE_RUNNING_LS: + AssertMsg( that->mMachineState == MachineState_LiveSnapshotting + || that->mMachineState == MachineState_Teleporting, + ("%s/%s -> %s\n", Global::stringifyMachineState(that->mMachineState), + VMR3GetStateName(enmOldState), VMR3GetStateName(enmState) )); + break; + + case VMSTATE_RUNNING_FT: + AssertMsg(that->mMachineState == MachineState_FaultTolerantSyncing, + ("%s/%s -> %s\n", Global::stringifyMachineState(that->mMachineState), + VMR3GetStateName(enmOldState), VMR3GetStateName(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. + * + * @param aClipboardMode new clipboard mode. + */ +void Console::i_changeClipboardMode(ClipboardMode_T aClipboardMode) +{ + VMMDev *pVMMDev = m_pVMMDev; + Assert(pVMMDev); + + 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_SHARED_CLIPBOARD_MODE_OFF; + break; + case ClipboardMode_GuestToHost: + LogRel(("Shared clipboard mode: Guest to Host\n")); + parm.u.uint32 = VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST; + break; + case ClipboardMode_HostToGuest: + LogRel(("Shared clipboard mode: Host to Guest\n")); + parm.u.uint32 = VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST; + break; + case ClipboardMode_Bidirectional: + LogRel(("Shared clipboard mode: Bidirectional\n")); + parm.u.uint32 = VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL; + break; + } + + pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE, 1, &parm); +} + +/** + * 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_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 +/** + * 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 Address(BstrAddress); + + Bstr id; + hrc = aHostDevice->COMGETTER(Id)(id.asOutParam()); + ComAssertComRCRetRC(hrc); + Guid uuid(id); + + BOOL fRemote = FALSE; + hrc = aHostDevice->COMGETTER(Remote)(&fRemote); + ComAssertComRCRetRC(hrc); + + Bstr BstrBackend; + hrc = aHostDevice->COMGETTER(Backend)(BstrBackend.asOutParam()); + ComAssertComRCRetRC(hrc); + + Utf8Str Backend(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())); + + void *pvRemoteBackend = NULL; + if (fRemote) + { + RemoteUSBDevice *pRemoteUSBDevice = static_cast<RemoteUSBDevice *>(aHostDevice); + pvRemoteBackend = i_consoleVRDPServer()->USBBackendRequestPointer(pRemoteUSBDevice->clientId(), &uuid); + if (!pvRemoteBackend) + return E_INVALIDARG; /* The clientId is invalid then. */ + } + + USBConnectionSpeed_T enmSpeed; + hrc = aHostDevice->COMGETTER(Speed)(&enmSpeed); + AssertComRCReturnRC(hrc); + + int vrc = VMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)i_usbAttachCallback, 10, + this, ptrVM.rawUVM(), aHostDevice, uuid.raw(), Backend.c_str(), + Address.c_str(), pvRemoteBackend, 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, IUSBDevice *aHostDevice, PCRTUUID aUuid, const char *pszBackend, + const char *aAddress, void *pvRemoteBackend, 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 = PDMR3UsbCreateProxyDevice(pUVM, aUuid, pszBackend, aAddress, pvRemoteBackend, + 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(PDMR3UsbHasHub(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 = VMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)i_usbDetachCallback, 5, + this, ptrVM.rawUVM(), 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, 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 = PDMR3UsbDetachDevice(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 = vrc; + 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 = vrc; + 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. */ + if (pErrorText->length()) + *pErrorText = Utf8StrFmt("%s.\n%N (%Rrc)", pErrorText->c_str(), + pszFormat, &va2, rc, rc); + else + *pErrorText = Utf8StrFmt("%N (%Rrc)", pszFormat, &va2, rc, rc); + + 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())); + + that->i_onRuntimeError(BOOL(fFatal), Bstr(pszErrorId).raw(), Bstr(message).raw()); + + 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(); +} + +/** + * Progress cancelation callback for fault tolerance VM poweron + */ +static void faultToleranceProgressCancelCallback(void *pvUser) +{ + PUVM pUVM = (PUVM)pvUser; + + if (pUVM) + FTMR3CancelStandby(pUVM); +} + +/** + * 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 saBuildID[48]; + RTStrPrintf(saBuildID, sizeof(saBuildID), "%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 + && pTask->mEnmFaultToleranceState != FaultToleranceState_Standby) + { + 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); + + /* + * 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(); + + PVM pVM; + vrc = VMR3Create(cCpus, + pConsole->mpVmm2UserMethods, + Console::i_genericVMSetErrorCallback, + &pTask->mErrorMsg, + pTask->mConfigConstructor, + static_cast<Console *>(pConsole), + &pVM, NULL); + alock.acquire(); + +#ifdef VBOX_WITH_AUDIO_VRDE + /* Attach the VRDE audio driver. */ + IVRDEServer *pVRDEServer = pConsole->i_getVRDEServer(); + if (pVRDEServer) + { + BOOL fVRDEEnabled = FALSE; + rc = pVRDEServer->COMGETTER(Enabled)(&fVRDEEnabled); + AssertComRCReturnVoid(rc); + + if ( fVRDEEnabled + && pConsole->mAudioVRDE) + pConsole->mAudioVRDE->doAttachDriverViaEmt(pConsole->mpUVM, &alock); + } +#endif + + /* Enable client connections to the VRDP server. */ + pConsole->i_consoleVRDPServer()->EnableConnections(); + +#ifdef VBOX_WITH_RECORDING + ComPtr<IRecordingSettings> recordingSettings; + rc = pConsole->mMachine->COMGETTER(RecordingSettings)(recordingSettings.asOutParam()); + AssertComRCReturnVoid(rc); + + BOOL fRecordingEnabled; + rc = recordingSettings->COMGETTER(Enabled)(&fRecordingEnabled); + AssertComRCReturnVoid(rc); + + if (fRecordingEnabled) + { + int vrc2 = pConsole->i_recordingEnable(fRecordingEnabled, &alock); + if (RT_SUCCESS(vrc2)) + { + fireRecordingChangedEvent(pConsole->mEventSource); + } + else + LogRel(("Recording: Failed with %Rrc on VM power up\n", vrc2)); + + /** Note: Do not use vrc here, as starting the video recording isn't critical to + * powering up the VM. */ + } +#endif + + if (RT_SUCCESS(vrc)) + { + do + { + /* + * Register our load/save state file handlers + */ + vrc = SSMR3RegisterExternal(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(); + } + + /* 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())); + + vrc = VMR3LoadFromFile(pConsole->mpUVM, + pTask->mSavedStateFile.c_str(), + Console::i_stateProgressCallback, + static_cast<IProgress *>(pTask->mProgress)); + + 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); +#endif + if (RT_SUCCESS(vrc)) + vrc = VMR3Resume(pConsole->mpUVM, VMRESUMEREASON_STATE_RESTORED); + AssertLogRelRC(vrc); + } + } + + /* Power off in case we failed loading or resuming the VM */ + if (RT_FAILURE(vrc)) + { + int vrc2 = VMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2); +#ifdef VBOX_WITH_EXTPACK + pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM); +#endif + } + } + else if (pTask->mTeleporterEnabled) + { + /* -> ConsoleImplTeleporter.cpp */ + bool fPowerOffOnFailure; + rc = pConsole->i_teleporterTrg(pConsole->mpUVM, pMachine, &pTask->mErrorMsg, pTask->mStartPaused, + pTask->mProgress, &fPowerOffOnFailure); + if (FAILED(rc) && fPowerOffOnFailure) + { + ErrorInfoKeeper eik; + int vrc2 = VMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2); +#ifdef VBOX_WITH_EXTPACK + pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM); +#endif + } + } + else if (pTask->mEnmFaultToleranceState != FaultToleranceState_Inactive) + { + /* + * Get the config. + */ + ULONG uPort; + rc = pMachine->COMGETTER(FaultTolerancePort)(&uPort); + if (SUCCEEDED(rc)) + { + ULONG uInterval; + rc = pMachine->COMGETTER(FaultToleranceSyncInterval)(&uInterval); + if (SUCCEEDED(rc)) + { + Bstr bstrAddress; + rc = pMachine->COMGETTER(FaultToleranceAddress)(bstrAddress.asOutParam()); + if (SUCCEEDED(rc)) + { + Bstr bstrPassword; + rc = pMachine->COMGETTER(FaultTolerancePassword)(bstrPassword.asOutParam()); + if (SUCCEEDED(rc)) + { + if (pTask->mProgress->i_setCancelCallback(faultToleranceProgressCancelCallback, + pConsole->mpUVM)) + { + if (SUCCEEDED(rc)) + { + Utf8Str strAddress(bstrAddress); + const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str(); + Utf8Str strPassword(bstrPassword); + const char *pszPassword = strPassword.isEmpty() ? NULL : strPassword.c_str(); + + /* Power on the FT enabled VM. */ +#ifdef VBOX_WITH_EXTPACK + vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM); +#endif + if (RT_SUCCESS(vrc)) + vrc = FTMR3PowerOn(pConsole->mpUVM, + pTask->mEnmFaultToleranceState == FaultToleranceState_Master /* fMaster */, + uInterval, + pszAddress, + uPort, + pszPassword); + AssertLogRelRC(vrc); + } + pTask->mProgress->i_setCancelCallback(NULL, NULL); + } + else + rc = E_FAIL; + + } + } + } + } + } + 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); +#endif + if (RT_SUCCESS(vrc)) + vrc = VMR3PowerOn(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(); + VMR3AtErrorDeregister(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(); + } + VMR3ReleaseUVM(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, 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 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, + 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, + 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; +} + +/** + * @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(ISnapshot)) + return ((MYVMM2USERMETHODS *)pThis)->pISnapshot; + + 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. */ + RTUINT iFirstLUN; + /** The unit number corresponding to the last entry in the LED array. + * (The size of the LED array is iLastLUN - iFirstLUN + 1.) */ + RTUINT iLastLUN; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** The Media Notify interface. */ + PDMIMEDIANOTIFY IMediaNotify; + /** Map for translating PDM storage controller/LUN information to + * IMediumAttachment references. */ + Console::MediumAttachmentMap *pmapMediumAttachments; + /** 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); + if (RT_FAILURE(rc)) + pLed = NULL; + ASMAtomicWritePtr(&pThis->papLeds[iLUN - pThis->iFirstLUN], pLed); + 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->pmapMediumAttachments) + { + AutoWriteLock alock(pThis->pConsole COMMA_LOCKVAL_SRC_POS); + + ComPtr<IMediumAttachment> pMediumAtt; + Utf8Str devicePath = Utf8StrFmt("%s/LUN#%u", pThis->pszDeviceInstance, uLUN); + Console::MediumAttachmentMap::const_iterator end = pThis->pmapMediumAttachments->end(); + Console::MediumAttachmentMap::const_iterator it = pThis->pmapMediumAttachments->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) + { + pThis->pmapMediumAttachments->erase(devicePath); + pThis->pmapMediumAttachments->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)); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "papLeds\0pmapMediumAttachments\0DeviceInstance\0pConsole\0First\0Last\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); + + /* + * Data. + */ + 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->pszDeviceInstance = NULL; + + /* + * Read config. + */ + int rc = CFGMR3QueryPtr(pCfg, "papLeds", (void **)&pThis->papLeds); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"papLeds\" value! rc=%Rrc\n", rc)); + return rc; + } + + rc = CFGMR3QueryPtrDef(pCfg, "pmapMediumAttachments", (void **)&pThis->pmapMediumAttachments, NULL); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"pmapMediumAttachments\" value! rc=%Rrc\n", rc)); + return rc; + } + if (pThis->pmapMediumAttachments) + { + rc = CFGMR3QueryStringAlloc(pCfg, "DeviceInstance", &pThis->pszDeviceInstance); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"DeviceInstance\" value! rc=%Rrc\n", rc)); + return rc; + } + rc = CFGMR3QueryPtr(pCfg, "pConsole", (void **)&pThis->pConsole); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"pConsole\" value! rc=%Rrc\n", rc)); + return rc; + } + } + + rc = CFGMR3QueryU32(pCfg, "First", &pThis->iFirstLUN); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + pThis->iFirstLUN = 0; + else if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"First\" value! rc=%Rrc\n", rc)); + return rc; + } + + rc = CFGMR3QueryU32(pCfg, "Last", &pThis->iLastLUN); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + pThis->iLastLUN = 0; + else if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: Failed to query the \"Last\" value! rc=%Rrc\n", rc)); + return rc; + } + if (pThis->iFirstLUN > pThis->iLastLUN) + { + AssertMsgFailed(("Configuration error: Invalid unit range %u-%u\n", pThis->iFirstLUN, pThis->iLastLUN)); + return VERR_GENERAL_FAILURE; + } + + /* + * 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..8235d581 --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImpl2.cpp @@ -0,0 +1,5990 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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" +#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 <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <VBox/param.h> +#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/version.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#ifdef VBOX_WITH_CROGL +# include <VBox/HostServices/VBoxCrOpenGLSvc.h> +#include <VBox/VBoxOGL.h> +#endif +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +# include <VBox/com/defs.h> +# include <VBox/com/array.h> +# include "HGCM.h" /** @todo It should be possible to register a service + * extension using a VMMDev callback. */ +# 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. + */ +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). */ + } +}; + +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; +} + +/** + * @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 rc = 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("iMac")) + || !strncmp(szProdName, RT_STR_TUPLE("Xserve")) + ) + && !strchr(szProdName, ' ') /* no spaces */ + && RT_C_IS_DIGIT(szProdName[strlen(szProdName) - 1]) /* version number */ + ) + *pfGetKeyFromRealSMC = true; + } + + int rc = VINF_SUCCESS; +#endif + + return rc; +} + + +/* + * 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("%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. + */ +static void InsertConfigString(PCFGMNODE pNode, + const char *pcszName, + const char *pcszValue) +{ + int vrc = CFGMR3InsertString(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. + */ +static void InsertConfigString(PCFGMNODE pNode, + const char *pcszName, + const Utf8Str &rStrValue) +{ + int vrc = CFGMR3InsertStringN(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. + */ +static void InsertConfigString(PCFGMNODE pNode, + const char *pcszName, + const Bstr &rBstrValue) +{ + InsertConfigString(pNode, pcszName, Utf8Str(rBstrValue)); +} + +/** + * 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. + */ +static void InsertConfigBytes(PCFGMNODE pNode, + const char *pcszName, + const void *pvBytes, + size_t cbBytes) +{ + int vrc = CFGMR3InsertBytes(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. + */ +static void InsertConfigInteger(PCFGMNODE pNode, + const char *pcszName, + uint64_t u64Integer) +{ + int vrc = CFGMR3InsertInteger(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. + */ +static void InsertConfigNode(PCFGMNODE pNode, + const char *pcszName, + PCFGMNODE *ppChild) +{ + int vrc = CFGMR3InsertNode(pNode, pcszName, ppChild); + if (RT_FAILURE(vrc)) + throw ConfigError("CFGMR3InsertNode", vrc, pcszName); +} + +/** + * Helper that calls CFGMR3RemoveValue and throws an RTCError if that fails. + * + * @param pNode See CFGMR3RemoveValue. + * @param pcszName See CFGMR3RemoveValue. + */ +static void RemoveConfigValue(PCFGMNODE pNode, + const char *pcszName) +{ + int vrc = CFGMR3RemoveValue(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 + +static int 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 VMR3SetError(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 + + +void Console::i_attachStatusDriver(PCFGMNODE pCtlInst, PPDMLED *papLeds, + uint64_t uFirst, uint64_t uLast, + Console::MediumAttachmentMap *pmapMediumAttachments, + const char *pcszDevice, unsigned uInstance) +{ + PCFGMNODE pLunL0, pCfg; + InsertConfigNode(pCtlInst, "LUN#999", &pLunL0); + InsertConfigString(pLunL0, "Driver", "MainStatus"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "papLeds", (uintptr_t)papLeds); + if (pmapMediumAttachments) + { + InsertConfigInteger(pCfg, "pmapMediumAttachments", (uintptr_t)pmapMediumAttachments); + InsertConfigInteger(pCfg, "pConsole", (uintptr_t)this); + AssertPtr(pcszDevice); + Utf8Str deviceInstance = Utf8StrFmt("%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. + * + * @param pUVM The user mode VM handle. + * @param pVM The cross context VM handle. + * @param pvConsole Pointer to the VMPowerUpTask object. + * @return VBox status code. + * + * @note Locks the Console object for writing. + */ +DECLCALLBACK(int) Console::i_configConstructor(PUVM pUVM, PVM pVM, 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; + VMR3RetainUVM(pUVM); + int vrc; + try + { + vrc = pConsole->i_configConstructorInner(pUVM, pVM, &alock); + } + catch (...) + { + vrc = VERR_UNEXPECTED_EXCEPTION; + } + if (RT_FAILURE(vrc)) + { + pConsole->mpUVM = NULL; + VMR3ReleaseUVM(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 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, AutoWriteLock *pAlock) +{ + RT_NOREF(pVM /* when everything is disabled */); + VMMDev *pVMMDev = m_pVMMDev; Assert(pVMMDev); + ComPtr<IMachine> pMachine = i_machine(); + + int rc; + 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(); + + hrc = pMachine->COMGETTER(HardwareUUID)(bstr.asOutParam()); H(); + RTUUID HardwareUuid; + rc = RTUuidFromUtf16(&HardwareUuid, bstr.raw()); + AssertRCReturn(rc, rc); + + 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 paravirtProvider; + hrc = pMachine->GetEffectiveParavirtProvider(¶virtProvider); H(); + + Bstr strParavirtDebug; + hrc = pMachine->COMGETTER(ParavirtDebug)(strParavirtDebug.asOutParam()); 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; + } + + BusAssignmentManager *pBusMgr = mBusMgr = BusAssignmentManager::createInstance(chipsetType); + + 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())); + + BOOL fIOAPIC; + hrc = biosSettings->COMGETTER(IOAPICEnabled)(&fIOAPIC); H(); + + 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; + if (!pGuestOSType.isNull()) + { + Bstr guestTypeFamilyId; + hrc = pGuestOSType->COMGETTER(FamilyId)(guestTypeFamilyId.asOutParam()); H(); + fOsXGuest = guestTypeFamilyId == Bstr("MacOS"); + } + + ULONG maxNetworkAdapters; + hrc = systemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); H(); + + /* + * Get root node first. + * This is the only node in the tree. + */ + PCFGMNODE pRoot = CFGMR3GetRootU(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); +#ifdef VBOX_WITH_RAW_MODE + InsertConfigInteger(pRoot, "RawR3Enabled", 1); /* boolean */ + InsertConfigInteger(pRoot, "RawR0Enabled", 1); /* boolean */ + /** @todo Config: RawR0, PATMEnabled and CSAMEnabled needs attention later. */ + InsertConfigInteger(pRoot, "PATMEnabled", 1); /* boolean */ + InsertConfigInteger(pRoot, "CSAMEnabled", 1); /* boolean */ +#endif + +#ifdef VBOX_WITH_RAW_RING1 + if (osTypeId == "QNX") + { + /* QNX needs special treatment in raw mode due to its use of ring-1. */ + InsertConfigInteger(pRoot, "RawR1Enabled", 1); /* boolean */ + } +#endif + + 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); + } + + /* Expose CMPXCHG16B. Currently a hack. */ + if ( osTypeId == "Windows81_64" + || osTypeId == "Windows2012_64" + || osTypeId == "Windows10_64" + || osTypeId == "Windows2016_64") + { + LogRel(("Enabling CMPXCHG16B for Windows 8.1 / 2k12 or newer guests\n")); + InsertConfigInteger(pIsaExts, "CMPXCHG16B", 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(); + InsertConfigInteger(pRoot, "EnablePAE", fEnablePAE); + + /* 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. */ + hrc = pMachine->COMGETTER(CPUProfile)(bstr.asOutParam()); H(); + InsertConfigString(pCPUM, "GuestCpuName", bstr); + + /* + * 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 ( bstr.equals("Intel 80386") /* just for now */ + || bstr.equals("Intel 80286") + || bstr.equals("Intel 80186") + || bstr.equals("Nec V20") + || bstr.equals("Intel 8086") ) + { + InsertConfigInteger(pEM, "IemExecutesAll", true); + if (!bstr.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. + */ + BOOL fSupportsHwVirtEx; + hrc = host->GetProcessorFeature(ProcessorFeature_HWVirtEx, &fSupportsHwVirtEx); H(); + + BOOL fIsGuest64Bit; + hrc = pMachine->GetCPUProperty(CPUPropertyType_LongMode, &fIsGuest64Bit); H(); + if (fIsGuest64Bit) + { + BOOL fSupportsLongMode; + hrc = host->GetProcessorFeature(ProcessorFeature_LongMode, &fSupportsLongMode); H(); + if (!fSupportsLongMode) + { + LogRel(("WARNING! 64-bit guest type selected but the host CPU does NOT support 64-bit.\n")); + fIsGuest64Bit = FALSE; + } + if (!fSupportsHwVirtEx) + { + LogRel(("WARNING! 64-bit guest type selected but the host CPU does NOT support HW virtualization.\n")); + fIsGuest64Bit = FALSE; + } + } + + /* Sanitize valid/useful APIC combinations, see @bugref{8868}. */ + if (!fEnableAPIC) + { + if (fIsGuest64Bit) + return VMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Cannot disable the APIC for a 64-bit guest.")); + if (cCpus > 1) + return VMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Cannot disable the APIC for an SMP guest.")); + if (fIOAPIC) + { + return VMR3SetError(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; +#ifdef VBOX_WITH_RAW_MODE + /* - With more than 4GB PGM will use different RAMRANGE sizes for raw + mode and hv mode to optimize lookup times. + - With more than one virtual CPU, raw-mode isn't a fallback option. + - With a 64-bit guest, raw-mode isn't a fallback option either. */ + fHMForced = fHMEnabled + && ( cbRam + cbRamHole > _4G + || cCpus > 1 + || fIsGuest64Bit); +# ifdef RT_OS_DARWIN + fHMForced = fHMEnabled; +# endif + if (fHMForced) + { + if (cbRam + cbRamHole > _4G) + LogRel(("fHMForced=true - Lots of RAM\n")); + if (cCpus > 1) + LogRel(("fHMForced=true - SMP\n")); + if (fIsGuest64Bit) + LogRel(("fHMForced=true - 64-bit guest\n")); +# ifdef RT_OS_DARWIN + LogRel(("fHMForced=true - Darwin host\n")); +# endif + } +#else /* !VBOX_WITH_RAW_MODE */ + fHMEnabled = fHMForced = TRUE; + LogRel(("fHMForced=true - No raw-mode support in this build!\n")); +#endif /* !VBOX_WITH_RAW_MODE */ + 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); +#if ARCH_BITS == 32 /* The recompiler must use VBoxREM64 (32-bit host only). */ + PCFGMNODE pREM; + InsertConfigNode(pRoot, "REM", &pREM); + InsertConfigInteger(pREM, "64bitEnabled", 1); +#endif + + /** @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); + + /* 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); + + /* Reset overwrite. */ + if (i_isResetTurnedIntoPowerOff()) + InsertConfigInteger(pRoot, "PowerOffInsteadOfReset", 1); + + /* Use NEM rather than HM. */ + BOOL fUseNativeApi = false; + hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UseNativeApi, &fUseNativeApi); H(); + InsertConfigInteger(pHM, "UseNEMInstead", fUseNativeApi); + + /* + * 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 (paravirtProvider) + { + 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 paravirtProvider=%d\n", paravirtProvider)); + return VMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Invalid paravirt. provider '%d'"), + paravirtProvider); + } + 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 (paravirtProvider == 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 VMR3SetError(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; + } + } + } + + /* + * 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]; + rc = RTPathAppPrivateArch(szPathVBoxC, RTPATH_MAX - sizeof("/components/VBoxC")); AssertRC(rc); + 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. + */ + PCFGMNODE pAc; + PCFGMNODE pAcFile; + PCFGMNODE pAcFileBwGroups; + ComPtr<IBandwidthControl> bwCtrl; + com::SafeIfaceArray<IBandwidthGroup> bwGroups; + + hrc = pMachine->COMGETTER(BandwidthControl)(bwCtrl.asOutParam()); H(); + + hrc = bwCtrl->GetAllBandwidthGroups(ComSafeArrayAsOutParam(bwGroups)); H(); + + InsertConfigNode(pPDM, "AsyncCompletion", &pAc); + InsertConfigNode(pAc, "File", &pAcFile); + InsertConfigNode(pAcFile, "BwGroups", &pAcFileBwGroups); +#ifdef VBOX_WITH_NETSHAPER + PCFGMNODE pNetworkShaper; + PCFGMNODE pNetworkBwGroups; + + InsertConfigNode(pPDM, "NetworkShaper", &pNetworkShaper); + InsertConfigNode(pNetworkShaper, "BwGroups", &pNetworkBwGroups); +#endif /* VBOX_WITH_NETSHAPER */ + + for (size_t i = 0; i < bwGroups.size(); i++) + { + Bstr strName; + LONG64 cMaxBytesPerSec; + BandwidthGroupType_T enmType; + + hrc = bwGroups[i]->COMGETTER(Name)(strName.asOutParam()); H(); + hrc = bwGroups[i]->COMGETTER(Type)(&enmType); H(); + hrc = bwGroups[i]->COMGETTER(MaxBytesPerSec)(&cMaxBytesPerSec); H(); + + if (strName.isEmpty()) + return VMR3SetError(pUVM, VERR_CFGM_NO_NODE, RT_SRC_POS, + N_("No bandwidth group name specified")); + + 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 + } + + /* + * 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; + rc = getSmcDeviceKey(virtualBox, pMachine, &strKey, &fGetKeyFromRealSMC); + AssertRCReturn(rc, rc); + + 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); + + 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"); + InsertConfigNode(pLunL1, "Config", &pCfg); + Keyboard *pKeyboard = mKeyboard; + InsertConfigInteger(pCfg, "Object", (uintptr_t)pKeyboard); + + Mouse *pMouse = mMouse; + PointingHIDType_T aPointingHID; + hrc = pMachine->COMGETTER(PointingHIDType)(&aPointingHID); H(); + 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"); + InsertConfigNode(pLunL1, "Config", &pCfg); + InsertConfigInteger(pCfg, "Object", (uintptr_t)pMouse); + + /* + * 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); + } + } + + /* + * 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. + */ + GraphicsControllerType_T enmGraphicsController; + hrc = pMachine->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. */ + RT_FALL_THROUGH(); + case GraphicsControllerType_VBoxSVGA: +#endif + case GraphicsControllerType_VBoxVGA: + rc = i_configGraphicsController(pDevices, enmGraphicsController, pBusMgr, pMachine, biosSettings, + RT_BOOL(fHMEnabled)); + if (FAILED(rc)) + return rc; + break; + default: + AssertMsgFailed(("Invalid graphicsController=%d\n", enmGraphicsController)); + return VMR3SetError(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)); + InsertConfigNode(pBiosCfg, "NetBoot", &pNetBootCfg); + InsertConfigInteger(pBiosCfg, "McfgBase", uMcfgBase); + InsertConfigInteger(pBiosCfg, "McfgLength", cbMcfgLength); + + DeviceType_T bootDevice; + AssertMsgReturn(SchemaDefs::MaxBootPosition <= 9, ("Too many boot devices %d\n", SchemaDefs::MaxBootPosition), + VERR_INVALID_PARAMETER); + + for (ULONG pos = 1; pos <= SchemaDefs::MaxBootPosition; ++pos) + { + hrc = pMachine->GetBootOrder(pos, &bootDevice); H(); + + char szParamName[] = "BootDeviceX"; + szParamName[sizeof(szParamName) - 2] = (char)(pos - 1 + '0'); + + const char *pszBootDevice; + switch (bootDevice) + { + 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 bootDevice=%d\n", bootDevice)); + return VMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Invalid boot device '%d'"), bootDevice); + } + 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; + + Utf8Str efiRomFile; + rc = findEfiRom(virtualBox, eFwType, &efiRomFile); + AssertRCReturn(rc, rc); + + /* Get boot args */ + Utf8Str bootArgs; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiBootArgs", &bootArgs); + + /* Get device props */ + Utf8Str deviceProps; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiDeviceProps", &deviceProps); + + /* 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", efiRomFile); + InsertConfigString(pCfg, "BootArgs", bootArgs); + InsertConfigString(pCfg, "DeviceProps", deviceProps); + InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC); + InsertConfigInteger(pCfg, "APIC", uFwAPIC); + InsertConfigBytes(pCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid)); + InsertConfigInteger(pCfg, "64BitEntry", f64BitEntry); /* boolean */ + 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); + } + InsertConfigNode(pInst, "LUN#0", &pLunL0); + InsertConfigString(pLunL0, "Driver", "NvramStorage"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigInteger(pCfg, "Object", (uintptr_t)mNvram); +#ifdef DEBUG_vvl + InsertConfigInteger(pCfg, "PermanentSave", 1); +#endif + } + + /* + * 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; + rc = 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; + rc = 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, &mapUSBLed[0], 0, 0, NULL, NULL, 0); + } +#ifdef VBOX_WITH_EHCI + else if (enmCtrlType == USBControllerType_EHCI) + { + /* + * USB 2.0 is only available if the proper ExtPack is installed. + * + * Note. Configuring EHCI 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_pszUsbExtPackName = "Oracle VM VirtualBox Extension Pack"; + if (mptrExtPackManager->i_isExtPackUsable(s_pszUsbExtPackName)) +# endif + { + 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, &mapUSBLed[1], 0, 0, NULL, NULL, 0); + } +# ifdef VBOX_WITH_EXTPACK + else + { + /* Always fatal! Up to VBox 4.0.4 we allowed to start the VM anyway + * but this induced problems when the user saved + restored the VM! */ + return VMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Implementation of the USB 2.0 controller not found!\n" + "Because the USB 2.0 controller state is part of the saved " + "VM state, the VM cannot be started. To fix " + "this problem, either install the '%s' or disable USB 2.0 " + "support in the VM settings.\n" + "Note! This error could also mean that an incompatible version of " + "the '%s' is installed"), + s_pszUsbExtPackName, s_pszUsbExtPackName); + } +# endif + } +#endif + else if (enmCtrlType == USBControllerType_XHCI) + { + /* + * USB 3.0 is only available if the proper ExtPack is installed. + * + * Note. Configuring EHCI 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_pszUsbExtPackName = "Oracle VM VirtualBox Extension Pack"; + if (mptrExtPackManager->i_isExtPackUsable(s_pszUsbExtPackName)) +# endif + { + 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, &mapUSBLed[0], 0, 1, NULL, NULL, 0); + } +# ifdef VBOX_WITH_EXTPACK + else + { + /* Always fatal. */ + return VMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, + N_("Implementation of the USB 3.0 controller not found!\n" + "Because the USB 3.0 controller state is part of the saved " + "VM state, the VM cannot be started. To fix " + "this problem, either install the '%s' or disable USB 3.0 " + "support in the VM settings"), + s_pszUsbExtPackName); + } +# endif + } + } /* 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); + InsertConfigInteger(pCfg, "Object", (uintptr_t)mUsbCardReader); +# endif + } +#endif + + /* Virtual USB Mouse/Tablet */ + if ( aPointingHID == PointingHIDType_USBMouse + || aPointingHID == PointingHIDType_USBTablet + || aPointingHID == PointingHIDType_USBMultiTouch) + { + 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"); + InsertConfigNode(pLunL1, "Config", &pCfg); + InsertConfigInteger(pCfg, "Object", (uintptr_t)pMouse); + } + if (aPointingHID == PointingHIDType_USBMultiTouch) + { + 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"); + InsertConfigNode(pLunL1, "Config", &pCfg); + InsertConfigInteger(pCfg, "Object", (uintptr_t)pMouse); + } + + /* Virtual USB Keyboard */ + KeyboardHIDType_T aKbdHID; + hrc = pMachine->COMGETTER(KeyboardHIDType)(&aKbdHID); H(); + 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"); + InsertConfigNode(pLunL1, "Config", &pCfg); + pKeyboard = mKeyboard; + InsertConfigInteger(pCfg, "Object", (uintptr_t)pKeyboard); + } + } + + /* + * Storage controllers. + */ + com::SafeIfaceArray<IStorageController> ctrls; + PCFGMNODE aCtrlNodes[StorageControllerType_NVMe + 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; + rc = ctrls[i]->COMGETTER(ControllerType)(&enmCtrlType); H(); + AssertRelease((unsigned)enmCtrlType < RT_ELEMENTS(aCtrlNodes) + || enmCtrlType == StorageControllerType_USB); + + StorageBus_T enmBus; + rc = ctrls[i]->COMGETTER(Bus)(&enmBus); H(); + + Bstr controllerName; + rc = ctrls[i]->COMGETTER(Name)(controllerName.asOutParam()); H(); + + ULONG ulInstance = 999; + rc = ctrls[i]->COMGETTER(Instance)(&ulInstance); H(); + + BOOL fUseHostIOCache; + rc = ctrls[i]->COMGETTER(UseHostIOCache)(&fUseHostIOCache); H(); + + BOOL fBootable; + rc = 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 */ + Assert(cLedScsi >= 16); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedScsi], 0, 15, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedScsi]; + 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 */ + Assert(cLedScsi >= 16); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedScsi], 0, 15, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedScsi]; + 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 */ + AssertRelease(cPorts <= cLedSata); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedSata], 0, cPorts - 1, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedSata]; + 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 */ + Assert(cLedIde >= 4); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedIde], 0, 3, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedIde]; + + /* 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 */ + Assert(cLedFloppy >= 2); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedFloppy], 0, 1, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedFloppy]; + 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 */ + Assert(cLedSas >= 8); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedSas], 0, 7, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedSas]; + 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 VMR3SetError(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 */ + AssertRelease(cPorts <= cLedSata); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedNvme], 0, cPorts - 1, + &mapMediumAttachments, pszCtrlDev, ulInstance); + paLedDevType = &maStorageDevType[iLedNvme]; + 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]; + rc = 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, + paLedDevType, + NULL /* ppLunL0 */); + if (RT_FAILURE(rc)) + return rc; + } + 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 */ + std::list<BootNic> llBootNics; + for (ULONG ulInstance = 0; ulInstance < maxNetworkAdapters; ++ulInstance) + { + ComPtr<INetworkAdapter> networkAdapter; + hrc = pMachine->GetNetworkAdapter(ulInstance, 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: + 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 */ + default: + AssertMsgFailed(("Invalid network adapter type '%d' for slot '%d'", + adapterType, ulInstance)); + return VMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Invalid network adapter type '%d' for slot '%d'"), + adapterType, ulInstance); + } + + InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).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 (ulInstance) + { + case 0: + iPCIDeviceNo = 3; + break; + case 1: case 2: case 3: + iPCIDeviceNo = ulInstance - 1 + 8; + break; + case 4: case 5: case 6: case 7: + iPCIDeviceNo = ulInstance - 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 = ulInstance; + /* 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 two 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_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_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; + 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)); + + /* + * 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, &mapNetworkLeds[ulInstance], 0, 0, NULL, NULL, 0); + + /* + * Configure the network card now + */ + bool fIgnoreConnectFailure = mMachineState == MachineState_Restoring; + rc = i_configNetwork(pszAdapterName, + ulInstance, + 0, + networkAdapter, + pCfg, + pLunL0, + pInst, + false /*fAttachDetach*/, + fIgnoreConnectFailure); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * 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) + { + rc = i_configSerialPort(pInst, eHostMode, Utf8Str(bstr).c_str(), RT_BOOL(fServer)); + if (RT_FAILURE(rc)) + return rc; + } + } + + /* + * 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); + InsertConfigInteger(pCfg, "Object", (uintptr_t)pVMMDev); + + /* + * Attach the status driver. + */ + i_attachStatusDriver(pInst, &mapSharedFolderLed, 0, 0, NULL, NULL, 0); + + /* + * AC'97 ICH / SoundBlaster16 audio / Intel HD Audio. + */ + BOOL fAudioEnabled = FALSE; + ComPtr<IAudioAdapter> audioAdapter; + hrc = pMachine->COMGETTER(AudioAdapter)(audioAdapter.asOutParam()); H(); + if (audioAdapter) + { + hrc = audioAdapter->COMGETTER(Enabled)(&fAudioEnabled); H(); + } + + if (fAudioEnabled) + { + Utf8Str strAudioDevice; + + AudioControllerType_T audioController; + hrc = audioAdapter->COMGETTER(AudioController)(&audioController); H(); + AudioCodecType_T audioCodec; + hrc = audioAdapter->COMGETTER(AudioCodec)(&audioCodec); H(); + + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp); + const uint64_t fDebugEnabled = (strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1")) ? 1 : 0; + + Utf8Str strDebugPathOut; + GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut); + + /** @todo Implement an audio device class, similar to the audio backend class, to construct the common stuff + * without duplicating (more) code. */ + + switch (audioController) + { + case AudioControllerType_AC97: + { + /* ICH AC'97. */ + strAudioDevice = "ichac97"; + + InsertConfigNode (pDevices, strAudioDevice.c_str(), &pDev); + InsertConfigNode (pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice(strAudioDevice.c_str(), pInst); H(); + InsertConfigNode (pInst, "Config", &pCfg); + switch (audioCodec) + { + case AudioCodecType_STAC9700: + InsertConfigString(pCfg, "Codec", "STAC9700"); + break; + case AudioCodecType_AD1980: + InsertConfigString(pCfg, "Codec", "AD1980"); + break; + default: AssertFailedBreak(); + } + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + InsertConfigString (pCfg, "DebugPathOut", strDebugPathOut); + break; + } + case AudioControllerType_SB16: + { + /* Legacy SoundBlaster16. */ + strAudioDevice = "sb16"; + + InsertConfigNode (pDevices, strAudioDevice.c_str(), &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); + break; + } + case AudioControllerType_HDA: + { + /* Intel HD Audio. */ + strAudioDevice = "hda"; + + InsertConfigNode (pDevices, strAudioDevice.c_str(), &pDev); + InsertConfigNode (pDev, "0", &pInst); + InsertConfigInteger(pInst, "Trusted", 1); /* boolean */ + hrc = pBusMgr->assignPCIDevice(strAudioDevice.c_str(), pInst); H(); + InsertConfigNode (pInst, "Config", &pCfg); + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + InsertConfigString (pCfg, "DebugPathOut", strDebugPathOut); + break; + } + default: 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. + */ + Utf8Str strAudioDriver; + + AudioDriverType_T audioDriver; + hrc = audioAdapter->COMGETTER(AudioDriver)(&audioDriver); H(); + switch (audioDriver) + { + case AudioDriverType_Null: + { + strAudioDriver = "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: + { + strAudioDriver = "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. */ + strAudioDriver = "OSSAudio"; + break; + } +#endif +#ifdef VBOX_WITH_AUDIO_OSS + case AudioDriverType_OSS: + { + strAudioDriver = "OSSAudio"; + break; + } +#endif +#ifdef VBOX_WITH_AUDIO_ALSA + case AudioDriverType_ALSA: + { + strAudioDriver = "ALSAAudio"; + break; + } +#endif +#ifdef VBOX_WITH_AUDIO_PULSE + case AudioDriverType_Pulse: + { + strAudioDriver = "PulseAudio"; + break; + } +#endif +#ifdef RT_OS_DARWIN + case AudioDriverType_CoreAudio: + { + strAudioDriver = "CoreAudio"; + break; + } +#endif + default: AssertFailedBreak(); + } + + unsigned uAudioLUN = 0; + + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%RU8", uAudioLUN); + rc = i_configAudioDriver(audioAdapter, virtualBox, pMachine, pLunL0, + strAudioDriver.c_str()); + if (RT_SUCCESS(rc)) + uAudioLUN++; + +#ifdef VBOX_WITH_AUDIO_VRDE + /* Insert dummy audio driver to have the LUN configured. */ + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%RU8", uAudioLUN); + InsertConfigString(pLunL0, "Driver", "AUDIO"); + AudioDriverCfg DrvCfgVRDE(strAudioDevice, 0 /* Instance */, uAudioLUN, "AudioVRDE"); + rc = mAudioVRDE->InitializeConfig(&DrvCfgVRDE); + if (RT_SUCCESS(rc)) + uAudioLUN++; +#endif /* VBOX_WITH_AUDIO_VRDE */ + +#ifdef VBOX_WITH_AUDIO_RECORDING + /* Insert dummy audio driver to have the LUN configured. */ + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%RU8", uAudioLUN); + InsertConfigString(pLunL0, "Driver", "AUDIO"); + AudioDriverCfg DrvCfgVideoRec(strAudioDevice, 0 /* Instance */, uAudioLUN, "AudioVideoRec"); + rc = Recording.mAudioRec->InitializeConfig(&DrvCfgVideoRec); + if (RT_SUCCESS(rc)) + uAudioLUN++; +#endif /* VBOX_WITH_AUDIO_RECORDING */ + + if (fDebugEnabled) + { +#ifdef VBOX_WITH_AUDIO_DEBUG + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%RU8", uAudioLUN); + rc = i_configAudioDriver(audioAdapter, virtualBox, pMachine, pLunL0, + "DebugAudio"); + if (RT_SUCCESS(rc)) + uAudioLUN++; +#endif /* VBOX_WITH_AUDIO_DEBUG */ + + /* + * Tweak the logging groups. + */ + Utf8Str strLogGroups = "drv_host_audio.e.l.l2.l3.f+" \ + "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"; + + rc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), strLogGroups.c_str()); + if (RT_FAILURE(rc)) + LogRel(("Audio: Setting debug logging failed, rc=%Rrc\n", rc)); + } + +#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT + /** @todo Make this a runtime-configurable entry! */ + + /* + * The ValidationKit backend. + */ + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%RU8", uAudioLUN); + rc = i_configAudioDriver(audioAdapter, virtualBox, pMachine, pLunL0, + "ValidationKitAudio"); + if (RT_SUCCESS(rc)) + uAudioLUN++; +#endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */ + } + + /* + * Shared Clipboard. + */ + { + ClipboardMode_T mode = ClipboardMode_Disabled; + hrc = pMachine->COMGETTER(ClipboardMode)(&mode); H(); + + if (/* mode != ClipboardMode_Disabled */ true) + { + /* Load the service */ + rc = pVMMDev->hgcmLoadService("VBoxSharedClipboard", "VBoxSharedClipboard"); + if (RT_FAILURE(rc)) + { + LogRel(("Shared clipboard is not available, rc=%Rrc\n", rc)); + /* That is not a fatal failure. */ + rc = VINF_SUCCESS; + } + else + { + LogRel(("Shared clipboard service loaded\n")); + + i_changeClipboardMode(mode); + + /* Setup the service. */ + VBOXHGCMSVCPARM parm; + HGCMSvcSetU32(&parm, !i_useHostClipboard()); + pVMMDev->hgcmHostCall("VBoxSharedClipboard", + VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS, 1, &parm); + } + } + } + + /* + * HGCM HostChannel. + */ + { + Bstr value; + hrc = pMachine->GetExtraData(Bstr("HGCM/HostChannel").raw(), + value.asOutParam()); + + if ( hrc == S_OK + && value == "1") + { + rc = pVMMDev->hgcmLoadService("VBoxHostChannel", "VBoxHostChannel"); + if (RT_FAILURE(rc)) + { + LogRel(("VBoxHostChannel is not available, rc=%Rrc\n", rc)); + /* That is not a fatal failure. */ + rc = 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 */ + rc = pVMMDev->hgcmLoadService("VBoxDragAndDropSvc", "VBoxDragAndDropSvc"); + if (RT_FAILURE(rc)) + { + LogRel(("Drag and drop service is not available, rc=%Rrc\n", rc)); + /* That is not a fatal failure. */ + rc = VINF_SUCCESS; + } + else + { + HGCMSVCEXTHANDLE hDummy; + rc = HGCMHostRegisterServiceExtension(&hDummy, "VBoxDragAndDropSvc", + &GuestDnD::notifyDnDDispatcher, + GuestDnDInst()); + if (RT_FAILURE(rc)) + Log(("Cannot register VBoxDragAndDropSvc extension, rc=%Rrc\n", rc)); + else + { + LogRel(("Drag and drop service loaded\n")); + rc = i_changeDnDMode(enmMode); + } + } + } +#endif /* VBOX_WITH_DRAG_AND_DROP */ + +#ifdef VBOX_WITH_CROGL + /* + * crOpenGL. + */ + { + BOOL fEnabled3D = false; + hrc = pMachine->COMGETTER(Accelerate3DEnabled)(&fEnabled3D); H(); + + if ( fEnabled3D +# ifdef VBOX_WITH_VMSVGA3D + && enmGraphicsController == GraphicsControllerType_VBoxVGA +# endif + ) + { + BOOL fSupports3D = VBoxOglIs3DAccelerationSupported(); + if (!fSupports3D) + return VMR3SetError(pUVM, VERR_NOT_AVAILABLE, RT_SRC_POS, + N_("This VM was configured to use 3D acceleration. However, the " + "3D support of the host is not working properly and the " + "VM cannot be started. To fix this problem, either " + "fix the host 3D support (update the host graphics driver?) " + "or disable 3D acceleration in the VM settings")); + + /* Load the service. */ + rc = pVMMDev->hgcmLoadService("VBoxSharedCrOpenGL", "VBoxSharedCrOpenGL"); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to load Shared OpenGL service, rc=%Rrc\n", rc)); + /* That is not a fatal failure. */ + rc = VINF_SUCCESS; + } + else + { + LogRel(("Shared OpenGL service loaded -- 3D enabled\n")); + + /* Setup the service. */ + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_PTR; + + parm.u.pointer.addr = (IConsole *)(Console *)this; + parm.u.pointer.size = sizeof(IConsole *); + + rc = pVMMDev->hgcmHostCall("VBoxSharedCrOpenGL", SHCRGL_HOST_FN_SET_CONSOLE, + SHCRGL_CPARMS_SET_CONSOLE, &parm); + if (!RT_SUCCESS(rc)) + AssertMsgFailed(("SHCRGL_HOST_FN_SET_CONSOLE failed with %Rrc\n", rc)); + + parm.u.pointer.addr = pVM; + parm.u.pointer.size = sizeof(pVM); + rc = pVMMDev->hgcmHostCall("VBoxSharedCrOpenGL", + SHCRGL_HOST_FN_SET_VM, SHCRGL_CPARMS_SET_VM, &parm); + if (!RT_SUCCESS(rc)) + AssertMsgFailed(("SHCRGL_HOST_FN_SET_VM failed with %Rrc\n", rc)); + } + } + } +#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); + } + } + 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. + * And only with hardware virtualization (@bugref{5454}). */ + if ( (fEnablePAE || fIsGuest64Bit) + && fSupportsHwVirtEx /* HwVirt needs to be supported by the host + otherwise VMM falls back to raw mode */ + && fHMEnabled /* HwVirt needs to be enabled in VM config */) + 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]); + + 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 rc2 = RTPathUserHome(szHomeDir, sizeof(szHomeDir) - 1); + if (RT_FAILURE(rc2)) + szHomeDir[0] = '\0'; + RTPathEnsureTrailingSeparator(szHomeDir, sizeof(szHomeDir)); + + + Utf8Str strPath; + strPath.append(strSettingsPath).append("debug/;"); + strPath.append(strSettingsPath).append(";"); + 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); + } + } + catch (ConfigError &x) + { + // InsertConfig threw something: + 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(rc)) + { + pAlock->release(); + rc = mptrExtPackManager->i_callAllVmConfigureVmmHooks(this, pVM); + pAlock->acquire(); + } +#endif + + /* + * Apply the CFGM overlay. + */ + if (RT_SUCCESS(rc)) + rc = i_configCfgmOverlay(pRoot, virtualBox, pMachine); + + /* + * Dump all extradata API settings tweaks, both global and per VM. + */ + if (RT_SUCCESS(rc)) + rc = i_configDumpAPISettingsTweaks(virtualBox, pMachine); + +#undef H + + pAlock->release(); /* Avoid triggering the lock order inversion check. */ + + /* + * Register VM state change handler. + */ + int rc2 = VMR3AtStateRegister(pUVM, Console::i_vmstateChangeCallback, this); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + + /* + * Register VM runtime error handler. + */ + rc2 = VMR3AtRuntimeErrorRegister(pUVM, Console::i_atVMRuntimeErrorCallback, this); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + + pAlock->acquire(); + + LogFlowFunc(("vrc = %Rrc\n", rc)); + LogFlowFuncLeave(); + + return rc; +} + +/** + * Retrieves an uint32_t value from the audio driver's extra data branch + * (VBoxInternal2/Audio/DriverName/Value), or, if not found, use global branch + * (VBoxInternal2/Audio/Value). + * + * The driver branch always supersedes the global branch. + * If both branches are not found (empty), return the given default value. + * + * @return VBox status code. + * @param pVirtualBox Pointer to IVirtualBox instance. + * @param pMachine Pointer to IMachine instance. + * @param pszDriverName Audio driver name to retrieve value for. + * @param pszValue Value name to retrieve. + * @param uDefault Default value to return if value not found / invalid. + */ +uint32_t Console::i_getAudioDriverValU32(IVirtualBox *pVirtualBox, IMachine *pMachine, + const char *pszDriverName, const char *pszValue, uint32_t uDefault) +{ + Utf8Str strTmp; + + Utf8StrFmt strPathDrv("VBoxInternal2/Audio/%s/%s", pszDriverName, pszValue); + GetExtraDataBoth(pVirtualBox, pMachine, strPathDrv.c_str(), &strTmp); + if (strTmp.isEmpty()) + { + Utf8StrFmt strPathGlobal("VBoxInternal2/Audio/%s", pszValue); + GetExtraDataBoth(pVirtualBox, pMachine, strPathGlobal.c_str(), &strTmp); + if (strTmp.isNotEmpty()) + return strTmp.toUInt32(); + } + else /* Return driver-specific value. */ + return strTmp.toUInt32(); + + return uDefault; +} + +/** + * Configures an audio driver via CFGM by getting (optional) values from extra data. + * + * @return VBox status code. + * @param pAudioAdapter Pointer to audio adapter instance. Needed for the driver's input / output configuration. + * @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. + */ +int Console::i_configAudioDriver(IAudioAdapter *pAudioAdapter, IVirtualBox *pVirtualBox, IMachine *pMachine, + PCFGMNODE pLUN, const char *pszDrvName) +{ +#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR) + + HRESULT hrc; + + Utf8Str strTmp; + GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp); + const uint64_t fDebugEnabled = (strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1")) ? 1 : 0; + + BOOL fAudioEnabledIn = FALSE; + hrc = pAudioAdapter->COMGETTER(EnabledIn)(&fAudioEnabledIn); H(); + BOOL fAudioEnabledOut = FALSE; + hrc = pAudioAdapter->COMGETTER(EnabledOut)(&fAudioEnabledOut); + + InsertConfigString(pLUN, "Driver", "AUDIO"); + + PCFGMNODE pCfg; + InsertConfigNode(pLUN, "Config", &pCfg); + InsertConfigString (pCfg, "DriverName", pszDrvName); + InsertConfigInteger(pCfg, "InputEnabled", fAudioEnabledIn); + InsertConfigInteger(pCfg, "OutputEnabled", fAudioEnabledOut); + + if (fDebugEnabled) + { + InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled); + + Utf8Str strDebugPathOut; + GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut); + InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut.c_str()); + } + + InsertConfigInteger(pCfg, "PeriodSizeMs", + i_getAudioDriverValU32(pVirtualBox, pMachine, pszDrvName, "PeriodSizeMs", 0 /* Default */)); + InsertConfigInteger(pCfg, "BufferSizeMs", + i_getAudioDriverValU32(pVirtualBox, pMachine, pszDrvName, "BufferSizeMs", 0 /* Default */)); + InsertConfigInteger(pCfg, "PreBufferSizeMs", + i_getAudioDriverValU32(pVirtualBox, pMachine, pszDrvName, "PreBufferSizeMs", UINT32_MAX /* Default */)); + + PCFGMNODE pLunL1; + InsertConfigNode(pLUN, "AttachedDriver", &pLunL1); + + InsertConfigNode(pLunL1, "Config", &pCfg); + + Bstr bstrTmp; + hrc = pMachine->COMGETTER(Name)(bstrTmp.asOutParam()); H(); + InsertConfigString(pCfg, "StreamName", bstrTmp); + + InsertConfigString(pLunL1, "Driver", pszDrvName); + + LogFlowFunc(("szDrivName=%s, hrc=%Rhrc\n", pszDrvName, hrc)); + return hrc; + +#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 rc = VINF_SUCCESS; + 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)); + + /* + * 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 = CFGMR3GetChild(pRoot, pszExtraDataKey); + if (pNode) + CFGMR3RemoveValue(pNode, pszCFGMValueName); + else + { + /* create the node */ + rc = CFGMR3InsertNode(pRoot, pszExtraDataKey, &pNode); + if (RT_FAILURE(rc)) + { + AssertLogRelMsgRC(rc, ("failed to insert node '%s'\n", pszExtraDataKey)); + continue; + } + Assert(pNode); + } + } + else + { + /* root value (no node path). */ + pNode = pRoot; + pszCFGMValueName = pszExtraDataKey; + pszExtraDataKey--; + CFGMR3RemoveValue(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.isEmpty()) + { + uint64_t u64Value; + + /* check for type prefix first. */ + if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("string:"))) + InsertConfigString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str() + sizeof("string:") - 1); + else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("integer:"))) + { + rc = RTStrToUInt64Full(strCFGMValueUtf8.c_str() + sizeof("integer:") - 1, 0, &u64Value); + if (RT_SUCCESS(rc)) + rc = CFGMR3InsertInteger(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) + { + rc = RTBase64Decode(pszBase64, pvBytes, cbValue, NULL, NULL); + if (RT_SUCCESS(rc)) + rc = CFGMR3InsertBytes(pNode, pszCFGMValueName, pvBytes, cbValue); + RTMemTmpFree(pvBytes); + } + else + rc = VERR_NO_TMP_MEMORY; + } + else if (cbValue == 0) + rc = CFGMR3InsertBytes(pNode, pszCFGMValueName, NULL, 0); + else + rc = VERR_INVALID_BASE64_ENCODING; + } + /* auto detect type. */ + else if (RT_SUCCESS(RTStrToUInt64Full(strCFGMValueUtf8.c_str(), 0, &u64Value))) + rc = CFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value); + else + InsertConfigString(pNode, pszCFGMValueName, strCFGMValueUtf8); + AssertLogRelMsgRCBreak(rc, ("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 rc; +} + +/** + * 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<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 = ptrMachine->COMGETTER(VRAMSize)(&cVRamMBs); H(); + InsertConfigInteger(pCfg, "VRamSize", cVRamMBs * _1M); + ULONG cMonitorCount; + hrc = ptrMachine->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 = ptrMachine->COMGETTER(Accelerate3DEnabled)(&f3DEnabled); H(); + InsertConfigInteger(pCfg, "3DEnabled", f3DEnabled); + + i_attachStatusDriver(pInst, &mapCrOglLed, 0, 0, NULL, NULL, 0); + +#ifdef VBOX_WITH_VMSVGA + if ( enmGraphicsController == GraphicsControllerType_VMSVGA + || enmGraphicsController == GraphicsControllerType_VBoxSVGA) + { + InsertConfigInteger(pCfg, "VMSVGAEnabled", true); + if (enmGraphicsController == GraphicsControllerType_VMSVGA) + InsertConfigInteger(pCfg, "VMSVGAPciId", true); +#ifdef VBOX_WITH_VMSVGA3D + InsertConfigInteger(pCfg, "VMSVGA3dEnabled", f3DEnabled); +#else + LogRel(("VMSVGA3d not available in this build!\n")); +#endif + } +#endif + + /* 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); + Display *pDisplay = mDisplay; + InsertConfigInteger(pCfg, "Object", (uintptr_t)pDisplay); + } + 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 strFile; + hrc = pMedium->COMGETTER(Location)(strFile.asOutParam()); H(); + Utf8Str utfFile = Utf8Str(strFile); + Bstr strSnap; + ComPtr<IMachine> pMachine = i_machine(); + hrc = pMachine->COMGETTER(SnapshotFolder)(strSnap.asOutParam()); H(); + Utf8Str utfSnap = Utf8Str(strSnap); + RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN; + RTFSTYPE enmFsTypeSnap = RTFSTYPE_UNKNOWN; + int rc2 = RTFsQueryType(utfFile.c_str(), &enmFsTypeFile); + AssertMsgRCReturn(rc2, ("Querying the file type of '%s' failed!\n", utfFile.c_str()), rc2); + /* Ignore the error code. On error, the file system type is still 'unknown' so + * none of the following paths are taken. This can happen for new VMs which + * still don't have a snapshot folder. */ + (void)RTFsQueryType(utfSnap.c_str(), &enmFsTypeSnap); + if (!mfSnapshotFolderDiskTypeShown) + { + LogRel(("File system of '%s' (snapshots) is %s\n", + utfSnap.c_str(), RTFsTypeName(enmFsTypeSnap))); + mfSnapshotFolderDiskTypeShown = true; + } + LogRel(("File system of '%s' is %s\n", utfFile.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 '%ls' 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.raw(), 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 rc = RTFileOpen(&file, utfFile.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + RTFOFF maxSize; + /* Careful: This function will work only on selected local file systems! */ + rc = RTFileGetMaxSizeEx(file, &maxSize); + RTFileClose(file); + if ( RT_SUCCESS(rc) + && 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 '%ls' 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.raw(), 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 '%ls' seems to be located on " + "a FAT(32) file system. The logical size of the medium '%ls' " + "(%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 '%ls' seems to be located on " + "a FAT(32) file system. The logical size of the medium '%ls' " + "(%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.raw(), strFile.raw(), 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 rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOsRelease, sizeof(szOsRelease)); + bool fKernelHasODirectBug = RT_FAILURE(rc) + || (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 '%ls' 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.raw(), 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(rc) + || (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 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, StorageBus_T enmBus, DeviceType_T enmDevType, + const char *pcszDevice, unsigned uInstance, unsigned uLUN, + bool fForceUnmount) +{ + /* Unmount existing media only for floppy and DVD drives. */ + int rc = VINF_SUCCESS; + PPDMIBASE pBase; + if (enmBus == StorageBus_USB) + rc = PDMR3UsbQueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase); + else if ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI) + || (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD)) + rc = PDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase); + else /* IDE or Floppy */ + rc = PDMR3QueryLun(pUVM, pcszDevice, uInstance, uLUN, &pBase); + + if (RT_FAILURE(rc)) + { + if (rc == VERR_PDM_LUN_NOT_FOUND || rc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + rc = VINF_SUCCESS; + AssertRC(rc); + } + else + { + PPDMIMOUNT pIMount = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMOUNT); + AssertReturn(pIMount, VERR_INVALID_POINTER); + + /* Unmount the media (but do not eject the medium!) */ + rc = pIMount->pfnUnmount(pIMount, fForceUnmount, false /*=fEject*/); + if (rc == VERR_PDM_MEDIA_NOT_MOUNTED) + rc = VINF_SUCCESS; + /* for example if the medium is locked */ + else if (RT_FAILURE(rc)) + return rc; + } + + return rc; +} + +/** + * 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 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, + DeviceType_T enmDevType, + PCFGMNODE *ppLunL0) +{ + int rc = VINF_SUCCESS; + bool fAddLun = false; + + /* First check if the LUN already exists. */ + PCFGMNODE pLunL0 = CFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + AssertReturn(!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) + { + rc = i_unmountMediumFromGuest(pUVM, enmBus, enmDevType, pcszDevice, + uInstance, uLUN, fForceUnmount); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * 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_USB)) + { + /* Get the current attached driver we have to detach. */ + PCFGMNODE pDrvLun = CFGMR3GetChildF(pCtlInst, "LUN#%u/AttachedDriver/", uLUN); + if (pDrvLun) + { + char szDriver[128]; + RT_ZERO(szDriver); + rc = CFGMR3QueryString(pDrvLun, "Driver", &szDriver[0], sizeof(szDriver)); + if (RT_SUCCESS(rc)) + pszDriverDetach = RTStrDup(&szDriver[0]); + + pLunL0 = pDrvLun; + } + } + + if (enmBus == StorageBus_USB) + rc = PDMR3UsbDriverDetach(pUVM, pcszDevice, uInstance, uLUN, + pszDriverDetach, 0 /* iOccurence */, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG); + else + rc = PDMR3DriverDetach(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. */ + CFGMR3RemoveNode(pLunL0); + pLunL0 = CFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN); + if (pLunL0) + { + try + { + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + } + } + if (rc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN) + rc = VINF_SUCCESS; + AssertRCReturn(rc, rc); + + /* + * 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; + CFGMR3RemoveNode(pLunL0); + } + } + else + fAddLun = true; + + try + { + if (fAddLun) + InsertConfigNode(pCtlInst, Utf8StrFmt("LUN#%u", uLUN).c_str(), &pLunL0); + } + catch (ConfigError &x) + { + // InsertConfig threw something: + return x.m_vrc; + } + + if (ppLunL0) + *ppLunL0 = pLunL0; + + return rc; +} + +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, + DeviceType_T *paLedDevType, + PCFGMNODE *ppLunL0) +{ + // InsertConfig* throws + try + { + int rc = 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 = CFGMR3GetChildF(CFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance); + else + { + /* If we hotplug a USB device create a new CFGM tree. */ + if (!fHotplug) + pCtlInst = CFGMR3GetChildF(CFGMR3GetRootU(pUVM), "USB/%s/", pcszDevice); + else + pCtlInst = CFGMR3CreateTree(pUVM); + } + AssertReturn(pCtlInst, VERR_INTERNAL_ERROR); + + if (enmBus == StorageBus_USB) + { + PCFGMNODE pCfg = NULL; + + /* Create correct instance. */ + if (!fHotplug) + { + if (!fAttachDetach) + InsertConfigNode(pCtlInst, Utf8StrFmt("%d", lPort).c_str(), &pCtlInst); + else + pCtlInst = CFGMR3GetChildF(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)); + rc = RTUuidCreate(&UsbMsd.mUuid); + AssertRCReturn(rc, rc); + rc = RTUuidToStr(&UsbMsd.mUuid, aszUuid, sizeof(aszUuid)); + AssertRCReturn(rc, rc); + + UsbMsd.iPort = uInstance; + + InsertConfigString(pCtlInst, "UUID", aszUuid); + mUSBStorageDevices.push_back(UsbMsd); + + /** @todo No LED after hotplugging. */ + /* Attach the status driver */ + Assert(cLedUsb >= 8); + i_attachStatusDriver(pCtlInst, &mapStorageLeds[iLedUsb], 0, 7, + &mapMediumAttachments, pcszDevice, 0); + paLedDevType = &maStorageDevType[iLedUsb]; + } + } + + rc = i_removeMediumDriverFromVm(pCtlInst, pcszDevice, uInstance, uLUN, enmBus, fAttachDetach, + fHotplug, fForceUnmount, pUVM, lType, &pLunL0); + if (RT_FAILURE(rc)) + return rc; + if (ppLunL0) + *ppLunL0 = pLunL0; + + Utf8Str devicePath = Utf8StrFmt("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN); + mapMediumAttachments[devicePath] = pMediumAtt; + + ComPtr<IMedium> pMedium; + hrc = pMediumAtt->COMGETTER(Medium)(pMedium.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)) + { + rc = i_checkMediumLocation(pMedium, &fUseHostIOCache); + if (RT_FAILURE(rc)) + return rc; + } + + BOOL fPassthrough = FALSE; + if (pMedium) + { + BOOL fHostDrive; + hrc = pMedium->COMGETTER(HostDrive)(&fHostDrive); H(); + if ( ( lType == DeviceType_DVD + || lType == DeviceType_Floppy) + && !fHostDrive) + { + /* + * Informative logging. + */ + Bstr strFile; + hrc = pMedium->COMGETTER(Location)(strFile.asOutParam()); H(); + Utf8Str utfFile = Utf8Str(strFile); + RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN; + (void)RTFsQueryType(utfFile.c_str(), &enmFsTypeFile); + LogRel(("File system of '%s' (%s) is %s\n", + utfFile.c_str(), lType == DeviceType_DVD ? "DVD" : "Floppy", + RTFsTypeName(enmFsTypeFile))); + } + + if (fHostDrive) + { + hrc = pMediumAtt->COMGETTER(Passthrough)(&fPassthrough); H(); + } + } + + ComObjPtr<IBandwidthGroup> pBwGroup; + Bstr strBwGroup; + hrc = pMediumAtt->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H(); + + if (!pBwGroup.isNull()) + { + hrc = pBwGroup->COMGETTER(Name)(strBwGroup.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_SATA && lType == DeviceType_DVD && !fPassthrough))) + { + InsertConfigString(pLunL0, "Driver", "SCSI"); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } + + rc = i_configMedium(pLunL0, + !!fPassthrough, + lType, + fUseHostIOCache, + fBuiltinIOCache, + fInsertDiskIntegrityDrv, + fSetupMerge, + uMergeSource, + uMergeTarget, + strBwGroup.isEmpty() ? NULL : Utf8Str(strBwGroup).c_str(), + !!fDiscard, + !!fNonRotational, + pMedium, + aMachineState, + phrc); + if (RT_FAILURE(rc)) + return rc; + + if (fAttachDetach) + { + /* Attach the new driver. */ + if (enmBus == StorageBus_USB) + { + if (fHotplug) + { + USBStorageDevice UsbMsd = USBStorageDevice(); + RTUuidCreate(&UsbMsd.mUuid); + UsbMsd.iPort = uInstance; + rc = PDMR3UsbCreateEmulatedDevice(pUVM, pcszDevice, pCtlInst, &UsbMsd.mUuid, NULL); + if (RT_SUCCESS(rc)) + mUSBStorageDevices.push_back(UsbMsd); + } + else + rc = PDMR3UsbDriverAttach(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_SATA && lType == DeviceType_DVD))) + rc = PDMR3DriverAttach(pUVM, pcszDevice, uInstance, uLUN, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/); + else + rc = PDMR3DeviceAttach(pUVM, pcszDevice, uInstance, uLUN, + fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/); + AssertRCReturn(rc, rc); + + /* + * 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; + rc = PDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "VD", &pIBase); + if (RT_SUCCESS(rc) && pIBase) + { + PPDMIMEDIA pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID); + if (pIMedium) + { + rc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp); + Assert(RT_SUCCESS(rc) || rc == 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) + CFGMR3Dump(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, + IMedium *pMedium, + 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 (pMedium) + { + hrc = pMedium->COMGETTER(HostDrive)(&fHostDrive); H(); + hrc = pMedium->COMGETTER(Type)(&mediumType); H(); + } + + if (fHostDrive) + { + Assert(pMedium); + if (enmType == DeviceType_DVD) + { + InsertConfigString(pLunL0, "Driver", "HostDVD"); + InsertConfigNode(pLunL0, "Config", &pCfg); + + hrc = pMedium->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 = pMedium->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 ( pMedium + && ( 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 = pMedium->RefreshState(&mediumState); H(); + if (mediumState == MediumState_Inaccessible) + { + Bstr loc; + hrc = pMedium->COMGETTER(Location)(loc.asOutParam()); H(); + i_atVMRuntimeErrorCallbackF(0, "DvdOrFloppyImageInaccessible", + "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"); + pMedium = NULL; + } + } + + if (pMedium) + { + /* Start with length of parent chain, as the list is reversed */ + unsigned uImage = 0; + IMedium *pTmp = pMedium; + while (pTmp) + { + uImage++; + hrc = pTmp->COMGETTER(Parent)(&pTmp); H(); + } + /* 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 = pMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCfg, "Path", bstr); + + hrc = pMedium->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 + || aMachineState == MachineState_FaultTolerantSyncing)) + 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, pMedium, &fHostIP, &fEncrypted); H(); + + /* Create an inverted list of parents. */ + uImage--; + IMedium *pParentMedium = pMedium; + for (PCFGMNODE pParent = pCfg;; uImage--) + { + hrc = pParentMedium->COMGETTER(Parent)(&pMedium); H(); + if (!pMedium) + break; + + PCFGMNODE pCur; + InsertConfigNode(pParent, "Parent", &pCur); + hrc = pMedium->COMGETTER(Location)(bstr.asOutParam()); H(); + InsertConfigString(pCur, "Path", bstr); + + hrc = pMedium->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, pMedium, &fHostIP, &fEncrypted); H(); + + /* next */ + pParent = pCur; + pParentMedium = pMedium; + } + + /* 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 = CFGMR3GetChild(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; +} + + +/** + * 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). + * + * @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) +{ + RT_NOREF(fIgnoreConnectFailure); + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), VERR_ACCESS_DENIED); + + // InsertConfig* throws + try + { + int rc = VINF_SUCCESS; + HRESULT hrc; + Bstr bstr; + +#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) + { + rc = PDMR3DeviceDetach(mpUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/); + if (rc == VINF_PDM_NO_DRIVER_ATTACHED_TO_LUN) + rc = VINF_SUCCESS; + AssertLogRelRCReturn(rc, rc); + + /* Nuke anything which might have been left behind. */ + CFGMR3RemoveNode(CFGMR3GetChildF(pInst, "LUN#%u", uLun)); + } + +#ifdef VBOX_WITH_NETSHAPER + ComObjPtr<IBandwidthGroup> pBwGroup; + Bstr strBwGroup; + hrc = aNetworkAdapter->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H(); + + if (!pBwGroup.isNull()) + { + hrc = pBwGroup->COMGETTER(Name)(strBwGroup.asOutParam()); H(); + } +#endif /* VBOX_WITH_NETSHAPER */ + + AssertMsg(uLun == 0, ("Network attachments with LUN > 0 are not supported yet\n")); + CFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%u", uLun); + +#ifdef VBOX_WITH_NETSHAPER + if (!strBwGroup.isEmpty()) + { + InsertConfigString(pLunL0, "Driver", "NetShaper"); + InsertConfigNode(pLunL0, "Config", &pCfg); + InsertConfigString(pCfg, "BwGroup", strBwGroup); + InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0); + } +#endif /* VBOX_WITH_NETSHAPER */ + + if (fSniffer) + { + 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); + } + + + Bstr networkName, trunkName, trunkType; + NetworkAttachmentType_T eAttachmentType; + hrc = aNetworkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H(); + 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); + + /* 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; + BSTR r = pfs[i]; + Utf8Str utf = Utf8Str(r); + 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 VERR_ACCESS_DENIED: + return VMSetError(VMR3GetVM(mpUVM), 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 VMSetError(VMR3GetVM(mpUVM), 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)", hrc, hrc)); + return VMSetError(VMR3GetVM(mpUVM), VERR_INTERNAL_ERROR, RT_SRC_POS, + N_("Nonexistent host networking interface, name '%ls'"), + BridgedIfName.raw()); + } + +# if defined(RT_OS_DARWIN) + /* The name is on the form '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 on the form 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 VMSetError(VMR3GetVM(mpUVM), 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 VERR_ACCESS_DENIED: + return VMSetError(VMR3GetVM(mpUVM), 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 VMSetError(VMR3GetVM(mpUVM), 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 + + 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 + +#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(); + ComPtr<IHostNetworkInterface> hostInterface; + rc = host->FindHostNetworkInterfaceByName(HostOnlyName.raw(), + hostInterface.asOutParam()); + if (!SUCCEEDED(rc)) + { + LogRel(("NetworkAttachmentType_HostOnly: FindByName failed, rc (0x%x)\n", rc)); + return VMSetError(VMR3GetVM(mpUVM), 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 VMSetError(VMR3GetVM(mpUVM), 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; + } + + 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: + case NetworkAttachmentType_NAT: + case NetworkAttachmentType_Generic: + case NetworkAttachmentType_NATNetwork: + { + if (SUCCEEDED(hrc) && RT_SUCCESS(rc)) + { + if (fAttachDetach) + { + rc = PDMR3DriverAttach(mpUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/, NULL /* ppBase */); + //AssertRC(rc); + } + + { + /** @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(networkName.raw(), + 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..6f4bbc02 --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp @@ -0,0 +1,1457 @@ +/* $Id: ConsoleImplTeleporter.cpp $ */ +/** @file + * VBox Console COM Class implementation, The Teleporter Part. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE +#include "LoggingNew.h" + +#include "ConsoleImpl.h" +#include "Global.h" +#include "ProgressImpl.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/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; + 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, Progress *pProgress, bool fIsSource) + : mptrConsole(pConsole) + , mpUVM(pUVM) + , mptrProgress(pProgress) + , mfIsSource(fIsSource) + , mhSocket(NIL_RTSOCKET) + , moffStream(UINT64_MAX / 2) + , mcbReadBlock(0) + , mfStopReading(false) + , mfEndOfStream(false) + , mfIOError(false) + { + VMR3RetainUVM(mpUVM); + } + + ~TeleporterState() + { + VMR3ReleaseUVM(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, Progress *pProgress, MachineState_T enmOldMachineState) + : TeleporterState(pConsole, pUVM, 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, Progress *pProgress, + IMachine *pMachine, IInternalMachineControl *pControl, + PRTTIMERLR phTimerLR, bool fStartPaused) + : TeleporterState(pConsole, pUVM, 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 rc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL); + if (RT_FAILURE(rc)) + { + LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", rc, pszStart)); + return rc; + } + 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 rc = RTTcpSgWriteL(pState->mhSocket, 2, &Hdr, sizeof(Hdr), pvBuf, (size_t)Hdr.cb); + if (RT_FAILURE(rc)) + { + LogRel(("Teleporter/TCP: Write error: %Rrc (cb=%#x)\n", rc, Hdr.cb)); + return rc; + } + 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 rc; + do + { + rc = RTTcpSelectOne(pState->mhSocket, 1000); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Header select error: %Rrc\n", rc)); + break; + } + if (pState->mfStopReading) + { + rc = VERR_EOF; + break; + } + } while (rc == VERR_TIMEOUT); + return rc; +} + + +/** + * @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 rc; + + /* + * 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) + { + rc = teleporterTcpReadSelect(pState); + if (RT_FAILURE(rc)) + return rc; + TELEPORTERTCPHDR Hdr; + rc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL); + if (RT_FAILURE(rc)) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Header read error: %Rrc\n", rc)); + return rc; + } + + 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. + */ + rc = teleporterTcpReadSelect(pState); + if (RT_FAILURE(rc)) + return rc; + uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead); + rc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead); + if (RT_FAILURE(rc)) + { + pState->mfIOError = true; + LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", rc, cb)); + return rc; + } + 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 rc = RTTcpSelectOne(pState->mhSocket, 0); + if (rc != VERR_TIMEOUT) + { + if (RT_SUCCESS(rc)) + { + LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n")); + rc = VERR_SSM_CANCELLED; + } + else + LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", rc)); + return rc; + } + } + + 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 rc = RTTcpWrite(pState->mhSocket, &EofHdr, sizeof(EofHdr)); + if (RT_FAILURE(rc)) + { + LogRel(("Teleporter/TCP: EOF Header write error: %Rrc\n", rc)); + return rc; + } + } + 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; + SSMR3Cancel(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) + { + SSMR3Cancel(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 = VMR3Teleport(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, tr("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 = VMR3GetStateU(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", VMR3GetStateName(enmVMState))); + AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, + ("%s\n", Global::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", VMR3GetStateName(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 rc = VMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORT_FAILED); + AssertLogRelMsgRC(rc, ("VMR3Resume -> %Rrc\n", rc)); + 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, 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, tr("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 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, IMachine *pMachine, Utf8Str *pErrorMsg, bool fStartPaused, + Progress *pProgress, bool *pfPowerOffOnFailure) +{ + LogThisFunc(("pUVM=%p pMachine=%p fStartPaused=%RTbool pProgress=%p\n", pUVM, 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, 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 = VMR3GetStateU(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 rc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1); + if (RT_FAILURE(rc)) + { + LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", rc)); + if (fAutomaticUnlock) + teleporterTrgUnlockMedia(pState); + } + return rc; +} + + +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 rc = RTTcpWrite(pState->mhSocket, szMsg, cch); + if (RT_FAILURE(rc)) + LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, rc)); + return rc; +} + + +/** + * @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 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 + LogRel(("Teleporter: Invalid password (off=%u)\n", off)); + 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 = VMR3AtErrorRegister(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 = VMR3LoadFromStream(pState->mpUVM, + &g_teleporterTcpOps, pvUser2, + teleporterProgressCallback, pvUser2); + + RTSocketRelease(pState->mhSocket); + vrc2 = VMR3AtErrorDeregister(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 = VMR3Resume(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..ccc7cc75 --- /dev/null +++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp @@ -0,0 +1,4051 @@ +/* $Id: ConsoleVRDPServer.cpp $ */ +/** @file + * VBox Console VRDP helper class. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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> +#include <VBox/HostServices/VBoxCrOpenGLSvc.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 rc = 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; + rc = VINF_SUCCESS; + } + else + { + rc = 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. */ + rc = VERR_TOO_MUCH_DATA; + break; + } + + if ((size_t)cbBuffer >= cbAddress) + { + memcpy(pvBuffer, address.c_str(), cbAddress); + rc = VINF_SUCCESS; + } + else + { + rc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = (uint32_t)cbAddress; + } break; + + case VRDE_QP_NUMBER_MONITORS: + { + ULONG cMonitors = 1; + + server->mConsole->i_machine()->COMGETTER(MonitorCount)(&cMonitors); + + if (cbBuffer >= sizeof(uint32_t)) + { + *(uint32_t *)pvBuffer = (uint32_t)cMonitors; + rc = VINF_SUCCESS; + } + else + { + rc = 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. */ + rc = VERR_TOO_MUCH_DATA; + break; + } + + if ((size_t)cbBuffer >= cbPortRange) + { + memcpy(pvBuffer, portRange.c_str(), cbPortRange); + rc = VINF_SUCCESS; + } + else + { + rc = 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; + rc = VINF_SUCCESS; + } + else + { + rc = 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; + rc = VINF_SUCCESS; + } + else + { + rc = 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; + rc = VINF_SUCCESS; + } + else + { + rc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = sizeof(uint32_t); + } break; + + case VRDE_QP_FEATURE: + { + if (cbBuffer < sizeof(VRDEFEATURE)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + size_t cbInfo = cbBuffer - RT_UOFFSETOF(VRDEFEATURE, achInfo); + + VRDEFEATURE *pFeature = (VRDEFEATURE *)pvBuffer; + + size_t cchInfo = 0; + rc = RTStrNLenEx(pFeature->achInfo, cbInfo, &cchInfo); + + if (RT_FAILURE(rc)) + { + rc = 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)) + { + rc = 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)) + { + rc = VERR_NOT_SUPPORTED; + } + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + /* Copy the value string to the callers buffer. */ + if (rc == 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 + { + rc = VINF_BUFFER_OVERFLOW; + } + + *pcbOut = (uint32_t)cb; + } + } break; + + case VRDE_SP_NETWORK_BIND_PORT: + { + if (cbBuffer != sizeof(uint32_t)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + ULONG port = *(uint32_t *)pvBuffer; + + server->mVRDPBindPort = port; + + rc = VINF_SUCCESS; + + if (pcbOut) + { + *pcbOut = sizeof(uint32_t); + } + + server->mConsole->i_onVRDEServerInfoChange(); + } break; + + case VRDE_SP_CLIENT_STATUS: + { + if (cbBuffer < sizeof(VRDECLIENTSTATUS)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + size_t cbStatus = cbBuffer - RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus); + + VRDECLIENTSTATUS *pStatus = (VRDECLIENTSTATUS *)pvBuffer; + + if (cbBuffer < RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus) + pStatus->cbStatus) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + size_t cchStatus = 0; + rc = RTStrNLenEx(pStatus->achStatus, cbStatus, &cchStatus); + + if (RT_FAILURE(rc)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + Log(("VRDE_SP_CLIENT_STATUS [%s]\n", pStatus->achStatus)); + + server->mConsole->i_VRDPClientStatusChange(pStatus->u32ClientId, pStatus->achStatus); + + rc = VINF_SUCCESS; + + if (pcbOut) + { + *pcbOut = cbBuffer; + } + + server->mConsole->i_onVRDEServerInfoChange(); + } break; + + default: + break; + } + + return rc; +} + +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 rc = VERR_NOT_SUPPORTED; + + switch (fu32Intercept) + { + case VRDE_CLIENT_INTERCEPT_AUDIO: + { + pServer->mConsole->i_VRDPInterceptAudio(u32ClientId); + if (ppvIntercept) + { + *ppvIntercept = pServer; + } + rc = VINF_SUCCESS; + } break; + + case VRDE_CLIENT_INTERCEPT_USB: + { + pServer->mConsole->i_VRDPInterceptUSB(u32ClientId, ppvIntercept); + rc = VINF_SUCCESS; + } break; + + case VRDE_CLIENT_INTERCEPT_CLIPBOARD: + { + pServer->mConsole->i_VRDPInterceptClipboard(u32ClientId); + if (ppvIntercept) + { + *ppvIntercept = pServer; + } + rc = 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)); + rc = VERR_NOT_SUPPORTED; + } + } break; + + default: + break; + } + + return rc; +} + +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 + 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); +} + +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) +{ + mConsole = console; + + int rc = RTCritSectInit(&mCritSect); + AssertRC(rc); + + 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); + + rc = RTCritSectInit(&mTSMFLock); + AssertRC(rc); + + 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)); + } +} + +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 rc = 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 rc = 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; + rc = 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(rc)) + { + 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 rc = 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 + { + rc = VERR_BUFFER_OVERFLOW; + } + *pcbOut = cbOut; + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + H3DORLOG(("H3DORContextProperty: %Rrc\n", rc)); + return rc; +} + +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; + } + + /* 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); + } + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + VBOXCRCMDCTL_HGCM data; + data.Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + data.Hdr.u32Function = SHCRGL_HOST_FN_SET_OUTPUT_REDIRECT; + + data.aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + data.aParms[0].u.pointer.addr = &outputRedirect; + data.aParms[0].u.pointer.size = sizeof(outputRedirect); + + int rc = mConsole->i_getDisplay()->i_crCtlSubmitSync(&data.Hdr, sizeof (data)); + if (!RT_SUCCESS(rc)) + { + Log(("SHCRGL_HOST_FN_SET_CONSOLE failed with %Rrc\n", rc)); + return; + } + + LogRel(("VRDE: %s 3D redirect.\n", fEnable? "Enabled": "Disabled")); +# ifdef DEBUG_misha + AssertFailed(); +# endif +#endif + + return; +} + +/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDEImageCbNotify (void *pvContext, + void *pvUser, + HVRDEIMAGE hVideo, + uint32_t u32Id, + void *pvData, + uint32_t cbData) +{ + RT_NOREF(hVideo); + H3DORLOG(("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); + 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; + H3DORLOG(("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. */ + } + } + + 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 rcRequest, + 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(rcRequest, pvUser, u32Function, pvData, cbData); +#else + NOREF(pvContext); + NOREF(rcRequest); + 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 rc = VINF_SUCCESS; + + if (mhServer && mpEntryPoints && m_interfaceSCard.VRDESCardRequest) + { + rc = m_interfaceSCard.VRDESCardRequest(mhServer, pvUser, u32Function, pvData, cbData); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + + +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 rc = RTCritSectEnter(&mTSMFLock); + AssertRC(rc); + return rc; +} + +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 rc = tsmfContextsAlloc(&pHostChCtx, &pVRDPCtx); + if (RT_FAILURE(rc)) + { + return rc; + } + + pHostChCtx->pThis = pThis; + pHostChCtx->pVRDPCtx = pVRDPCtx; + + pVRDPCtx->pThis = pThis; + pVRDPCtx->pCallbacks = pCallbacks; + pVRDPCtx->pvCallbacks = pvCallbacks; + pVRDPCtx->pHostChCtx = pHostChCtx; + + rc = pThis->m_interfaceTSMF.VRDETSMFChannelCreate(pThis->mhServer, pVRDPCtx, u32Flags); + + if (RT_SUCCESS(rc)) + { + /** @todo contexts should be in a list for accounting. */ + *ppvChannel = pHostChCtx; + } + else + { + RTMemFree(pHostChCtx); + RTMemFree(pVRDPCtx); + } + + return rc; +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::tsmfHostChannelDetach(void *pvChannel) +{ + LogFlowFunc(("\n")); + + TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel; + ConsoleVRDPServer *pThis = pHostChCtx->pThis; + + int rc = pThis->tsmfLock(); + if (RT_SUCCESS(rc)) + { + 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 rc = pThis->tsmfLock(); + if (RT_SUCCESS(rc)) + { + 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)); + rc = pThis->m_interfaceTSMF.VRDETSMFChannelSend(pThis->mhServer, u32ChannelHandle, + pvData, cbData); + } + } + + return rc; +} + +/* 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 rc = pThis->tsmfLock(); + if (RT_SUCCESS(rc)) + { + 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 rc; +} + +/* 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 rc = pVMMDev->hgcmHostCall("VBoxHostChannel", + VBOX_HOST_CHANNEL_HOST_FN_REGISTER, + 2, + &parms.name); + + if (!RT_SUCCESS(rc)) + { + Log(("VBOX_HOST_CHANNEL_HOST_FN_REGISTER failed with %Rrc\n", rc)); + 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 rc = 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; + + rc = pThis->tsmfLock(); + + if (RT_SUCCESS(rc)) + { + 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; + + rc = pThis->tsmfLock(); + if (RT_SUCCESS(rc)) + { + 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 rcRequest, + void *pDeviceCtx, + void *pvUser, + const VRDEVIDEOINDEVICEDESC *pDeviceDesc, + uint32_t cbDevice) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbDeviceDesc(rcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDevice); + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInControl(void *pvCallback, + int rcRequest, + void *pDeviceCtx, + void *pvUser, + const VRDEVIDEOINCTRLHDR *pControl, + uint32_t cbControl) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbControl(rcRequest, pDeviceCtx, pvUser, pControl, cbControl); + } +} + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInFrame(void *pvCallback, + int rcRequest, + void *pDeviceCtx, + const VRDEVIDEOINPAYLOADHDR *pFrame, + uint32_t cbFrame) +{ + ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback); + if (pThis->mEmWebcam) + { + pThis->mEmWebcam->EmWebcamCbFrame(rcRequest, pDeviceCtx, pFrame, cbFrame); + } +} + +int ConsoleVRDPServer::VideoInDeviceAttach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, void *pvDeviceCtx) +{ + int rc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceAttach) + { + rc = m_interfaceVideoIn.VRDEVideoInDeviceAttach(mhServer, pDeviceHandle, pvDeviceCtx); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int ConsoleVRDPServer::VideoInDeviceDetach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle) +{ + int rc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceDetach) + { + rc = m_interfaceVideoIn.VRDEVideoInDeviceDetach(mhServer, pDeviceHandle); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int ConsoleVRDPServer::VideoInGetDeviceDesc(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle) +{ + int rc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInGetDeviceDesc) + { + rc = m_interfaceVideoIn.VRDEVideoInGetDeviceDesc(mhServer, pvUser, pDeviceHandle); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +int ConsoleVRDPServer::VideoInControl(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, + const VRDEVIDEOINCTRLHDR *pReq, uint32_t cbReq) +{ + int rc; + + if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInControl) + { + rc = m_interfaceVideoIn.VRDEVideoInControl(mhServer, pvUser, pDeviceHandle, pReq, cbReq); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + + +/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputSetup(void *pvCallback, + int rcRequest, + uint32_t u32Method, + const void *pvResult, + uint32_t cbResult) +{ + NOREF(pvCallback); + NOREF(rcRequest); + 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), + (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 rc = 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 + { + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +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) +{ + Assert(VALID_PTR(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 rc = RTThreadUserSignal(thread); + AssertRC(rc); +} + +bool ConsoleVRDPServer::isRemoteUSBThreadRunning(void) +{ + return mUSBBackends.fThreadRunning; +} + +void ConsoleVRDPServer::waitRemoteUSBThreadEvent(RTMSINTERVAL cMillies) +{ + int rc = RTSemEventWait(mUSBBackends.event, cMillies); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); + NOREF(rc); +} + +void ConsoleVRDPServer::remoteUSBThreadStart(void) +{ + int rc = RTSemEventCreate(&mUSBBackends.event); + + if (RT_FAILURE(rc)) + { + AssertFailed(); + mUSBBackends.event = 0; + } + + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&mUSBBackends.thread, threadRemoteUSB, this, 65536, + RTTHREADTYPE_VRDP_IO, RTTHREADFLAGS_WAITABLE, "remote usb"); + } + + if (RT_FAILURE(rc)) + { + LogRel(("Warning: could not start the remote USB thread, rc = %Rrc!!!\n", rc)); + mUSBBackends.thread = NIL_RTTHREAD; + } + else + { + /* Wait until the thread is ready. */ + rc = RTThreadUserWait(mUSBBackends.thread, 60000); + AssertRC(rc); + Assert (mUSBBackends.fThreadRunning || RT_FAILURE(rc)); + } +} + +void ConsoleVRDPServer::remoteUSBThreadStop(void) +{ + mUSBBackends.fThreadRunning = false; + + if (mUSBBackends.thread != NIL_RTTHREAD) + { + Assert (mUSBBackends.event != 0); + + RTSemEventSignal(mUSBBackends.event); + + int rc = RTThreadWait(mUSBBackends.thread, 60000, NULL); + AssertRC(rc); + + 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, mConsole->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 hr = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams), + authResult.asOutParam()); + LogFlowFunc(("%Rhrc [%ls]\n", hr, authResult.raw())); NOREF(hr); + } + catch (std::bad_alloc &) + { + } +#else + AuthLibDisconnect(&mAuthLibCtx, uuid.raw(), u32ClientId); +#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */ +} + +int ConsoleVRDPServer::lockConsoleVRDPServer(void) +{ + int rc = RTCritSectEnter(&mCritSect); + AssertRC(rc); + return rc; +} + +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 rc = VINF_SUCCESS; + + ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvCallback); + + NOREF(u32ClientId); + + switch (u32Function) + { + case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE: + { + if (pServer->mpfnClipboardCallback) + { + pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, + u32Format, + (void *)pvData, + cbData); + } + } break; + + case VRDE_CLIPBOARD_FUNCTION_DATA_READ: + { + if (pServer->mpfnClipboardCallback) + { + pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, + u32Format, + (void *)pvData, + cbData); + } + } break; + + default: + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +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 rc = VINF_SUCCESS; + + ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvExtension); + + VBOXCLIPBOARDEXTPARMS *pParms = (VBOXCLIPBOARDEXTPARMS *)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->u32Format, + 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->u32Format, + 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->u32Format, + pParms->u.pvData, + pParms->cbData, + NULL); + } + } break; + + default: + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + +void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId) +{ + RT_NOREF(u32ClientId); + int rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + if (mcClipboardRefs == 0) + { + rc = HGCMHostRegisterServiceExtension(&mhClipboard, "VBoxSharedClipboard", ClipboardServiceExtension, this); + + if (RT_SUCCESS(rc)) + { + mcClipboardRefs++; + } + } + + unlockConsoleVRDPServer(); + } +} + +void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId) +{ + RT_NOREF(u32ClientId); + int rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + mcClipboardRefs--; + + if (mcClipboardRefs == 0) + { + HGCMHostUnregisterServiceExtension(mhClipboard); + } + + 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 rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + 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(rc)) + { + pRemoteUSBBackend->Release(); + } + } +#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 rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + 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(); + } +#endif +} + +void *ConsoleVRDPServer::USBBackendRequestPointer(uint32_t u32ClientId, const Guid *pGuid) +{ +#ifdef VBOX_WITH_USB + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + /* Find the instance. */ + int rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + 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(); + } + +#endif + return NULL; +} + +void ConsoleVRDPServer::USBBackendReleasePointer(const Guid *pGuid) +{ +#ifdef VBOX_WITH_USB + RemoteUSBBackend *pRemoteUSBBackend = NULL; + + /* Find the instance. */ + int rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + pRemoteUSBBackend = usbBackendFindByUUID(pGuid); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->removeUUID(pGuid); + } + + unlockConsoleVRDPServer(); + + if (pRemoteUSBBackend) + { + pRemoteUSBBackend->Release(); + } + } +#endif +} + +RemoteUSBBackend *ConsoleVRDPServer::usbBackendGetNext(RemoteUSBBackend *pRemoteUSBBackend) +{ + LogFlow(("ConsoleVRDPServer::usbBackendGetNext: pBackend = %p\n", pRemoteUSBBackend)); + + RemoteUSBBackend *pNextRemoteUSBBackend = NULL; +#ifdef VBOX_WITH_USB + + int rc = lockConsoleVRDPServer(); + + if (RT_SUCCESS(rc)) + { + 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 rc = lockConsoleVRDPServer(); + AssertRC(rc); + + /* 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(); +#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 *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 rc = VINF_SUCCESS; + + if (mVRDPLibrary == NIL_RTLDRMOD) + { + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + if (RTPathHavePath(pszLibraryName)) + rc = SUPR3HardenedLdrLoadPlugIn(pszLibraryName, &mVRDPLibrary, &ErrInfo.Core); + else + rc = SUPR3HardenedLdrLoadAppPriv(pszLibraryName, &mVRDPLibrary, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + { + 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++) + { + rc = RTLdrGetSymbol(mVRDPLibrary, s_aSymbols[i].name, s_aSymbols[i].ppfn); + + if (RT_FAILURE(rc)) + { + LogRel(("VRDE: Error resolving symbol '%s', rc %Rrc.\n", s_aSymbols[i].name, rc)); + break; + } + } + } + else + { + if (RTErrInfoIsSet(&ErrInfo.Core)) + LogRel(("VRDE: Error loading the library '%s': %s (%Rrc)\n", pszLibraryName, ErrInfo.Core.pszMsg, rc)); + else + LogRel(("VRDE: Error loading the library '%s' rc = %Rrc.\n", pszLibraryName, rc)); + + mVRDPLibrary = NIL_RTLDRMOD; + } + } + + if (RT_FAILURE(rc)) + { + if (mVRDPLibrary != NIL_RTLDRMOD) + { + RTLdrClose(mVRDPLibrary); + mVRDPLibrary = NIL_RTLDRMOD; + } + } + + return rc; +} + +/* + * 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..6fe81500 --- /dev/null +++ b/src/VBox/Main/src-client/DisplayImpl.cpp @@ -0,0 +1,4720 @@ +/* $Id: DisplayImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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/pdmdrv.h> +#if defined(DEBUG) || defined(VBOX_STRICT) /* for VM_ASSERT_EMT(). */ +# include <VBox/vmm/vm.h> +#endif +#include <VBox/VMMDev.h> + +#ifdef VBOX_WITH_VIDEOHWACCEL +# include <VBoxVideo.h> +#endif + +#if defined(VBOX_WITH_CROGL) || defined(VBOX_WITH_CRHGSMI) +# include <VBox/HostServices/VBoxCrOpenGLSvc.h> +#endif + +#include <VBox/com/array.h> + +#ifdef VBOX_WITH_RECORDING +# include <iprt/path.h> +# include "Recording.h" + +# ifdef VBOX_WITH_LIBVPX +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4668) /* vpx_codec.h(64) : warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +# include <vpx/vpx_encoder.h> +# pragma warning(pop) +# else +# include <vpx/vpx_encoder.h> +# endif +# endif + +# include <VBox/vmm/pdmapi.h> +# include <VBox/vmm/pdmaudioifs.h> +#endif + +#ifdef VBOX_WITH_CROGL +typedef enum +{ + CRVREC_STATE_IDLE, + CRVREC_STATE_SUBMITTED +} CRVREC_STATE; +#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 keyboard port interface of the driver/device above us. */ + PPDMIDISPLAYPORT pUpPort; + /** Our display connector interface. */ + PDMIDISPLAYCONNECTOR IConnector; +#if defined(VBOX_WITH_VIDEOHWACCEL) || defined(VBOX_WITH_CRHGSMI) + /** 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), mfIsCr3DEnabled(false) +{ +} + +Display::~Display() +{ +} + + +HRESULT Display::FinalConstruct() +{ + int rc = videoAccelConstruct(&mVideoAccelLegacy); + AssertRC(rc); + + mfVideoAccelVRDP = false; + mfu32SupportedOrders = 0; + mcVideoAccelVRDPRefs = 0; + + mfSeamlessEnabled = false; + mpRectVisibleRegion = NULL; + mcRectVisibleRegion = 0; + +#ifdef VBOX_WITH_CROGL + mfCrOglDataHidden = false; +#endif + + mpDrv = NULL; + + rc = RTCritSectInit(&mVideoAccelLock); + AssertRC(rc); + +#ifdef VBOX_WITH_HGSMI + mu32UpdateVBVAFlags = 0; + mfVMMDevSupportsGraphics = false; + mfGuestVBVACapabilities = 0; + mfHostCursorCapabilities = 0; +#endif + +#ifdef VBOX_WITH_RECORDING + rc = RTCritSectInit(&mVideoRecLock); + AssertRC(rc); + + for (unsigned i = 0; i < RT_ELEMENTS(maRecordingEnabled); i++) + maRecordingEnabled[i] = true; +#endif + +#ifdef VBOX_WITH_CRHGSMI + mhCrOglSvc = NULL; + rc = RTCritSectRwInit(&mCrOglLock); + AssertRC(rc); +#endif + +#ifdef VBOX_WITH_CROGL + RT_ZERO(mCrOglCallbacks); + RT_ZERO(mCrOglScreenshotData); + mfCrOglVideoRecState = CRVREC_STATE_IDLE; + mCrOglScreenshotData.u32Screen = CRSCREEN_ALL; + mCrOglScreenshotData.pvContext = this; + mCrOglScreenshotData.pfnScreenshotBegin = i_displayCrVRecScreenshotBegin; + mCrOglScreenshotData.pfnScreenshotPerform = i_displayCrVRecScreenshotPerform; + mCrOglScreenshotData.pfnScreenshotEnd = i_displayCrVRecScreenshotEnd; +#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); + } + +#ifdef VBOX_WITH_CRHGSMI + if (RTCritSectRwIsInitialized(&mCrOglLock)) + { + RTCritSectRwDelete(&mCrOglLock); + RT_ZERO(mCrOglLock); + } +#endif + 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 rc = 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 + { + rc = VERR_NO_MEMORY; + } + + return rc; +} + +#ifdef VBOX_WITH_CROGL +typedef struct +{ + CRVBOXHGCMTAKESCREENSHOT Base; + + /* 32bpp small RGB image. */ + uint8_t *pu8Thumbnail; + uint32_t cbThumbnail; + uint32_t cxThumbnail; + uint32_t cyThumbnail; + + /* PNG screenshot. */ + uint8_t *pu8PNG; + uint32_t cbPNG; + uint32_t cxPNG; + uint32_t cyPNG; +} VBOX_DISPLAY_SAVESCREENSHOT_DATA; + +static DECLCALLBACK(void) displaySaveScreenshotReport(void *pvCtx, uint32_t uScreen, + uint32_t x, uint32_t y, uint32_t uBitsPerPixel, + uint32_t uBytesPerLine, uint32_t uGuestWidth, uint32_t uGuestHeight, + uint8_t *pu8BufferAddress, uint64_t u64Timestamp) +{ + RT_NOREF(uScreen, x, y, uBitsPerPixel,uBytesPerLine, u64Timestamp); + VBOX_DISPLAY_SAVESCREENSHOT_DATA *pData = (VBOX_DISPLAY_SAVESCREENSHOT_DATA*)pvCtx; + displayMakeThumbnail(pu8BufferAddress, uGuestWidth, uGuestHeight, &pData->pu8Thumbnail, + &pData->cbThumbnail, &pData->cxThumbnail, &pData->cyThumbnail); + int rc = DisplayMakePNG(pu8BufferAddress, uGuestWidth, uGuestHeight, &pData->pu8PNG, + &pData->cbPNG, &pData->cxPNG, &pData->cyPNG, 1); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("DisplayMakePNG failed (rc=%Rrc)\n", rc)); + if (pData->pu8PNG) + { + RTMemFree(pData->pu8PNG); + pData->pu8PNG = NULL; + } + pData->cbPNG = 0; + pData->cxPNG = 0; + pData->cyPNG = 0; + } +} +#endif + +DECLCALLBACK(void) Display::i_displaySSMSaveScreenshot(PSSMHANDLE pSSM, void *pvUser) +{ + Display *that = static_cast<Display*>(pvUser); + + /* 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(that->mParent); + if (ptrVM.isOk()) + { +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + BOOL f3DSnapshot = FALSE; + if ( that->mfIsCr3DEnabled + && that->mCrOglCallbacks.pfnHasData + && that->mCrOglCallbacks.pfnHasData()) + { + VMMDev *pVMMDev = that->mParent->i_getVMMDev(); + if (pVMMDev) + { + VBOX_DISPLAY_SAVESCREENSHOT_DATA *pScreenshot; + pScreenshot = (VBOX_DISPLAY_SAVESCREENSHOT_DATA*)RTMemAllocZ(sizeof(*pScreenshot)); + if (pScreenshot) + { + /* screen id or CRSCREEN_ALL to specify all enabled */ + pScreenshot->Base.u32Screen = 0; + pScreenshot->Base.u32Width = 0; + pScreenshot->Base.u32Height = 0; + pScreenshot->Base.u32Pitch = 0; + pScreenshot->Base.pvBuffer = NULL; + pScreenshot->Base.pvContext = pScreenshot; + pScreenshot->Base.pfnScreenshotBegin = NULL; + pScreenshot->Base.pfnScreenshotPerform = displaySaveScreenshotReport; + pScreenshot->Base.pfnScreenshotEnd = NULL; + + VBOXCRCMDCTL_HGCM data; + data.Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + data.Hdr.u32Function = SHCRGL_HOST_FN_TAKE_SCREENSHOT; + + data.aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + data.aParms[0].u.pointer.addr = &pScreenshot->Base; + data.aParms[0].u.pointer.size = sizeof(pScreenshot->Base); + + int rc = that->i_crCtlSubmitSync(&data.Hdr, sizeof(data)); + if (RT_SUCCESS(rc)) + { + if (pScreenshot->pu8PNG) + { + pu8Thumbnail = pScreenshot->pu8Thumbnail; + cbThumbnail = pScreenshot->cbThumbnail; + cxThumbnail = pScreenshot->cxThumbnail; + cyThumbnail = pScreenshot->cyThumbnail; + + /* PNG screenshot. */ + pu8PNG = pScreenshot->pu8PNG; + cbPNG = pScreenshot->cbPNG; + cxPNG = pScreenshot->cxPNG; + cyPNG = pScreenshot->cyPNG; + f3DSnapshot = TRUE; + } + else + AssertMsgFailed(("no png\n")); + } + else + AssertMsgFailed(("SHCRGL_HOST_FN_TAKE_SCREENSHOT failed (rc=%Rrc)\n", rc)); + + + RTMemFree(pScreenshot); + } + } + } + + if (!f3DSnapshot) +#endif + { + /* 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 rc = Display::i_displayTakeScreenshotEMT(that, 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(rc) && pbData) + { + Assert(cx && cy); + + /* Prepare a small thumbnail and a PNG screenshot. */ + displayMakeThumbnail(pbData, cx, cy, &pu8Thumbnail, &cbThumbnail, &cxThumbnail, &cyThumbnail); + rc = DisplayMakePNG(pbData, cx, cy, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 1); + if (RT_FAILURE(rc)) + { + if (pu8PNG) + { + RTMemFree(pu8PNG); + pu8PNG = NULL; + } + cbPNG = 0; + cxPNG = 0; + cyPNG = 0; + } + + if (fFreeMem) + RTMemFree(pbData); + else + that->mpDrv->pUpPort->pfnFreeScreenshot(that->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] + */ + SSMR3PutU32(pSSM, 2); /* Write thumbnail and PNG screenshot. */ + + /* First block. */ + SSMR3PutU32(pSSM, (uint32_t)(cbThumbnail + 2 * sizeof(uint32_t))); + SSMR3PutU32(pSSM, 0); /* Block type: thumbnail. */ + + if (cbThumbnail) + { + SSMR3PutU32(pSSM, cxThumbnail); + SSMR3PutU32(pSSM, cyThumbnail); + SSMR3PutMem(pSSM, pu8Thumbnail, cbThumbnail); + } + + /* Second block. */ + SSMR3PutU32(pSSM, (uint32_t)(cbPNG + 2 * sizeof(uint32_t))); + SSMR3PutU32(pSSM, 1); /* Block type: png. */ + + if (cbPNG) + { + SSMR3PutU32(pSSM, cxPNG); + SSMR3PutU32(pSSM, cyPNG); + SSMR3PutMem(pSSM, pu8PNG, cbPNG); + } + + RTMemFree(pu8PNG); + RTMemFree(pu8Thumbnail); +} + +DECLCALLBACK(int) +Display::i_displaySSMLoadScreenshot(PSSMHANDLE pSSM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + RT_NOREF(pvUser); + if (uVersion != sSSMDisplayScreenshotVer) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + /* Skip data. */ + uint32_t cBlocks; + int rc = SSMR3GetU32(pSSM, &cBlocks); + AssertRCReturn(rc, rc); + + for (uint32_t i = 0; i < cBlocks; i++) + { + uint32_t cbBlock; + rc = SSMR3GetU32(pSSM, &cbBlock); + AssertRCBreak(rc); + + uint32_t typeOfBlock; + rc = SSMR3GetU32(pSSM, &typeOfBlock); + AssertRCBreak(rc); + + 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)) + { + rc = SSMR3Skip(pSSM, cbBlock); + AssertRCBreak(rc); + } + } + + return rc; +} + +/** + * Save/Load some important guest state + */ +DECLCALLBACK(void) +Display::i_displaySSMSave(PSSMHANDLE pSSM, void *pvUser) +{ + Display *that = static_cast<Display*>(pvUser); + + SSMR3PutU32(pSSM, that->mcMonitors); + for (unsigned i = 0; i < that->mcMonitors; i++) + { + SSMR3PutU32(pSSM, that->maFramebuffers[i].u32Offset); + SSMR3PutU32(pSSM, that->maFramebuffers[i].u32MaxFramebufferSize); + SSMR3PutU32(pSSM, that->maFramebuffers[i].u32InformationSize); + SSMR3PutU32(pSSM, that->maFramebuffers[i].w); + SSMR3PutU32(pSSM, that->maFramebuffers[i].h); + SSMR3PutS32(pSSM, that->maFramebuffers[i].xOrigin); + SSMR3PutS32(pSSM, that->maFramebuffers[i].yOrigin); + SSMR3PutU32(pSSM, that->maFramebuffers[i].flags); + } + SSMR3PutS32(pSSM, that->xInputMappingOrigin); + SSMR3PutS32(pSSM, that->yInputMappingOrigin); + SSMR3PutU32(pSSM, that->cxInputMapping); + SSMR3PutU32(pSSM, that->cyInputMapping); + SSMR3PutU32(pSSM, that->mfGuestVBVACapabilities); + SSMR3PutU32(pSSM, that->mfHostCursorCapabilities); +} + +DECLCALLBACK(int) +Display::i_displaySSMLoad(PSSMHANDLE pSSM, void *pvUser, uint32_t uVersion, uint32_t uPass) +{ + Display *that = static_cast<Display*>(pvUser); + + 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 rc = SSMR3GetU32(pSSM, &cMonitors); + AssertRCReturn(rc, rc); + if (cMonitors != that->mcMonitors) + return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Number of monitors changed (%d->%d)!"), cMonitors, that->mcMonitors); + + for (uint32_t i = 0; i < cMonitors; i++) + { + SSMR3GetU32(pSSM, &that->maFramebuffers[i].u32Offset); + SSMR3GetU32(pSSM, &that->maFramebuffers[i].u32MaxFramebufferSize); + SSMR3GetU32(pSSM, &that->maFramebuffers[i].u32InformationSize); + if ( uVersion == sSSMDisplayVer2 + || uVersion == sSSMDisplayVer3 + || uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + uint32_t w; + uint32_t h; + SSMR3GetU32(pSSM, &w); + SSMR3GetU32(pSSM, &h); + that->maFramebuffers[i].w = w; + that->maFramebuffers[i].h = h; + } + if ( uVersion == sSSMDisplayVer3 + || uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + int32_t xOrigin; + int32_t yOrigin; + uint32_t flags; + SSMR3GetS32(pSSM, &xOrigin); + SSMR3GetS32(pSSM, &yOrigin); + SSMR3GetU32(pSSM, &flags); + that->maFramebuffers[i].xOrigin = xOrigin; + that->maFramebuffers[i].yOrigin = yOrigin; + that->maFramebuffers[i].flags = (uint16_t)flags; + that->maFramebuffers[i].fDisabled = (that->maFramebuffers[i].flags & VBVA_SCREEN_F_DISABLED) != 0; + } + } + if ( uVersion == sSSMDisplayVer4 + || uVersion == sSSMDisplayVer5) + { + SSMR3GetS32(pSSM, &that->xInputMappingOrigin); + SSMR3GetS32(pSSM, &that->yInputMappingOrigin); + SSMR3GetU32(pSSM, &that->cxInputMapping); + SSMR3GetU32(pSSM, &that->cyInputMapping); + } + if (uVersion == sSSMDisplayVer5) + { + SSMR3GetU32(pSSM, &that->mfGuestVBVACapabilities); + SSMR3GetU32(pSSM, &that->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; + + ULONG ul; + mParent->i_machine()->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].fRenderThreadMode = false; + maFramebuffers[ul].pVBVAHostFlags = NULL; +#endif /* VBOX_WITH_HGSMI */ +#ifdef VBOX_WITH_CROGL + RT_ZERO(maFramebuffers[ul].pendingViewportInfo); +#endif + } + + { + // 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); + } + + /* Cache the 3D settings. */ + BOOL fIs3DEnabled = FALSE; + mParent->i_machine()->COMGETTER(Accelerate3DEnabled)(&fIs3DEnabled); + GraphicsControllerType_T enmGpuType = GraphicsControllerType_VBoxVGA; + mParent->i_machine()->COMGETTER(GraphicsControllerType)(&enmGpuType); + mfIsCr3DEnabled = fIs3DEnabled && enmGpuType == GraphicsControllerType_VBoxVGA; + + /* 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) +{ + /* 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 rc = SSMR3RegisterExternal(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(rc, rc); + + /* + * Register loaders for old saved states where iInstance was + * 3 * sizeof(uint32_t *) due to a code mistake. + */ + rc = SSMR3RegisterExternal(pUVM, "DisplayData", 12 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, i_displaySSMLoad, NULL, this); + AssertRCReturn(rc, rc); + + rc = SSMR3RegisterExternal(pUVM, "DisplayData", 24 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, i_displaySSMLoad, NULL, this); + AssertRCReturn(rc, rc); + + /* uInstance is an arbitrary value greater than 1024. Such a value will ensure a quick seek in saved state file. */ + rc = SSMR3RegisterExternal(pUVM, "DisplayScreenshot", 1100 /*uInstance*/, sSSMDisplayScreenshotVer, 0 /*cbGuess*/, + NULL, NULL, NULL, + NULL, i_displaySSMSaveScreenshot, NULL, + NULL, i_displaySSMLoadScreenshot, NULL, this); + + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +DECLCALLBACK(void) Display::i_displayCrCmdFree(struct VBOXCRCMDCTL *pCmd, uint32_t cbCmd, int rc, void *pvCompletion) +{ + RT_NOREF(pCmd, cbCmd, rc); + Assert(pvCompletion); + RTMemFree((void *)pvCompletion); +} + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) +int Display::i_crOglWindowsShow(bool fShow) +{ + if (!mfCrOglDataHidden == !!fShow) + return VINF_SUCCESS; + + if (!mhCrOglSvc) + { + /* No 3D or the VMSVGA3d kind. */ + Assert(!mfIsCr3DEnabled); + return VERR_INVALID_STATE; + } + + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (!pVMMDev) + { + AssertMsgFailed(("no vmmdev\n")); + return VERR_INVALID_STATE; + } + + VBOXCRCMDCTL_HGCM *pData = (VBOXCRCMDCTL_HGCM*)RTMemAlloc(sizeof(VBOXCRCMDCTL_HGCM)); + if (!pData) + { + AssertMsgFailed(("RTMemAlloc failed\n")); + return VERR_NO_MEMORY; + } + + pData->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pData->Hdr.u32Function = SHCRGL_HOST_FN_WINDOWS_SHOW; + + pData->aParms[0].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[0].u.uint32 = (uint32_t)fShow; + + int rc = i_crCtlSubmit(&pData->Hdr, sizeof(*pData), i_displayCrCmdFree, pData); + if (RT_SUCCESS(rc)) + mfCrOglDataHidden = !fShow; + else + { + AssertMsgFailed(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + RTMemFree(pData); + } + + return rc; +} +#endif + + +// public methods only for internal purposes +///////////////////////////////////////////////////////////////////////////// + +int Display::i_notifyCroglResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, void *pvVRAM) +{ + RT_NOREF(pView); +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + if (maFramebuffers[pScreen->u32ViewIndex].fRenderThreadMode) + return VINF_SUCCESS; /* nop it */ + + if (mfIsCr3DEnabled) + { + int rc = VERR_INVALID_STATE; + if (mhCrOglSvc) + { + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + VBOXCRCMDCTL_HGCM *pCtl; + pCtl = (VBOXCRCMDCTL_HGCM*)RTMemAlloc(sizeof(CRVBOXHGCMDEVRESIZE) + sizeof(VBOXCRCMDCTL_HGCM)); + if (pCtl) + { + CRVBOXHGCMDEVRESIZE *pData = (CRVBOXHGCMDEVRESIZE*)(pCtl+1); + pData->Screen = *pScreen; + pData->pvVRAM = pvVRAM; + + pCtl->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pCtl->Hdr.u32Function = SHCRGL_HOST_FN_DEV_RESIZE; + pCtl->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pCtl->aParms[0].u.pointer.addr = pData; + pCtl->aParms[0].u.pointer.size = sizeof(*pData); + + rc = i_crCtlSubmit(&pCtl->Hdr, sizeof(*pCtl), i_displayCrCmdFree, pCtl); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + RTMemFree(pCtl); + } + } + else + rc = VERR_NO_MEMORY; + } + } + + return rc; + } +#else /* !VBOX_WITH_HGCM || !VBOX_WITH_CROGL */ + RT_NOREF(pScreen, pvVRAM); +#endif + return VINF_SUCCESS; +} + +/** + * 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) +{ + LogRel(("Display::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 actially chnaged 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) + 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; + } + + /* 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 /* VBOX_WITH_HGSMI */ + /* 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 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; + } + if (cxInputMapping && cyInputMapping) + { + x1 = xInputMappingOrigin; + y1 = yInputMappingOrigin; + x2 = xInputMappingOrigin + cxInputMapping; + y2 = yInputMappingOrigin + cyInputMapping; + } + else + 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; +} + +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. */ + mpDrv->pUpPort->pfnReportHostCursorCapabilities(mpDrv->pUpPort, fCapabilitiesAdded, fCapabilitiesRemoved); + mfHostCursorCapabilities = fHostCursorCapabilities; + return S_OK; +} + +HRESULT Display::i_reportHostCursorPosition(int32_t x, int32_t y) +{ + 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. */ + mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, xAdj, yAdj); + 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 rc = i_saveVisibleRegion(cRect, pRect); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pVisibleRegion); + return rc; + } + + 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); + } + } + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + VMMDev *vmmDev = mParent->i_getVMMDev(); + if (mfIsCr3DEnabled && vmmDev) + { + if (mhCrOglSvc) + { + VBOXCRCMDCTL_HGCM *pCtl; + pCtl = (VBOXCRCMDCTL_HGCM*)RTMemAlloc(RT_MAX(cRect, 1) * sizeof(RTRECT) + sizeof(VBOXCRCMDCTL_HGCM)); + if (pCtl) + { + RTRECT *pRectsCopy = (RTRECT*)(pCtl+1); + memcpy(pRectsCopy, pRect, cRect * sizeof(RTRECT)); + + pCtl->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pCtl->Hdr.u32Function = SHCRGL_HOST_FN_SET_VISIBLE_REGION; + + pCtl->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pCtl->aParms[0].u.pointer.addr = pRectsCopy; + pCtl->aParms[0].u.pointer.size = (uint32_t)(cRect * sizeof(RTRECT)); + + rc = i_crCtlSubmit(&pCtl->Hdr, sizeof(*pCtl), i_displayCrCmdFree, pCtl); + if (!RT_SUCCESS(rc)) + { + AssertMsgFailed(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + RTMemFree(pCtl); + } + } + else + AssertMsgFailed(("failed to allocate rects memory\n")); + } + else + AssertMsgFailed(("mhCrOglSvc is NULL\n")); + } +#endif + + RTMemTmpFree(pVisibleRegion); + + 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 rc = videoAccelEnterVMMDev(&mVideoAccelLegacy); + if (RT_SUCCESS(rc)) + { + rc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort); + videoAccelLeaveVMMDev(&mVideoAccelLegacy); + } + LogFlowFunc(("leave %Rrc\n", rc)); + return rc; +} + +int Display::VideoAccelEnableVGA(bool fEnable, VBVAMEMORY *pVbvaMemory) +{ + LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory)); + int rc = videoAccelEnterVGA(&mVideoAccelLegacy); + if (RT_SUCCESS(rc)) + { + rc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort); + videoAccelLeaveVGA(&mVideoAccelLegacy); + } + LogFlowFunc(("leave %Rrc\n", rc)); + return rc; +} + +void Display::VideoAccelFlushVMMDev(void) +{ + LogFlowFunc(("enter\n")); + int rc = videoAccelEnterVMMDev(&mVideoAccelLegacy); + if (RT_SUCCESS(rc)) + { + i_VideoAccelFlush(mpDrv->pUpPort); + videoAccelLeaveVMMDev(&mVideoAccelLegacy); + } + LogFlowFunc(("leave\n")); +} + +/* Called always by one VRDP server thread. Can be thread-unsafe. + */ +void Display::i_VideoAccelVRDP(bool fEnable) +{ + LogRelFlowFunc(("fEnable = %d\n", fEnable)); + + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + + int c = fEnable? + ASMAtomicIncS32(&mcVideoAccelVRDPRefs): + ASMAtomicDecS32(&mcVideoAccelVRDPRefs); + + Assert (c >= 0); + + /* 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()) + { +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + if (mfIsCr3DEnabled) + { + VBOXCRCMDCTL_HGCM data; + RT_ZERO(data); + data.Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + data.Hdr.u32Function = SHCRGL_HOST_FN_SCREEN_CHANGED; + + data.aParms[0].type = VBOX_HGCM_SVC_PARM_32BIT; + data.aParms[0].u.uint32 = aScreenId; + + int vrc = i_crCtlSubmitSync(&data.Hdr, sizeof(data)); + AssertRC(vrc); + } +#endif /* defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) */ + + VMR3ReqCallNoWaitU(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(); + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + if (mfIsCr3DEnabled) + { + VBOXCRCMDCTL_HGCM data; + RT_ZERO(data); + data.Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + data.Hdr.u32Function = SHCRGL_HOST_FN_SCREEN_CHANGED; + + data.aParms[0].type = VBOX_HGCM_SVC_PARM_32BIT; + data.aParms[0].u.uint32 = aScreenId; + + int vrc = i_crCtlSubmitSync(&data.Hdr, sizeof(data)); + AssertRC(vrc); + } + } +#endif /* defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) */ + + 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) +{ + 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; + } + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aDisplay >= 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(); + + /* 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); + if ( mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS + && !(mfGuestVBVACapabilities & VBVACAPS_IRQ)) + { + 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) + { + 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; + + pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, 1, &d, false); + } + } + 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); + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + if (!enabled) + { + VMMDev *vmmDev = mParent->i_getVMMDev(); + if (mfIsCr3DEnabled && vmmDev) + { + VBOXCRCMDCTL_HGCM *pData = (VBOXCRCMDCTL_HGCM*)RTMemAlloc(sizeof(VBOXCRCMDCTL_HGCM)); + if (!pData) + { + AssertMsgFailed(("RTMemAlloc failed\n")); + return VERR_NO_MEMORY; + } + + pData->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pData->Hdr.u32Function = SHCRGL_HOST_FN_SET_VISIBLE_REGION; + + pData->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pData->aParms[0].u.pointer.addr = NULL; + pData->aParms[0].u.pointer.size = 0; /* <- means null rects, NULL pRects address and 0 rects means "disable" */ + + int rc = i_crCtlSubmit(&pData->Hdr, sizeof(*pData), i_displayCrCmdFree, pData); + if (!RT_SUCCESS(rc)) + { + AssertMsgFailed(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + RTMemFree(pData); + } + } + } +#endif + return S_OK; +} + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) +BOOL Display::i_displayCheckTakeScreenshotCrOgl(Display *pDisplay, ULONG aScreenId, uint8_t *pbData, + uint32_t u32Width, uint32_t u32Height) +{ + if ( pDisplay->mfIsCr3DEnabled + && pDisplay->mCrOglCallbacks.pfnHasData + && pDisplay->mCrOglCallbacks.pfnHasData()) + { + VMMDev *pVMMDev = pDisplay->mParent->i_getVMMDev(); + if (pVMMDev) + { + CRVBOXHGCMTAKESCREENSHOT *pScreenshot = (CRVBOXHGCMTAKESCREENSHOT *)RTMemAlloc(sizeof(*pScreenshot)); + if (pScreenshot) + { + /* screen id or CRSCREEN_ALL to specify all enabled */ + pScreenshot->u32Screen = aScreenId; + pScreenshot->u32Width = u32Width; + pScreenshot->u32Height = u32Height; + pScreenshot->u32Pitch = u32Width * 4; + pScreenshot->pvBuffer = pbData; + pScreenshot->pvContext = NULL; + pScreenshot->pfnScreenshotBegin = NULL; + pScreenshot->pfnScreenshotPerform = NULL; + pScreenshot->pfnScreenshotEnd = NULL; + + VBOXCRCMDCTL_HGCM data; + data.Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + data.Hdr.u32Function = SHCRGL_HOST_FN_TAKE_SCREENSHOT; + + data.aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + data.aParms[0].u.pointer.addr = pScreenshot; + data.aParms[0].u.pointer.size = sizeof(*pScreenshot); + + int rc = pDisplay->i_crCtlSubmitSync(&data.Hdr, sizeof(data)); + + RTMemFree(pScreenshot); + + if (RT_SUCCESS(rc)) + return TRUE; + AssertMsgFailed(("failed to get screenshot data from crOgl (rc=%Rrc)\n", rc)); + /* fall back to the non-3d mechanism */ + } + } + } + return FALSE; +} +#endif + +/* static */ +int Display::i_displayTakeScreenshotEMT(Display *pDisplay, ULONG aScreenId, uint8_t **ppbData, size_t *pcbData, + uint32_t *pcx, uint32_t *pcy, bool *pfMemFree) +{ + int rc; + if ( aScreenId == VBOX_VIDEO_PRIMARY_SCREEN + && pDisplay->maFramebuffers[aScreenId].fVBVAEnabled == false) /* A non-VBVA mode. */ + { + if (pDisplay->mpDrv) + { + rc = 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; + rc = 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; + + rc = 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); + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + *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 ( rc == VERR_INVALID_STATE + && aScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + rc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy); + *pfMemFree = false; + } + } + } + else + rc = VERR_NO_MEMORY; + } + else + { + /* No image. */ + *ppbData = NULL; + *pcbData = 0; + *pcx = 0; + *pcy = 0; + *pfMemFree = true; + rc = VINF_SUCCESS; + } + } + else + rc = VERR_INVALID_PARAMETER; + return rc; +} + +static int i_displayTakeScreenshot(PUVM pUVM, Display *pDisplay, struct DRVMAINDISPLAY *pDrv, ULONG aScreenId, + BYTE *address, ULONG width, ULONG height) +{ +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + /* + * CrOgl screenshot hook/hack. + */ + if (Display::i_displayCheckTakeScreenshotCrOgl(pDisplay, aScreenId, (uint8_t *)address, width, height)) + return VINF_SUCCESS; +#endif + + 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 = VMR3ReqPriorityCallWaitU(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 rc = 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(), 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 + { + rc = setError(E_FAIL, + tr("PNG is larger than 32bpp bitmap")); + } + } + else + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not convert screenshot to PNG (%Rrc)"), vrc); + RTMemFree(pu8PNG); + } + } + else if (vrc == VERR_TRY_AGAIN) + rc = setErrorBoth(E_UNEXPECTED, vrc, tr("Screenshot is not available at this time")); + else if (RT_FAILURE(vrc)) + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not take a screenshot (%Rrc)"), vrc); + + return rc; +} + +HRESULT Display::takeScreenShot(ULONG aScreenId, + BYTE *aAddress, + ULONG aWidth, + ULONG aHeight, + BitmapFormat_T aBitmapFormat) +{ + HRESULT rc = S_OK; + + LogRelFlowFunc(("[%d] address=%p, width=%d, height=%d, format 0x%08X\n", + aScreenId, aAddress, aWidth, aHeight, aBitmapFormat)); + + ULONG cbOut = 0; + rc = takeScreenShotWorker(aScreenId, aAddress, aWidth, aHeight, aBitmapFormat, &cbOut); + NOREF(cbOut); + + LogRelFlowFunc(("%Rhrc\n", rc)); + return rc; +} + +HRESULT Display::takeScreenShotToArray(ULONG aScreenId, + ULONG aWidth, + ULONG aHeight, + BitmapFormat_T aBitmapFormat, + std::vector<BYTE> &aScreenData) +{ + HRESULT rc = S_OK; + + 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; + rc = takeScreenShotWorker(aScreenId, &aScreenData.front(), aWidth, aHeight, aBitmapFormat, &cbOut); + if (FAILED(rc)) + cbOut = 0; + + aScreenData.resize(cbOut); + + LogRelFlowFunc(("%Rhrc\n", rc)); + return rc; +} + +#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(); + + 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 rc2 = RTCritSectEnter(&mVideoRecLock); + if (RT_SUCCESS(rc2)) + { + maFramebuffers[uScreenId].Recording.pSourceBitmap = pSourceBitmap; + + rc2 = RTCritSectLeave(&mVideoRecLock); + AssertRC(rc2); + } +} +#endif /* VBOX_WITH_RECORDING */ + +int Display::i_drawToScreenEMT(Display *pDisplay, ULONG aScreenId, BYTE *address, + ULONG x, ULONG y, ULONG width, ULONG height) +{ + int rc = VINF_SUCCESS; + + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId]; + + if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN) + { + rc = 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; + + rc = 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(rc)) + { + 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 + { + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + pDisplay->mParent->i_consoleVRDPServer()->SendUpdateBitmap(aScreenId, x, y, width, height); + + return rc; +} + +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 = VMR3ReqCallWaitU(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 rc = S_OK; + if (vrc == VERR_NOT_SUPPORTED || vrc == VERR_NOT_IMPLEMENTED) + { + /** @todo implement generic fallback for screen blitting. */ + rc = E_NOTIMPL; + } + else if (RT_FAILURE(vrc)) + rc = 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(("rc=%Rhrc\n", rc)); + return rc; +} + +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); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + HRESULT rc = S_OK; + + LogRelFlowFunc(("Sending DPYUPDATE request\n")); + + /* Have to release the lock when calling EMT. */ + alock.release(); + + int vrc = VMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, 0, true); + alock.acquire(); + + if (RT_FAILURE(vrc)) + rc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not invalidate and update the screen (%Rrc)"), vrc); + + LogRelFlowFunc(("rc=%Rhrc\n", rc)); + return rc; +} + +HRESULT Display::invalidateAndUpdateScreen(ULONG aScreenId) +{ + LogRelFlowFunc(("\n")); + + HRESULT rc = S_OK; + + Console::SafeVMPtr ptrVM(mParent); + if (!ptrVM.isOk()) + return ptrVM.rc(); + + int vrc = VMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, aScreenId, false); + if (RT_FAILURE(vrc)) + rc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not invalidate and update the screen %d (%Rrc)"), aScreenId, vrc); + + LogRelFlowFunc(("rc=%Rhrc\n", rc)); + return rc; +} + +HRESULT Display::completeVHWACommand(BYTE *aCommand) +{ +#ifdef VBOX_WITH_VIDEOHWACCEL + mpDrv->pVBVACallbacks->pfnVHWACommandCompleteAsync(mpDrv->pVBVACallbacks, (VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)aCommand); + return S_OK; +#else + 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); + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + if (mfIsCr3DEnabled) + { + int rc = i_crViewportNotify(aScreenId, aX, aY, aWidth, aHeight); + if (RT_FAILURE(rc)) + { + DISPLAYFBINFO *pFb = &maFramebuffers[aScreenId]; + pFb->pendingViewportInfo.fPending = true; + pFb->pendingViewportInfo.x = aX; + pFb->pendingViewportInfo.y = aY; + pFb->pendingViewportInfo.width = aWidth; + pFb->pendingViewportInfo.height = aHeight; + } + } +#endif /* VBOX_WITH_CROGL && VBOX_WITH_HGCM */ + + /* 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(); + + 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) + VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT, + 3, this, aScreenId, false); + } + + 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; + pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, cDisplays, paDisplayDefs, fForce); + + 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")); + +#ifdef VBOX_WITH_CROGL + i_crOglWindowsShow(true); +#endif + } + else + { +#ifdef VBOX_WITH_CROGL + if (machineState == MachineState_Paused) + i_crOglWindowsShow(false); +#endif + } + break; + } + default: + AssertFailed(); + } + + return S_OK; +} + + +// private methods +///////////////////////////////////////////////////////////////////////////// + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) +int Display::i_crViewportNotify(ULONG aScreenId, ULONG x, ULONG y, ULONG width, ULONG height) +{ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (!pVMMDev) + return VERR_INVALID_STATE; + + size_t cbData = RT_UOFFSETOF_DYN(VBOXCRCMDCTL_HGCM, aParms[5]); /* (clang doesn't think this is a POD, thus _DYN.) */ + VBOXCRCMDCTL_HGCM *pData = (VBOXCRCMDCTL_HGCM *)alloca(cbData); + + pData->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pData->Hdr.u32Function = SHCRGL_HOST_FN_VIEWPORT_CHANGED; + + pData->aParms[0].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[0].u.uint32 = aScreenId; + + pData->aParms[1].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[1].u.uint32 = x; + + pData->aParms[2].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[2].u.uint32 = y; + + pData->aParms[3].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[3].u.uint32 = width; + + pData->aParms[4].type = VBOX_HGCM_SVC_PARM_32BIT; + pData->aParms[4].u.uint32 = height; + + return i_crCtlSubmitSyncIfHasDataForScreen(aScreenId, &pData->Hdr, (uint32_t)cbData); +} +#endif +#ifdef VBOX_WITH_CRHGSMI + +void Display::i_setupCrHgsmiData(void) +{ + VMMDev *pVMMDev = mParent->i_getVMMDev(); + Assert(pVMMDev); + int rc = RTCritSectRwEnterExcl(&mCrOglLock); + AssertRC(rc); + + if (pVMMDev) + rc = pVMMDev->hgcmHostSvcHandleCreate("VBoxSharedCrOpenGL", &mhCrOglSvc); + else + rc = VERR_GENERAL_FAILURE; + + if (RT_SUCCESS(rc)) + { + Assert(mhCrOglSvc); + /* setup command completion callback */ + VBOXVDMACMD_CHROMIUM_CTL_CRHGSMI_SETUP_MAINCB Completion; + Completion.Hdr.enmType = VBOXVDMACMD_CHROMIUM_CTL_TYPE_CRHGSMI_SETUP_MAINCB; + Completion.Hdr.cbCmd = sizeof(Completion); + Completion.hCompletion = mpDrv->pVBVACallbacks; + Completion.pfnCompletion = mpDrv->pVBVACallbacks->pfnCrHgsmiCommandCompleteAsync; + + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = &Completion; + parm.u.pointer.size = 0; + + rc = pVMMDev->hgcmHostCall("VBoxSharedCrOpenGL", SHCRGL_HOST_FN_CRHGSMI_CTL, 1, &parm); + if (RT_SUCCESS(rc)) + mCrOglCallbacks = Completion.MainInterface; + else + AssertMsgFailed(("VBOXVDMACMD_CHROMIUM_CTL_TYPE_CRHGSMI_SETUP_COMPLETION failed (rc=%Rrc)\n", rc)); + } + + if (RT_FAILURE(rc)) + mhCrOglSvc = NULL; + + RTCritSectRwLeaveExcl(&mCrOglLock); +} + +void Display::i_destructCrHgsmiData(void) +{ + int rc = RTCritSectRwEnterExcl(&mCrOglLock); + AssertRC(rc); + mhCrOglSvc = NULL; + RTCritSectRwLeaveExcl(&mCrOglLock); +} + +#endif /* VBOX_WITH_CRHGSMI */ + +/** + * 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 rc = 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 rc; +} + +/** + * 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 rc = pDisplay->i_videoAccelRefreshProcess(pDrv->pUpPort); + if (rc != VINF_TRY_AGAIN) /* Means 'do nothing' here. */ + { + if (rc == 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 defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + if (pDisplay->mfIsCr3DEnabled) + { + if (ASMAtomicCmpXchgU32(&pDisplay->mfCrOglVideoRecState, CRVREC_STATE_SUBMITTED, CRVREC_STATE_IDLE)) + { + if ( pDisplay->mCrOglCallbacks.pfnHasData + && pDisplay->mCrOglCallbacks.pfnHasData()) + { + /* submit */ + VBOXCRCMDCTL_HGCM *pData = &pDisplay->mCrOglScreenshotCtl; + + pData->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pData->Hdr.u32Function = SHCRGL_HOST_FN_TAKE_SCREENSHOT; + + pData->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pData->aParms[0].u.pointer.addr = &pDisplay->mCrOglScreenshotData; + pData->aParms[0].u.pointer.size = sizeof(pDisplay->mCrOglScreenshotData); + rc = pDisplay->i_crCtlSubmit(&pData->Hdr, sizeof(*pData), Display::i_displayVRecCompletion, pDisplay); + if (RT_SUCCESS(rc)) + break; + AssertMsgFailed(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + } + + /* no 3D data available, or error has occured, + * go the straight way */ + ASMAtomicWriteU32(&pDisplay->mfCrOglVideoRecState, CRVREC_STATE_IDLE); + } + else + { + /* record request is still in progress, don't do anything */ + break; + } + } +# endif /* VBOX_WITH_HGCM && VBOX_WITH_CROGL */ + + /* 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; + + DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId]; + if (!pFBInfo->fDisabled) + { + ComPtr<IDisplaySourceBitmap> pSourceBitmap; + int rc2 = RTCritSectEnter(&pDisplay->mVideoRecLock); + if (RT_SUCCESS(rc2)) + { + 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 hr = pSourceBitmap->QueryBitmapInfo(&pbAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + if (SUCCEEDED(hr) && pbAddress) + rc = pCtx->SendVideoFrame(uScreenId, 0, 0, BitmapFormat_BGR, + ulBitsPerPixel, ulBytesPerLine, ulWidth, ulHeight, + pbAddress, tsNowMs); + else + rc = VERR_NOT_SUPPORTED; + + pSourceBitmap.setNull(); + } + else + rc = VERR_NOT_SUPPORTED; + + if (rc == 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 + +#ifndef S_FALSE +# define S_FALSE ((HRESULT)1L) +#endif + +int Display::i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand) +{ + 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 + +#ifdef VBOX_WITH_CRHGSMI +void Display::i_handleCrHgsmiCommandCompletion(int32_t result, uint32_t u32Function, PVBOXHGCMSVCPARM pParam) +{ + RT_NOREF(u32Function); + mpDrv->pVBVACallbacks->pfnCrHgsmiCommandCompleteAsync(mpDrv->pVBVACallbacks, + (PVBOXVDMACMD_CHROMIUM_CMD)pParam->u.pointer.addr, result); +} + +void Display::i_handleCrHgsmiControlCompletion(int32_t result, uint32_t u32Function, PVBOXHGCMSVCPARM pParam) +{ + RT_NOREF(u32Function); + PVBOXVDMACMD_CHROMIUM_CTL pCtl = (PVBOXVDMACMD_CHROMIUM_CTL)pParam->u.pointer.addr; + mpDrv->pVBVACallbacks->pfnCrHgsmiControlCompleteAsync(mpDrv->pVBVACallbacks, pCtl, result); +} + +void Display::i_handleCrHgsmiCommandProcess(VBOXVDMACMD_CHROMIUM_CMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd, uint32_t cbCmd) +{ + int rc = VERR_NOT_SUPPORTED; + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = (void *)pCmd; + parm.u.pointer.size = cbCmd; + + if (mhCrOglSvc) + { + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + /* no completion callback is specified with this call, + * the CrOgl code will complete the CrHgsmi command once it processes it */ + rc = pVMMDev->hgcmHostFastCallAsync(mhCrOglSvc, SHCRGL_HOST_FN_CRHGSMI_CMD, &parm, NULL, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + return; + } + else + rc = VERR_INVALID_STATE; + } + + /* we are here because something went wrong with command processing, complete it */ + i_handleCrHgsmiCommandCompletion(rc, SHCRGL_HOST_FN_CRHGSMI_CMD, &parm); +} + +void Display::i_handleCrHgsmiControlProcess(VBOXVDMACMD_CHROMIUM_CTL RT_UNTRUSTED_VOLATILE_GUEST *pCtl, uint32_t cbCtl) +{ + int rc = VERR_NOT_SUPPORTED; + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = (void *)pCtl; + parm.u.pointer.size = cbCtl; + + if (mhCrOglSvc) + { + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + bool fCheckPendingViewport = (pCtl->enmType == VBOXVDMACMD_CHROMIUM_CTL_TYPE_CRHGSMI_SETUP); + rc = pVMMDev->hgcmHostFastCallAsync(mhCrOglSvc, SHCRGL_HOST_FN_CRHGSMI_CTL, &parm, + Display::i_displayCrHgsmiControlCompletion, this); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (fCheckPendingViewport) + { + ULONG ul; + for (ul = 0; ul < mcMonitors; ul++) + { + DISPLAYFBINFO *pFb = &maFramebuffers[ul]; + if (!pFb->pendingViewportInfo.fPending) + continue; + + rc = i_crViewportNotify(ul, pFb->pendingViewportInfo.x, pFb->pendingViewportInfo.y, + pFb->pendingViewportInfo.width, pFb->pendingViewportInfo.height); + if (RT_SUCCESS(rc)) + pFb->pendingViewportInfo.fPending = false; + else + { + AssertMsgFailed(("crViewportNotify failed (rc=%Rrc)\n", rc)); + rc = VINF_SUCCESS; + } + } + } + return; + } + } + else + rc = VERR_INVALID_STATE; + } + + /* we are here because something went wrong with command processing, complete it */ + i_handleCrHgsmiControlCompletion(rc, SHCRGL_HOST_FN_CRHGSMI_CTL, &parm); +} + +DECLCALLBACK(void) Display::i_displayCrHgsmiCommandProcess(PPDMIDISPLAYCONNECTOR pInterface, + VBOXVDMACMD_CHROMIUM_CMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd, + uint32_t cbCmd) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + pDrv->pDisplay->i_handleCrHgsmiCommandProcess(pCmd, cbCmd); +} + +DECLCALLBACK(void) Display::i_displayCrHgsmiControlProcess(PPDMIDISPLAYCONNECTOR pInterface, + VBOXVDMACMD_CHROMIUM_CTL RT_UNTRUSTED_VOLATILE_GUEST *pCmd, + uint32_t cbCmd) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + + pDrv->pDisplay->i_handleCrHgsmiControlProcess(pCmd, cbCmd); +} + +DECLCALLBACK(void) Display::i_displayCrHgsmiCommandCompletion(int32_t result, uint32_t u32Function, PVBOXHGCMSVCPARM pParam, + void *pvContext) +{ + AssertMsgFailed(("not expected!\n")); + Display *pDisplay = (Display *)pvContext; + pDisplay->i_handleCrHgsmiCommandCompletion(result, u32Function, pParam); +} + +DECLCALLBACK(void) Display::i_displayCrHgsmiControlCompletion(int32_t result, uint32_t u32Function, PVBOXHGCMSVCPARM pParam, + void *pvContext) +{ + Display *pDisplay = (Display *)pvContext; + pDisplay->i_handleCrHgsmiControlCompletion(result, u32Function, pParam); + +} +#endif + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) +DECLCALLBACK(void) Display::i_displayCrHgcmCtlSubmitCompletion(int32_t result, uint32_t u32Function, PVBOXHGCMSVCPARM pParam, + void *pvContext) +{ + RT_NOREF(u32Function); + VBOXCRCMDCTL *pCmd = (VBOXCRCMDCTL *)pParam->u.pointer.addr; + if (pCmd->u.pfnInternal) + ((PFNCRCTLCOMPLETION)pCmd->u.pfnInternal)(pCmd, pParam->u.pointer.size, result, pvContext); +} + +int Display::i_handleCrHgcmCtlSubmit(struct VBOXCRCMDCTL RT_UNTRUSTED_VOLATILE_GUEST *pCmd, uint32_t cbCmd, + PFNCRCTLCOMPLETION pfnCompletion, void *pvCompletion) +{ + VMMDev *pVMMDev = mParent ? mParent->i_getVMMDev() : NULL; + if (!pVMMDev) + { + AssertMsgFailed(("no vmmdev\n")); + return VERR_INVALID_STATE; + } + + Assert(mhCrOglSvc); + VBOXHGCMSVCPARM parm; + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = (void *)pCmd; + parm.u.pointer.size = cbCmd; + + pCmd->u.pfnInternal = (PFNRT)pfnCompletion; + int rc = pVMMDev->hgcmHostFastCallAsync(mhCrOglSvc, SHCRGL_HOST_FN_CTL, &parm, i_displayCrHgcmCtlSubmitCompletion, + pvCompletion); + if (!RT_SUCCESS(rc)) + AssertMsgFailed(("hgcmHostFastCallAsync failed (rc=%Rrc)\n", rc)); + + return rc; +} + +DECLCALLBACK(int) Display::i_displayCrHgcmCtlSubmit(PPDMIDISPLAYCONNECTOR pInterface, struct VBOXCRCMDCTL *pCmd, uint32_t cbCmd, + PFNCRCTLCOMPLETION pfnCompletion, void *pvCompletion) +{ + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + return pThis->i_handleCrHgcmCtlSubmit(pCmd, cbCmd, pfnCompletion, pvCompletion); +} + +int Display::i_crCtlSubmit(struct VBOXCRCMDCTL* pCmd, uint32_t cbCmd, PFNCRCTLCOMPLETION pfnCompletion, void *pvCompletion) +{ + int rc = RTCritSectRwEnterShared(&mCrOglLock); + if (RT_SUCCESS(rc)) + { + if (mhCrOglSvc) + rc = mpDrv->pVBVACallbacks->pfnCrCtlSubmit(mpDrv->pVBVACallbacks, pCmd, cbCmd, pfnCompletion, pvCompletion); + else + rc = VERR_NOT_SUPPORTED; + + RTCritSectRwLeaveShared(&mCrOglLock); + } + return rc; +} + +int Display::i_crCtlSubmitSync(struct VBOXCRCMDCTL* pCmd, uint32_t cbCmd) +{ + int rc = RTCritSectRwEnterShared(&mCrOglLock); + if (RT_SUCCESS(rc)) + { + if (mhCrOglSvc) + rc = mpDrv->pVBVACallbacks->pfnCrCtlSubmitSync(mpDrv->pVBVACallbacks, pCmd, cbCmd); + else + rc = VERR_NOT_SUPPORTED; + + RTCritSectRwLeaveShared(&mCrOglLock); + } + return rc; +} + +int Display::i_crCtlSubmitAsyncCmdCopy(struct VBOXCRCMDCTL* pCmd, uint32_t cbCmd) +{ + VBOXCRCMDCTL* pCmdCopy = (VBOXCRCMDCTL*)RTMemAlloc(cbCmd); + if (!pCmdCopy) + { + LogRel(("RTMemAlloc failed\n")); + return VERR_NO_MEMORY; + } + + memcpy(pCmdCopy, pCmd, cbCmd); + + int rc = i_crCtlSubmit(pCmdCopy, cbCmd, i_displayCrCmdFree, pCmdCopy); + if (RT_FAILURE(rc)) + { + LogRel(("crCtlSubmit failed (rc=%Rrc)\n", rc)); + RTMemFree(pCmdCopy); + return rc; + } + + return VINF_SUCCESS; +} + +int Display::i_crCtlSubmitSyncIfHasDataForScreen(uint32_t u32ScreenID, struct VBOXCRCMDCTL* pCmd, uint32_t cbCmd) +{ + int rc = RTCritSectRwEnterShared(&mCrOglLock); + AssertRCReturn(rc, rc); + + if ( mCrOglCallbacks.pfnHasDataForScreen + && mCrOglCallbacks.pfnHasDataForScreen(u32ScreenID)) + rc = i_crCtlSubmitSync(pCmd, cbCmd); + else + rc = i_crCtlSubmitAsyncCmdCopy(pCmd, cbCmd); + + RTCritSectRwLeaveShared(&mCrOglLock); + + return rc; +} + +bool Display::i_handleCrVRecScreenshotBegin(uint32_t uScreen, uint64_t uTimestampMs) +{ +# ifdef VBOX_WITH_RECORDING + RecordingContext *pCtx = mParent->i_recordingGetContext(); + return ( pCtx + && pCtx->IsReady(uScreen, uTimestampMs)); +# else + RT_NOREF(uScreen, uTimestampMs); + return false; +# endif +} + +void Display::i_handleCrVRecScreenshotEnd(uint32_t uScreen, uint64_t uTimestampMs) +{ + RT_NOREF(uScreen, uTimestampMs); +} + +void Display::i_handleCrVRecScreenshotPerform(uint32_t uScreen, + uint32_t x, uint32_t y, uint32_t uPixelFormat, + uint32_t uBitsPerPixel, uint32_t uBytesPerLine, + uint32_t uGuestWidth, uint32_t uGuestHeight, + uint8_t *pu8BufferAddress, uint64_t uTimestampMs) +{ + Assert(mfCrOglVideoRecState == CRVREC_STATE_SUBMITTED); +# ifdef VBOX_WITH_RECORDING + RecordingContext *pCtx = mParent->i_recordingGetContext(); + + if ( pCtx + && pCtx->IsStarted() + && pCtx->IsFeatureEnabled(RecordingFeature_Video)) + { + int rc2 = pCtx->SendVideoFrame(uScreen, x, y, + uPixelFormat, + uBitsPerPixel, uBytesPerLine, + uGuestWidth, uGuestHeight, + pu8BufferAddress, uTimestampMs); + RT_NOREF(rc2); + Assert(rc2 == VINF_SUCCESS /* || rc == VERR_TRY_AGAIN || rc == VINF_TRY_AGAIN*/); + } +# else + RT_NOREF(uScreen, x, y, uPixelFormat, \ + uBitsPerPixel, uBytesPerLine, uGuestWidth, uGuestHeight, pu8BufferAddress, uTimestampMs); +# endif /* VBOX_WITH_RECORDING */ +} + +void Display::i_handleVRecCompletion() +{ + Assert(mfCrOglVideoRecState == CRVREC_STATE_SUBMITTED); + ASMAtomicWriteU32(&mfCrOglVideoRecState, CRVREC_STATE_IDLE); +} + +#endif /* VBOX_WITH_HGCM && VBOX_WITH_CROGL */ + +HRESULT Display::notifyScaleFactorChange(ULONG aScreenId, ULONG aScaleFactorWMultiplied, ULONG aScaleFactorHMultiplied) +{ +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + HRESULT hr = E_UNEXPECTED; + + if (aScreenId >= mcMonitors) + return E_INVALIDARG; + + /* 3D acceleration enabled in VM config. */ + if (mfIsCr3DEnabled) + { + /* VBoxSharedCrOpenGL HGCM host service is running. */ + if (mhCrOglSvc) + { + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + VBOXCRCMDCTL_HGCM *pCtl; + pCtl = (VBOXCRCMDCTL_HGCM *)RTMemAlloc(sizeof(CRVBOXHGCMSETSCALEFACTOR) + sizeof(VBOXCRCMDCTL_HGCM)); + if (pCtl) + { + CRVBOXHGCMSETSCALEFACTOR *pData = (CRVBOXHGCMSETSCALEFACTOR *)(pCtl + 1); + int rc; + + pData->u32Screen = aScreenId; + pData->u32ScaleFactorWMultiplied = aScaleFactorWMultiplied; + pData->u32ScaleFactorHMultiplied = aScaleFactorHMultiplied; + + pCtl->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pCtl->Hdr.u32Function = SHCRGL_HOST_FN_SET_SCALE_FACTOR; + pCtl->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pCtl->aParms[0].u.pointer.addr = pData; + pCtl->aParms[0].u.pointer.size = sizeof(*pData); + + rc = i_crCtlSubmitSync(&pCtl->Hdr, sizeof(*pCtl)); + if (RT_FAILURE(rc)) + AssertMsgFailed(("crCtlSubmitSync failed (rc=%Rrc)\n", rc)); + else + hr = S_OK; + + RTMemFree(pCtl); + } + else + { + LogRel(("Running out of memory on attempt to set OpenGL content scale factor. Ignored.\n")); + hr = E_OUTOFMEMORY; + } + } + else + LogRel(("Internal error occurred on attempt to set OpenGL content scale factor. Ignored.\n")); + } + else + LogRel(("Attempt to specify OpenGL content scale factor while corresponding HGCM host service not yet runing. Ignored.\n")); + } + else +# 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 + { + hr = S_OK; + /* 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 hr; + +#else /* !VBOX_WITH_HGCM || !VBOX_WITH_CROGL */ + RT_NOREF(aScreenId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); + AssertMsgFailed(("Attempt to specify OpenGL content scale factor while corresponding functionality is disabled.")); + return E_UNEXPECTED; +#endif /* !VBOX_WITH_HGCM || !VBOX_WITH_CROGL */ +} + +HRESULT Display::notifyHiDPIOutputPolicyChange(BOOL fUnscaledHiDPI) +{ +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + HRESULT hr = E_UNEXPECTED; + + /* 3D acceleration enabled in VM config. */ + if (mfIsCr3DEnabled) + { + /* VBoxSharedCrOpenGL HGCM host service is running. */ + if (mhCrOglSvc) + { + VMMDev *pVMMDev = mParent->i_getVMMDev(); + if (pVMMDev) + { + VBOXCRCMDCTL_HGCM *pCtl; + pCtl = (VBOXCRCMDCTL_HGCM *)RTMemAlloc(sizeof(CRVBOXHGCMSETUNSCALEDHIDPIOUTPUT) + sizeof(VBOXCRCMDCTL_HGCM)); + if (pCtl) + { + CRVBOXHGCMSETUNSCALEDHIDPIOUTPUT *pData = (CRVBOXHGCMSETUNSCALEDHIDPIOUTPUT *)(pCtl + 1); + int rc; + + pData->fUnscaledHiDPI = RT_BOOL(fUnscaledHiDPI); + + pCtl->Hdr.enmType = VBOXCRCMDCTL_TYPE_HGCM; + pCtl->Hdr.u32Function = SHCRGL_HOST_FN_SET_UNSCALED_HIDPI; + pCtl->aParms[0].type = VBOX_HGCM_SVC_PARM_PTR; + pCtl->aParms[0].u.pointer.addr = pData; + pCtl->aParms[0].u.pointer.size = sizeof(*pData); + + rc = i_crCtlSubmitSync(&pCtl->Hdr, sizeof(*pCtl)); + if (RT_FAILURE(rc)) + AssertMsgFailed(("crCtlSubmitSync failed (rc=%Rrc)\n", rc)); + else + hr = S_OK; + + RTMemFree(pCtl); + } + else + { + LogRel(("Running out of memory on attempt to notify OpenGL about HiDPI output scaling policy change. Ignored.\n")); + hr = E_OUTOFMEMORY; + } + } + else + LogRel(("Internal error occurred on attempt to notify OpenGL about HiDPI output scaling policy change. Ignored.\n")); + } + else + LogRel(("Attempt to notify OpenGL about HiDPI output scaling policy change while corresponding HGCM host service not yet runing. Ignored.\n")); + } + else + { + hr = S_OK; + /* 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 hr; + +#else /* !VBOX_WITH_HGCM || !VBOX_WITH_CROGL */ + RT_NOREF(fUnscaledHiDPI); + AssertMsgFailed(("Attempt to notify OpenGL about HiDPI output scaling policy change while corresponding functionality is disabled.")); + return E_UNEXPECTED; +#endif /* !VBOX_WITH_HGCM || !VBOX_WITH_CROGL */ +} + +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) +DECLCALLBACK(void) Display::i_displayCrVRecScreenshotPerform(void *pvCtx, uint32_t uScreen, + uint32_t x, uint32_t y, + uint32_t uBitsPerPixel, uint32_t uBytesPerLine, + uint32_t uGuestWidth, uint32_t uGuestHeight, + uint8_t *pu8BufferAddress, uint64_t u64Timestamp) +{ + Display *pDisplay = (Display *)pvCtx; + pDisplay->i_handleCrVRecScreenshotPerform(uScreen, + x, y, BitmapFormat_BGR, uBitsPerPixel, + uBytesPerLine, uGuestWidth, uGuestHeight, + pu8BufferAddress, u64Timestamp); +} + +DECLCALLBACK(bool) Display::i_displayCrVRecScreenshotBegin(void *pvCtx, uint32_t uScreen, uint64_t u64Timestamp) +{ + Display *pDisplay = (Display *)pvCtx; + return pDisplay->i_handleCrVRecScreenshotBegin(uScreen, u64Timestamp); +} + +DECLCALLBACK(void) Display::i_displayCrVRecScreenshotEnd(void *pvCtx, uint32_t uScreen, uint64_t u64Timestamp) +{ + Display *pDisplay = (Display *)pvCtx; + pDisplay->i_handleCrVRecScreenshotEnd(uScreen, u64Timestamp); +} + +DECLCALLBACK(void) Display::i_displayVRecCompletion(struct VBOXCRCMDCTL *pCmd, uint32_t cbCmd, int rc, void *pvCompletion) +{ + RT_NOREF(pCmd, cbCmd, rc); + Display *pDisplay = (Display *)pvCompletion; + pDisplay->i_handleVRecCompletion(); +} + +#endif + + +#ifdef VBOX_WITH_HGSMI +/** + * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAEnable} + */ +DECLCALLBACK(int) Display::i_displayVBVAEnable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, + VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pHostFlags, + bool fRenderThreadMode) +{ + LogRelFlowFunc(("uScreenId %d\n", uScreenId)); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + if (pThis->maFramebuffers[uScreenId].fVBVAEnabled && pThis->maFramebuffers[uScreenId].fRenderThreadMode != fRenderThreadMode) + { + 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].fRenderThreadMode = fRenderThreadMode; + 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; + + DISPLAYFBINFO *pFBInfo = &pThis->maFramebuffers[uScreenId]; + + bool fRenderThreadMode = pFBInfo->fRenderThreadMode; + + 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; + pFBInfo->fRenderThreadMode = false; + + vbvaSetMemoryFlagsHGSMI(uScreenId, 0, false, pFBInfo); + + pFBInfo->pVBVAHostFlags = NULL; + + if (!fRenderThreadMode && 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 = &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 = &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); + + DISPLAYFBINFO *pFBInfo = &maFramebuffers[pScreen->u32ViewIndex]; + + 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_notifyCroglResize(pView, pScreen, pvVRAM); + + 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(); + + i_notifyCroglResize(pView, pScreen, pvVRAM); + + 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")); + + 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, bool fData, uint32_t x, uint32_t y) +{ + LogFlowFunc(("\n")); + + PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); + Display *pThis = pDrv->pDisplay; + + fireCursorPositionChangedEvent(pThis->mParent->i_getEventSource(), fData, 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 +#ifdef VBOX_WITH_CRHGSMI + pThis->pDisplay->i_destructCrHgsmiData(); +#endif +#if defined(VBOX_WITH_VIDEOHWACCEL) || defined(VBOX_WITH_CRHGSMI) + 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 +#ifdef VBOX_WITH_CRHGSMI + pThis->pDisplay->i_destructCrHgsmiData(); +#endif +#if defined(VBOX_WITH_VIDEOHWACCEL) || defined(VBOX_WITH_CRHGSMI) + pThis->pVBVACallbacks = NULL; +#endif + + pThis->pDisplay->mpDrv = NULL; + pThis->pDisplay = NULL; + } +#if defined(VBOX_WITH_VIDEOHWACCEL) || defined(VBOX_WITH_CRHGSMI) + pThis->pVBVACallbacks = NULL; +#endif +} + + +/** + * Construct a display driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +DECLCALLBACK(int) Display::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY); + LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "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); + + /* + * 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_CRHGSMI + pThis->IConnector.pfnCrHgsmiCommandProcess = Display::i_displayCrHgsmiCommandProcess; + pThis->IConnector.pfnCrHgsmiControlProcess = Display::i_displayCrHgsmiControlProcess; +#endif +#if defined(VBOX_WITH_HGCM) && defined(VBOX_WITH_CROGL) + pThis->IConnector.pfnCrHgcmCtlSubmit = Display::i_displayCrHgcmCtlSubmit; +#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 + + /* + * 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) || defined(VBOX_WITH_CRHGSMI) + 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. + */ + void *pv; + int rc = CFGMR3QueryPtr(pCfg, "Object", &pv); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc)); + return rc; + } + Display *pDisplay = (Display *)pv; /** @todo Check this cast! */ + pThis->pDisplay = pDisplay; + 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); + +#ifdef VBOX_WITH_CRHGSMI + pDisplay->i_setupCrHgsmiData(); +#endif + + return rc; +} + + +/** + * 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..b632f232 --- /dev/null +++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp @@ -0,0 +1,1014 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 rc = RTSemXRoadsCreate(&pVideoAccel->hXRoadsVideoAccel); + AssertRC(rc); + + return rc; +} + +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) +{ + int rc; + LogRelFlowFunc(("fEnable = %d\n", fEnable)); + + rc = i_videoAccelEnable(fEnable, pVbvaMemory, pUpPort); + + LogRelFlowFunc(("%Rrc.\n", rc)); + return rc; +} + +int Display::i_videoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort) +{ + int rc = VINF_SUCCESS; + + 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 rc; + + 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(("%Rrc.\n", rc)); + return rc; +} + +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 rc = i_videoAccelFlush(pUpPort); + if (RT_FAILURE(rc)) + { + /* 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 rc = VWRN_INVALID_STATE; /* Default is to do a display update in VGA device. */ + + VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy; + + videoAccelEnterVGA(pVideoAccel); + + if (pVideoAccel->fVideoAccelEnabled) + { + Assert(pVideoAccel->pVbvaMemory); + rc = i_videoAccelFlush(pUpPort); + if (RT_FAILURE(rc)) + { + /* Disable on errors. */ + i_videoAccelEnable(false, NULL, pUpPort); + rc = VWRN_INVALID_STATE; /* Do a display update in VGA device. */ + } + else + { + rc = VINF_SUCCESS; + } + } + + videoAccelLeaveVGA(pVideoAccel); + + return rc; +} + +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..5cd92c89 --- /dev/null +++ b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp @@ -0,0 +1,187 @@ +/* $Id: DisplaySourceBitmapImpl.cpp $ */ +/** @file + * Bitmap of a guest screen implementation. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 rc = initSourceBitmap(uScreenId, pFBInfo); + + if (RT_FAILURE(rc)) + 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 hr = S_OK; + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aScreenId = m.uScreenId; + return hr; +} + +HRESULT DisplaySourceBitmap::queryBitmapInfo(BYTE **aAddress, + ULONG *aWidth, + ULONG *aHeight, + ULONG *aBitsPerPixel, + ULONG *aBytesPerLine, + BitmapFormat_T *aBitmapFormat) +{ + HRESULT hr = 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 hr; +} + +int DisplaySourceBitmap::initSourceBitmap(unsigned aScreenId, + DISPLAYFBINFO *pFBInfo) +{ + RT_NOREF(aScreenId); + int rc = 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) + { + rc = VERR_NO_MEMORY; + } + else + { + pAddress = m.pu8Allocated; + } + } + + if (RT_SUCCESS(rc)) + { + 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 rc; +} + +/* 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..57b1327a --- /dev/null +++ b/src/VBox/Main/src-client/DrvAudioRec.cpp @@ -0,0 +1,1353 @@ +/* $Id: DrvAudioRec.cpp $ */ +/** @file + * Video recording audio backend for Main. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* This code makes use of the Opus codec (libopus): + * + * Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic, + * Jean-Marc Valin, Timothy B. Terriberry, + * CSIRO, Gregory Maxwell, Mark Borgerding, + * Erik de Castro Lopo + * + * 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 Internet Society, IETF or IETF Trust, nor the + * names of specific 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 COPYRIGHT OWNER + * 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. + * + * Opus is subject to the royalty-free patent licenses which are + * specified at: + * + * Xiph.Org Foundation: + * https://datatracker.ietf.org/ipr/1524/ + * + * Microsoft Corporation: + * https://datatracker.ietf.org/ipr/1914/ + * + * Broadcom Corporation: + * https://datatracker.ietf.org/ipr/1526/ + * + */ + +/** + * 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include "LoggingNew.h" + +#include "DrvAudioRec.h" +#include "ConsoleImpl.h" + +#include "../../Devices/Audio/DrvAudio.h" +#include "WebMWriter.h" + +#include <iprt/mem.h> +#include <iprt/cdefs.h> + +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/cfgm.h> +#include <VBox/err.h> + +#ifdef VBOX_WITH_LIBOPUS +# include <opus.h> +#endif + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +#define AVREC_OPUS_HZ_MAX 48000 /** Maximum sample rate (in Hz) Opus can handle. */ +#define AVREC_OPUS_FRAME_MS_DEFAULT 20 /** Default Opus frame size (in ms). */ + + +/********************************************************************************************************************************* +* 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 +{ + /** 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; + +/** + * Structure for keeping generic codec parameters. + */ +typedef struct AVRECCODECPARMS +{ + /** The codec's used PCM properties. */ + PDMAUDIOPCMPROPS PCMProps; + /** The codec's bitrate. 0 if not used / cannot be specified. */ + uint32_t uBitrate; + +} AVRECCODECPARMS, *PAVRECCODECPARMS; + +/** + * Structure for keeping codec-specific data. + */ +typedef struct AVRECCODEC +{ + /** Generic codec parameters. */ + AVRECCODECPARMS Parms; + union + { +#ifdef VBOX_WITH_LIBOPUS + struct + { + /** Encoder we're going to use. */ + OpusEncoder *pEnc; + /** Time (in ms) an (encoded) frame takes. + * + * For Opus, valid frame sizes are: + * ms Frame size + * 2.5 120 + * 5 240 + * 10 480 + * 20 (Default) 960 + * 40 1920 + * 60 2880 + */ + uint32_t msFrame; + /** The frame size in bytes (based on msFrame). */ + uint32_t cbFrame; + /** The frame size in samples per frame (based on msFrame). */ + uint32_t csFrame; + } Opus; +#endif /* VBOX_WITH_LIBOPUS */ + }; + +#ifdef VBOX_WITH_STATISTICS /** @todo Make these real STAM values. */ + struct + { + /** Number of frames encoded. */ + uint64_t cEncFrames; + /** Total time (in ms) of already encoded audio data. */ + uint64_t msEncTotal; + } STAM; +#endif /* VBOX_WITH_STATISTICS */ + +} AVRECCODEC, *PAVRECCODEC; + +typedef struct AVRECSINK +{ + /** @todo Add types for container / codec as soon as we implement more stuff. */ + + /** Container data to use for data processing. */ + AVRECCONTAINER Con; + /** Codec data this sink uses for encoding. */ + AVRECCODEC Codec; + /** Timestamp (in ms) of when the sink was created. */ + uint64_t tsStartMs; +} AVRECSINK, *PAVRECSINK; + +/** + * Audio video recording (output) stream. + */ +typedef struct AVRECSTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + /** (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; + /** Temporary buffer for the encoded output (destination) data. */ + void *pvDstBuf; + /** Size (in bytes) of the temporary buffer holding the encoded output (destination) data. */ + size_t cbDstBuf; +} 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. */ + PPDMIAUDIOCONNECTOR pDrvAudio; + /** The driver's configured container parameters. */ + AVRECCONTAINERPARMS ContainerParms; + /** The driver's configured codec parameters. */ + AVRECCODECPARMS CodecParms; + /** The driver's sink for writing output to. */ + AVRECSINK Sink; +} DRVAUDIORECORDING, *PDRVAUDIORECORDING; + +/** Makes DRVAUDIORECORDING out of PDMIHOSTAUDIO. */ +#define PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface) /* (clang doesn't think it is a POD, thus _DYN.) */ \ + ( (PDRVAUDIORECORDING)((uintptr_t)pInterface - RT_UOFFSETOF_DYN(DRVAUDIORECORDING, IHostAudio)) ) + +/** + * Initializes a recording sink. + * + * @returns IPRT status code. + * @param pThis Driver instance. + * @param pSink Sink to initialize. + * @param pConParms Container parameters to set. + * @param pCodecParms Codec parameters to set. + */ +static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PAVRECCODECPARMS pCodecParms) +{ + uint32_t uHz = pCodecParms->PCMProps.uHz; + uint8_t cBytes = pCodecParms->PCMProps.cBytes; + uint8_t cChannels = pCodecParms->PCMProps.cChannels; + uint32_t uBitrate = pCodecParms->uBitrate; + + /* Opus only supports certain input sample rates in an efficient manner. + * So make sure that we use those by resampling the data to the requested rate. */ + if (uHz > 24000) uHz = AVREC_OPUS_HZ_MAX; + else if (uHz > 16000) uHz = 24000; + else if (uHz > 12000) uHz = 16000; + else if (uHz > 8000 ) uHz = 12000; + else uHz = 8000; + + if (cChannels > 2) + { + LogRel(("Recording: Warning: More than 2 (stereo) channels are not supported at the moment\n")); + cChannels = 2; + } + + int orc; + OpusEncoder *pEnc = opus_encoder_create(uHz, cChannels, OPUS_APPLICATION_AUDIO, &orc); + if (orc != OPUS_OK) + { + LogRel(("Recording: Audio codec failed to initialize: %s\n", opus_strerror(orc))); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + AssertPtr(pEnc); + + if (uBitrate) /* Only explicitly set the bitrate if we specified one. Otherwise let Opus decide. */ + { + opus_encoder_ctl(pEnc, OPUS_SET_BITRATE(uBitrate)); + if (orc != OPUS_OK) + { + opus_encoder_destroy(pEnc); + pEnc = NULL; + + LogRel(("Recording: Audio codec failed to set bitrate (%RU32): %s\n", uBitrate, opus_strerror(orc))); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + + const bool fUseVBR = true; /** Use Variable Bit Rate (VBR) by default. @todo Make this configurable? */ + + orc = opus_encoder_ctl(pEnc, OPUS_SET_VBR(fUseVBR ? 1 : 0)); + if (orc != OPUS_OK) + { + opus_encoder_destroy(pEnc); + pEnc = NULL; + + LogRel(("Recording: Audio codec failed to %s VBR mode: %s\n", fUseVBR ? "enable" : "disable", opus_strerror(orc))); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + + int rc = VINF_SUCCESS; + + try + { + switch (pConParms->enmType) + { + case AVRECCONTAINERTYPE_MAIN_CONSOLE: + { + if (pThis->pConsole) + { + pSink->Con.Main.pConsole = pThis->pConsole; + } + else + rc = VERR_NOT_SUPPORTED; + break; + } + + case AVRECCONTAINERTYPE_WEBM: + { + /* 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(); + rc = 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, + WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None); + if (RT_SUCCESS(rc)) + { + rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cBytes * 8 /* Bits */, + &pSink->Con.WebM.uTrack); + if (RT_SUCCESS(rc)) + { + LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile)); + } + else + LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, rc)); + } + else + LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, rc)); + } + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + pSink->Con.Parms.enmType = pConParms->enmType; + + PAVRECCODEC pCodec = &pSink->Codec; + + pCodec->Parms.PCMProps.uHz = uHz; + pCodec->Parms.PCMProps.cChannels = cChannels; + pCodec->Parms.PCMProps.cBytes = cBytes; + pCodec->Parms.PCMProps.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pSink->Codec.Parms.PCMProps.cBytes, + pSink->Codec.Parms.PCMProps.cChannels); + pCodec->Parms.uBitrate = uBitrate; + + pCodec->Opus.pEnc = pEnc; + pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT; + + if (!pCodec->Opus.msFrame) + pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT; /* 20ms by default; to prevent division by zero. */ + pCodec->Opus.csFrame = pSink->Codec.Parms.PCMProps.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame); + pCodec->Opus.cbFrame = DrvAudioHlpFramesToBytes(pCodec->Opus.csFrame, &pSink->Codec.Parms.PCMProps); + +#ifdef VBOX_WITH_STATISTICS + pSink->Codec.STAM.cEncFrames = 0; + pSink->Codec.STAM.msEncTotal = 0; +#endif + pSink->tsStartMs = RTTimeMilliTS(); + } + else + { + if (pEnc) + { + opus_encoder_destroy(pEnc); + pEnc = NULL; + } + + LogRel(("Recording: Error creating sink (%Rrc)\n", rc)); + } + + return rc; +} + + +/** + * Shuts down (closes) a recording sink, + * + * @returns IPRT status code. + * @param pSink Recording sink to shut down. + */ +static void avRecSinkShutdown(PAVRECSINK pSink) +{ + AssertPtrReturnVoid(pSink); + +#ifdef VBOX_WITH_LIBOPUS + if (pSink->Codec.Opus.pEnc) + { + opus_encoder_destroy(pSink->Codec.Opus.pEnc); + pSink->Codec.Opus.pEnc = NULL; + } +#endif + 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 rc2 = pSink->Con.WebM.pWebM->Close(); + AssertRC(rc2); + + delete pSink->Con.WebM.pWebM; + pSink->Con.WebM.pWebM = NULL; + } + break; + } + + case AVRECCONTAINERTYPE_MAIN_CONSOLE: + default: + break; + } +} + + +/** + * Creates an audio output stream and associates it with the specified recording sink. + * + * @returns IPRT 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, PPDMAUDIOSTREAMCFG 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->DestSource.Dest != PDMAUDIOPLAYBACKDEST_FRONT) + { + AssertFailed(); + + LogRel2(("Recording: Support for surround audio not implemented yet\n")); + return VERR_NOT_SUPPORTED; + } + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_LIBOPUS + rc = RTCircBufCreate(&pStreamAV->pCircBuf, pSink->Codec.Opus.cbFrame * 2 /* Use "double buffering" */); + if (RT_SUCCESS(rc)) + { + size_t cbScratchBuf = pSink->Codec.Opus.cbFrame; + pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf); + if (pStreamAV->pvSrcBuf) + { + pStreamAV->cbSrcBuf = cbScratchBuf; + pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf); + if (pStreamAV->pvDstBuf) + { + pStreamAV->cbDstBuf = cbScratchBuf; + + pStreamAV->pSink = pSink; /* Assign sink to stream. */ + pStreamAV->uLastPTSMs = 0; + + if (pCfgAcq) + { + /* Make sure to let the driver backend know that we need the audio data in + * a specific sampling rate Opus is optimized for. */ + pCfgAcq->Props.uHz = pSink->Codec.Parms.PCMProps.uHz; + pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels); + + /* Every Opus frame marks a period for now. Optimize this later. */ + pCfgAcq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(pSink->Codec.Opus.msFrame, &pCfgAcq->Props); + pCfgAcq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(100 /* ms */, &pCfgAcq->Props); /** @todo Make this configurable. */ + pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod * 2; + } + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_NO_MEMORY; + } +#else + RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq); + rc = VERR_NOT_SUPPORTED; +#endif /* VBOX_WITH_LIBOPUS */ + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Destroys (closes) an audio output stream. + * + * @returns IPRT 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->cbSrcBuf = 0; + } + + if (pStreamAV->pvDstBuf) + { + Assert(pStreamAV->cbDstBuf); + RTMemFree(pStreamAV->pvDstBuf); + pStreamAV->cbDstBuf = 0; + } + + return VINF_SUCCESS; +} + + +/** + * Controls an audio output stream + * + * @returns IPRT status code. + * @param pThis Driver instance. + * @param pStreamAV Audio output stream to control. + * @param enmStreamCmd Stream command to issue. + */ +static int avRecControlStreamOut(PDRVAUDIORECORDING pThis, + PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(pThis, pStreamAV); + + int rc = VINF_SUCCESS; + + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + case PDMAUDIOSTREAMCMD_DISABLE: + case PDMAUDIOSTREAMCMD_RESUME: + case PDMAUDIOSTREAMCMD_PAUSE: + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvAudioVideoRecInit(PPDMIHOSTAUDIO pInterface) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + + LogRel(("Recording: Audio driver is using %RU32Hz, %RU16bit, %RU8 %s\n", + pThis->CodecParms.PCMProps.uHz, pThis->CodecParms.PCMProps.cBytes * 8, + pThis->CodecParms.PCMProps.cChannels, pThis->CodecParms.PCMProps.cChannels == 1 ? "channel" : "channels")); + + int rc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, &pThis->CodecParms); + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Audio recording driver failed to initialize, rc=%Rrc\n", rc)); + } + else + LogRel2(("Recording: Audio recording driver initialized\n")); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + RT_NOREF(pInterface, pStream, pvBuf, cxBuf); + + if (pcxRead) + *pcxRead = 0; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxWritten is optional. */ + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + RT_NOREF(pThis); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + + int rc = VINF_SUCCESS; + + uint32_t cbWrittenTotal = 0; + + /* + * Call the encoder with the data. + */ +#ifdef VBOX_WITH_LIBOPUS + PAVRECSINK pSink = pStreamAV->pSink; + AssertPtr(pSink); + PAVRECCODEC pCodec = &pSink->Codec; + AssertPtr(pCodec); + PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf; + AssertPtr(pCircBuf); + + void *pvCircBuf; + size_t cbCircBuf; + + uint32_t cbToWrite = cxBuf; + + /* + * Fetch as much as we can into our internal ring buffer. + */ + while ( cbToWrite + && RTCircBufFree(pCircBuf)) + { + RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf); + + if (cbCircBuf) + { + memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf), + cbWrittenTotal += (uint32_t)cbCircBuf; + Assert(cbToWrite >= cbCircBuf); + cbToWrite -= (uint32_t)cbCircBuf; + } + + RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf); + + if ( RT_FAILURE(rc) + || !cbCircBuf) + { + break; + } + } + + /* + * Process our internal ring buffer and encode the data. + */ + + uint32_t cbSrc; + + /* Only encode data if we have data for the given time period (or more). */ + while (RTCircBufUsed(pCircBuf) >= pCodec->Opus.cbFrame) + { + LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n", + RTCircBufUsed(pCircBuf), pCodec->Opus.csFrame, pCodec->Opus.cbFrame)); + + cbSrc = 0; + + while (cbSrc < pCodec->Opus.cbFrame) + { + RTCircBufAcquireReadBlock(pCircBuf, pCodec->Opus.cbFrame - cbSrc, &pvCircBuf, &cbCircBuf); + + if (cbCircBuf) + { + memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf); + + cbSrc += (uint32_t)cbCircBuf; + Assert(cbSrc <= pStreamAV->cbSrcBuf); + } + + RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf); + + if (!cbCircBuf) + break; + } + +# ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFILE fh; + RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm", + RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + RTFileWrite(fh, pStreamAV->pvSrcBuf, cbSrc, NULL); + RTFileClose(fh); +# endif + + Assert(cbSrc == pCodec->Opus.cbFrame); + + /* + * Opus always encodes PER "OPUS FRAME", that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data. + * + * A packet can have up to 120ms worth of audio data. + * Anything > 120ms of data will result in a "corrupted package" error message by + * by decoding application. + */ + + /* Call the encoder to encode one "Opus frame" per iteration. */ + opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc, + (opus_int16 *)pStreamAV->pvSrcBuf, pCodec->Opus.csFrame, + (uint8_t *)pStreamAV->pvDstBuf, (opus_int32)pStreamAV->cbDstBuf); + if (cbWritten > 0) + { + /* Get overall frames encoded. */ + const uint32_t cEncFrames = opus_packet_get_nb_frames((uint8_t *)pStreamAV->pvDstBuf, cbWritten); + +# ifdef VBOX_WITH_STATISTICS + pSink->Codec.STAM.cEncFrames += cEncFrames; + pSink->Codec.STAM.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames; +# endif + Assert((uint32_t)cbWritten <= (uint32_t)pStreamAV->cbDstBuf); + const uint32_t cbDst = RT_MIN((uint32_t)cbWritten, (uint32_t)pStreamAV->cbDstBuf); + + Assert(cEncFrames == 1); + + if (pStreamAV->uLastPTSMs == 0) + pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */ + + const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames; + const uint64_t uPTSMs = pStreamAV->uLastPTSMs; + + pStreamAV->uLastPTSMs += uDurationMs; + + switch (pSink->Con.Parms.enmType) + { + case AVRECCONTAINERTYPE_MAIN_CONSOLE: + { + HRESULT hr = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbDst, uPTSMs); + Assert(hr == S_OK); + RT_NOREF(hr); + + break; + } + + case AVRECCONTAINERTYPE_WEBM: + { + WebMWriter::BlockData_Opus blockData = { pStreamAV->pvDstBuf, cbDst, uPTSMs }; + rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData)); + AssertRC(rc); + + break; + } + + default: + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + break; + } + } + else if (cbWritten < 0) + { + AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten))); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_FAILURE(rc)) + break; + } + + if (pcxWritten) + *pcxWritten = cbWrittenTotal; +#else + /* Report back all data as being processed. */ + if (pcxWritten) + *pcxWritten = cxBuf; + + rc = VERR_NOT_SUPPORTED; +#endif /* VBOX_WITH_LIBOPUS */ + + LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioVideoRecGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Video recording audio driver"); + + pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM); + pBackendCfg->cbStreamIn = 0; + pBackendCfg->cMaxStreamsIn = 0; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvAudioVideoRecShutdown(PPDMIHOSTAUDIO pInterface) +{ + LogFlowFuncEnter(); + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + + avRecSinkShutdown(&pThis->Sink); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + return VERR_NOT_SUPPORTED; + + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + + /* 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 rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq); + if (RT_SUCCESS(rc)) + { + pStreamAV->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamAV->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + + if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT) + rc = avRecDestroyStreamOut(pThis, pStreamAV); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamAV->pCfg); + pStreamAV->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface); + PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; + + if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT) + return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return 0; /* Video capturing does not provide any input. */ +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return UINT32_MAX; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + + return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvAudioVideoRecStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Nothing to do here for video recording. */ + return VINF_SUCCESS; +} + + +/** + * @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; +} + + +AudioVideoRec::AudioVideoRec(Console *pConsole) + : AudioDriver(pConsole) + , mpDrv(NULL) +{ +} + + +AudioVideoRec::~AudioVideoRec(void) +{ + if (mpDrv) + { + mpDrv->pAudioVideoRec = NULL; + mpDrv = NULL; + } +} + + +/** + * Applies a video recording configuration to this driver instance. + * + * @returns IPRT status code. + * @param Settings Capturing configuration to apply. + */ +int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings) +{ + /** @todo Do some validation here. */ + mVideoRecCfg = Settings; /* Note: Does have an own copy operator. */ + return VINF_SUCCESS; +} + + +/** + * @copydoc AudioDriver::configureDriver + */ +int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg) +{ + int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)mpConsole->i_recordingGetAudioDrv()); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertInteger(pLunCfg, "ObjectConsole", (uintptr_t)mpConsole); + AssertRCReturn(rc, rc); + + /** @todo For now we're using the configuration of the first screen here audio-wise. */ + Assert(mVideoRecCfg.mapScreens.size() >= 1); + const settings::RecordingScreenSettings &Screen0Settings = mVideoRecCfg.mapScreens[0]; + + rc = CFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)Screen0Settings.enmDest); + AssertRCReturn(rc, rc); + if (Screen0Settings.enmDest == RecordingDestination_File) + { + rc = CFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(Screen0Settings.File.strName).c_str()); + AssertRCReturn(rc, rc); + } + rc = CFGMR3InsertInteger(pLunCfg, "CodecHz", Screen0Settings.Audio.uHz); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertInteger(pLunCfg, "CodecBits", Screen0Settings.Audio.cBits); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertInteger(pLunCfg, "CodecChannels", Screen0Settings.Audio.cChannels); + AssertRCReturn(rc, rc); + rc = CFGMR3InsertInteger(pLunCfg, "CodecBitrate", 0); /* Let Opus decide for now. */ + AssertRCReturn(rc, rc); + + return AudioDriver::configureDriver(pLunCfg); +} + + +/** + * 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 */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVideoRec); + + /* + * Get the Console object pointer. + */ + void *pvUser; + int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */ + AssertRCReturn(rc, rc); + + /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */ + pThis->pConsole = (Console *)pvUser; + AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER); + + /* + * Get the pointer to the audio driver instance. + */ + rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */ + AssertRCReturn(rc, rc); + + pThis->pAudioVideoRec = (AudioVideoRec *)pvUser; + AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER); + + /* + * Get the recording container and codec parameters from the audio driver instance. + */ + PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms; + PAVRECCODECPARMS pCodecParms = &pThis->CodecParms; + PPDMAUDIOPCMPROPS pPCMProps = &pCodecParms->PCMProps; + + RT_ZERO(pThis->ContainerParms); + RT_ZERO(pThis->CodecParms); + + rc = CFGMR3QueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType); + AssertRCReturn(rc, rc); + + switch (pConParams->enmType) + { + case AVRECCONTAINERTYPE_WEBM: + rc = CFGMR3QueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile); + AssertRCReturn(rc, rc); + break; + + default: + break; + } + + rc = CFGMR3QueryU32(pCfg, "CodecHz", &pPCMProps->uHz); + AssertRCReturn(rc, rc); + rc = CFGMR3QueryU8(pCfg, "CodecBits", &pPCMProps->cBytes); + AssertRCReturn(rc, rc); + rc = CFGMR3QueryU8(pCfg, "CodecChannels", &pPCMProps->cChannels); + AssertRCReturn(rc, rc); + rc = CFGMR3QueryU32(pCfg, "CodecBitrate", &pCodecParms->uBitrate); + AssertRCReturn(rc, rc); + + pPCMProps->cBytes = pPCMProps->cBytes / 8; /* Bits to bytes. */ + pPCMProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pPCMProps->cBytes, pPCMProps->cChannels); + pPCMProps->fSigned = true; + pPCMProps->fSwapEndian = false; + + AssertMsgReturn(DrvAudioHlpPCMPropsAreValid(pPCMProps), + ("Configuration error: Audio configuration is invalid!\n"), VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES); + + pThis->pAudioVideoRec = (AudioVideoRec *)pvUser; + AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER); + + pThis->pAudioVideoRec->mpDrv = pThis; + + /* + * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls. + * Described in CFGM tree. + */ + pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR); + AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + +#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.webm"); + RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm"); +#endif + + return VINF_SUCCESS; +} + + +/** + * @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(); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnAttach} + */ +/* static */ +DECLCALLBACK(int) AudioVideoRec::drvAttach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(pDrvIns, fFlags); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDRVREG,pfnDetach} + */ +/* static */ +DECLCALLBACK(void) AudioVideoRec::drvDetach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(pDrvIns, fFlags); + + LogFlowFuncEnter(); +} + +/** + * 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 */ + AudioVideoRec::drvAttach, + /* pfnDetach */ + AudioVideoRec::drvDetach, + /* pfnPowerOff */ + NULL, + /* 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..78cda34d --- /dev/null +++ b/src/VBox/Main/src-client/DrvAudioVRDE.cpp @@ -0,0 +1,877 @@ +/* $Id: DrvAudioVRDE.cpp $ */ +/** @file + * VRDE audio backend for Main. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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 "../../Devices/Audio/DrvAudio.h" + +#include <iprt/mem.h> +#include <iprt/cdefs.h> +#include <iprt/circbuf.h> + +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/RemoteDesktop/VRDE.h> +#include <VBox/vmm/cfgm.h> +#include <VBox/err.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Audio VRDE driver instance data. + */ +typedef struct DRVAUDIOVRDE +{ + /** Pointer to audio VRDE object. */ + AudioVRDE *pAudioVRDE; + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Pointer to the VRDP's console object. */ + ConsoleVRDPServer *pConsoleVRDPServer; + /** Pointer to the DrvAudio port interface that is above us. */ + PPDMIAUDIOCONNECTOR pDrvAudio; + /** Number of connected clients to this VRDE instance. */ + uint32_t cClients; +} DRVAUDIOVRDE, *PDRVAUDIOVRDE; + +typedef struct VRDESTREAM +{ + /** The stream's acquired configuration. */ + PPDMAUDIOSTREAMCFG pCfg; + union + { + struct + { + /** Circular buffer for holding the recorded audio frames from the host. */ + PRTCIRCBUF pCircBuf; + } In; + }; +} VRDESTREAM, *PVRDESTREAM; + +/* Sanity. */ +AssertCompileSize(PDMAUDIOFRAME, sizeof(int64_t) * 2 /* st_sample_t using by VRDP server */); + +static int vrdeCreateStreamIn(PVRDESTREAM pStreamVRDE, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pCfgReq); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + pCfgAcq->Props.uHz = 22050; /* The VRDP server's internal frequency. */ + pCfgAcq->Props.cChannels = 2; + pCfgAcq->Props.cBytes = 2; /* 16 bit. */ + pCfgAcq->Props.fSigned = true; + pCfgAcq->Props.fSwapEndian = false; + pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels); + + /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */ + const uint32_t cfVRDPServer = DrvAudioHlpMilliToFrames(200 /* ms */, &pCfgAcq->Props); + + int rc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, DrvAudioHlpFramesToBytes(cfVRDPServer, &pCfgAcq->Props)); + if (RT_SUCCESS(rc)) + { + /* + * Because of historical reasons the VRDP server operates on st_sample_t structures internally, + * which is 2 * int64_t for left/right (stereo) channels. + * + * As the audio connector also uses this format, set the layout to "raw" and just let pass through + * the data without any layout modification needed. + */ + pCfgAcq->enmLayout = PDMAUDIOSTREAMLAYOUT_RAW; + + pCfgAcq->Backend.cfPeriod = cfVRDPServer; + pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering". */ + pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod; + } + + return rc; +} + + +static int vrdeCreateStreamOut(PVRDESTREAM pStreamVRDE, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + RT_NOREF(pStreamVRDE, pCfgReq); + + if (pCfgAcq) + { + /* + * Because of historical reasons the VRDP server operates on st_sample_t structures internally, + * which is 2 * int64_t for left/right (stereo) channels. + * + * As the audio connector also uses this format, set the layout to "raw" and just let pass through + * the data without any layout modification needed. + */ + pCfgAcq->enmLayout = PDMAUDIOSTREAMLAYOUT_RAW; + + pCfgAcq->Props.uHz = 22050; /* The VRDP server's internal frequency. */ + pCfgAcq->Props.cChannels = 2; + pCfgAcq->Props.cBytes = 2; /* 16 bit. */ + pCfgAcq->Props.fSigned = true; + pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels); + + /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */ + pCfgAcq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(20 /* ms */, &pCfgAcq->Props); + pCfgAcq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(100 /* ms */, &pCfgAcq->Props); + pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod * 2; + } + + return VINF_SUCCESS; +} + + +static int vrdeControlStreamOut(PDRVAUDIOVRDE pDrv, PVRDESTREAM pStreamVRDE, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + RT_NOREF(pDrv, pStreamVRDE, enmStreamCmd); + + LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd)); + + return VINF_SUCCESS; +} + + +static int vrdeControlStreamIn(PDRVAUDIOVRDE pDrv, PVRDESTREAM pStreamVRDE, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd)); + + if (!pDrv->pConsoleVRDPServer) + return VINF_SUCCESS; + + int rc; + + /* Initialize only if not already done. */ + switch (enmStreamCmd) + { + case PDMAUDIOSTREAMCMD_ENABLE: + { + rc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE, + DrvAudioHlpMilliToFrames(200 /* ms */, &pStreamVRDE->pCfg->Props), + pStreamVRDE->pCfg->Props.uHz, pStreamVRDE->pCfg->Props.cChannels, + pStreamVRDE->pCfg->Props.cBytes * 8 /* Bit */); + if (rc == VERR_NOT_SUPPORTED) + { + LogRel2(("Audio: No VRDE client connected, so no input recording available\n")); + rc = VINF_SUCCESS; + } + + break; + } + + case PDMAUDIOSTREAMCMD_DISABLE: + { + pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */); + rc = VINF_SUCCESS; + + break; + } + + case PDMAUDIOSTREAMCMD_PAUSE: + { + rc = VINF_SUCCESS; + break; + } + + case PDMAUDIOSTREAMCMD_RESUME: + { + rc = VINF_SUCCESS; + break; + } + + default: + { + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(rc)) + LogFunc(("Failed with %Rrc\n", rc)); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnInit} + */ +static DECLCALLBACK(int) drvAudioVRDEInit(PPDMIHOSTAUDIO pInterface) +{ + RT_NOREF(pInterface); + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamCapture(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxRead is optional. */ + + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + size_t cbData = 0; + + if (RTCircBufUsed(pStreamVRDE->In.pCircBuf)) + { + void *pvData; + + RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cxBuf, &pvData, &cbData); + + if (cbData) + memcpy(pvBuf, pvData, cbData); + + RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData); + } + + if (pcxRead) + *pcxRead = (uint32_t)cbData; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cxBuf, VERR_INVALID_PARAMETER); + /* pcxWritten is optional. */ + + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + if (!pDrv->pConsoleVRDPServer) + return VERR_NOT_AVAILABLE; + + /* Note: We get the number of *frames* in cxBuf + * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout) on stream creation. */ + uint32_t cfLive = cxBuf; + + PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->pCfg->Props; + + VRDEAUDIOFORMAT format = VRDE_AUDIO_FMT_MAKE(pProps->uHz, + pProps->cChannels, + pProps->cBytes * 8 /* Bit */, + pProps->fSigned); + + /* Use the internal counter to track if we (still) can write to the VRDP server + * or if we need to wait another round (time slot). */ + uint32_t cfToWrite = cfLive; + + Log3Func(("cfLive=%RU32, cfToWrite=%RU32\n", cfLive, cfToWrite)); + + /* Don't play more than available. */ + if (cfToWrite > cfLive) + cfToWrite = cfLive; + + int rc = VINF_SUCCESS; + + PPDMAUDIOFRAME paSampleBuf = (PPDMAUDIOFRAME)pvBuf; + AssertPtr(paSampleBuf); + + /* + * Call the VRDP server with the data. + */ + uint32_t cfWritten = 0; + while (cfToWrite) + { + uint32_t cfChunk = cfToWrite; /** @todo For now write all at once. */ + + if (!cfChunk) /* Nothing to send. Bail out. */ + break; + + /* Note: The VRDP server expects int64_t samples per channel, regardless of the actual + * sample bits (e.g 8 or 16 bits). */ + pDrv->pConsoleVRDPServer->SendAudioSamples(paSampleBuf + cfWritten, cfChunk /* Frames */, format); + + cfWritten += cfChunk; + Assert(cfWritten <= cfLive); + + Assert(cfToWrite >= cfChunk); + cfToWrite -= cfChunk; + } + + if (RT_SUCCESS(rc)) + { + /* Return frames instead of bytes here + * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout). */ + if (pcxWritten) + *pcxWritten = cfWritten; + } + + return rc; +} + + +static int vrdeDestroyStreamIn(PDRVAUDIOVRDE pDrv, PVRDESTREAM pStreamVRDE) +{ + if (pDrv->pConsoleVRDPServer) + pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL); + + if (pStreamVRDE->In.pCircBuf) + { + RTCircBufDestroy(pStreamVRDE->In.pCircBuf); + pStreamVRDE->In.pCircBuf = NULL; + } + + return VINF_SUCCESS; +} + + +static int vrdeDestroyStreamOut(PDRVAUDIOVRDE pDrv, PVRDESTREAM pStreamVRDE) +{ + RT_NOREF(pDrv, pStreamVRDE); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvAudioVRDEGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VRDE audio driver"); + + pBackendCfg->cbStreamOut = sizeof(VRDESTREAM); + pBackendCfg->cbStreamIn = sizeof(VRDESTREAM); + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown} + */ +static DECLCALLBACK(void) drvAudioVRDEShutdown(PPDMIHOSTAUDIO pInterface) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + AssertPtrReturnVoid(pDrv); + + if (pDrv->pConsoleVRDPServer) + pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL); +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVRDEGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + AssertPtrReturn(pDrv, PDMAUDIOBACKENDSTS_ERROR); + + RT_NOREF(enmDir); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + + RT_NOREF(pInterface); + + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + int rc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + rc = vrdeCreateStreamIn (pStreamVRDE, pCfgReq, pCfgAcq); + else + rc = vrdeCreateStreamOut(pStreamVRDE, pCfgReq, pCfgAcq); + + if (RT_SUCCESS(rc)) + { + pStreamVRDE->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq); + if (!pStreamVRDE->pCfg) + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + if (!pStreamVRDE->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamVRDE->pCfg->enmDir == PDMAUDIODIR_IN) + rc = vrdeDestroyStreamIn(pDrv, pStreamVRDE); + else + rc = vrdeDestroyStreamOut(pDrv, pStreamVRDE); + + if (RT_SUCCESS(rc)) + { + DrvAudioHlpStreamCfgFree(pStreamVRDE->pCfg); + pStreamVRDE->pCfg = NULL; + } + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamControl(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + if (!pStreamVRDE->pCfg) /* Not (yet) configured? Skip. */ + return VINF_SUCCESS; + + int rc; + if (pStreamVRDE->pCfg->enmDir == PDMAUDIODIR_IN) + rc = vrdeControlStreamIn(pDrv, pStreamVRDE, enmStreamCmd); + else + rc = vrdeControlStreamOut(pDrv, pStreamVRDE, enmStreamCmd); + + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvAudioVRDEStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + if (pStreamVRDE->pCfg->enmDir == PDMAUDIODIR_IN) + { + /* Return frames instead of bytes here + * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout). */ + return (uint32_t)PDMAUDIOSTREAMCFG_B2F(pStreamVRDE->pCfg, RTCircBufUsed(pStreamVRDE->In.pCircBuf)); + } + + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvAudioVRDEStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream; + + PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->pCfg->Props; + + RT_NOREF(pDrv, pProps); + + /* Return frames instead of bytes here + * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout). */ + if (pDrv->cClients) + return _16K; /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */ + + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} + */ +static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVRDEStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio); + RT_NOREF(pStream); + + PDMAUDIOSTREAMSTS streamSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED; + + if (pDrv->cClients) /* If any clients are connected, flag the stream as enabled. */ + streamSts |= PDMAUDIOSTREAMSTS_FLAG_ENABLED; + + return streamSts; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate} + */ +static DECLCALLBACK(int) drvAudioVRDEStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + + /* Nothing to do here for VRDE. */ + return VINF_SUCCESS; +} + + +/** + * @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; +} + + +AudioVRDE::AudioVRDE(Console *pConsole) + : AudioDriver(pConsole) + , mpDrv(NULL) +{ +} + + +AudioVRDE::~AudioVRDE(void) +{ + if (mpDrv) + { + mpDrv->pAudioVRDE = NULL; + mpDrv = NULL; + } +} + + +/** + * @copydoc AudioDriver::configureDriver + */ +int AudioVRDE::configureDriver(PCFGMNODE pLunCfg) +{ + int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)this); + AssertRCReturn(rc, rc); + CFGMR3InsertInteger(pLunCfg, "ObjectVRDPServer", (uintptr_t)mpConsole->i_consoleVRDPServer()); + AssertRCReturn(rc, rc); + + return AudioDriver::configureDriver(pLunCfg); +} + + +void AudioVRDE::onVRDEClientConnect(uint32_t uClientID) +{ + RT_NOREF(uClientID); + + LogRel2(("Audio: VRDE client connected\n")); + if (mpDrv) + mpDrv->cClients++; +} + + +void AudioVRDE::onVRDEClientDisconnect(uint32_t uClientID) +{ + RT_NOREF(uClientID); + + LogRel2(("Audio: VRDE client disconnected\n")); + Assert(mpDrv->cClients); + if (mpDrv) + mpDrv->cClients--; +} + + +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. + * + * @return IPRT 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); + + VRDEAUDIOFORMAT audioFmt = pVRDEAudioBegin->fmt; + + int iSampleHz = VRDE_AUDIO_FMT_SAMPLE_FREQ(audioFmt); RT_NOREF(iSampleHz); + int cChannels = VRDE_AUDIO_FMT_CHANNELS(audioFmt); RT_NOREF(cChannels); + int cBits = VRDE_AUDIO_FMT_BITS_PER_SAMPLE(audioFmt); RT_NOREF(cBits); + bool fUnsigned = VRDE_AUDIO_FMT_SIGNED(audioFmt); RT_NOREF(fUnsigned); + + LogFlowFunc(("cbSample=%RU32, iSampleHz=%d, cChannels=%d, cBits=%d, fUnsigned=%RTbool\n", + VRDE_AUDIO_FMT_BYTES_PER_SAMPLE(audioFmt), iSampleHz, cChannels, cBits, fUnsigned)); + + return VINF_SUCCESS; +} + + +int AudioVRDE::onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData) +{ + PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pvContext; + AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER); + + void *pvBuf; + size_t cbBuf; + + RTCircBufAcquireWriteBlock(pStreamVRDE->In.pCircBuf, cbData, &pvBuf, &cbBuf); + + if (cbBuf) + memcpy(pvBuf, pvData, cbBuf); + + RTCircBufReleaseWriteBlock(pStreamVRDE->In.pCircBuf, cbBuf); + + if (cbBuf < cbData) + LogRel(("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. */ +} + + +/** + * Construct a VRDE audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +/* static */ +DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE); + + 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; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvAudioVRDEQueryInterface; + /* IHostAudio */ + PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVRDE); + + /* + * Get the ConsoleVRDPServer object pointer. + */ + void *pvUser; + int rc = CFGMR3QueryPtr(pCfg, "ObjectVRDPServer", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */ + AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectVRDPServer\" value, rc=%Rrc\n", rc), rc); + + /* CFGM tree saves the pointer to ConsoleVRDPServer in the Object node of AudioVRDE. */ + pThis->pConsoleVRDPServer = (ConsoleVRDPServer *)pvUser; + pThis->cClients = 0; + + /* + * Get the AudioVRDE object pointer. + */ + pvUser = NULL; + rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */ + AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc); + + pThis->pAudioVRDE = (AudioVRDE *)pvUser; + pThis->pAudioVRDE->mpDrv = pThis; + + /* + * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls. + * Described in CFGM tree. + */ + pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR); + AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + + return VINF_SUCCESS; +} + + +/** + * @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(); + + /* + * 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. + */ + if (pThis->pAudioVRDE) + { + pThis->pAudioVRDE->mpDrv = NULL; + pThis->pAudioVRDE = NULL; + } +} + +/** + * @interface_method_impl{PDMDRVREG,pfnAttach} + */ +/* static */ +DECLCALLBACK(int) AudioVRDE::drvAttach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(pDrvIns, fFlags); + + LogFlowFuncEnter(); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDRVREG,pfnDetach} + */ +/* static */ +DECLCALLBACK(void) AudioVRDE::drvDetach(PPDMDRVINS pDrvIns, uint32_t fFlags) +{ + RT_NOREF(pDrvIns, fFlags); + + LogFlowFuncEnter(); +} + + +/** + * 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 */ + AudioVRDE::drvAttach, + /* pfnDetach */ + AudioVRDE::drvDetach, + /* pfnPowerOff */ + NULL, + /* 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..2bce743a --- /dev/null +++ b/src/VBox/Main/src-client/EBMLWriter.cpp @@ -0,0 +1,265 @@ +/* $Id: EBMLWriter.cpp $ */ +/** @file + * EBMLWriter.cpp - EBML writer implementation. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** + * 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 rc = RTFileOpen(&m_hFile, a_pszFile, fOpen); + if (RT_SUCCESS(rc)) + m_strFile = a_pszFile; + + return rc; +} + +/** Returns available space on storage. */ +uint64_t EBMLWriter::getAvailableSpace(void) +{ + RTFOFF pcbFree; + int rc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0); + return (RT_SUCCESS(rc)? (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..a88cfc8e --- /dev/null +++ b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp @@ -0,0 +1,691 @@ +/* $Id: EmulatedUSBImpl.cpp $ */ +/** @file + * Emulated USB manager implementation. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_EMULATEDUSB +#include "LoggingNew.h" + +#include "EmulatedUSBImpl.h" +#include "ConsoleImpl.h" + +#include <VBox/vmm/pdmusb.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, EUSBWEBCAM *pThis, const char *pszDriver); + static DECLCALLBACK(int) emulatedWebcamDetach(PUVM pUVM, 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, + const char *pszDriver); + HRESULT Detach(Console *pConsole, + PUVM pUVM); + + bool HasId(const char *pszId) { return RTStrCmp(pszId, mszUuid) == 0;} + + EUSBDEVICESTATUS enmStatus; +}; + +static int emulatedWebcamInsertSettings(PCFGMNODE pConfig, EUSBSettingsMap *pSettings) +{ + int rc = VINF_SUCCESS; + + EUSBSettingsMap::const_iterator it; + for (it = pSettings->begin(); it != pSettings->end(); ++it) + { + /* Convert some well known settings for backward compatibility. */ + if ( RTStrCmp(it->first.c_str(), "MaxPayloadTransferSize") == 0 + || RTStrCmp(it->first.c_str(), "MaxFramerate") == 0) + { + uint32_t u32 = 0; + rc = RTStrToUInt32Full(it->second.c_str(), 10, &u32); + if (rc == VINF_SUCCESS) + { + rc = CFGMR3InsertInteger(pConfig, it->first.c_str(), u32); + } + else + { + if (RT_SUCCESS(rc)) /* VWRN_* */ + { + rc = VERR_INVALID_PARAMETER; + } + } + } + else + { + rc = CFGMR3InsertString(pConfig, it->first.c_str(), it->second.c_str()); + } + + if (RT_FAILURE(rc)) + { + break; + } + } + + return rc; +} + +/* static */ DECLCALLBACK(int) EUSBWEBCAM::emulatedWebcamAttach(PUVM pUVM, EUSBWEBCAM *pThis, const char *pszDriver) +{ + PCFGMNODE pInstance = CFGMR3CreateTree(pUVM); + PCFGMNODE pConfig; + CFGMR3InsertNode(pInstance, "Config", &pConfig); + int rc = emulatedWebcamInsertSettings(pConfig, &pThis->mDevSettings); + if (RT_FAILURE(rc)) + return rc; + PCFGMNODE pEUSB; + CFGMR3InsertNode(pConfig, "EmulatedUSB", &pEUSB); + CFGMR3InsertString(pEUSB, "Id", pThis->mszUuid); + CFGMR3InsertInteger(pEUSB, "pfnCallback", (uintptr_t)EmulatedUSB::i_eusbCallback); + CFGMR3InsertInteger(pEUSB, "pvCallback", (uintptr_t)pThis->mpEmulatedUSB); + + PCFGMNODE pLunL0; + CFGMR3InsertNode(pInstance, "LUN#0", &pLunL0); + CFGMR3InsertString(pLunL0, "Driver", pszDriver); + CFGMR3InsertNode(pLunL0, "Config", &pConfig); + CFGMR3InsertString(pConfig, "DevicePath", pThis->mPath.c_str()); + CFGMR3InsertInteger(pConfig, "Object", (uintptr_t)pThis->mpvObject); + rc = emulatedWebcamInsertSettings(pConfig, &pThis->mDrvSettings); + if (RT_FAILURE(rc)) + return rc; + + /* pInstance will be used by PDM and deallocated on error. */ + rc = PDMR3UsbCreateEmulatedDevice(pUVM, "Webcam", pInstance, &pThis->mUuid, NULL); + LogRelFlowFunc(("PDMR3UsbCreateEmulatedDevice %Rrc\n", rc)); + return rc; +} + +/* static */ DECLCALLBACK(int) EUSBWEBCAM::emulatedWebcamDetach(PUVM pUVM, EUSBWEBCAM *pThis) +{ + return PDMR3UsbDetachDevice(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); + if (RT_SUCCESS(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; + } + } + } + + if (SUCCEEDED(hrc) && RT_FAILURE(vrc)) + { + LogFlowThisFunc(("%Rrc\n", vrc)); + hrc = pConsole->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, "Init emulated USB webcam (%Rrc)", vrc); + } + + 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 = RTStrStr(pszSrc, "="); + if (!pszEq) + { + hr = E_INVALIDARG; + break; + } + + char *pszEnd = RTStrStr(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, + const char *pszDriver) +{ + HRESULT hrc = S_OK; + + int vrc = VMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)emulatedWebcamAttach, 3, + pUVM, this, pszDriver); + + if (SUCCEEDED(hrc) && RT_FAILURE(vrc)) + { + LogFlowThisFunc(("%Rrc\n", vrc)); + hrc = pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, "Attach emulated USB webcam (%Rrc)", vrc); + } + + return hrc; +} + +HRESULT EUSBWEBCAM::Detach(Console *pConsole, + PUVM pUVM) +{ + HRESULT hrc = S_OK; + + int vrc = VMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */, + (PFNRT)emulatedWebcamDetach, 2, + pUVM, this); + + if (SUCCEEDED(hrc) && RT_FAILURE(vrc)) + { + LogFlowThisFunc(("%Rrc\n", vrc)); + hrc = pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, "Detach emulated USB webcam (%Rrc)", vrc); + } + + return hrc; +} + + +/* + * 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; + + /* 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; +} + +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(), 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()); + 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 rc = VINF_SUCCESS; + if (iEvent == 0) + { + com::Utf8Str path; + HRESULT hr = pThis->webcamPathFromId(&path, pszId); + if (SUCCEEDED(hr)) + { + hr = pThis->webcamDetach(path); + if (FAILED(hr)) + { + rc = VERR_INVALID_STATE; + } + } + else + { + rc = VERR_NOT_FOUND; + } + } + else + { + rc = VERR_INVALID_PARAMETER; + } + + RTMemFree(pszId); + RTMemFree(pvData); + + LogRelFlowFunc(("rc %Rrc\n", rc)); + return rc; +} + +/* 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 rc = VINF_SUCCESS; + + void *pvIdCopy = NULL; + void *pvDataCopy = NULL; + if (cbData > 0) + { + pvDataCopy = RTMemDup(pvData, cbData); + if (!pvDataCopy) + { + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + pvIdCopy = RTMemDup(pszId, strlen(pszId) + 1); + if (!pvIdCopy) + { + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + EmulatedUSB *pThis = (EmulatedUSB *)pv; + Console::SafeVMPtr ptrVM(pThis->m.pConsole); + if (ptrVM.isOk()) + { + /* No wait. */ + rc = VMR3ReqCallNoWaitU(ptrVM.rawUVM(), 0 /* idDstCpu */, + (PFNRT)EmulatedUSB::eusbCallbackEMT, 5, + pThis, pvIdCopy, iEvent, pvDataCopy, cbData); + } + else + { + rc = VERR_INVALID_STATE; + } + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pvIdCopy); + RTMemFree(pvDataCopy); + } + + return rc; +} + +HRESULT EmulatedUSB::webcamPathFromId(com::Utf8Str *pPath, const char *pszId) +{ + HRESULT hr = 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()) + { + hr = E_FAIL; + } + alock.release(); + } + else + { + hr = VBOX_E_INVALID_VM_STATE; + } + + return hr; +} + +/* 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..cd47f61c --- /dev/null +++ b/src/VBox/Main/src-client/GuestCtrlImpl.cpp @@ -0,0 +1,588 @@ +/* $Id: GuestCtrlImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation: Guest + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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. + * + * @todo + * + * @todo r=bird: This code mostly returned VINF_SUCCESS with the comment + * "Never return any errors back to the guest here." attached to the + * return locations. However, there is no explaination for this attitude + * thowards error handling. Further, it creates a slight problem since + * the service would route all message calls it didn't recognize here, + * thereby making any undefined messages confusingly return VINF_SUCCESS. + * + * In my humble opinion, if the guest gives us incorrect input it should + * expect and deal with error statuses. If there is unimplemented + * features I expect there to have been sufficient forethought by the + * coder that these return sensible status codes. + * + * It would be much appreciated if the esteemed card house builder could + * please step in and explain this confusing state of affairs. + */ +/* 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); + + /* + * 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 rc = pGuest->i_dispatchToSession(&CtxCb, pSvcCb); + + Log2Func(("CID=%#x, idSession=%RU32, uObject=%RU32, uCount=%RU32, rc=%Rrc\n", + idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext), VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(idContext), + VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(idContext), rc)); + return rc; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +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 rc; + 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; + rc = 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: + rc = 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: + rc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + + /* File stuff. */ + case GUEST_MSG_FILE_NOTIFY: + rc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + + /* Session stuff. */ + case GUEST_MSG_SESSION_NOTIFY: + rc = pSession->i_dispatchToThis(pCtxCb, pSvcCb); + break; + + default: + rc = pSession->i_dispatchToObject(pCtxCb, pSvcCb); + break; + } + } + } + else + rc = VERR_INVALID_SESSION_ID; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int Guest::i_sessionRemove(uint32_t uSessionID) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int rc = VERR_NOT_FOUND; + + LogFlowThisFunc(("Removing 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)); + + rc = pSession->i_onRemove(); + mData.mGuestSessions.erase(itSessions); + + alock.release(); /* Release lock before firing off event. */ + + fireGuestSessionRegisteredEvent(mEventSource, pSession, false /* Unregistered */); + pSession.setNull(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int Guest::i_sessionCreate(const GuestSessionStartupInfo &ssInfo, + const GuestCredentials &guestCreds, ComObjPtr<GuestSession> &pGuestSession) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + int rc = VERR_MAX_PROCS_REACHED; + if (mData.mGuestSessions.size() >= VBOX_GUESTCTRL_MAX_SESSIONS) + return rc; + + 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)) + { + rc = 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(rc)) throw rc; + + /* Create the session object. */ + HRESULT hr = pGuestSession.createObject(); + if (FAILED(hr)) 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; + } + + rc = pGuestSession->init(this, startupInfo, guestCredentials); + if (RT_FAILURE(rc)) throw rc; + + /* + * 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 rc2) + { + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +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::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); + + int vrc = VINF_SUCCESS; + + ProcessArguments aArgs; + aArgs.resize(0); + + if (aArguments.size()) + { + try + { + for (size_t i = 0; i < aArguments.size(); ++i) + aArgs.push_back(aArguments[i]); + } + catch(std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + } + + HRESULT hr = S_OK; + + /* + * 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; + RT_ZERO(guestCreds); + + ComObjPtr<GuestSession> pSession; + if (RT_SUCCESS(vrc)) + vrc = i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_MAX_PROCS_REACHED: + hr = 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: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc); + break; + } + } + else + { + Assert(!pSession.isNull()); + int rcGuest; + vrc = pSession->i_startSession(&rcGuest); + if (RT_FAILURE(vrc)) + { + /** @todo Handle rcGuest! */ + + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc); + } + else + { + + ComObjPtr<Progress> pProgress; + GuestSessionTaskUpdateAdditions *pTask = NULL; + try + { + try + { + pTask = new GuestSessionTaskUpdateAdditions(pSession /* GuestSession */, aSource, aArgs, fFlags); + } + catch(...) + { + hr = setError(E_OUTOFMEMORY, tr("Failed to create SessionTaskUpdateAdditions object ")); + throw; + } + + + hr = pTask->Init(Utf8StrFmt(tr("Updating Guest Additions"))); + if (FAILED(hr)) + { + delete pTask; + hr = setError(hr, tr("Creating progress object for SessionTaskUpdateAdditions object failed")); + throw hr; + } + + hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + + if (SUCCEEDED(hr)) + { + /* Return progress to the caller. */ + pProgress = pTask->GetProgressObject(); + hr = pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else + hr = setError(hr, tr("Starting thread for updating Guest Additions on the guest failed ")); + } + catch(std::bad_alloc &) + { + hr = E_OUTOFMEMORY; + } + catch(...) + { + LogFlowThisFunc(("Exception was caught in the function\n")); + } + } + } + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#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..5cbfbb38 --- /dev/null +++ b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp @@ -0,0 +1,1587 @@ +/* $Id: GuestCtrlPrivate.cpp $ */ +/** @file + * Internal helpers/structures for guest control functionality. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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/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; +} + +int GuestFsObjData::FromStat(const GuestProcessStreamBlock &strmBlk) +{ + /* Should be identical output. */ + return GuestFsObjData::FromLs(strmBlk, true /*fLong*/); +} + +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(const GuestProcessStreamBlock &otherBlock) +{ + for (GuestCtrlStreamPairsIter it = otherBlock.mPairs.begin(); + it != otherBlock.end(); ++it) + { + mPairs[it->first] = new + if (it->second.pszValue) + { + RTMemFree(it->second.pszValue); + it->second.pszValue = NULL; + } + } +}*/ + +GuestProcessStreamBlock::~GuestProcessStreamBlock() +{ + Clear(); +} + +/** + * Destroys the currently stored stream pairs. + * + * @return IPRT status code. + */ +void GuestProcessStreamBlock::Clear(void) +{ + mPairs.clear(); +} + +#ifdef DEBUG +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 IPRT 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 IPRT status code. + */ +int GuestProcessStreamBlock::GetRc(void) const +{ + const char *pszValue = GetString("rc"); + if (pszValue) + { + return RTStrToInt16(pszValue); + } + 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(Utf8Str(pszKey)); /** @todo r=bird: this string conversion is excellent performance wise... */ + if (itPairs != mPairs.end()) + return itPairs->second.mValue.c_str(); + } + catch (const std::exception &ex) + { + NOREF(ex); + } + return NULL; +} + +/** + * Returns a 32-bit unsigned integer of a specified key. + * + * @return IPRT 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 IPRT 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) + { + NOREF(ex); + } + return rc; +} + +/////////////////////////////////////////////////////////////////////////////// + +GuestProcessStream::GuestProcessStream(void) + : 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 IPRT status code. + * @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) + { +/** @todo Put an upper limit on the allocation? */ + size_t cbAlloc = m_cbUsed + cbData; + 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; + } + + /* 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 +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 IPRT 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 *pszOff = (char*)&m_pbBuffer[m_offBuffer]; + char *pszStart = pszOff; + uint32_t uDistance; + while (*pszStart) + { + size_t pairLen = strlen(pszStart); + uDistance = (pszStart - pszOff); + if (m_offBuffer + uDistance + pairLen + 1 >= m_cbUsed) + { + rc = VERR_MORE_DATA; + break; + } + else + { + char *pszSep = strchr(pszStart, '='); + char *pszVal = NULL; + if (pszSep) + pszVal = pszSep + 1; + if (!pszSep || !pszVal) + { + rc = VERR_MORE_DATA; + break; + } + + /* Terminate the separator so that we can + * use pszStart as our key from now on. */ + *pszSep = '\0'; + + rc = streamBlock.SetValue(pszStart, pszVal); + if (RT_FAILURE(rc)) + return rc; + } + + /* Next pair. */ + pszStart += pairLen + 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. */ + uDistance = (pszStart - pszOff); + if ( !uDistance + && *pszStart == '\0' + && m_offBuffer < m_cbUsed) + { + uDistance++; + } + m_offBuffer += uDistance; + + return rc; +} + +GuestBase::GuestBase(void) + : mConsole(NULL) + , mNextContextID(RTRandU32() % VBOX_GUESTCTRL_MAX_CONTEXTS) +{ +} + +GuestBase::~GuestBase(void) +{ +} + +int GuestBase::baseInit(void) +{ + int rc = RTCritSectInit(&mWaitEventCritSect); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +void GuestBase::baseUninit(void) +{ + LogFlowThisFuncEnter(); + + int rc2 = RTCritSectDelete(&mWaitEventCritSect); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc2); + /* No return value. */ +} + +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); + + GuestWaitEventPayload evPayload(dataCb.uType, dataCb.pvPayload, dataCb.cbPayload); /* This bugger throws int. */ + vrc = signalWaitEventInternal(pCtxCb, dataCb.rc, &evPayload); + } + 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; +} + +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); + if (uCount >= VBOX_GUESTCTRL_MAX_CONTEXTS) + uCount = 0; + + 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 IPRT status code. + * @retval VERR_ALREADY_EXISTS if an event with the given session and object ID + * already has been registered. r=bird: Incorrect, see explanation in + * registerWaitEventEx(). + * + * @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_ALREADY_EXISTS if an event with the given session and object ID + * already has been registered. r=bird: No, this isn't when this is + * returned, it is returned when generateContextID() generates a + * duplicate. The duplicate being in the count part (bits 15:0) of the + * session ID. So, VERR_DUPLICATE would be more appropraite. + * + * @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); + +#if 1 /** @todo r=bird: Incorrect exception and memory handling, no strategy for dealing with duplicate IDs: */ + rc = RTCritSectEnter(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + { + try + { + GuestWaitEvent *pEvent = new GuestWaitEvent(idContext, lstEvents); + AssertPtr(pEvent); + + LogFlowThisFunc(("New event=%p, CID=%RU32\n", pEvent, idContext)); + + /* Insert event into matching event group. This is for faster per-group + * lookup of all events later. */ + for (GuestEventTypes::const_iterator itEvents = lstEvents.begin(); + itEvents != lstEvents.end(); ++itEvents) + { + /* Check if the event group already has an event with the same + * context ID in it (collision). */ + GuestWaitEvents eventGroup = mWaitEventGroups[(*itEvents)]; /** @todo r=bird: Why copy it? */ + if (eventGroup.find(idContext) == eventGroup.end()) + { + /* No, insert. */ + mWaitEventGroups[(*itEvents)].insert(std::pair<uint32_t, GuestWaitEvent *>(idContext, pEvent)); + } + else + { + rc = VERR_ALREADY_EXISTS; + break; + } + } + + if (RT_SUCCESS(rc)) + { + /* Register event in regular event list. */ + if (mWaitEvents.find(idContext) == mWaitEvents.end()) + { + mWaitEvents[idContext] = pEvent; + } + else + rc = VERR_ALREADY_EXISTS; + } + + if (RT_SUCCESS(rc)) + *ppEvent = pEvent; + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + int rc2 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + return rc; + +#else /** @todo r=bird: Version with proper exception handling, no leaks and limited duplicate CID handling: */ + + GuestWaitEvent *pEvent = new GuestWaitEvent(idContext, lstEvents); + AssertReturn(pEvent, VERR_NO_MEMORY); + 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); + Log(("Duplicate! Trying a different context ID: %#x\n", idContext)); + if (mWaitEvents.find(idContext) != mWaitEvents.end()) + rc = VERR_ALREADY_EXISTS; + } 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); + 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)) + return rc; + + delete pEvent; + return rc; +#endif +} + +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()) + { +#if 1 /** @todo r=bird: consider the other variant. */ + GuestWaitEvents::iterator itEvents = itGroup->second.begin(); + while (itEvents != itGroup->second.end()) + { +#ifdef DEBUG + LogFlowThisFunc(("Signalling event=%p, type=%ld (CID %RU32: Session=%RU32, Object=%RU32, Count=%RU32) ...\n", + itEvents->second, aType, itEvents->first, + VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(itEvents->first), + VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(itEvents->first), + VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(itEvents->first))); +#endif + ComPtr<IEvent> pThisEvent = aEvent; /** @todo r=bird: This means addref/release for each iteration. Isn't that silly? */ + Assert(!pThisEvent.isNull()); + int rc2 = itEvents->second->SignalExternal(aEvent); + if (RT_SUCCESS(rc)) + rc = rc2; /** @todo r=bird: This doesn't make much sense since it will only fail if not + * properly initialized or major memory corruption. And if it's broken, why + * don't you just remove it instead of leaving it in the group??? It would + * make life so much easier here as you could just change the while condition + * to while ((itEvents = itGroup->second.begin() != itGroup->second.end()) + * and skip all this two step removal below. I'll put this in a #if 0 and show what I mean... */ + + if (RT_SUCCESS(rc2)) + { + /** @todo r=bird: I don't follow the logic here. Why don't you just remove + * it from all groups, including this one? You just have move the */ + + /* Remove the event from all other event groups (except the + * original one!) because it was signalled. */ + AssertPtr(itEvents->second); + const GuestEventTypes evTypes = itEvents->second->Types(); + for (GuestEventTypes::const_iterator itType = evTypes.begin(); + itType != evTypes.end(); ++itType) + { + if ((*itType) != aType) /* Only remove all other groups. */ + { + /* Get current event group. */ + GuestEventGroup::iterator evGroup = mWaitEventGroups.find((*itType)); + Assert(evGroup != mWaitEventGroups.end()); + + /* Lookup event in event group. */ + GuestWaitEvents::iterator evEvent = evGroup->second.find(itEvents->first /* Context ID */); + Assert(evEvent != evGroup->second.end()); + + LogFlowThisFunc(("Removing event=%p (type %ld)\n", evEvent->second, (*itType))); + evGroup->second.erase(evEvent); + + LogFlowThisFunc(("%zu events for type=%ld left\n", + evGroup->second.size(), aType)); + } + } + + /* Remove the event from the passed-in event group. */ + GuestWaitEvents::iterator itEventsNext = itEvents; + ++itEventsNext; + itGroup->second.erase(itEvents); + itEvents = itEventsNext; + } + else + ++itEvents; +#ifdef DEBUG + cEvents++; +#endif + } +#else + /* 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 */ + } + } + } + } +#endif + } + + int rc2 = RTCritSectLeave(&mWaitEventCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + LogFlowThisFunc(("Signalled %RU32 events, rc=%Rrc\n", cEvents, rc)); +#endif + return rc; +} + +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); +} + +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 IPRT 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) + { + 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 IPRT status code. + * @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()) /* Having a VBoxEventType_ event is optional. */ /** @todo r=bird: misplaced comment? */ + { + 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; +} + +/** + * 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; +} + +GuestObject::GuestObject(void) + : mSession(NULL), + mObjectID(0) +{ +} + +GuestObject::~GuestObject(void) +{ +} + +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; +} + +int GuestObject::registerWaitEvent(const GuestEventTypes &lstEvents, + GuestWaitEvent **ppEvent) +{ + AssertPtr(mSession); + return GuestBase::registerWaitEventEx(mSession->i_getId(), mObjectID, lstEvents, ppEvent); +} + +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; + } +} + +int GuestWaitEventBase::Init(uint32_t uCID) +{ + mCID = uCID; + + return RTSemEventCreate(&mEventSem); +} + +int GuestWaitEventBase::SignalInternal(int rc, int rcGuest, + const GuestWaitEventPayload *pPayload) +{ + if (ASMAtomicReadBool(&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; +} + +int GuestWaitEventBase::Wait(RTMSINTERVAL msTimeout) +{ + int rc = VINF_SUCCESS; + + if (ASMAtomicReadBool(&mfAborted)) + rc = VERR_CANCELLED; + + if (RT_SUCCESS(rc)) + { + AssertReturn(mEventSem != NIL_RTSEMEVENT, VERR_CANCELLED); + + rc = RTSemEventWait(mEventSem, msTimeout ? msTimeout : RT_INDEFINITE_WAIT); + if (ASMAtomicReadBool(&mfAborted)) + rc = VERR_CANCELLED; + if (RT_SUCCESS(rc)) + { + /* If waiting succeeded, return the overall + * result code. */ + rc = mRc; + } + } + + return rc; +} + +GuestWaitEvent::GuestWaitEvent(uint32_t uCID, + const GuestEventTypes &lstEvents) +{ + int rc2 = Init(uCID); + AssertRC(rc2); /** @todo Throw exception here. */ /** @todo r=bird: add+use Init() instead. Will cause weird VERR_CANCELLED errors in GuestBase::signalWaitEvent. */ + + mEventTypes = lstEvents; +} + +GuestWaitEvent::GuestWaitEvent(uint32_t uCID) +{ + int rc2 = Init(uCID); + AssertRC(rc2); /** @todo Throw exception here. */ /** @todo r=bird: add+use Init() instead. Will cause weird VERR_CANCELLED errors in GuestBase::signalWaitEvent. */ +} + +GuestWaitEvent::~GuestWaitEvent(void) +{ + +} + +/** + * Cancels the event. + */ +int GuestWaitEvent::Cancel(void) +{ + AssertReturn(!mfAborted, VERR_CANCELLED); + ASMAtomicWriteBool(&mfAborted, true); + +#ifdef DEBUG_andy + LogFlowThisFunc(("Cancelling %p ...\n")); +#endif + return RTSemEventSignal(mEventSem); +} + +int GuestWaitEvent::Init(uint32_t uCID) +{ + return GuestWaitEventBase::Init(uCID); +} + +/** + * Signals the event. + * + * @return IPRT status code. + * @param pEvent Public IEvent to associate. + * Optional. + */ +int GuestWaitEvent::SignalExternal(IEvent *pEvent) +{ + /** @todo r=bird: VERR_CANCELLED is misleading. mEventSem can only be NIL if + * not successfully initialized! */ + AssertReturn(mEventSem != NIL_RTSEMEVENT, VERR_CANCELLED); + + if (pEvent) + mEvent = pEvent; + + return RTSemEventSignal(mEventSem); +} + diff --git a/src/VBox/Main/src-client/GuestDirectoryImpl.cpp b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp new file mode 100644 index 00000000..fad85191 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp @@ -0,0 +1,417 @@ +/* $Id: GuestDirectoryImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest directory handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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 = Utf8StrFmt(tr("Reading 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 asynchronously and keep it around so that we can use + * it later in subsequent read() calls. + * Note: No guest rc available because operation is asynchronous. + */ + vrc = mData.mProcessTool.init(mSession, procInfo, + true /* Async */, NULL /* Guest rc */); + } + + if (RT_SUCCESS(vrc)) + { + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + return vrc; + } + else + autoInitSpan.setFailed(); + + 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 +///////////////////////////////////////////////////////////////////////////// + +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; +} + +/* static */ +Utf8Str GuestDirectory::i_guestErrorToString(int rcGuest) +{ + Utf8Str strError; + + /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ + switch (rcGuest) + { + case VERR_CANT_CREATE: + strError += Utf8StrFmt("Access denied"); + break; + + case VERR_DIR_NOT_EMPTY: + strError += Utf8StrFmt("Not empty"); + break; + + default: + strError += Utf8StrFmt("%Rrc", rcGuest); + break; + } + + return strError; +} + +/** + * Called by IGuestSession right before this directory gets + * removed from the public directory list. + */ +int GuestDirectory::i_onRemove(void) +{ + 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 rc = mData.mProcessTool.terminate(30 * 1000 /* 30s timeout */, prcGuest); + if (RT_FAILURE(rc)) + return rc; + + AssertPtr(mSession); + int rc2 = mSession->i_directoryUnregister(this); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowThisFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +/** + * 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_readInternal(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; + + GuestProcessStreamBlock curBlock; + int rc = mData.mProcessTool.waitEx(GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK, &curBlock, prcGuest); + if (RT_SUCCESS(rc)) + { + /* + * Note: The guest process can still be around to serve the next + * upcoming stream block next time. + */ + if (!mData.mProcessTool.isRunning()) + rc = mData.mProcessTool.getTerminationStatus(); /* Tool process is not running (anymore). Check termination status. */ + + if (RT_SUCCESS(rc)) + { + if (curBlock.GetCount()) /* Did we get content? */ + { + GuestFsObjData objData; + rc = objData.FromLs(curBlock, true /* fLong */); + if (RT_SUCCESS(rc)) + { + rc = fsObjInfo->init(objData); + } + else + rc = VERR_PATH_NOT_FOUND; + } + else + { + /* Nothing to read anymore. Tell the caller. */ + rc = VERR_NO_MORE_FILES; + } + } + } + + LogFlowThisFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +/* static */ +HRESULT GuestDirectory::i_setErrorExternal(VirtualBoxBase *pInterface, int rcGuest) +{ + AssertPtr(pInterface); + AssertMsg(RT_FAILURE(rcGuest), ("Guest rc does not indicate a failure when setting error\n")); + + return pInterface->setError(VBOX_E_IPRT_ERROR, GuestDirectory::i_guestErrorToString(rcGuest).c_str()); +} + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// +HRESULT GuestDirectory::close() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + int rcGuest; + int vrc = i_closeInternal(&rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestDirectory::i_setErrorExternal(this, rcGuest); + break; + + case VERR_NOT_SUPPORTED: + /* Silently skip old Guest Additions which do not support killing the + * the guest directory handling process. */ + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Terminating open guest directory \"%s\" failed: %Rrc"), mData.mOpenInfo.mPath.c_str(), vrc); + break; + } + } + + return hr; +} + +HRESULT GuestDirectory::read(ComPtr<IFsObjInfo> &aObjInfo) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + ComObjPtr<GuestFsObjInfo> fsObjInfo; int rcGuest; + int vrc = i_readInternal(fsObjInfo, &rcGuest); + if (RT_SUCCESS(vrc)) + { + /* Return info object to the caller. */ + hr = fsObjInfo.queryInterfaceTo(aObjInfo.asOutParam()); + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestDirectory::i_setErrorExternal(this, rcGuest); + break; + + case VERR_GSTCTL_PROCESS_EXIT_CODE: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading directory \"%s\" failed: %Rrc"), + mData.mOpenInfo.mPath.c_str(), mData.mProcessTool.getRc()); + break; + + case VERR_PATH_NOT_FOUND: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading directory \"%s\" failed: Path not found"), + mData.mOpenInfo.mPath.c_str()); + break; + + case VERR_NO_MORE_FILES: + /* See SDK reference. */ + hr = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("Reading directory \"%s\" failed: No more entries"), + mData.mOpenInfo.mPath.c_str()); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading directory \"%s\" returned error: %Rrc\n"), + mData.mOpenInfo.mPath.c_str(), vrc); + break; + } + } + + LogFlowThisFunc(("Returning hr=%Rhrc / vrc=%Rrc\n", hr, vrc)); + return hr; +} + diff --git a/src/VBox/Main/src-client/GuestDnDPrivate.cpp b/src/VBox/Main/src-client/GuestDnDPrivate.cpp new file mode 100644 index 00000000..c505f62d --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDPrivate.cpp @@ -0,0 +1,1004 @@ +/* $Id: GuestDnDPrivate.cpp $ */ +/** @file + * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 interface (see UIDnDHandler.cpp). + * 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. Currently only X11 is supported (see draganddrop.cpp within + * VBoxClient). + * + * Host -> Guest: + * 1. There are DnD Enter, Move, Leave events which are send exactly like this + * to the guest. The info includes the pos, mimetypes and allowed actions. + * The guest has to respond with an action it would accept, so the GUI could + * change the cursor. + * 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. + * + * Some 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. + * + * Of course only regularly files are supported. Symlinks are resolved and + * transfered as regularly files. First we don't know if the other side support + * symlinks at all and second they could point to somewhere in a directory tree + * which not exists on the other side. + * + * The code tries to preserve the file modes of the transfered dirs/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). + * + * Cancel is supported in both directions and cleans up all previous steps + * (thats is: deleting already transfered dirs/files). + * + * In general I propose the following changes in the VBox HGCM infrastructure + * for the future: + * - Currently it isn't really possible to send messages to the guest from the + * host. The host informs the guest just that there is something, the guest + * than has to ask which message and depending on that send the appropriate + * message to the host, which is filled with the right data. + * - There is no generic interface for sending bigger memory blocks to/from the + * guest. This is now done here, but I guess was also necessary for e.g. + * guest execution. So something generic which brake this up into smaller + * blocks and send it would be nice (with all the error handling and such + * ofc). + * - I developed a "protocol" for the DnD communication here. So the host and + * the guest have always to match in the revision. This is ofc bad, because + * the additions could be outdated easily. So some generic protocol number + * support in HGCM for asking the host and the guest of the support version, + * would be nice. Ofc at least the host should be able to talk to the guest, + * even when the version is below the host one. + * All this stuff would be useful for the current services, but also for future + * onces. + * + ** @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. Maybe also a host specific implementation becomes necessary ... + * this would be really worst ofc. + * - 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). + */ + +GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void) +{ + if (NIL_RTSEMEVENT != mSemEvent) + RTSemEventDestroy(mSemEvent); +} + +int GuestDnDCallbackEvent::Reset(void) +{ + int rc = VINF_SUCCESS; + + if (NIL_RTSEMEVENT == mSemEvent) + rc = RTSemEventCreate(&mSemEvent); + + mRc = VINF_SUCCESS; + return rc; +} + +int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */) +{ + mRc = rc; + return RTSemEventSignal(mSemEvent); +} + +int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout) +{ + return RTSemEventWait(mSemEvent, msTimeout); +} + +/////////////////////////////////////////////////////////////////////////////// + +GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest) + : m_EventSem(NIL_RTSEMEVENT) + , m_dndActionDefault(0) + , m_dndLstActionsAllowed(0) + , m_pParent(pGuest) +{ + int rc = RTSemEventCreate(&m_EventSem); + if (RT_FAILURE(rc)) + throw rc; +} + +GuestDnDResponse::~GuestDnDResponse(void) +{ + reset(); + + int rc = RTSemEventDestroy(m_EventSem); + AssertRC(rc); +} + +int GuestDnDResponse::notifyAboutGuestResponse(void) const +{ + return RTSemEventSignal(m_EventSem); +} + +void GuestDnDResponse::reset(void) +{ + LogFlowThisFuncEnter(); + + m_dndActionDefault = 0; + m_dndLstActionsAllowed = 0; + + m_lstFormats.clear(); +} + +HRESULT GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent) +{ + m_pProgress.setNull(); + + HRESULT hr = m_pProgress.createObject(); + if (SUCCEEDED(hr)) + { + hr = m_pProgress->init(static_cast<IGuest *>(pParent), + Bstr(pParent->tr("Dropping data")).raw(), + TRUE /* aCancelable */); + } + + return hr; +} + +bool GuestDnDResponse::isProgressCanceled(void) const +{ + BOOL fCanceled; + if (!m_pProgress.isNull()) + { + HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled); + AssertComRC(hr); + } + else + fCanceled = TRUE; + + return RT_BOOL(fCanceled); +} + +int GuestDnDResponse::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */) +{ + GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg); + + /* Add. */ + if (pfnCallback) + { + if (it == m_mapCallbacks.end()) + { + m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser); + return VINF_SUCCESS; + } + + AssertMsgFailed(("Callback for message %RU32 already registered\n", uMsg)); + return VERR_ALREADY_EXISTS; + } + + /* Remove. */ + if (it != m_mapCallbacks.end()) + m_mapCallbacks.erase(it); + + return VINF_SUCCESS; +} + +int GuestDnDResponse::setProgress(unsigned uPercentage, + uint32_t uStatus, + int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */) +{ + RT_NOREF(rcOp); + LogFlowFunc(("uStatus=%RU32, uPercentage=%RU32, rcOp=%Rrc, strMsg=%s\n", + uStatus, uPercentage, rcOp, strMsg.c_str())); + + int rc = VINF_SUCCESS; + if (!m_pProgress.isNull()) + { + BOOL fCompleted; + HRESULT hr = m_pProgress->COMGETTER(Completed)(&fCompleted); + AssertComRC(hr); + + BOOL fCanceled; + hr = m_pProgress->COMGETTER(Canceled)(&fCanceled); + AssertComRC(hr); + + LogFlowFunc(("Current: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled)); + + if (!fCompleted) + { + switch (uStatus) + { + case DragAndDropSvc::DND_PROGRESS_ERROR: + { + hr = m_pProgress->i_notifyComplete(VBOX_E_IPRT_ERROR, + COM_IIDOF(IGuest), + m_pParent->getComponentName(), strMsg.c_str()); + reset(); + break; + } + + case DragAndDropSvc::DND_PROGRESS_CANCELLED: + { + hr = m_pProgress->Cancel(); + AssertComRC(hr); + hr = m_pProgress->i_notifyComplete(S_OK); + AssertComRC(hr); + + reset(); + break; + } + + case DragAndDropSvc::DND_PROGRESS_RUNNING: + case DragAndDropSvc::DND_PROGRESS_COMPLETE: + { + if (!fCanceled) + { + hr = m_pProgress->SetCurrentOperationProgress(uPercentage); + AssertComRC(hr); + if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE + || uPercentage >= 100) + { + hr = m_pProgress->i_notifyComplete(S_OK); + AssertComRC(hr); + } + } + break; + } + + default: + break; + } + } + + hr = m_pProgress->COMGETTER(Completed)(&fCompleted); + AssertComRC(hr); + hr = m_pProgress->COMGETTER(Canceled)(&fCanceled); + AssertComRC(hr); + + LogFlowFunc(("New: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDResponse::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. */ + bool fTryCallbacks = false; + + switch (u32Function) + { + case DragAndDropSvc::GUEST_DND_CONNECT: + { + LogThisFunc(("Client connected\n")); + + /* Nothing to do here (yet). */ + rc = VINF_SUCCESS; + break; + } + + case DragAndDropSvc::GUEST_DND_DISCONNECT: + { + LogThisFunc(("Client disconnected\n")); + rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS); + break; + } + + case DragAndDropSvc::GUEST_DND_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); + + setActionDefault(pCBData->uAction); + rc = notifyAboutGuestResponse(); + break; + } + + case DragAndDropSvc::GUEST_DND_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_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(); + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case DragAndDropSvc::GUEST_DND_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); + + 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 + { + LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size())); + rc = VERR_NOT_SUPPORTED; /* Tell the guest. */ + } + } + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress) +{ + return m_pProgress.queryInterfaceTo(ppProgress); +} + +int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const +{ + int rc = RTSemEventWait(m_EventSem, msTimeout); +#ifdef DEBUG_andy + LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc)); +#endif + return rc; +} + +/////////////////////////////////////////////////////////////////////////////// + +GuestDnD* GuestDnD::s_pInstance = NULL; + +GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest) + : m_pGuest(pGuest) +{ + LogFlowFuncEnter(); + + m_pResponse = new GuestDnDResponse(pGuest); + + /* 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(); + + if (m_pResponse) + delete m_pResponse; +} + +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 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; +} + +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); +} + +/* 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. */ + GuestDnDResponse *pResp = pGuestDnD->m_pResponse; + if (pResp) + return pResp->onDispatch(u32Function, pvParms, cbParms); + + return VERR_NOT_SUPPORTED; +} + +/* static */ +bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats) +{ + return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end(); +} + +/* static */ +GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats) +{ + GuestDnDMIMEList lstFormats; + RTCList<RTCString> lstFormatsTmp = strFormats.split("\r\n"); + + for (size_t i = 0; i < lstFormatsTmp.size(); i++) + lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i))); + + return lstFormats; +} + +/* static */ +com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats) +{ + com::Utf8Str strFormat; + for (size_t i = 0; i < lstFormats.size(); i++) + { + const com::Utf8Str &f = lstFormats.at(i); + strFormat += f + "\r\n"; + } + + return strFormat; +} + +/* 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 */ +GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted) +{ + GuestDnDMIMEList lstFmt; + + RTCList<RTCString> lstFormats = strFormatsWanted.split("\r\n"); + 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 */ +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 */ +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 */ +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 */ +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::GuestDnDBase(void) +{ + /* Initialize public attributes. */ + m_lstFmtSupported = GuestDnDInst()->defaultFormats(); + + /* Initialzie private stuff. */ + mDataBase.m_cTransfersPending = 0; + mDataBase.m_uProtocolVersion = 0; +} + +HRESULT GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported) +{ + *aSupported = std::find(m_lstFmtSupported.begin(), + m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end() + ? TRUE : FALSE; + return S_OK; +} + +HRESULT GuestDnDBase::i_getFormats(GuestDnDMIMEList &aFormats) +{ + aFormats = m_lstFmtSupported; + + return S_OK; +} + +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; +} + +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; +} + +HRESULT GuestDnDBase::i_getProtocolVersion(ULONG *puVersion) +{ + int rc = getProtocolVersion((uint32_t *)puVersion); + return RT_SUCCESS(rc) ? S_OK : E_FAIL; +} + +/** + * Tries to guess the DnD protocol version to use on the guest, based on the + * installed Guest Additions version + revision. + * + * If unable to retrieve the protocol version, VERR_NOT_FOUND is returned along + * with protocol version 1. + * + * @return IPRT status code. + * @param puProto Where to store the protocol version. + */ +int GuestDnDBase::getProtocolVersion(uint32_t *puProto) +{ + AssertPtrReturn(puProto, VERR_INVALID_POINTER); + + int rc; + + uint32_t uProto = 0; + uint32_t uVerAdditions; + uint32_t uRevAdditions; + if ( m_pGuest + && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0 + && (uRevAdditions = m_pGuest->i_getAdditionsRevision()) > 0) + { +#if 0 && defined(DEBUG) + /* Hardcode the to-used protocol version; nice for testing side effects. */ + if (true) + uProto = 3; + else +#endif + if (uVerAdditions >= VBOX_FULL_VERSION_MAKE(5, 0, 0)) + { +/** @todo + * r=bird: This is just too bad for anyone using an OSE additions build... + */ + if (uRevAdditions >= 103344) /* Since r103344: Protocol v3. */ + uProto = 3; + else + uProto = 2; /* VBox 5.0.0 - 5.0.8: Protocol v2. */ + } + /* else: uProto: 0 */ + + LogFlowFunc(("uVerAdditions=%RU32 (%RU32.%RU32.%RU32), r%RU32\n", + uVerAdditions, VBOX_FULL_VERSION_GET_MAJOR(uVerAdditions), VBOX_FULL_VERSION_GET_MINOR(uVerAdditions), + VBOX_FULL_VERSION_GET_BUILD(uVerAdditions), uRevAdditions)); + rc = VINF_SUCCESS; + } + else + { + uProto = 1; /* Fallback. */ + rc = VERR_NOT_FOUND; + } + + LogRel2(("DnD: Guest is using protocol v%RU32, rc=%Rrc\n", uProto, rc)); + + *puProto = uProto; + return rc; +} + +int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg) +{ + mDataBase.m_lstMsgOut.push_back(pMsg); + return VINF_SUCCESS; +} + +GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void) +{ + if (mDataBase.m_lstMsgOut.empty()) + return NULL; + return mDataBase.m_lstMsgOut.front(); +} + +void GuestDnDBase::msgQueueRemoveNext(void) +{ + if (!mDataBase.m_lstMsgOut.empty()) + { + GuestDnDMsg *pMsg = mDataBase.m_lstMsgOut.front(); + if (pMsg) + delete pMsg; + mDataBase.m_lstMsgOut.pop_front(); + } +} + +void GuestDnDBase::msgQueueClear(void) +{ + LogFlowFunc(("cMsg=%zu\n", mDataBase.m_lstMsgOut.size())); + + GuestDnDMsgList::iterator itMsg = mDataBase.m_lstMsgOut.begin(); + while (itMsg != mDataBase.m_lstMsgOut.end()) + { + GuestDnDMsg *pMsg = *itMsg; + if (pMsg) + delete pMsg; + + itMsg++; + } + + mDataBase.m_lstMsgOut.clear(); +} + +int GuestDnDBase::sendCancel(void) +{ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_CANCEL); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + + LogRel2(("DnD: Cancelling operation on guest ...")); + + return GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); +} + +int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp, + uint32_t cbDataAdd /* = 0 */) +{ + AssertPtrReturn(pData, VERR_INVALID_POINTER); + AssertPtrReturn(pResp, VERR_INVALID_POINTER); + /* cbDataAdd is optional. */ + + LogFlowFunc(("cbTotal=%RU64, cbProcessed=%RU64, cbRemaining=%RU64, cbDataAdd=%RU32\n", + pData->getTotal(), pData->getProcessed(), pData->getRemaining(), cbDataAdd)); + + if (!pResp) + return VINF_SUCCESS; + + if (cbDataAdd) + pData->addProcessed(cbDataAdd); + + int rc = pResp->setProgress(pData->getPercentComplete(), + pData->isComplete() + ? DND_PROGRESS_COMPLETE + : DND_PROGRESS_RUNNING); + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** @todo GuestDnDResponse *pResp needs to go. */ +int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + AssertPtrReturn(pResp, 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 (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */ + { + 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..53013923 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp @@ -0,0 +1,1622 @@ +/* $Id: GuestDnDSourceImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation - Guest drag and drop source. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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) { } + + int getRC(void) const { return mRC; } + bool isOk(void) const { return RT_SUCCESS(mRC); } + const ComObjPtr<GuestDnDSource> &getSource(void) const { return mSource; } + +protected: + + const ComObjPtr<GuestDnDSource> mSource; + int mRC; +}; + +/** + * Task structure for receiving data from a source using + * a worker thread. + */ +class RecvDataTask : public GuestDnDSourceTask +{ +public: + + RecvDataTask(GuestDnDSource *pSource, PRECVDATACTX pCtx) + : GuestDnDSourceTask(pSource) + , mpCtx(pCtx) + { + m_strTaskName = "dndSrcRcvData"; + } + + void handler() + { + GuestDnDSource::i_receiveDataThreadTask(this); + } + + virtual ~RecvDataTask(void) { } + + PRECVDATACTX getCtx(void) { return mpCtx; } + +protected: + + /** Pointer to receive data context. */ + PRECVDATACTX mpCtx; +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestDnDSource) + +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 = _64K; /** @todo Make this configurable. */ + + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void GuestDnDSource::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +int 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; + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return VINF_SUCCESS; +} + +/** + * 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); + + return GuestDnDBase::i_isFormatSupported(aFormat, aSupported); +#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); + + return GuestDnDBase::i_getFormats(aFormats); +#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 */ +} + +HRESULT GuestDnDSource::getProtocolVersion(ULONG *aProtocolVersion) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + 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_getProtocolVersion(aProtocolVersion); +#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(); + + /* Determine guest DnD protocol to use. */ + GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion); + + /* Default is ignoring the action. */ + if (aDefaultAction) + *aDefaultAction = DnDAction_Ignore; + + HRESULT hr = S_OK; + + GuestDnDMsg Msg; + Msg.setType(HOST_DND_GH_REQ_PENDING); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextUInt32(uScreenId); + + int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + GuestDnDResponse *pResp = GuestDnDInst()->response(); + AssertPtr(pResp); + + bool fFetchResult = true; + + rc = pResp->waitForGuestResponse(100 /* Timeout in ms */); + if (RT_FAILURE(rc)) + fFetchResult = false; + + if ( fFetchResult + && isDnDIgnoreAction(pResp->getActionDefault())) + fFetchResult = false; + + /* Fetch the default action to use. */ + if (fFetchResult) + { + /* + * 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 lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, pResp->formats()); + if (lstFiltered.size()) + { + LogRel3(("DnD: Host offered the following formats:\n")); + for (size_t i = 0; i < lstFiltered.size(); i++) + LogRel3(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str())); + + aFormats = lstFiltered; + aAllowedActions = GuestDnD::toMainActions(pResp->getActionsAllowed()); + if (aDefaultAction) + *aDefaultAction = GuestDnD::toMainAction(pResp->getActionDefault()); + + /* Apply the (filtered) formats list. */ + m_lstFmtOffered = lstFiltered; + } + else + LogRel2(("DnD: Negotiation of formats between guest and host failed, drag and drop to host not possible\n")); + } + + LogFlowFunc(("fFetchResult=%RTbool, lstActionsAllowed=0x%x\n", fFetchResult, pResp->getActionsAllowed())); + } + + LogFlowFunc(("hr=%Rhrc\n", hr)); + return hr; +#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()); + + 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); + + /* At the moment we only support one transfer at a time. */ + if (mDataBase.m_cTransfersPending) + return setError(E_INVALIDARG, tr("Another drop operation already is in progress")); + + /* Dito. */ + GuestDnDResponse *pResp = GuestDnDInst()->response(); + AssertPtr(pResp); + + HRESULT hr = pResp->resetProgress(m_pGuest); + if (FAILED(hr)) + return hr; + + RecvDataTask *pTask = NULL; + + try + { + mData.mRecvCtx.mIsActive = false; + mData.mRecvCtx.mpSource = this; + mData.mRecvCtx.mpResp = pResp; + mData.mRecvCtx.mFmtReq = aFormat; + mData.mRecvCtx.mFmtOffered = m_lstFmtOffered; + + LogRel2(("DnD: Requesting data from guest in format: %s\n", aFormat.c_str())); + + pTask = new RecvDataTask(this, &mData.mRecvCtx); + if (!pTask->isOk()) + { + delete pTask; + LogRel2(("DnD: Could not create RecvDataTask 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); + + } + catch (std::bad_alloc &) + { + hr = setError(E_OUTOFMEMORY); + } + catch (...) + { + LogRel2(("DnD: Could not create thread for data receiving task\n")); + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) + { + mDataBase.m_cTransfersPending++; + + hr = pResp->queryProgressTo(aProgress.asOutParam()); + ComAssertComRC(hr); + + /* Note: pTask is now owned by the worker thread. */ + } + else + hr = setError(hr, tr("Starting thread for GuestDnDSource::i_receiveDataThread failed (%Rhrc)"), hr); + /* Note: mDataBase.mfTransferIsPending will be set to false again by i_receiveDataThread. */ + + 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(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("cTransfersPending=%RU32\n", mDataBase.m_cTransfersPending)); + + /* Don't allow receiving the actual data until our transfer actually is complete. */ + if (mDataBase.m_cTransfersPending) + return setError(E_FAIL, tr("Current drop operation still in progress")); + + PRECVDATACTX pCtx = &mData.mRecvCtx; + HRESULT hr = S_OK; + + try + { + bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length()); + if (fHasURIList) + { + LogRel2(("DnD: Drop directory is: %s\n", pCtx->mURI.getDroppedFiles().GetDirAbs())); + int rc2 = pCtx->mURI.toMetaData(aData); + if (RT_FAILURE(rc2)) + hr = E_OUTOFMEMORY; + } + else + { + const size_t cbData = pCtx->mData.getMeta().getSize(); + LogFlowFunc(("cbData=%zu\n", cbData)); + if (cbData) + { + /* Copy the data into a safe array of bytes. */ + aData.resize(cbData); + memcpy(&aData.front(), pCtx->mData.getMeta().getData(), 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. +///////////////////////////////////////////////////////////////////////////// + +/* 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; +} + +/* 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; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +int GuestDnDSource::i_onReceiveDataHdr(PRECVDATACTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pDataHdr, VERR_INVALID_POINTER); + + pCtx->mData.setEstimatedSize(pDataHdr->cbTotal, pDataHdr->cbMeta); + + Assert(pCtx->mURI.getObjToProcess() == 0); + pCtx->mURI.reset(); + pCtx->mURI.setEstimatedObjects(pDataHdr->cObjects); + + /** @todo Handle compression type. */ + /** @todo Handle checksum type. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +int GuestDnDSource::i_onReceiveData(PRECVDATACTX pCtx, PVBOXDNDSNDDATA pSndData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSndData, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + try + { + GuestDnDData *pData = &pCtx->mData; + GuestDnDURIData *pURI = &pCtx->mURI; + + uint32_t cbData; + void *pvData; + uint64_t cbTotal; + uint32_t cbMeta; + + if (mDataBase.m_uProtocolVersion < 3) + { + cbData = pSndData->u.v1.cbData; + pvData = pSndData->u.v1.pvData; + + /* Sends the total data size to receive for every data chunk. */ + cbTotal = pSndData->u.v1.cbTotalSize; + + /* Meta data size always is cbData, meaning there cannot be an + * extended data chunk transfer by sending further data. */ + cbMeta = cbData; + } + else + { + cbData = pSndData->u.v3.cbData; + pvData = pSndData->u.v3.pvData; + + /* Note: Data sizes get updated in i_onReceiveDataHdr(). */ + cbTotal = pData->getTotal(); + cbMeta = pData->getMeta().getSize(); + } + Assert(cbTotal); + + if ( cbData == 0 + || cbData > cbTotal /* Paranoia */) + { + LogFlowFunc(("Incoming data size invalid: cbData=%RU32, cbToProcess=%RU64\n", cbData, pData->getTotal())); + rc = VERR_INVALID_PARAMETER; + } + else if (cbTotal < cbMeta) + { + AssertMsgFailed(("cbTotal (%RU64) is smaller than cbMeta (%RU32)\n", cbTotal, cbMeta)); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + cbMeta = pData->getMeta().add(pvData, cbData); + LogFlowThisFunc(("cbMetaSize=%zu, cbData=%RU32, cbMeta=%RU32, cbTotal=%RU64\n", + pData->getMeta().getSize(), cbData, cbMeta, cbTotal)); + } + + if (RT_SUCCESS(rc)) + { + /* + * (Meta) Data transfer complete? + */ + Assert(cbMeta <= pData->getMeta().getSize()); + if (cbMeta == pData->getMeta().getSize()) + { + bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length()); + LogFlowThisFunc(("fHasURIList=%RTbool\n", fHasURIList)); + if (fHasURIList) + { + /* Try parsing the data as URI list. */ + rc = pURI->fromRemoteMetaData(pData->getMeta()); + if (RT_SUCCESS(rc)) + { + if (mDataBase.m_uProtocolVersion < 3) + pData->setEstimatedSize(cbTotal, cbMeta); + + /* + * Update our process with the data we already received. + * Note: The total size will consist of the meta data (in pVecData) and + * the actual accumulated file/directory data from the guest. + */ + rc = updateProgress(pData, pCtx->mpResp, (uint32_t)pData->getMeta().getSize()); + } + } + else /* Raw data. */ + rc = updateProgress(pData, pCtx->mpResp, cbData); + } + } + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDSource::i_onReceiveDir(PRECVDATACTX 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)); + + /* + * Sanity checking. + */ + if ( !cbPath + || cbPath > RTPATH_MAX) + { + LogFlowFunc(("Path length invalid, bailing out\n")); + return VERR_INVALID_PARAMETER; + } + + int rc = RTStrValidateEncodingEx(pszPath, RTSTR_MAX, 0); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Path validation failed with %Rrc, bailing out\n", rc)); + return VERR_INVALID_PARAMETER; + } + + if (pCtx->mURI.isComplete()) + { + LogFlowFunc(("Data transfer already complete, bailing out\n")); + return VERR_INVALID_PARAMETER; + } + + GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */ + + rc = objCtx.createIntermediate(DnDURIObject::Type_Directory); + if (RT_FAILURE(rc)) + return rc; + + DnDURIObject *pObj = objCtx.getObj(); + AssertPtr(pObj); + + const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs(); + char *pszDir = RTPathJoinA(pszDroppedFilesDir, pszPath); + if (pszDir) + { +#ifdef RT_OS_WINDOWS + RTPathChangeToDosSlashes(pszDir, true /* fForce */); +#else + RTPathChangeToDosSlashes(pszDir, true /* fForce */); +#endif + rc = RTDirCreateFullPath(pszDir, fMode); + if (RT_SUCCESS(rc)) + { + pCtx->mURI.processObject(*pObj); + + /* Add for having a proper rollback. */ + int rc2 = pCtx->mURI.getDroppedFiles().AddDir(pszDir); + AssertRC(rc2); + + objCtx.reset(); + LogRel2(("DnD: Created guest directory '%s' on host\n", pszDir)); + } + else + LogRel(("DnD: Error creating guest directory '%s' on host, rc=%Rrc\n", pszDir, rc)); + + RTStrFree(pszDir); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDSource::i_onReceiveFileHdr(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath, + uint64_t cbSize, uint32_t fMode, uint32_t fFlags) +{ + RT_NOREF(fFlags); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cbPath, VERR_INVALID_PARAMETER); + AssertReturn(fMode, VERR_INVALID_PARAMETER); + /* fFlags are optional. */ + + LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags)); + + /* + * Sanity checking. + */ + if ( !cbPath + || cbPath > RTPATH_MAX) + { + return VERR_INVALID_PARAMETER; + } + + if (!RTStrIsValidEncoding(pszPath)) + return VERR_INVALID_PARAMETER; + + if (cbSize > pCtx->mData.getTotal()) + { + AssertMsgFailed(("File size (%RU64) exceeds total size to transfer (%RU64)\n", cbSize, pCtx->mData.getTotal())); + return VERR_INVALID_PARAMETER; + } + + if (pCtx->mURI.getObjToProcess() && pCtx->mURI.isComplete()) + return VERR_INVALID_PARAMETER; + + int rc = VINF_SUCCESS; + + do + { + GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */ + DnDURIObject *pObj = objCtx.getObj(); + + /* + * Sanity checking. + */ + if (pObj) + { + if ( pObj->IsOpen() + && !pObj->IsComplete()) + { + AssertMsgFailed(("Object '%s' not complete yet\n", pObj->GetDestPathAbs().c_str())); + rc = VERR_WRONG_ORDER; + break; + } + + if (pObj->IsOpen()) /* File already opened? */ + { + AssertMsgFailed(("Current opened object is '%s', close this first\n", pObj->GetDestPathAbs().c_str())); + rc = VERR_WRONG_ORDER; + break; + } + } + else + { + /* + * Create new intermediate object to work with. + */ + rc = objCtx.createIntermediate(DnDURIObject::Type_File); + } + + if (RT_SUCCESS(rc)) + { + pObj = objCtx.getObj(); + AssertPtr(pObj); + + const char *pszDroppedFilesDir = pCtx->mURI.getDroppedFiles().GetDirAbs(); + AssertPtr(pszDroppedFilesDir); + + char pszPathAbs[RTPATH_MAX]; + rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), pszDroppedFilesDir, pszPath); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc)); + break; + } + + rc = DnDPathSanitize(pszPathAbs, sizeof(pszPathAbs)); + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc)); + break; + } + + LogRel2(("DnD: Absolute file path for guest file on the host is now '%s'\n", pszPathAbs)); + + /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */ + rc = pObj->OpenEx(pszPathAbs, DnDURIObject::View_Target, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE, + (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); + if (RT_SUCCESS(rc)) + { + /* Add for having a proper rollback. */ + int rc2 = pCtx->mURI.getDroppedFiles().AddFile(pszPathAbs); + AssertRC(rc2); + } + else + LogRel(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n", pszPathAbs, rc)); + } + + if (RT_SUCCESS(rc)) + { + /* Note: Protocol v1 does not send any file sizes, so always 0. */ + if (mDataBase.m_uProtocolVersion >= 2) + rc = pObj->SetSize(cbSize); + + /** @todo Unescpae path before printing. */ + LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode 0x%x)\n", + pObj->GetDestPathAbs().c_str(), pObj->GetSize(), pObj->GetMode())); + + /** @todo Set progress object title to current file being transferred? */ + + if (!cbSize) /* 0-byte file? Close again. */ + pObj->Close(); + } + + } while (0); + + if (RT_FAILURE(rc)) + LogRel(("DnD: Error receiving guest file header, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX 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 + { + GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObj(0); /** @todo Fill in context ID. */ + DnDURIObject *pObj = objCtx.getObj(); + + if (!pObj) + { + LogFlowFunc(("Warning: No current object set\n")); + rc = VERR_WRONG_ORDER; + break; + } + + if (pObj->IsComplete()) + { + LogFlowFunc(("Warning: Object '%s' already completed\n", pObj->GetDestPathAbs().c_str())); + rc = VERR_WRONG_ORDER; + break; + } + + if (!pObj->IsOpen()) /* File opened on host? */ + { + LogFlowFunc(("Warning: Object '%s' not opened\n", pObj->GetDestPathAbs().c_str())); + rc = VERR_WRONG_ORDER; + break; + } + + uint32_t cbWritten; + rc = pObj->Write(pvData, cbData, &cbWritten); + if (RT_SUCCESS(rc)) + { + Assert(cbWritten <= cbData); + if (cbWritten < cbData) + { + /** @todo What to do when the host's disk is full? */ + rc = VERR_DISK_FULL; + } + + if (RT_SUCCESS(rc)) + rc = updateProgress(&pCtx->mData, pCtx->mpResp, cbWritten); + } + else + LogRel(("DnD: Error writing guest file data for '%s', rc=%Rrc\n", pObj->GetDestPathAbs().c_str(), rc)); + + if (RT_SUCCESS(rc)) + { + if (pObj->IsComplete()) + { + /** @todo Sanitize path. */ + LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pObj->GetDestPathAbs().c_str())); + pCtx->mURI.processObject(*pObj); + objCtx.reset(); + } + } + + } 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 */ + +/** + * @returns VBox status code that the caller ignores. Not sure if that's + * intentional or not. + */ +int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnD *pInst = GuestDnDInst(); + if (!pInst) + return VERR_INVALID_POINTER; + + GuestDnDResponse *pResp = pCtx->mpResp; + AssertPtr(pCtx->mpResp); + + int rc = pCtx->mCBEvent.Reset(); + if (RT_FAILURE(rc)) + return rc; + + /* Is this context already in receiving state? */ + if (ASMAtomicReadBool(&pCtx->mIsActive)) + return VERR_WRONG_ORDER; + ASMAtomicWriteBool(&pCtx->mIsActive, true); + + /* + * Reset any old data. + */ + pCtx->mData.reset(); + pCtx->mURI.reset(); + pResp->reset(); + + /* + * 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(("mFmtReq=%s, mFmtRecv=%s, mAction=0x%x\n", + pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str(), pCtx->mAction)); + + /* Plain text wanted? */ + if ( pCtx->mFmtReq.equalsIgnoreCase("text/plain") + || pCtx->mFmtReq.equalsIgnoreCase("text/plain;charset=utf-8")) + { + /* Did the guest offer a file? Receive a file instead. */ + if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered)) + pCtx->mFmtRecv = "text/uri-list"; + /* Guest only offers (plain) text. */ + else + pCtx->mFmtRecv = "text/plain;charset=utf-8"; + + /** @todo Add more conversions here. */ + } + /* File(s) wanted? */ + else if (pCtx->mFmtReq.equalsIgnoreCase("text/uri-list")) + { + /* Does the guest support sending files? */ + if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->mFmtOffered)) + pCtx->mFmtRecv = "text/uri-list"; + else /* Bail out. */ + fFoundFormat = false; + } + + if (fFoundFormat) + { + Assert(!pCtx->mFmtReq.isEmpty()); + Assert(!pCtx->mFmtRecv.isEmpty()); + + if (!pCtx->mFmtRecv.equals(pCtx->mFmtReq)) + LogRel3(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n", + pCtx->mFmtReq.c_str(), pCtx->mFmtRecv.c_str())); + + /* + * Call the appropriate receive handler based on the data format to handle. + */ + bool fURIData = DnDMIMENeedsDropDir(pCtx->mFmtRecv.c_str(), pCtx->mFmtRecv.length()); + if (fURIData) + { + rc = i_receiveURIData(pCtx, msTimeout); + } + else + { + rc = i_receiveRawData(pCtx, msTimeout); + } + } + else /* Just inform the user (if verbose release logging is enabled). */ + { + LogRel2(("DnD: The guest does not support format '%s':\n", pCtx->mFmtReq.c_str())); + LogRel2(("DnD: Guest offered the following formats:\n")); + for (size_t i = 0; i < pCtx->mFmtOffered.size(); i++) + LogRel2(("DnD:\tFormat #%zu: %s\n", i, pCtx->mFmtOffered.at(i).c_str())); + } + + ASMAtomicWriteBool(&pCtx->mIsActive, false); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* static */ +void GuestDnDSource::i_receiveDataThreadTask(RecvDataTask *pTask) +{ + LogFlowFunc(("pTask=%p\n", pTask)); + AssertPtrReturnVoid(pTask); + + const ComObjPtr<GuestDnDSource> pThis(pTask->getSource()); + Assert(!pThis.isNull()); + + AutoCaller autoCaller(pThis); + if (FAILED(autoCaller.rc())) + return; + + int vrc = pThis->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */); + if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */ + { + AssertFailed(); + LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc)); + } + + AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS); + + Assert(pThis->mDataBase.m_cTransfersPending); + if (pThis->mDataBase.m_cTransfersPending) + pThis->mDataBase.m_cTransfersPending--; + + LogFlowFunc(("pSource=%p, vrc=%Rrc (ignored)\n", (GuestDnDSource *)pThis, vrc)); +} + +int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int rc; + + LogFlowFuncEnter(); + + GuestDnDResponse *pResp = pCtx->mpResp; + AssertPtr(pCtx->mpResp); + + GuestDnD *pInst = GuestDnDInst(); + if (!pInst) + return VERR_INVALID_POINTER; + +#define REGISTER_CALLBACK(x) \ + do { \ + rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \ + if (RT_FAILURE(rc)) \ + return rc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int rc2 = pResp->setCallback(x, NULL); \ + AssertRC(rc2); \ + } while (0) + + /* + * Register callbacks. + */ + REGISTER_CALLBACK(GUEST_DND_CONNECT); + REGISTER_CALLBACK(GUEST_DND_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + if (mDataBase.m_uProtocolVersion >= 3) + REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR); + REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA); + + do + { + /* + * Receive the raw data. + */ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_GH_EVT_DROPPED); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1); + Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1); + Msg.setNextUInt32(pCtx->mAction); + + /* 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->mCBEvent, pCtx->mpResp, msTimeout); + if (RT_SUCCESS(rc)) + rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + } + + } while (0); + + /* + * Unregister callbacks. + */ + UNREGISTER_CALLBACK(GUEST_DND_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + if (mDataBase.m_uProtocolVersion >= 3) + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR); + UNREGISTER_CALLBACK(GUEST_DND_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->mpResp->setProgress(100, DND_PROGRESS_CANCELLED); + AssertRC(rc2); + } + else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + int rc2 = pCtx->mpResp->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; +} + +int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int rc; + + LogFlowFuncEnter(); + + GuestDnDResponse *pResp = pCtx->mpResp; + AssertPtr(pCtx->mpResp); + + GuestDnD *pInst = GuestDnDInst(); + if (!pInst) + return VERR_INVALID_POINTER; + +#define REGISTER_CALLBACK(x) \ + do { \ + rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \ + if (RT_FAILURE(rc)) \ + return rc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int rc2 = pResp->setCallback(x, NULL); \ + AssertRC(rc2); \ + } while (0) + + /* + * Register callbacks. + */ + /* Guest callbacks. */ + REGISTER_CALLBACK(GUEST_DND_CONNECT); + REGISTER_CALLBACK(GUEST_DND_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + if (mDataBase.m_uProtocolVersion >= 3) + REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR); + REGISTER_CALLBACK(GUEST_DND_GH_SND_DATA); + REGISTER_CALLBACK(GUEST_DND_GH_SND_DIR); + if (mDataBase.m_uProtocolVersion >= 2) + REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR); + REGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA); + + DnDDroppedFiles &droppedFiles = pCtx->mURI.getDroppedFiles(); + + do + { + rc = droppedFiles.OpenTemp(0 /* fFlags */); + if (RT_FAILURE(rc)) + break; + LogFlowFunc(("rc=%Rrc, strDropDir=%s\n", rc, droppedFiles.GetDirAbs())); + if (RT_FAILURE(rc)) + break; + + /* + * Receive the URI list. + */ + GuestDnDMsg Msg; + Msg.setType(HOST_DND_GH_EVT_DROPPED); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextPointer((void*)pCtx->mFmtRecv.c_str(), (uint32_t)pCtx->mFmtRecv.length() + 1); + Msg.setNextUInt32((uint32_t)pCtx->mFmtRecv.length() + 1); + Msg.setNextUInt32(pCtx->mAction); + + /* 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->mCBEvent, pCtx->mpResp, msTimeout); + if (RT_SUCCESS(rc)) + rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + + LogFlowFunc(("Waiting ended with rc=%Rrc\n", rc)); + } + + } while (0); + + /* + * Unregister callbacks. + */ + UNREGISTER_CALLBACK(GUEST_DND_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA_HDR); + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DATA); + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_DIR); + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_HDR); + UNREGISTER_CALLBACK(GUEST_DND_GH_SND_FILE_DATA); + +#undef REGISTER_CALLBACK +#undef UNREGISTER_CALLBACK + + if (RT_FAILURE(rc)) + { + int rc2 = droppedFiles.Rollback(); + if (RT_FAILURE(rc2)) + LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n", + rc2, droppedFiles.GetDirAbs())); + + 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->mpResp->setProgress(100, DND_PROGRESS_CANCELLED); + AssertRC(rc2); + } + else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, + rc, GuestDnDSource::i_hostErrorToString(rc)); + AssertRC(rc2); + } + + rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */ + } + + droppedFiles.Close(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* static */ +DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + PRECVDATACTX pCtx = (PRECVDATACTX)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDSource *pThis = pCtx->mpSource; + 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_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_DISCONNECT: + rc = VERR_CANCELLED; + break; + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_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_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_GH_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->mpResp->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->mpResp->setProgress(100, DND_PROGRESS_CANCELLED); + } + else + rc = pCtx->mpResp->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_GSTDND_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->mpResp); + int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(rc2); + } + + /* All data processed? */ + if (pCtx->mData.isComplete()) + fNotify = true; + + LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n", + pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc)); + + if (fNotify) + { + int rc2 = pCtx->mCBEvent.Notify(rcCallback); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; /* Tell the guest. */ +} + +/* static */ +DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + PRECVDATACTX pCtx = (PRECVDATACTX)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDSource *pThis = pCtx->mpSource; + 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_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_DISCONNECT: + rc = VERR_CANCELLED; + break; + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_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_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_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_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_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->mDataBase.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_GH_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->mpResp->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->mpResp->setProgress(100, DND_PROGRESS_CANCELLED); + } + else + rc = pCtx->mpResp->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_GSTDND_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->mpResp); + int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(rc2); + } + + /* All data processed? */ + if ( pCtx->mURI.isComplete() + && pCtx->mData.isComplete()) + { + fNotify = true; + } + + LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rcCallback=%Rrc, rc=%Rrc\n", + pCtx->mData.getProcessed(), pCtx->mData.getTotal(), fNotify, rcCallback, rc)); + + if (fNotify) + { + int rc2 = pCtx->mCBEvent.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..bc665c22 --- /dev/null +++ b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp @@ -0,0 +1,1546 @@ +/* $Id: GuestDnDTargetImpl.cpp $ */ +/** @file + * VBox Console COM Class implementation - Guest drag'n drop target. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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) { } + + int getRC(void) const { return mRC; } + bool isOk(void) const { return RT_SUCCESS(mRC); } + const ComObjPtr<GuestDnDTarget> &getTarget(void) const { return mTarget; } + +protected: + + const ComObjPtr<GuestDnDTarget> mTarget; + int mRC; +}; + +/** + * Task structure for sending data to a target using + * a worker thread. + */ +class SendDataTask : public GuestDnDTargetTask +{ +public: + + SendDataTask(GuestDnDTarget *pTarget, PSENDDATACTX pCtx) + : GuestDnDTargetTask(pTarget), + mpCtx(pCtx) + { + m_strTaskName = "dndTgtSndData"; + } + + void handler() + { + GuestDnDTarget::i_sendDataThreadTask(this); + } + + virtual ~SendDataTask(void) + { + if (mpCtx) + { + delete mpCtx; + mpCtx = NULL; + } + } + + + PSENDDATACTX getCtx(void) { return mpCtx; } + +protected: + + /** Pointer to send data context. */ + PSENDDATACTX mpCtx; +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(GuestDnDTarget) + +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 = _64K; /** @todo Make this configurable. */ + + LogFlowThisFunc(("\n")); + return BaseFinalConstruct(); +} + +void GuestDnDTarget::FinalRelease(void) +{ + LogFlowThisFuncEnter(); + uninit(); + BaseFinalRelease(); + LogFlowThisFuncLeave(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +int 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; + + /* Confirm a successful initialization when it's the case. */ + autoInitSpan.setSucceeded(); + + return VINF_SUCCESS; +} + +/** + * 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 (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_isFormatSupported(aFormat, aSupported); +#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 (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_getFormats(aFormats); +#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 (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 GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + 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 */ +} + +HRESULT GuestDnDTarget::getProtocolVersion(ULONG *aProtocolVersion) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return GuestDnDBase::i_getProtocolVersion(aProtocolVersion); +#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 (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Determine guest DnD protocol to use. */ + GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion); + + /* 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; + + /* + * 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("\r\n"); + 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()); + + HRESULT hr = S_OK; + + /* Adjust the coordinates in a multi-monitor setup. */ + int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (RT_SUCCESS(rc)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_HG_EVT_ENTER); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextUInt32(aScreenId); + Msg.setNextUInt32(aX); + Msg.setNextUInt32(aY); + Msg.setNextUInt32(dndActionDefault); + Msg.setNextUInt32(dndActionListAllowed); + Msg.setNextPointer((void *)strFormats.c_str(), cbFormats); + Msg.setNextUInt32(cbFormats); + + rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + GuestDnDResponse *pResp = GuestDnDInst()->response(); + if (pResp && RT_SUCCESS(pResp->waitForGuestResponse())) + resAction = GuestDnD::toMainAction(pResp->getActionDefault()); + } + } + + if (RT_FAILURE(rc)) + hr = VBOX_E_IPRT_ERROR; + + if (SUCCEEDED(hr)) + { + if (aResultAction) + *aResultAction = resAction; + } + + LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction)); + return hr; +#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 (FAILED(autoCaller.rc())) 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; + + /* + * 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 hr = S_OK; + + int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (RT_SUCCESS(rc)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_HG_EVT_MOVE); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextUInt32(aScreenId); + Msg.setNextUInt32(aX); + Msg.setNextUInt32(aY); + Msg.setNextUInt32(dndActionDefault); + Msg.setNextUInt32(dndActionListAllowed); + Msg.setNextPointer((void *)strFormats.c_str(), cbFormats); + Msg.setNextUInt32(cbFormats); + + rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + GuestDnDResponse *pResp = GuestDnDInst()->response(); + if (pResp && RT_SUCCESS(pResp->waitForGuestResponse())) + resAction = GuestDnD::toMainAction(pResp->getActionDefault()); + } + } + + if (RT_FAILURE(rc)) + hr = VBOX_E_IPRT_ERROR; + + if (SUCCEEDED(hr)) + { + if (aResultAction) + *aResultAction = resAction; + } + + LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction)); + return hr; +#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 (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hr = S_OK; + + GuestDnDMsg Msg; + Msg.setType(HOST_DND_HG_EVT_LEAVE); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + + int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + { + GuestDnDResponse *pResp = GuestDnDInst()->response(); + if (pResp) + pResp->waitForGuestResponse(); + } + + if (RT_FAILURE(rc)) + hr = VBOX_E_IPRT_ERROR; + + LogFlowFunc(("hr=%Rhrc\n", hr)); + return hr; +#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 resAction = DnDAction_Ignore; + + /* 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; + } + + /* + * 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 hr = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY); + if (SUCCEEDED(hr)) + { + GuestDnDMsg Msg; + Msg.setType(HOST_DND_HG_EVT_DROPPED); + if (mDataBase.m_uProtocolVersion >= 3) + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextUInt32(aScreenId); + Msg.setNextUInt32(aX); + Msg.setNextUInt32(aY); + Msg.setNextUInt32(dndActionDefault); + Msg.setNextUInt32(dndActionListAllowed); + Msg.setNextPointer((void*)strFormats.c_str(), cbFormats); + Msg.setNextUInt32(cbFormats); + + int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(vrc)) + { + GuestDnDResponse *pResp = GuestDnDInst()->response(); + AssertPtr(pResp); + + vrc = pResp->waitForGuestResponse(); + if (RT_SUCCESS(vrc)) + { + resAction = GuestDnD::toMainAction(pResp->getActionDefault()); + + GuestDnDMIMEList lstFormats = pResp->formats(); + if (lstFormats.size() == 1) /* Exactly one format to use specified? */ + { + aFormat = lstFormats.at(0); + LogFlowFunc(("resFormat=%s, resAction=%RU32\n", aFormat.c_str(), pResp->getActionDefault())); + } + else + /** @todo r=bird: This isn't an IPRT error, is it? */ + hr = setError(VBOX_E_IPRT_ERROR, tr("Guest returned invalid drop formats (%zu formats)"), lstFormats.size()); + } + else + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for response of dropped event failed (%Rrc)"), vrc); + } + else + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Sending dropped event to guest failed (%Rrc)"), vrc); + } + else + hr = setError(hr, tr("Retrieving drop coordinates failed")); + + if (SUCCEEDED(hr)) + { + if (aResultAction) + *aResultAction = resAction; + } + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +/* static */ +void GuestDnDTarget::i_sendDataThreadTask(SendDataTask *pTask) +{ + LogFlowFunc(("pTask=%p\n", pTask)); + AssertPtrReturnVoid(pTask); + + const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget()); + Assert(!pThis.isNull()); + + AutoCaller autoCaller(pThis); + if (FAILED(autoCaller.rc())) + return; + + int vrc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */); + if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_sendData(). */ + { + AssertFailed(); + LogRel(("DnD: Sending data to guest failed with %Rrc\n", vrc)); + } + + AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS); + + Assert(pThis->mDataBase.m_cTransfersPending); + if (pThis->mDataBase.m_cTransfersPending) + pThis->mDataBase.m_cTransfersPending--; + + LogFlowFunc(("pTarget=%p, vrc=%Rrc (ignored)\n", (GuestDnDTarget *)pThis, vrc)); +} + +/** + * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the + * guest in this case. + * + * @return HRESULT + * @param aScreenId + * @param aFormat + * @param aData + * @param aProgress + */ +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); + + /* At the moment we only support one transfer at a time. */ + if (mDataBase.m_cTransfersPending) + return setError(E_INVALIDARG, tr("Another drop operation already is in progress")); + + /* Ditto. */ + GuestDnDResponse *pResp = GuestDnDInst()->response(); + AssertPtr(pResp); + + HRESULT hr = pResp->resetProgress(m_pGuest); + if (FAILED(hr)) + return hr; + + SendDataTask *pTask = NULL; + PSENDDATACTX pSendCtx = NULL; + + try + { + //pSendCtx is passed into SendDataTask where one is deleted in destructor + pSendCtx = new SENDDATACTX; + RT_BZERO(pSendCtx, sizeof(SENDDATACTX)); + + pSendCtx->mpTarget = this; + pSendCtx->mpResp = pResp; + pSendCtx->mScreenID = aScreenId; + pSendCtx->mFmtReq = aFormat; + pSendCtx->mData.getMeta().add(aData); + + /* pTask is responsible for deletion of pSendCtx after creating */ + pTask = new SendDataTask(this, pSendCtx); + if (!pTask->isOk()) + { + delete pTask; + LogRel2(("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); + + } + catch (std::bad_alloc &) + { + hr = setError(E_OUTOFMEMORY); + } + catch (...) + { + LogRel2(("DnD: Could not create thread for data sending task\n")); + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) + { + mDataBase.m_cTransfersPending++; + + hr = pResp->queryProgressTo(aProgress.asOutParam()); + ComAssertComRC(hr); + + /* Note: pTask is now owned by the worker thread. */ + } + else + hr = setError(hr, tr("Starting thread for GuestDnDTarget::i_sendDataThread (%Rhrc)"), hr); + + LogFlowFunc(("Returning hr=%Rhrc\n", hr)); + return hr; +#endif /* VBOX_WITH_DRAG_AND_DROP */ +} + +/* 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; +} + +/* 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; +} + +/** + * @returns VBox status code that the caller ignores. Not sure if that's + * intentional or not. + */ +int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* Is this context already in sending state? */ + if (ASMAtomicReadBool(&pCtx->mIsActive)) + return VERR_WRONG_ORDER; + ASMAtomicWriteBool(&pCtx->mIsActive, true); + + /* Clear all remaining outgoing messages. */ + mDataBase.m_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 an URI 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 an URI list (pointing to a file on the guest itself). + * + ** @todo Support more than one format; add a format<->function handler concept. Later. */ + int rc; + bool fHasURIList = std::find(m_lstFmtOffered.begin(), + m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end(); + if (fHasURIList) + { + rc = i_sendURIData(pCtx, msTimeout); + } + else + { + rc = i_sendRawData(pCtx, msTimeout); + } + + ASMAtomicWriteBool(&pCtx->mIsActive, false); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendDataBody(PSENDDATACTX pCtx, GuestDnDData *pData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pData, VERR_INVALID_POINTER); + + /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */ + if (pData->getMeta().getSize() > _64K) + return VERR_NOT_IMPLEMENTED; + + GuestDnDMsg Msg; + + LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32, cbChksum=%RU32\n", + pData->getFmtSize(), pData->getMeta().getSize(), pData->getChkSumSize())); + + Msg.setType(HOST_DND_HG_SND_DATA); + if (mDataBase.m_uProtocolVersion < 3) + { + Msg.setNextUInt32(pCtx->mScreenID); /* uScreenId */ + Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvFormat */ + Msg.setNextUInt32(pData->getFmtSize()); /* cbFormat */ + Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */ + /* Fill in the current data block size to send. + * Note: Only supports uint32_t. */ + Msg.setNextUInt32((uint32_t)pData->getMeta().getSize()); /* cbData */ + } + else + { + Msg.setNextUInt32(0); /** @todo ContextID not used yet. */ + Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */ + Msg.setNextUInt32(pData->getMeta().getSize()); /* cbData */ + Msg.setNextPointer(pData->getChkSumMutable(), pData->getChkSumSize()); /** @todo pvChecksum; not used yet. */ + Msg.setNextUInt32(pData->getChkSumSize()); /** @todo cbChecksum; not used yet. */ + } + + int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + if (RT_SUCCESS(rc)) + rc = updateProgress(pData, pCtx->mpResp, pData->getMeta().getSize()); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendDataHeader(PSENDDATACTX pCtx, GuestDnDData *pData, GuestDnDURIData *pURIData /* = NULL */) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pData, VERR_INVALID_POINTER); + /* pURIData is optional. */ + + GuestDnDMsg Msg; + + Msg.setType(HOST_DND_HG_SND_DATA_HDR); + + Msg.setNextUInt32(0); /** @todo uContext; not used yet. */ + Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */ + Msg.setNextUInt32(pCtx->mScreenID); /* uScreen */ + Msg.setNextUInt64(pData->getTotal()); /* cbTotal */ + Msg.setNextUInt32(pData->getMeta().getSize()); /* cbMeta*/ + Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvMetaFmt */ + Msg.setNextUInt32(pData->getFmtSize()); /* cbMetaFmt */ + Msg.setNextUInt64(pURIData ? pURIData->getObjToProcess() : 0); /* cObjects */ + Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */ + Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */ + Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */ + Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */ + + int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms()); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + DnDURIObject *pObj = pObjCtx->getObj(); + AssertPtr(pObj); + + RTCString strPath = pObj->GetDestPathAbs(); + if (strPath.isEmpty()) + return VERR_INVALID_PARAMETER; + if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */ + return VERR_BUFFER_OVERFLOW; + + LogRel2(("DnD: Transferring host directory '%s' to guest\n", strPath.c_str())); + + pMsg->setType(HOST_DND_HG_SND_DIR); + if (mDataBase.m_uProtocolVersion >= 3) + pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */ + pMsg->setNextString(strPath.c_str()); /* path */ + pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length (maximum is RTPATH_MAX on guest side). */ + pMsg->setNextUInt32(pObj->GetMode()); /* mode */ + + return VINF_SUCCESS; +} + +int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + DnDURIObject *pObj = pObjCtx->getObj(); + AssertPtr(pObj); + + RTCString strPathSrc = pObj->GetSourcePathAbs(); + if (strPathSrc.isEmpty()) + return VERR_INVALID_PARAMETER; + + int rc = VINF_SUCCESS; + + LogFlowFunc(("Sending file with %RU32 bytes buffer, using protocol v%RU32 ...\n", + mData.mcbBlockSize, mDataBase.m_uProtocolVersion)); + LogFlowFunc(("strPathSrc=%s, fIsOpen=%RTbool, cbSize=%RU64\n", strPathSrc.c_str(), pObj->IsOpen(), pObj->GetSize())); + + if (!pObj->IsOpen()) + { + LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", strPathSrc.c_str())); + rc = pObj->OpenEx(strPathSrc, DnDURIObject::View_Source, + RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE); + if (RT_FAILURE(rc)) + LogRel(("DnD: Opening host file '%s' failed, rc=%Rrc\n", strPathSrc.c_str(), rc)); + } + + bool fSendData = false; + if (RT_SUCCESS(rc)) + { + if (mDataBase.m_uProtocolVersion >= 2) + { + uint32_t fState = pObjCtx->getState(); + if (!(fState & DND_OBJCTX_STATE_HAS_HDR)) + { + /* + * 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_HG_SND_FILE_HDR); + pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */ + pMsg->setNextString(pObj->GetDestPathAbs().c_str()); /* pvName */ + pMsg->setNextUInt32((uint32_t)(pObj->GetDestPathAbs().length() + 1)); /* cbName */ + pMsg->setNextUInt32(0); /* uFlags */ + pMsg->setNextUInt32(pObj->GetMode()); /* fMode */ + pMsg->setNextUInt64(pObj->GetSize()); /* uSize */ + + LogFlowFunc(("Sending file header ...\n")); + LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n", + strPathSrc.c_str(), pObj->GetSize(), pObj->GetMode())); + + /** @todo Set progress object title to current file being transferred? */ + + pObjCtx->setState(fState | DND_OBJCTX_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(rc) + && fSendData) + { + rc = i_sendFileData(pCtx, pObjCtx, pMsg); + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Sending host file to guest failed, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + DnDURIObject *pObj = pObjCtx->getObj(); + AssertPtr(pObj); + + AssertPtr(pCtx->mpResp); + + /** @todo Don't allow concurrent reads per context! */ + + /* + * Start sending stuff. + */ + + /* Set the message type. */ + pMsg->setType(HOST_DND_HG_SND_FILE_DATA); + + /* 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_HG_SND_FILE_HDR. */ + if (mDataBase.m_uProtocolVersion <= 1) + { + pMsg->setNextString(pObj->GetDestPathAbs().c_str()); /* pvName */ + pMsg->setNextUInt32((uint32_t)(pObj->GetDestPathAbs().length() + 1)); /* cbName */ + } + else if (mDataBase.m_uProtocolVersion >= 2) + { + pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */ + } + + uint32_t cbRead = 0; + + int rc = pObj->Read(pCtx->mURI.getBufferMutable(), pCtx->mURI.getBufferSize(), &cbRead); + if (RT_SUCCESS(rc)) + { + pCtx->mData.addProcessed(cbRead); + LogFlowFunc(("cbBufSize=%zu, cbRead=%RU32\n", pCtx->mURI.getBufferSize(), cbRead)); + + if (mDataBase.m_uProtocolVersion <= 1) + { + pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */ + pMsg->setNextUInt32(cbRead); /* cbData */ + pMsg->setNextUInt32(pObj->GetMode()); /* fMode */ + } + else /* Protocol v2 and up. */ + { + pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */ + pMsg->setNextUInt32(cbRead); /* cbData */ + + if (mDataBase.m_uProtocolVersion >= 3) + { + /** @todo Calculate checksum. */ + pMsg->setNextPointer(NULL, 0); /* pvChecksum */ + pMsg->setNextUInt32(0); /* cbChecksum */ + } + } + + if (pObj->IsComplete()) /* Done reading? */ + { + LogRel2(("DnD: Transferring file '%s' to guest complete\n", pObj->GetSourcePathAbs().c_str())); + LogFlowFunc(("File '%s' complete\n", pObj->GetSourcePathAbs().c_str())); + + /* DnDURIObject::Read() returns VINF_EOF when finished reading the entire fire, + * but we don't want this here -- so just override this with VINF_SUCCESS. */ + rc = VINF_SUCCESS; + } + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Reading from host file '%s' failed, rc=%Rrc\n", pObj->GetSourcePathAbs().c_str(), rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/* static */ +DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser) +{ + PSENDDATACTX pCtx = (PSENDDATACTX)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + GuestDnDTarget *pThis = pCtx->mpTarget; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg)); + + int rc = VINF_SUCCESS; + int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */ + bool fNotify = false; + + switch (uMsg) + { + case GUEST_DND_CONNECT: + /* Nothing to do here (yet). */ + break; + + case GUEST_DND_DISCONNECT: + rc = VERR_CANCELLED; + break; + + case GUEST_DND_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(); + + rc = pThis->i_sendURIDataLoop(pCtx, pMsg); + if (rc == VINF_EOF) /* Transfer complete? */ + { + LogFlowFunc(("Last URI item processed, bailing out\n")); + } + else if (RT_SUCCESS(rc)) + { + rc = pThis->msgQueueAdd(pMsg); + if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */ + { + LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount())); + pCBData->uMsg = pMsg->getType(); + pCBData->cParms = pMsg->getCount(); + } + } + + if ( RT_FAILURE(rc) + || rc == VINF_EOF) /* Transfer complete? */ + { + delete pMsg; + pMsg = NULL; + } + } + catch(std::bad_alloc & /*e*/) + { + rc = VERR_NO_MEMORY; + } + break; + } + case GUEST_DND_GH_EVT_ERROR: + { + PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms); + AssertPtr(pCBData); + AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER); + AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER); + + pCtx->mpResp->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. */ + } + + rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc, + GuestDnDTarget::i_guestErrorToString(pCBData->rc)); + if (RT_SUCCESS(rc)) + { + rc = VERR_GSTDND_GUEST_ERROR; + rcGuest = pCBData->rc; + } + break; + } + case HOST_DND_HG_SND_DIR: + case HOST_DND_HG_SND_FILE_HDR: + case HOST_DND_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(); + + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Returning uMsg=%RU32\n", uMsg)); + rc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(), + false /* fDeepCopy */); + if (RT_SUCCESS(rc)) + { + pCBData->cParms = pMsg->getCount(); + pThis->msgQueueRemoveNext(); + } + else + LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc)); + } + } + else + rc = VERR_NO_DATA; + + LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc)); + break; + } + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */ + + /* + * Resolve errors. + */ + switch (rc) + { + 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. */ + rcToGuest = VERR_NO_DATA; + break; + } + + case VERR_GSTDND_GUEST_ERROR: + { + LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest)); + break; + } + + case VERR_CANCELLED: + { + LogRel2(("DnD: Transfer to guest canceled\n")); + rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */ + break; + } + + default: + { + LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc)); + rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */ + break; + } + } + + if (RT_FAILURE(rc)) + { + /* Unregister this callback. */ + AssertPtr(pCtx->mpResp); + int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */); + AssertRC(rc2); + + /* Let the waiter(s) know. */ + fNotify = true; + } + + LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest)); + + if (fNotify) + { + int rc2 = pCtx->mCBEvent.Notify(rc); /** @todo Also pass guest error back? */ + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rcToGuest; /* Tell the guest. */ +} + +int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtr(pCtx->mpResp); + +#define REGISTER_CALLBACK(x) \ + do { \ + rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \ + if (RT_FAILURE(rc)) \ + return rc; \ + } while (0) + +#define UNREGISTER_CALLBACK(x) \ + do { \ + int rc2 = pCtx->mpResp->setCallback(x, NULL); \ + AssertRC(rc2); \ + } while (0) + + int rc = pCtx->mURI.init(mData.mcbBlockSize); + if (RT_FAILURE(rc)) + return rc; + + rc = pCtx->mCBEvent.Reset(); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register callbacks. + */ + /* Guest callbacks. */ + REGISTER_CALLBACK(GUEST_DND_CONNECT); + REGISTER_CALLBACK(GUEST_DND_DISCONNECT); + REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG); + REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + /* Host callbacks. */ + REGISTER_CALLBACK(HOST_DND_HG_SND_DIR); + if (mDataBase.m_uProtocolVersion >= 2) + REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR); + REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA); + + do + { + /* + * Extract URI list from current meta data. + */ + GuestDnDData *pData = &pCtx->mData; + GuestDnDURIData *pURI = &pCtx->mURI; + + rc = pURI->fromLocalMetaData(pData->getMeta()); + if (RT_FAILURE(rc)) + break; + + LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n", + pURI->getURIList().GetRootCount(), pURI->getURIList().GetTotalBytes())); + + /* + * Set the new meta data with the URI list in it. + */ + rc = pData->getMeta().fromURIList(pURI->getURIList()); + if (RT_FAILURE(rc)) + break; + + /* + * Set the estimated data sizes we are going to send. + * The total size also contains the meta data size. + */ + const uint32_t cbMeta = pData->getMeta().getSize(); + pData->setEstimatedSize(pURI->getURIList().GetTotalBytes() + cbMeta /* cbTotal */, + cbMeta /* cbMeta */); + + /* + * Set the meta format. + */ + void *pvFmt = (void *)pCtx->mFmtReq.c_str(); + uint32_t cbFmt = (uint32_t)pCtx->mFmtReq.length() + 1; /* Include terminating zero. */ + + pData->setFmt(pvFmt, cbFmt); + + /* + * The first message always is the data header. The meta data itself then follows + * and *only* contains the root elements of an URI 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. + */ + GuestDnDMsg Msg; + + /* + * Send the data header first. + */ + if (mDataBase.m_uProtocolVersion >= 3) + rc = i_sendDataHeader(pCtx, pData, &pCtx->mURI); + + /* + * Send the (meta) data body. + */ + if (RT_SUCCESS(rc)) + rc = i_sendDataBody(pCtx, pData); + + if (RT_SUCCESS(rc)) + { + rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout); + if (RT_SUCCESS(rc)) + pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS); + } + + } while (0); + + /* + * Unregister callbacks. + */ + /* Guest callbacks. */ + UNREGISTER_CALLBACK(GUEST_DND_CONNECT); + UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT); + UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG); + UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR); + /* Host callbacks. */ + UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR); + if (mDataBase.m_uProtocolVersion >= 2) + UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR); + UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_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->mpResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS); + AssertRC(rc2); + } + else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */ + { + int rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc, + GuestDnDTarget::i_hostErrorToString(rc)); + AssertRC(rc2); + } + + rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */ + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + int rc = updateProgress(&pCtx->mData, pCtx->mpResp); + AssertRC(rc); + + if ( pCtx->mData.isComplete() + && pCtx->mURI.isComplete()) + { + return VINF_EOF; + } + + GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObjCurrent(); + if (!objCtx.isValid()) + return VERR_WRONG_ORDER; + + DnDURIObject *pCurObj = objCtx.getObj(); + AssertPtr(pCurObj); + + DnDURIObject::Type enmType = pCurObj->GetType(); + LogRel3(("DnD: Processing: srcPath=%s, dstPath=%s, enmType=%RU32, cbSize=%RU32\n", + pCurObj->GetSourcePathAbs().c_str(), pCurObj->GetDestPathAbs().c_str(), + enmType, pCurObj->GetSize())); + + if (enmType == DnDURIObject::Type_Directory) + { + rc = i_sendDirectory(pCtx, &objCtx, pMsg); + } + else if (DnDURIObject::Type_File) + { + rc = i_sendFile(pCtx, &objCtx, pMsg); + } + else + { + AssertMsgFailed(("enmType=%RU32 is not supported for srcPath=%s, dstPath=%s\n", + enmType, pCurObj->GetSourcePathAbs().c_str(), pCurObj->GetDestPathAbs().c_str())); + rc = VERR_NOT_SUPPORTED; + } + + bool fRemove = false; /* Remove current entry? */ + if ( pCurObj->IsComplete() + || RT_FAILURE(rc)) + { + fRemove = true; + } + + if (fRemove) + { + LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", pCurObj->GetSourcePathAbs().c_str(), rc)); + pCtx->mURI.removeObjCurrent(); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + NOREF(msTimeout); + + GuestDnDData *pData = &pCtx->mData; + + /** @todo At the moment we only allow sending up to 64K raw data. + * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA. + * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */ + if (!pData->getMeta().getSize()) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + /* + * Send the data header first. + */ + if (mDataBase.m_uProtocolVersion >= 3) + rc = i_sendDataHeader(pCtx, pData, NULL /* URI list */); + + /* + * Send the (meta) data body. + */ + if (RT_SUCCESS(rc)) + rc = i_sendDataBody(pCtx, pData); + + int rc2; + if (RT_FAILURE(rc)) + rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc, + GuestDnDTarget::i_hostErrorToString(rc)); + else + rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, rc); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +HRESULT GuestDnDTarget::cancel(BOOL *aVeto) +{ +#if !defined(VBOX_WITH_DRAG_AND_DROP) + ReturnComNotImplemented(); +#else /* VBOX_WITH_DRAG_AND_DROP */ + + int rc = GuestDnDBase::sendCancel(); + + if (aVeto) + *aVeto = FALSE; /** @todo */ + + HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR; + + LogFlowFunc(("hr=%Rhrc\n", hr)); + return hr; +#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..e20185fd --- /dev/null +++ b/src/VBox/Main/src-client/GuestFileImpl.cpp @@ -0,0 +1,1515 @@ +/* $Id: GuestFileImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest file handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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> + + +/** + * 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 rc2 = mFile->signalWaitEvent(aType, aEvent); + NOREF(rc2); +#ifdef DEBUG_andy + LogFlowFunc(("Signalling events of type=%RU32, file=%p resulted in rc=%Rrc\n", + aType, mFile, rc2)); +#endif + break; + } + + default: + AssertMsgFailed(("Unhandled event %RU32\n", aType)); + break; + } + + return S_OK; + } + +private: + + 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.mInitialSize = 0; + mData.mStatus = FileStatus_Undefined; + mData.mOpenInfo = openInfo; + + 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); + +/** @todo r=bird: Why do you have both a offset and a tell() function? + * After a ReadAt or WriteAt with a non-current offset, the tell() result will + * differ from this value, because mOffCurrent is only ever incremented with + * data read or written. */ + *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 +///////////////////////////////////////////////////////////////////////////// + +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; +} + +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); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/* static */ +Utf8Str GuestFile::i_guestErrorToString(int rcGuest) +{ + Utf8Str strError; + + /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ + switch (rcGuest) + { + case VERR_ACCESS_DENIED: + strError += Utf8StrFmt(tr("Access denied")); + break; + + case VERR_ALREADY_EXISTS: + strError += Utf8StrFmt(tr("File already exists")); + break; + + case VERR_FILE_NOT_FOUND: + strError += Utf8StrFmt(tr("File not found")); + break; + + case VERR_NET_HOST_NOT_FOUND: + strError += Utf8StrFmt(tr("Host name not found")); + break; + + case VERR_SHARING_VIOLATION: + strError += Utf8StrFmt(tr("Sharing violation")); + break; + + default: + strError += Utf8StrFmt("%Rrc", rcGuest); + break; + } + + return strError; +} + +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; + /* pSvcCb->mpaParms[0] always contains the context ID. */ + HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.uType); + HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.rc); + + int rcGuest = (int)dataCb.rc; /* uint32_t vs. int. */ + + LogFlowThisFunc(("uType=%RU32, rcGuest=%Rrc\n", dataCb.uType, rcGuest)); + + if (RT_FAILURE(rcGuest)) + { + int rc2 = i_setFileStatus(FileStatus_Error, rcGuest); + AssertRC(rc2); + + /* Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternal(pCbCtx, rcGuest, 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 rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ + + switch (dataCb.uType) + { + case GUEST_FILE_NOTIFYTYPE_ERROR: + { + rc = i_setFileStatus(FileStatus_Error, rcGuest); + break; + } + + case GUEST_FILE_NOTIFYTYPE_OPEN: + { + if (pSvcCbData->mParms == 4) + { + rc = HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.u.open.uHandle); + if (RT_FAILURE(rc)) + break; + + /* Set the process status. */ + rc = i_setFileStatus(FileStatus_Open, rcGuest); + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_CLOSE: + { + rc = i_setFileStatus(FileStatus_Closed, rcGuest); + break; + } + + case GUEST_FILE_NOTIFYTYPE_READ: + { + if (pSvcCbData->mParms == 4) + { + rc = HGCMSvcGetPv(&pSvcCbData->mpaParms[idx++], &dataCb.u.read.pvData, + &dataCb.u.read.cbData); + if (RT_FAILURE(rc)) + break; + + const uint32_t cbRead = dataCb.u.read.cbData; + + Log3ThisFunc(("cbRead=%RU32\n", cbRead)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mOffCurrent += cbRead; + + alock.release(); + + com::SafeArray<BYTE> data((size_t)cbRead); + data.initFrom((BYTE*)dataCb.u.read.pvData, cbRead); + + fireGuestFileReadEvent(mEventSource, mSession, this, mData.mOffCurrent, + cbRead, ComSafeArrayAsInParam(data)); + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_WRITE: + { + if (pSvcCbData->mParms == 4) + { + rc = HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.u.write.cbWritten); + if (RT_FAILURE(rc)) + break; + + Log3ThisFunc(("cbWritten=%RU32\n", dataCb.u.write.cbWritten)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mOffCurrent += dataCb.u.write.cbWritten; + + alock.release(); + + fireGuestFileWriteEvent(mEventSource, mSession, this, mData.mOffCurrent, + dataCb.u.write.cbWritten); + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_SEEK: + { + if (pSvcCbData->mParms == 4) + { + rc = HGCMSvcGetU64(&pSvcCbData->mpaParms[idx++], &dataCb.u.seek.uOffActual); + if (RT_FAILURE(rc)) + 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, mData.mOffCurrent, 0 /* Processed */); + } + break; + } + + case GUEST_FILE_NOTIFYTYPE_TELL: + { + if (pSvcCbData->mParms == 4) + { + rc = HGCMSvcGetU64(&pSvcCbData->mpaParms[idx++], &dataCb.u.tell.uOffActual); + if (RT_FAILURE(rc)) + break; + + Log3ThisFunc(("uOffActual=%RU64\n", dataCb.u.tell.uOffActual)); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mOffCurrent = dataCb.u.tell.uOffActual; + + alock.release(); + + fireGuestFileOffsetChangedEvent(mEventSource, mSession, this, mData.mOffCurrent, 0 /* Processed */); + } + break; + } + + default: + break; + } + + if (RT_SUCCESS(rc)) + { + GuestWaitEventPayload payload(dataCb.uType, &dataCb, sizeof(dataCb)); + + /* Ignore rc, as the event to signal might not be there (anymore). */ + signalWaitEventInternal(pCbCtx, rcGuest, &payload); + } + + LogFlowThisFunc(("uType=%RU32, rcGuest=%Rrc, rc=%Rrc\n", dataCb.uType, rcGuest, rc)); + return rc; +} + +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; +} + +/** + * Called by IGuestSession right before this file gets removed + * from the public file list. + */ +int GuestFile::i_onRemove(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; +} + +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=0x%x, enmOpenAction=0x%x, uCreationMode=%RU32, mfOpenEx=%RU32\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: RT_FALL_THRU(); + case FileAccessMode_AppendRead: return VERR_NOT_IMPLEMENTED; + 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++], mData.mOpenInfo.muOffset); + /** @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; +} + +int GuestFile::i_queryInfo(GuestFsObjData &objData, int *prcGuest) +{ + AssertPtr(mSession); + return mSession->i_fsQueryInfo(mData.mOpenInfo.mFilename, FALSE /* fFollowSymlinks */, objData, prcGuest); +} + +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; +} + +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; +} + +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; +} + +/* static */ +HRESULT GuestFile::i_setErrorExternal(VirtualBoxBase *pInterface, int rcGuest) +{ + AssertPtr(pInterface); + AssertMsg(RT_FAILURE(rcGuest), ("Guest rc does not indicate a failure when setting error\n")); + + return pInterface->setError(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest).c_str()); +} + +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)); + ComAssertComRC(hr); + } + + alock.release(); /* Release lock before firing off event. */ + + fireGuestFileStateChangedEvent(mEventSource, mSession, + this, fileStatus, errorInfo); + } + + return VINF_SUCCESS; +} + +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; +} + +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) + { + ComPtr<IGuestFileReadEvent> pFileEvent = pIEvent; + Assert(!pFileEvent.isNull()); + + HRESULT hr; + if (pvData) + { + com::SafeArray <BYTE> data; + hr = pFileEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data)); + ComAssertComRC(hr); + const size_t cbRead = data.size(); + if (cbRead) + { + if (cbRead <= cbData) + memcpy(pvData, data.raw(), cbRead); + else + vrc = VERR_BUFFER_OVERFLOW; + } + else + vrc = VERR_NO_DATA; + } + if (pcbRead) + { + hr = pFileEvent->COMGETTER(Processed)((ULONG*)pcbRead); + ComAssertComRC(hr); + } + } + else + vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED; + } + + return vrc; +} + +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; + } + + 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; +} + +int GuestFile::i_writeData(uint32_t uTimeoutMS, 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++], 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; +} + +int GuestFile::i_writeDataAt(uint64_t uOffset, uint32_t uTimeoutMS, + 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++], 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 rcGuest; + int vrc = i_closeFile(&rcGuest); + /* On failure don't return here, instead do all the cleanup + * work first and then return an error. */ + + AssertPtr(mSession); + int vrc2 = mSession->i_fileUnregister(this); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + return GuestFile::i_setErrorExternal(this, rcGuest); + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Closing guest file failed with %Rrc\n"), 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 hr = S_OK; + + GuestFsObjData fsObjData; int rcGuest; + int vrc = i_queryInfo(fsObjData, &rcGuest); + if (RT_SUCCESS(vrc)) + { + ComObjPtr<GuestFsObjInfo> ptrFsObjInfo; + hr = ptrFsObjInfo.createObject(); + if (SUCCEEDED(hr)) + { + vrc = ptrFsObjInfo->init(fsObjData); + if (RT_SUCCESS(vrc)) + hr = ptrFsObjInfo.queryInterfaceTo(aObjInfo.asOutParam()); + else + hr = setErrorVrc(vrc); + } + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + else + hr = setErrorVrc(vrc, tr("Querying file information failed: %Rrc"), vrc); + } + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +HRESULT GuestFile::querySize(LONG64 *aSize) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + GuestFsObjData fsObjData; int rcGuest; + int vrc = i_queryInfo(fsObjData, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aSize = fsObjData.mObjectSize; + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + else + hr = setErrorVrc(vrc, tr("Querying file size failed: %Rrc"), vrc); + } + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +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(); + + aData.resize(aToRead); + + HRESULT hr = S_OK; + + uint32_t cbRead; + int vrc = i_readData(aToRead, aTimeoutMS, + &aData.front(), aToRead, &cbRead); + + if (RT_SUCCESS(vrc)) + { + if (aData.size() != cbRead) + aData.resize(cbRead); + } + else + { + aData.resize(0); + + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" failed: %Rrc"), + mData.mOpenInfo.mFilename.c_str(), vrc); + } + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +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 is zero")); + + LogFlowThisFuncEnter(); + + aData.resize(aToRead); + + HRESULT hr = S_OK; + + size_t cbRead; + int 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); + + hr = 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 hr; +} + +HRESULT GuestFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LONG64 *aNewOffset) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hr = 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 specified")); + } + + 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 + hr = 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 hr; +} + +HRESULT GuestFile::setACL(const com::Utf8Str &aAcl, ULONG aMode) +{ + RT_NOREF(aAcl, aMode); + ReturnComNotImplemented(); +} + +HRESULT GuestFile::setSize(LONG64 aSize) +{ + RT_NOREF(aSize); + ReturnComNotImplemented(); +} + +HRESULT GuestFile::write(const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + uint32_t cbData = (uint32_t)aData.size(); + void *pvData = cbData > 0? (void *)&aData.front(): NULL; + int vrc = i_writeData(aTimeoutMS, pvData, cbData, (uint32_t*)aWritten); + if (RT_FAILURE(vrc)) + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing %zubytes to file \"%s\" failed: %Rrc"), + aData.size(), mData.mOpenInfo.mFilename.c_str(), vrc); + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +HRESULT GuestFile::writeAt(LONG64 aOffset, const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + uint32_t cbData = (uint32_t)aData.size(); + void *pvData = cbData > 0? (void *)&aData.front(): NULL; + int vrc = i_writeData(aTimeoutMS, pvData, cbData, (uint32_t*)aWritten); + if (RT_FAILURE(vrc)) + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing %zubytes to file \"%s\" (at offset %RU64) failed: %Rrc"), + aData.size(), mData.mOpenInfo.mFilename.c_str(), aOffset, vrc); + + LogFlowFuncLeaveRC(vrc); + return hr; +} + diff --git a/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp new file mode 100644 index 00000000..36b941e5 --- /dev/null +++ b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp @@ -0,0 +1,227 @@ +/* $Id: GuestFsObjInfoImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest file system object information handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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..4f63834c --- /dev/null +++ b/src/VBox/Main/src-client/GuestImpl.cpp @@ -0,0 +1,1112 @@ +/* $Id: GuestImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation: Guest features. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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/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; + + /* Confirm a successful initialization when it's the case */ + autoInitSpan.setSucceeded(); + + ULONG aMemoryBalloonSize; + HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize); + if (hr == S_OK) /** @todo r=andy SUCCEEDED? */ + mMemoryBalloonSize = aMemoryBalloonSize; + else + mMemoryBalloonSize = 0; /* Default is no ballooning */ + + BOOL fPageFusionEnabled; + hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled); + if (hr == S_OK) /** @todo r=andy SUCCEEDED? */ + 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; + int vrc = RTTimerLRCreate(&mStatTimer, 1000 /* ms */, + &Guest::i_staticUpdateStats, this); + AssertMsgRC(vrc, ("Failed to create guest statistics update timer (%Rrc)\n", vrc)); + + hr = unconst(mEventSource).createObject(); + if (SUCCEEDED(hr)) + hr = mEventSource->init(); + + mCpus = 1; + +#ifdef VBOX_WITH_DRAG_AND_DROP + 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 + + 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 = NULL; + 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 + 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, STAMVISIBILITY enmVisiblity, + const char *pszDesc, void *pvUser) +{ + RT_NOREF(enmVisiblity, pszDesc); + 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 rc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance); + AssertLogRelMsgReturn(RT_SUCCESS(rc) && rc != VWRN_NUMBER_TOO_BIG && rc != VWRN_NEGATIVE_UNSIGNED, + ("%Rrc '%s'\n", rc, 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 rc; + + /* + * 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; + rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn); + if (rc == VINF_SUCCESS) + validStats |= pm::VMSTATMASK_GUEST_MEMSHARED; + } + + if (mCollectVMMStats) + { + rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal); + AssertRC(rc); + if (rc == 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; + rc = STAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this); + AssertRC(rc); + + 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); + 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); + + if (mStatUpdateInterval) + if (aStatisticsUpdateInterval == 0) + RTTimerLRStop(mStatTimer); + else + RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval); + else + if (aStatisticsUpdateInterval != 0) + { + RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval); + RTTimerLRStart(mStatTimer, 0); + } + mStatUpdateInterval = aStatisticsUpdateInterval; + /* 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->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval); + } + + return S_OK; +} + + +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 rc = PGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal, &cbBalloonedTotal, &cbSharedTotal); + AssertRCReturn(rc, 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; + rc = PGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn); + AssertRCReturn(rc, 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 rc = 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: + rc = setError(VBOX_E_NOT_SUPPORTED, + tr("Invalid status level defined: %u"), aLevel); + break; + } + + return rc; +} +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); +} + +/** + * 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; +} + +void Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus, + uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS) +{ + AssertReturnVoid( a_enmFacility < VBoxGuestFacilityType_All + && a_enmFacility > VBoxGuestFacilityType_Unknown); + + FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility); + if (it != mData.mFacilityMap.end()) + { + AdditionsFacility *pFac = it->second; + 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... */ + AssertFailedReturnVoid(); + } + + ComObjPtr<AdditionsFacility> ptrFac; + ptrFac.createObject(); + AssertReturnVoid(!ptrFac.isNull()); + + HRESULT hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus, + a_fFlags, a_pTimeSpecTS); + if (SUCCEEDED(hrc)) + mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac)); + } +} + +/** + * 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_onUserStateChange(Bstr aUser, Bstr aDomain, VBoxGuestUserState enmState, + const uint8_t *pbDetails, uint32_t cbDetails) +{ + RT_NOREF(pbDetails, cbDetails); + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + Bstr strDetails; /** @todo Implement state details here. */ + + fireGuestUserStateChangedEvent(mEventSource, aUser.raw(), aDomain.raw(), + (GuestUserState_T)enmState, strDetails.raw()); + 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. + */ + if (a_enmFacility == VBoxGuestFacilityType_All) + for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it) + i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS); + else /* Update one facility only. */ + i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS); + + /* + * Recalc the runlevel. + */ + 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; +} + +/** + * 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); + + 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 */ +} diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp new file mode 100644 index 00000000..133a17fc --- /dev/null +++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp @@ -0,0 +1,2558 @@ +/* $Id: GuestProcessImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest process handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** + * 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 "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> + + +class GuestProcessTask : public ThreadTask +{ +public: + + GuestProcessTask(GuestProcess *pProcess) + : ThreadTask("GenericGuestProcessTask") + , mProcess(pProcess) + , mRC(VINF_SUCCESS) { } + + virtual ~GuestProcessTask(void) { } + + int i_rc(void) const { return mRC; } + bool i_isOk(void) const { return RT_SUCCESS(mRC); } + const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; } + +protected: + + const ComObjPtr<GuestProcess> mProcess; + int mRC; +}; + +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 rc2 = mProcess->signalWaitEvent(aType, aEvent); + RT_NOREF(rc2); +#ifdef LOG_ENABLED + LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in rc=%Rrc\n", + aType, &mProcess, rc2)); +#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 +///////////////////////////////////////////////////////////////////////////// + +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)); + + /* Terminate process if not already done yet. */ + int rcGuest = VINF_SUCCESS; + int vrc = i_terminateProcess(30 * 1000, &rcGuest); /** @todo Make timeouts configurable. */ + /* Note: Don't return here yet; first uninit all other stuff in + * case of failure. */ + + if (mData.mpSessionBaseEnv) + { + mData.mpSessionBaseEnv->releaseConst(); + mData.mpSessionBaseEnv = NULL; + } + + baseUninit(); + + LogFlowThisFunc(("Returning rc=%Rrc, rcGuest=%Rrc\n", vrc, rcGuest)); + RT_NOREF_PV(vrc); +} + +// 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(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aStatus = mData.mStatus; + + return S_OK; +} + +// private methods +///////////////////////////////////////////////////////////////////////////// + +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 rc = 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)); + rc = VERR_NOT_FOUND; + } + } + + return rc; +} + +/* static */ +Utf8Str GuestProcess::i_guestErrorToString(int rcGuest) +{ + Utf8Str strError; + + /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ + switch (rcGuest) + { + case VERR_FILE_NOT_FOUND: /* This is the most likely error. */ + RT_FALL_THROUGH(); + case VERR_PATH_NOT_FOUND: + strError += Utf8StrFmt(tr("No such file or directory on guest")); + break; + + case VERR_INVALID_VM_HANDLE: + strError += Utf8StrFmt(tr("VMM device is not available (is the VM running?)")); + break; + + case VERR_HGCM_SERVICE_NOT_FOUND: + strError += Utf8StrFmt(tr("The guest execution service is not available")); + break; + + case VERR_BAD_EXE_FORMAT: + strError += Utf8StrFmt(tr("The specified file is not an executable format on guest")); + break; + + case VERR_AUTHENTICATION_FAILURE: + strError += Utf8StrFmt(tr("The specified user was not able to logon on guest")); + break; + + case VERR_INVALID_NAME: + strError += Utf8StrFmt(tr("The specified file is an invalid name")); + break; + + case VERR_TIMEOUT: + strError += Utf8StrFmt(tr("The guest did not respond within time")); + break; + + case VERR_CANCELLED: + strError += Utf8StrFmt(tr("The execution operation was canceled")); + break; + + case VERR_PERMISSION_DENIED: /** @todo r=bird: This is probably completely and utterly misleading. VERR_AUTHENTICATION_FAILURE could have this message. */ + strError += Utf8StrFmt(tr("Invalid user/password credentials")); + break; + + case VERR_GSTCTL_MAX_OBJECTS_REACHED: + strError += Utf8StrFmt(tr("Maximum number of concurrent guest processes has been reached")); + break; + + case VERR_NOT_FOUND: + strError += Utf8StrFmt(tr("The guest execution service is not ready (yet)")); + break; + + default: + strError += Utf8StrFmt("%Rrc", rcGuest); + break; + } + + return strError; +} + +/** + * 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); +} + +inline bool GuestProcess::i_isAlive(void) +{ + return ( mData.mStatus == ProcessStatus_Started + || mData.mStatus == ProcessStatus_Paused + || mData.mStatus == ProcessStatus_Terminating); +} + +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); +} + +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; +} + +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; +} + +int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); + + return VERR_NOT_IMPLEMENTED; +} + +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 rc2 = i_setProcessStatus(procStatus, procRc); + if (RT_SUCCESS(vrc)) + vrc = rc2; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +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; +} + +/** + * Called by IGuestSession right before this process gets + * removed from the public process list. + */ +int GuestProcess::i_onRemove(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; +} + +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 == OUTPUT_HANDLE_ID_STDOUT + || uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED) + && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut)) + || ( uHandle == OUTPUT_HANDLE_ID_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; +} + +/* Does not do locking; caller is responsible for that! */ +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 rc = VINF_SUCCESS; + + if (mData.mStatus != procStatus) /* Was there a process status change? */ + { + mData.mStatus = procStatus; + mData.mLastError = procRc; + + ComObjPtr<VirtualBoxErrorInfo> errorInfo; + HRESULT hr = errorInfo.createObject(); + ComAssertComRC(hr); + if (RT_FAILURE(mData.mLastError)) + { + hr = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError, + COM_IIDOF(IGuestProcess), getComponentName(), + i_guestErrorToString(mData.mLastError)); + ComAssertComRC(hr); + } + + /* 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")); + rc = cancelWaitEvents(); + } +#endif + } + + return rc; +} + +/* static */ +HRESULT GuestProcess::i_setErrorExternal(VirtualBoxBase *pInterface, int rcGuest) +{ + AssertPtr(pInterface); + AssertMsg(RT_FAILURE(rcGuest), ("Guest rc does not indicate a failure when setting error\n")); + + return pInterface->setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, GuestProcess::i_guestErrorToString(rcGuest).c_str()); +} + +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; +} + +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; + + 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; + + if (uProtocol < UINT32_C(0xdeadbeef) ) /** @todo implement a way of sending argv[0], best idea is a new command. */ + vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + else + vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + + RTMemFree(papszArgv); + if (RT_FAILURE(vrc)) + return vrc; + + /* Note! No returns after this. */ + } + + /* Calculate arguments size (in bytes). */ + size_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */ + + /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */ + size_t cbEnvBlock; + char *pszzEnvBlock; + vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock); + if (RT_SUCCESS(vrc)) + { + Assert(cbEnvBlock > 0); + cbEnvBlock--; + + /* 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 rc2 = i_setProcessStatus(ProcessStatus_Error, vrc); + AssertRC(rc2); + } + + mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock); + } + + RTStrFree(pszArgs); + + if (RT_SUCCESS(vrc)) + vrc = i_waitForStatusChange(pEvent, cMsTimeout, + NULL /* Process status */, prcGuest); + return vrc; +} + +int GuestProcess::i_startProcessAsync(void) +{ + LogFlowThisFuncEnter(); + + int vrc = VINF_SUCCESS; + HRESULT hr = S_OK; + + GuestProcessStartTask* pTask = NULL; + try + { + pTask = new GuestProcessStartTask(this); + if (!pTask->i_isOk()) + { + delete pTask; + LogFlowThisFunc(("Could not create GuestProcessStartTask object\n")); + throw VERR_MEMOBJ_INIT_FAILED; + } + LogFlowThisFunc(("Successfully created GuestProcessStartTask object\n")); + //this function delete pTask in case of exceptions, so there is no need in the call of delete operator + hr = pTask->createThread(); + } + catch(std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + catch(int eVRC) + { + vrc = eVRC; + LogFlowThisFunc(("Could not create thread for GuestProcessStartTask task %Rrc\n", vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/* 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; +} + +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)); + } + 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; +} + +/* 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)); + 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; +} + +ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags) +{ + AssertPtr(mSession); + return GuestProcess::i_waitFlagsToResultEx(fWaitFlags, + mData.mStatus /* curStatus */, mData.mStatus /* newStatus */, + mData.mProcess.mFlags, mSession->i_getProtocolVersion()); +} + +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; +} + +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; +} + +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; +} + +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; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/* static */ +bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult, + ProcessStatus_T procStatus, uint32_t uProcFlags, + uint32_t uProtocol) +{ + /** @todo r=bird: If you subscribe to HN, which the 'u' in 'uProcFlags' + * indicates, you should actually be using 'fProc'! */ + RT_NOREF(uProtocol, uProcFlags); + 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; +} + +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 hr = S_OK; + + uint32_t cbRead; int rcGuest; + int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &rcGuest); + if (RT_SUCCESS(vrc)) + { + if (aData.size() != cbRead) + aData.resize(cbRead); + } + else + { + aData.resize(0); + + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from 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 hr; +} + +HRESULT GuestProcess::terminate() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + int rcGuest; + int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + + case VERR_NOT_SUPPORTED: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Terminating process \"%s\" (PID %RU32) not supported by installed Guest Additions"), + mData.mProcess.mExecutable.c_str(), mData.mPID); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Terminating process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + } + + /* Remove process from guest session list. Now only API clients + * still can hold references to it. */ + AssertPtr(mSession); + int rc2 = mSession->i_processUnregister(this); + if (RT_SUCCESS(vrc)) + vrc = rc2; + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +HRESULT GuestProcess::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, ProcessWaitResult_T *aReason) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + /* + * Note: Do not hold any locks here while waiting! + */ + HRESULT hr = S_OK; + + int rcGuest; + ProcessWaitResult_T waitResult; + int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aReason = waitResult; + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + + case VERR_TIMEOUT: + *aReason = ProcessWaitResult_Timeout; + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for process \"%s\" (PID %RU32) failed: %Rrc"), + mData.mProcess.mExecutable.c_str(), mData.mPID, vrc); + break; + } + } + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + HRESULT hr = S_OK; + + uint32_t cbWritten; int rcGuest; + 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, &rcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + + default: + hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing to 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 hr; +} + +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(); +} + +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) + ) + { + if (prcGuest) + *prcGuest = vrcGuest; + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +void GuestProcessTool::uninit(void) +{ + /* Make sure the process is terminated and unregistered from the guest session. */ + int rcGuestIgnored; + terminate(30 * 1000 /* 30s timeout */, &rcGuestIgnored); + + /* 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(); +} + +int GuestProcessTool::getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock) +{ + const GuestProcessStream *pStream = NULL; + if (uHandle == OUTPUT_HANDLE_ID_STDOUT) + pStream = &mStdOut; + else if (uHandle == OUTPUT_HANDLE_ID_STDERR) + pStream = &mStdErr; + + if (!pStream) + return VERR_INVALID_PARAMETER; + + 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; +} + +int GuestProcessTool::getRc(void) const +{ + LONG exitCode = -1; + HRESULT hr = pProcess->COMGETTER(ExitCode(&exitCode)); + AssertComRC(hr); + + return GuestProcessTool::exitCodeToRc(mStartupInfo, exitCode); +} + +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 prcGuest 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 *prcGuest /* = NULL */) +{ + int rcGuest; + + GuestProcessToolErrorInfo errorInfo; + 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. */ + rcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode); + else /* At least return something. */ + rcGuest = errorInfo.rcGuest; + + if (prcGuest) + *prcGuest = rcGuest; + + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + + LogFlowFunc(("Returned rc=%Rrc, rcGuest=%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 prcGuest 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 *prcGuest /* = NULL */) +{ + int rcGuest; + + GuestProcessToolErrorInfo errorInfo; + 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. */ + rcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode); + else /* At least return something. */ + rcGuest = errorInfo.rcGuest; + + if (prcGuest) + *prcGuest = rcGuest; + + vrc = VERR_GSTCTL_GUEST_ERROR; + } + } + + LogFlowFunc(("Returned rc=%Rrc, rcGuest=%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_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; +} + +int GuestProcessTool::wait(uint32_t fToolWaitFlags, int *prcGuest) +{ + return waitEx(fToolWaitFlags, NULL /* pStrmBlkOut */, prcGuest); +} + +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(OUTPUT_HANDLE_ID_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(OUTPUT_HANDLE_ID_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(OUTPUT_HANDLE_ID_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(OUTPUT_HANDLE_ID_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; +} + +int GuestProcessTool::terminate(uint32_t uTimeoutMS, int *prcGuest) +{ + LogFlowThisFuncEnter(); + + int rc; + if (!pProcess.isNull()) + rc = pProcess->i_terminateProcess(uTimeoutMS, prcGuest); + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Converts a toolbox tool's exit code to an IPRT error code. + * + * @return int Returned IPRT error for the particular tool. + * @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. + * + * @return Returned IPRT error for the particular tool. + * @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_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; + } + } + + 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; +} + diff --git a/src/VBox/Main/src-client/GuestSessionImpl.cpp b/src/VBox/Main/src-client/GuestSessionImpl.cpp new file mode 100644 index 00000000..942202cf --- /dev/null +++ b/src/VBox/Main/src-client/GuestSessionImpl.cpp @@ -0,0 +1,4187 @@ +/* $Id: GuestSessionImpl.cpp $ */ +/** @file + * VirtualBox Main - Guest session handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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) { } + + int rc(void) const { return mRC; } + bool isOk(void) const { return RT_SUCCESS(mRC); } + const ComObjPtr<GuestSession> &Session(void) const { return mSession; } + +protected: + + const ComObjPtr<GuestSession> mSession; + int mRC; +}; + +/** + * Class for asynchronously opening a guest session. + */ +class GuestSessionTaskInternalOpen : public GuestSessionTaskInternal +{ +public: + + GuestSessionTaskInternalOpen(GuestSession *pSession) + : GuestSessionTaskInternal(pSession) + { + m_strTaskName = "gctlSesStart"; + } + + void handler() + { + 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. + * + * @return IPRT status code. + ** @todo Docs! + */ +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(SESSIONOBJECTTYPE_SESSION, &mData.mObjectID); + if (RT_SUCCESS(rc)) + { + rc = mData.mEnvironmentChanges.initChangeRecord(); + 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(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("Closing directories (%zu total)\n", + mData.mDirectories.size())); + for (SessionDirectories::iterator itDirs = mData.mDirectories.begin(); + itDirs != mData.mDirectories.end(); ++itDirs) + { + itDirs->second->i_onRemove(); + itDirs->second->uninit(); + } + mData.mDirectories.clear(); + + LogFlowThisFunc(("Closing files (%zu total)\n", + mData.mFiles.size())); + for (SessionFiles::iterator itFiles = mData.mFiles.begin(); + itFiles != mData.mFiles.end(); ++itFiles) + { + itFiles->second->i_onRemove(); + itFiles->second->uninit(); + } + mData.mFiles.clear(); + + LogFlowThisFunc(("Closing processes (%zu total)\n", + mData.mProcesses.size())); + for (SessionProcesses::iterator itProcs = mData.mProcesses.begin(); + itProcs != mData.mProcesses.end(); ++itProcs) + { + itProcs->second->i_onRemove(); + itProcs->second->uninit(); + } + mData.mProcesses.clear(); + + /* Unregister the session's object ID. */ + i_objectUnregister(mData.mObjectID); + + 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(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + int vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges); + + LogFlowFuncLeaveRC(vrc); + return Global::vboxStatusCodeToCOM(vrc); +} + +HRESULT GuestSession::setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges) +{ + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + mData.mEnvironmentChanges.reset(); + int vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges); + + LogFlowFuncLeaveRC(vrc); + return Global::vboxStatusCodeToCOM(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_getPathStyle(); + 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_isReadyExternal(); + if (FAILED(hr)) + return hr; + + int rcGuest; + 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_isReadyExternal(); + if (FAILED(hr)) + return hr; + + int rcGuest; + 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 +/////////////////////////////////////////////////////////////////////////////// + +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)); + + AutoWriteLock 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)); + + VBOXHGCMSVCPARM paParms[4]; + int i = 0; + HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); + HGCMSvcSetU32(&paParms[i++], uFlags); + + alock.release(); /* Drop the write lock before waiting. */ + + 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_isReadyExternal(); + 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")); + + try + { + GuestSessionTaskCopyFrom *pTask = NULL; + ComObjPtr<Progress> pProgressObj; + try + { + pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination); + } + catch(...) + { + hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to create SessionTaskCopyFrom object")); + throw; + } + + hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str())); + if (FAILED(hrc)) + { + delete pTask; + hrc = setError(VBOX_E_IPRT_ERROR, + tr("Creating progress object for SessionTaskCopyFrom object failed")); + throw hrc; + } + + hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + if (SUCCEEDED(hrc)) + { + /* Return progress to the caller. */ + pProgressObj = pTask->GetProgressObject(); + hrc = pProgressObj.queryInterfaceTo(pProgress.asOutParam()); + } + else + hrc = setError(hrc, tr("Starting thread for copying from guest to \"%s\" on the host failed"), strDestination.c_str()); + + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + catch (HRESULT eHR) + { + hrc = eHR; + } + + 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_isReadyExternal(); + 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")); + + try + { + GuestSessionTaskCopyTo *pTask = NULL; + ComObjPtr<Progress> pProgressObj; + try + { + pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination); + } + catch(...) + { + hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to create SessionTaskCopyTo object")); + throw; + } + + hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str())); + if (FAILED(hrc)) + { + delete pTask; + hrc = setError(VBOX_E_IPRT_ERROR, + tr("Creating progress object for SessionTaskCopyTo object failed")); + throw hrc; + } + + hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + if (SUCCEEDED(hrc)) + { + /* Return progress to the caller. */ + pProgressObj = pTask->GetProgressObject(); + hrc = pProgressObj.queryInterfaceTo(pProgress.asOutParam()); + } + else + hrc = setError(hrc, tr("Starting thread for copying from host to \"%s\" on the guest failed"), strDestination.c_str()); + + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + catch (HRESULT eHR) + { + hrc = eHR; + } + + 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 pfFlags Where to store the extracted (and validated) flags. + */ +HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, 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 + 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; +} + +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; +} + +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; +} + +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 session. + * + * @return VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore). + * @param pDirectory Directory object to unregister from session. + */ +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_onRemove(); + 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; +} + +int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t uFlags, int *prcGuest) +{ + AssertReturn(!(uFlags & ~DIRREMOVE_FLAG_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), uFlags)); + + AutoWriteLock 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++], uFlags); + + alock.release(); /* Drop write 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; +} + +int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName, + int *prcGuest) +{ + AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); + + LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool\n", + strTemplate.c_str(), strPath.c_str(), fDirectory)); + + 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); + } + 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 = vrc; + 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; +} + +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); + + /* Register a new object ID. */ + uint32_t idObject; + int rc = i_objectRegister(SESSIONOBJECTTYPE_DIRECTORY, &idObject); + if (RT_FAILURE(rc)) + return rc; + + /* Create the directory object. */ + HRESULT hr = pDirectory.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + Console *pConsole = mParent->i_getConsole(); + AssertPtr(pConsole); + + int vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo); + if (RT_FAILURE(vrc)) + return vrc; + + /* + * 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 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 &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* 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. + */ +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 itObjs = mData.mObjects.find(idObject); + if (itObjs != mData.mObjects.end()) + { + /* Set protocol version so that pSvcCb can be interpreted right. */ + pCtxCb->uProtocol = mData.mProtocolVersion; + + switch (itObjs->second.enmType) + { + case SESSIONOBJECTTYPE_ANONYMOUS: + rc = VERR_NOT_SUPPORTED; + break; + + case SESSIONOBJECTTYPE_SESSION: + { + alock.release(); + + rc = i_dispatchToThis(pCtxCb, pSvcCb); + break; + } + case SESSIONOBJECTTYPE_DIRECTORY: + { + SessionDirectories::const_iterator itDir = mData.mDirectories.find(idObject); + if (itDir != mData.mDirectories.end()) + { + ComObjPtr<GuestDirectory> pDirectory(itDir->second); + Assert(!pDirectory.isNull()); + + alock.release(); + + rc = pDirectory->i_callbackDispatcher(pCtxCb, pSvcCb); + } + break; + } + case SESSIONOBJECTTYPE_FILE: + { + SessionFiles::const_iterator itFile = mData.mFiles.find(idObject); + if (itFile != mData.mFiles.end()) + { + ComObjPtr<GuestFile> pFile(itFile->second); + Assert(!pFile.isNull()); + + alock.release(); + + rc = pFile->i_callbackDispatcher(pCtxCb, pSvcCb); + } + break; + } + case SESSIONOBJECTTYPE_PROCESS: + { + SessionProcesses::const_iterator itProc = mData.mProcesses.find(idObject); + if (itProc != mData.mProcesses.end()) + { + ComObjPtr<GuestProcess> pProcess(itProc->second); + Assert(!pProcess.isNull()); + + alock.release(); + + rc = pProcess->i_callbackDispatcher(pCtxCb, pSvcCb); + } + break; + } + default: + AssertMsgFailed(("%d\n", itObjs->second.enmType)); + rc = VERR_INTERNAL_ERROR_4; + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) +{ + AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n", + mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb)); + 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, pSvcCb); + break; + } + + default: + rc = dispatchGeneric(pCbCtx, pSvcCb); + 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 pfFlags Where to store the extracted (and validated) flags. + */ +HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, 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 + 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; +} + +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 session. + * + * @return VBox status code. VERR_NOT_FOUND if the file is not registered (anymore). + * @param pFile File object to unregister from session. + */ +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_onRemove(); + 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; +} + +int GuestSession::i_fileRemove(const Utf8Str &strPath, int *prcGuest) +{ + LogFlowThisFunc(("strPath=%s\n", strPath.c_str())); + + int vrc = VINF_SUCCESS; + + 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 &) + { + vrc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(vrc)) + vrc = GuestProcessTool::run(this, procInfo, prcGuest); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +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; + RT_ZERO(openInfo); + + openInfo.mFilename = aPath; + openInfo.mCreationMode = aCreationMode; + openInfo.mAccessMode = aAccessMode; + openInfo.mOpenAction = aOpenAction; + openInfo.mSharingMode = aSharingMode; + + /* Combine and validate flags. */ + uint32_t fOpenEx = 0; + for (size_t i = 0; i < aFlags.size(); i++) + fOpenEx = aFlags[i]; + if (fOpenEx) + return VERR_INVALID_PARAMETER; /* FileOpenExFlag not implemented yet. */ + openInfo.mfOpenEx = fOpenEx; + + return i_fileOpen(openInfo, pFile, prcGuest); +} + +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; + } + + /* Register a new object ID. */ + uint32_t idObject; + int rc = i_objectRegister(SESSIONOBJECTTYPE_FILE, &idObject); + if (RT_FAILURE(rc)) + return rc; + + /* Create the directory object. */ + HRESULT hr = pFile.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + 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; + rc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &rcGuest); + if ( rc == VERR_GSTCTL_GUEST_ERROR + && prcGuest) + { + *prcGuest = rcGuest; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +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; +} + +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 = vrc; + 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; +} + +const GuestCredentials& GuestSession::i_getCredentials(void) +{ + return mData.mCredentials; +} + +Utf8Str GuestSession::i_getName(void) +{ + return mData.mSession.mName; +} + +/* 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 += Utf8StrFmt(tr("VMM device is not available (is the VM running?)")); + break; + + case VERR_HGCM_SERVICE_NOT_FOUND: + strError += Utf8StrFmt(tr("The guest execution service is not available")); + break; + + case VERR_ACCOUNT_RESTRICTED: + strError += Utf8StrFmt(tr("The specified user account on the guest is restricted and can't be used to logon")); + break; + + case VERR_AUTHENTICATION_FAILURE: + strError += Utf8StrFmt(tr("The specified user was not able to logon on guest")); + break; + + case VERR_TIMEOUT: + strError += Utf8StrFmt(tr("The guest did not respond within time")); + break; + + case VERR_CANCELLED: + strError += Utf8StrFmt(tr("The session operation was canceled")); + break; + + case VERR_PERMISSION_DENIED: /** @todo r=bird: This is probably completely and utterly misleading. VERR_AUTHENTICATION_FAILURE could have this message. */ + strError += Utf8StrFmt(tr("Invalid user/password credentials")); + break; + + case VERR_GSTCTL_MAX_OBJECTS_REACHED: + strError += Utf8StrFmt(tr("Maximum number of concurrent guest processes has been reached")); + break; + + case VERR_NOT_FOUND: + strError += Utf8StrFmt(tr("The guest execution service is not ready (yet)")); + break; + + default: + strError += Utf8StrFmt("%Rrc", rcGuest); + break; + } + + return strError; +} + +/** + * 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_isReadyExternal(void) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /** @todo Be a bit more informative. */ + if (mData.mStatus != GuestSessionStatus_Started) + return setError(E_UNEXPECTED, tr("Session is not in started state")); + + return S_OK; +} + +/** + * Called by IGuest right before this session gets removed from + * the public session list. + */ +int GuestSession::i_onRemove(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; +} + +/** No locking! */ +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); + + 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; + 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(); + if (RT_SUCCESS(vrc)) + vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock); + if (RT_SUCCESS(vrc)) + mData.mpBaseEnvironment = pBaseEnv; + else + pBaseEnv->release(); + } + } +#endif + break; + + case GUEST_SESSION_NOTIFYTYPE_TEN: + case GUEST_SESSION_NOTIFYTYPE_TES: + case GUEST_SESSION_NOTIFYTYPE_TEA: + sessionStatus = GuestSessionStatus_Terminated; + break; + + case GUEST_SESSION_NOTIFYTYPE_TOK: + sessionStatus = GuestSessionStatus_TimedOutKilled; + break; + + case GUEST_SESSION_NOTIFYTYPE_TOA: + sessionStatus = GuestSessionStatus_TimedOutAbnormally; + break; + + case GUEST_SESSION_NOTIFYTYPE_DWN: + sessionStatus = GuestSessionStatus_Down; + break; + + case GUEST_SESSION_NOTIFYTYPE_UNDEFINED: + default: + vrc = VERR_NOT_SUPPORTED; + break; + } + + 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; +} + +PathStyle_T GuestSession::i_getPathStyle(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; +} + +int GuestSession::i_startSession(int *prcGuest) +{ + AutoWriteLock 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) + { + mData.mStatus = GuestSessionStatus_Started; + + 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. */ + + /* Set current session status. */ + mData.mStatus = GuestSessionStatus_Starting; + mData.mRC = VINF_SUCCESS; /* Clear previous error, if any. */ + + 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; + + 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 write 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. + */ + mData.mStatus = GuestSessionStatus_Error; + mData.mRC = vrc; + } + + unregisterWaitEvent(pEvent); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +int GuestSession::i_startSessionAsync(void) +{ + LogFlowThisFuncEnter(); + + int vrc; + GuestSessionTaskInternalOpen* pTask = NULL; + try + { + pTask = new GuestSessionTaskInternalOpen(this); + if (!pTask->isOk()) + { + delete pTask; + LogFlow(("GuestSession: Could not create GuestSessionTaskInternalOpen object \n")); + throw VERR_MEMOBJ_INIT_FAILED; + } + + /* Asynchronously open the session on the guest by kicking off a + * worker thread. */ + //this function delete pTask in case of exceptions, so there is no need in the call of delete operator + HRESULT hrc = pTask->createThread(); + vrc = Global::vboxStatusCodeFromCOM(hrc); + } + catch(std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + } + catch(int eVRC) + { + vrc = eVRC; + LogFlow(("GuestSession: Could not create thread for GuestSessionTaskInternalOpen task %Rrc\n", vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/* static */ +void GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalOpen *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; + + int vrc = pSession->i_startSession(NULL /* Guest rc, ignored */); +/** @todo + * + * r=bird: Is it okay to ignore @a vrc here? + * + */ + + /* Nothing to do here anymore. */ + + LogFlowFuncLeaveRC(vrc); + NOREF(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 enmType Session object type to register. + * @param pidObject Where to return the object ID on success. + */ +int GuestSession::i_objectRegister(SESSIONOBJECTTYPE enmType, uint32_t *pidObject) +{ + /* + * 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_OBJECTS_REACHED); + idObject = iHit; + AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2); + } + else + { + LogFunc(("enmType=%RU32 -> VERR_GSTCTL_MAX_OBJECTS_REACHED!! (%zu objects)\n", enmType, mData.mObjects.size())); + return VERR_GSTCTL_MAX_OBJECTS_REACHED; + } + + Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size())); + + try + { + mData.mObjects[idObject].enmType = enmType; + mData.mObjects[idObject].msBirth = RTTimeMilliTS(); + } + catch (std::bad_alloc &) + { + ASMBitClear(&mData.bmObjectIds[0], idObject); + return VERR_NO_MEMORY; + } + + alock.release(); + + *pidObject = idObject; + return VINF_SUCCESS; +} + +/** + * Unregisters an object from a session. + * + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_FOUND if the object ID was not found. + * @param idObject Object ID to unregister. + */ +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; +} + +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)); + + AutoWriteLock 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 write 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. + * + * @return 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. + */ +int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *prcGuest) +{ + AutoWriteLock 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 write 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. + * + * @return 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. + */ +int GuestSession::i_pathUserHome(Utf8Str &strPath, int *prcGuest) +{ + AutoWriteLock 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 write 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 session. + * + * @return VBox status code. VERR_NOT_FOUND if the process is not registered (anymore). + * @param pProcess Process object to unregister from session. + */ +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_onRemove(); + 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. + * + * @return IPRT status code. + * @param procInfo + * @param pProcess + */ +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; + } + + /* 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); + + /* Register a new object ID. */ + uint32_t idObject; + int rc = i_objectRegister(SESSIONOBJECTTYPE_PROCESS, &idObject); + if (RT_FAILURE(rc)) + return rc; + + /* Create the process object. */ + HRESULT hr = pProcess.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + 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; +} + +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; +} + +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.rc()) + 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; +} + +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; +} + +/* static */ +HRESULT GuestSession::i_setErrorExternal(VirtualBoxBase *pInterface, int rcGuest) +{ + AssertPtr(pInterface); + AssertMsg(RT_FAILURE(rcGuest), ("Guest rc does not indicate a failure when setting error\n")); + + return pInterface->setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, GuestSession::i_guestErrorToString(rcGuest).c_str()); +} + +/* Does not do locking; caller is responsible for that! */ +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)); + + if (mData.mStatus != sessionStatus) + { + mData.mStatus = sessionStatus; + mData.mRC = sessionRc; + + 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); + + fireGuestSessionStateChangedEvent(mEventSource, this, + mData.mSession.mID, sessionStatus, errorInfo); + } + + return VINF_SUCCESS; +} + +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; +} + +/** + * Determines the protocol version (sets mData.mProtocolVersion). + * + * This is called from the init method prior to to establishing a guest + * session. + * + * @return IPRT 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, (tr("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; +} + +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; + + 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, &sessionStatus, prcGuest); + if (RT_SUCCESS(vrc)) + { + switch (sessionStatus) + { + case GuestSessionStatus_Started: + waitResult = GuestSessionWaitResult_Start; + break; + + case GuestSessionStatus_Terminated: + waitResult = GuestSessionWaitResult_Terminate; + break; + + case GuestSessionStatus_TimedOutKilled: + 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); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +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)) + { + Assert(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)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +// implementation of public methods +///////////////////////////////////////////////////////////////////////////// + +HRESULT GuestSession::close() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + LogFlowThisFuncEnter(); + + /* Note: Don't check if the session is ready via i_isReadyExternal() here; + * the session (already) could be in a stopped / aborted state. */ + + /* Close session on guest. */ + int rcGuest = VINF_SUCCESS; + int vrc = i_closeSession(0 /* Flags */, 30 * 1000 /* Timeout */, &rcGuest); + /* On failure don't return here, instead do all the cleanup + * work first and then return an error. */ + + /* Remove ourselves from the session list. */ + AssertPtr(mParent); + int vrc2 = mParent->i_sessionRemove(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) + return GuestSession::i_setErrorExternal(this, rcGuest); + return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session failed with %Rrc"), 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + uint32_t fFlags = FileCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_File; + source.enmPathStyle = i_getPathStyle(); + source.Type.File.fCopyFlags = (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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + uint32_t fFlags = FileCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_File; + source.enmPathStyle = i_getPathStyle(); + source.Type.File.fCopyFlags = (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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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; + int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &rcGuest); + if ( RT_FAILURE(vrc) + && !fContinueOnErrors) + { + if (GuestProcess::i_isGuestError(vrc)) + return setError(E_FAIL, tr("Unable to query type for source '%s': %s"), (*itSource).c_str(), + GuestProcess::i_guestErrorToString(rcGuest).c_str()); + else + 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 = objData.mType; + source.enmPathStyle = i_getPathStyle(); + + HRESULT hrc; + if (source.enmType == FsObjType_Directory) + { + hrc = GuestSession::i_directoryCopyFlagFromStr(strFlags, &source.Type.Dir.fCopyFlags); + source.Type.Dir.fRecursive = true; /* Implicit. */ + } + else if (source.enmType == FsObjType_File) + hrc = GuestSession::i_fileCopyFlagFromStr(strFlags, &source.Type.File.fCopyFlags); + else + return setError(E_INVALIDARG, tr("Source type %d invalid / not supported"), source.enmType); + if (FAILED(hrc)) + return hrc; + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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 = i_getPathStyle(); + + HRESULT hrc; + if (source.enmType == FsObjType_Directory) + { + hrc = GuestSession::i_directoryCopyFlagFromStr(strFlags, &source.Type.Dir.fCopyFlags); + source.Type.Dir.fFollowSymlinks = true; /** @todo Add a flag for that in DirectoryCopyFlag_T. Later. */ + source.Type.Dir.fRecursive = true; /* Implicit. */ + } + else if (source.enmType == FsObjType_File) + hrc = GuestSession::i_fileCopyFlagFromStr(strFlags, &source.Type.File.fCopyFlags); + else + return setError(E_INVALIDARG, tr("Source type %d invalid / not supported"), source.enmType); + if (FAILED(hrc)) + return hrc; + + SourceSet.push_back(source); + + ++itSource; + } + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + uint32_t fFlags = DirectoryCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_Directory; + source.enmPathStyle = i_getPathStyle(); + source.Type.Dir.fCopyFlags = (DirectoryCopyFlag_T)fFlags; + source.Type.Dir.fRecursive = true; /* Implicit. */ + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + uint32_t fFlags = DirectoryCopyFlag_None; + if (aFlags.size()) + { + for (size_t i = 0; i < aFlags.size(); i++) + fFlags |= aFlags[i]; + } + + GuestSessionFsSourceSet SourceSet; + + GuestSessionFsSourceSpec source; + source.strSource = aSource; + source.enmType = FsObjType_Directory; + source.enmPathStyle = i_getPathStyle(); + source.Type.Dir.fCopyFlags = (DirectoryCopyFlag_T)fFlags; + source.Type.Dir.fFollowSymlinks = true; /** @todo Add a flag for that in DirectoryCopyFlag_T. Later. */ + source.Type.Dir.fRecursive = true; /* Implicit. */ + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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) + if (!(fFlags & DirectoryCreateFlag_Parents)) + return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags); + } + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + ComObjPtr <GuestDirectory> pDirectory; int rcGuest; + int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &rcGuest); + if (RT_FAILURE(vrc)) + { + if (GuestProcess::i_isGuestError(vrc)) + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, + tr("Directory creation failed: %s"), GuestDirectory::i_guestErrorToString(rcGuest).c_str()); + else + { + switch (vrc) + { + case VERR_INVALID_PARAMETER: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Directory creation failed: Invalid parameters given")); + break; + + case VERR_BROKEN_PIPE: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Directory creation failed: Unexpectedly aborted")); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("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) +{ + RT_NOREF(aMode, aSecure); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + int rcGuest; + int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, &rcGuest); + if (!RT_SUCCESS(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory to check existence for specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestFsObjData objData; int rcGuest; + int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); + if (RT_SUCCESS(vrc)) + *aExists = objData.mType == FsObjType_Directory; + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_PATH_NOT_FOUND: + *aExists = FALSE; + break; + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying directory existence \"%s\" failed: %s"), + aPath.c_str(), GuestProcess::i_guestErrorToString(rcGuest).c_str()); + break; + } + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestDirectoryOpenInfo openInfo; + openInfo.mPath = aPath; + openInfo.mFilter = aFilter; + openInfo.mFlags = fFlags; + + ComObjPtr <GuestDirectory> pDirectory; int rcGuest; + 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 directory \"%s\" failed; invalid parameters given"), + aPath.c_str()); + break; + + case VERR_GSTCTL_GUEST_ERROR: + hrc = GuestDirectory::i_setErrorExternal(this, rcGuest); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + break; + } + } + + return hrc; +} + +HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory to remove specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + /* No flags; only remove the directory when empty. */ + uint32_t uFlags = 0; + + int rcGuest; + int vrc = i_directoryRemove(aPath, uFlags, &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: + hrc = GuestDirectory::i_setErrorExternal(this, rcGuest); + 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) +{ + RT_NOREF(aFlags); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No directory to remove recursively specified")); + +/** @todo r=bird: Must check that the flags matches the hardcoded behavior + * further down!! */ + + HRESULT hrc = i_isReadyExternal(); + 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; + + /* Remove the directory + all its contents. */ + uint32_t uFlags = DIRREMOVE_FLAG_RECURSIVE + | DIRREMOVE_FLAG_CONTENT_AND_DIR; + int rcGuest; + int vrc = i_directoryRemove(aPath, uFlags, &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: + hrc = GuestFile::i_setErrorExternal(this, rcGuest); + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hrc; + if (RT_LIKELY(aName.isNotEmpty())) + { + if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) + { + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + int vrc = mData.mEnvironmentChanges.setVariable(aName, aValue); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorVrc(vrc); + } + else + hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); + } + else + hrc = setError(E_INVALIDARG, tr("No variable name specified")); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hrc; + if (RT_LIKELY(aName.isNotEmpty())) + { + if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) + { + LogFlowThisFuncEnter(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + int vrc = mData.mEnvironmentChanges.unsetVariable(aName); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorVrc(vrc); + } + else + hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); + } + else + hrc = setError(E_INVALIDARG, tr("No variable name specified")); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hrc; + if (RT_LIKELY(aName.isNotEmpty())) + { + if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) + { + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (mData.mpBaseEnvironment) + { + int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + 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")); + } + else + hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); + } + else + hrc = setError(E_INVALIDARG, tr("No variable name specified")); + + LogFlowThisFuncLeave(); + return hrc; +} + +HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + *aExists = FALSE; + + HRESULT hrc; + if (RT_LIKELY(aName.isNotEmpty())) + { + if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) + { + LogFlowThisFuncEnter(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + 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")); + } + else + hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); + } + else + hrc = setError(E_INVALIDARG, tr("No variable name specified")); + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + { + *aExists = FALSE; + return S_OK; + } + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestFsObjData objData; int rcGuest; + int vrc = i_fileQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aExists = TRUE; + return S_OK; + } + + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + break; + +/** @todo r=bird: what about VERR_PATH_NOT_FOUND and VERR_FILE_NOT_FOUND? + * Where does that get converted to *aExists = FALSE? */ + case VERR_NOT_A_FILE: + *aExists = FALSE; + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) + return setError(E_INVALIDARG, tr("No file to open specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFuncEnter(); + + GuestFileOpenInfo openInfo; + openInfo.mFilename = aPath; + openInfo.mCreationMode = aCreationMode; + + /* Validate aAccessMode. */ + switch (aAccessMode) + { + case FileAccessMode_ReadOnly: + RT_FALL_THRU(); + case FileAccessMode_WriteOnly: + RT_FALL_THRU(); + case FileAccessMode_ReadWrite: + openInfo.mAccessMode = aAccessMode; + 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: + openInfo.mOpenAction = aOpenAction; + break; + default: + return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode); + } + + /* Validate aSharingMode. */ + switch (aSharingMode) + { + case FileSharingMode_All: + openInfo.mSharingMode = aSharingMode; + 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); + openInfo.mfOpenEx = fOpenEx; + + ComObjPtr <GuestFile> pFile; + int rcGuest; + 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: + hrc = GuestFile::i_setErrorExternal(this, rcGuest); + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + int64_t llSize; int rcGuest; + int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &rcGuest); + if (RT_SUCCESS(vrc)) + { + *aSize = llSize; + } + else + { + if (GuestProcess::i_isGuestError(vrc)) + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying file size failed: %Rrc"), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); + + *aExists = false; + + GuestFsObjData objData; + int rcGuest; + 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 + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + } + else + hrc = setErrorVrc(vrc, tr("Querying file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr<IGuestFsObjInfo> &aInfo) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); + + GuestFsObjData Info; int rcGuest; + 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)) + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + else + hrc = setErrorVrc(vrc, tr("Querying file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + if (RT_UNLIKELY(aPath.isEmpty())) + return setError(E_INVALIDARG, tr("No path specified")); + + HRESULT hrc = i_isReadyExternal(); + if (FAILED(hrc)) + return hrc; + + LogFlowThisFunc(("aPath=%s\n", aPath.c_str())); + + int rcGuest; + int vrc = i_fileRemove(aPath, &rcGuest); + if (RT_FAILURE(vrc)) + { + if (GuestProcess::i_isGuestError(vrc)) + hrc = GuestProcess::i_setErrorExternal(this, rcGuest); + else + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing file \"%s\" failed: %Rrc"), aPath.c_str(), vrc); + } + + return hrc; +} + +HRESULT GuestSession::fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths, ComPtr<IProgress> &aProgress) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + RT_NOREF(aPaths, aProgress); + + return E_NOTIMPL; +} + +HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource, + const com::Utf8Str &aDestination, + const std::vector<FsObjRenameFlag_T> &aFlags) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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_isReadyExternal(); + 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; + 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 directories not supported by installed Guest Additions")); + break; + + case VERR_GSTCTL_GUEST_ERROR: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Renaming guest directory failed: %Rrc"), rcGuest); + break; + + default: + hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest directory \"%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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + HRESULT hr = i_isReadyExternal(); + if (FAILED(hr)) + return hr; + + /** @todo r=bird: Check input better? aPriority is passed on to the guest + * without any validation. Flags not existing in this vbox version are + * ignored, potentially doing something entirely different than what the + * caller had in mind. */ + + /* + * 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")); + } + + 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]); + + /* 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)) + vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment); + 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_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 = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc); + + LogFlowFuncLeaveRC(vrc); + return hr; +} + +HRESULT GuestSession::processGet(ULONG aPid, ComPtr<IGuestProcess> &aGuestProcess) + +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Note: No call to i_isReadyExternal() 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; 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: + hrc = GuestSession::i_setErrorExternal(this, rcGuest); + 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) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* Note: No call to i_isReadyExternal() 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..8385a05b --- /dev/null +++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp @@ -0,0 +1,2510 @@ +/* $Id: GuestSessionImplTasks.cpp $ */ +/** @file + * VirtualBox Main - Guest session tasks. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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 transfered. */ +#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_getPathStyle()) + { + case PathStyle_DOS: + mfPathStyle = RTPATH_STR_F_STYLE_DOS; + mPathStyle = "\\"; + break; + + default: + mfPathStyle = RTPATH_STR_F_STYLE_UNIX; + mPathStyle = "/"; + break; + } +} + +GuestSessionTask::~GuestSessionTask(void) +{ +} + +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; +} + +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); +} + +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; +} + +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; +} + +int GuestSessionTask::setProgressSuccess(void) +{ + if (mProgress.isNull()) /* Progress is optional. */ + return VINF_SUCCESS; + + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && !fCompleted) + { + HRESULT hr = mProgress->i_notifyComplete(S_OK); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; /** @todo Find a better rc. */ + } + + return VINF_SUCCESS; +} + +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(), + strMsg.c_str()); + if (FAILED(hr2)) + return hr2; + } + return hr; /* Return original rc. */ +} + +/** + * Creates a directory on the guest. + * + * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists. + * @param strPath Absolute path to directory on the guest (guest style path) to create. + * @param enmDirectoryCreateFlags Directory creation flags. + * @param uMode Directory mode to use for creation. + * @param fFollowSymlinks Whether to follow symlinks on the guest or not. + */ +int GuestSessionTask::directoryCreate(const com::Utf8Str &strPath, + DirectoryCreateFlag_T enmDirectoryCreateFlags, uint32_t uMode, bool fFollowSymlinks) +{ + LogFlowFunc(("strPath=%s, fFlags=0x%x, uMode=%RU32, fFollowSymlinks=%RTbool\n", + strPath.c_str(), enmDirectoryCreateFlags, uMode, fFollowSymlinks)); + + GuestFsObjData objData; int rcGuest; + int rc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &rcGuest); + if (RT_SUCCESS(rc)) + { + return VERR_ALREADY_EXISTS; + } + else + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (rcGuest) + { + case VERR_FILE_NOT_FOUND: + case VERR_PATH_NOT_FOUND: + rc = mSession->i_directoryCreate(strPath.c_str(), uMode, enmDirectoryCreateFlags, &rcGuest); + break; + default: + break; + } + break; + } + + default: + break; + } + } + + if (RT_FAILURE(rc)) + { + if (rc == VERR_GSTCTL_GUEST_ERROR) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(rcGuest)); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Error creating directory on the guest: %Rrc"), strPath.c_str(), rc)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main function for copying a file from guest to the host. + * + * @return VBox status code. + * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already. + * @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(ComObjPtr<GuestFile> &srcFile, PRTFILE phDstFile, FileCopyFlag_T fFileCopyFlags, + uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int rc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + rc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Seeking to offset %RU64 failed: %Rrc"), offCopy, rc)); + return rc; + } + } + + BYTE byBuf[_64K]; + while (cbToRead) + { + uint32_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + rc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Reading %RU32 bytes @ %RU64 from guest failed: %Rrc"), cbChunk, cbWrittenTotal, rc)); + break; + } + + rc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Writing %RU32 bytes to file on host failed: %Rrc"), cbRead, rc)); + 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; + + rc = setProgress((ULONG)(cbWrittenTotal / ((uint64_t)cbSize / 100.0))); + if (RT_FAILURE(rc)) + break; + } + + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + return VINF_SUCCESS; + + if (RT_FAILURE(rc)) + return rc; + + /* + * Even if we succeeded until here make sure to check whether we really transfered + * everything. + */ + if ( cbSize > 0 + && cbWrittenTotal == 0) + { + /* If nothing was transfered 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(GuestSession::tr("Writing guest file to host failed: Access denied"))); + rc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Copying guest file to host to failed (%RU64/%RU64 bytes transfered)"), + cbWrittenTotal, cbSize)); + rc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Copies a file from the guest to the host. + * + * @return VBox status code. VINF_NO_CHANGE if file was skipped. + * @param strSource Full path of source file on the guest to copy. + * @param strDest Full destination path and file name (host style) to copy file to. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=0x%x\n", strSource.c_str(), strDest.c_str(), fFileCopyFlags)); + + GuestFileOpenInfo srcOpenInfo; + RT_ZERO(srcOpenInfo); + srcOpenInfo.mFilename = strSource; + srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting; + srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly; + srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> srcFile; + + GuestFsObjData srcObjData; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int rc = mSession->i_fsQueryInfo(strSource, TRUE /* fFollowSymlinks */, srcObjData, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest)); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source file lookup for \"%s\" failed: %Rrc"), + strSource.c_str(), rc)); + break; + } + } + else + { + switch (srcObjData.mType) + { + case FsObjType_File: + break; + + case FsObjType_Symlink: + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source file \"%s\" is a symbolic link"), + strSource.c_str(), rc)); + rc = VERR_IS_A_SYMLINK; + } + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source element \"%s\" is not a file"), strSource.c_str())); + rc = VERR_NOT_A_FILE; + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + rc = mSession->i_fileOpen(srcOpenInfo, srcFile, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest)); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source file \"%s\" could not be opened: %Rrc"), + strSource.c_str(), rc)); + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + char *pszDstFile = NULL; + RTFSOBJINFO dstObjInfo; + RT_ZERO(dstObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(rc)) + { + rc = RTPathQueryInfo(strDest.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"), strDest.c_str())); + rc = VERR_ALREADY_EXISTS; + } + + if (fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC srcModificationTimeTS; + RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime); + if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" has same or newer modification date"), + strDest.c_str())); + fSkip = true; + } + } + } + else + { + if (rc != VERR_FILE_NOT_FOUND) /* Destination file does not exist (yet)? */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file lookup for \"%s\" failed: %Rrc"), + strDest.c_str(), rc)); + } + } + + if (fSkip) + { + int rc2 = srcFile->i_closeFile(&rcGuest); + AssertRC(rc2); + return VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + if (RTFS_IS_FILE(dstObjInfo.Attr.fMode)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"), + strDest.c_str(), rc)); + rc = VERR_ALREADY_EXISTS; + } + else + pszDstFile = RTStrDup(strDest.c_str()); + } + else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode)) + { + /* Build the final file name with destination path (on the host). */ + char szDstPath[RTPATH_MAX]; + RTStrPrintf2(szDstPath, sizeof(szDstPath), "%s", strDest.c_str()); + + if ( !strDest.endsWith("\\") + && !strDest.endsWith("/")) + RTPathAppend(szDstPath, sizeof(szDstPath), "/"); /* IPRT can handle / on all hosts. */ + + RTPathAppend(szDstPath, sizeof(szDstPath), RTPathFilenameEx(strSource.c_str(), mfPathStyle)); + + pszDstFile = RTStrDup(szDstPath); + } + else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode)) + { + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" is a symbolic link"), + strDest.c_str(), rc)); + rc = VERR_IS_A_SYMLINK; + } + else + pszDstFile = RTStrDup(strDest.c_str()); + } + else + { + LogFlowThisFunc(("Object type %RU32 not implemented yet\n", dstObjInfo.Attr.fMode)); + rc = VERR_NOT_IMPLEMENTED; + } + } + else if (rc == VERR_FILE_NOT_FOUND) + pszDstFile = RTStrDup(strDest.c_str()); + + if ( RT_SUCCESS(rc) + || rc == VERR_FILE_NOT_FOUND) + { + if (!pszDstFile) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(GuestSession::tr("No memory to allocate destination file path"))); + rc = VERR_NO_MEMORY; + } + else + { + RTFILE hDstFile; + rc = RTFileOpen(&hDstFile, pszDstFile, + RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */ + if (RT_SUCCESS(rc)) + { + LogFlowThisFunc(("Copying '%s' to '%s' (%RI64 bytes) ...\n", strSource.c_str(), pszDstFile, srcObjData.mObjectSize)); + + rc = fileCopyFromGuestInner(srcFile, &hDstFile, fFileCopyFlags, 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize); + + int rc2 = RTFileClose(hDstFile); + AssertRC(rc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Opening/creating destination file \"%s\" failed: %Rrc"), + pszDstFile, rc)); + } + } + + RTStrFree(pszDstFile); + + int rc2 = srcFile->i_closeFile(&rcGuest); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Main function for copying a file from host to the guest. + * + * @return VBox status code. + * @param hVfsFile The VFS file handle to read from. + * @param dstFile 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(RTVFSFILE hVfsFile, ComObjPtr<GuestFile> &dstFile, FileCopyFlag_T fFileCopyFlags, + uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int rc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + rc = RTVfsFileSeek(hVfsFile, offCopy, RTFILE_SEEK_END, &offActual); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Seeking to offset %RU64 failed: %Rrc"), offCopy, rc)); + return rc; + } + } + + BYTE byBuf[_64K]; + while (cbToRead) + { + size_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + rc = RTVfsFileRead(hVfsFile, byBuf, cbChunk, &cbRead); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Reading %RU32 bytes @ %RU64 from host failed: %Rrc"), cbChunk, cbWrittenTotal, rc)); + break; + } + + rc = dstFile->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Writing %zu bytes to file on guest failed: %Rrc"), cbRead, rc)); + 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; + + rc = setProgress((ULONG)(cbWrittenTotal / ((uint64_t)cbSize / 100.0))); + if (RT_FAILURE(rc)) + break; + } + + if (RT_FAILURE(rc)) + return rc; + + /* + * Even if we succeeded until here make sure to check whether we really transfered + * everything. + */ + if ( cbSize > 0 + && cbWrittenTotal == 0) + { + /* If nothing was transfered 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(GuestSession::tr("Writing to destination file failed: Access denied"))); + rc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Copying to destination failed (%RU64/%RU64 bytes transfered)"), + cbWrittenTotal, cbSize)); + rc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Copies a file from the guest to the host. + * + * @return VBox status code. VINF_NO_CHANGE if file was skipped. + * @param strSource Full path of source file on the host to copy. + * @param strDest Full destination path and file name (guest style) to copy file to. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSource, const Utf8Str &strDest, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDest=%s, fFileCopyFlags=0x%x\n", strSource.c_str(), strDest.c_str(), fFileCopyFlags)); + + Utf8Str strDestFinal = strDest; + + GuestFsObjData dstObjData; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int rc = mSession->i_fsQueryInfo(strDest, TRUE /* fFollowSymlinks */, dstObjData, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + if (rcGuest == VERR_FILE_NOT_FOUND) /* File might not exist on the guest yet. */ + { + rc = VINF_SUCCESS; + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest)); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file lookup for \"%s\" failed: %Rrc"), + strDest.c_str(), rc)); + break; + } + } + else + { + switch (dstObjData.mType) + { + case FsObjType_Directory: + { + /* Build the final file name with destination path (on the host). */ + char szDstPath[RTPATH_MAX]; + RTStrPrintf2(szDstPath, sizeof(szDstPath), "%s", strDest.c_str()); + + if ( !strDest.endsWith("\\") + && !strDest.endsWith("/")) + RTStrCat(szDstPath, sizeof(szDstPath), "/"); + + RTStrCat(szDstPath, sizeof(szDstPath), RTPathFilename(strSource.c_str())); + + strDestFinal = szDstPath; + break; + } + + case FsObjType_File: + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" already exists"), + strDest.c_str(), rc)); + rc = VERR_ALREADY_EXISTS; + } + break; + + case FsObjType_Symlink: + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" is a symbolic link"), + strDest.c_str(), rc)); + rc = VERR_IS_A_SYMLINK; + } + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination element \"%s\" not supported"), strDest.c_str())); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + GuestFileOpenInfo dstOpenInfo; + RT_ZERO(dstOpenInfo); + dstOpenInfo.mFilename = strDestFinal; + 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; + rc = mSession->i_fileOpen(dstOpenInfo, dstFile, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest)); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" could not be opened: %Rrc"), + strDestFinal.c_str(), rc)); + break; + } + } + + if (RT_FAILURE(rc)) + return rc; + + char szSrcReal[RTPATH_MAX]; + + RTFSOBJINFO srcObjInfo; + RT_ZERO(srcObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(rc)) + { + rc = RTPathReal(strSource.c_str(), szSrcReal, sizeof(szSrcReal)); + if (RT_FAILURE(rc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source path lookup for \"%s\" failed: %Rrc"), + strSource.c_str(), rc)); + } + else + { + rc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + if (fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC dstModificationTimeTS; + RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime); + if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" has same or newer modification date"), + strDestFinal.c_str())); + fSkip = true; + } + } + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Source file lookup for \"%s\" failed: %Rrc"), + szSrcReal, rc)); + } + } + } + + if (fSkip) + { + int rc2 = dstFile->i_closeFile(&rcGuest); + AssertRC(rc2); + return VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + RTVFSFILE hSrcFile; + rc = RTVfsFileOpenNormal(szSrcReal, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hSrcFile); /** @todo Use the correct open modes! */ + if (RT_SUCCESS(rc)) + { + LogFlowThisFunc(("Copying '%s' to '%s' (%RI64 bytes) ...\n", + szSrcReal, strDestFinal.c_str(), srcObjInfo.cbObject)); + + rc = fileCopyToGuestInner(hSrcFile, dstFile, fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject); + + int rc2 = RTVfsFileRelease(hSrcFile); + AssertRC(rc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Opening source file \"%s\" failed: %Rrc"), + szSrcReal, rc)); + } + + int rc2 = dstFile->i_closeFile(&rcGuest); + AssertRC(rc2); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * 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 (...) + { + 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 & RTFS_TYPE_MASK; + pEntry->strPath = strFile; + + mVecEntries.push_back(pEntry); + } + catch (...) + { + 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; + + /* If the source is a directory, make sure the path is properly terminated already. */ + if (mSourceSpec.enmType == FsObjType_Directory) + { + if ( !mSrcRootAbs.endsWith("/") + && !mSrcRootAbs.endsWith("\\")) + mSrcRootAbs += "/"; + + if ( !mDstRootAbs.endsWith("/") + && !mDstRootAbs.endsWith("\\")) + mDstRootAbs += "/"; + } + + 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(); +} + +/** + * 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("/") + && !strPathAbs.endsWith("\\")) + strPathAbs += "/"; + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith("/") + && !strPathSub.endsWith("\\")) + strPathSub += "/"; + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering '%s' (sub '%s')\n", strPathAbs.c_str(), strPathSub.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 rcGuest; + int rc = pSession->i_directoryOpen(dirOpenInfo, pDir, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_INVALID_PARAMETER: + break; + + case VERR_GSTCTL_GUEST_ERROR: + break; + + default: + break; + } + + return rc; + } + + if (strPathSub.isNotEmpty()) + { + GuestFsObjData fsObjData; + fsObjData.mType = FsObjType_Directory; + + rc = AddEntryFromGuest(strPathSub, fsObjData); + } + + if (RT_SUCCESS(rc)) + { + ComObjPtr<GuestFsObjInfo> fsObjInfo; + while (RT_SUCCESS(rc = pDir->i_readInternal(fsObjInfo, &rcGuest))) + { + FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */ + HRESULT hr2 = fsObjInfo->COMGETTER(Type)(&enmObjType); + AssertComRC(hr2); + + com::Bstr bstrName; + hr2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam()); + AssertComRC(hr2); + + Utf8Str strEntry = strPathSub + Utf8Str(bstrName); + + LogFlowFunc(("Entry '%s'\n", strEntry.c_str())); + + switch (enmObjType) + { + case FsObjType_Directory: + { + if ( bstrName.equals(".") + || bstrName.equals("..")) + { + break; + } + + if (!(mSourceSpec.Type.Dir.fRecursive)) + break; + + rc = AddDirFromGuest(strPath, strEntry); + break; + } + + case FsObjType_Symlink: + { + if (mSourceSpec.Type.Dir.fFollowSymlinks) + { + /** @todo Symlink handling from guest is not imlemented yet. + * See IGuestSession::symlinkRead(). */ + LogRel2(("Guest Control: Warning: Symlink support on guest side not available, skipping \"%s\"", + strEntry.c_str())); + } + break; + } + + case FsObjType_File: + { + rc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData()); + break; + } + + default: + break; + } + } + + if (rc == VERR_NO_MORE_FILES) /* End of listing reached? */ + rc = VINF_SUCCESS; + } + + int rc2 = pDir->i_closeInternal(&rcGuest); + if (RT_SUCCESS(rc)) + rc = rc2; + + return rc; +} + +/** + * Builds a host file list from a given path (and optional filter). + * + * @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. + */ +int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir) +{ + Utf8Str strPathAbs = strPath; + if ( !strPathAbs.endsWith("/") + && !strPathAbs.endsWith("\\")) + strPathAbs += "/"; + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith("/") + && !strPathSub.endsWith("\\")) + strPathSub += "/"; + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering '%s' (sub '%s')\n", strPathAbs.c_str(), strPathSub.c_str())); + + RTFSOBJINFO objInfo; + int rc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + if (strPathSub.isNotEmpty()) + rc = AddEntryFromHost(strPathSub, &objInfo); + + if (RT_SUCCESS(rc)) + { + RTDIR hDir; + rc = RTDirOpen(&hDir, strPathAbs.c_str()); + if (RT_SUCCESS(rc)) + { + do + { + /* Retrieve the next directory entry. */ + RTDIRENTRYEX Entry; + rc = RTDirReadEx(hDir, &Entry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + break; + } + + Utf8Str strEntry = strPathSub + Utf8Str(Entry.szName); + + LogFlowFunc(("Entry '%s'\n", strEntry.c_str())); + + switch (Entry.Info.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + /* Skip "." and ".." entries. */ + if (RTDirEntryExIsStdDotLink(&Entry)) + break; + + if (!(mSourceSpec.Type.Dir.fRecursive)) + break; + + rc = AddDirFromHost(strPath, strEntry); + break; + } + + case RTFS_TYPE_FILE: + { + rc = AddEntryFromHost(strEntry, &Entry.Info); + break; + } + + case RTFS_TYPE_SYMLINK: + { + if (mSourceSpec.Type.Dir.fFollowSymlinks) + { + Utf8Str strEntryAbs = strPathAbs + Utf8Str(Entry.szName); + + char szPathReal[RTPATH_MAX]; + rc = RTPathReal(strEntryAbs.c_str(), szPathReal, sizeof(szPathReal)); + if (RT_SUCCESS(rc)) + { + rc = RTPathQueryInfo(szPathReal, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Symlink '%s' -> '%s'\n", strEntryAbs.c_str(), szPathReal)); + + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + LogFlowFunc(("Symlink to directory\n")); + rc = AddDirFromHost(strPath, strEntry); + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + { + LogFlowFunc(("Symlink to file\n")); + rc = AddEntryFromHost(strEntry, &objInfo); + } + else + rc = VERR_NOT_SUPPORTED; + } + else + LogFlowFunc(("Unable to query symlink info for '%s', rc=%Rrc\n", szPathReal, rc)); + } + else + { + LogFlowFunc(("Unable to resolve symlink for '%s', rc=%Rrc\n", strPathAbs.c_str(), rc)); + if (rc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */ + rc = VINF_SUCCESS; + } + } + break; + } + + default: + break; + } + + } while (RT_SUCCESS(rc)); + + RTDirClose(hDir); + } + } + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + { + rc = VERR_IS_A_FILE; + } + else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode)) + { + rc = VERR_IS_A_SYMLINK; + } + else + rc = VERR_NOT_SUPPORTED; + } + else + LogFlowFunc(("Unable to query '%s', rc=%Rrc\n", strPathAbs.c_str(), rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS) + : GuestSessionTask(pSession) + , mFlags(uFlags) + , mTimeoutMS(uTimeoutMS) +{ + m_strTaskName = "gctlSesOpen"; +} + +GuestSessionTaskOpen::~GuestSessionTaskOpen(void) +{ + +} + +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 vecSrc, const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyFrm"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void) +{ +} + +HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc) +{ + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hr = pProgress.createObject(); + if (FAILED(hr)) + return hr; + + mProgress = pProgress; + + int rc = 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. + */ + + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + bool fFollowSymlinks; + + if (itSrc->enmType == FsObjType_Directory) + { + /* If the source does not end with a slash, copy over the entire directory + * (and not just its contents). */ + if ( !strSrc.endsWith("/") + && !strSrc.endsWith("\\")) + { + if ( !strDst.endsWith("/") + && !strDst.endsWith("\\")) + strDst += "/"; + + strDst += Utf8StrFmt("%s", RTPathFilenameEx(strSrc.c_str(), mfPathStyle)); + } + + fFollowSymlinks = itSrc->Type.Dir.fFollowSymlinks; + } + else + { + fFollowSymlinks = RT_BOOL(itSrc->Type.File.fCopyFlags & FileCopyFlag_FollowLinks); + } + + LogFlowFunc(("strSrc=%s, strDst=%s, fFollowSymlinks=%RTbool\n", strSrc.c_str(), strDst.c_str(), fFollowSymlinks)); + + GuestFsObjData srcObjData; int rcGuest; + rc = mSession->i_fsQueryInfo(strSrc, fFollowSymlinks, srcObjData, &rcGuest); + if (RT_FAILURE(rc)) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("No such source file/directory: %s"), strSrc.c_str()); + break; + } + + if (srcObjData.mType == FsObjType_Directory) + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a file: %s"), strSrc.c_str()); + rc = VERR_NOT_A_FILE; + break; + } + } + else + { + if (itSrc->enmType != FsObjType_File) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a directory: %s"), strSrc.c_str()); + rc = VERR_NOT_A_DIRECTORY; + break; + } + } + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + rc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(rc)) + { + if (itSrc->enmType == FsObjType_Directory) + { + rc = pFsList->AddDirFromGuest(strSrc); + } + else + rc = pFsList->AddEntryFromGuest(RTPathFilename(strSrc.c_str()), srcObjData); + } + + if (RT_FAILURE(rc)) + { + delete pFsList; + strErrorInfo = Utf8StrFmt(GuestSession::tr("Error adding source '%s' to list: %Rrc"), strSrc.c_str(), rc); + break; + } + + mVecLists.push_back(pFsList); + } + catch (...) + { + rc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + + if (cOperations) /* Use the first element as description (if available). */ + { + Assert(mVecLists.size()); + Assert(mVecLists[0]->mVecEntries.size()); + + Utf8Str strFirstOp = mDest + mVecLists[0]->mVecEntries[0]->strPath; + hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, + Bstr(strFirstOp).raw()); + } + else /* If no operations have been defined, go with an "empty" progress object when will be used for error handling. */ + hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (RT_FAILURE(rc)) + { + Assert(strErrorInfo.isNotEmpty()); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hr, rc)); + return rc; +} + +int GuestSessionTaskCopyFrom::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int rc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + const bool fCopyIntoExisting = pList->mSourceSpec.Type.Dir.fCopyFlags & DirectoryCopyFlag_CopyIntoExisting; + const uint32_t fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + /* Create the root directory. */ + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + rc = RTDirCreate(pList->mDstRootAbs.c_str(), fDirMode, 0 /* fCreate */); + if ( rc == VWRN_ALREADY_EXISTS + && !fCopyIntoExisting) + { + break; + } + } + + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while (itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = pList->mSrcRootAbs; + Utf8Str strDstAbs = pList->mDstRootAbs; + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + strSrcAbs += pEntry->strPath; + strDstAbs += pEntry->strPath; + + if (pList->mSourceSpec.enmPathStyle == PathStyle_DOS) + strDstAbs.findReplace('\\', '/'); + } + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + LogFlowFunc(("Directory '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + { + rc = RTDirCreate(strDstAbs.c_str(), fDirMode, 0 /* fCreate */); + if (rc == VERR_ALREADY_EXISTS) + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination directory \"%s\" already exists"), + strDstAbs.c_str())); + break; + } + + rc = VINF_SUCCESS; + } + + if (RT_FAILURE(rc)) + break; + } + break; + + case RTFS_TYPE_FILE: + LogFlowFunc(("File '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + rc = fileCopyFromGuest(strSrcAbs, strDstAbs, FileCopyFlag_None); + break; + + default: + LogFlowFunc(("Warning: Type %d for '%s' is not supported\n", + pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str())); + break; + } + + if (RT_FAILURE(rc)) + break; + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + ++itEntry; + } + + if (RT_FAILURE(rc)) + break; + + ++itList; + } + + if (RT_SUCCESS(rc)) + rc = setProgressSuccess(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet vecSrc, const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyTo"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void) +{ +} + +HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc) +{ + LogFlowFuncEnter(); + + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hr = pProgress.createObject(); + if (FAILED(hr)) + return hr; + + mProgress = pProgress; + + int rc = 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. + */ + + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + if (itSrc->enmType == FsObjType_Directory) + { + /* If the source does not end with a slash, copy over the entire directory + * (and not just its contents). */ + if ( !strSrc.endsWith("/") + && !strSrc.endsWith("\\")) + { + if ( !strDst.endsWith("/") + && !strDst.endsWith("\\")) + strDst += "/"; + + strDst += Utf8StrFmt("%s", RTPathFilenameEx(strSrc.c_str(), mfPathStyle)); + } + } + + LogFlowFunc(("strSrc=%s, strDst=%s\n", strSrc.c_str(), strDst.c_str())); + + RTFSOBJINFO srcFsObjInfo; + rc = RTPathQueryInfo(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("No such source file/directory: %s"), strSrc.c_str()); + break; + } + + if (RTFS_IS_DIRECTORY(srcFsObjInfo.Attr.fMode)) + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a file: %s"), strSrc.c_str()); + rc = VERR_NOT_A_FILE; + break; + } + } + else + { + if (itSrc->enmType == FsObjType_Directory) + { + strErrorInfo = Utf8StrFmt(GuestSession::tr("Source is not a directory: %s"), strSrc.c_str()); + rc = VERR_NOT_A_DIRECTORY; + break; + } + } + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + rc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(rc)) + { + if (itSrc->enmType == FsObjType_Directory) + { + rc = pFsList->AddDirFromHost(strSrc); + } + else + rc = pFsList->AddEntryFromHost(RTPathFilename(strSrc.c_str()), &srcFsObjInfo); + } + + if (RT_FAILURE(rc)) + { + delete pFsList; + strErrorInfo = Utf8StrFmt(GuestSession::tr("Error adding source '%s' to list: %Rrc"), strSrc.c_str(), rc); + break; + } + + mVecLists.push_back(pFsList); + } + catch (...) + { + rc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + + if (cOperations) /* Use the first element as description (if available). */ + { + Assert(mVecLists.size()); + Assert(mVecLists[0]->mVecEntries.size()); + + Utf8Str strFirstOp = mDest + mVecLists[0]->mVecEntries[0]->strPath; + + hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, + Bstr(strFirstOp).raw()); + } + else /* If no operations have been defined, go with an "empty" progress object when will be used for error handling. */ + hr = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (RT_FAILURE(rc)) + { + Assert(strErrorInfo.isNotEmpty()); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hr, rc)); + return hr; +} + +int GuestSessionTaskCopyTo::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int rc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + bool fCopyIntoExisting = false; + bool fFollowSymlinks = false; + uint32_t fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + /* Create the root directory. */ + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + fCopyIntoExisting = pList->mSourceSpec.Type.Dir.fCopyFlags & DirectoryCopyFlag_CopyIntoExisting; + fFollowSymlinks = pList->mSourceSpec.Type.Dir.fFollowSymlinks; + + rc = directoryCreate(pList->mDstRootAbs.c_str(), DirectoryCreateFlag_None, fDirMode, + pList->mSourceSpec.Type.Dir.fFollowSymlinks); + if ( rc == VWRN_ALREADY_EXISTS + && !fCopyIntoExisting) + { + break; + } + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.Type.File.fCopyFlags & FileCopyFlag_NoReplace); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.Type.File.fCopyFlags & FileCopyFlag_FollowLinks); + } + else + AssertFailed(); + + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while (itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = pList->mSrcRootAbs; + Utf8Str strDstAbs = pList->mDstRootAbs; + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + strSrcAbs += pEntry->strPath; + strDstAbs += pEntry->strPath; + } + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + LogFlowFunc(("Directory '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + { + rc = directoryCreate(strDstAbs.c_str(), DirectoryCreateFlag_None, fDirMode, fFollowSymlinks); + if (RT_FAILURE(rc)) + { + if (rc == VERR_ALREADY_EXISTS) + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination directory '%s' already exists"), + strDstAbs.c_str())); + } + else /* Copy into destination directory. */ + rc = VINF_SUCCESS; + } + } + } + break; + + case RTFS_TYPE_FILE: + LogFlowFunc(("File '%s': %s -> %s\n", pEntry->strPath.c_str(), strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + rc = fileCopyToGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.Type.File.fCopyFlags); + break; + + default: + LogFlowFunc(("Warning: Type %d for '%s' is not supported\n", + pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str())); + break; + } + + if (RT_FAILURE(rc)) + break; + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + ++itEntry; + } + + if (RT_FAILURE(rc)) + break; + + ++itList; + } + + if (RT_SUCCESS(rc)) + rc = setProgressSuccess(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +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) +{ + +} + +int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource) +{ + int rc = VINF_SUCCESS; + + 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 rc; +} + +int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso, + Utf8Str const &strFileSource, const Utf8Str &strFileDest, + bool fOptional) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertReturn(hVfsIso != NIL_RTVFS, VERR_INVALID_POINTER); + + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + int rc = RTVfsFileOpen(hVfsIso, strFileSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ, &hVfsFile); + if (RT_SUCCESS(rc)) + { + uint64_t cbSrcSize = 0; + rc = RTVfsFileGetSize(hVfsFile, &cbSrcSize); + if (RT_SUCCESS(rc)) + { + LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n", + strFileSource.c_str(), strFileDest.c_str())); + + GuestFileOpenInfo dstOpenInfo; + RT_ZERO(dstOpenInfo); + dstOpenInfo.mFilename = strFileDest; + dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace; + dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly; + dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> dstFile; int rcGuest; + rc = mSession->i_fileOpen(dstOpenInfo, dstFile, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(rcGuest)); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Destination file \"%s\" could not be opened: %Rrc"), + strFileDest.c_str(), rc)); + break; + } + } + else + { + rc = fileCopyToGuestInner(hVfsFile, dstFile, FileCopyFlag_None, 0 /*cbOffset*/, cbSrcSize); + + int rc2 = dstFile->i_closeFile(&rcGuest); + AssertRC(rc2); + } + } + + RTVfsFileRelease(hVfsFile); + if (RT_FAILURE(rc)) + return rc; + } + else + { + if (fOptional) + return VINF_SUCCESS; + + return rc; + } + + return rc; +} + +int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + LogRel(("Running %s ...\n", procInfo.mName.c_str())); + + GuestProcessTool procTool; + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = procTool.init(pSession, procInfo, false /* Async */, &rcGuest); + if (RT_SUCCESS(vrc)) + { + if (RT_SUCCESS(rcGuest)) + vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &rcGuest); + 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(GuestSession::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, GuestProcess::i_guestErrorToString(rcGuest)); + break; + + case VERR_INVALID_STATE: /** @todo Special guest control rc needed! */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Update file \"%s\" reported invalid running state"), + procInfo.mExecutable.c_str())); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Error while running update file \"%s\" on guest: %Rrc"), + procInfo.mExecutable.c_str(), vrc)); + break; + } + } + + return vrc; +} + +int GuestSessionTaskUpdateAdditions::Run(void) +{ + LogFlowThisFuncEnter(); + + ComObjPtr<GuestSession> pSession = mSession; + Assert(!pSession.isNull()); + + AutoCaller autoCaller(pSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int rc = setProgress(10); + if (RT_FAILURE(rc)) + return rc; + + HRESULT hr = 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(hr = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + && ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000) + { + rc = VERR_TIMEOUT; + break; + } + + RTThreadSleep(100); /* Wait a bit. */ + } + + if (FAILED(hr)) rc = VERR_TIMEOUT; + if (rc == VERR_TIMEOUT) + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::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(hr = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + || ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if (addsRunLevel == AdditionsRunLevelType_System) + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Guest Additions are installed but not fully loaded yet, aborting automatic update"))); + else + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Guest Additions not installed or ready, aborting automatic update"))); + rc = VERR_NOT_SUPPORTED; + } +#endif + + if (RT_SUCCESS(rc)) + { + /* + * Determine if we are able to update automatically. This only works + * if there are recent Guest Additions installed already. + */ + Utf8Str strAddsVer; + rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(rc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0) + { + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"), + strAddsVer.c_str())); + rc = VERR_NOT_SUPPORTED; + } + } + + Utf8Str strOSVer; + eOSType osType = eOSType_Unknown; + if (RT_SUCCESS(rc)) + { + /* + * Determine guest OS type and the required installer image. + */ + Utf8Str strOSType; + rc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType); + if (RT_SUCCESS(rc)) + { + if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive) + || strOSType.contains("Windows", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Windows; + + /* + * Determine guest OS version. + */ + rc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer); + if (RT_FAILURE(rc)) + { + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Unable to detected guest OS version, please update manually"))); + rc = 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(rc) + && 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)) + { + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually"))); + rc = VERR_NOT_SUPPORTED; + } + } + } + else + { + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("%s (%s) not supported for automatic updating, please update manually"), + strOSType.c_str(), strOSVer.c_str())); + rc = VERR_NOT_SUPPORTED; + } + } + else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Solaris; + } + else /* Everything else hopefully means Linux :-). */ + osType = eOSType_Linux; + +#if 1 /* Only Windows is supported (and tested) at the moment. */ + if ( RT_SUCCESS(rc) + && osType != eOSType_Windows) + { + hr = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(GuestSession::tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"), + strOSType.c_str())); + rc = VERR_NOT_SUPPORTED; + } +#endif + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Try to open the .ISO file to extract all needed files. + */ + RTVFSFILE hVfsFileIso; + rc = RTVfsFileOpenNormal(mSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &hVfsFileIso); + if (RT_SUCCESS(rc)) + { + RTVFS hVfsIso; + rc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, NULL); + if (RT_FAILURE(rc)) + { + hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"), + mSource.c_str(), rc)); + } + else + { + /* Set default installation directories. */ + Utf8Str strUpdateDir = "/tmp/"; + if (osType == eOSType_Windows) + strUpdateDir = "C:\\Temp\\"; + + rc = setProgress(5); + + /* Try looking up the Guest Additions installation directory. */ + if (RT_SUCCESS(rc)) + { + /* 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; + rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(rc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0) + { + fUseInstallDir = true; + } + + if (fUseInstallDir) + { + if (RT_SUCCESS(rc)) + rc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir); + if (RT_SUCCESS(rc)) + { + if (osType == eOSType_Windows) + { + strUpdateDir.findReplace('/', '\\'); + strUpdateDir.append("\\Update\\"); + } + else + strUpdateDir.append("/update/"); + } + } + } + + if (RT_SUCCESS(rc)) + LogRel(("Guest Additions update directory is: %s\n", + strUpdateDir.c_str())); + + /* Create the installation directory. */ + int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; + rc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &rcGuest); + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_GSTCTL_GUEST_ERROR: + hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(rcGuest)); + break; + + default: + hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Error creating installation directory \"%s\" on the guest: %Rrc"), + strUpdateDir.c_str(), rc)); + break; + } + } + if (RT_SUCCESS(rc)) + rc = setProgress(10); + + if (RT_SUCCESS(rc)) + { + /* 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; + rc = RTVfsQueryPathInfo(hVfsIso, s_aCertFiles[i].pszIso, &ObjInfo, RTFSOBJATTRADD_NOTHING, + RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + 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"; + 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"; + 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; + 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. */ + rc = addProcessArguments(siInstaller.mArguments, mArguments); + AssertRC(rc); + /* 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: + /** @todo Add Linux support. */ + break; + case eOSType_Solaris: + /** @todo Add Solaris support. */ + break; + default: + AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType)); + break; + } + } + + if (RT_SUCCESS(rc)) + { + /* 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; + rc = copyFileToGuest(pSession, hVfsIso, itFiles->strSource, itFiles->strDest, fOptional); + if (RT_FAILURE(rc)) + { + hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"), + itFiles->strSource.c_str(), itFiles->strDest.c_str(), rc)); + break; + } + } + + rc = setProgress(uOffset); + if (RT_FAILURE(rc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + /* Done copying, close .ISO file. */ + RTVfsRelease(hVfsIso); + + if (RT_SUCCESS(rc)) + { + /* 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) + { + rc = runFileOnGuest(pSession, itFiles->mProcInfo); + if (RT_FAILURE(rc)) + break; + } + + rc = setProgress(uOffset); + if (RT_FAILURE(rc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + if (RT_SUCCESS(rc)) + { + LogRel(("Automatic update of Guest Additions succeeded\n")); + rc = setProgressSuccess(); + } + } + + RTVfsFileRelease(hVfsFileIso); + } + } + + if (RT_FAILURE(rc)) + { + if (rc == VERR_CANCELLED) + { + LogRel(("Automatic update of Guest Additions was canceled\n")); + + hr = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(GuestSession::tr("Installation was canceled"))); + } + else + { + Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", rc); + if (!mProgress.isNull()) /* Progress object is optional. */ + { + 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(), hr)); + } + + LogRel(("Please install Guest Additions manually\n")); + } + + /** @todo Clean up copied / left over installation files. */ + + LogFlowFuncLeaveRC(rc); + return rc; +} + diff --git a/src/VBox/Main/src-client/HGCM.cpp b/src/VBox/Main/src-client/HGCM.cpp new file mode 100644 index 00000000..da2342e1 --- /dev/null +++ b/src/VBox/Main/src-client/HGCM.cpp @@ -0,0 +1,2998 @@ +/* $Id: HGCM.cpp $ */ +/** @file + * HGCM (Host-Guest Communication Manager) + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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/sup.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. */ +}; + +/** 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; + + uint32_t m_cClients; + uint32_t m_cClientsAllocated; + + uint32_t *m_paClientIds; + +#ifdef VBOX_WITH_CRHGSMI + uint32_t m_cHandleAcquires; +#endif + + HGCMSVCEXTHANDLE m_hExtension; + + PUVM m_pUVM; + PPDMIHGCMPORT m_pHgcmPort; + + /** @name Statistics + * @{ */ + STAMPROFILE m_StatHandleMsg; + /** @} */ + + int loadServiceDLL(void); + void unloadServiceDLL(void); + + /* + * Main HGCM thread methods. + */ + int instanceCreate(const char *pszServiceLibrary, const char *pszServiceName, PUVM pUVM, PPDMIHGCMPORT pHgcmPort); + void instanceDestroy(void); + + int saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM); + int loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, uint32_t uVersion); + + HGCMService(); + ~HGCMService() {}; + + static DECLCALLBACK(int) svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc); + static DECLCALLBACK(void) svcHlpDisconnectClient(void *pvInstance, uint32_t u32ClientId); + 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, 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); + static int LoadState(PSSMHANDLE pSSM, uint32_t uVersion); + + int CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring); + int DisconnectClient(uint32_t u32ClientId, bool fFromService); + + int HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms); + static void BroadcastNotify(HGCMNOTIFYEVENT enmEvent); + void Notify(HGCMNOTIFYEVENT enmEvent); + +#ifdef VBOX_WITH_CRHGSMI + int HandleAcquired(); + int HandleReleased(); + int HostFastCallAsync(uint32_t u32Function, VBOXHGCMSVCPARM *pParm, PHGCMHOSTFASTCALLCB pfnCompletion, + void *pvCompletion); +#endif + + 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, + 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) + : HGCMObject(HGCMOBJ_CLIENT) + , pService(NULL) + , pvData(NULL) + , fRequestor(a_fRequestor) + {} + ~HGCMClient(); + + int Init(HGCMService *pSvc); + + /** 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; + + 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_cClients (0), + m_cClientsAllocated (0), + m_paClientIds (NULL), +#ifdef VBOX_WITH_CRHGSMI + m_cHandleAcquires (0), +#endif + m_hExtension (NULL), + m_pUVM (NULL), + m_pHgcmPort (NULL) +{ + 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 rc; + + if (RTPathHasPath(m_pszSvcLibrary)) + rc = SUPR3HardenedLdrLoadPlugIn(m_pszSvcLibrary, &m_hLdrMod, &ErrInfo.Core); + else + rc = SUPR3HardenedLdrLoadAppPriv(m_pszSvcLibrary, &m_hLdrMod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core); + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("successfully loaded the library.\n")); + + m_pfnLoad = NULL; + + rc = RTLdrGetSymbol(m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad); + + if (RT_FAILURE(rc) || !m_pfnLoad) + { + Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, rc = %d, m_pfnLoad = %p\n", + VBOX_HGCM_SVCLOAD_NAME, rc, m_pfnLoad)); + + if (RT_SUCCESS(rc)) + { + /* m_pfnLoad was NULL */ + rc = VERR_SYMBOL_NOT_FOUND; + } + } + + if (RT_SUCCESS(rc)) + { + RT_ZERO(m_fntable); + + m_fntable.cbSize = sizeof(m_fntable); + m_fntable.u32Version = VBOX_HGCM_SVC_VERSION; + m_fntable.pHelpers = &m_svcHelpers; + + rc = m_pfnLoad(&m_fntable); + + LogFlowFunc(("m_pfnLoad rc = %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + if ( m_fntable.pfnUnload == NULL + || m_fntable.pfnConnect == NULL + || m_fntable.pfnDisconnect == NULL + || m_fntable.pfnCall == NULL + ) + { + Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n")); + + rc = VERR_INVALID_PARAMETER; + + if (m_fntable.pfnUnload) + { + m_fntable.pfnUnload(m_fntable.pvService); + } + } + } + } + } + else + { + LogRel(("HGCM: Failed to load the service library: [%s], rc = %Rrc - %s. The service will be not available.\n", + m_pszSvcLibrary, rc, ErrInfo.Core.pszMsg)); + m_hLdrMod = NIL_RTLDRMOD; + } + + if (RT_FAILURE(rc)) + { + unloadServiceDLL(); + } + + return rc; +} + +/** 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 */ +#ifdef VBOX_WITH_CRHGSMI +# define SVC_MSG_HOSTFASTCALLASYNC (21) /* pfnHostCall */ +#endif + +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; +}; + +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() {} + + HGCMMsgCall(HGCMThread *pThread) + { + InitializeCore(SVC_MSG_GUESTCALL, pThread); + Initialize(); + } + ~HGCMMsgCall() { Log(("~HGCMMsgCall %p\n", this)); } + + /* 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; + 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; +}; + +#ifdef VBOX_WITH_CRHGSMI +class HGCMMsgHostFastCallAsyncSvc: public HGCMMsgCore +{ + public: + /* function number */ + uint32_t u32Function; + /* parameter */ + VBOXHGCMSVCPARM Param; + /* completion info */ + PHGCMHOSTFASTCALLCB pfnCompletion; + void *pvCompletion; +}; +#endif + +static HGCMMsgCore *hgcmMessageAllocSvc(uint32_t u32MsgId) +{ + switch (u32MsgId) + { +#ifdef VBOX_WITH_CRHGSMI + case SVC_MSG_HOSTFASTCALLASYNC: return new HGCMMsgHostFastCallAsyncSvc(); +#endif + 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 rc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(rc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", rc)); + break; + } + + STAM_REL_PROFILE_START(&pSvc->m_StatHandleMsg, a); + + /* Cache required information to avoid unnecessary pMsgCore access. */ + uint32_t u32MsgId = pMsgCore->MsgId(); + + switch (u32MsgId) + { +#ifdef VBOX_WITH_CRHGSMI + case SVC_MSG_HOSTFASTCALLASYNC: + { + HGCMMsgHostFastCallAsyncSvc *pMsg = (HGCMMsgHostFastCallAsyncSvc *)pMsgCore; + + LogFlowFunc(("SVC_MSG_HOSTFASTCALLASYNC u32Function = %d, pParm = %p\n", pMsg->u32Function, &pMsg->Param)); + + rc = pSvc->m_fntable.pfnHostCall(pSvc->m_fntable.pvService, pMsg->u32Function, 1, &pMsg->Param); + } break; +#endif + case SVC_MSG_LOAD: + { + LogFlowFunc(("SVC_MSG_LOAD\n")); + rc = 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 *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + rc = pSvc->m_fntable.pfnConnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), + pMsg->fRequestor, pMsg->fRestoring); + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_DISCONNECT: + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore; + + LogFlowFunc(("SVC_MSG_DISCONNECT u32ClientId = %d\n", pMsg->u32ClientId)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + rc = pSvc->m_fntable.pfnDisconnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient)); + + hgcmObjDereference(pClient); + } + else + { + rc = 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 *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + 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 + { + rc = 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 *)hgcmObjReference(pMsg->idClient, HGCMOBJ_CLIENT); + + if (pClient) + { + pSvc->m_fntable.pfnCancelled(pSvc->m_fntable.pvService, pMsg->idClient, HGCM_CLIENT_DATA(pSvc, pClient)); + + hgcmObjDereference(pClient); + } + else + { + rc = 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)); + + rc = 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 *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + /* fRequestor: Restored by the message sender already. */ + bool fHaveClientState = pSvc->m_fntable.pfnLoadState != NULL; + if (pMsg->uVersion > HGCM_SAVED_STATE_VERSION_V2) + rc = SSMR3GetBool(pMsg->pSSM, &fHaveClientState); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc) ) + { + if (pSvc->m_fntable.pfnLoadState) + rc = pSvc->m_fntable.pfnLoadState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, + fHaveClientState ? pMsg->uVersion : 0); + else + AssertLogRelStmt(!fHaveClientState, rc = VERR_INTERNAL_ERROR_5); + } + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_SAVESTATE: + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore; + + LogFlowFunc(("SVC_MSG_SAVESTATE\n")); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + rc = VINF_SUCCESS; + + if (pClient) + { + SSMR3PutU32(pMsg->pSSM, pClient->fRequestor); /* Quicker to save this here than in the message sender. */ + rc = SSMR3PutBool(pMsg->pSSM, pSvc->m_fntable.pfnSaveState != NULL); + if (RT_SUCCESS(rc) && pSvc->m_fntable.pfnSaveState) + { + g_fSaveState = true; + rc = pSvc->m_fntable.pfnSaveState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM); + g_fSaveState = false; + } + + hgcmObjDereference(pClient); + } + else + { + rc = 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) + { + rc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + rc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, pMsg->pfnExtension, + pMsg->pvExtension); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + if (RT_SUCCESS(rc)) + { + 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) + { + rc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + rc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, NULL, NULL); + } + else + { + rc = 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)); + rc = 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, rc); + } + 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(void) HGCMService::svcHlpDisconnectClient(void *pvInstance, uint32_t u32ClientId) +{ + HGCMService *pService = static_cast <HGCMService *> (pvInstance); + + if (pService) + { + pService->DisconnectClient(u32ClientId, true); + } +} + +/** + * @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); + + return STAMR3RegisterVU(pService->m_pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, va); +} + +/** + * @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 STAMR3DeregisterV(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); + + return DBGFR3InfoRegisterExternal(pService->m_pUVM, pszName, pszDesc, pfnHandler, pvUser); +} + +/** + * @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 DBGFR3InfoDeregisterExternal(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, 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 rc = hgcmThreadCreate(&m_pThread, szThreadName, hgcmServiceThread, this, pszServiceName, pUVM); + + if (RT_SUCCESS(rc)) + { + 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; + + rc = VERR_NO_MEMORY; + } + else + { + m_pUVM = pUVM; + m_pHgcmPort = pHgcmPort; + + /* Register statistics: */ + STAMR3RegisterFU(pUVM, &m_StatHandleMsg, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + "Message handling", "/HGCM/%s/Msg", pszServiceName); + + /* 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; + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcLoad *pMsg = (HGCMMsgSvcLoad *)pCoreMsg; + + pMsg->pUVM = pUVM; + + rc = hgcmMsgSend(pMsg); + } + } + } + + if (RT_FAILURE(rc)) + { + instanceDestroy(); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +void HGCMService::instanceDestroy(void) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pMsg; + int rc = hgcmMsgAlloc(m_pThread, &pMsg, SVC_MSG_UNLOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + hgcmThreadWait(m_pThread); + } + + if (m_pszSvcName && m_pUVM) + STAMR3DeregisterF(m_pUVM, "/HGCM/%s/*", m_pszSvcName); + m_pUVM = NULL; + m_pHgcmPort = NULL; + + RTStrFree(m_pszSvcLibrary); + m_pszSvcLibrary = NULL; + + RTStrFree(m_pszSvcName); + m_pszSvcName = NULL; +} + +int HGCMService::saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_SAVESTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + pMsg->pSSM = pSSM; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMService::loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, uint32_t uVersion) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOADSTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->pSSM = pSSM; + pMsg->uVersion = uVersion; + pMsg->u32ClientId = u32ClientId; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + + +/** 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 pHgcmPort The VMMDev HGCM port interface. + * + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadService(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, 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 rc = HGCMService::ResolveService(&pSvc, pszServiceName); + + if (RT_SUCCESS(rc)) + { + /* The service is already loaded. */ + pSvc->ReleaseService(); + rc = VERR_HGCM_SERVICE_EXISTS; + } + else + { + /* Create the new service. */ + pSvc = new (std::nothrow) HGCMService(); + + if (!pSvc) + { + rc = VERR_NO_MEMORY; + } + else + { + /* Load the library and call the initialization entry point. */ + rc = pSvc->instanceCreate(pszServiceLibrary, pszServiceName, pUVM, pHgcmPort); + + if (RT_SUCCESS(rc)) + { + /* 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(("rc = %Rrc\n", rc)); + return rc; +} + +/** 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) + { + LogFlowFunc(("handle %d\n", pSvc->m_paClientIds[0])); + pSvc->DisconnectClient(pSvc->m_paClientIds[0], false); + } + +#ifdef VBOX_WITH_CRHGSMI + /** @todo could this actually happen that the service is destroyed on ReleaseService? */ + HGCMService *pNextSvc = pSvc->m_pSvcNext; + while (pSvc->m_cHandleAcquires) + { + pSvc->HandleReleased(); + pSvc->ReleaseService(); + } + pSvc = pNextSvc; +#else + pSvc = pSvc->m_pSvcNext; +#endif + } + + g_fResetting = false; +} + +/** The method saves the HGCM state. + * + * @param pSSM The saved state context. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::SaveState(PSSMHANDLE pSSM) +{ + /* Save the current handle count and restore afterwards to avoid client id conflicts. */ + int rc = SSMR3PutU32(pSSM, hgcmObjQueryHandleCount()); + AssertRCReturn(rc, rc); + + LogFlowFunc(("%d services to be saved:\n", sm_cServices)); + + /* Save number of services. */ + rc = SSMR3PutU32(pSSM, sm_cServices); + AssertRCReturn(rc, rc); + + /* Save every service. */ + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + LogFlowFunc(("Saving service [%s]\n", pSvc->m_pszSvcName)); + + /* Save the length of the service name. */ + rc = SSMR3PutU32(pSSM, (uint32_t) strlen(pSvc->m_pszSvcName) + 1); + AssertRCReturn(rc, rc); + + /* Save the name of the service. */ + rc = SSMR3PutStrZ(pSSM, pSvc->m_pszSvcName); + AssertRCReturn(rc, rc); + + /* Save the number of clients. */ + rc = SSMR3PutU32(pSSM, pSvc->m_cClients); + AssertRCReturn(rc, rc); + + /* 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.) */ + rc = SSMR3PutU32(pSSM, u32ClientId); + AssertRCReturn(rc, rc); + + /* Call the service, so the operation is executed by the service thread. */ + rc = pSvc->saveClientState(u32ClientId, pSSM); + AssertRCReturn(rc, rc); + } + + pSvc = pSvc->m_pSvcNext; + } + + return VINF_SUCCESS; +} + +/** The method loads saved HGCM state. + * + * @param pSSM The saved state handle. + * @param uVersion The state version being loaded. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadState(PSSMHANDLE pSSM, uint32_t uVersion) +{ + /* Restore handle count to avoid client id conflicts. */ + uint32_t u32; + + int rc = SSMR3GetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + + hgcmObjSetHandleCount(u32); + + /* Get the number of services. */ + uint32_t cServices; + + rc = SSMR3GetU32(pSSM, &cServices); + AssertRCReturn(rc, rc); + + LogFlowFunc(("%d services to be restored:\n", cServices)); + + while (cServices--) + { + /* Get the length of the service name. */ + rc = SSMR3GetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + AssertReturn(u32 <= VBOX_HGCM_SVC_NAME_MAX_BYTES, VERR_SSM_UNEXPECTED_DATA); + + /* Get the service name. */ + char szServiceName[VBOX_HGCM_SVC_NAME_MAX_BYTES]; + rc = SSMR3GetStrZ(pSSM, szServiceName, u32); + AssertRCReturn(rc, rc); + + LogRel(("HGCM: Restoring [%s]\n", szServiceName)); + + /* Resolve the service instance. */ + HGCMService *pSvc; + rc = ResolveService(&pSvc, szServiceName); + AssertLogRelMsgReturn(pSvc, ("rc=%Rrc, %s\n", rc, szServiceName), VERR_SSM_UNEXPECTED_DATA); + + /* Get the number of clients. */ + uint32_t cClients; + rc = SSMR3GetU32(pSSM, &cClients); + if (RT_FAILURE(rc)) + { + pSvc->ReleaseService(); + AssertFailed(); + return rc; + } + + 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; + rc = SSMR3GetU32(pSSM, &u32ClientId); + uint32_t fRequestor = VMMDEV_REQUESTOR_LEGACY; + if (RT_SUCCESS(rc) && uVersion > HGCM_SAVED_STATE_VERSION_V2) + rc = SSMR3GetU32(pSSM, &fRequestor); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + + /* Connect the client. */ + rc = pSvc->CreateAndConnectClient(NULL, u32ClientId, fRequestor, true /*fRestoring*/); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + + /* Call the service, so the operation is executed by the service thread. */ + rc = pSvc->loadClientState(u32ClientId, pSSM, uVersion); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + } + + 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)); + + /* Allocate a client information structure. */ + HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor); + + 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 rc = pClient->Init(this); + + if (RT_SUCCESS(rc)) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg; + + pMsg->u32ClientId = handle; + pMsg->fRequestor = fRequestor; + pMsg->fRestoring = fRestoring; + + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + { + /* 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 + { + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + + m_paClientIds[m_cClients] = handle; + m_cClients++; + } + } + } + + if (RT_FAILURE(rc)) + { + hgcmObjDeleteHandle(handle); + } + else + { + if (pu32ClientIdOut != NULL) + { + *pu32ClientIdOut = handle; + } + + ReferenceService(); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* Disconnect the client from the service and delete the client handle. + * + * @param u32ClientId The handle of the client. + * @return VBox rc. + */ +int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("client id = %d, fFromService = %d\n", u32ClientId, fFromService)); + + if (!fFromService) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_DISCONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + + rc = 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, rc)); + } + } + + /* Remove the client id from the array in any case, rc does not matter. */ + uint32_t i; + + for (i = 0; i < m_cClients; i++) + { + if (m_paClientIds[i] == u32ClientId) + { + 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); + + /* The service must be released. */ + ReleaseService(); + + break; + } + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMService::RegisterExtension(HGCMSVCEXTHANDLE handle, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) +{ + LogFlowFunc(("%s\n", handle->pszServiceName)); + + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_REGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pCoreMsg; + + pMsg->handle = handle; + pMsg->pfnExtension = pfnExtension; + pMsg->pvExtension = pvExtension; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +void HGCMService::UnregisterExtension(HGCMSVCEXTHANDLE handle) +{ + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_UNREGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); +} + +/** 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 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, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + LogFlow(("MAIN::HGCMService::GuestCall\n")); + + int rc; + HGCMMsgCall *pMsg = new (std::nothrow) HGCMMsgCall(m_pThread); + if (pMsg) + { + pMsg->Reference(); /** @todo starts out with zero references. */ + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + pMsg->tsArrival = tsArrival; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + else + { + Log(("MAIN::HGCMService::GuestCall: Message allocation failed\n")); + rc = VERR_NO_MEMORY; + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** 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 rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTCALL, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pCoreMsg; + + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** 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 rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_NOTIFY, hgcmMessageAllocSvc); + if (RT_SUCCESS(rc)) + { + HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pCoreMsg; + pMsg->enmEvent = enmEvent; + + rc = hgcmMsgPost(pMsg, NULL); + AssertRC(rc); + } + } +} + +#ifdef VBOX_WITH_CRHGSMI + +static DECLCALLBACK(int) hgcmMsgFastCallCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore) +{ + /* Call the VMMDev port interface to issue IRQ notification. */ + LogFlow(("MAIN::hgcmMsgFastCallCompletionCallback: message %p\n", pMsgCore)); + + HGCMMsgHostFastCallAsyncSvc *pMsg = (HGCMMsgHostFastCallAsyncSvc *)pMsgCore; + if (pMsg->pfnCompletion) + pMsg->pfnCompletion(result, pMsg->u32Function, &pMsg->Param, pMsg->pvCompletion); + return VINF_SUCCESS; +} + +int HGCMService::HandleAcquired() +{ + ++m_cHandleAcquires; + return VINF_SUCCESS; +} + +int HGCMService::HandleReleased() +{ + Assert(m_cHandleAcquires); + if (m_cHandleAcquires) + { + --m_cHandleAcquires; + return VINF_SUCCESS; + } + return VERR_INVALID_STATE; +} + +int HGCMService::HostFastCallAsync(uint32_t u32Function, VBOXHGCMSVCPARM *pParm, PHGCMHOSTFASTCALLCB pfnCompletion, + void *pvCompletion) +{ + LogFlowFunc(("%s u32Function = %d, pParm = %p\n", + m_pszSvcName, u32Function, pParm)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTFASTCALLASYNC, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgHostFastCallAsyncSvc *pMsg = (HGCMMsgHostFastCallAsyncSvc *)pCoreMsg; + + pMsg->u32Function = u32Function; + pMsg->Param = *pParm; + pMsg->pfnCompletion = pfnCompletion; + pMsg->pvCompletion = pvCompletion; + + rc = hgcmMsgPost(pMsg, hgcmMsgFastCallCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +#endif /* VBOX_WITH_CRHGSMI */ + +/* + * 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). */ +#ifdef VBOX_WITH_CRHGSMI +# define HGCM_MSG_SVCAQUIRE (30) /**< Acquire a service handle (for fast host calls) */ +# define HGCM_MSG_SVCRELEASE (31) /**< Release a service */ +#endif + +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 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 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; +}; + +#ifdef VBOX_WITH_CRHGSMI +class HGCMMsgMainSvcAcquire: public HGCMMsgCore +{ + public: + /* Which service to call. */ + const char *pszServiceName; + /* Returned service. */ + HGCMService *pService; +}; + +class HGCMMsgMainSvcRelease: public HGCMMsgCore +{ + public: + /* Svc . */ + HGCMService *pService; +}; +#endif + + +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(); +#ifdef VBOX_WITH_CRHGSMI + case HGCM_MSG_SVCAQUIRE: return new HGCMMsgMainSvcAcquire(); + case HGCM_MSG_SVCRELEASE: return new HGCMMsgMainSvcRelease(); +#endif + + 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 rc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(rc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", rc)); + 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; + rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(rc)) + { + /* Call the service instance method. */ + rc = 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 *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (!pClient) + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + break; + } + + /* The service the client belongs to. */ + HGCMService *pService = pClient->pService; + + /* Call the service instance to disconnect the client. */ + rc = pService->DisconnectClient(pMsg->u32ClientId, false); + + 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)); + + rc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName, pMsg->pUVM, 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; + rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(rc)) + { + rc = 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; + +#ifdef VBOX_WITH_CRHGSMI + case HGCM_MSG_SVCAQUIRE: + { + HGCMMsgMainSvcAcquire *pMsg = (HGCMMsgMainSvcAcquire *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_SVCAQUIRE pszServiceName %s\n", pMsg->pszServiceName)); + + /* Resolve the service name to the pointer to service instance. */ + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + if (RT_SUCCESS(rc)) + { + rc = pService->HandleAcquired(); + if (RT_SUCCESS(rc)) + { + pMsg->pService = pService; + } + else + { + pService->ReleaseService(); + } + } + } break; + + case HGCM_MSG_SVCRELEASE: + { + HGCMMsgMainSvcRelease *pMsg = (HGCMMsgMainSvcRelease *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_SVCARELEASE pService %p\n", pMsg->pService)); + + /* Resolve the service name to the pointer to service instance. */ + + rc = pMsg->pService->HandleReleased(); + if (RT_SUCCESS(rc)) + { + pMsg->pService->ReleaseService(); + } + } break; +#endif + + 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")); + + rc = HGCMService::LoadState(pMsg->pSSM, pMsg->uVersion); + } break; + + case HGCM_MSG_SAVESTATE: + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_SAVESTATE\n")); + + rc = HGCMService::SaveState(pMsg->pSSM); + } 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) + { + rc = VERR_NO_MEMORY; + } + else + { + handle->pszServiceName = (char *)((uint8_t *)handle + sizeof(struct _HGCMSVCEXTHANDLEDATA)); + strcpy(handle->pszServiceName, pMsg->pszServiceName); + + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, handle->pszServiceName); + + if (RT_SUCCESS(rc)) + { + pService->RegisterExtension(handle, pMsg->pfnExtension, pMsg->pvExtension); + + pService->ReleaseService(); + } + + if (RT_FAILURE(rc)) + { + RTMemFree(handle); + } + else + { + *pMsg->pHandle = handle; + } + } + } break; + + case HGCM_MSG_UNREGEXT: + { + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_UNREGEXT\n")); + + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, pMsg->handle->pszServiceName); + + if (RT_SUCCESS(rc)) + { + pService->UnregisterExtension(pMsg->handle); + + pService->ReleaseService(); + } + + RTMemFree(pMsg->handle); + } break; + + default: + { + AssertMsgFailed(("hgcmThread: Unsupported message number %08X!!!\n", u32MsgId)); + rc = VERR_NOT_SUPPORTED; + } break; + } + + /* Complete the message processing. */ + hgcmMsgComplete(pMsgCore, rc); + + LogFlowFunc(("message processed %Rrc\n", rc)); + } +} + + +/* + * 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 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, + 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_LOAD, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* 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->pHgcmPort = pHgcmPort; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_REGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* 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; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("*pHandle = %p, rc = %Rrc\n", *pHandle, rc)); + return rc; +} + +void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle) +{ + LogFlowFunc(("handle = %p\n", handle)); + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_UNREGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. */ + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_CONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* 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; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_DISCONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. */ + HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pCoreMsg; + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Helper to send either HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE messages to the main HGCM thread. + * + * @param pSSM The SSM handle. + * @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, uint32_t idMsg, uint32_t uVersion) +{ + LogFlowFunc(("pSSM = %p, idMsg = %d, uVersion = uVersion\n", pSSM, idMsg)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, idMsg, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pCoreMsg; + AssertRelease(pMsg); + + pMsg->pSSM = pSSM; + pMsg->uVersion = uVersion; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Save the state of services. + * + * @param pSSM The SSM handle. + * @return VBox rc. + */ +int HGCMHostSaveState(PSSMHANDLE pSSM) +{ + return hgcmHostLoadSaveState(pSSM, HGCM_MSG_SAVESTATE, HGCM_SAVED_STATE_VERSION); +} + +/** Load the state of services. + * + * @param pSSM The SSM handle. + * @param uVersion The state version being loaded. + * @return VBox rc. + */ +int HGCMHostLoadState(PSSMHANDLE pSSM, uint32_t uVersion) +{ + return hgcmHostLoadSaveState(pSSM, 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 rc = VERR_HGCM_INVALID_CLIENT_ID; + + /* Resolve the client handle to the client instance pointer. */ + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + AssertRelease(pClient->pService); + + /* Forward the message to the service thread. */ + rc = pClient->pService->GuestCall(pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms, tsArrival); + + hgcmObjDereference(pClient); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** 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 *)hgcmObjReference(idClient, HGCMOBJ_CLIENT); + + 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_HOSTCALL, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pCoreMsg; + + pMsg->pszServiceName = (char *)pszServiceName; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_BRD_NOTIFY, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pCoreMsg; + + pMsg->enmEvent = enmEvent; + + rc = hgcmMsgPost(pMsg, NULL); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + + +#ifdef VBOX_WITH_CRHGSMI +int HGCMHostSvcHandleCreate(const char *pszServiceName, HGCMCVSHANDLE * phSvc) +{ + LogFlowFunc(("name = %s\n", pszServiceName)); + + if (!pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + if (!phSvc) + { + 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_SVCAQUIRE, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainSvcAcquire *pMsg = (HGCMMsgMainSvcAcquire *)pCoreMsg; + + pMsg->pszServiceName = (char *)pszServiceName; + pMsg->pService = NULL; + + pMsg->Reference(); + + rc = hgcmMsgSend(pMsg); + if (RT_SUCCESS(rc)) + { + /* for simplicity just use a svc ptr as handle for now */ + *phSvc = (HGCMCVSHANDLE)pMsg->pService; + } + pMsg->Dereference(); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostSvcHandleDestroy(HGCMCVSHANDLE hSvc) +{ + LogFlowFunc(("hSvc = %p\n", hSvc)); + + if (!hSvc) + { + 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 rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_SVCRELEASE, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainSvcRelease *pMsg = (HGCMMsgMainSvcRelease *)pCoreMsg; + + pMsg->pService = (HGCMService *)hSvc; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostFastCallAsync(HGCMCVSHANDLE hSvc, uint32_t function, VBOXHGCMSVCPARM *pParm, PHGCMHOSTFASTCALLCB pfnCompletion, + void *pvCompletion) +{ + LogFlowFunc(("hSvc = %p, u32Function = %d, pParm = %p\n", + hSvc, function, pParm)); + + if (!hSvc) + { + return VERR_INVALID_PARAMETER; + } + + HGCMService *pService = (HGCMService *)hSvc; + int rc = pService->HostFastCallAsync(function, pParm, pfnCompletion, pvCompletion); + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} +#endif + +int HGCMHostReset(bool fForShutdown) +{ + LogFlowFunc(("\n")); + + /* Disconnect all clients. + */ + + HGCMMsgCore *pMsgCore; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_RESET, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore; + + pMsg->fForShutdown = fForShutdown; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostInit(void) +{ + LogFlowFunc(("\n")); + + int rc = hgcmThreadInit(); + + if (RT_SUCCESS(rc)) + { + /* + * Start main HGCM thread. + */ + + rc = hgcmThreadCreate(&g_pHgcmThread, "MainHGCMthread", hgcmThread, NULL /*pvUser*/, NULL /*pszStatsSubDir*/, NULL /*pUVM*/); + + if (RT_FAILURE(rc)) + LogRel(("Failed to start HGCM thread. HGCM services will be unavailable!!! rc = %Rrc\n", rc)); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostShutdown(bool fUvmIsInvalid /*= false*/) +{ + LogFlowFunc(("\n")); + + /* + * Do HGCMReset and then unload all services. + */ + + int rc = HGCMHostReset(true /*fForShutdown*/); + + if (RT_SUCCESS(rc)) + { + /* Send the quit message to the main hgcmThread. */ + HGCMMsgCore *pMsgCore; + rc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_QUIT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore; + pMsg->fUvmIsInvalid = fUvmIsInvalid; + + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + { + /* Wait for the thread termination. */ + hgcmThreadWait(g_pHgcmThread); + g_pHgcmThread = NULL; + + hgcmThreadUninit(); + } + } + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + diff --git a/src/VBox/Main/src-client/HGCMObjects.cpp b/src/VBox/Main/src-client/HGCMObjects.cpp new file mode 100644 index 00000000..620c700e --- /dev/null +++ b/src/VBox/Main/src-client/HGCMObjects.cpp @@ -0,0 +1,276 @@ +/* $Id: HGCMObjects.cpp $ */ +/** @file + * HGCMObjects - Host-Guest Communication Manager objects + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 PAVLULNODECORE 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 rc = RTCritSectInit(&g_critsect); + + LogFlow(("MAIN::hgcmObjInit: rc = %Rrc\n", rc)); + + return rc; +} + +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 rc = hgcmObjEnter(); + + if (RT_SUCCESS(rc)) + { + 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 = RTAvlULInsert(&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, rc = %Rrc, return void\n", handle, rc)); + + 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 rc = VINF_SUCCESS; + + LogFlow(("MAIN::hgcmObjDeleteHandle: handle 0x%08X\n", handle)); + + if (handle) + { + rc = hgcmObjEnter(); + + if (RT_SUCCESS(rc)) + { + ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlULRemove(&g_pTree, handle); + + if (pCore) + { + AssertRelease(pCore->pSelf); + + pCore->pSelf->Dereference(); + } + + hgcmObjLeave(); + } + else + { + AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, rc = %Rrc", rc)); + } + } + + LogFlow(("MAIN::hgcmObjDeleteHandle: rc = %Rrc, return void\n", rc)); +} + +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 rc = hgcmObjEnter(); + + if (RT_SUCCESS(rc)) + { + ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlULGet(&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, rc = %Rrc", rc)); + } + + 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 rc = hgcmObjEnter(); + + if (RT_SUCCESS(rc)) + { + 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..6ca68d72 --- /dev/null +++ b/src/VBox/Main/src-client/HGCMThread.cpp @@ -0,0 +1,772 @@ +/* $Id: HGCMThread.cpp $ */ +/** @file + * HGCMThread - Host-Guest Communication Manager Threads + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_HGCM +#include "LoggingNew.h" + +#include "HGCMThread.h" + +#include <VBox/err.h> +#include <VBox/vmm/stam.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); + + int MsgAlloc(HGCMMsgCore **pMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage); + int MsgGet(HGCMMsgCore **ppMsg); + int MsgPost(HGCMMsgCore *pMsg, PHGCMMSGCALLBACK 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 rc = RTThreadUserSignal(hThreadSelf); + AssertRC(rc); + + pThread->m_pfnThread(pThread, pThread->m_pvUser); + + pThread->m_fu32ThreadFlags |= HGCMMSG_TF_TERMINATED; + + pThread->m_hThread = NIL_RTTHREAD; + + LogFlow(("MAIN::hgcmWorkerThreadFunc: completed HGCM thread %p\n", pThread)); + + return rc; +} + +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 rc = VINF_SUCCESS; + LogFlowFunc(("\n")); + + if (m_hThread != NIL_RTTHREAD) + rc = RTThreadWait(m_hThread, 5000, NULL); + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMThread::Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser, const char *pszStatsSubDir, PUVM pUVM) +{ + int rc = RTSemEventCreate(&m_eventThread); + + if (RT_SUCCESS(rc)) + { + rc = RTSemEventMultiCreate(&m_eventSend); + + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&m_critsect); + + if (RT_SUCCESS(rc)) + { + m_pfnThread = pfnThread; + m_pvUser = pvUser; + + m_fu32ThreadFlags = HGCMMSG_TF_INITIALIZING; + + RTTHREAD hThread; + rc = RTThreadCreate(&hThread, hgcmWorkerThreadFunc, this, 0, /* default stack size; some services + may need quite a bit */ + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + pszThreadName); + + if (RT_SUCCESS(rc)) + { + /* Register statistics while the thread starts. */ + if (pUVM) + { + STAMR3RegisterFU(pUVM, &m_StatPostMsgNoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, + "Times a message was appended to an empty input queue.", + "/HGCM/%s/PostMsg0Pending", pszStatsSubDir); + STAMR3RegisterFU(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); + STAMR3RegisterFU(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); + STAMR3RegisterFU(pUVM, &m_StatPostMsgTwoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, + "Times a message was appended to input queue with only one pending message.", + "/HGCM/%s/PostMsg3Pending", pszStatsSubDir); + STAMR3RegisterFU(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. */ + rc = RTThreadUserWait(hThread, 30000); + AssertRC(rc); + Assert(!(m_fu32ThreadFlags & HGCMMSG_TF_INITIALIZING) || RT_FAILURE(rc)); + } + 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 rc; +} + +inline int HGCMThread::Enter(void) +{ + int rc = RTCritSectEnter(&m_critsect); + +#ifdef LOG_ENABLED + if (RT_FAILURE(rc)) + Log(("HGCMThread::MsgPost: FAILURE: could not obtain worker thread mutex, rc = %Rrc!!!\n", rc)); +#endif + + return rc; +} + +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, PHGCMMSGCALLBACK pfnCallback, bool fWait) +{ + LogFlow(("HGCMThread::MsgPost: thread = %p, pMsg = %p, pfnCallback = %p\n", this, pMsg, pfnCallback)); + + int rc = Enter(); + + if (RT_SUCCESS(rc)) + { + 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); + + rc = pMsg->m_rcSend; + } + } + + LogFlow(("HGCMThread::MsgPost: rc = %Rrc\n", rc)); + return rc; +} + + +int HGCMThread::MsgGet(HGCMMsgCore **ppMsg) +{ + int rc = VINF_SUCCESS; + + LogFlow(("HGCMThread::MsgGet: thread = %p, ppMsg = %p\n", this, ppMsg)); + + for (;;) + { + if (m_fu32ThreadFlags & HGCMMSG_TF_TERMINATE) + { + rc = VERR_INTERRUPTED; + break; + } + + LogFlow(("MAIN::hgcmMsgGet: m_pMsgInputQueueHead = %p\n", m_pMsgInputQueueHead)); + + if (m_pMsgInputQueueHead) + { + /* Move the message to the m_pMsgInProcessHead list */ + rc = Enter(); + + if (RT_FAILURE(rc)) + { + 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 rc = %Rrc\n", *ppMsg, rc)); + return rc; +} + +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 rcRet = VINF_SUCCESS; + if (pMsg->m_pfnCallback) + { + /** @todo call callback with error code in MsgPost in case of errors */ + + rcRet = pMsg->m_pfnCallback(result, pMsg); + + LogFlow(("HGCMThread::MsgComplete: callback executed. pMsg = %p, thread = %p, rcRet = %Rrc\n", pMsg, this, rcRet)); + } + + /* Message processing has been completed. */ + + int rc = Enter(); + + if (RT_SUCCESS(rc)) + { + /* 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 rcRet; +} + +/* + * Thread API. Public interface. + */ + +int hgcmThreadCreate(HGCMThread **ppThread, const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser, + const char *pszStatsSubDir, PUVM pUVM) +{ + LogFlow(("MAIN::hgcmThreadCreate\n")); + int rc; + + /* 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. */ + rc = pThread->Initialize(pszThreadName, pfnThread, pvUser, pszStatsSubDir, pUVM); + if (RT_SUCCESS(rc)) + { + *ppThread = pThread; + LogFlow(("MAIN::hgcmThreadCreate: rc = %Rrc\n", rc)); + return rc; + } + + Log(("hgcmThreadCreate: FAILURE: Initialize failed: rc = %Rrc\n", rc)); + + pThread->Dereference(); + } + else + { + Log(("hgcmThreadCreate: FAILURE: Can't allocate memory for a hgcm worker thread.\n")); + rc = VERR_NO_MEMORY; + } + *ppThread = NULL; + + LogFlow(("MAIN::hgcmThreadCreate: rc = %Rrc\n", rc)); + return rc; +} + +int hgcmThreadWait(HGCMThread *pThread) +{ + LogFlowFunc(("%p\n", pThread)); + + int rc; + if (pThread) + { + rc = pThread->WaitForTermination(); + + pThread->Dereference(); + } + else + rc = VERR_INVALID_HANDLE; + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +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 rc = pThread->MsgAlloc(ppMsg, u32MsgId, pfnNewMessage); + + LogFlow(("MAIN::hgcmMsgAlloc: *ppMsg = %p, rc = %Rrc\n", *ppMsg, rc)); + return rc; +} + +DECLINLINE(int) hgcmMsgPostInternal(HGCMMsgCore *pMsg, PHGCMMSGCALLBACK pfnCallback, bool fWait) +{ + LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait)); + Assert(pMsg); + + pMsg->Reference(); /* paranoia? */ + + int rc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait); + + pMsg->Dereference(); + + LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, rc = %Rrc\n", pMsg, rc)); + return rc; +} + +int hgcmMsgPost(HGCMMsgCore *pMsg, PHGCMMSGCALLBACK pfnCallback) +{ + int rc = hgcmMsgPostInternal(pMsg, pfnCallback, false); + + if (RT_SUCCESS(rc)) + rc = VINF_HGCM_ASYNC_EXECUTE; + + return rc; +} + +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 rc = pThread->MsgGet(ppMsg); + + pThread->Dereference(); + + LogFlow(("MAIN::hgcmMsgGet: *ppMsg = %p, rc = %Rrc\n", *ppMsg, rc)); + return rc; +} + +int hgcmMsgComplete(HGCMMsgCore *pMsg, int32_t rcMsg) +{ + LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg = %Rrc (%d)\n", pMsg, rcMsg, rcMsg)); + + int rc; + if (pMsg) + rc = pMsg->Thread()->MsgComplete(pMsg, rcMsg); + else + rc = VINF_SUCCESS; + + LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, rcMsg =%Rrc (%d), returns rc = %Rrc\n", pMsg, rcMsg, rcMsg, rc)); + return rc; +} + +int hgcmThreadInit(void) +{ + LogFlow(("MAIN::hgcmThreadInit\n")); + + /** @todo error processing. */ + + int rc = hgcmObjInit(); + + LogFlow(("MAIN::hgcmThreadInit: rc = %Rrc\n", rc)); + return rc; +} + +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..303a1484 --- /dev/null +++ b/src/VBox/Main/src-client/KeyboardImpl.cpp @@ -0,0 +1,472 @@ +/* $Id: KeyboardImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_KEYBOARD +#include "LoggingNew.h" + +#include "KeyboardImpl.h" +#include "ConsoleImpl.h" + +#include "AutoCaller.h" + +#include <VBox/com/array.h> +#include <VBox/vmm/pdmdrv.h> +#include <VBox/err.h> + +#include <iprt/cpp/utils.h> + + +// defines +//////////////////////////////////////////////////////////////////////////////// + +// globals +//////////////////////////////////////////////////////////////////////////////// + +/** @name 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 rc = mEventSource->init(); + AssertComRCReturnRC(rc); + + /* 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]; + + VBoxEventDesc evDesc; + evDesc.init(mEventSource, VBoxEventType_OnGuestKeyboard, ComSafeArrayAsInParam(keys)); + evDesc.fire(0); + + 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 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() +{ + std::vector<LONG> scancodes; + scancodes.resize(1); + scancodes[0] = 0xFC; /* Magic scancode, see PS/2 and USB keyboard devices. */ + return putScancodes(scancodes, NULL); +} + +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); + 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) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD); + LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "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 = 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. + */ + void *pv; + int rc = CFGMR3QueryPtr(pCfg, "Object", &pv); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc)); + return rc; + } + pThis->pKeyboard = (Keyboard *)pv; /** @todo Check this cast! */ + 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..7592d81c --- /dev/null +++ b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp @@ -0,0 +1,1692 @@ +/* $Id: MachineDebuggerImpl.cpp $ */ +/** @file + * VBox IMachineDebugger COM class implementation (VBoxC). + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_MACHINEDEBUGGER +#include "LoggingNew.h" + +#include "MachineDebuggerImpl.h" + +#include "Global.h" +#include "ConsoleImpl.h" + +#include "AutoCaller.h" + +#include <VBox/vmm/em.h> +#include <VBox/vmm/patm.h> +#include <VBox/vmm/csam.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; + mRecompileUserQueued = -1; + mRecompileSupervisorQueued = -1; + mPatmEnabledQueued = -1; + mCsamEnabledQueued = -1; + mLogEnabledQueued = -1; + mVirtualTimeRateQueued = UINT32_MAX; + mFlushMode = false; + + /* 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; +} + +// 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)) + EMR3QueryExecutionPolicy(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 = EMR3SetExecutionPolicy(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 recompile user mode code flag. + * + * @returns COM status code + * @param aRecompileUser address of result variable + */ +HRESULT MachineDebugger::getRecompileUser(BOOL *aRecompileUser) +{ + return i_getEmExecPolicyProperty(EMEXECPOLICY_RECOMPILE_RING3, aRecompileUser); +} + +/** + * Sets the recompile user mode code flag. + * + * @returns COM status + * @param aRecompileUser new user mode code recompile flag. + */ +HRESULT MachineDebugger::setRecompileUser(BOOL aRecompileUser) +{ + LogFlowThisFunc(("enable=%d\n", aRecompileUser)); + return i_setEmExecPolicyProperty(EMEXECPOLICY_RECOMPILE_RING3, aRecompileUser); +} + +/** + * Returns the current recompile supervisor code flag. + * + * @returns COM status code + * @param aRecompileSupervisor address of result variable + */ +HRESULT MachineDebugger::getRecompileSupervisor(BOOL *aRecompileSupervisor) +{ + return i_getEmExecPolicyProperty(EMEXECPOLICY_RECOMPILE_RING0, aRecompileSupervisor); +} + +/** + * Sets the new recompile supervisor code flag. + * + * @returns COM status code + * @param aRecompileSupervisor new recompile supervisor code flag + */ +HRESULT MachineDebugger::setRecompileSupervisor(BOOL aRecompileSupervisor) +{ + LogFlowThisFunc(("enable=%d\n", aRecompileSupervisor)); + return i_setEmExecPolicyProperty(EMEXECPOLICY_RECOMPILE_RING0, aRecompileSupervisor); +} + +/** + * 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 current patch manager enabled flag. + * + * @returns COM status code + * @param aPATMEnabled address of result variable + */ +HRESULT MachineDebugger::getPATMEnabled(BOOL *aPATMEnabled) +{ +#ifdef VBOX_WITH_RAW_MODE + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + *aPATMEnabled = PATMR3IsEnabled(ptrVM.rawUVM()); + else +#endif + *aPATMEnabled = false; + + return S_OK; +} + +/** + * Set the new patch manager enabled flag. + * + * @returns COM status code + * @param aPATMEnabled new patch manager enabled flag + */ +HRESULT MachineDebugger::setPATMEnabled(BOOL aPATMEnabled) +{ + LogFlowThisFunc(("enable=%d\n", aPATMEnabled)); + +#ifdef VBOX_WITH_RAW_MODE + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (i_queueSettings()) + { + // queue the request + mPatmEnabledQueued = aPATMEnabled; + return S_OK; + } + + Console::SafeVMPtr ptrVM(mParent); + if (FAILED(ptrVM.rc())) + return ptrVM.rc(); + + int vrc = PATMR3AllowPatching(ptrVM.rawUVM(), RT_BOOL(aPATMEnabled)); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("PATMR3AllowPatching returned %Rrc"), vrc); + +#else /* !VBOX_WITH_RAW_MODE */ + if (aPATMEnabled) + return setErrorBoth(VBOX_E_VM_ERROR, VERR_RAW_MODE_NOT_SUPPORTED, tr("PATM not present"), VERR_NOT_SUPPORTED); +#endif /* !VBOX_WITH_RAW_MODE */ + return S_OK; +} + +/** + * Returns the current code scanner enabled flag. + * + * @returns COM status code + * @param aCSAMEnabled address of result variable + */ +HRESULT MachineDebugger::getCSAMEnabled(BOOL *aCSAMEnabled) +{ +#ifdef VBOX_WITH_RAW_MODE + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtrQuiet ptrVM(mParent); + + if (ptrVM.isOk()) + *aCSAMEnabled = CSAMR3IsEnabled(ptrVM.rawUVM()); + else +#endif /* VBOX_WITH_RAW_MODE */ + *aCSAMEnabled = false; + + return S_OK; +} + +/** + * Sets the new code scanner enabled flag. + * + * @returns COM status code + * @param aCSAMEnabled new code scanner enabled flag + */ +HRESULT MachineDebugger::setCSAMEnabled(BOOL aCSAMEnabled) +{ + LogFlowThisFunc(("enable=%d\n", aCSAMEnabled)); + +#ifdef VBOX_WITH_RAW_MODE + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (i_queueSettings()) + { + // queue the request + mCsamEnabledQueued = aCSAMEnabled; + return S_OK; + } + + Console::SafeVMPtr ptrVM(mParent); + if (FAILED(ptrVM.rc())) + return ptrVM.rc(); + + int vrc = CSAMR3SetScanningEnabled(ptrVM.rawUVM(), aCSAMEnabled != FALSE); + if (RT_FAILURE(vrc)) + return setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("CSAMR3SetScanningEnabled returned %Rrc"), vrc); + +#else /* !VBOX_WITH_RAW_MODE */ + if (aCSAMEnabled) + return setErrorBoth(VBOX_E_VM_ERROR, VERR_RAW_MODE_NOT_SUPPORTED, tr("CASM not present")); +#endif /* !VBOX_WITH_RAW_MODE */ + return S_OK; +} + +/** + * 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 && !(pLogInstance->fFlags & 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 = DBGFR3LogModifyFlags(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(), RTLogGetFlags, "RTGetFlags", &aLogDbgFlags); +} + +HRESULT MachineDebugger::getLogDbgGroups(com::Utf8Str &aLogDbgGroups) +{ + return i_logStringProps(RTLogGetDefaultInstance(), RTLogGetGroupSettings, "RTLogGetGroupSettings", &aLogDbgGroups); +} + +HRESULT MachineDebugger::getLogDbgDestinations(com::Utf8Str &aLogDbgDestinations) +{ + return i_logStringProps(RTLogGetDefaultInstance(), RTLogGetDestinations, "RTLogGetDestinations", &aLogDbgDestinations); +} + +HRESULT MachineDebugger::getLogRelFlags(com::Utf8Str &aLogRelFlags) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogGetFlags, "RTGetFlags", &aLogRelFlags); +} + +HRESULT MachineDebugger::getLogRelGroups(com::Utf8Str &aLogRelGroups) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogGetGroupSettings, "RTLogGetGroupSettings", &aLogRelGroups); +} + +HRESULT MachineDebugger::getLogRelDestinations(com::Utf8Str &aLogRelDestinations) +{ + return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogGetDestinations, "RTLogGetDestinations", &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 rc = EMR3QueryMainExecutionEngine(ptrVM.rawUVM(), &bEngine); + if (RT_SUCCESS(rc)) + switch (bEngine) + { + case VM_EXEC_ENGINE_NOT_SET: *apenmEngine = VMExecutionEngine_NotSet; break; + case VM_EXEC_ENGINE_RAW_MODE: *apenmEngine = VMExecutionEngine_RawMode; 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 hardware virtualization flag. + * + * @returns COM status code + * @param aHWVirtExEnabled address of result variable + */ +HRESULT MachineDebugger::getHWVirtExEnabled(BOOL *aHWVirtExEnabled) +{ + *aHWVirtExEnabled = false; + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + Console::SafeVMPtrQuiet ptrVM(mParent); + if (ptrVM.isOk()) + { + uint8_t bEngine = UINT8_MAX; + int rc = EMR3QueryMainExecutionEngine(ptrVM.rawUVM(), &bEngine); + *aHWVirtExEnabled = RT_SUCCESS(rc) && bEngine == VM_EXEC_ENGINE_HW_VIRT; + } + + 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 = HMR3IsNestedPagingActive(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 = HMR3IsVpidActive(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 = HMR3IsUXActive(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 = DBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), szName, sizeof(szName), NULL, 0); + if (RT_SUCCESS(vrc)) + { + try + { + Bstr bstrName(szName); + aOSName = Utf8Str(bstrName); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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 = DBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), NULL, 0, szVersion, sizeof(szVersion)); + if (RT_SUCCESS(vrc)) + { + try + { + Bstr bstrVersion(szVersion); + aOSVersion = Utf8Str(bstrVersion); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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 rc = DBGFR3RegCpuQueryU32(ptrVM.rawUVM(), 0 /*idCpu*/, DBGFREG_CR4, &cr4); AssertRC(rc); + *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 = TMR3GetWarpDrive(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 = TMR3SetWarpDrive(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; +} + +/** + * Hack for getting the user mode VM handle (UVM). + * + * This is only temporary (promise) while prototyping the debugger. + * + * @returns COM status code + * @param aVM 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!) + * @remarks The returned handle must be passed to VMR3ReleaseUVM()! + * @remarks Prior to 4.3 this returned PVM. + */ +HRESULT MachineDebugger::getVM(LONG64 *aVM) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Console::SafeVMPtr ptrVM(mParent); + HRESULT hrc = ptrVM.rc(); + if (SUCCEEDED(hrc)) + { + VMR3RetainUVM(ptrVM.rawUVM()); + *aVM = (intptr_t)ptrVM.rawUVM(); + } + + /* + * Note! ptrVM protection provided by SafeVMPtr is no long effective + * after we return from this method. + */ + 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)TMR3TimeVirtGetMilli(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 = DBGFR3CoreWrite(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. + */ +static void MachineDebuggerInfoInit(PMACHINEDEBUGGERINOFHLP pHlp) +{ + pHlp->Core.pfnPrintf = MachineDebuggerInfoPrintf; + pHlp->Core.pfnPrintfV = MachineDebuggerInfoPrintfV; + 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); + int vrc = DBGFR3Info(ptrVM.rawUVM(), aName.c_str(), aArgs.c_str(), &Hlp.Core); + if (RT_SUCCESS(vrc)) + { + if (!Hlp.fOutOfMemory) + { + /* + * Convert the info string, watching out for allocation errors. + */ + try + { + Bstr bstrInfo(Hlp.pszBuf); + aInfo = bstrInfo; + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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 = DBGFR3InjectNMI(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 = DBGFR3LogModifyFlags(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 = DBGFR3LogModifyGroups(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 = DBGFR3LogModifyDestinations(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")) + { + DBGFR3PlugInLoadAll(ptrVM.rawUVM()); + try + { + aPlugInName = "all"; + hrc = S_OK; + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + else + { + RTERRINFOSTATIC ErrInfo; + char szName[80]; + int vrc = DBGFR3PlugInLoad(ptrVM.rawUVM(), aName.c_str(), szName, sizeof(szName), RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(vrc)) + { + try + { + aPlugInName = szName; + hrc = S_OK; + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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")) + { + DBGFR3PlugInUnloadAll(ptrVM.rawUVM()); + hrc = S_OK; + } + else + { + int vrc = DBGFR3PlugInUnload(ptrVM.rawUVM(), aName.c_str()); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else if (vrc == VERR_NOT_FOUND) + hrc = setErrorBoth(E_FAIL, vrc, "Plug-in '%s' was not found", aName.c_str()); + else + hrc = setErrorVrc(vrc, "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 = DBGFR3OSDetect(ptrVM.rawUVM(), szName, sizeof(szName)); + if (RT_SUCCESS(vrc) && vrc != VINF_DBGF_OS_NOT_DETCTED) + { + try + { + aOs = szName; + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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)DBGFR3OSQueryInterface(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(), 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(), 0 /*fFlags*/, cMessages, + aDmesg.mutableRaw(), cbBuf, &cbActual); + } + if (RT_SUCCESS(vrc)) + aDmesg.jolt(); + else if (vrc == VERR_BUFFER_OVERFLOW) + hrc = setError(E_FAIL, "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, "The dmesg interface isn't implemented by guest OS digger, or detectOS() has not been called."); + } + return hrc; +} + +/** + * Formats a register value. + * + * This is used by both register getter methods. + * + * @returns + * @param a_pbstr The output Bstr variable. + * @param a_pValue The value to format. + * @param a_enmType The type of the value. + */ +DECLINLINE(HRESULT) formatRegisterValue(Bstr *a_pbstr, PCDBGFREGVAL a_pValue, DBGFREGVALTYPE a_enmType) +{ + char szHex[160]; + ssize_t cch = DBGFR3RegFormatValue(szHex, sizeof(szHex), a_pValue, a_enmType, true /*fSpecial*/); + if (RT_UNLIKELY(cch <= 0)) + return E_UNEXPECTED; + *a_pbstr = szHex; + return S_OK; +} + +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 = DBGFR3RegNmQuery(ptrVM.rawUVM(), aCpuId, aName.c_str(), &Value, &enmType); + if (RT_SUCCESS(vrc)) + { + try + { + Bstr bstrValue; + hrc = formatRegisterValue(&bstrValue, &Value, enmType); + if (SUCCEEDED(hrc)) + aValue = Utf8Str(bstrValue); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } + 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 = DBGFR3RegNmQueryAllCount(ptrVM.rawUVM(), &cRegs); + if (RT_SUCCESS(vrc)) + { + PDBGFREGENTRYNM paRegs = (PDBGFREGENTRYNM)RTMemAllocZ(sizeof(paRegs[0]) * cRegs); + if (paRegs) + { + vrc = DBGFR3RegNmQueryAll(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 = DBGFR3RegFormatValue(szHex, sizeof(szHex), &paRegs[iReg].Val, + paRegs[iReg].enmType, true /*fSpecial*/); + Assert(cch > 0); NOREF(cch); + aNames[iReg] = Utf8Str(paRegs[iReg].pszName); + aValues[iReg] = Utf8Str(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 = VMR3GetStateU(ptrVM.rawUVM()); + if ( enmVmState == VMSTATE_RUNNING + || enmVmState == VMSTATE_RUNNING_LS + || enmVmState == VMSTATE_RUNNING_FT) + { + alock.release(); + vrc = VMR3Suspend(ptrVM.rawUVM(), VMSUSPENDREASON_USER); + alock.acquire(); + fPaused = RT_SUCCESS(vrc); + } + } + if (RT_SUCCESS(vrc)) + { + PCDBGFSTACKFRAME pFirstFrame; + vrc = DBGFR3StackWalkBegin(ptrVM.rawUVM(), aCpuId, DBGFCODETYPE_GUEST, &pFirstFrame); + if (RT_SUCCESS(vrc)) + { + /* + * Print header. + */ + try + { + uint32_t fBitFlags = 0; + for (PCDBGFSTACKFRAME pFrame = pFirstFrame; + pFrame; + pFrame = DBGFR3StackWalkNext(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.append(Utf8StrFmt("%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.append(Utf8StrFmt("%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.append(Utf8StrFmt("%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.append(Utf8StrFmt(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.append(Utf8StrFmt(" %s+%llx", pFrame->pSymPC->szName, (int64_t)offDisp)); + else if (offDisp < 0) + aStack.append(Utf8StrFmt(" %s-%llx", pFrame->pSymPC->szName, -(int64_t)offDisp)); + else + aStack.append(Utf8StrFmt(" %s", pFrame->pSymPC->szName)); + } + if (pFrame->pLinePC) + aStack.append(Utf8StrFmt(" [%s @ 0i%d]", pFrame->pLinePC->szFilename, pFrame->pLinePC->uLineNo)); + aStack.append(Utf8StrFmt("\n")); + + fBitFlags = fCurBitFlags; + } + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + + DBGFR3StackWalkEnd(pFirstFrame); + } + else + hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3StackWalkBegin failed with %Rrc"), vrc); + + /* + * Resume the VM if we suspended it. + */ + if (fPaused) + { + alock.release(); + VMR3Resume(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, "Machine is not running"); + + STAMR3Reset(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, "Machine is not running"); + + STAMR3Dump(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, "Machine is not running"); + + char *pszSnapshot; + int vrc = STAMR3Snapshot(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. */ + aStats = Utf8Str(pszSnapshot); + STAMR3SnapshotFree(ptrVM.rawUVM(), pszSnapshot); + + return S_OK; +} + + +// 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 (mPatmEnabledQueued != -1) + { + COMSETTER(PATMEnabled)(mPatmEnabledQueued); + mPatmEnabledQueued = -1; + } + if (mCsamEnabledQueued != -1) + { + COMSETTER(CSAMEnabled)(mCsamEnabledQueued); + mCsamEnabledQueued = -1; + } + 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..e5d58a01 --- /dev/null +++ b/src/VBox/Main/src-client/MouseImpl.cpp @@ -0,0 +1,1335 @@ +/* $Id: MouseImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 <VBox/vmm/pdmdrv.h> +#include <VBox/VMMDev.h> +#include <VBox/err.h> + + +class ATL_NO_VTABLE MousePointerShape: + public MousePointerShapeWrap +{ +public: + + DECLARE_EMPTY_CTOR_DTOR(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 reporting */ + MOUSE_DEVCAP_MULTI_TOUCH = 4 +}; +/** @} */ + + +/** + * 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 rc = mEventSource->init(); + AssertComRCReturnRC(rc); + mMouseEvent.init(mEventSource, VBoxEventType_OnGuestMouse, + 0, 0, 0, 0, 0, 0); + + /* 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 rc = pVMMDevPort->pfnUpdateMouseCapabilities(pVMMDevPort, fCapsAdded, + fCapsRemoved); + if (RT_FAILURE(rc)) + 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 + * mouse events. + * + * @returns COM status code + * @param aMultiTouchSupported address of result variable + */ +HRESULT Mouse::getMultiTouchSupported(BOOL *aMultiTouchSupported) +{ + *aMultiTouchSupported = i_supportsMT(); + 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, + uint32_t u32ScanTime) +{ + HRESULT hrc = S_OK; + + 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 & MOUSE_DEVCAP_MULTI_TOUCH)) + { + pUpPort = mpDrv[i]->pUpPort; + break; + } + } + } + + if (pUpPort) + { + int vrc = pUpPort->pfnPutEventMultiTouch(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) +{ + VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface(); + ComAssertRet(pVMMDev, E_FAIL); + PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort(); + ComAssertRet(pVMMDevPort, E_FAIL); + + if (x != mcLastX || y != mcLastY) + { + int vrc = pVMMDevPort->pfnSetAbsoluteMouse(pVMMDevPort, + x, y); + 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 rc; + /** 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) + { + rc = i_reportAbsEventToVMMDev(x, y); + cJiggle = !fUsesVMMDevEvent; + } + rc = i_reportRelEventToMouseDev(cJiggle, 0, dz, dw, fButtons); + } + else + rc = i_reportAbsEventToMouseDev(x, y, dz, dw, fButtons); + + mcLastX = x; + mcLastY = y; + return rc; +} + + +/** + * 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); + } + 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) + { + VBoxEventDesc evDesc; + evDesc.init(mEventSource, VBoxEventType_OnGuestMouse, mode, x, y, + dz, dw, fButtons); + evDesc.fire(0); + } + else + { + mMouseEvent.reinit(VBoxEventType_OnGuestMouse, mode, x, y, dz, dw, + fButtons); + mMouseEvent.fire(0); + } +} + +void Mouse::i_fireMultiTouchEvent(uint8_t cContacts, + const LONG64 *paContacts, + 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); + } + + VBoxEventDesc evDesc; + evDesc.init(mEventSource, VBoxEventType_OnGuestMultiTouch, + cContacts, ComSafeArrayAsInParam(xPositions), ComSafeArrayAsInParam(yPositions), + ComSafeArrayAsInParam(contactIds), ComSafeArrayAsInParam(contactFlags), u32ScanTime); + evDesc.fire(0); +} + +/** + * 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) +{ + HRESULT rc; + uint32_t fButtonsAdj; + + LogRel3(("%s: dx=%d, dy=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__, + dx, dy, dz, dw)); + + 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); + rc = i_reportRelEventToMouseDev(dx, dy, dz, dw, fButtonsAdj); + + i_fireMouseEvent(false, dx, dy, dz, dw, aButtonState); + + return rc; +} + +/** + * 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 rc = pDisplay->i_getScreenResolution(0, &displayWidth, + &displayHeight, &ulDummy, &lDummy, &lDummy); + if (FAILED(rc)) + return rc; + + *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)); + + int32_t xAdj, yAdj; + uint32_t fButtonsAdj; + bool fValid; + + /** @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 rc = i_convertDisplayRes(x, y, &xAdj, &yAdj, &fValid); + if (FAILED(rc)) return rc; + + fButtonsAdj = i_mouseButtonsToPDM(aButtonState); + /* 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); + if (fValid) + { + rc = i_reportAbsEventToInputDevices(xAdj, yAdj, dz, dw, fButtonsAdj, + RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL)); + if (FAILED(rc)) return rc; + + i_fireMouseEvent(true, x, y, dz, dw, aButtonState); + } + rc = i_reportAbsEventToDisplayDevice(x, y); + + return rc; +} + +/** + * 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 aScanTime Timestamp. + */ +HRESULT Mouse::putEventMultiTouch(LONG aCount, + const std::vector<LONG64> &aContacts, + ULONG aScanTime) +{ + LogRel3(("%s: aCount %d(actual %d), aScanTime %u\n", + __FUNCTION__, aCount, aContacts.size(), aScanTime)); + + HRESULT rc = S_OK; + + if ((LONG)aContacts.size() >= aCount) + { + const LONG64 *paContacts = aCount > 0? &aContacts.front(): NULL; + + rc = i_putEventMultiTouch(aCount, paContacts, aScanTime); + } + else + { + rc = E_INVALIDARG; + } + + return rc; +} + +/** + * 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 aScanTime Timestamp. + */ +HRESULT Mouse::putEventMultiTouchString(LONG aCount, + const com::Utf8Str &aContacts, + ULONG aScanTime) +{ + /** @todo implement: convert the string to LONG64 array and call putEventMultiTouch. */ + NOREF(aCount); + NOREF(aContacts); + NOREF(aScanTime); + return E_NOTIMPL; +} + + +// private methods +///////////////////////////////////////////////////////////////////////////// + +/* Used by PutEventMultiTouch and PutEventMultiTouchString. */ +HRESULT Mouse::i_putEventMultiTouch(LONG aCount, + const LONG64 *paContacts, + ULONG aScanTime) +{ + if (aCount >= 256) + { + return E_INVALIDARG; + } + + DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface(); + ComAssertRet(pDisplay, E_FAIL); + + /* Touch events are mapped to the primary monitor, because the emulated USB + * touchscreen device is associated with one (normally the primary) screen in the guest. + */ + ULONG uScreenId = 0; + + ULONG cWidth = 0; + ULONG cHeight = 0; + ULONG cBPP = 0; + LONG xOrigin = 0; + LONG yOrigin = 0; + HRESULT rc = pDisplay->i_getScreenResolution(uScreenId, &cWidth, &cHeight, &cBPP, &xOrigin, &yOrigin); + NOREF(cBPP); + ComAssertComRCRetRC(rc); + + 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) + { + 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: [%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 + { + rc = E_OUTOFMEMORY; + } + } + + if (SUCCEEDED(rc)) + { + rc = i_reportMultiTouchEventToDevice(cContacts, cContacts? pau64Contacts: NULL, (uint32_t)aScanTime); + + /* Send the original contact information. */ + i_fireMultiTouchEvent(cContacts, cContacts? paContacts: NULL, (uint32_t)aScanTime); + } + + RTMemTmpFree(pau64Contacts); + + return rc; +} + + +/** 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); +} + + +/** Check what sort of reporting can be done using the devices currently + * enabled. Does not consider the VMM device. + * + * @param pfAbs supports absolute mouse coordinates. + * @param pfRel supports relative mouse coordinates. + * @param pfMT supports multitouch. + */ +void Mouse::i_getDeviceCaps(bool *pfAbs, bool *pfRel, bool *pfMT) +{ + bool fAbsDev = false; + bool fRelDev = false; + bool fMTDev = false; + + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i) + if (mpDrv[i]) + { + if (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_ABSOLUTE) + fAbsDev = true; + if (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_RELATIVE) + fRelDev = true; + if (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_MULTI_TOUCH) + fMTDev = true; + } + if (pfAbs) + *pfAbs = fAbsDev; + if (pfRel) + *pfRel = fRelDev; + if (pfMT) + *pfMT = fMTDev; +} + + +/** Does the VMM device currently support absolute reporting? */ +bool Mouse::i_vmmdevCanAbs(void) +{ + bool fRelDev; + + i_getDeviceCaps(NULL, &fRelDev, NULL); + return (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE) + && fRelDev; +} + + +/** Does the VMM device currently support absolute reporting? */ +bool Mouse::i_deviceCanAbs(void) +{ + bool fAbsDev; + + i_getDeviceCaps(&fAbsDev, NULL, NULL); + return fAbsDev; +} + + +/** Can we currently send relative events to the guest? */ +bool Mouse::i_supportsRel(void) +{ + bool fRelDev; + + i_getDeviceCaps(NULL, &fRelDev, NULL); + return fRelDev; +} + + +/** Can we currently send absolute events to the guest? */ +bool Mouse::i_supportsAbs(void) +{ + bool fAbsDev; + + i_getDeviceCaps(&fAbsDev, NULL, NULL); + return fAbsDev || i_vmmdevCanAbs(); +} + + +/** Can we currently send absolute events to the guest? */ +bool Mouse::i_supportsMT(void) +{ + bool fMTDev; + + i_getDeviceCaps(NULL, NULL, &fMTDev); + return fMTDev; +} + + +/** 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, fMTDev, fCanAbs, fNeedsHostCursor; + + { + AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS); + + i_getDeviceCaps(NULL, &fRelDev, &fMTDev); + fCanAbs = i_supportsAbs(); + fNeedsHostCursor = i_guestNeedsHostCursor(); + } + mParent->i_onMouseCapabilityChange(fCanAbs, fRelDev, fMTDev, fNeedsHostCursor); +} + + +/** + * @interface_method_impl{PDMIMOUSECONNECTOR,pfnReportModes} + * A virtual device is notifying us about its current state and capabilities + */ +DECLCALLBACK(void) Mouse::i_mouseReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRelative, + bool fAbsolute, bool fMultiTouch) +{ + 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 (fMultiTouch) + pDrv->u32DevCaps |= MOUSE_DEVCAP_MULTI_TOUCH; + else + pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MULTI_TOUCH; + + 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) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE); + LogFlow(("drvMainMouse_Construct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "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 = 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. + */ + void *pv; + int rc = CFGMR3QueryPtr(pCfg, "Object", &pv); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc)); + return rc; + } + pThis->pMouse = (Mouse *)pv; /** @todo Check this cast! */ + 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/Nvram.cpp b/src/VBox/Main/src-client/Nvram.cpp new file mode 100644 index 00000000..5707b729 --- /dev/null +++ b/src/VBox/Main/src-client/Nvram.cpp @@ -0,0 +1,441 @@ +/* $Id: Nvram.cpp $ */ +/** @file + * VBox NVRAM COM Class implementation. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_EFI +#include "LoggingNew.h" + +#include "Nvram.h" +#include "ConsoleImpl.h" +#include "Global.h" + +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmnvram.h> +#include <VBox/vmm/cfgm.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/uuid.h> +#include <iprt/base64.h> +#include <VBox/version.h> +#include <iprt/file.h> +#include <iprt/semaphore.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct NVRAM NVRAM; +typedef struct NVRAM *PNVRAM; + +/** + * Intstance data associated with PDMDRVINS. + */ +struct NVRAM +{ + /** Pointer to the associated class instance. */ + Nvram *pNvram; + /** The NVRAM connector interface we provide to DevEFI. */ + PDMINVRAMCONNECTOR INvramConnector; + /** The root of the 'Vars' child of the driver config (i.e. + * VBoxInternal/Devices/efi/0/LUN#0/Config/Vars/). + * This node has one child node per NVRAM variable. */ + PCFGMNODE pCfgVarRoot; + /** The variable node used in the privous drvNvram_VarQueryByIndex call. */ + PCFGMNODE pLastVarNode; + /** The index pLastVarNode corresponds to. */ + uint32_t idxLastVar; + /** Whether to permanently save the variables or not. */ + bool fPermanentSave; +}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default NVRAM attribute value (non-volatile, boot servier access, + runtime access). */ +#define NVRAM_DEFAULT_ATTRIB UINT32_C(0x7) +/** The CFGM overlay path of the NVRAM variables. */ +#define NVRAM_CFGM_OVERLAY_PATH "VBoxInternal/Devices/efi/0/LUN#0/Config/Vars" + +/** + * Constructor/destructor + */ +Nvram::Nvram(Console *pConsole) + : mParent(pConsole), + mpDrv(NULL) +{ +} + +Nvram::~Nvram() +{ + if (mpDrv) + { + mpDrv->pNvram = NULL; + mpDrv = NULL; + } +} + + +/** + * @interface_method_impl{PDMINVRAMCONNECTOR,pfnVarStoreSeqEnd} + */ +DECLCALLBACK(int) drvNvram_VarStoreSeqEnd(PPDMINVRAMCONNECTOR pInterface, int rc) +{ + NOREF(pInterface); + return rc; +} + +/** + * Converts the binary to a CFGM overlay binary string. + * + * @returns Pointer to a heap buffer (hand it to RTMemFree when done). + * @param pvBuf The binary data to convert. + * @param cbBuf The number of bytes to convert. + */ +static char *drvNvram_binaryToCfgmString(void const *pvBuf, size_t cbBuf) +{ + static char s_szPrefix[] = "bytes:"; + size_t cbStr = RTBase64EncodedLength(cbBuf) + sizeof(s_szPrefix); + char *pszStr = (char *)RTMemAlloc(cbStr); + if (pszStr) + { + memcpy(pszStr, s_szPrefix, sizeof(s_szPrefix) - 1); + int rc = RTBase64Encode(pvBuf, cbBuf, &pszStr[sizeof(s_szPrefix) - 1], cbStr - sizeof(s_szPrefix) + 1, NULL); + if (RT_FAILURE(rc)) + { + RTMemFree(pszStr); + pszStr = NULL; + } + } + return pszStr; +} + +/** + * @interface_method_impl{PDMINVRAMCONNECTOR,pfnVarStoreSeqPut} + */ +DECLCALLBACK(int) drvNvram_VarStoreSeqPut(PPDMINVRAMCONNECTOR pInterface, int idxVariable, + PCRTUUID pVendorUuid, const char *pszName, size_t cchName, + uint32_t fAttributes, uint8_t const *pbValue, size_t cbValue) +{ + PNVRAM pThis = RT_FROM_MEMBER(pInterface, NVRAM, INvramConnector); + int rc = VINF_SUCCESS; + + if (pThis->fPermanentSave && pThis->pNvram) + { + char szExtraName[256]; + size_t offValueNm = RTStrPrintf(szExtraName, sizeof(szExtraName) - 16, + NVRAM_CFGM_OVERLAY_PATH "/%04u/", idxVariable); + + char szUuid[RTUUID_STR_LENGTH]; + int rc2 = RTUuidToStr(pVendorUuid, szUuid, sizeof(szUuid)); AssertRC(rc2); + + char szAttribs[32]; + if (fAttributes != NVRAM_DEFAULT_ATTRIB) + RTStrPrintf(szAttribs, sizeof(szAttribs), "%#x", fAttributes); + else + szAttribs[0] = '\0'; + + char *pszValue = drvNvram_binaryToCfgmString(pbValue, cbValue); + if (pszValue) + { + const char *apszTodo[] = + { + "Name", pszName, + "Uuid", szUuid, + "Value", pszValue, + "Attribs", szAttribs, + }; + for (unsigned i = 0; i < RT_ELEMENTS(apszTodo); i += 2) + { + if (!apszTodo[i + 1][0]) + continue; + + Assert(strlen(apszTodo[i]) < 16); + strcpy(szExtraName + offValueNm, apszTodo[i]); + try + { + HRESULT hrc = pThis->pNvram->getParent()->i_machine()->SetExtraData(Bstr(szExtraName).raw(), + Bstr(apszTodo[i + 1]).raw()); + if (FAILED(hrc)) + { + LogRel(("drvNvram_deleteVar: SetExtraData(%s,%s) returned %Rhrc\n", szExtraName, apszTodo[i + 1], hrc)); + rc = Global::vboxStatusCodeFromCOM(hrc); + } + } + catch (...) + { + LogRel(("drvNvram_deleteVar: SetExtraData(%s,%s) threw exception\n", szExtraName, apszTodo[i + 1])); + rc = VERR_UNEXPECTED_EXCEPTION; + } + } + } + else + rc = VERR_NO_MEMORY; + RTMemFree(pszValue); + } + + NOREF(cchName); + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Deletes a variable. + * + * @param pThis The NVRAM driver instance data. + * @param pszVarNodeNm The variable node name. + */ +static void drvNvram_deleteVar(PNVRAM pThis, const char *pszVarNodeNm) +{ + char szExtraName[256]; + size_t offValue = RTStrPrintf(szExtraName, sizeof(szExtraName) - 16, NVRAM_CFGM_OVERLAY_PATH "/%s/", pszVarNodeNm); + static const char *s_apszValueNames[] = { "Name", "Uuid", "Value", "Attribs" }; + for (unsigned i = 0; i < RT_ELEMENTS(s_apszValueNames); i++) + { + Assert(strlen(s_apszValueNames[i]) < 16); + strcpy(szExtraName + offValue, s_apszValueNames[i]); + try + { + HRESULT hrc = pThis->pNvram->getParent()->i_machine()->SetExtraData(Bstr(szExtraName).raw(), Bstr().raw()); + if (FAILED(hrc)) + LogRel(("drvNvram_deleteVar: SetExtraData(%s,) returned %Rhrc\n", szExtraName, hrc)); + } + catch (...) + { + LogRel(("drvNvram_deleteVar: SetExtraData(%s,) threw exception\n", szExtraName)); + } + } +} + +/** + * @interface_method_impl{PDMINVRAMCONNECTOR,pfnVarStoreSeqBegin} + */ +DECLCALLBACK(int) drvNvram_VarStoreSeqBegin(PPDMINVRAMCONNECTOR pInterface, uint32_t cVariables) +{ + PNVRAM pThis = RT_FROM_MEMBER(pInterface, NVRAM, INvramConnector); + int rc = VINF_SUCCESS; + if (pThis->fPermanentSave && pThis->pNvram) + { + /* + * Remove all existing variables. + */ + for (PCFGMNODE pVarNode = CFGMR3GetFirstChild(pThis->pCfgVarRoot); pVarNode; pVarNode = CFGMR3GetNextChild(pVarNode)) + { + char szName[128]; + rc = CFGMR3GetName(pVarNode, szName, sizeof(szName)); + if (RT_SUCCESS(rc)) + drvNvram_deleteVar(pThis, szName); + else + LogRel(("drvNvram_VarStoreSeqBegin: CFGMR3GetName -> %Rrc\n", rc)); + } + } + + NOREF(cVariables); + return rc; +} + +/** + * @interface_method_impl{PDMINVRAMCONNECTOR,pfnVarQueryByIndex} + */ +DECLCALLBACK(int) drvNvram_VarQueryByIndex(PPDMINVRAMCONNECTOR pInterface, uint32_t idxVariable, + PRTUUID pVendorUuid, char *pszName, uint32_t *pcchName, + uint32_t *pfAttributes, uint8_t *pbValue, uint32_t *pcbValue) +{ + PNVRAM pThis = RT_FROM_MEMBER(pInterface, NVRAM, INvramConnector); + + /* + * Find the requested variable node. + */ + PCFGMNODE pVarNode; + if (pThis->idxLastVar + 1 == idxVariable && pThis->pLastVarNode) + pVarNode = CFGMR3GetNextChild(pThis->pLastVarNode); + else + { + pVarNode = CFGMR3GetFirstChild(pThis->pCfgVarRoot); + for (uint32_t i = 0; i < idxVariable && pVarNode; i++) + pVarNode = CFGMR3GetNextChild(pVarNode); + } + if (!pVarNode) + return VERR_NOT_FOUND; + + /* cache it */ + pThis->pLastVarNode = pVarNode; + pThis->idxLastVar = idxVariable; + + /* + * Read the variable node. + */ + int rc = CFGMR3QueryString(pVarNode, "Name", pszName, *pcchName); + AssertRCReturn(rc, rc); + *pcchName = (uint32_t)strlen(pszName); + + char szUuid[RTUUID_STR_LENGTH]; + rc = CFGMR3QueryString(pVarNode, "Uuid", szUuid, sizeof(szUuid)); + AssertRCReturn(rc, rc); + rc = RTUuidFromStr(pVendorUuid, szUuid); + AssertRCReturn(rc, rc); + + rc = CFGMR3QueryU32Def(pVarNode, "Attribs", pfAttributes, NVRAM_DEFAULT_ATTRIB); + AssertRCReturn(rc, rc); + + size_t cbValue; + rc = CFGMR3QuerySize(pVarNode, "Value", &cbValue); + AssertRCReturn(rc, rc); + AssertReturn(cbValue <= *pcbValue, VERR_BUFFER_OVERFLOW); + rc = CFGMR3QueryBytes(pVarNode, "Value", pbValue, cbValue); + AssertRCReturn(rc, rc); + *pcbValue = (uint32_t)cbValue; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +DECLCALLBACK(void *) Nvram::drvNvram_QueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + LogFlowFunc(("pInterface=%p pszIID=%s\n", pInterface, pszIID)); + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PNVRAM pThis = PDMINS_2_DATA(pDrvIns, PNVRAM); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMINVRAMCONNECTOR, &pThis->INvramConnector); + return NULL; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnDestruct} + */ +DECLCALLBACK(void) Nvram::drvNvram_Destruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + LogFlowFunc(("iInstance/#%d\n", pDrvIns->iInstance)); + PNVRAM pThis = PDMINS_2_DATA(pDrvIns, PNVRAM); + if (pThis->pNvram != NULL) + pThis->pNvram->mpDrv = NULL; +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnConstruct} + */ +DECLCALLBACK(int) Nvram::drvNvram_Construct(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)); + PNVRAM pThis = PDMINS_2_DATA(pDrvIns, PNVRAM); + + /* + * Initalize instance data variables first. + */ + //pThis->pNvram = NULL; + //pThis->cLoadedVariables = 0; + //pThis->fPermanentSave = false; + pThis->pCfgVarRoot = CFGMR3GetChild(pCfg, "Vars"); + //pThis->pLastVarNode = NULL; + pThis->idxLastVar = UINT32_MAX / 2; + + pDrvIns->IBase.pfnQueryInterface = Nvram::drvNvram_QueryInterface; + pThis->INvramConnector.pfnVarQueryByIndex = drvNvram_VarQueryByIndex; + pThis->INvramConnector.pfnVarStoreSeqBegin = drvNvram_VarStoreSeqBegin; + pThis->INvramConnector.pfnVarStoreSeqPut = drvNvram_VarStoreSeqPut; + pThis->INvramConnector.pfnVarStoreSeqEnd = drvNvram_VarStoreSeqEnd; + + /* + * Validate and read configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "Object\0" + "PermanentSave\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); + + int rc = CFGMR3QueryPtr(pCfg, "Object", (void **)&pThis->pNvram); + AssertMsgRCReturn(rc, ("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc), rc); + + rc = CFGMR3QueryBoolDef(pCfg, "PermanentSave", &pThis->fPermanentSave, false); + AssertRCReturn(rc, rc); + + /* + * Let the associated class instance know about us. + */ + pThis->pNvram->mpDrv = pThis; + + return VINF_SUCCESS; +} + + +const PDMDRVREG Nvram::DrvReg = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName[32] */ + "NvramStorage", + /* szRCMod[32] */ + "", + /* szR0Mod[32] */ + "", + /* pszDescription */ + "NVRAM Main Driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass */ + PDM_DRVREG_CLASS_VMMDEV, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(NVRAM), + /* pfnConstruct */ + Nvram::drvNvram_Construct, + /* pfnDestruct */ + Nvram::drvNvram_Destruct, + /* 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/PCIRawDevImpl.cpp b/src/VBox/Main/src-client/PCIRawDevImpl.cpp new file mode 100644 index 00000000..0e8392ca --- /dev/null +++ b/src/VBox/Main/src-client/PCIRawDevImpl.cpp @@ -0,0 +1,220 @@ +/* $Id: PCIRawDevImpl.cpp $ */ +/** @file + * VirtualBox Driver Interface to raw PCI device. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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) + : mpDrv(NULL), + mParent(console) +{ +} + +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..522ad627 --- /dev/null +++ b/src/VBox/Main/src-client/Recording.cpp @@ -0,0 +1,718 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY +#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 + +#ifdef VBOX_RECORDING_DUMP +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + uint16_t u16Magic; + uint32_t u32Size; + uint16_t u16Reserved1; + uint16_t u16Reserved2; + uint32_t u32OffBits; +} RECORDINGBMPHDR, *PRECORDINGBMPHDR; +AssertCompileSize(RECORDINGBMPHDR, 14); + +typedef struct +{ + uint32_t u32Size; + uint32_t u32Width; + uint32_t u32Height; + uint16_t u16Planes; + uint16_t u16BitCount; + uint32_t u32Compression; + uint32_t u32SizeImage; + uint32_t u32XPelsPerMeter; + uint32_t u32YPelsPerMeter; + uint32_t u32ClrUsed; + uint32_t u32ClrImportant; +} RECORDINGBMPDIBHDR, *PRECORDINGBMPDIBHDR; +AssertCompileSize(RECORDINGBMPDIBHDR, 40); + +#pragma pack(pop) +#endif /* VBOX_RECORDING_DUMP */ + + +RecordingContext::RecordingContext(Console *a_pConsole, const settings::RecordingSettings &a_Settings) + : pConsole(a_pConsole) + , enmState(RECORDINGSTS_UNINITIALIZED) + , cStreamsEnabled(0) +{ + int rc = RecordingContext::createInternal(a_Settings); + if (RT_FAILURE(rc)) + throw rc; +} + +RecordingContext::~RecordingContext(void) +{ + destroyInternal(); +} + +/** + * 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); + + LogFunc(("Thread started\n")); + + for (;;) + { + int rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT); + AssertRCBreak(rc); + + Log2Func(("Processing %zu streams\n", pThis->vecStreams.size())); + + /** @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->vecStreams.begin(); + while (itStream != pThis->vecStreams.end()) + { + RecordingStream *pStream = (*itStream); + + rc = pStream->Process(pThis->mapBlocksCommon); + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), rc)); + break; + } + + ++itStream; + } + + if (RT_FAILURE(rc)) + LogRel(("Recording: Encoding thread failed (%Rrc)\n", rc)); + + /* Keep going in case of errors. */ + + if (ASMAtomicReadBool(&pThis->fShutdown)) + { + LogFunc(("Thread is shutting down ...\n")); + break; + } + + } /* for */ + + LogFunc(("Thread ended\n")); + return VINF_SUCCESS; +} + +/** + * Notifies a recording context's encoding thread. + * + * @returns IPRT status code. + */ +int RecordingContext::threadNotify(void) +{ + return RTSemEventSignal(this->WaitEvent); +} + +/** + * Creates a recording context. + * + * @returns IPRT status code. + * @param a_Settings Recording settings to use for context creation. + */ +int RecordingContext::createInternal(const settings::RecordingSettings &a_Settings) +{ + int rc = RTCritSectInit(&this->CritSect); + if (RT_FAILURE(rc)) + return rc; + + settings::RecordingScreenMap::const_iterator itScreen = a_Settings.mapScreens.begin(); + while (itScreen != a_Settings.mapScreens.end()) + { + RecordingStream *pStream = NULL; + try + { + pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second); + this->vecStreams.push_back(pStream); + if (itScreen->second.fEnabled) + this->cStreamsEnabled++; + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + break; + } + + ++itScreen; + } + + if (RT_SUCCESS(rc)) + { + this->tsStartMs = RTTimeMilliTS(); + this->enmState = RECORDINGSTS_CREATED; + this->fShutdown = false; + + /* Copy the settings to our context. */ + this->Settings = a_Settings; + + rc = RTSemEventCreate(&this->WaitEvent); + AssertRCReturn(rc, rc); + } + + if (RT_FAILURE(rc)) + destroyInternal(); + + return rc; +} + +/** + * Starts a recording context by creating its worker thread. + * + * @returns IPRT status code. + */ +int RecordingContext::startInternal(void) +{ + if (this->enmState == RECORDINGSTS_STARTED) + return VINF_SUCCESS; + + Assert(this->enmState == RECORDINGSTS_CREATED); + + int rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0, + RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record"); + + if (RT_SUCCESS(rc)) /* Wait for the thread to start. */ + rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */); + + if (RT_SUCCESS(rc)) + { + LogRel(("Recording: Started\n")); + this->enmState = RECORDINGSTS_STARTED; + } + else + Log(("Recording: Failed to start (%Rrc)\n", rc)); + + return rc; +} + +/** + * Stops a recording context by telling the worker thread to stop and finalizing its operation. + * + * @returns IPRT status code. + */ +int RecordingContext::stopInternal(void) +{ + if (this->enmState != RECORDINGSTS_STARTED) + return VINF_SUCCESS; + + LogThisFunc(("Shutting down thread ...\n")); + + /* Set shutdown indicator. */ + ASMAtomicWriteBool(&this->fShutdown, true); + + /* Signal the thread and wait for it to shut down. */ + int rc = threadNotify(); + if (RT_SUCCESS(rc)) + rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL); + + lock(); + + if (RT_SUCCESS(rc)) + { + LogRel(("Recording: Stopped\n")); + this->enmState = RECORDINGSTS_CREATED; + } + else + Log(("Recording: Failed to stop (%Rrc)\n", rc)); + + unlock(); + + LogFlowThisFunc(("%Rrc\n", rc)); + return rc; +} + +/** + * Destroys a recording context, internal version. + */ +void RecordingContext::destroyInternal(void) +{ + if (this->enmState == RECORDINGSTS_UNINITIALIZED) + return; + + int rc = stopInternal(); + AssertRCReturnVoid(rc); + + lock(); + + rc = RTSemEventDestroy(this->WaitEvent); + AssertRCReturnVoid(rc); + + this->WaitEvent = NIL_RTSEMEVENT; + + RecordingStreams::iterator it = this->vecStreams.begin(); + while (it != this->vecStreams.end()) + { + RecordingStream *pStream = (*it); + + rc = pStream->Uninit(); + AssertRC(rc); + + delete pStream; + pStream = NULL; + + this->vecStreams.erase(it); + it = this->vecStreams.begin(); + } + + /* Sanity. */ + Assert(this->vecStreams.empty()); + Assert(this->mapBlocksCommon.size() == 0); + + unlock(); + + if (RTCritSectIsInitialized(&this->CritSect)) + { + Assert(RTCritSectGetWaiters(&this->CritSect) == -1); + RTCritSectDelete(&this->CritSect); + } + + this->enmState = RECORDINGSTS_UNINITIALIZED; +} + +/** + * Returns a recording context's current settings. + * + * @returns The recording context's current settings. + */ +const settings::RecordingSettings &RecordingContext::GetConfig(void) const +{ + return this->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 = this->vecStreams.at(uScreen); + } + catch (std::out_of_range &) + { + pStream = NULL; + } + + return pStream; +} + +int RecordingContext::lock(void) +{ + int rc = RTCritSectEnter(&this->CritSect); + AssertRC(rc); + return rc; +} + +int RecordingContext::unlock(void) +{ + int rc = RTCritSectLeave(&this->CritSect); + AssertRC(rc); + return rc; +} + +/** + * 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 this->vecStreams.size(); +} + +/** + * Creates a new recording context. + * + * @returns IPRT status code. + * @param a_Settings Recording settings to use for creation. + * + */ +int RecordingContext::Create(const settings::RecordingSettings &a_Settings) +{ + return createInternal(a_Settings); +} + +/** + * Destroys a recording context. + */ +void RecordingContext::Destroy(void) +{ + destroyInternal(); +} + +/** + * Starts a recording context. + * + * @returns IPRT 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 = this->vecStreams.begin(); + while (itStream != this->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 = this->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 Current timestamp (in ms). Currently not being used. + */ +bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp) +{ + RT_NOREF(msTimestamp); + + lock(); + + bool fIsReady = false; + + if (this->enmState != RECORDINGSTS_STARTED) + { + const RecordingStream *pStream = GetStream(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 = this->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", this->cStreamsEnabled)); + + const bool fLimitReached = this->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 (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; +} + +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(this->cStreamsEnabled); + this->cStreamsEnabled--; + + LogFlowThisFunc(("cStreamsEnabled=%RU16\n", cStreamsEnabled)); + + unlock(); + + return VINF_SUCCESS; +} + +/** + * Sends an audio frame to the video encoding thread. + * + * @thread EMT + * + * @returns IPRT status code. + * @param pvData Audio frame data to send. + * @param cbData Size (in bytes) of (encoded) audio frame data. + * @param msTimestamp Timestamp (in ms) of audio playback. + */ +int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp) +{ +#ifdef VBOX_WITH_AUDIO_RECORDING + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + /* To save time spent in EMT, do the required audio multiplexing in the encoding thread. + * + * The multiplexing is needed to supply all recorded (enabled) screens with the same + * audio data at the same given point in time. + */ + RecordingBlock *pBlock = new RecordingBlock(); + pBlock->enmType = 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->pvData = pFrame; + pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData; + pBlock->cRefs = this->cStreamsEnabled; + pBlock->msTimestamp = msTimestamp; + + lock(); + + int rc; + + try + { + RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(msTimestamp); + if (itBlocks == this->mapBlocksCommon.end()) + { + RecordingBlocks *pRecordingBlocks = new RecordingBlocks(); + pRecordingBlocks->List.push_back(pBlock); + + this->mapBlocksCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks)); + } + else + itBlocks->second->List.push_back(pBlock); + + rc = VINF_SUCCESS; + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + rc = VERR_NO_MEMORY; + } + + unlock(); + + if (RT_SUCCESS(rc)) + rc = threadNotify(); + + return rc; +#else + RT_NOREF(pvData, cbData, msTimestamp); + return VINF_SUCCESS; +#endif +} + +/** + * Copies a source video frame to the intermediate RGB buffer. + * This function is executed only once per time. + * + * @thread EMT + * + * @returns IPRT 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 (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 = GetStream(uScreen); + if (!pStream) + { + unlock(); + + AssertFailed(); + return VERR_NOT_FOUND; + } + + int rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp); + + unlock(); + + if ( RT_SUCCESS(rc) + && rc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */ + { + threadNotify(); + } + + return rc; +} + diff --git a/src/VBox/Main/src-client/RecordingInternals.cpp b/src/VBox/Main/src-client/RecordingInternals.cpp new file mode 100644 index 00000000..2e6c4252 --- /dev/null +++ b/src/VBox/Main/src-client/RecordingInternals.cpp @@ -0,0 +1,62 @@ +/* $Id: RecordingInternals.cpp $ */ +/** @file + * Recording internals code. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "RecordingInternals.h" + +#include <iprt/assert.h> +#include <iprt/mem.h> + +#ifdef VBOX_WITH_AUDIO_RECORDING +/** + * 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; + + if (pFrame->pvBuf) + { + Assert(pFrame->cbBuf); + RTMemFree(pFrame->pvBuf); + } + RTMemFree(pFrame); + pFrame = NULL; +} +#endif + +/** + * Frees a recording video frame. + * + * @returns IPRT status code. + * @param pFrame Pointer to video frame to free. The pointer will be invalid after return. + */ +void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame) +{ + if (!pFrame) + return; + + if (pFrame->pu8RGBBuf) + { + Assert(pFrame->cbRGBBuf); + RTMemFree(pFrame->pu8RGBBuf); + } + RTMemFree(pFrame); +} + diff --git a/src/VBox/Main/src-client/RecordingStream.cpp b/src/VBox/Main/src-client/RecordingStream.cpp new file mode 100644 index 00000000..a5556b48 --- /dev/null +++ b/src/VBox/Main/src-client/RecordingStream.cpp @@ -0,0 +1,1232 @@ +/* $Id: RecordingStream.cpp $ */ +/** @file + * Recording stream code. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY +#include "LoggingNew.h" + +#include <stdexcept> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/file.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 "Recording.h" +#include "RecordingStream.h" +#include "RecordingUtils.h" +#include "WebMWriter.h" + + +RecordingStream::RecordingStream(RecordingContext *a_pCtx) + : pCtx(a_pCtx) + , enmState(RECORDINGSTREAMSTATE_UNINITIALIZED) + , tsStartMs(0) +{ + File.pWEBM = NULL; + File.hFile = NIL_RTFILE; +} + +RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings) + : enmState(RECORDINGSTREAMSTATE_UNINITIALIZED) + , tsStartMs(0) +{ + File.pWEBM = NULL; + File.hFile = NIL_RTFILE; + + int rc2 = initInternal(a_pCtx, uScreen, Settings); + if (RT_FAILURE(rc2)) + throw rc2; +} + +RecordingStream::~RecordingStream(void) +{ + int rc2 = uninitInternal(); + AssertRC(rc2); +} + +/** + * Opens a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::open(const settings::RecordingScreenSettings &Settings) +{ + /* Sanity. */ + Assert(Settings.enmDest != RecordingDestination_None); + + int rc; + + switch (Settings.enmDest) + { + case RecordingDestination_File: + { + Assert(Settings.File.strName.isNotEmpty()); + + char *pszAbsPath = RTPathAbsDup(Settings.File.strName.c_str()); + AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY); + + RTPathStripSuffix(pszAbsPath); + + char *pszSuff = RTStrDup(".webm"); + if (!pszSuff) + { + RTStrFree(pszAbsPath); + rc = VERR_NO_MEMORY; + break; + } + + char *pszFile = NULL; + + if (this->uScreenID > 0) + rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, this->uScreenID + 1, pszSuff); + else + rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff); + + if (RT_SUCCESS(rc)) + { + uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE; + + /* Play safe: the file must not exist, overwriting is potentially + * hazardous as nothing prevents the user from picking a file name of some + * other important file, causing unintentional data loss. */ + fOpen |= RTFILE_O_CREATE; + + RTFILE hFile; + rc = RTFileOpen(&hFile, pszFile, fOpen); + if (rc == VERR_ALREADY_EXISTS) + { + RTStrFree(pszFile); + pszFile = NULL; + + RTTIMESPEC ts; + RTTimeNow(&ts); + RTTIME time; + RTTimeExplode(&time, &ts); + + if (this->uScreenID > 0) + rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s", + pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay, + time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond, + this->uScreenID + 1, pszSuff); + else + rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ%s", + pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay, + time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond, + pszSuff); + + if (RT_SUCCESS(rc)) + rc = RTFileOpen(&hFile, pszFile, fOpen); + } + + try + { + Assert(File.pWEBM == NULL); + File.pWEBM = new WebMWriter(); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + this->File.hFile = hFile; + this->ScreenSettings.File.strName = pszFile; + } + } + + RTStrFree(pszSuff); + RTStrFree(pszAbsPath); + + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Failed to open file '%s' for screen %RU32, rc=%Rrc\n", + pszFile ? pszFile : "<Unnamed>", this->uScreenID, rc)); + } + + RTStrFree(pszFile); + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Parses an options string to configure advanced / hidden / experimental features of a recording stream. + * Unknown values will be skipped. + * + * @returns IPRT status code. + * @param strOptions Options string to parse. + */ +int RecordingStream::parseOptionsString(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", Utf8Str::CaseInsensitive) == 0) + { +#ifdef VBOX_WITH_LIBVPX + Assert(this->ScreenSettings.Video.ulFPS); + if (value.compare("realtime", Utf8Str::CaseInsensitive) == 0) + this->Video.Codec.VPX.uEncoderDeadline = VPX_DL_REALTIME; + else if (value.compare("good", Utf8Str::CaseInsensitive) == 0) + this->Video.Codec.VPX.uEncoderDeadline = 1000000 / this->ScreenSettings.Video.ulFPS; + else if (value.compare("best", Utf8Str::CaseInsensitive) == 0) + this->Video.Codec.VPX.uEncoderDeadline = VPX_DL_BEST_QUALITY; + else + { + this->Video.Codec.VPX.uEncoderDeadline = value.toUInt32(); +#endif + } + } + else if (key.compare("vc_enabled", Utf8Str::CaseInsensitive) == 0) + { + if (value.compare("false", Utf8Str::CaseInsensitive) == 0) + this->ScreenSettings.featureMap[RecordingFeature_Video] = false; + } + else if (key.compare("ac_enabled", Utf8Str::CaseInsensitive) == 0) + { +#ifdef VBOX_WITH_AUDIO_RECORDING + if (value.compare("true", Utf8Str::CaseInsensitive) == 0) + this->ScreenSettings.featureMap[RecordingFeature_Audio] = true; +#endif + } + else if (key.compare("ac_profile", Utf8Str::CaseInsensitive) == 0) + { +#ifdef VBOX_WITH_AUDIO_RECORDING + if (value.compare("low", Utf8Str::CaseInsensitive) == 0) + { + this->ScreenSettings.Audio.uHz = 8000; + this->ScreenSettings.Audio.cBits = 16; + this->ScreenSettings.Audio.cChannels = 1; + } + else if (value.startsWith("med" /* "med[ium]" */, Utf8Str::CaseInsensitive) == 0) + { + /* Stay with the default set above. */ + } + else if (value.compare("high", Utf8Str::CaseInsensitive) == 0) + { + this->ScreenSettings.Audio.uHz = 48000; + this->ScreenSettings.Audio.cBits = 16; + this->ScreenSettings.Audio.cChannels = 2; + } +#endif + } + else + LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str())); + + } /* while */ + + return VINF_SUCCESS; +} + +/** + * Returns the recording stream's used configuration. + * + * @returns The recording stream's used configuration. + */ +const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const +{ + return this->ScreenSettings; +} + +/** + * Checks if a specified limit for a recording stream has been reached, internal version. + * + * @returns true if any limit has been reached. + * @param msTimestamp Timestamp (in ms) to check for. + */ +bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const +{ + LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n", + msTimestamp, this->ScreenSettings.ulMaxTimeS, this->tsStartMs)); + + if ( this->ScreenSettings.ulMaxTimeS + && msTimestamp >= this->tsStartMs + (this->ScreenSettings.ulMaxTimeS * RT_MS_1SEC)) + { + LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n", + this->uScreenID, this->ScreenSettings.ulMaxTimeS)); + return true; + } + + if (this->ScreenSettings.enmDest == RecordingDestination_File) + { + if (this->ScreenSettings.File.ulMaxSizeMB) + { + uint64_t sizeInMB = this->File.pWEBM->GetFileSize() / _1M; + if(sizeInMB >= this->ScreenSettings.File.ulMaxSizeMB) + { + LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n", + this->uScreenID, this->ScreenSettings.File.ulMaxSizeMB)); + return true; + } + } + + /* Check for available free disk space */ + if ( this->File.pWEBM + && this->File.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 IPRT status code. + * @param msTimestamp Current timestamp (in ms). + */ +int RecordingStream::iterateInternal(uint64_t msTimestamp) +{ + if (!this->fEnabled) + return VINF_SUCCESS; + + int rc; + + if (isLimitReachedInternal(msTimestamp)) + { + rc = VINF_RECORDING_LIMIT_REACHED; + } + else + rc = VINF_SUCCESS; + + AssertPtr(this->pCtx); + + switch (rc) + { + case VINF_RECORDING_LIMIT_REACHED: + { + this->fEnabled = false; + + int rc2 = this->pCtx->OnLimitReached(this->uScreenID, VINF_SUCCESS /* rc */); + AssertRC(rc2); + break; + } + + default: + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Checks if a specified limit for a recording stream has been reached. + * + * @returns true if any limit has been reached. + * @param msTimestamp Timestamp (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 this->fEnabled; +} + +/** + * 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 IPRT status code. + * @param mapBlocksCommon Map of common block to process for this stream. + */ +int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon) +{ + LogFlowFuncEnter(); + + lock(); + + if (!this->ScreenSettings.fEnabled) + { + unlock(); + return VINF_SUCCESS; + } + + int rc = VINF_SUCCESS; + + RecordingBlockMap::iterator itStreamBlocks = Blocks.Map.begin(); + while (itStreamBlocks != 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); + +#ifdef VBOX_WITH_LIBVPX + if (pBlock->enmType == RECORDINGBLOCKTYPE_VIDEO) + { + PRECORDINGVIDEOFRAME pVideoFrame = (PRECORDINGVIDEOFRAME)pBlock->pvData; + + int rc2 = RecordingUtilsRGBToYUV(pVideoFrame->uPixelFormat, + /* Destination */ + this->Video.Codec.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight, + /* Source */ + pVideoFrame->pu8RGBBuf, this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight); + if (RT_SUCCESS(rc2)) + { + rc2 = writeVideoVPX(msTimestamp, pVideoFrame); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } +#endif + pBlocks->List.pop_front(); + delete pBlock; + } + + Assert(pBlocks->List.empty()); + delete pBlocks; + + Blocks.Map.erase(itStreamBlocks); + itStreamBlocks = Blocks.Map.begin(); + } + +#ifdef VBOX_WITH_AUDIO_RECORDING + AssertPtr(pCtx); + + /* 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); + + WebMWriter::BlockData_Opus blockData = { pAudioFrame->pvBuf, pAudioFrame->cbBuf, + pBlockCommon->msTimestamp }; + AssertPtr(this->File.pWEBM); + int rc2 = this->File.pWEBM->WriteBlock(this->uTrackAudio, &blockData, sizeof(blockData)); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + 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())); + } +#endif + + unlock(); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a raw (e.g. not yet encoded) video frame to the recording stream. + * + * @returns IPRT 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 (in ms) as PTS. + */ +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) +{ + lock(); + + LogFlowFunc(("msTimestamp=%RU64\n", msTimestamp)); + + PRECORDINGVIDEOFRAME pFrame = NULL; + + int rc = iterateInternal(msTimestamp); + if (rc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */ + { + unlock(); + return rc; + } + + do + { + if (msTimestamp < this->Video.uLastTimeStampMs + this->Video.uDelayMs) + { + rc = VINF_RECORDING_THROTTLED; /* Respect maximum frames per second. */ + break; + } + + this->Video.uLastTimeStampMs = msTimestamp; + + int xDiff = ((int)this->ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2; + uint32_t w = uSrcWidth; + if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */ + { + rc = 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)this->ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2; + if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */ + { + rc = 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 > this->ScreenSettings.Video.ulWidth + || destY > this->ScreenSettings.Video.ulHeight) + { + rc = VERR_INVALID_PARAMETER; /* Nothing visible. */ + break; + } + + if (destX + w > this->ScreenSettings.Video.ulWidth) + w = this->ScreenSettings.Video.ulWidth - destX; + + if (destY + h > this->ScreenSettings.Video.ulHeight) + h = this->ScreenSettings.Video.ulHeight - destY; + + pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME)); + AssertBreakStmt(pFrame, rc = 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->uPixelFormat = RECORDINGPIXELFMT_RGB32; + break; + case 24: + pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB24; + break; + case 16: + pFrame->uPixelFormat = RECORDINGPIXELFMT_RGB565; + break; + default: + AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), rc = VERR_NOT_SUPPORTED); + break; + } + } + else + AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), rc = VERR_NOT_SUPPORTED); + + const size_t cbRGBBuf = this->ScreenSettings.Video.ulWidth + * this->ScreenSettings.Video.ulHeight + * uBytesPerPixel; + AssertBreakStmt(cbRGBBuf, rc = VERR_INVALID_PARAMETER); + + pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf); + AssertBreakStmt(pFrame->pu8RGBBuf, rc = 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 < this->ScreenSettings.Video.ulWidth + || uSrcHeight < this->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 * this->ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel; + +#ifdef VBOX_RECORDING_DUMP + RECORDINGBMPHDR bmpHdr; + RT_ZERO(bmpHdr); + + RECORDINGBMPDIBHDR bmpDIBHdr; + RT_ZERO(bmpDIBHdr); + + bmpHdr.u16Magic = 0x4d42; /* Magic */ + bmpHdr.u32Size = (uint32_t)(sizeof(RECORDINGBMPHDR) + sizeof(RECORDINGBMPDIBHDR) + (w * h * uBytesPerPixel)); + bmpHdr.u32OffBits = (uint32_t)(sizeof(RECORDINGBMPHDR) + sizeof(RECORDINGBMPDIBHDR)); + + bmpDIBHdr.u32Size = sizeof(RECORDINGBMPDIBHDR); + bmpDIBHdr.u32Width = w; + bmpDIBHdr.u32Height = h; + bmpDIBHdr.u16Planes = 1; + bmpDIBHdr.u16BitCount = uBPP; + bmpDIBHdr.u32XPelsPerMeter = 5000; + bmpDIBHdr.u32YPelsPerMeter = 5000; + + char szFileName[RTPATH_MAX]; + RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", this->uScreenID); + + RTFILE fh; + int rc2 = RTFileOpen(&fh, szFileName, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc2)) + { + RTFileWrite(fh, &bmpHdr, sizeof(bmpHdr), NULL); + RTFileWrite(fh, &bmpDIBHdr, sizeof(bmpDIBHdr), 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 <= this->ScreenSettings.Video.ulHeight * this->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 += this->ScreenSettings.Video.ulWidth * uBytesPerPixel; + } + +#ifdef VBOX_RECORDING_DUMP + if (RT_SUCCESS(rc2)) + RTFileClose(fh); +#endif + + } while (0); + + if (rc == 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(this->Blocks.Map.find(msTimestamp) == this->Blocks.Map.end()); + this->Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks)); + } + catch (const std::exception &ex) + { + RT_NOREF(ex); + + delete pBlock; + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_FAILURE(rc)) + RecordingVideoFrameFree(pFrame); + + unlock(); + + return rc; +} + +/** + * Initializes a recording stream. + * + * @returns IPRT status code. + * @param a_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 *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings) +{ + return initInternal(a_pCtx, uScreen, Settings); +} + +/** + * Initializes a recording stream, internal version. + * + * @returns IPRT status code. + * @param a_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::initInternal(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings) +{ + this->pCtx = a_pCtx; + this->uScreenID = uScreen; + this->ScreenSettings = Settings; + + int rc = parseOptionsString(this->ScreenSettings.strOptions); + if (RT_FAILURE(rc)) + return rc; + + settings::RecordingScreenSettings *pSettings = &this->ScreenSettings; + + rc = RTCritSectInit(&this->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = open(this->ScreenSettings); + if (RT_FAILURE(rc)) + return rc; + + const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video); + const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio); + + if (fVideoEnabled) + { + rc = initVideo(); + if (RT_FAILURE(rc)) + return rc; + } + + if (fAudioEnabled) + { + rc = initAudio(); + if (RT_FAILURE(rc)) + return rc; + } + + switch (this->ScreenSettings.enmDest) + { + case RecordingDestination_File: + { + Assert(pSettings->File.strName.isNotEmpty()); + const char *pszFile = pSettings->File.strName.c_str(); + + AssertPtr(File.pWEBM); + rc = File.pWEBM->OpenEx(pszFile, &this->File.hFile, +#ifdef VBOX_WITH_AUDIO_RECORDING + fAudioEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None, +#else + WebMWriter::AudioCodec_None, +#endif + fVideoEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None); + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, rc)); + break; + } + + if (fVideoEnabled) + { + rc = this->File.pWEBM->AddVideoTrack(pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS, + &this->uTrackVideo); + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc)); + break; + } + + LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n", + this->uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight, + pSettings->Video.ulRate, pSettings->Video.ulFPS, this->uTrackVideo)); + } + +#ifdef VBOX_WITH_AUDIO_RECORDING + if (fAudioEnabled) + { + rc = this->File.pWEBM->AddAudioTrack(pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits, + &this->uTrackAudio); + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc)); + break; + } + + LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n", + this->uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels, + pSettings->Audio.cChannels ? "channels" : "channel", this->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, this->uScreenID, pszFile)); + } + + break; + } + + default: + AssertFailed(); /* Should never happen. */ + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(rc)) + { + this->enmState = RECORDINGSTREAMSTATE_INITIALIZED; + this->fEnabled = true; + this->tsStartMs = RTTimeProgramMilliTS(); + } + else + { + int rc2 = uninitInternal(); + AssertRC(rc2); + return rc; + } + + return VINF_SUCCESS; +} + +/** + * Closes a recording stream. + * Depending on the stream's recording destination, this function closes all associated handles + * and finalizes recording. + * + * @returns IPRT status code. + */ +int RecordingStream::close(void) +{ + int rc = VINF_SUCCESS; + + switch (this->ScreenSettings.enmDest) + { + case RecordingDestination_File: + { + if (this->File.pWEBM) + rc = this->File.pWEBM->Close(); + break; + } + + default: + AssertFailed(); /* Should never happen. */ + break; + } + + this->Blocks.Clear(); + + LogRel(("Recording: Recording screen #%u stopped\n", this->uScreenID)); + + if (RT_FAILURE(rc)) + { + LogRel(("Recording: Error stopping recording screen #%u, rc=%Rrc\n", this->uScreenID, rc)); + return rc; + } + + switch (this->ScreenSettings.enmDest) + { + case RecordingDestination_File: + { + if (RTFileIsValid(this->File.hFile)) + { + rc = RTFileClose(this->File.hFile); + if (RT_SUCCESS(rc)) + { + LogRel(("Recording: Closed file '%s'\n", this->ScreenSettings.File.strName.c_str())); + } + else + { + LogRel(("Recording: Error closing file '%s', rc=%Rrc\n", this->ScreenSettings.File.strName.c_str(), rc)); + break; + } + } + + if (this->File.pWEBM) + { + delete this->File.pWEBM; + this->File.pWEBM = NULL; + } + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Uninitializes a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::Uninit(void) +{ + return uninitInternal(); +} + +/** + * Uninitializes a recording stream, internal version. + * + * @returns IPRT status code. + */ +int RecordingStream::uninitInternal(void) +{ + if (this->enmState != RECORDINGSTREAMSTATE_INITIALIZED) + return VINF_SUCCESS; + + int rc = close(); + if (RT_FAILURE(rc)) + return rc; + + if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Video)) + { + int rc2 = unitVideo(); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + RTCritSectDelete(&this->CritSect); + + this->enmState = RECORDINGSTREAMSTATE_UNINITIALIZED; + this->fEnabled = false; + + return rc; +} + +/** + * Uninitializes video recording for a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::unitVideo(void) +{ +#ifdef VBOX_WITH_LIBVPX + /* At the moment we only have VPX. */ + return uninitVideoVPX(); +#else + return VERR_NOT_SUPPORTED; +#endif +} + +#ifdef VBOX_WITH_LIBVPX +/** + * Uninitializes the VPX codec for a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::uninitVideoVPX(void) +{ + PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec; + vpx_img_free(&pCodec->VPX.RawImage); + pCodec->VPX.pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */ + + vpx_codec_err_t rcv = vpx_codec_destroy(&this->Video.Codec.VPX.Ctx); + Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv); + + return VINF_SUCCESS; +} +#endif + +/** + * Initializes the video recording for a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::initVideo(void) +{ + /* Sanity. */ + AssertReturn(this->ScreenSettings.Video.ulRate, VERR_INVALID_PARAMETER); + AssertReturn(this->ScreenSettings.Video.ulWidth, VERR_INVALID_PARAMETER); + AssertReturn(this->ScreenSettings.Video.ulHeight, VERR_INVALID_PARAMETER); + AssertReturn(this->ScreenSettings.Video.ulFPS, VERR_INVALID_PARAMETER); + + this->Video.cFailedEncodingFrames = 0; + this->Video.uLastTimeStampMs = 0; + this->Video.uDelayMs = RT_MS_1SEC / this->ScreenSettings.Video.ulFPS; + + int rc; + +#ifdef VBOX_WITH_LIBVPX + /* At the moment we only have VPX. */ + rc = initVideoVPX(); +#else + rc = VERR_NOT_SUPPORTED; +#endif + + if (RT_FAILURE(rc)) + LogRel(("Recording: Failed to initialize video encoding (%Rrc)\n", rc)); + + return rc; +} + +#ifdef VBOX_WITH_LIBVPX +/** + * Initializes the VPX codec for a recording stream. + * + * @returns IPRT status code. + */ +int RecordingStream::initVideoVPX(void) +{ +# 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 + + PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec; + + vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pCodec->VPX.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. */ + pCodec->VPX.Cfg.rc_target_bitrate = this->ScreenSettings.Video.ulRate; + /* Frame width. */ + pCodec->VPX.Cfg.g_w = this->ScreenSettings.Video.ulWidth; + /* Frame height. */ + pCodec->VPX.Cfg.g_h = this->ScreenSettings.Video.ulHeight; + /* 1ms per frame. */ + pCodec->VPX.Cfg.g_timebase.num = 1; + pCodec->VPX.Cfg.g_timebase.den = 1000; + /* Disable multithreading. */ + pCodec->VPX.Cfg.g_threads = 0; + + /* Initialize codec. */ + rcv = vpx_codec_enc_init(&pCodec->VPX.Ctx, pCodecIface, &pCodec->VPX.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(&pCodec->VPX.RawImage, VPX_IMG_FMT_I420, + this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight, 1)) + { + LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", + this->ScreenSettings.Video.ulWidth, this->ScreenSettings.Video.ulHeight)); + return VERR_NO_MEMORY; + } + + /* Save a pointer to the first raw YUV plane. */ + pCodec->VPX.pu8YuvBuf = pCodec->VPX.RawImage.planes[0]; + + return VINF_SUCCESS; +} +#endif + +/** + * Initializes the audio part of a recording stream, + * + * @returns IPRT status code. + */ +int RecordingStream::initAudio(void) +{ +#ifdef VBOX_WITH_AUDIO_RECORDING + if (this->ScreenSettings.isFeatureEnabled(RecordingFeature_Audio)) + { + /* Sanity. */ + AssertReturn(this->ScreenSettings.Audio.uHz, VERR_INVALID_PARAMETER); + AssertReturn(this->ScreenSettings.Audio.cBits, VERR_INVALID_PARAMETER); + AssertReturn(this->ScreenSettings.Audio.cChannels, VERR_INVALID_PARAMETER); + } +#endif + + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_LIBVPX +/** + * Encodes the source image and write the encoded image to the stream's destination. + * + * @returns IPRT status code. + * @param msTimestamp Absolute timestamp (PTS) of frame (in ms) to encode. + * @param pFrame Frame to encode and submit. + */ +int RecordingStream::writeVideoVPX(uint64_t msTimestamp, PRECORDINGVIDEOFRAME pFrame) +{ + AssertPtrReturn(pFrame, VERR_INVALID_POINTER); + + int rc; + + PRECORDINGVIDEOCODEC pCodec = &this->Video.Codec; + + /* Presentation TimeStamp (PTS). */ + vpx_codec_pts_t pts = msTimestamp; + vpx_codec_err_t rcv = vpx_codec_encode(&pCodec->VPX.Ctx, + &pCodec->VPX.RawImage, + pts /* Timestamp */, + this->Video.uDelayMs /* How long to show this frame */, + 0 /* Flags */, + pCodec->VPX.uEncoderDeadline /* Quality setting */); + if (rcv != VPX_CODEC_OK) + { + if (this->Video.cFailedEncodingFrames++ < 64) /** @todo Make this configurable. */ + { + LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv))); + return VERR_GENERAL_FAILURE; + } + } + + this->Video.cFailedEncodingFrames = 0; + + vpx_codec_iter_t iter = NULL; + rc = VERR_NO_DATA; + for (;;) + { + const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pCodec->VPX.Ctx, &iter); + if (!pPacket) + break; + + switch (pPacket->kind) + { + case VPX_CODEC_CX_FRAME_PKT: + { + WebMWriter::BlockData_VP8 blockData = { &pCodec->VPX.Cfg, pPacket }; + rc = this->File.pWEBM->WriteBlock(this->uTrackVideo, &blockData, sizeof(blockData)); + break; + } + + default: + AssertFailed(); + LogFunc(("Unexpected video packet type %ld\n", pPacket->kind)); + break; + } + } + + return rc; +} +#endif /* VBOX_WITH_LIBVPX */ + +/** + * Locks a recording stream. + */ +void RecordingStream::lock(void) +{ + int rc = RTCritSectEnter(&CritSect); + AssertRC(rc); +} + +/** + * Unlocks a locked recording stream. + */ +void RecordingStream::unlock(void) +{ + int rc = RTCritSectLeave(&CritSect); + AssertRC(rc); +} + diff --git a/src/VBox/Main/src-client/RecordingUtils.cpp b/src/VBox/Main/src-client/RecordingUtils.cpp new file mode 100644 index 00000000..540414fc --- /dev/null +++ b/src/VBox/Main/src-client/RecordingUtils.cpp @@ -0,0 +1,177 @@ +/* $Id: RecordingUtils.cpp $ */ +/** @file + * Recording utility code. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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> + + +/** + * 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 rc = 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 && rc; ++i) + { + unsigned red, green, blue; + rc = iter.getRGB(&red, &green, &blue); + if (rc) + { + aDestBuf[i * PIX_SIZE ] = red; + aDestBuf[i * PIX_SIZE + 1] = green; + aDestBuf[i * PIX_SIZE + 2] = blue; + } + } + return rc; +} + +/** + * Converts a RGB to YUV buffer. + * + * @returns IPRT status code. + * @param uPixelFormat 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(uint32_t uPixelFormat, + uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight, + uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight) +{ + switch (uPixelFormat) + { + 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; +} + diff --git a/src/VBox/Main/src-client/RemoteUSBBackend.cpp b/src/VBox/Main/src-client/RemoteUSBBackend.cpp new file mode 100644 index 00000000..8e95e4b0 --- /dev/null +++ b/src/VBox/Main/src-client/RemoteUSBBackend.cpp @@ -0,0 +1,1399 @@ +/* $Id: RemoteUSBBackend.cpp $ */ +/** @file + * VirtualBox Remote USB backend + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 rc = RTCritSectEnter(&pDevice->critsect); + AssertRC(rc); +} + +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 rc = 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: + { + rc = pThis->saveDeviceList(pvRet, cbRet); + } break; + + case VRDE_USB_REQ_NEGOTIATE: + { + if (pvRet && cbRet >= sizeof(VRDEUSBREQNEGOTIATERET)) + { + VRDEUSBREQNEGOTIATERET *pret = (VRDEUSBREQNEGOTIATERET *)pvRet; + + rc = pThis->negotiateResponse(pret, cbRet); + } + else + { + Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n", + pvRet, cbRet, sizeof(VRDEUSBREQNEGOTIATERET))); + + rc = VERR_INVALID_PARAMETER; + } + } break; + + case VRDE_USB_REQ_REAP_URB: + { + rc = pThis->reapURB(pvRet, cbRet); + + LogFlow(("USBClientResponseCallback: reap URB, rc = %Rrc.\n", rc)); + } 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)); + rc = 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 rc; +} + +/* + * Backend entry points. + */ +static DECLCALLBACK(int) iface_Open(PREMOTEUSBBACKEND pInstance, const char *pszAddress, + size_t cbAddress, PREMOTEUSBDEVICE *ppDevice) +{ + RT_NOREF(cbAddress); + int rc = VINF_SUCCESS; + + RemoteUSBBackend *pThis = (RemoteUSBBackend *)pInstance; + + REMOTEUSBDEVICE *pDevice = (REMOTEUSBDEVICE *)RTMemAllocZ(sizeof(REMOTEUSBDEVICE)); + + if (!pDevice) + { + rc = 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(); + rc = VERR_INVALID_PARAMETER; + } + else + { + /* Initialize the device structure. */ + pDevice->pOwner = pThis; + pDevice->fWokenUp = false; + + rc = RTCritSectInit(&pDevice->critsect); + AssertRC(rc); + + if (RT_SUCCESS(rc)) + { + 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(); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + AssertFailed(); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + 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(rc)) + { + *ppDevice = pDevice; + + pThis->addDevice(pDevice); + } + else + { + RTMemFree(pDevice); + } + + return rc; +} + +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 rc = 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) + { + rc = 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(); + rc = 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(); rc = 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(); rc = 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(rc)) + { + qurbFree(qurb); + } + + return rc; +} + +/* 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 rc = 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 rc; +} + +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 rc = RTCritSectInit(&mCritsect); + + if (RT_FAILURE(rc)) + { + 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 rc = 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)); + rc = VERR_NOT_SUPPORTED; + } + } + else + { + LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet)); + rc = VERR_NOT_SUPPORTED; + } + } + else + { + /* This is a client version 1. */ + mClientVersion = VRDE_USB_VERSION_1; + } + + if (RT_SUCCESS(rc)) + { + 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)); + rc = VERR_NOT_SUPPORTED; + } + } + + menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest; + } + + return rc; +} + +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 rc = RTCritSectEnter(&mCritsect); + AssertRC(rc); +} + +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 rc = VINF_SUCCESS; + + LogFlow(("RemoteUSBBackend::reapURB: pvBody = %p, cbBody = %d\n", pvBody, cbBody)); + + VRDEUSBREQREAPURBBODY *pBody = (VRDEUSBREQREAPURBBODY *)pvBody; + + 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 + || sizeof(VRDEUSBREQREAPURBBODY) > cbBody + || pBody->handle == 0) + { + LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid reply data. Skipping the reply.\n")); + rc = VERR_INVALID_PARAMETER; + break; + } + + PREMOTEUSBDEVICE pDevice = deviceFromId(pBody->id); + + if (!pDevice) + { + LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid device id. Skipping the reply.\n")); + rc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t cbBodyData = 0; /* Data contained in the URB body structure for input URBs. */ + + 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\n", qurb)); + + /* Update the URB error field. */ + 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. */ + bool fURBCompleted = true; + + if (qurb->fInput) + { + cbBodyData = pBody->len; /* VRDE_USB_DIRECTION_IN URBs include some data. */ + } + + if ( qurb->u32Err == VUSBSTATUS_OK + && qurb->fInput) + { + LogFlow(("RemoteUSBBackend::reapURB: copying data %d bytes\n", pBody->len)); + + uint32_t u32DataLen = qurb->u32TransferredLen + pBody->len; + + if (u32DataLen > qurb->u32Len) + { + /* 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; + } + else + { + memcpy ((uint8_t *)qurb->pvData + qurb->u32TransferredLen, &pBody[1], 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; + } + } + + qurb->u32TransferredLen += pBody->len; /* Update the value for all 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. */ + uint32_t cbBodySize = sizeof (VRDEUSBREQREAPURBBODY) + cbBodyData; + + if (cbBodySize > cbBody) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + pBody = (VRDEUSBREQREAPURBBODY *)((uint8_t *)pBody + cbBodySize); + cbBody -= cbBodySize; + } + + LogFlow(("RemoteUSBBackend::reapURB: returns %Rrc\n", rc)); + + return rc; +} +/* 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..912f7136 --- /dev/null +++ b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp @@ -0,0 +1,304 @@ +/* $Id: RemoteUSBDeviceImpl.cpp $ */ +/** @file + * VirtualBox IHostUSBDevice COM interface implementation for remote (VRDP) USB devices. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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"; + + 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.portVersion) = 1; + unconst(mData.speed) = USBConnectionSpeed_Full; + break; + + case VRDE_USBDEVICESPEED_HIGH: + case VRDE_USBDEVICESPEED_VARIABLE: + unconst(mData.portVersion) = 2; + unconst(mData.speed) = USBConnectionSpeed_High; + break; + + case VRDE_USBDEVICESPEED_SUPERSPEED: + unconst(mData.portVersion) = 3; + unconst(mData.speed) = USBConnectionSpeed_Super; + break; + } + } + else + { + unconst(mData.portVersion) = mData.version; + 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.version) = 1; + unconst(mData.portVersion) = 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::getVersion(USHORT *aVersion) +{ + /* this is const, no need to lock */ + *aVersion = mData.version; + + return S_OK; +} + +HRESULT RemoteUSBDevice::getPortVersion(USHORT *aPortVersion) +{ + /* this is const, no need to lock */ + *aPortVersion = mData.portVersion; + + 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..5b8f168d --- /dev/null +++ b/src/VBox/Main/src-client/SessionImpl.cpp @@ -0,0 +1,1257 @@ +/* $Id: SessionImpl.cpp $ */ +/** @file + * VBox Client Session COM Class implementation in VBoxC. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_SESSION +#include "LoggingNew.h" + +#include "SessionImpl.h" +#include "ConsoleImpl.h" +#include "Global.h" +#include "ClientTokenHolder.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, 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 rc = init(); + + BaseFinalConstruct(); + + return rc; +} + +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 rc = i_unlockMachine(true /* aFinalRelease */, false /* aFromServer */, alock); + AssertComRC(rc); + } + + 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 rc; +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + rc = mConsole->i_machine().queryInterfaceTo(aMachine.asOutParam()); + else +#endif + rc = mRemoteMachine.queryInterfaceTo(aMachine.asOutParam()); + if (FAILED(rc)) + { +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + setError(rc, tr("Failed to query the session machine")); + else +#endif + if (FAILED_DEAD_INTERFACE(rc)) + setError(rc, tr("Peer process crashed")); + else + setError(rc, tr("Failed to query the remote session machine")); + } + + return rc; +} + +HRESULT Session::getConsole(ComPtr<IConsole> &aConsole) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + CHECK_OPEN(); + + HRESULT rc = S_OK; +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + rc = mConsole.queryInterfaceTo(aConsole.asOutParam()); + else +#endif + rc = mRemoteConsole.queryInterfaceTo(aConsole.asOutParam()); + + if (FAILED(rc)) + { +#ifndef VBOX_COM_INPROC_API_CLIENT + if (mConsole) + setError(rc, tr("Failed to query the console")); + else +#endif + if (FAILED_DEAD_INTERFACE(rc)) + setError(rc, tr("Peer process crashed")); + else + setError(rc, tr("Failed to query the remote console")); + } + + return rc; +} + +// 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(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + +#ifndef VBOX_COM_INPROC_API_CLIENT + AssertMsgReturn(mType == SessionType_WriteLock && !!mConsole, + ("This is not a direct session!\n"), + VBOX_E_INVALID_OBJECT_STATE); + + /* return a failure if the session already transitioned to Closing + * but the server hasn't processed Machine::OnSessionEnd() yet. */ + if (mState != SessionState_Locked) + return VBOX_E_INVALID_VM_STATE; + + mConsole.queryInterfaceTo(aConsole.asOutParam()); + + LogFlowThisFuncLeave(); + + return S_OK; + +#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 rc = 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. */ + rc = mConsole.createObject(); + AssertComRCReturn(rc, rc); + + rc = mConsole->init(aMachine, mControl, aLockType); + AssertComRCReturn(rc, rc); + } + 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; + rc = E_FAIL; + } + } + catch (std::bad_alloc &) + { + rc = E_OUTOFMEMORY; + } + + /* + * Reference the VirtualBox object to ensure the server is up + * until the session is closed + */ + if (SUCCEEDED(rc)) + rc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam()); + + if (SUCCEEDED(rc)) + { + 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 rc; +} + +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 rc = 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 + */ + rc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam()); + + if (SUCCEEDED(rc)) + { + /* + * 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(("rc=%08X\n", rc)); + LogFlowThisFuncLeave(); + + return rc; +} + +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 rc = S_OK; + + if (getObjectState().getState() == ObjectState::Ready) + { + /* close() needs write lock */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("mState=%s, mType=%d\n", Global::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 */ + rc = 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")); + rc = autoCaller.rc(); + } + + LogFlowThisFunc(("rc=%08X\n", rc)); + LogFlowThisFuncLeave(); + + return rc; +} + +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::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() +{ + 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(); +#else + 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::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::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::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 rc = mConsole->i_saveState(aReason, aProgress, aSnapshot, aStateFilePath, !!aPauseVM, fLeftPaused); + if (aLeftPaused) + *aLeftPaused = fLeftPaused; + return rc; +#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", Global::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 rc = mControl->OnSessionEnd(this, progress.asOutParam()); + LogFlowThisFunc(("mControl->OnSessionEnd()=%08X\n", rc)); + + 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 && (rc == E_UNEXPECTED || rc == E_ACCESSDENIED)) + rc = S_OK; + +#if !defined(DEBUG_bird) && !defined(DEBUG_andy) /* I don't want clients crashing on me just because VBoxSVC went belly up. */ + AssertComRC(rc); +#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..53441544 --- /dev/null +++ b/src/VBox/Main/src-client/USBDeviceImpl.cpp @@ -0,0 +1,342 @@ +/* $Id: USBDeviceImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 tmp; + BSTR *bptr = &tmp; + + hrc = aUSBDevice->COMGETTER(Manufacturer)(bptr); + ComAssertComRCRet(hrc, hrc); + unconst(mData.manufacturer) = Utf8Str(tmp); + + hrc = aUSBDevice->COMGETTER(Product)(bptr); + ComAssertComRCRet(hrc, hrc); + unconst(mData.product) = Utf8Str(tmp); + + hrc = aUSBDevice->COMGETTER(SerialNumber)(bptr); + ComAssertComRCRet(hrc, hrc); + unconst(mData.serialNumber) = Utf8Str(tmp); + + hrc = aUSBDevice->COMGETTER(Address)(bptr); + ComAssertComRCRet(hrc, hrc); + unconst(mData.address) = Utf8Str(tmp); + + hrc = aUSBDevice->COMGETTER(Backend)(bptr); + ComAssertComRCRet(hrc, hrc); + unconst(mData.backend) = Utf8Str(tmp); + + hrc = aUSBDevice->COMGETTER(Port)(&unconst(mData.port)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(Version)(&unconst(mData.version)); + ComAssertComRCRet(hrc, hrc); + + hrc = aUSBDevice->COMGETTER(PortVersion)(&unconst(mData.portVersion)); + 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.version) = 1; + unconst(mData.portVersion) = 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::getVersion(USHORT *aVersion) +{ + /* this is const, no need to lock */ + *aVersion = mData.version; + + return S_OK; +} + +HRESULT OUSBDevice::getPortVersion(USHORT *aPortVersion) +{ + /* this is const, no need to lock */ + *aPortVersion = mData.portVersion; + + 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..7b6c6e0d --- /dev/null +++ b/src/VBox/Main/src-client/UsbCardReader.cpp @@ -0,0 +1,1978 @@ +/* $Id: UsbCardReader.cpp $ */ +/** @file + * UsbCardReader - Driver Interface to USB Smart Card Reader emulation. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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 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); + 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; + + if (!CFGMR3AreValuesValid(pCfg, "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); + + void *pv; + int rc = CFGMR3QueryPtr(pCfg, "Object", &pv); + AssertMsgRCReturn(rc, ("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc), rc); + + pThis->pUsbCardReader = (UsbCardReader *)pv; + 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 */ + 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..990c2875 --- /dev/null +++ b/src/VBox/Main/src-client/UsbWebcamInterface.cpp @@ -0,0 +1,472 @@ +/* $Id: UsbWebcamInterface.cpp $ */ +/** @file + * UsbWebcamInterface - Driver Interface for USB Webcam emulation. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +#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 rc = 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(); + rc = VERR_NOT_SUPPORTED; + break; + } + + EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)RTMemAllocZ(sizeof(EMWEBCAMREMOTE)); + if (pRemote == NULL) + { + rc = 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. */ + rc = mParent->VideoInDeviceAttach(&mpRemote->deviceHandle, mpRemote); + if (RT_FAILURE(rc)) + { + RTMemFree(mpRemote); + mpRemote = NULL; + break; + } + + /* Get the device description. */ + rc = mParent->VideoInGetDeviceDesc(NULL, &mpRemote->deviceHandle); + + if (RT_FAILURE(rc)) + { + 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: + rc = 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 rc = VINF_SUCCESS; + + EMWEBCAMREQCTX *pCtx = NULL; + + /* Verify that there is a remote device. */ + if ( !mpRemote + || mpRemote->u64DeviceId != u64DeviceId) + { + rc = VERR_NOT_SUPPORTED; + } + + if (RT_SUCCESS(rc)) + { + pCtx = (EMWEBCAMREQCTX *)RTMemAlloc(sizeof(EMWEBCAMREQCTX)); + if (!pCtx) + { + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + pCtx->pRemote = mpRemote; + pCtx->pvUser = pvUser; + + rc = mParent->VideoInControl(pCtx, &mpRemote->deviceHandle, pControl, cbControl); + + if (RT_FAILURE(rc)) + { + RTMemFree(pCtx); + } + } + + return rc; +} + +/* 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; + } + + void *pv = NULL; + int rc = CFGMR3QueryPtr(pCfg, "Object", &pv); + if (!RT_VALID_PTR(pv)) + rc = VERR_INVALID_PARAMETER; + AssertMsgReturn(RT_SUCCESS(rc), + ("Configuration error: No/bad \"Object\" %p value! rc=%Rrc\n", pv, rc), rc); + + /* Everything ok. Initialize. */ + pThis->pRemote = (EMWEBCAMREMOTE *)pv; + 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..ca2703cb --- /dev/null +++ b/src/VBox/Main/src-client/VBoxDriversRegister.cpp @@ -0,0 +1,113 @@ +/* $Id: VBoxDriversRegister.cpp $ */ +/** @file + * + * Main driver registration. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN +#include "LoggingNew.h" + +#include "MouseImpl.h" +#include "KeyboardImpl.h" +#include "DisplayImpl.h" +#include "VMMDev.h" +#ifdef VBOX_WITH_AUDIO_VRDE +# include "DrvAudioVRDE.h" +#endif +#ifdef VBOX_WITH_AUDIO_RECORDING +# include "DrvAudioRec.h" +#endif +#include "Nvram.h" +#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, &Nvram::DrvReg); + if (RT_FAILURE(rc)) + return rc; + + 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 + + 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..f12c696b --- /dev/null +++ b/src/VBox/Main/src-client/VMMDevInterface.cpp @@ -0,0 +1,1227 @@ +/* $Id: VMMDevInterface.cpp $ */ +/** @file + * VirtualBox Driver Interface to VMM device. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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" +# if defined(RT_OS_DARWIN) && defined(VBOX_WITH_CROGL) +# include <VBox/HostServices/VBoxCrOpenGLSvc.h> +# endif +#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 +} DRVMAINVMMDEV, *PDRVMAINVMMDEV; + +// +// constructor / destructor +// +VMMDev::VMMDev(Console *console) + : mpDrv(NULL) + , mParent(console) +{ + int rc = RTSemEventCreate(&mCredentialsEvent); + AssertRC(rc); +#ifdef VBOX_WITH_HGCM + rc = HGCMHostInit(); + AssertRC(rc); + 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 rc = RTSemEventWait(mCredentialsEvent, u32Timeout); + + if (RT_SUCCESS(rc)) + { + *pu32CredentialsFlags = mu32CredentialsFlags; + } + + return rc; +} + +int VMMDev::SetCredentialsJudgementResult(uint32_t u32Flags) +{ + mu32CredentialsFlags = u32Flags; + + int rc = RTSemEventSignal(mCredentialsEvent); + AssertRC(rc); + + return rc; +} + + +/** + * @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_onUserStateChange(Bstr(pszUser), Bstr(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; +} + +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) iface_hgcmSave(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pDrvIns); + Log9(("Enter\n")); + return HGCMHostSaveState(pSSM); +} + + +/** + * 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) iface_hgcmLoad(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + RT_NOREF(pDrvIns); + 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); + + return HGCMHostLoadState(pSSM, 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(), 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*/) +{ + if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true)) + HGCMHostShutdown(fUvmIsInvalid); +} + +# ifdef VBOX_WITH_CRHGSMI +int VMMDev::hgcmHostSvcHandleCreate(const char *pszServiceName, HGCMCVSHANDLE * phSvc) +{ + if (!hgcmIsActive()) + return VERR_INVALID_STATE; + return HGCMHostSvcHandleCreate(pszServiceName, phSvc); +} + +int VMMDev::hgcmHostSvcHandleDestroy(HGCMCVSHANDLE hSvc) +{ + if (!hgcmIsActive()) + return VERR_INVALID_STATE; + return HGCMHostSvcHandleDestroy(hSvc); +} + +int VMMDev::hgcmHostFastCallAsync(HGCMCVSHANDLE hSvc, uint32_t function, PVBOXHGCMSVCPARM pParm, + PHGCMHOSTFASTCALLCB pfnCompletion, void *pvCompletion) +{ + if (!hgcmIsActive()) + return VERR_INVALID_STATE; + return HGCMHostFastCallAsync(hSvc, function, pParm, pfnCompletion, pvCompletion); +} +# endif + +#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)); + + 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 rc = hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS, 1, &parm); + if (RT_FAILURE(rc)) + { + 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 rc; +} + + +/** + * 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 rc = hgcmLoadService("VBoxGuestPropSvc", "VBoxGuestPropSvc"); + if (RT_FAILURE(rc)) + { + LogRel(("VBoxGuestPropSvc is not available. rc = %Rrc\n", rc)); + 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(rc) && i < cProps; ++i) + { + AssertPtrBreakStmt(namesOut[i], rc = VERR_INVALID_PARAMETER); + rc = RTUtf16ToUtf8(namesOut[i], &papszNames[i]); + if (RT_FAILURE(rc)) + break; + if (valuesOut[i]) + rc = RTUtf16ToUtf8(valuesOut[i], &papszValues[i]); + else + papszValues[i] = szEmpty; + if (RT_FAILURE(rc)) + break; + pai64Timestamps[i] = timestampsOut[i]; + if (flagsOut[i]) + rc = RTUtf16ToUtf8(flagsOut[i], &papszFlags[i]); + else + papszFlags[i] = szEmpty; + } + if (RT_SUCCESS(rc)) + 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 + rc = VERR_NO_MEMORY; + RTMemTmpFree(papszNames); + RTMemTmpFree(papszValues); + RTMemTmpFree(pai64Timestamps); + RTMemTmpFree(papszFlags); + AssertRCReturn(rc, rc); + + /* + * Register the host notification callback + */ + HGCMSVCEXTHANDLE hDummy; + HGCMHostRegisterServiceExtension(&hDummy, "VBoxGuestPropSvc", Console::i_doGuestPropNotification, ptrConsole.m_p); + +# ifdef VBOX_WITH_GUEST_PROPS_RDONLY_GUEST + rc = i_guestPropSetGlobalPropertyFlags(GUEST_PROP_F_RDONLYGUEST); + AssertRCReturn(rc, rc); +# 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 pCfgHandle, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV); + LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance)); + + /* + * 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 = 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.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. + */ + void *pv; + int rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Rrc\n", rc)); + return rc; + } + + pThis->pVMMDev = (VMMDev*)pv; /** @todo Check this cast! */ + pThis->pVMMDev->mpDrv = pThis; + +#ifdef VBOX_WITH_HGCM + /* + * Load & configure the shared folders service. + */ + rc = pThis->pVMMDev->hgcmLoadService(VBOXSHAREDFOLDERS_DLL, "VBoxSharedFolders"); + pThis->pVMMDev->fSharedFolderActive = RT_SUCCESS(rc); + if (RT_SUCCESS(rc)) + { + 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); + rc = pLedPort->pfnQueryStatusLed(pLedPort, 0, &pLed); + if (RT_SUCCESS(rc) && pLed) + { + VBOXHGCMSVCPARM parm; + + parm.type = VBOX_HGCM_SVC_PARM_PTR; + parm.u.pointer.addr = pLed; + parm.u.pointer.size = sizeof(*pLed); + + rc = HGCMHostCall("VBoxSharedFolders", SHFL_FN_SET_STATUS_LED, 1, &parm); + } + else + AssertMsgFailed(("pfnQueryStatusLed failed with %Rrc (pLed=%x)\n", rc, pLed)); + } + else + LogRel(("Failed to load Shared Folders service %Rrc\n", rc)); + + + /* + * Load and configure the guest control service. + */ +# ifdef VBOX_WITH_GUEST_CONTROL + rc = pThis->pVMMDev->hgcmLoadService("VBoxGuestControlSvc", "VBoxGuestControlSvc"); + if (RT_SUCCESS(rc)) + { + HGCMSVCEXTHANDLE hDummy; + rc = HGCMHostRegisterServiceExtension(&hDummy, "VBoxGuestControlSvc", + &Guest::i_notifyCtrlDispatcher, + pThis->pVMMDev->mParent->i_getGuest()); + if (RT_SUCCESS(rc)) + LogRel(("Guest Control service loaded\n")); + else + LogRel(("Warning: Cannot register VBoxGuestControlSvc extension! rc=%Rrc\n", rc)); + } + else + LogRel(("Warning!: Failed to load the Guest Control Service! %Rrc\n", rc)); +# endif /* VBOX_WITH_GUEST_CONTROL */ + + + /* + * Load and configure the guest properties service. + */ +# ifdef VBOX_WITH_GUEST_PROPS + rc = pThis->pVMMDev->i_guestPropLoadAndConfigure(); + AssertLogRelRCReturn(rc, rc); +# endif + + + /* + * The HGCM saved state. + */ + rc = PDMDrvHlpSSMRegisterEx(pDrvIns, HGCM_SAVED_STATE_VERSION, 4096 /* bad guess */, + NULL, NULL, NULL, + NULL, iface_hgcmSave, NULL, + NULL, iface_hgcmLoad, NULL); + if (RT_FAILURE(rc)) + return rc; + +#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..864b6ca7 --- /dev/null +++ b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp @@ -0,0 +1,654 @@ +/* $Id: VirtualBoxClientImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 <iprt/asm.h> +#include <iprt/thread.h> +#include <iprt/critsect.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 + + +/** 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; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +HRESULT VirtualBoxClient::FinalConstruct() +{ + HRESULT rc = init(); + BaseFinalConstruct(); + return rc; +} + +void VirtualBoxClient::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the VirtualBoxClient object. + * + * @returns COM result indicator + */ +HRESULT VirtualBoxClient::init() +{ + +#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_SDS) + // setup COM Security to enable impersonation + // This works for console VirtualBox clients, GUI has own security settings + // For GUI VirtualBox it will be second call so can return TOO_LATE error + HRESULT hrGUICoInitializeSecurity = CoInitializeSecurity(NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL); + NOREF(hrGUICoInitializeSecurity); + Assert(SUCCEEDED(hrGUICoInitializeSecurity) || hrGUICoInitializeSecurity == RPC_E_TOO_LATE); +#endif + + 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 rc = S_OK; + try + { + if (ASMAtomicIncU32(&g_cInstances) != 1) + AssertFailedStmt(throw setError(E_FAIL, tr("Attempted to create more than one VirtualBoxClient instance"))); + + mData.m_ThreadWatcher = NIL_RTTHREAD; + mData.m_SemEvWatcher = NIL_RTSEMEVENT; + + rc = mData.m_pVirtualBox.createLocalObject(CLSID_VirtualBox); + if (FAILED(rc)) +#ifdef RT_OS_WINDOWS + throw i_investigateVirtualBoxObjectCreationFailure(rc); +#else + throw rc; +#endif + + /* VirtualBox error return is postponed to method calls, fetch it. */ + ULONG rev; + rc = mData.m_pVirtualBox->COMGETTER(Revision)(&rev); + if (FAILED(rc)) + throw rc; + + rc = unconst(mData.m_pEventSource).createObject(); + AssertComRCThrow(rc, setError(rc, tr("Could not create EventSource for VirtualBoxClient"))); + rc = mData.m_pEventSource->init(); + AssertComRCThrow(rc, setError(rc, tr("Could not initialize EventSource for VirtualBoxClient"))); + + /* HACK ALERT! This is for DllCanUnloadNow(). */ + s_cUnnecessaryAtlModuleLocks++; + AssertMsg(s_cUnnecessaryAtlModuleLocks == 1, ("%d\n", s_cUnnecessaryAtlModuleLocks)); + + /* 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. */ + int 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 (rc=%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 (rc=%Rrc)"), vrc)); + } + } + catch (HRESULT err) + { + /* we assume that error info is set by the thrower */ + rc = err; + } + catch (...) + { + rc = 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(rc)) + autoInitSpan.setSucceeded(); + else + autoInitSpan.setFailed(rc); + + LogFlowThisFunc(("rc=%Rhrc\n", rc)); + 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 'qc 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()) + return; + + 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; + } + + mData.m_pToken.setNull(); + mData.m_pVirtualBox.setNull(); + + ASMAtomicDecU32(&g_cInstances); +} + +// 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 rc = pSession.createInprocObject(CLSID_Session); + if (SUCCEEDED(rc)) + aSession = pSession; + return rc; +} + +/** + * 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 rc = aMachine->COMGETTER(Accessible)(&fAccessible); + if (FAILED(rc)) + return setError(rc, tr("Could not check the accessibility status of the VM")); + else if (!fAccessible) + { + ComPtr<IVirtualBoxErrorInfo> pAccessError; + rc = aMachine->COMGETTER(AccessError)(pAccessError.asOutParam()); + if (FAILED(rc)) + return setError(rc, 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; + int vrc; + + /* The likelihood of early crashes are high, so start with a short wait. */ + vrc = RTSemEventWait(sem, cMillies / 2); + + /* As long as the waiting times out keep retrying the wait. */ + while (RT_FAILURE(vrc)) + { + { + HRESULT rc = S_OK; + ComPtr<IVirtualBox> pV; + { + AutoReadLock alock(pThis COMMA_LOCKVAL_SRC_POS); + pV = pThis->mData.m_pVirtualBox; + } + if (!pV.isNull()) + { + ULONG rev; + rc = pV->COMGETTER(Revision)(&rev); + if (FAILED_DEAD_INTERFACE(rc)) + { + LogRel(("VirtualBoxClient: detected unresponsive VBoxSVC (rc=%Rhrc)\n", rc)); + { + 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; + rc = pVirtualBox.createLocalObject(CLSID_VirtualBox); + if (FAILED(rc)) + cMillies = 3 * VBOXCLIENT_DEFAULT_INTERVAL; + else + { + LogRel(("VirtualBoxClient: detected working VBoxSVC (rc=%Rhrc)\n", rc)); + { + 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; + } + fireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, TRUE); + cMillies = VBOXCLIENT_DEFAULT_INTERVAL; + } + } + } + vrc = RTSemEventWait(sem, cMillies); + } + return 0; +} + +/* 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..7bdb9062 --- /dev/null +++ b/src/VBox/Main/src-client/WebMWriter.cpp @@ -0,0 +1,918 @@ +/* $Id: WebMWriter.cpp $ */ +/** @file + * WebMWriter.cpp - WebM container handling. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** + * 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_MAIN_DISPLAY +#include "LoggingNew.h" + +#include <iprt/cdefs.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/buildconfig.h> + +#include <VBox/log.h> +#include <VBox/version.h> + +#include "WebMWriter.h" +#include "EBML_MKV.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, + WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec) +{ + try + { + m_enmAudioCodec = a_enmAudioCodec; + m_enmVideoCodec = a_enmVideoCodec; + + LogFunc(("Creating '%s'\n", a_pszFilename)); + + int rc = createEx(a_pszFilename, a_phFile); + if (RT_SUCCESS(rc)) + { + rc = init(); + if (RT_SUCCESS(rc)) + rc = writeHeader(); + } + } + catch(int rc) + { + return rc; + } + 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, + WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec) +{ + try + { + m_enmAudioCodec = a_enmAudioCodec; + m_enmVideoCodec = a_enmVideoCodec; + + LogFunc(("Creating '%s'\n", a_pszFilename)); + + int rc = create(a_pszFilename, a_fOpen); + if (RT_SUCCESS(rc)) + { + rc = init(); + if (RT_SUCCESS(rc)) + rc = writeHeader(); + } + } + catch(int rc) + { + return rc; + } + return VINF_SUCCESS; +} + +/** + * Closes the WebM file and drains all queues. + * + * @returns IPRT status code. + */ +int WebMWriter::Close(void) +{ + LogFlowFuncEnter(); + + if (!isOpen()) + return VINF_SUCCESS; + + /* Make sure to drain all queues. */ + processQueue(&CurSeg.queueBlocks, true /* fForce */); + + writeFooter(); + + WebMTracks::iterator itTrack = CurSeg.mapTracks.begin(); + while (itTrack != CurSeg.mapTracks.end()) + { + WebMTrack *pTrack = itTrack->second; + if (pTrack) /* Paranoia. */ + delete pTrack; + + CurSeg.mapTracks.erase(itTrack); + + itTrack = CurSeg.mapTracks.begin(); + } + + Assert(CurSeg.queueBlocks.Map.size() == 0); + Assert(CurSeg.mapTracks.size() == 0); + + com::Utf8Str strFileName = getFileName().c_str(); + + close(); + + int rc = VINF_SUCCESS; + + /* If no clusters (= data) was written, delete the file again. */ + if (!CurSeg.cClusters) + rc = RTFileDelete(strFileName.c_str()); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Adds an audio track. + * + * @returns IPRT status code. + * @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(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack) +{ +#ifdef VBOX_WITH_LIBOPUS + AssertReturn(uHz, VERR_INVALID_PARAMETER); + AssertReturn(cBits, VERR_INVALID_PARAMETER); + AssertReturn(cChannels, VERR_INVALID_PARAMETER); + + /* + * Adjust the handed-in Hz rate to values which are supported by the Opus codec. + * + * Only the following values are supported by an Opus standard build + * -- every other rate only is supported by a custom build. + * + * See opus_encoder_create() for more information. + */ + if (uHz > 24000) uHz = 48000; + else if (uHz > 16000) uHz = 24000; + else if (uHz > 12000) uHz = 16000; + else if (uHz > 8000 ) uHz = 12000; + else uHz = 8000; + + /* 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)CurSeg.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_Audio, uTrack, RTFileTell(getFile())); + + pTrack->Audio.uHz = uHz; + pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */ + pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock); + + WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels); + + LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n", + pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock)); + + serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4) + .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */) + .serializeString(MkvElem_CodecID, "A_OPUS") + .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData)) + .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); + + CurSeg.mapTracks[uTrack] = pTrack; + + if (puTrack) + *puTrack = uTrack; + + return VINF_SUCCESS; +#else + RT_NOREF(uHz, cChannels, cBits, puTrack); + return VERR_NOT_SUPPORTED; +#endif +} + +/** + * Adds a video track. + * + * @returns IPRT status code. + * @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(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)CurSeg.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, 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); + + CurSeg.mapTracks[uTrack] = pTrack; + + if (puTrack) + *puTrack = uTrack; + + return VINF_SUCCESS; +#else + RT_NOREF(uWidth, uHeight, dbFPS, 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 IPRT status code. + */ +int WebMWriter::init(void) +{ + return CurSeg.init(); +} + +/** + * Takes care of the destruction of the instance. + */ +void WebMWriter::destroy(void) +{ + CurSeg.uninit(); +} + +/** + * Writes the WebM file header. + * + * @returns IPRT 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. */ + CurSeg.offStart = RTFileTell(getFile()); + + writeSeekHeader(); + + /* Save offset of upcoming tracks segment. */ + CurSeg.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 IPRT 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 = CurSeg.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 IPRT 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 rc = 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 = CurSeg.queueBlocks.Map.find(tcAbsPTS); + if (itQueue != CurSeg.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); + + CurSeg.queueBlocks.Map[tcAbsPTS] = Blocks; + } + + rc = processQueue(&CurSeg.queueBlocks, false /* fForce */); + } + catch(...) + { + delete a_pBlock; + a_pBlock = NULL; + + rc = VERR_NO_MEMORY; + } + + return rc; +} + +#ifdef VBOX_WITH_LIBVPX +/** + * Writes VPX (VP8 video) simple data block. + * + * @returns IPRT status code. + * @param a_pTrack Track ID to write data to. + * @param a_pCfg VPX encoder configuration to use. + * @param a_pPkt VPX packet video data packet to write. + */ +int WebMWriter::writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt) +{ + RT_NOREF(a_pTrack); + + /* Calculate the absolute PTS of this frame (in ms). */ + WebMTimecodeAbs tcAbsPTSMs = a_pPkt->data.frame.pts * 1000 + * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den; + if ( tcAbsPTSMs + && tcAbsPTSMs <= a_pTrack->tcAbsLastWrittenMs) + { + AssertFailed(); /* Should never happen. */ + tcAbsPTSMs = a_pTrack->tcAbsLastWrittenMs + 1; + } + + const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY); + + uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE; + if (fKeyframe) + fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME; + if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) + fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE; + + return writeSimpleBlockQueued(a_pTrack, + new WebMSimpleBlock(a_pTrack, + tcAbsPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags)); +} +#endif /* VBOX_WITH_LIBVPX */ + +#ifdef VBOX_WITH_LIBOPUS +/** + * Writes an Opus (audio) simple data block. + * + * @returns IPRT status code. + * @param a_pTrack Track ID to write data to. + * @param pvData Pointer to simple data block to write. + * @param cbData Size (in bytes) of simple data block to write. + * @param tcAbsPTSMs Absolute PTS of simple data block. + * + * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. + */ +int WebMWriter::writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs) +{ + AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + /* Every Opus frame is a key frame. */ + const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME; + + return writeSimpleBlockQueued(a_pTrack, + new WebMSimpleBlock(a_pTrack, tcAbsPTSMs, pvData, cbData, fFlags)); +} +#endif /* VBOX_WITH_LIBOPUS */ + +/** + * Writes a data block to the specified track. + * + * @returns IPRT 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. + */ +int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData) +{ + RT_NOREF(cbData); /* Only needed for assertions for now. */ + + int rc = RTCritSectEnter(&CurSeg.CritSect); + AssertRC(rc); + + WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack); + if (itTrack == CurSeg.mapTracks.end()) + { + RTCritSectLeave(&CurSeg.CritSect); + return VERR_NOT_FOUND; + } + + WebMTrack *pTrack = itTrack->second; + AssertPtr(pTrack); + + if (m_fInTracksSection) + { + subEnd(MkvElem_Tracks); + m_fInTracksSection = false; + } + + switch (pTrack->enmType) + { + + case WebMTrackType_Audio: + { +#ifdef VBOX_WITH_LIBOPUS + if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus) + { + Assert(cbData == sizeof(WebMWriter::BlockData_Opus)); + WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData; + rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs); + } + else +#endif /* VBOX_WITH_LIBOPUS */ + rc = VERR_NOT_SUPPORTED; + break; + } + + case WebMTrackType_Video: + { +#ifdef VBOX_WITH_LIBVPX + if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8) + { + Assert(cbData == sizeof(WebMWriter::BlockData_VP8)); + WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData; + rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt); + } + else +#endif /* VBOX_WITH_LIBVPX */ + rc = VERR_NOT_SUPPORTED; + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + int rc2 = RTCritSectLeave(&CurSeg.CritSect); + AssertRC(rc2); + + return rc; +} + +/** + * Processes a render queue. + * + * @returns IPRT 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 = CurSeg.CurCluster; + + /* Iterate through the block map. */ + WebMBlockMap::iterator it = pQueue->Map.begin(); + while (it != CurSeg.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 (CurSeg.cClusters == 0) + { + CurSeg.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 = CurSeg.tcAbsStartMs; + + Cluster.fOpen = true; + Cluster.uID = CurSeg.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 = CurSeg.mapTracks.begin(); + while (itTrack != CurSeg.mapTracks.end()) + { + pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart); + ++itTrack; + } + + CurSeg.lstCuePoints.push_back(pCuePoint); + + subStart(MkvElem_Cluster) + .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs - CurSeg.tcAbsStartMs); + + CurSeg.cClusters++; + + mapBlocks.fClusterStarted = true; + } + + Log2Func(("[C%RU64] SegTcAbsStartMs=%RU64, ClusterTcAbsStartMs=%RU64, ClusterTcAbsLastWrittenMs=%RU64, mapAbsPTSMs=%RU64\n", + Cluster.uID, CurSeg.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 rc2 = writeSimpleBlockEBML(pTrack, pBlock); + AssertRC(rc2); + + Cluster.cBlocks++; + Cluster.tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs; + + pTrack->cTotalBlocks++; + pTrack->tcAbsLastWrittenMs = Cluster.tcAbsLastWrittenMs; + + if (CurSeg.tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs) + CurSeg.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 = CurSeg.mapTracks.begin(); + while (itTrack != CurSeg.mapTracks.end()) + { + pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart); + ++itTrack; + } + + CurSeg.lstCuePoints.push_back(pCuePoint); + } + + delete pBlock; + pBlock = NULL; + + mapBlocks.Queue.pop(); + } + + Assert(mapBlocks.Queue.empty()); + + CurSeg.queueBlocks.Map.erase(it); + + it = CurSeg.queueBlocks.Map.begin(); + } + + Assert(CurSeg.queueBlocks.Map.empty()); + + pQueue->tsLastProcessedMs = RTTimeMilliTS(); + + return VINF_SUCCESS; +} + +/** + * Writes the WebM footer. + * + * @returns IPRT status code. + */ +int WebMWriter::writeFooter(void) +{ + AssertReturn(isOpen(), VERR_WRONG_ORDER); + + if (m_fInTracksSection) + { + subEnd(MkvElem_Tracks); + m_fInTracksSection = false; + } + + if (CurSeg.CurCluster.fOpen) + { + subEnd(MkvElem_Cluster); + CurSeg.CurCluster.fOpen = false; + } + + /* + * Write Cues element. + */ + CurSeg.offCues = RTFileTell(getFile()); + LogFunc(("Cues @ %RU64\n", CurSeg.offCues)); + + subStart(MkvElem_Cues); + + WebMCuePointList::iterator itCuePoint = CurSeg.lstCuePoints.begin(); + while (itCuePoint != CurSeg.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 - CurSeg.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 (CurSeg.offSeekInfo) + RTFileSeek(getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL); + else + CurSeg.offSeekInfo = RTFileTell(getFile()); + + LogFunc(("Seek Header @ %RU64\n", CurSeg.offSeekInfo)); + + subStart(MkvElem_SeekHead); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks) + .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8) + .subEnd(MkvElem_Seek); + + if (CurSeg.offCues) + LogFunc(("Updating Cues @ %RU64\n", CurSeg.offCues)); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues) + .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8) + .subEnd(MkvElem_Seek); + + subStart(MkvElem_Seek) + .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info) + .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8) + .subEnd(MkvElem_Seek); + + subEnd(MkvElem_SeekHead); + + /* + * Write the segment's info element. + */ + + /* Save offset of the segment's info element. */ + CurSeg.offInfo = RTFileTell(getFile()); + + LogFunc(("Info @ %RU64\n", CurSeg.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 = CurSeg.tcAbsLastWrittenMs - CurSeg.tcAbsStartMs; + + if (!CurSeg.lstCuePoints.empty()) + { + LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs)); + AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n")); + } + + subStart(MkvElem_Info) + .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.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..c03e2a69 --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxC.def @@ -0,0 +1,27 @@ +;; @file +; +; VBoxC DLL Definition File. +; + +; +; Copyright (C) 2006-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; + +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..a47c46b6 --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxC.rc @@ -0,0 +1,62 @@ +/* $Id: VBoxC.rc $ */ +/** @file + * VBoxC - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..1f3ca01e --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxClient-x86.def @@ -0,0 +1,26 @@ +; $Id: VBoxClient-x86.def $ +;; @file +; VBoxClient-x86 DLL Definition File. +; + +; +; Copyright (C) 2006-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; + +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..95d8803d --- /dev/null +++ b/src/VBox/Main/src-client/win/VBoxClient-x86.rc @@ -0,0 +1,63 @@ +/* $Id: VBoxClient-x86.rc $ */ +/** @file + * VBoxC - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..1522fc89 --- /dev/null +++ b/src/VBox/Main/src-client/win/dllmain.cpp @@ -0,0 +1,166 @@ +/* $Id: dllmain.cpp $ */ +/** @file + * VBoxC - COM DLL exports and DLL init/term. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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..cca31bee --- /dev/null +++ b/src/VBox/Main/src-client/win/precomp_vcc.h @@ -0,0 +1,39 @@ +/* $Id: precomp_vcc.h $ */ +/** @file + * VirtualBox COM - Visual C++ precompiled header for VBoxC. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + + +#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..dd8e7731 --- /dev/null +++ b/src/VBox/Main/src-client/xpcom/module.cpp @@ -0,0 +1,149 @@ +/* $Id: module.cpp $ */ +/** @file + * XPCOM module implementation functions + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..37117d7c --- /dev/null +++ b/src/VBox/Main/src-client/xpcom/precomp_gcc.h @@ -0,0 +1,43 @@ +/* $Id: precomp_gcc.h $ */ +/** @file + * VirtualBox COM - GNU C++ precompiled header for VBoxC. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +#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 + |