summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-client')
-rw-r--r--src/VBox/Main/src-client/AdditionsFacilityImpl.cpp231
-rw-r--r--src/VBox/Main/src-client/AudioDriver.cpp337
-rw-r--r--src/VBox/Main/src-client/BusAssignmentManager.cpp711
-rw-r--r--src/VBox/Main/src-client/ClientTokenHolder.cpp347
-rw-r--r--src/VBox/Main/src-client/CloudGateway.cpp309
-rw-r--r--src/VBox/Main/src-client/ConsoleImpl.cpp12082
-rw-r--r--src/VBox/Main/src-client/ConsoleImpl2.cpp6970
-rw-r--r--src/VBox/Main/src-client/ConsoleImplTeleporter.cpp1483
-rw-r--r--src/VBox/Main/src-client/ConsoleVRDPServer.cpp4059
-rw-r--r--src/VBox/Main/src-client/DisplayImpl.cpp3871
-rw-r--r--src/VBox/Main/src-client/DisplayImplLegacy.cpp1018
-rw-r--r--src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp196
-rw-r--r--src/VBox/Main/src-client/DrvAudioRec.cpp971
-rw-r--r--src/VBox/Main/src-client/DrvAudioVRDE.cpp823
-rw-r--r--src/VBox/Main/src-client/EBMLWriter.cpp275
-rw-r--r--src/VBox/Main/src-client/EmulatedUSBImpl.cpp678
-rw-r--r--src/VBox/Main/src-client/GuestCtrlImpl.cpp725
-rw-r--r--src/VBox/Main/src-client/GuestCtrlPrivate.cpp1985
-rw-r--r--src/VBox/Main/src-client/GuestDirectoryImpl.cpp501
-rw-r--r--src/VBox/Main/src-client/GuestDnDPrivate.cpp1625
-rw-r--r--src/VBox/Main/src-client/GuestDnDSourceImpl.cpp1715
-rw-r--r--src/VBox/Main/src-client/GuestDnDTargetImpl.cpp1789
-rw-r--r--src/VBox/Main/src-client/GuestFileImpl.cpp1890
-rw-r--r--src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp237
-rw-r--r--src/VBox/Main/src-client/GuestImpl.cpp1200
-rw-r--r--src/VBox/Main/src-client/GuestProcessImpl.cpp3010
-rw-r--r--src/VBox/Main/src-client/GuestSessionImpl.cpp4809
-rw-r--r--src/VBox/Main/src-client/GuestSessionImplTasks.cpp3363
-rw-r--r--src/VBox/Main/src-client/HGCM.cpp3038
-rw-r--r--src/VBox/Main/src-client/HGCMObjects.cpp286
-rw-r--r--src/VBox/Main/src-client/HGCMThread.cpp790
-rw-r--r--src/VBox/Main/src-client/KeyboardImpl.cpp547
-rw-r--r--src/VBox/Main/src-client/MachineDebuggerImpl.cpp1560
-rw-r--r--src/VBox/Main/src-client/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/MouseImpl.cpp1403
-rw-r--r--src/VBox/Main/src-client/PCIRawDevImpl.cpp230
-rw-r--r--src/VBox/Main/src-client/README.testing16
-rw-r--r--src/VBox/Main/src-client/Recording.cpp945
-rw-r--r--src/VBox/Main/src-client/RecordingCodec.cpp894
-rw-r--r--src/VBox/Main/src-client/RecordingInternals.cpp158
-rw-r--r--src/VBox/Main/src-client/RecordingStream.cpp1039
-rw-r--r--src/VBox/Main/src-client/RecordingUtils.cpp290
-rw-r--r--src/VBox/Main/src-client/RemoteUSBBackend.cpp1416
-rw-r--r--src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp314
-rw-r--r--src/VBox/Main/src-client/SessionImpl.cpp1339
-rw-r--r--src/VBox/Main/src-client/USBDeviceImpl.cpp351
-rw-r--r--src/VBox/Main/src-client/UsbCardReader.cpp1892
-rw-r--r--src/VBox/Main/src-client/UsbWebcamInterface.cpp492
-rw-r--r--src/VBox/Main/src-client/VBoxDriversRegister.cpp124
-rw-r--r--src/VBox/Main/src-client/VMMDevInterface.cpp1265
-rw-r--r--src/VBox/Main/src-client/VirtualBoxClientImpl.cpp833
-rw-r--r--src/VBox/Main/src-client/WebMWriter.cpp881
-rw-r--r--src/VBox/Main/src-client/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/win/VBoxC.def37
-rw-r--r--src/VBox/Main/src-client/win/VBoxC.rc72
-rw-r--r--src/VBox/Main/src-client/win/VBoxClient-x86.def36
-rw-r--r--src/VBox/Main/src-client/win/VBoxClient-x86.rc73
-rw-r--r--src/VBox/Main/src-client/win/dllmain.cpp176
-rw-r--r--src/VBox/Main/src-client/win/precomp_vcc.h49
-rw-r--r--src/VBox/Main/src-client/xpcom/Makefile.kup0
-rw-r--r--src/VBox/Main/src-client/xpcom/module.cpp159
-rw-r--r--src/VBox/Main/src-client/xpcom/precomp_gcc.h53
62 files changed, 77968 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..59ef98b5
--- /dev/null
+++ b/src/VBox/Main/src-client/AdditionsFacilityImpl.cpp
@@ -0,0 +1,231 @@
+/* $Id: AdditionsFacilityImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Additions facility class.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_ADDITIONSFACILITY
+#include "LoggingNew.h"
+
+#include "AdditionsFacilityImpl.h"
+#include "Global.h"
+
+#include "AutoCaller.h"
+
+
+/**
+ * @note We ASSUME that unknown is the first entry!
+ */
+/* static */
+const AdditionsFacility::FacilityInfo AdditionsFacility::s_aFacilityInfo[8] =
+{
+ { "Unknown", AdditionsFacilityType_None, AdditionsFacilityClass_None },
+ { "VirtualBox Base Driver", AdditionsFacilityType_VBoxGuestDriver, AdditionsFacilityClass_Driver },
+ { "Auto Logon", AdditionsFacilityType_AutoLogon, AdditionsFacilityClass_Feature },
+ { "VirtualBox System Service", AdditionsFacilityType_VBoxService, AdditionsFacilityClass_Service },
+ { "VirtualBox Desktop Integration", AdditionsFacilityType_VBoxTrayClient, AdditionsFacilityClass_Program },
+ { "Seamless Mode", AdditionsFacilityType_Seamless, AdditionsFacilityClass_Feature },
+ { "Graphics Mode", AdditionsFacilityType_Graphics, AdditionsFacilityClass_Feature },
+ { "Guest Monitor Attach", AdditionsFacilityType_MonitorAttach, AdditionsFacilityClass_Feature },
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(AdditionsFacility)
+
+HRESULT AdditionsFacility::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void AdditionsFacility::FinalRelease()
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT AdditionsFacility::init(Guest *a_pParent, AdditionsFacilityType_T a_enmFacility, AdditionsFacilityStatus_T a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ RT_NOREF(a_pParent); /** @todo r=bird: For locking perhaps? */
+ LogFlowThisFunc(("a_pParent=%p\n", a_pParent));
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Initialize the data: */
+ mData.mType = a_enmFacility;
+ mData.mStatus = a_enmStatus;
+ mData.mTimestamp = *a_pTimeSpecTS;
+ mData.mfFlags = a_fFlags;
+ mData.midxInfo = 0;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFacilityInfo); ++i)
+ if (s_aFacilityInfo[i].mType == a_enmFacility)
+ {
+ mData.midxInfo = i;
+ break;
+ }
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ *
+ * Called from FinalRelease().
+ */
+void AdditionsFacility::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT AdditionsFacility::getClassType(AdditionsFacilityClass_T *aClassType)
+{
+ LogFlowThisFuncEnter();
+
+ /* midxInfo is static, so no need to lock anything. */
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ *aClassType = s_aFacilityInfo[idxInfo].mClass;
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ /* midxInfo is static, so no need to lock anything. */
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ int vrc = aName.assignNoThrow(s_aFacilityInfo[idxInfo].mName);
+ return RT_SUCCESS(vrc) ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT AdditionsFacility::getLastUpdated(LONG64 *aLastUpdated)
+{
+ LogFlowThisFuncEnter();
+
+ /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aLastUpdated = RTTimeSpecGetMilli(&mData.mTimestamp);
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getStatus(AdditionsFacilityStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ /** @todo r=bird: Should take parent (Guest) lock here, see i_update(). */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aStatus = mData.mStatus;
+ return S_OK;
+}
+
+HRESULT AdditionsFacility::getType(AdditionsFacilityType_T *aType)
+{
+ LogFlowThisFuncEnter();
+
+ /* mType is static, so no need to lock anything. */
+ *aType = mData.mType;
+ return S_OK;
+}
+
+#if 0 /* unused */
+
+AdditionsFacilityType_T AdditionsFacility::i_getType() const
+{
+ return mData.mType;
+}
+
+AdditionsFacilityClass_T AdditionsFacility::i_getClass() const
+{
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ return s_aFacilityInfo[idxInfo].mClass;
+}
+
+const char *AdditionsFacility::i_getName() const
+{
+ size_t idxInfo = mData.midxInfo;
+ AssertStmt(idxInfo < RT_ELEMENTS(s_aFacilityInfo), idxInfo = 0);
+ return s_aFacilityInfo[idxInfo].mName;
+}
+
+#endif /* unused */
+
+/**
+ * @note Caller should read lock the Guest object.
+ */
+LONG64 AdditionsFacility::i_getLastUpdated() const
+{
+ return RTTimeSpecGetMilli(&mData.mTimestamp);
+}
+
+/**
+ * @note Caller should read lock the Guest object.
+ */
+AdditionsFacilityStatus_T AdditionsFacility::i_getStatus() const
+{
+ return mData.mStatus;
+}
+
+/**
+ * Method used by IGuest::facilityUpdate to make updates.
+ *
+ * @returns change indicator.
+ *
+ * @todo r=bird: Locking here isn't quite sane. While updating is serialized
+ * by the caller holding down the Guest object lock, this code doesn't
+ * serialize with this object. So, the read locking done in the getter
+ * methods is utterly pointless. OTOH, the getter methods only get
+ * single values, so there isn't really much to be worried about here,
+ * especially with 32-bit hosts no longer being supported.
+ */
+bool AdditionsFacility::i_update(AdditionsFacilityStatus_T a_enmStatus, uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ bool const fChanged = mData.mStatus != a_enmStatus;
+
+ mData.mTimestamp = *a_pTimeSpecTS;
+ mData.mStatus = a_enmStatus;
+ mData.mfFlags = a_fFlags;
+
+ return fChanged;
+}
+
diff --git a/src/VBox/Main/src-client/AudioDriver.cpp b/src/VBox/Main/src-client/AudioDriver.cpp
new file mode 100644
index 00000000..536df209
--- /dev/null
+++ b/src/VBox/Main/src-client/AudioDriver.cpp
@@ -0,0 +1,337 @@
+/* $Id: AudioDriver.cpp $ */
+/** @file
+ * VirtualBox audio base class for Main audio drivers.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include "LoggingNew.h"
+
+#include <VBox/log.h>
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmapi.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/vmmr3vtable.h>
+
+#include "AudioDriver.h"
+#include "ConsoleImpl.h"
+
+AudioDriver::AudioDriver(Console *pConsole)
+ : mpConsole(pConsole)
+ , mfAttached(false)
+{
+}
+
+
+AudioDriver::~AudioDriver(void)
+{
+}
+
+
+AudioDriver &AudioDriver::operator=(AudioDriver const &a_rThat) RT_NOEXCEPT
+{
+ mpConsole = a_rThat.mpConsole;
+ mCfg = a_rThat.mCfg;
+ mfAttached = a_rThat.mfAttached;
+
+ return *this;
+}
+
+
+/**
+ * Initializes the audio driver with a certain (device) configuration.
+ *
+ * @returns VBox status code.
+ * @param pCfg Audio driver configuration to use.
+ */
+int AudioDriver::InitializeConfig(AudioDriverCfg *pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ /* Sanity. */
+ AssertReturn(pCfg->strDev.isNotEmpty(), VERR_INVALID_PARAMETER);
+ AssertReturn(pCfg->uLUN != UINT8_MAX, VERR_INVALID_PARAMETER);
+ AssertReturn(pCfg->strName.isNotEmpty(), VERR_INVALID_PARAMETER);
+
+ /* Apply configuration. */
+ mCfg = *pCfg;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Attaches the driver via EMT, if configured.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle for talking to EMT.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pAutoLock The callers auto lock instance. Can be NULL if not
+ * locked.
+ */
+int AudioDriver::doAttachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock)
+{
+ if (!isConfigured())
+ return VINF_SUCCESS;
+
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)attachDriverOnEmt, 1, this);
+ if (vrc == VERR_TIMEOUT)
+ {
+ /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */
+ if (pAutoLock)
+ pAutoLock->release();
+
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ AssertRC(vrc);
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ return vrc;
+}
+
+
+/**
+ * Configures the audio driver (to CFGM) and attaches it to the audio chain.
+ * Does nothing if the audio driver already is attached.
+ *
+ * @returns VBox status code.
+ * @param pThis Audio driver to detach.
+ */
+/* static */
+DECLCALLBACK(int) AudioDriver::attachDriverOnEmt(AudioDriver *pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole);
+ Assert(ptrVM.isOk());
+
+ if (pThis->mfAttached) /* Already attached? Bail out. */
+ {
+ LogFunc(("%s: Already attached\n", pThis->mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ AudioDriverCfg *pCfg = &pThis->mCfg;
+
+ LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n",
+ pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN));
+
+ /* Detach the driver chain from the audio device first. */
+ int vrc = ptrVM.vtable()->pfnPDMR3DeviceDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN, 0 /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pThis->configure(pCfg->uLUN, true /* Attach */);
+ if (RT_SUCCESS(vrc))
+ vrc = ptrVM.vtable()->pfnPDMR3DriverAttach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN,
+ 0 /* fFlags */, NULL /* ppBase */);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pThis->mfAttached = true;
+ LogRel2(("%s: Driver attached (LUN #%u)\n", pCfg->strName.c_str(), pCfg->uLUN));
+ }
+ else
+ LogRel(("%s: Failed to attach audio driver, vrc=%Rrc\n", pCfg->strName.c_str(), vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Detatches the driver via EMT, if configured.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle for talking to EMT.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pAutoLock The callers auto lock instance. Can be NULL if not
+ * locked.
+ */
+int AudioDriver::doDetachDriverViaEmt(PUVM pUVM, PCVMMR3VTABLE pVMM, util::AutoWriteLock *pAutoLock)
+{
+ if (!isConfigured())
+ return VINF_SUCCESS;
+
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, VMCPUID_ANY, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)detachDriverOnEmt, 1, this);
+ if (vrc == VERR_TIMEOUT)
+ {
+ /* Release the lock before a blocking VMR3* call (EMT might wait for it, @bugref{7648})! */
+ if (pAutoLock)
+ pAutoLock->release();
+
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ AssertRC(vrc);
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ return vrc;
+}
+
+
+/**
+ * Detaches an already attached audio driver from the audio chain.
+ * Does nothing if the audio driver already is detached or not attached.
+ *
+ * @returns VBox status code.
+ * @param pThis Audio driver to detach.
+ */
+/* static */
+DECLCALLBACK(int) AudioDriver::detachDriverOnEmt(AudioDriver *pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!pThis->mfAttached) /* Not attached? Bail out. */
+ {
+ LogFunc(("%s: Not attached\n", pThis->mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ Console::SafeVMPtrQuiet ptrVM(pThis->mpConsole);
+ Assert(ptrVM.isOk());
+
+ AudioDriverCfg *pCfg = &pThis->mCfg;
+
+ Assert(pCfg->uLUN != UINT8_MAX);
+
+ LogFunc(("strName=%s, strDevice=%s, uInst=%u, uLUN=%u\n",
+ pCfg->strName.c_str(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN));
+
+ /* Destroy the entire driver chain for the specified LUN.
+ *
+ * Start with the "AUDIO" driver, as this driver serves as the audio connector between
+ * the device emulation and the select backend(s). */
+ int vrc = ptrVM.vtable()->pfnPDMR3DriverDetach(ptrVM.rawUVM(), pCfg->strDev.c_str(), pCfg->uInst, pCfg->uLUN,
+ "AUDIO", 0 /* iOccurrence */, 0 /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ vrc = pThis->configure(pCfg->uLUN, false /* Detach */);/** @todo r=bird: Illogical and from what I can tell pointless! */
+
+ if (RT_SUCCESS(vrc))
+ {
+ pThis->mfAttached = false;
+ LogRel2(("%s: Driver detached\n", pCfg->strName.c_str()));
+ }
+ else
+ LogRel(("%s: Failed to detach audio driver, vrc=%Rrc\n", pCfg->strName.c_str(), vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Configures the audio driver via CFGM.
+ *
+ * @returns VBox status code.
+ * @param uLUN LUN to attach driver to.
+ * @param fAttach Whether to attach or detach the driver configuration to CFGM.
+ *
+ * @thread EMT
+ */
+int AudioDriver::configure(unsigned uLUN, bool fAttach)
+{
+ Console::SafeVMPtrQuiet ptrVM(mpConsole);
+ AssertReturn(ptrVM.isOk(), VERR_INVALID_STATE);
+
+ PCFGMNODE pRoot = ptrVM.vtable()->pfnCFGMR3GetRootU(ptrVM.rawUVM());
+ AssertPtr(pRoot);
+ PCFGMNODE pDev0 = ptrVM.vtable()->pfnCFGMR3GetChildF(pRoot, "Devices/%s/%u/", mCfg.strDev.c_str(), mCfg.uInst);
+
+ if (!pDev0) /* No audio device configured? Bail out. */
+ {
+ LogRel2(("%s: No audio device configured, skipping to attach driver\n", mCfg.strName.c_str()));
+ return VINF_SUCCESS;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ PCFGMNODE pDevLun = ptrVM.vtable()->pfnCFGMR3GetChildF(pDev0, "LUN#%u/", uLUN);
+
+ if (fAttach)
+ {
+ do /* break "loop" */
+ {
+ AssertMsgBreakStmt(pDevLun, ("%s: Device LUN #%u not found\n", mCfg.strName.c_str(), uLUN), vrc = VERR_NOT_FOUND);
+
+ LogRel2(("%s: Configuring audio driver (to LUN #%u)\n", mCfg.strName.c_str(), uLUN));
+
+ ptrVM.vtable()->pfnCFGMR3RemoveNode(pDevLun); /* Remove LUN completely first. */
+
+ /* Insert new LUN configuration and build up the new driver chain. */
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNodeF(pDev0, &pDevLun, "LUN#%u/", uLUN); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertString(pDevLun, "Driver", "AUDIO"); AssertRCBreak(vrc);
+
+ PCFGMNODE pLunCfg;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "Config", &pLunCfg); AssertRCBreak(vrc);
+
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pLunCfg, "DriverName", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "InputEnabled", mCfg.fEnabledIn); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertInteger(pLunCfg, "OutputEnabled", mCfg.fEnabledOut); AssertRCBreak(vrc);
+
+ PCFGMNODE pAttachedDriver;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pDevLun, "AttachedDriver", &pAttachedDriver); AssertRCBreak(vrc);
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertStringF(pAttachedDriver, "Driver", "%s", mCfg.strName.c_str()); AssertRCBreak(vrc);
+ PCFGMNODE pAttachedDriverCfg;
+ vrc = ptrVM.vtable()->pfnCFGMR3InsertNode(pAttachedDriver, "Config", &pAttachedDriverCfg); AssertRCBreak(vrc);
+
+ /* Call the (virtual) method for driver-specific configuration. */
+ vrc = configureDriver(pAttachedDriverCfg, ptrVM.vtable()); AssertRCBreak(vrc);
+
+ } while (0);
+ }
+ else /* Detach */
+ {
+ LogRel2(("%s: Unconfiguring audio driver\n", mCfg.strName.c_str()));
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+#ifdef LOG_ENABLED
+ LogFunc(("%s: fAttach=%RTbool\n", mCfg.strName.c_str(), fAttach));
+ ptrVM.vtable()->pfnCFGMR3Dump(pDevLun);
+#endif
+ }
+ else
+ LogRel(("%s: %s audio driver failed with vrc=%Rrc\n", mCfg.strName.c_str(), fAttach ? "Configuring" : "Unconfiguring", vrc));
+
+ LogFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/BusAssignmentManager.cpp b/src/VBox/Main/src-client/BusAssignmentManager.cpp
new file mode 100644
index 00000000..a8d36d07
--- /dev/null
+++ b/src/VBox/Main/src-client/BusAssignmentManager.cpp
@@ -0,0 +1,711 @@
+/* $Id: BusAssignmentManager.cpp $ */
+/** @file
+ * VirtualBox bus slots assignment manager
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "LoggingNew.h"
+
+#include "BusAssignmentManager.h"
+
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/com/array.h>
+
+#include <map>
+#include <vector>
+#include <algorithm>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct DeviceAssignmentRule
+{
+ const char *pszName;
+ int iBus;
+ int iDevice;
+ int iFn;
+ int iPriority;
+};
+
+struct DeviceAliasRule
+{
+ const char *pszDevName;
+ const char *pszDevAlias;
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/* Those rules define PCI slots assignment */
+/** @note
+ * The EFI takes assumptions about PCI slot assignments which are different
+ * from the following tables in certain cases, for example the IDE device
+ * is assumed to be 00:01.1! */
+
+/* Device Bus Device Function Priority */
+
+/* Generic rules */
+static const DeviceAssignmentRule g_aGenericRules[] =
+{
+ /* VGA controller */
+ {"vga", 0, 2, 0, 0},
+
+ /* VMM device */
+ {"VMMDev", 0, 4, 0, 0},
+
+ /* Audio controllers */
+ {"ichac97", 0, 5, 0, 0},
+ {"hda", 0, 5, 0, 0},
+
+ /* Storage controllers */
+ {"buslogic", 0, 21, 0, 1},
+ {"lsilogicsas", 0, 22, 0, 1},
+ {"nvme", 0, 14, 0, 1},
+ {"virtio-scsi", 0, 15, 0, 1},
+
+ /* USB controllers */
+ {"usb-ohci", 0, 6, 0, 0},
+ {"usb-ehci", 0, 11, 0, 0},
+ {"usb-xhci", 0, 12, 0, 0},
+
+ /* ACPI controller */
+#if 0
+ // It really should be this for 440FX chipset (part of PIIX4 actually)
+ {"acpi", 0, 1, 3, 0},
+#else
+ {"acpi", 0, 7, 0, 0},
+#endif
+
+ /* Network controllers */
+ /* the first network card gets the PCI ID 3, the next 3 gets 8..10,
+ * next 4 get 16..19. In "VMWare compatibility" mode the IDs 3 and 17
+ * swap places, i.e. the first card goes to ID 17=0x11. */
+ {"nic", 0, 3, 0, 1},
+ {"nic", 0, 8, 0, 1},
+ {"nic", 0, 9, 0, 1},
+ {"nic", 0, 10, 0, 1},
+ {"nic", 0, 16, 0, 1},
+ {"nic", 0, 17, 0, 1},
+ {"nic", 0, 18, 0, 1},
+ {"nic", 0, 19, 0, 1},
+
+ /* ISA/LPC controller */
+ {"lpc", 0, 31, 0, 0},
+
+ { NULL, -1, -1, -1, 0}
+};
+
+/* PIIX3 chipset rules */
+static const DeviceAssignmentRule g_aPiix3Rules[] =
+{
+ {"piix3ide", 0, 1, 1, 0},
+ {"ahci", 0, 13, 0, 1},
+ {"lsilogic", 0, 20, 0, 1},
+ {"pcibridge", 0, 24, 0, 0},
+ {"pcibridge", 0, 25, 0, 0},
+ { NULL, -1, -1, -1, 0}
+};
+
+
+/* ICH9 chipset rules */
+static const DeviceAssignmentRule g_aIch9Rules[] =
+{
+ /* Host Controller */
+ {"i82801", 0, 30, 0, 0},
+
+ /* Those are functions of LPC at 00:1e:00 */
+ /**
+ * Please note, that for devices being functions, like we do here, device 0
+ * must be multifunction, i.e. have header type 0x80. Our LPC device is.
+ * Alternative approach is to assign separate slot to each device.
+ */
+ {"piix3ide", 0, 31, 1, 2},
+ {"ahci", 0, 31, 2, 2},
+ {"smbus", 0, 31, 3, 2},
+ {"usb-ohci", 0, 31, 4, 2},
+ {"usb-ehci", 0, 31, 5, 2},
+ {"thermal", 0, 31, 6, 2},
+
+ /* to make sure rule never used before rules assigning devices on it */
+ {"ich9pcibridge", 0, 24, 0, 10},
+ {"ich9pcibridge", 0, 25, 0, 10},
+ {"ich9pcibridge", 2, 24, 0, 9}, /* Bridges must be instantiated depth */
+ {"ich9pcibridge", 2, 25, 0, 9}, /* first (assumption in PDM and other */
+ {"ich9pcibridge", 4, 24, 0, 8}, /* places), so make sure that nested */
+ {"ich9pcibridge", 4, 25, 0, 8}, /* bridges are added to the last bridge */
+ {"ich9pcibridge", 6, 24, 0, 7}, /* only, avoiding the need to re-sort */
+ {"ich9pcibridge", 6, 25, 0, 7}, /* everything before starting the VM. */
+ {"ich9pcibridge", 8, 24, 0, 6},
+ {"ich9pcibridge", 8, 25, 0, 6},
+ {"ich9pcibridge", 10, 24, 0, 5},
+ {"ich9pcibridge", 10, 25, 0, 5},
+
+ /* Storage controllers */
+ {"ahci", 1, 0, 0, 0},
+ {"ahci", 1, 1, 0, 0},
+ {"ahci", 1, 2, 0, 0},
+ {"ahci", 1, 3, 0, 0},
+ {"ahci", 1, 4, 0, 0},
+ {"ahci", 1, 5, 0, 0},
+ {"ahci", 1, 6, 0, 0},
+ {"lsilogic", 1, 7, 0, 0},
+ {"lsilogic", 1, 8, 0, 0},
+ {"lsilogic", 1, 9, 0, 0},
+ {"lsilogic", 1, 10, 0, 0},
+ {"lsilogic", 1, 11, 0, 0},
+ {"lsilogic", 1, 12, 0, 0},
+ {"lsilogic", 1, 13, 0, 0},
+ {"buslogic", 1, 14, 0, 0},
+ {"buslogic", 1, 15, 0, 0},
+ {"buslogic", 1, 16, 0, 0},
+ {"buslogic", 1, 17, 0, 0},
+ {"buslogic", 1, 18, 0, 0},
+ {"buslogic", 1, 19, 0, 0},
+ {"buslogic", 1, 20, 0, 0},
+ {"lsilogicsas", 1, 21, 0, 0},
+ {"lsilogicsas", 1, 26, 0, 0},
+ {"lsilogicsas", 1, 27, 0, 0},
+ {"lsilogicsas", 1, 28, 0, 0},
+ {"lsilogicsas", 1, 29, 0, 0},
+ {"lsilogicsas", 1, 30, 0, 0},
+ {"lsilogicsas", 1, 31, 0, 0},
+
+ /* NICs */
+ {"nic", 2, 0, 0, 0},
+ {"nic", 2, 1, 0, 0},
+ {"nic", 2, 2, 0, 0},
+ {"nic", 2, 3, 0, 0},
+ {"nic", 2, 4, 0, 0},
+ {"nic", 2, 5, 0, 0},
+ {"nic", 2, 6, 0, 0},
+ {"nic", 2, 7, 0, 0},
+ {"nic", 2, 8, 0, 0},
+ {"nic", 2, 9, 0, 0},
+ {"nic", 2, 10, 0, 0},
+ {"nic", 2, 11, 0, 0},
+ {"nic", 2, 12, 0, 0},
+ {"nic", 2, 13, 0, 0},
+ {"nic", 2, 14, 0, 0},
+ {"nic", 2, 15, 0, 0},
+ {"nic", 2, 16, 0, 0},
+ {"nic", 2, 17, 0, 0},
+ {"nic", 2, 18, 0, 0},
+ {"nic", 2, 19, 0, 0},
+ {"nic", 2, 20, 0, 0},
+ {"nic", 2, 21, 0, 0},
+ {"nic", 2, 26, 0, 0},
+ {"nic", 2, 27, 0, 0},
+ {"nic", 2, 28, 0, 0},
+ {"nic", 2, 29, 0, 0},
+ {"nic", 2, 30, 0, 0},
+ {"nic", 2, 31, 0, 0},
+
+ /* Storage controller #2 (NVMe, virtio-scsi) */
+ {"nvme", 3, 0, 0, 0},
+ {"nvme", 3, 1, 0, 0},
+ {"nvme", 3, 2, 0, 0},
+ {"nvme", 3, 3, 0, 0},
+ {"nvme", 3, 4, 0, 0},
+ {"nvme", 3, 5, 0, 0},
+ {"nvme", 3, 6, 0, 0},
+ {"virtio-scsi", 3, 7, 0, 0},
+ {"virtio-scsi", 3, 8, 0, 0},
+ {"virtio-scsi", 3, 9, 0, 0},
+ {"virtio-scsi", 3, 10, 0, 0},
+ {"virtio-scsi", 3, 11, 0, 0},
+ {"virtio-scsi", 3, 12, 0, 0},
+ {"virtio-scsi", 3, 13, 0, 0},
+
+ { NULL, -1, -1, -1, 0}
+};
+
+
+#ifdef VBOX_WITH_IOMMU_AMD
+/*
+ * AMD IOMMU and LSI Logic controller rules.
+ *
+ * Since the PCI slot (BDF=00:20.0) of the LSI Logic controller
+ * conflicts with the SB I/O APIC, we assign the LSI Logic controller
+ * to device number 23 when the VM is configured for an AMD IOMMU.
+ */
+static const DeviceAssignmentRule g_aIch9IommuAmdRules[] =
+{
+ /* AMD IOMMU. */
+ {"iommu-amd", 0, 0, 0, 0},
+ /* AMD IOMMU: Reserved for southbridge I/O APIC. */
+ {"sb-ioapic", 0, 20, 0, 0},
+
+ /* Storage controller */
+ {"lsilogic", 0, 23, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+#endif
+
+#ifdef VBOX_WITH_IOMMU_INTEL
+/*
+ * Intel IOMMU.
+ * The VT-d misc, address remapping, system management device is
+ * located at BDF 0:5:0 on real hardware but we use 0:1:0 since that
+ * slot isn't used for anything else.
+ *
+ * While we could place the I/O APIC anywhere, we keep it consistent
+ * with the AMD IOMMU and we assign the LSI Logic controller to
+ * device number 23 (and I/O APIC at device 20).
+ */
+static const DeviceAssignmentRule g_aIch9IommuIntelRules[] =
+{
+ /* Intel IOMMU. */
+ {"iommu-intel", 0, 1, 0, 0},
+ /* Intel IOMMU: Reserved for I/O APIC. */
+ {"sb-ioapic", 0, 20, 0, 0},
+
+ /* Storage controller */
+ {"lsilogic", 0, 23, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+#endif
+
+/* LSI Logic Controller. */
+static const DeviceAssignmentRule g_aIch9LsiRules[] =
+{
+ /* Storage controller */
+ {"lsilogic", 0, 20, 0, 1},
+ { NULL, -1, -1, -1, 0}
+};
+
+/* Aliasing rules */
+static const DeviceAliasRule g_aDeviceAliases[] =
+{
+ {"e1000", "nic"},
+ {"pcnet", "nic"},
+ {"virtio-net", "nic"},
+ {"ahci", "storage"},
+ {"lsilogic", "storage"},
+ {"buslogic", "storage"},
+ {"lsilogicsas", "storage"},
+ {"nvme", "storage"},
+ {"virtio-scsi", "storage"}
+};
+
+
+
+/**
+ * Bus assignment manage state data.
+ * @internal
+ */
+struct BusAssignmentManager::State
+{
+ struct PCIDeviceRecord
+ {
+ char szDevName[32];
+ PCIBusAddress HostAddress;
+
+ PCIDeviceRecord(const char *pszName, PCIBusAddress aHostAddress)
+ {
+ RTStrCopy(this->szDevName, sizeof(szDevName), pszName);
+ this->HostAddress = aHostAddress;
+ }
+
+ PCIDeviceRecord(const char *pszName)
+ {
+ RTStrCopy(this->szDevName, sizeof(szDevName), pszName);
+ }
+
+ bool operator<(const PCIDeviceRecord &a) const
+ {
+ return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) < 0;
+ }
+
+ bool operator==(const PCIDeviceRecord &a) const
+ {
+ return RTStrNCmp(szDevName, a.szDevName, sizeof(szDevName)) == 0;
+ }
+ };
+
+ typedef std::map<PCIBusAddress,PCIDeviceRecord> PCIMap;
+ typedef std::vector<PCIBusAddress> PCIAddrList;
+ typedef std::vector<const DeviceAssignmentRule *> PCIRulesList;
+ typedef std::map<PCIDeviceRecord,PCIAddrList> ReversePCIMap;
+
+ volatile int32_t cRefCnt;
+ ChipsetType_T mChipsetType;
+ const char * mpszBridgeName;
+ IommuType_T mIommuType;
+ PCIMap mPCIMap;
+ ReversePCIMap mReversePCIMap;
+ PCVMMR3VTABLE mpVMM;
+
+ State()
+ : cRefCnt(1), mChipsetType(ChipsetType_Null), mpszBridgeName("unknownbridge"), mpVMM(NULL)
+ {}
+ ~State()
+ {}
+
+ HRESULT init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType);
+
+ HRESULT record(const char *pszName, PCIBusAddress& GuestAddress, PCIBusAddress HostAddress);
+ HRESULT autoAssign(const char *pszName, PCIBusAddress& Address);
+ bool checkAvailable(PCIBusAddress& Address);
+ bool findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address);
+
+ const char *findAlias(const char *pszName);
+ void addMatchingRules(const char *pszName, PCIRulesList& aList);
+ void listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached);
+};
+
+
+HRESULT BusAssignmentManager::State::init(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType)
+{
+ mpVMM = pVMM;
+
+ if (iommuType != IommuType_None)
+ {
+#if defined(VBOX_WITH_IOMMU_AMD) && defined(VBOX_WITH_IOMMU_INTEL)
+ Assert(iommuType == IommuType_AMD || iommuType == IommuType_Intel);
+#elif defined(VBOX_WITH_IOMMU_AMD)
+ Assert(iommuType == IommuType_AMD);
+#elif defined(VBOX_WITH_IOMMU_INTEL)
+ Assert(iommuType == IommuType_Intel);
+#endif
+ }
+
+ mChipsetType = chipsetType;
+ mIommuType = iommuType;
+ switch (chipsetType)
+ {
+ case ChipsetType_PIIX3:
+ mpszBridgeName = "pcibridge";
+ break;
+ case ChipsetType_ICH9:
+ mpszBridgeName = "ich9pcibridge";
+ break;
+ default:
+ mpszBridgeName = "unknownbridge";
+ AssertFailed();
+ break;
+ }
+ return S_OK;
+}
+
+HRESULT BusAssignmentManager::State::record(const char *pszName, PCIBusAddress& Address, PCIBusAddress HostAddress)
+{
+ PCIDeviceRecord devRec(pszName, HostAddress);
+
+ /* Remember address -> device mapping */
+ mPCIMap.insert(PCIMap::value_type(Address, devRec));
+
+ ReversePCIMap::iterator it = mReversePCIMap.find(devRec);
+ if (it == mReversePCIMap.end())
+ {
+ mReversePCIMap.insert(ReversePCIMap::value_type(devRec, PCIAddrList()));
+ it = mReversePCIMap.find(devRec);
+ }
+
+ /* Remember device name -> addresses mapping */
+ it->second.push_back(Address);
+
+ return S_OK;
+}
+
+bool BusAssignmentManager::State::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address)
+{
+ PCIDeviceRecord devRec(pszDevName);
+
+ ReversePCIMap::iterator it = mReversePCIMap.find(devRec);
+ if (it == mReversePCIMap.end())
+ return false;
+
+ if (iInstance >= (int)it->second.size())
+ return false;
+
+ Address = it->second[iInstance];
+ return true;
+}
+
+void BusAssignmentManager::State::addMatchingRules(const char *pszName, PCIRulesList& aList)
+{
+ size_t iRuleset, iRule;
+ const DeviceAssignmentRule *aArrays[3] = {g_aGenericRules, NULL, NULL};
+
+ switch (mChipsetType)
+ {
+ case ChipsetType_PIIX3:
+ aArrays[1] = g_aPiix3Rules;
+ break;
+ case ChipsetType_ICH9:
+ {
+ aArrays[1] = g_aIch9Rules;
+#ifdef VBOX_WITH_IOMMU_AMD
+ if (mIommuType == IommuType_AMD)
+ aArrays[2] = g_aIch9IommuAmdRules;
+ else
+#endif
+#ifdef VBOX_WITH_IOMMU_INTEL
+ if (mIommuType == IommuType_Intel)
+ aArrays[2] = g_aIch9IommuIntelRules;
+ else
+#endif
+ {
+ aArrays[2] = g_aIch9LsiRules;
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ break;
+ }
+
+ for (iRuleset = 0; iRuleset < RT_ELEMENTS(aArrays); iRuleset++)
+ {
+ if (aArrays[iRuleset] == NULL)
+ continue;
+
+ for (iRule = 0; aArrays[iRuleset][iRule].pszName != NULL; iRule++)
+ {
+ if (RTStrCmp(pszName, aArrays[iRuleset][iRule].pszName) == 0)
+ aList.push_back(&aArrays[iRuleset][iRule]);
+ }
+ }
+}
+
+const char *BusAssignmentManager::State::findAlias(const char *pszDev)
+{
+ for (size_t iAlias = 0; iAlias < RT_ELEMENTS(g_aDeviceAliases); iAlias++)
+ {
+ if (strcmp(pszDev, g_aDeviceAliases[iAlias].pszDevName) == 0)
+ return g_aDeviceAliases[iAlias].pszDevAlias;
+ }
+ return NULL;
+}
+
+static bool RuleComparator(const DeviceAssignmentRule *r1, const DeviceAssignmentRule *r2)
+{
+ return (r1->iPriority > r2->iPriority);
+}
+
+HRESULT BusAssignmentManager::State::autoAssign(const char *pszName, PCIBusAddress& Address)
+{
+ PCIRulesList matchingRules;
+
+ addMatchingRules(pszName, matchingRules);
+ const char *pszAlias = findAlias(pszName);
+ if (pszAlias)
+ addMatchingRules(pszAlias, matchingRules);
+
+ AssertMsg(matchingRules.size() > 0, ("No rule for %s(%s)\n", pszName, pszAlias));
+
+ stable_sort(matchingRules.begin(), matchingRules.end(), RuleComparator);
+
+ for (size_t iRule = 0; iRule < matchingRules.size(); iRule++)
+ {
+ const DeviceAssignmentRule *rule = matchingRules[iRule];
+
+ Address.miBus = rule->iBus;
+ Address.miDevice = rule->iDevice;
+ Address.miFn = rule->iFn;
+
+ if (checkAvailable(Address))
+ return S_OK;
+ }
+ AssertLogRelMsgFailed(("BusAssignmentManager: All possible candidate positions for %s exhausted\n", pszName));
+
+ return E_INVALIDARG;
+}
+
+bool BusAssignmentManager::State::checkAvailable(PCIBusAddress& Address)
+{
+ PCIMap::const_iterator it = mPCIMap.find(Address);
+
+ return (it == mPCIMap.end());
+}
+
+void BusAssignmentManager::State::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached)
+{
+ aAttached.resize(mPCIMap.size());
+
+ size_t i = 0;
+ PCIDeviceInfo dev;
+ for (PCIMap::const_iterator it = mPCIMap.begin(); it != mPCIMap.end(); ++it, ++i)
+ {
+ dev.strDeviceName = it->second.szDevName;
+ dev.guestAddress = it->first;
+ dev.hostAddress = it->second.HostAddress;
+ aAttached[i] = dev;
+ }
+}
+
+BusAssignmentManager::BusAssignmentManager()
+ : pState(NULL)
+{
+ pState = new State();
+ Assert(pState);
+}
+
+BusAssignmentManager::~BusAssignmentManager()
+{
+ if (pState)
+ {
+ delete pState;
+ pState = NULL;
+ }
+}
+
+BusAssignmentManager *BusAssignmentManager::createInstance(PCVMMR3VTABLE pVMM, ChipsetType_T chipsetType, IommuType_T iommuType)
+{
+ BusAssignmentManager *pInstance = new BusAssignmentManager();
+ pInstance->pState->init(pVMM, chipsetType, iommuType);
+ Assert(pInstance);
+ return pInstance;
+}
+
+void BusAssignmentManager::AddRef()
+{
+ ASMAtomicIncS32(&pState->cRefCnt);
+}
+
+void BusAssignmentManager::Release()
+{
+ if (ASMAtomicDecS32(&pState->cRefCnt) == 0)
+ delete this;
+}
+
+DECLINLINE(HRESULT) InsertConfigInteger(PCVMMR3VTABLE pVMM, PCFGMNODE pCfg, const char *pszName, uint64_t u64)
+{
+ int vrc = pVMM->pfnCFGMR3InsertInteger(pCfg, pszName, u64);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+
+ return S_OK;
+}
+
+DECLINLINE(HRESULT) InsertConfigNode(PCVMMR3VTABLE pVMM, PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild)
+{
+ int vrc = pVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild);
+ if (RT_FAILURE(vrc))
+ return E_INVALIDARG;
+
+ return S_OK;
+}
+
+
+HRESULT BusAssignmentManager::assignPCIDeviceImpl(const char *pszDevName,
+ PCFGMNODE pCfg,
+ PCIBusAddress& GuestAddress,
+ PCIBusAddress HostAddress,
+ bool fGuestAddressRequired)
+{
+ HRESULT hrc = S_OK;
+
+ if (!GuestAddress.valid())
+ hrc = pState->autoAssign(pszDevName, GuestAddress);
+ else
+ {
+ bool fAvailable = pState->checkAvailable(GuestAddress);
+
+ if (!fAvailable)
+ {
+ if (fGuestAddressRequired)
+ hrc = E_ACCESSDENIED;
+ else
+ hrc = pState->autoAssign(pszDevName, GuestAddress);
+ }
+ }
+
+ if (FAILED(hrc))
+ return hrc;
+
+ Assert(GuestAddress.valid() && pState->checkAvailable(GuestAddress));
+
+ hrc = pState->record(pszDevName, GuestAddress, HostAddress);
+ if (FAILED(hrc))
+ return hrc;
+
+ PCVMMR3VTABLE const pVMM = pState->mpVMM;
+ if (pCfg)
+ {
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIBusNo", GuestAddress.miBus);
+ if (FAILED(hrc))
+ return hrc;
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIDeviceNo", GuestAddress.miDevice);
+ if (FAILED(hrc))
+ return hrc;
+ hrc = InsertConfigInteger(pVMM, pCfg, "PCIFunctionNo", GuestAddress.miFn);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* Check if the bus is still unknown, i.e. the bridge to it is missing */
+ if ( GuestAddress.miBus > 0
+ && !hasPCIDevice(pState->mpszBridgeName, GuestAddress.miBus - 1))
+ {
+ PCFGMNODE pDevices = pVMM->pfnCFGMR3GetParent(pVMM->pfnCFGMR3GetParent(pCfg));
+ AssertLogRelMsgReturn(pDevices, ("BusAssignmentManager: cannot find base device configuration\n"), E_UNEXPECTED);
+ PCFGMNODE pBridges = pVMM->pfnCFGMR3GetChild(pDevices, "ich9pcibridge");
+ AssertLogRelMsgReturn(pBridges, ("BusAssignmentManager: cannot find bridge configuration base\n"), E_UNEXPECTED);
+
+ /* Device should be on a not yet existing bus, add it automatically */
+ for (int iBridge = 0; iBridge <= GuestAddress.miBus - 1; iBridge++)
+ {
+ if (!hasPCIDevice(pState->mpszBridgeName, iBridge))
+ {
+ PCIBusAddress BridgeGuestAddress;
+ hrc = pState->autoAssign(pState->mpszBridgeName, BridgeGuestAddress);
+ if (FAILED(hrc))
+ return hrc;
+ if (BridgeGuestAddress.miBus > iBridge)
+ AssertLogRelMsgFailedReturn(("BusAssignmentManager: cannot create bridge for bus %i because the possible parent bus positions are exhausted\n", iBridge + 1), E_UNEXPECTED);
+
+ PCFGMNODE pInst;
+ InsertConfigNode(pVMM, pBridges, Utf8StrFmt("%d", iBridge).c_str(), &pInst);
+ InsertConfigInteger(pVMM, pInst, "Trusted", 1);
+ hrc = assignPCIDevice(pState->mpszBridgeName, pInst);
+ if (FAILED(hrc))
+ return hrc;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+
+bool BusAssignmentManager::findPCIAddress(const char *pszDevName, int iInstance, PCIBusAddress& Address)
+{
+ return pState->findPCIAddress(pszDevName, iInstance, Address);
+}
+void BusAssignmentManager::listAttachedPCIDevices(std::vector<PCIDeviceInfo> &aAttached)
+{
+ pState->listAttachedPCIDevices(aAttached);
+}
diff --git a/src/VBox/Main/src-client/ClientTokenHolder.cpp b/src/VBox/Main/src-client/ClientTokenHolder.cpp
new file mode 100644
index 00000000..c2cb06d1
--- /dev/null
+++ b/src/VBox/Main/src-client/ClientTokenHolder.cpp
@@ -0,0 +1,347 @@
+/* $Id: ClientTokenHolder.cpp $ */
+/** @file
+ *
+ * VirtualBox API client session token holder (in the client process)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SESSION
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/semaphore.h>
+#include <iprt/process.h>
+
+#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER
+# include <errno.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/ipc.h>
+# include <sys/sem.h>
+#endif
+
+#include <VBox/com/defs.h>
+
+#include "ClientTokenHolder.h"
+#include "SessionImpl.h"
+
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/** client token holder thread */
+static DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser);
+#endif
+
+
+Session::ClientTokenHolder::ClientTokenHolder()
+{
+ AssertReleaseFailed();
+}
+
+Session::ClientTokenHolder::~ClientTokenHolder()
+{
+ /* release the client token */
+#if defined(RT_OS_WINDOWS)
+
+ if (mSem && mThreadSem)
+ {
+ /*
+ * tell the thread holding the token to release it;
+ * it will close mSem handle
+ */
+ ::SetEvent(mSem);
+ /* wait for the thread to finish */
+ ::WaitForSingleObject(mThreadSem, INFINITE);
+ ::CloseHandle(mThreadSem);
+
+ mThreadSem = NULL;
+ mSem = NULL;
+ mThread = NIL_RTTHREAD;
+ }
+
+#elif defined(RT_OS_OS2)
+
+ if (mThread != NIL_RTTHREAD)
+ {
+ Assert(mSem != NIL_RTSEMEVENT);
+
+ /* tell the thread holding the token to release it */
+ int vrc = RTSemEventSignal(mSem);
+ AssertRC(vrc == NO_ERROR);
+
+ /* wait for the thread to finish */
+ vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED);
+
+ mThread = NIL_RTTHREAD;
+ }
+
+ if (mSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mSem);
+ mSem = NIL_RTSEMEVENT;
+ }
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+
+ if (mSem >= 0)
+ {
+ ::sembuf sop = { 0, 1, SEM_UNDO };
+ ::semop(mSem, &sop, 1);
+
+ mSem = -1;
+ }
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+
+ if (!mToken.isNull())
+ {
+ mToken->Abandon();
+ mToken.setNull();
+ }
+
+#else
+# error "Port me!"
+#endif
+}
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+Session::ClientTokenHolder::ClientTokenHolder(const Utf8Str &strTokenId) :
+ mClientTokenId(strTokenId)
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+Session::ClientTokenHolder::ClientTokenHolder(IToken *aToken) :
+ mToken(aToken)
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+{
+#ifdef CTHSEMTYPE
+ mSem = CTHSEMARG;
+#endif
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ mThread = NIL_RTTHREAD;
+#endif
+
+#if defined(RT_OS_WINDOWS)
+ mThreadSem = CTHTHREADSEMARG;
+
+ /*
+ * Since there is no guarantee that the constructor and destructor will be
+ * called in the same thread, we need a separate thread to hold the token.
+ */
+
+ mThreadSem = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertMsgReturnVoid(mThreadSem,
+ ("Cannot create an event sem, err=%d", ::GetLastError()));
+
+ void *data[3];
+ data[0] = (void*)strTokenId.c_str();
+ data[1] = (void*)mThreadSem;
+ data[2] = 0; /* will get an output from the thread */
+
+ /* create a thread to hold the token until signalled to release it */
+ int vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void*)data, 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
+ AssertRCReturnVoid(vrc);
+
+ /* wait until thread init is completed */
+ DWORD wrc = ::WaitForSingleObject(mThreadSem, INFINITE);
+ AssertMsg(wrc == WAIT_OBJECT_0, ("Wait failed, err=%d\n", ::GetLastError()));
+ Assert(data[2]);
+
+ if (wrc == WAIT_OBJECT_0 && data[2])
+ {
+ /* memorize the event sem we should signal in close() */
+ mSem = (HANDLE)data[2];
+ }
+ else
+ {
+ ::CloseHandle(mThreadSem);
+ mThreadSem = NULL;
+ }
+#elif defined(RT_OS_OS2)
+ /*
+ * Since there is no guarantee that the constructor and destructor will be
+ * called in the same thread, we need a separate thread to hold the token.
+ */
+
+ int vrc = RTSemEventCreate(&mSem);
+ AssertRCReturnVoid(vrc);
+
+ void *data[3];
+ data[0] = (void*)strTokenId.c_str();
+ data[1] = (void*)mSem;
+ data[2] = (void*)false; /* will get the thread result here */
+
+ /* create a thread to hold the token until signalled to release it */
+ vrc = RTThreadCreate(&mThread, ClientTokenHolderThread, (void *) data,
+ 0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
+ AssertRCReturnVoid(vrc);
+ /* wait until thread init is completed */
+ vrc = RTThreadUserWait(mThread, RT_INDEFINITE_WAIT);
+ AssertReturnVoid(RT_SUCCESS(vrc) || vrc == VERR_INTERRUPTED);
+
+ /* the thread must succeed */
+ AssertReturnVoid((bool)data[2]);
+
+#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
+
+# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN
+ key_t key = RTStrToUInt32(strTokenId.c_str());
+ AssertMsgReturnVoid(key != 0,
+ ("Key value of 0 is not valid for client token"));
+# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+ char *pszSemName = NULL;
+ RTStrUtf8ToCurrentCP(&pszSemName, strTokenId);
+ key_t key = ::ftok(pszSemName, 'V');
+ RTStrFree(pszSemName);
+# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
+ int s = ::semget(key, 0, 0);
+ AssertMsgReturnVoid(s >= 0,
+ ("Cannot open semaphore, errno=%d", errno));
+
+ /* grab the semaphore */
+ ::sembuf sop = { 0, -1, SEM_UNDO };
+ int rv = ::semop(s, &sop, 1);
+ AssertMsgReturnVoid(rv == 0,
+ ("Cannot grab semaphore, errno=%d", errno));
+ mSem = s;
+
+#elif defined(VBOX_WITH_GENERIC_SESSION_WATCHER)
+
+ /* nothing to do */
+
+#else
+# error "Port me!"
+#endif
+}
+
+bool Session::ClientTokenHolder::isReady()
+{
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ return mSem != CTHSEMARG;
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ return !mToken.isNull();
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+}
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/** client token holder thread */
+DECLCALLBACK(int) ClientTokenHolderThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ LogFlowFuncEnter();
+
+ Assert(pvUser);
+
+ void **data = (void **)pvUser;
+
+# if defined(RT_OS_WINDOWS)
+ Utf8Str strSessionId = (const char *)data[0];
+ HANDLE initDoneSem = (HANDLE)data[1];
+
+ Bstr bstrSessionId(strSessionId);
+ HANDLE mutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, bstrSessionId.raw());
+
+ //AssertMsg(mutex, ("cannot open token, err=%u\n", ::GetLastError()));
+ AssertLogRelMsg(mutex, ("cannot open token %ls, err=%u\n", bstrSessionId.raw(), ::GetLastError()));
+ if (mutex)
+ {
+ /* grab the token */
+ DWORD wrc = ::WaitForSingleObject(mutex, 0);
+ AssertMsg(wrc == WAIT_OBJECT_0, ("cannot grab token, err=%d\n", wrc));
+ if (wrc == WAIT_OBJECT_0)
+ {
+ HANDLE finishSem = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertMsg(finishSem, ("cannot create event sem, err=%d\n", ::GetLastError()));
+ if (finishSem)
+ {
+ data[2] = (void*)finishSem;
+ /* signal we're done with init */
+ ::SetEvent(initDoneSem);
+ /* wait until we're signaled to release the token */
+ ::WaitForSingleObject(finishSem, INFINITE);
+ /* release the token */
+ LogFlow(("ClientTokenHolderThread(): releasing token...\n"));
+ BOOL fRc = ::ReleaseMutex(mutex);
+ AssertMsg(fRc, ("cannot release token, err=%d\n", ::GetLastError())); NOREF(fRc);
+ ::CloseHandle(mutex);
+ ::CloseHandle(finishSem);
+ }
+ }
+ }
+
+ /* signal we're done */
+ ::SetEvent(initDoneSem);
+# elif defined(RT_OS_OS2)
+ Utf8Str strSessionId = (const char *)data[0];
+ RTSEMEVENT finishSem = (RTSEMEVENT)data[1];
+
+ LogFlowFunc(("strSessionId='%s', finishSem=%p\n", strSessionId.c_str(), finishSem));
+
+ HMTX mutex = NULLHANDLE;
+ APIRET arc = ::DosOpenMutexSem((PSZ)strSessionId.c_str(), &mutex);
+ AssertMsg(arc == NO_ERROR, ("cannot open token, arc=%ld\n", arc));
+
+ if (arc == NO_ERROR)
+ {
+ /* grab the token */
+ LogFlowFunc(("grabbing token...\n"));
+ arc = ::DosRequestMutexSem(mutex, SEM_IMMEDIATE_RETURN);
+ AssertMsg(arc == NO_ERROR, ("cannot grab token, arc=%ld\n", arc));
+ if (arc == NO_ERROR)
+ {
+ /* store the answer */
+ data[2] = (void*)true;
+ /* signal we're done */
+ int vrc = RTThreadUserSignal(Thread);
+ AssertRC(vrc);
+
+ /* wait until we're signaled to release the token */
+ LogFlowFunc(("waiting for termination signal..\n"));
+ vrc = RTSemEventWait(finishSem, RT_INDEFINITE_WAIT);
+ Assert(arc == ERROR_INTERRUPT || ERROR_TIMEOUT);
+
+ /* release the token */
+ LogFlowFunc(("releasing token...\n"));
+ arc = ::DosReleaseMutexSem(mutex);
+ AssertMsg(arc == NO_ERROR, ("cannot release token, arc=%ld\n", arc));
+ }
+ ::DosCloseMutexSem(mutex);
+ }
+
+ /* store the answer */
+ data[1] = (void*)false;
+ /* signal we're done */
+ int vrc = RTThreadUserSignal(Thread);
+ AssertRC(vrc);
+# else
+# error "Port me!"
+# endif
+
+ LogFlowFuncLeave();
+
+ return 0;
+}
+#endif
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/CloudGateway.cpp b/src/VBox/Main/src-client/CloudGateway.cpp
new file mode 100644
index 00000000..ad7ec6bf
--- /dev/null
+++ b/src/VBox/Main/src-client/CloudGateway.cpp
@@ -0,0 +1,309 @@
+/* $Id: CloudGateway.cpp $ */
+/** @file
+ * Implementation of local and cloud gateway management.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+
+/* Make sure all the stdint.h macros are included - must come first! */
+#ifndef __STDC_LIMIT_MACROS
+# define __STDC_LIMIT_MACROS
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+# define __STDC_CONSTANT_MACROS
+#endif
+
+#include "LoggingNew.h"
+#include "ApplianceImpl.h"
+#include "CloudNetworkImpl.h"
+#include "CloudGateway.h"
+
+#include <iprt/http.h>
+#include <iprt/inifile.h>
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/vfs.h>
+#include <iprt/uri.h>
+#ifdef DEBUG
+#include <iprt/file.h>
+#include <VBox/com/utils.h>
+#endif
+
+#ifdef VBOX_WITH_LIBSSH
+/* Prevent inclusion of Winsock2.h */
+#define _WINSOCK2API_
+#include <libssh/libssh.h>
+#endif /* VBOX_WITH_LIBSSH */
+
+
+static HRESULT setMacAddress(const Utf8Str& str, RTMAC& mac)
+{
+ int vrc = RTNetStrToMacAddr(str.c_str(), &mac);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Invalid MAC address '%s'\n", str.c_str()));
+ return E_INVALIDARG;
+ }
+ return S_OK;
+}
+
+
+HRESULT GatewayInfo::setCloudMacAddress(const Utf8Str& mac)
+{
+ return setMacAddress(mac, mCloudMacAddress);
+}
+
+
+HRESULT GatewayInfo::setLocalMacAddress(const Utf8Str& mac)
+{
+ return setMacAddress(mac, mLocalMacAddress);
+}
+
+
+class CloudError
+{
+public:
+ CloudError(HRESULT hrc, const Utf8Str& strText) : mHrc(hrc), mText(strText) {};
+ HRESULT getRc() { return mHrc; };
+ Utf8Str getText() { return mText; };
+
+private:
+ HRESULT mHrc;
+ Utf8Str mText;
+};
+
+
+static void handleErrors(HRESULT hrc, const char *pszFormat, ...)
+{
+ if (FAILED(hrc))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ Utf8Str strError(pszFormat, va);
+ va_end(va);
+ LogRel(("CLOUD-NET: %s (hrc=%x)\n", strError.c_str(), hrc));
+ throw CloudError(hrc, strError);
+ }
+
+}
+
+
+class CloudClient
+{
+public:
+ CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile);
+ ~CloudClient() {};
+
+ void startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway);
+ void stopCloudGateway(const GatewayInfo& gateway);
+
+private:
+ ComPtr<ICloudProviderManager> mManager;
+ ComPtr<ICloudProvider> mProvider;
+ ComPtr<ICloudProfile> mProfile;
+ ComPtr<ICloudClient> mClient;
+};
+
+
+CloudClient::CloudClient(ComPtr<IVirtualBox> virtualBox, const Bstr& strProvider, const Bstr& strProfile)
+{
+ HRESULT hrc = virtualBox->COMGETTER(CloudProviderManager)(mManager.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud provider manager object");
+ hrc = mManager->GetProviderByShortName(strProvider.raw(), mProvider.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud provider '%ls'", strProvider.raw());
+ hrc = mProvider->GetProfileByName(strProfile.raw(), mProfile.asOutParam());
+ handleErrors(hrc, "Failed to obtain cloud profile '%ls'", strProfile.raw());
+ hrc = mProfile->CreateCloudClient(mClient.asOutParam());
+ handleErrors(hrc, "Failed to create cloud client");
+}
+
+
+void CloudClient::startCloudGateway(const ComPtr<ICloudNetwork> &network, GatewayInfo& gateway)
+{
+ ComPtr<IProgress> progress;
+ ComPtr<ICloudNetworkGatewayInfo> gatewayInfo;
+ HRESULT hrc = mClient->StartCloudNetworkGateway(network, Bstr(gateway.mPublicSshKey).raw(),
+ gatewayInfo.asOutParam(), progress.asOutParam());
+ handleErrors(hrc, "Failed to launch compute instance");
+ hrc = progress->WaitForCompletion(-1);
+ handleErrors(hrc, "Failed to launch compute instance (wait)");
+
+ Bstr instanceId;
+ hrc = gatewayInfo->COMGETTER(InstanceId)(instanceId.asOutParam());
+ handleErrors(hrc, "Failed to get launched compute instance id");
+ gateway.mGatewayInstanceId = instanceId;
+
+ Bstr publicIP;
+ hrc = gatewayInfo->COMGETTER(PublicIP)(publicIP.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway public IP address");
+ gateway.mCloudPublicIp = publicIP;
+
+ Bstr secondaryPublicIP;
+ hrc = gatewayInfo->COMGETTER(SecondaryPublicIP)(secondaryPublicIP.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway secondary public IP address");
+ gateway.mCloudSecondaryPublicIp = secondaryPublicIP;
+
+ Bstr macAddress;
+ hrc = gatewayInfo->COMGETTER(MacAddress)(macAddress.asOutParam());
+ handleErrors(hrc, "Failed to get cloud gateway public IP address");
+ gateway.setCloudMacAddress(macAddress);
+}
+
+
+void CloudClient::stopCloudGateway(const GatewayInfo& gateway)
+{
+ ComPtr<IProgress> progress;
+ HRESULT hrc = mClient->TerminateInstance(Bstr(gateway.mGatewayInstanceId).raw(), progress.asOutParam());
+ handleErrors(hrc, "Failed to terminate compute instance");
+#if 0
+ /* Someday we may want to wait until the cloud gateway has terminated. */
+ hrc = progress->WaitForCompletion(-1);
+ handleErrors(hrc, "Failed to terminate compute instance (wait)");
+#endif
+}
+
+
+HRESULT startCloudGateway(ComPtr<IVirtualBox> virtualBox, ComPtr<ICloudNetwork> network, GatewayInfo& gateway)
+{
+ HRESULT hrc = S_OK;
+
+ try {
+ hrc = network->COMGETTER(Provider)(gateway.mCloudProvider.asOutParam());
+ hrc = network->COMGETTER(Profile)(gateway.mCloudProfile.asOutParam());
+ CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile);
+ client.startCloudGateway(network, gateway);
+ }
+ catch (CloudError e)
+ {
+ hrc = e.getRc();
+ }
+
+ return hrc;
+}
+
+
+HRESULT stopCloudGateway(ComPtr<IVirtualBox> virtualBox, GatewayInfo& gateway)
+{
+ if (gateway.mGatewayInstanceId.isEmpty())
+ return S_OK;
+
+ LogRel(("CLOUD-NET: Terminating cloud gateway instance '%s'...\n", gateway.mGatewayInstanceId.c_str()));
+
+ HRESULT hrc = S_OK;
+ try {
+ CloudClient client(virtualBox, gateway.mCloudProvider, gateway.mCloudProfile);
+ client.stopCloudGateway(gateway);
+#if 0
+# ifdef DEBUG
+ char szKeyPath[RTPATH_MAX];
+
+ int vrc = GetVBoxUserHomeDirectory(szKeyPath, sizeof(szKeyPath), false /* fCreateDir */);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szKeyPath, sizeof(szKeyPath), "gateway-key.pem");
+ AssertRCReturn(vrc, vrc);
+ vrc = RTFileDelete(szKeyPath);
+ if (RT_FAILURE(vrc))
+ LogRel(("WARNING! Failed to delete private key %s with vrc=%d\n", szKeyPath, vrc));
+ }
+ else
+ LogRel(("WARNING! Failed to get VirtualBox user home directory with '%Rrc'\n", vrc));
+# endif /* DEBUG */
+#endif
+ }
+ catch (CloudError e)
+ {
+ hrc = e.getRc();
+ LogRel(("CLOUD-NET: Failed to terminate cloud gateway instance (hrc=%x).\n", hrc));
+ }
+ gateway.mGatewayInstanceId.setNull();
+ return hrc;
+}
+
+
+HRESULT generateKeys(GatewayInfo& gateway)
+{
+#ifndef VBOX_WITH_LIBSSH
+ RT_NOREF(gateway);
+ return E_NOTIMPL;
+#else /* VBOX_WITH_LIBSSH */
+ ssh_key single_use_key;
+ int iRcSsh = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &single_use_key);
+ if (iRcSsh != SSH_OK)
+ {
+ LogRel(("Failed to generate a key pair. iRcSsh = %d\n", iRcSsh));
+ return E_FAIL;
+ }
+
+ char *pstrKey = NULL;
+ iRcSsh = ssh_pki_export_privkey_base64(single_use_key, NULL, NULL, NULL, &pstrKey);
+ if (iRcSsh != SSH_OK)
+ {
+ LogRel(("Failed to export private key. iRcSsh = %d\n", iRcSsh));
+ return E_FAIL;
+ }
+ gateway.mPrivateSshKey = pstrKey;
+#if 0
+# ifdef DEBUG
+ char szConfigPath[RTPATH_MAX];
+
+ vrc = GetVBoxUserHomeDirectory(szConfigPath, sizeof(szConfigPath), false /* fCreateDir */);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szConfigPath, sizeof(szConfigPath), "gateway-key.pem");
+ AssertRCReturn(vrc, E_FAIL);
+ iRcSsh = ssh_pki_export_privkey_file(single_use_key, NULL, NULL, NULL, szConfigPath);
+ if (iRcSsh != SSH_OK)
+ {
+ LogRel(("Failed to export private key to %s with iRcSsh=%d\n", szConfigPath, iRcSsh));
+ return E_FAIL;
+ }
+# ifndef RT_OS_WINDOWS
+ vrc = RTPathSetMode(szConfigPath, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); /* Satisfy ssh client */
+ AssertRCReturn(vrc, E_FAIL);
+# endif
+ }
+ else
+ {
+ LogRel(("Failed to get VirtualBox user home directory with '%Rrc'\n", vrc));
+ return E_FAIL;
+ }
+# endif /* DEBUG */
+#endif
+ ssh_string_free_char(pstrKey);
+ pstrKey = NULL;
+ iRcSsh = ssh_pki_export_pubkey_base64(single_use_key, &pstrKey);
+ if (iRcSsh != SSH_OK)
+ {
+ LogRel(("Failed to export public key. iRcSsh = %d\n", iRcSsh));
+ return E_FAIL;
+ }
+ gateway.mPublicSshKey = Utf8StrFmt("ssh-rsa %s single-use-key", pstrKey);
+ ssh_string_free_char(pstrKey);
+ ssh_key_free(single_use_key);
+
+ return S_OK;
+#endif /* VBOX_WITH_LIBSSH */
+}
diff --git a/src/VBox/Main/src-client/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp
new file mode 100644
index 00000000..6d94eb1f
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImpl.cpp
@@ -0,0 +1,12082 @@
+/* $Id: ConsoleImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation
+ */
+
+/*
+ * Copyright (C) 2005-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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 "ConsoleSharedFolderImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "ProgressImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "VMMDev.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#include "BusAssignmentManager.h"
+#include "PCIDeviceAttachmentImpl.h"
+#include "EmulatedUSBImpl.h"
+#include "NvramStoreImpl.h"
+#include "StringifyEnums.h"
+
+#include "VBoxEvents.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#ifdef VBOX_WITH_RECORDING
+# include "Recording.h"
+#endif
+
+#include "CryptoUtils.h"
+
+#include <VBox/com/array.h>
+#include "VBox/com/ErrorInfo.h"
+#include <VBox/com/listeners.h>
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/base64.h>
+#include <iprt/memsafer.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/vmm.h>
+#include <VBox/vmm/pdmapi.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmasynccompletion.h>
+#include <VBox/vmm/pdmnetifs.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#ifdef VBOX_WITH_USB
+# include <VBox/vmm/pdmusb.h>
+#endif
+#ifdef VBOX_WITH_NETSHAPER
+# include <VBox/vmm/pdmnetshaper.h>
+#endif /* VBOX_WITH_NETSHAPER */
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/vusb.h>
+
+#include <VBox/VMMDev.h>
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+# include <VBox/HostServices/VBoxClipboardSvc.h>
+#endif
+#include <VBox/HostServices/DragAndDropSvc.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+# include <VBox/com/array.h>
+#endif
+
+#ifdef VBOX_OPENSSL_FIPS
+# include <openssl/crypto.h>
+#endif
+
+#include <set>
+#include <algorithm>
+#include <memory> // for auto_ptr
+#include <vector>
+#include <exception>// std::exception
+
+// VMTask and friends
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Task structure for asynchronous VM operations.
+ *
+ * Once created, the task structure adds itself as a Console caller. This means:
+ *
+ * 1. The user must check for #hrc() before using the created structure
+ * (e.g. passing it as a thread function argument). If #hrc() 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.hrc();
+ if (FAILED(mRC))
+ return;
+ if (aUsesVMPtr)
+ {
+ mpSafeVMPtr = new Console::SafeVMPtr(aConsole);
+ if (!mpSafeVMPtr->isOk())
+ mRC = mpSafeVMPtr->hrc();
+ }
+ }
+
+ virtual ~VMTask()
+ {
+ releaseVMCaller();
+ }
+
+ HRESULT hrc() const { return mRC; }
+ bool isOk() const { return SUCCEEDED(hrc()); }
+
+ /** Releases the VM caller before destruction. Not normally necessary. */
+ void releaseVMCaller()
+ {
+ if (mpSafeVMPtr)
+ {
+ delete mpSafeVMPtr;
+ mpSafeVMPtr = NULL;
+ }
+ }
+
+ const ComObjPtr<Console> mConsole;
+ AutoCaller mConsoleCaller;
+ const ComObjPtr<Progress> mProgress;
+ Utf8Str mErrorMsg;
+ const ComPtr<IProgress> mServerProgress;
+
+private:
+ HRESULT mRC;
+ Console::SafeVMPtr *mpSafeVMPtr;
+};
+
+
+class VMPowerUpTask : public VMTask
+{
+public:
+ VMPowerUpTask(Console *aConsole,
+ Progress *aProgress)
+ : VMTask(aConsole, aProgress, NULL /* aServerProgress */, false /* aUsesVMPtr */)
+ , mpfnConfigConstructor(NULL)
+ , mStartPaused(false)
+ , mTeleporterEnabled(FALSE)
+ , m_pKeyStore(NULL)
+ {
+ m_strTaskName = "VMPwrUp";
+ }
+
+ PFNCFGMCONSTRUCTOR mpfnConfigConstructor;
+ Utf8Str mSavedStateFile;
+ Utf8Str mKeyStore;
+ Utf8Str mKeyId;
+ Console::SharedFolderDataMap mSharedFolders;
+ bool mStartPaused;
+ BOOL mTeleporterEnabled;
+ SecretKeyStore *m_pKeyStore;
+
+ /* array of progress objects for hard disk reset operations */
+ typedef std::list<ComPtr<IProgress> > ProgressList;
+ ProgressList hardDiskProgresses;
+
+ void handler()
+ {
+ Console::i_powerUpThreadTask(this);
+ }
+
+};
+
+class VMPowerDownTask : public VMTask
+{
+public:
+ VMPowerDownTask(Console *aConsole,
+ const ComPtr<IProgress> &aServerProgress)
+ : VMTask(aConsole, NULL /* aProgress */, aServerProgress,
+ true /* aUsesVMPtr */)
+ {
+ m_strTaskName = "VMPwrDwn";
+ }
+
+ void handler()
+ {
+ Console::i_powerDownThreadTask(this);
+ }
+};
+
+// Handler for global events
+////////////////////////////////////////////////////////////////////////////////
+inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType);
+
+class VmEventListener
+{
+public:
+ VmEventListener()
+ {}
+
+
+ HRESULT init(Console *aConsole)
+ {
+ mConsole = aConsole;
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ virtual ~VmEventListener()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch(aType)
+ {
+ case VBoxEventType_OnNATRedirect:
+ {
+ ComPtr<IMachine> pMachine = mConsole->i_machine();
+ ComPtr<INATRedirectEvent> pNREv = aEvent;
+ Assert(pNREv);
+
+ Bstr id;
+ HRESULT hrc = pNREv->COMGETTER(MachineId)(id.asOutParam());
+ AssertComRC(hrc);
+ if (id != mConsole->i_getId())
+ break;
+
+ /* now we can operate with redirects */
+ NATProtocol_T proto = (NATProtocol_T)0;
+ pNREv->COMGETTER(Proto)(&proto);
+ BOOL fRemove;
+ pNREv->COMGETTER(Remove)(&fRemove);
+ Bstr hostIp;
+ pNREv->COMGETTER(HostIP)(hostIp.asOutParam());
+ LONG hostPort = 0;
+ pNREv->COMGETTER(HostPort)(&hostPort);
+ Bstr guestIp;
+ pNREv->COMGETTER(GuestIP)(guestIp.asOutParam());
+ LONG guestPort = 0;
+ pNREv->COMGETTER(GuestPort)(&guestPort);
+ ULONG ulSlot;
+ hrc = pNREv->COMGETTER(Slot)(&ulSlot);
+ AssertComRCBreak(hrc, RT_NOTHING);
+ mConsole->i_onNATRedirectRuleChanged(ulSlot, fRemove, proto, hostIp.raw(), hostPort, guestIp.raw(), guestPort);
+ break;
+ }
+
+ case VBoxEventType_OnHostNameResolutionConfigurationChange:
+ {
+ mConsole->i_onNATDnsChanged();
+ break;
+ }
+
+ case VBoxEventType_OnHostPCIDevicePlug:
+ {
+ // handle if needed
+ break;
+ }
+
+ case VBoxEventType_OnExtraDataChanged:
+ {
+ ComPtr<IExtraDataChangedEvent> pEDCEv = aEvent;
+ Bstr strMachineId;
+ HRESULT hrc = pEDCEv->COMGETTER(MachineId)(strMachineId.asOutParam());
+ if (FAILED(hrc)) break;
+
+ Bstr strKey;
+ hrc = pEDCEv->COMGETTER(Key)(strKey.asOutParam());
+ if (FAILED(hrc)) break;
+
+ Bstr strVal;
+ hrc = pEDCEv->COMGETTER(Value)(strVal.asOutParam());
+ if (FAILED(hrc)) break;
+
+ mConsole->i_onExtraDataChange(strMachineId.raw(), strKey.raw(), strVal.raw());
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+private:
+ ComObjPtr<Console> mConsole;
+};
+
+typedef ListenerImpl<VmEventListener, Console*> VmEventListenerImpl;
+
+
+VBOX_LISTENER_DECLARE(VmEventListenerImpl)
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Console::Console()
+ : mSavedStateDataLoaded(false)
+ , mConsoleVRDPServer(NULL)
+ , mfVRDEChangeInProcess(false)
+ , mfVRDEChangePending(false)
+ , mhModVMM(NIL_RTLDRMOD)
+ , mpVMM(NULL)
+ , mpUVM(NULL)
+ , mVMCallers(0)
+ , mVMZeroCallersSem(NIL_RTSEMEVENT)
+ , mVMDestroying(false)
+ , mVMPoweredOff(false)
+ , mVMIsAlreadyPoweringOff(false)
+ , mfSnapshotFolderSizeWarningShown(false)
+ , mfSnapshotFolderExt4WarningShown(false)
+ , mfSnapshotFolderDiskTypeShown(false)
+ , mfVMHasUsbController(false)
+ , mfTurnResetIntoPowerOff(false)
+ , mfPowerOffCausedByReset(false)
+ , mpVmm2UserMethods(NULL)
+ , m_pVMMDev(NULL)
+ , mAudioVRDE(NULL)
+#ifdef VBOX_WITH_USB_CARDREADER
+ , mUsbCardReader(NULL)
+#endif
+ , mBusMgr(NULL)
+ , mLedLock(LOCKCLASS_LISTOFOTHEROBJECTS /* must be higher than LOCKCLASS_OTHEROBJECT */)
+ , muLedGen(0)
+ , muLedTypeGen(0)
+ , mcLedSets(0)
+ , m_pKeyStore(NULL)
+ , mpIfSecKey(NULL)
+ , mpIfSecKeyHlp(NULL)
+ , mVMStateChangeCallbackDisabled(false)
+ , mfUseHostClipboard(true)
+ , mMachineState(MachineState_PoweredOff)
+ , mhLdrModCrypto(NIL_RTLDRMOD)
+ , mcRefsCrypto(0)
+ , mpCryptoIf(NULL)
+{
+ RT_ZERO(maLedSets);
+ RT_ZERO(maLedTypes);
+}
+
+Console::~Console()
+{}
+
+HRESULT Console::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ MYVMM2USERMETHODS *pVmm2UserMethods = (MYVMM2USERMETHODS *)RTMemAllocZ(sizeof(*mpVmm2UserMethods) + sizeof(Console *));
+ if (!pVmm2UserMethods)
+ return E_OUTOFMEMORY;
+ pVmm2UserMethods->u32Magic = VMM2USERMETHODS_MAGIC;
+ pVmm2UserMethods->u32Version = VMM2USERMETHODS_VERSION;
+ pVmm2UserMethods->pfnSaveState = Console::i_vmm2User_SaveState;
+ pVmm2UserMethods->pfnNotifyEmtInit = Console::i_vmm2User_NotifyEmtInit;
+ pVmm2UserMethods->pfnNotifyEmtTerm = Console::i_vmm2User_NotifyEmtTerm;
+ pVmm2UserMethods->pfnNotifyPdmtInit = Console::i_vmm2User_NotifyPdmtInit;
+ pVmm2UserMethods->pfnNotifyPdmtTerm = Console::i_vmm2User_NotifyPdmtTerm;
+ pVmm2UserMethods->pfnNotifyResetTurnedIntoPowerOff = Console::i_vmm2User_NotifyResetTurnedIntoPowerOff;
+ pVmm2UserMethods->pfnQueryGenericObject = Console::i_vmm2User_QueryGenericObject;
+ pVmm2UserMethods->u32EndMagic = VMM2USERMETHODS_MAGIC;
+ pVmm2UserMethods->pConsole = this;
+ mpVmm2UserMethods = pVmm2UserMethods;
+
+ MYPDMISECKEY *pIfSecKey = (MYPDMISECKEY *)RTMemAllocZ(sizeof(*mpIfSecKey) + sizeof(Console *));
+ if (!pIfSecKey)
+ return E_OUTOFMEMORY;
+ pIfSecKey->pfnKeyRetain = Console::i_pdmIfSecKey_KeyRetain;
+ pIfSecKey->pfnKeyRelease = Console::i_pdmIfSecKey_KeyRelease;
+ pIfSecKey->pfnPasswordRetain = Console::i_pdmIfSecKey_PasswordRetain;
+ pIfSecKey->pfnPasswordRelease = Console::i_pdmIfSecKey_PasswordRelease;
+ pIfSecKey->pConsole = this;
+ mpIfSecKey = pIfSecKey;
+
+ MYPDMISECKEYHLP *pIfSecKeyHlp = (MYPDMISECKEYHLP *)RTMemAllocZ(sizeof(*mpIfSecKeyHlp) + sizeof(Console *));
+ if (!pIfSecKeyHlp)
+ return E_OUTOFMEMORY;
+ pIfSecKeyHlp->pfnKeyMissingNotify = Console::i_pdmIfSecKeyHlp_KeyMissingNotify;
+ pIfSecKeyHlp->pConsole = this;
+ mpIfSecKeyHlp = pIfSecKeyHlp;
+
+ mRemoteUsbIf.pvUser = this;
+ mRemoteUsbIf.pfnQueryRemoteUsbBackend = Console::i_usbQueryRemoteUsbBackend;
+
+ return BaseFinalConstruct();
+}
+
+void Console::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/** @todo r=bird: aLockType is always LockType_VM. */
+HRESULT Console::initWithMachine(IMachine *aMachine, IInternalMachineControl *aControl, LockType_T aLockType)
+{
+ AssertReturn(aMachine && aControl, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aMachine=%p, aControl=%p\n", aMachine, aControl));
+
+ unconst(mMachine) = aMachine;
+ unconst(mControl) = aControl;
+
+ /* Cache essential properties and objects, and create child objects */
+
+ HRESULT hrc = mMachine->COMGETTER(State)(&mMachineState);
+ AssertComRCReturnRC(hrc);
+
+ hrc = mMachine->COMGETTER(Id)(mstrUuid.asOutParam());
+ AssertComRCReturnRC(hrc);
+
+#ifdef VBOX_WITH_EXTPACK
+ unconst(mptrExtPackManager).createObject();
+ hrc = mptrExtPackManager->initExtPackManager(NULL, VBOXEXTPACKCTX_VM_PROCESS);
+ AssertComRCReturnRC(hrc);
+#endif
+
+ // Event source may be needed by other children
+ unconst(mEventSource).createObject();
+ hrc = mEventSource->init();
+ AssertComRCReturnRC(hrc);
+
+ mcAudioRefs = 0;
+ mcVRDPClients = 0;
+ mu32SingleRDPClientId = 0;
+ mcGuestCredentialsProvided = false;
+
+ /* Now the VM specific parts */
+ /** @todo r=bird: aLockType is always LockType_VM. */
+ if (aLockType == LockType_VM)
+ {
+ /* Load the VMM. We won't continue without it being successfully loaded here. */
+ hrc = i_loadVMM();
+ AssertComRCReturnRC(hrc);
+
+ hrc = mMachine->COMGETTER(VRDEServer)(unconst(mVRDEServer).asOutParam());
+ AssertComRCReturnRC(hrc);
+
+ unconst(mGuest).createObject();
+ hrc = mGuest->init(this);
+ AssertComRCReturnRC(hrc);
+
+ ULONG cCpus = 1;
+ hrc = mMachine->COMGETTER(CPUCount)(&cCpus);
+ mGuest->i_setCpuCount(cCpus);
+
+ unconst(mKeyboard).createObject();
+ hrc = mKeyboard->init(this);
+ AssertComRCReturnRC(hrc);
+
+ unconst(mMouse).createObject();
+ hrc = mMouse->init(this);
+ AssertComRCReturnRC(hrc);
+
+ unconst(mDisplay).createObject();
+ hrc = mDisplay->init(this);
+ AssertComRCReturnRC(hrc);
+
+ unconst(mVRDEServerInfo).createObject();
+ hrc = mVRDEServerInfo->init(this);
+ AssertComRCReturnRC(hrc);
+
+ unconst(mEmulatedUSB).createObject();
+ hrc = mEmulatedUSB->init(this);
+ AssertComRCReturnRC(hrc);
+
+ /* Init the NVRAM store. */
+ ComPtr<INvramStore> pNvramStore;
+ hrc = aMachine->COMGETTER(NonVolatileStore)(pNvramStore.asOutParam());
+ AssertComRCReturnRC(hrc);
+
+ Bstr strNonVolatilePath;
+ pNvramStore->COMGETTER(NonVolatileStorageFile)(strNonVolatilePath.asOutParam());
+
+ unconst(mptrNvramStore).createObject();
+ hrc = mptrNvramStore->init(this, strNonVolatilePath);
+ AssertComRCReturnRC(hrc);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ Bstr bstrNvramKeyId;
+ Bstr bstrNvramKeyStore;
+ hrc = pNvramStore->COMGETTER(KeyId)(bstrNvramKeyId.asOutParam());
+ AssertComRCReturnRC(hrc);
+ hrc = pNvramStore->COMGETTER(KeyStore)(bstrNvramKeyStore.asOutParam());
+ AssertComRCReturnRC(hrc);
+ const Utf8Str strNvramKeyId(bstrNvramKeyId);
+ const Utf8Str strNvramKeyStore(bstrNvramKeyStore);
+ mptrNvramStore->i_updateEncryptionSettings(strNvramKeyId, strNvramKeyStore);
+#endif
+
+ /* Grab global and machine shared folder lists */
+
+ hrc = i_fetchSharedFolders(true /* aGlobal */);
+ AssertComRCReturnRC(hrc);
+ hrc = i_fetchSharedFolders(false /* aGlobal */);
+ AssertComRCReturnRC(hrc);
+
+ /* Create other child objects */
+
+ unconst(mConsoleVRDPServer) = new ConsoleVRDPServer(this);
+ AssertReturn(mConsoleVRDPServer, E_FAIL);
+
+ /* Figure out size of meAttachmentType vector */
+ ComPtr<IVirtualBox> pVirtualBox;
+ hrc = aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(hrc);
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ChipsetType_T chipsetType = ChipsetType_PIIX3;
+ aMachine->COMGETTER(ChipsetType)(&chipsetType);
+ ULONG maxNetworkAdapters = 0;
+ if (pSystemProperties)
+ pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters);
+ meAttachmentType.resize(maxNetworkAdapters);
+ for (ULONG slot = 0; slot < maxNetworkAdapters; ++slot)
+ meAttachmentType[slot] = NetworkAttachmentType_Null;
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ unconst(mAudioVRDE) = new AudioVRDE(this);
+ AssertReturn(mAudioVRDE, E_FAIL);
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ unconst(mRecording.mAudioRec) = new AudioVideoRec(this);
+ AssertReturn(mRecording.mAudioRec, E_FAIL);
+#endif
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ unconst(mUsbCardReader) = new UsbCardReader(this);
+ AssertReturn(mUsbCardReader, E_FAIL);
+#endif
+
+ m_cDisksPwProvided = 0;
+ m_cDisksEncrypted = 0;
+
+ unconst(m_pKeyStore) = new SecretKeyStore(true /* fKeyBufNonPageable */);
+ AssertReturn(m_pKeyStore, E_FAIL);
+
+ /* VirtualBox events registration. */
+ {
+ ComPtr<IEventSource> pES;
+ hrc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam());
+ AssertComRC(hrc);
+ 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);
+ hrc = pES->RegisterListener(aVmListener, ComSafeArrayAsInParam(eventTypes), true);
+ AssertComRC(hrc);
+ }
+ }
+
+ /* 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(hrc))
+ 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 hrc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc) && !pVirtualBox.isNull())
+ {
+ hrc = pVirtualBox->COMGETTER(EventSource)(pES.asOutParam());
+ AssertComRC(hrc);
+ if (!pES.isNull())
+ {
+ hrc = pES->UnregisterListener(mVmListener);
+ AssertComRC(hrc);
+ }
+ }
+ mVmListener.setNull();
+ }
+
+ /* power down the VM if necessary */
+ if (mpUVM)
+ {
+ i_powerDown();
+ Assert(mpUVM == NULL);
+ }
+
+ if (mVMZeroCallersSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mVMZeroCallersSem);
+ mVMZeroCallersSem = NIL_RTSEMEVENT;
+ }
+
+ if (mpVmm2UserMethods)
+ {
+ RTMemFree((void *)mpVmm2UserMethods);
+ mpVmm2UserMethods = NULL;
+ }
+
+ if (mpIfSecKey)
+ {
+ RTMemFree((void *)mpIfSecKey);
+ mpIfSecKey = NULL;
+ }
+
+ if (mpIfSecKeyHlp)
+ {
+ RTMemFree((void *)mpIfSecKeyHlp);
+ mpIfSecKeyHlp = NULL;
+ }
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ if (mUsbCardReader)
+ {
+ delete mUsbCardReader;
+ unconst(mUsbCardReader) = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ if (mAudioVRDE)
+ {
+ delete mAudioVRDE;
+ unconst(mAudioVRDE) = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_RECORDING
+ i_recordingDestroy();
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ if (mRecording.mAudioRec)
+ {
+ delete mRecording.mAudioRec;
+ unconst(mRecording.mAudioRec) = NULL;
+ }
+# endif
+#endif /* VBOX_WITH_RECORDING */
+
+ // if the VM had a VMMDev with an HGCM thread, then remove that here
+ if (m_pVMMDev)
+ {
+ delete m_pVMMDev;
+ unconst(m_pVMMDev) = NULL;
+ }
+
+ if (mBusMgr)
+ {
+ mBusMgr->Release();
+ mBusMgr = NULL;
+ }
+
+ if (m_pKeyStore)
+ {
+ delete m_pKeyStore;
+ unconst(m_pKeyStore) = NULL;
+ }
+
+ m_mapGlobalSharedFolders.clear();
+ m_mapMachineSharedFolders.clear();
+ m_mapSharedFolders.clear(); // console instances
+
+ mRemoteUSBDevices.clear();
+ mUSBDevices.clear();
+
+ if (mVRDEServerInfo)
+ {
+ mVRDEServerInfo->uninit();
+ unconst(mVRDEServerInfo).setNull();
+ }
+
+ if (mEmulatedUSB)
+ {
+ mEmulatedUSB->uninit();
+ unconst(mEmulatedUSB).setNull();
+ }
+
+ if (mDebugger)
+ {
+ mDebugger->uninit();
+ unconst(mDebugger).setNull();
+ }
+
+ if (mDisplay)
+ {
+ mDisplay->uninit();
+ unconst(mDisplay).setNull();
+ }
+
+ if (mMouse)
+ {
+ mMouse->uninit();
+ unconst(mMouse).setNull();
+ }
+
+ if (mKeyboard)
+ {
+ mKeyboard->uninit();
+ unconst(mKeyboard).setNull();
+ }
+
+ if (mGuest)
+ {
+ mGuest->uninit();
+ unconst(mGuest).setNull();
+ }
+
+ if (mConsoleVRDPServer)
+ {
+ delete mConsoleVRDPServer;
+ unconst(mConsoleVRDPServer) = NULL;
+ }
+
+ if (mptrNvramStore)
+ {
+ mptrNvramStore->uninit();
+ unconst(mptrNvramStore).setNull();
+ }
+
+ unconst(mVRDEServer).setNull();
+
+ unconst(mControl).setNull();
+ unconst(mMachine).setNull();
+
+ // we don't perform uninit() as it's possible that some pending event refers to this source
+ unconst(mEventSource).setNull();
+
+#ifdef VBOX_WITH_EXTPACK
+ unconst(mptrExtPackManager).setNull();
+#endif
+
+ /* Unload the VMM. */
+ mpVMM = NULL;
+ if (mhModVMM != NIL_RTLDRMOD)
+ {
+ RTLdrClose(mhModVMM);
+ mhModVMM = NIL_RTLDRMOD;
+ }
+
+ /* Release memory held by the LED sets (no need to take lock). */
+ for (size_t idxType = 0; idxType < RT_ELEMENTS(maLedTypes); idxType++)
+ {
+ maLedTypes[idxType].cLeds = 0;
+ maLedTypes[idxType].cAllocated = 0;
+ RTMemFree(maLedTypes[idxType].pappLeds);
+ maLedTypes[idxType].pappLeds = NULL;
+ }
+ for (size_t idxSet = 0; idxSet < mcLedSets; idxSet++)
+ {
+ maLedSets[idxSet].cLeds = 0;
+ RTMemFree((void *)maLedSets[idxSet].papLeds);
+ maLedSets[idxSet].papLeds = NULL;
+ maLedSets[idxSet].paSubTypes = NULL;
+ }
+ mcLedSets = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Close the release log before unloading the cryptographic module. */
+ if (m_fEncryptedLog)
+ {
+ PRTLOGGER pLogEnc = RTLogRelSetDefaultInstance(NULL);
+ int vrc = RTLogDestroy(pLogEnc);
+ AssertRC(vrc);
+ }
+#endif
+
+ HRESULT hrc = i_unloadCryptoIfModule();
+ AssertComRC(hrc);
+
+ 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\", hrc=%Rhrc\n",
+ names[i].c_str(), hrc));
+ }
+ }
+ }
+ else
+ LogRel(("RESET: Unable to enumerate guest properties, hrc=%Rhrc\n", hrc));
+}
+
+bool Console::i_guestPropertiesVRDPEnabled(void)
+{
+ Bstr value;
+ HRESULT hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableGuestPropertiesVRDP").raw(),
+ value.asOutParam());
+ if ( hrc == S_OK
+ && value == "1")
+ return true;
+ return false;
+}
+
+void Console::i_guestPropertiesVRDPUpdateLogon(uint32_t u32ClientId, const char *pszUser, const char *pszDomain)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ Bstr clientName;
+ mVRDEServerInfo->COMGETTER(ClientName)(clientName.asOutParam());
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientName.raw(),
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ Bstr(pszUser).raw(),
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ Bstr(pszDomain).raw(),
+ bstrReadOnlyGuest.raw());
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId);
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastConnectedClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrReadOnlyGuest.raw());
+
+ return;
+}
+
+void Console::i_guestPropertiesVRDPUpdateActiveClient(uint32_t u32ClientId)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("%d\n", u32ClientId));
+
+ Bstr bstrFlags(L"RDONLYGUEST,TRANSIENT");
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%u", u32ClientId);
+
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/ActiveClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrFlags.raw());
+
+ return;
+}
+
+void Console::i_guestPropertiesVRDPUpdateNameChange(uint32_t u32ClientId, const char *pszName)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ Bstr clientName(pszName);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientName.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateIPAddrChange(uint32_t u32ClientId, const char *pszIPAddr)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/IPAddr", u32ClientId);
+ Bstr clientIPAddr(pszIPAddr);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientIPAddr.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateLocationChange(uint32_t u32ClientId, const char *pszLocation)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Location", u32ClientId);
+ Bstr clientLocation(pszLocation);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientLocation.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateOtherInfoChange(uint32_t u32ClientId, const char *pszOtherInfo)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ char szPropNm[256];
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/OtherInfo", u32ClientId);
+ Bstr clientOtherInfo(pszOtherInfo);
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ clientOtherInfo.raw(),
+ bstrReadOnlyGuest.raw());
+
+}
+
+void Console::i_guestPropertiesVRDPUpdateClientAttach(uint32_t u32ClientId, bool fAttached)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ char szPropNm[256];
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId);
+
+ Bstr bstrValue = fAttached? "1": "0";
+
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(),
+ bstrValue.raw(),
+ bstrReadOnlyGuest.raw());
+}
+
+void Console::i_guestPropertiesVRDPUpdateDisconnect(uint32_t u32ClientId)
+{
+ if (!i_guestPropertiesVRDPEnabled())
+ return;
+
+ LogFlowFunc(("\n"));
+
+ Bstr bstrReadOnlyGuest(L"RDONLYGUEST");
+
+ char szPropNm[256];
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Name", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/User", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Domain", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ RTStrPrintf(szPropNm, sizeof(szPropNm), "/VirtualBox/HostInfo/VRDP/Client/%u/Attach", u32ClientId);
+ mMachine->SetGuestProperty(Bstr(szPropNm).raw(), NULL,
+ bstrReadOnlyGuest.raw());
+
+ char szClientId[64];
+ RTStrPrintf(szClientId, sizeof(szClientId), "%d", u32ClientId);
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VRDP/LastDisconnectedClient").raw(),
+ Bstr(szClientId).raw(),
+ bstrReadOnlyGuest.raw());
+
+ return;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#ifdef VBOX_WITH_EXTPACK
+/**
+ * Used by VRDEServer and others to talke to the extension pack manager.
+ *
+ * @returns The extension pack manager.
+ */
+ExtPackManager *Console::i_getExtPackManager()
+{
+ return mptrExtPackManager;
+}
+#endif
+
+
+int Console::i_VRDPClientLogon(uint32_t u32ClientId, const char *pszUser, const char *pszPassword, const char *pszDomain)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("%d, %s, %s, %s\n", u32ClientId, pszUser, pszPassword, pszDomain));
+
+ AutoCaller autoCaller(this);
+ if (!autoCaller.isOk())
+ {
+ /* Console has been already uninitialized, deny request */
+ LogRel(("AUTH: Access denied (Console uninitialized).\n"));
+ LogFlowFuncLeave();
+ return VERR_ACCESS_DENIED;
+ }
+
+ Guid uuid = Guid(i_getId());
+
+ AuthType_T authType = AuthType_Null;
+ HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ ULONG authTimeout = 0;
+ hrc = mVRDEServer->COMGETTER(AuthTimeout)(&authTimeout);
+ AssertComRCReturn(hrc, VERR_ACCESS_DENIED);
+
+ AuthResult result = AuthResultAccessDenied;
+ AuthGuestJudgement guestJudgement = AuthGuestNotAsked;
+
+ LogFlowFunc(("Auth type %d\n", authType));
+
+ LogRel(("AUTH: User: [%s]. Domain: [%s]. Authentication type: [%s]\n",
+ pszUser, pszDomain,
+ authType == AuthType_Null?
+ "Null":
+ (authType == AuthType_External?
+ "External":
+ (authType == AuthType_Guest?
+ "Guest":
+ "INVALID"
+ )
+ )
+ ));
+
+ switch (authType)
+ {
+ case AuthType_Null:
+ {
+ result = AuthResultAccessGranted;
+ break;
+ }
+
+ case AuthType_External:
+ {
+ /* Call the external library. */
+ result = mConsoleVRDPServer->Authenticate(uuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId);
+
+ if (result != AuthResultDelegateToGuest)
+ {
+ break;
+ }
+
+ LogRel(("AUTH: Delegated to guest.\n"));
+
+ LogFlowFunc(("External auth asked for guest judgement\n"));
+ }
+ RT_FALL_THRU();
+
+ case AuthType_Guest:
+ {
+ guestJudgement = AuthGuestNotReacted;
+
+ /** @todo r=dj locking required here for m_pVMMDev? */
+ PPDMIVMMDEVPORT pDevPort;
+ if ( m_pVMMDev
+ && ((pDevPort = m_pVMMDev->getVMMDevPort()))
+ )
+ {
+ /* Issue the request to guest. Assume that the call does not require EMT. It should not. */
+
+ /* Ask the guest to judge these credentials. */
+ uint32_t u32GuestFlags = VMMDEV_SETCREDENTIALS_JUDGE;
+
+ int vrc = pDevPort->pfnSetCredentials(pDevPort, pszUser, pszPassword, pszDomain, u32GuestFlags);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Wait for guest. */
+ vrc = m_pVMMDev->WaitCredentialsJudgement(authTimeout, &u32GuestFlags);
+ if (RT_SUCCESS(vrc))
+ {
+ 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 vrc = %Rrc!!!\n", vrc));
+ LogFlowFunc(("Guest judgement %d\n", guestJudgement));
+ }
+ else
+ LogFlowFunc(("Could not set credentials vrc = %Rrc!!!\n", vrc));
+ }
+
+ 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 vrc = pDevPort->pfnSetCredentials(m_pVMMDev->getVMMDevPort(), pszUser, pszPassword, pszDomain, u32GuestFlags);
+ AssertRC(vrc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+void Console::i_VRDPClientStatusChange(uint32_t u32ClientId, const char *pszStatus)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ 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.hrc());
+
+ uint32_t u32Clients = ASMAtomicIncU32(&mcVRDPClients);
+ VMMDev *pDev;
+ PPDMIVMMDEVPORT pPort;
+ if ( (u32Clients == 1)
+ && ((pDev = i_getVMMDev()))
+ && ((pPort = pDev->getVMMDevPort()))
+ )
+ {
+ pPort->pfnVRDPChange(pPort,
+ true,
+ VRDP_EXPERIENCE_LEVEL_FULL); /** @todo configurable */
+ }
+
+ NOREF(u32ClientId);
+ mDisplay->i_VRDPConnectionEvent(true);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ i_guestPropertiesVRDPUpdateActiveClient(u32ClientId);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPClientDisconnect(uint32_t u32ClientId,
+ uint32_t fu32Intercepted)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ uint32_t u32Clients = ASMAtomicDecU32(&mcVRDPClients);
+ VMMDev *pDev;
+ PPDMIVMMDEVPORT pPort;
+
+ if ( (u32Clients == 0)
+ && ((pDev = i_getVMMDev()))
+ && ((pPort = pDev->getVMMDevPort()))
+ )
+ {
+ pPort->pfnVRDPChange(pPort,
+ false,
+ 0);
+ }
+
+ mDisplay->i_VRDPConnectionEvent(false);
+
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_USB)
+ {
+ mConsoleVRDPServer->USBBackendDelete(u32ClientId);
+ }
+
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_CLIPBOARD)
+ {
+ mConsoleVRDPServer->ClipboardDelete(u32ClientId);
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_AUDIO)
+ {
+ if (mAudioVRDE)
+ mAudioVRDE->onVRDEControl(false /* fEnable */, 0 /* uFlags */);
+ }
+#endif
+
+ AuthType_T authType = AuthType_Null;
+ HRESULT hrc = mVRDEServer->COMGETTER(AuthType)(&authType);
+ AssertComRC(hrc);
+
+ if (authType == AuthType_External)
+ mConsoleVRDPServer->AuthDisconnect(i_getId(), u32ClientId);
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ i_guestPropertiesVRDPUpdateDisconnect(u32ClientId);
+ if (u32Clients == 0)
+ i_guestPropertiesVRDPUpdateActiveClient(0);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ if (u32Clients == 0)
+ mcGuestCredentialsProvided = false;
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPInterceptAudio(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ 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.hrc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ mConsoleVRDPServer->USBBackendCreate(u32ClientId, ppvIntercept);
+
+ LogFlowFuncLeave();
+ return;
+}
+
+void Console::i_VRDPInterceptClipboard(uint32_t u32ClientId)
+{
+ LogFlowFuncEnter();
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AssertReturnVoid(mConsoleVRDPServer);
+
+ mConsoleVRDPServer->ClipboardCreate(u32ClientId);
+
+ LogFlowFuncLeave();
+ return;
+}
+
+
+//static
+const char *Console::sSSMConsoleUnit = "ConsoleData";
+/** The saved state version. */
+#define CONSOLE_SAVED_STATE_VERSION UINT32_C(0x00010002)
+/** The saved state version, pre shared folder autoMountPoint. */
+#define CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT UINT32_C(0x00010001)
+
+inline static const char *networkAdapterTypeToName(NetworkAdapterType_T adapterType)
+{
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ case NetworkAdapterType_Am79C960:
+ return "pcnet";
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+ return "e1000";
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ case NetworkAdapterType_Virtio:
+ return "virtio-net";
+#endif
+ case NetworkAdapterType_NE1000:
+ case NetworkAdapterType_NE2000:
+ case NetworkAdapterType_WD8003:
+ case NetworkAdapterType_WD8013:
+ case NetworkAdapterType_ELNK2:
+ return "dp8390";
+ case NetworkAdapterType_ELNK1:
+ return "3c501";
+ default:
+ AssertFailed();
+ return "unknown";
+ }
+ /* not reached */
+}
+
+/**
+ * Loads various console data stored in the saved state file.
+ *
+ * This method does validation of the state file and returns an error info
+ * when appropriate.
+ *
+ * The method does nothing if the machine is not in the Saved file or if
+ * console data from it has already been loaded.
+ *
+ * @note The caller must lock this object for writing.
+ */
+HRESULT Console::i_loadDataFromSavedState()
+{
+ if ( ( mMachineState != MachineState_Saved
+ && mMachineState != MachineState_AbortedSaved)
+ || mSavedStateDataLoaded)
+ return S_OK;
+
+ Bstr bstrSavedStateFile;
+ HRESULT hrc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrStateKeyId;
+ hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrStateKeyStore;
+ hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str const strSavedStateFile(bstrSavedStateFile);
+
+ PCVMMR3VTABLE pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+
+ PSSMHANDLE pSSM;
+ SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+
+ int vrc = ssmStream.open(strSavedStateFile.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion = 0;
+ vrc = pVMM->pfnSSMR3Seek(pSSM, sSSMConsoleUnit, 0 /* iInstance */, &uVersion);
+ /** @todo r=bird: This version check is premature, so the logic here is
+ * buggered as we won't ignore VERR_SSM_UNIT_NOT_FOUND as seems to be
+ * intended. Sigh. */
+ if (SSM_VERSION_MAJOR(uVersion) == SSM_VERSION_MAJOR(CONSOLE_SAVED_STATE_VERSION))
+ {
+ if (RT_SUCCESS(vrc))
+ try
+ {
+ vrc = i_loadStateFileExecInternal(pSSM, pVMM, uVersion);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else if (vrc == VERR_SSM_UNIT_NOT_FOUND)
+ vrc = VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ ssmStream.close();
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc,
+ tr("The saved state file '%s' is invalid (%Rrc). Delete the saved state and try again"),
+ strSavedStateFile.c_str(), vrc);
+
+ mSavedStateDataLoaded = true;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Callback handler to save various console data to the state file,
+ * called when the user saves the VM state.
+ *
+ * @returns VBox status code.
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvUser Pointer to Console
+ *
+ * @note Locks the Console object for reading.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_saveStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ LogFlowFunc(("\n"));
+
+ Console *pThat = static_cast<Console *>(pvUser);
+ AssertReturn(pThat, VERR_INVALID_POINTER);
+
+ AutoCaller autoCaller(pThat);
+ AssertComRCReturn(autoCaller.hrc(), VERR_INVALID_STATE);
+
+ AutoReadLock alock(pThat COMMA_LOCKVAL_SRC_POS);
+
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)pThat->m_mapSharedFolders.size());
+
+ for (SharedFolderMap::const_iterator it = pThat->m_mapSharedFolders.begin();
+ it != pThat->m_mapSharedFolders.end();
+ ++it)
+ {
+ ConsoleSharedFolder *pSF = (*it).second;
+ AutoCaller sfCaller(pSF);
+ AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS);
+
+ const Utf8Str &name = pSF->i_getName();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)name.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, name.c_str());
+
+ const Utf8Str &hostPath = pSF->i_getHostPath();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)hostPath.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, hostPath.c_str());
+
+ pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isWritable());
+ pVMM->pfnSSMR3PutBool(pSSM, !!pSF->i_isAutoMounted());
+
+ const Utf8Str &rStrAutoMountPoint = pSF->i_getAutoMountPoint();
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)rStrAutoMountPoint.length() + 1 /* term. 0 */);
+ pVMM->pfnSSMR3PutStrZ(pSSM, rStrAutoMountPoint.c_str());
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler to load various console data from the state file.
+ *
+ * Called when the VM is being restored from the saved state.
+ *
+ * @returns VBox status code.
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvUser pointer to Console
+ * @param uVersion Console unit version. Should match sSSMConsoleVer.
+ * @param uPass The data pass.
+ */
+//static
+DECLCALLBACK(int)
+Console::i_loadStateFileExec(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ LogFlowFunc(("uVersion=%#x uPass=%#x\n", uVersion, uPass));
+ Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass);
+
+ if (SSM_VERSION_MAJOR_CHANGED(uVersion, CONSOLE_SAVED_STATE_VERSION))
+ return VERR_VERSION_MISMATCH;
+
+ Console *pThat = static_cast<Console *>(pvUser);
+ AssertReturn(pThat, VERR_INVALID_PARAMETER);
+
+ /* Currently, nothing to do when we've been called from VMR3Load*. */
+ return pVMM->pfnSSMR3SkipToEndOfUnit(pSSM);
+}
+
+/**
+ * Method to load various console data from the state file.
+ *
+ * Called from #i_loadDataFromSavedState.
+ *
+ * @param pSSM SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param u32Version Console unit version.
+ * Should match sSSMConsoleVer.
+ *
+ * @note Locks the Console object for writing.
+ */
+int Console::i_loadStateFileExecInternal(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t u32Version)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(m_mapSharedFolders.empty(), VERR_INTERNAL_ERROR);
+
+ uint32_t size = 0;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &size);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < size; ++i)
+ {
+ Utf8Str strName;
+ Utf8Str strHostPath;
+ bool writable = true;
+ bool autoMount = false;
+
+ uint32_t cbStr = 0;
+ char *buf = NULL;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ buf = new char[cbStr];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr);
+ AssertRC(vrc);
+ strName = buf;
+ delete[] buf;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ buf = new char[cbStr];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, buf, cbStr);
+ AssertRC(vrc);
+ strHostPath = buf;
+ delete[] buf;
+
+ if (u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT)
+ pVMM->pfnSSMR3GetBool(pSSM, &writable);
+
+ if ( u32Version >= CONSOLE_SAVED_STATE_VERSION_PRE_AUTO_MOUNT_POINT
+#ifndef VBOX_OSE /* This broke saved state when introduced in r63916 (4.0). */
+ && pVMM->pfnSSMR3HandleRevision(pSSM) >= 63916
+#endif
+ )
+ pVMM->pfnSSMR3GetBool(pSSM, &autoMount);
+
+ Utf8Str strAutoMountPoint;
+ if (u32Version >= CONSOLE_SAVED_STATE_VERSION)
+ {
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbStr);
+ AssertRCReturn(vrc, vrc);
+ vrc = strAutoMountPoint.reserveNoThrow(cbStr);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, strAutoMountPoint.mutableRaw(), cbStr);
+ AssertRCReturn(vrc, vrc);
+ strAutoMountPoint.jolt();
+ }
+
+ ComObjPtr<ConsoleSharedFolder> pSharedFolder;
+ pSharedFolder.createObject();
+ HRESULT hrc = pSharedFolder->init(this,
+ strName,
+ strHostPath,
+ writable,
+ autoMount,
+ strAutoMountPoint,
+ false /* fFailOnError */);
+ AssertComRCReturn(hrc, 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));
+
+ Bstr name(pCBData->pcszName);
+ Bstr value(pCBData->pcszValue);
+ Bstr flags(pCBData->pcszFlags);
+ BOOL fWasDeleted = !pCBData->pcszValue;
+ ComObjPtr<Console> pConsole = reinterpret_cast<Console *>(pvExtension);
+ HRESULT hrc = pConsole->mControl->PushGuestProperty(name.raw(),
+ value.raw(),
+ pCBData->u64Timestamp,
+ flags.raw(),
+ fWasDeleted);
+ if (SUCCEEDED(hrc))
+ {
+ ::FireGuestPropertyChangedEvent(pConsole->mEventSource, pConsole->i_getId().raw(), name.raw(), value.raw(), flags.raw(),
+ fWasDeleted);
+ return VINF_SUCCESS;
+ }
+ LogFlow(("Console::doGuestPropNotification: hrc=%Rhrc pCBData={.pcszName=%s, .pcszValue=%s, .pcszFlags=%s}\n",
+ hrc, pCBData->pcszName, pCBData->pcszValue, pCBData->pcszFlags));
+ return Global::vboxStatusCodeFromCOM(hrc);
+}
+
+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 hrc = i_loadDataFromSavedState();
+ if (FAILED(hrc))
+ return hrc;
+
+ size_t i = 0;
+ aSharedFolders.resize(m_mapSharedFolders.size());
+ for (SharedFolderMap::const_iterator it = m_mapSharedFolders.begin(); it != m_mapSharedFolders.end(); ++i, ++it)
+ (it)->second.queryInterfaceTo(aSharedFolders[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Console::getAttachedPCIDevices(std::vector<ComPtr<IPCIDeviceAttachment> > &aAttachedPCIDevices)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mBusMgr)
+ {
+ std::vector<BusAssignmentManager::PCIDeviceInfo> devInfos;
+ mBusMgr->listAttachedPCIDevices(devInfos);
+ ComObjPtr<PCIDeviceAttachment> dev;
+ aAttachedPCIDevices.resize(devInfos.size());
+ for (size_t i = 0; i < devInfos.size(); i++)
+ {
+ const BusAssignmentManager::PCIDeviceInfo &devInfo = devInfos[i];
+ dev.createObject();
+ dev->init(NULL, devInfo.strDeviceName,
+ devInfo.hostAddress.valid() ? devInfo.hostAddress.asLong() : -1,
+ devInfo.guestAddress.asLong(),
+ devInfo.hostAddress.valid());
+ dev.queryInterfaceTo(aAttachedPCIDevices[i].asOutParam());
+ }
+ }
+ else
+ aAttachedPCIDevices.resize(0);
+
+ return S_OK;
+}
+
+HRESULT Console::getUseHostClipboard(BOOL *aUseHostClipboard)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aUseHostClipboard = mfUseHostClipboard;
+
+ return S_OK;
+}
+
+HRESULT Console::setUseHostClipboard(BOOL aUseHostClipboard)
+{
+ if (mfUseHostClipboard != RT_BOOL(aUseHostClipboard))
+ {
+ mfUseHostClipboard = RT_BOOL(aUseHostClipboard);
+ LogRel(("Shared Clipboard: %s using host clipboard\n", mfUseHostClipboard ? "Enabled" : "Disabled"));
+ }
+
+ return S_OK;
+}
+
+// IConsole methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Console::powerUp(ComPtr<IProgress> &aProgress)
+{
+ return i_powerUp(aProgress.asOutParam(), false /* aPaused */);
+}
+
+HRESULT Console::powerUpPaused(ComPtr<IProgress> &aProgress)
+{
+ return i_powerUp(aProgress.asOutParam(), true /* aPaused */);
+}
+
+HRESULT Console::powerDown(ComPtr<IProgress> &aProgress)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Paused:
+ case MachineState_Stuck:
+ break;
+
+ /* Try cancel the save state. */
+ case MachineState_Saving:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point during a save state"));
+
+ /* Try cancel the teleportation. */
+ case MachineState_Teleporting:
+ case MachineState_TeleportingPausedVM:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a teleportation"));
+
+ /* Try cancel the online snapshot. */
+ case MachineState_OnlineSnapshotting:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in an online snapshot"));
+
+ /* Try cancel the live snapshot. */
+ case MachineState_LiveSnapshotting:
+ if (!mptrCancelableProgress.isNull())
+ {
+ HRESULT hrc = mptrCancelableProgress->Cancel();
+ if (SUCCEEDED(hrc))
+ break;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down at this point in a live snapshot"));
+
+ /* extra nice error message for a common case */
+ case MachineState_Saved:
+ case MachineState_AbortedSaved:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Cannot power down a saved virtual machine"));
+ case MachineState_Stopping:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is being powered down"));
+ default:
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s (must be Running, Paused or Stuck)"),
+ Global::stringifyMachineState(mMachineState));
+ }
+ LogFlowThisFunc(("Initiating SHUTDOWN request...\n"));
+
+ /* memorize the current machine state */
+ MachineState_T lastMachineState = mMachineState;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (mfTurnResetIntoPowerOff)
+ {
+ alock.release(); /** @todo r=bird: This code introduces a race condition wrt to the state. This must be done elsewhere! */
+ mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw());
+ mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(),
+ Bstr("PowerOff").raw(), Bstr("RDONLYGUEST").raw());
+ mMachine->SaveSettings();
+ alock.acquire();
+ }
+#endif
+
+ /*
+ * Request a progress object from the server (this will set the machine state
+ * to Stopping on the server to block others from accessing this machine).
+ */
+ ComPtr<IProgress> ptrProgress;
+ HRESULT hrc = mControl->BeginPoweringDown(ptrProgress.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* Sync the state with the server: */
+ i_setMachineStateLocally(MachineState_Stopping);
+
+ /* Create the power down task: */
+ VMPowerDownTask *pTask = NULL;
+ try
+ {
+ pTask = new VMPowerDownTask(this, ptrProgress);
+ if (!pTask->isOk())
+ {
+ hrc = setError(FAILED(pTask->hrc()) ? pTask->hrc() : E_FAIL, tr("Could not create VMPowerDownTask object\n"));
+ delete(pTask);
+ pTask = NULL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pTask->createThread();
+ if (SUCCEEDED(hrc))
+ {
+ ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc));
+ return hrc;
+ }
+ }
+
+ /*
+ * Cancel the requested power down procedure.
+ * This will reset the machine state to the state it had right
+ * before calling mControl->BeginPoweringDown().
+ */
+ ErrorInfoKeeper eik;
+ mControl->EndPoweringDown(eik.getResultCode(), eik.getText().raw());
+ i_setMachineStateLocally(lastMachineState);
+ }
+ LogFlowThisFunc(("LEAVE: hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Console::reset()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ /** @todo r=bird: This should be allowed on paused VMs as well. Later. */
+ )
+ return i_setInvalidMachineStateError();
+
+ /* protect mpUVM */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ int vrc = ptrVM.vtable()->pfnVMR3Reset(ptrVM.rawUVM());
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not reset the machine (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("mMachineState=%d, hrc=%Rhrc\n", mMachineState, hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(int) Console::i_unplugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu)
+{
+ LogFlowFunc(("pThis=%p pVM=%p idCpu=%u\n", pThis, pUVM, idCpu));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ int vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, "acpi", 0, idCpu, 0);
+ Log(("UnplugCpu: vrc=%Rrc\n", vrc));
+
+ return vrc;
+}
+
+HRESULT Console::i_doCPURemove(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return hrc;
+
+ 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;
+ hrc = mMachine->GetCPUStatus(aCpu, &fCpuAttached);
+ if (FAILED(hrc))
+ return hrc;
+ if (!fCpuAttached)
+ return setError(E_FAIL, tr("CPU %d is not attached"), aCpu);
+
+ /* Leave the lock before any EMT/VMMDev call. */
+ alock.release();
+ bool fLocked = true;
+
+ /* Check if the CPU is unlocked */
+ PPDMIBASE pBase;
+ int vrc = pVMM->pfnPDMR3QueryDeviceLun(pUVM, "acpi", 0, aCpu, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pApicPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+
+ /* Notify the guest if possible. */
+ uint32_t idCpuCore, idCpuPackage;
+ vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pVmmDevPort->pfnCpuHotUnplug(pVmmDevPort, idCpuCore, idCpuPackage);
+ if (RT_SUCCESS(vrc))
+ {
+ unsigned cTries = 100;
+ do
+ {
+ /* It will take some time until the event is processed in the guest. Wait... */
+ vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER;
+ if (RT_SUCCESS(vrc) && !fLocked)
+ break;
+
+ /* Sleep a bit */
+ RTThreadSleep(100);
+ } while (cTries-- > 0);
+ }
+ else if (vrc == VERR_VMMDEV_CPU_HOTPLUG_NOT_MONITORED_BY_GUEST)
+ {
+ /* Query one time. It is possible that the user ejected the CPU. */
+ vrc = pApicPort ? pApicPort->pfnGetCpuStatus(pApicPort, aCpu, &fLocked) : VERR_INVALID_POINTER;
+ }
+ }
+
+ /* If the CPU was unlocked we can detach it now. */
+ if (RT_SUCCESS(vrc) && !fLocked)
+ {
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall.
+ */
+ PVMREQ pReq;
+ vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_unplugCpu, 4,
+ this, pUVM, pVMM, (VMCPUID)aCpu);
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Detach it from the VM */
+ vrc = pVMM->pfnVMR3HotUnplugCpu(pUVM, aCpu);
+ AssertRC(vrc);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Hot-Remove failed (vrc=%Rrc)"), vrc);
+ }
+ else
+ hrc = 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, hrc=%Rhrc\n", mMachineState, hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(int) Console::i_plugCpu(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, VMCPUID idCpu)
+{
+ LogFlowFunc(("pThis=%p uCpu=%u\n", pThis, idCpu));
+ RT_NOREF(pThis);
+
+ int vrc = pVMM->pfnVMR3HotPlugCpu(pUVM, idCpu);
+ AssertRC(vrc);
+
+ /** @todo r=bird: Error handling here just sucks. */
+
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChild(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/acpi/0/");
+ AssertRelease(pInst);
+ /* nuke anything which might have been left behind. */
+ pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", idCpu));
+
+#define RC_CHECK() do { AssertReleaseRC(vrc); } while (0)
+
+ PCFGMNODE pLunL0;
+ PCFGMNODE pCfg;
+ vrc = pVMM->pfnCFGMR3InsertNodeF(pInst, &pLunL0, "LUN#%u", idCpu); RC_CHECK();
+ vrc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", "ACPICpu"); RC_CHECK();
+ vrc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pCfg); RC_CHECK();
+
+ /*
+ * Attach the driver.
+ */
+ PPDMIBASE pBase;
+ vrc = pVMM->pfnPDMR3DeviceAttach(pUVM, "acpi", 0, idCpu, 0, &pBase); RC_CHECK();
+
+ Log(("PlugCpu: vrc=%Rrc\n", vrc));
+
+ pVMM->pfnCFGMR3Dump(pInst);
+
+#undef RC_CHECK
+
+ return VINF_SUCCESS;
+}
+
+HRESULT Console::i_doCPUAdd(ULONG aCpu, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return hrc;
+
+ 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;
+ hrc = mMachine->GetCPUStatus(aCpu, &fCpuAttached);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (fCpuAttached)
+ return setError(E_FAIL, tr("CPU %d is already attached"), aCpu);
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_plugCpu, 4,
+ this, pUVM, pVMM, aCpu);
+
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Notify the guest if possible. */
+ uint32_t idCpuCore, idCpuPackage;
+ vrc = pVMM->pfnVMR3GetCpuCoreAndPackageIdFromCpuId(pUVM, aCpu, &idCpuCore, &idCpuPackage); AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pDevPort->pfnCpuHotPlug(pDevPort, idCpuCore, idCpuPackage);
+ /** @todo warning if the guest doesn't support it */
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not add CPU to the machine (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("mMachineState=%d, hrc=%Rhrc\n", mMachineState, hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::pause()
+{
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = i_pause(Reason_Unspecified);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+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 hrc = i_resume(Reason_Unspecified, alock);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::powerButton()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the button. */
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnPowerButtonPress(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Controlled power off failed (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::getPowerButtonHandled(BOOL *aHandled)
+{
+ LogFlowThisFuncEnter();
+
+ *aHandled = FALSE;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and check if the button press was handled. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ {
+ bool fHandled = false;
+ vrc = pPort->pfnGetPowerButtonHandled(pPort, &fHandled);
+ if (RT_SUCCESS(vrc))
+ *aHandled = fHandled;
+ }
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_PDM_ERROR, vrc,
+ tr("Checking if the ACPI Power Button event was handled by the guest OS failed (%Rrc)"), vrc);
+
+ }
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::getGuestEnteredACPIMode(BOOL *aEntered)
+{
+ LogFlowThisFuncEnter();
+
+ *aEntered = FALSE;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state %s when checking if the guest entered the ACPI mode"),
+ Global::stringifyMachineState(mMachineState));
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and query the information. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ {
+ bool fEntered = false;
+ vrc = pPort->pfnGetGuestEnteredACPIMode(pPort, &fEntered);
+ if (RT_SUCCESS(vrc))
+ *aEntered = fEntered;
+ }
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT Console::sleepButton()
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_LiveSnapshotting)
+ return i_setInvalidMachineStateError();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the sleep button. */
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnSleepButtonPress(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ hrc = RT_SUCCESS(vrc) ? S_OK : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending sleep button event failed (%Rrc)"), vrc);
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Refreshes the maLedTypes and muLedTypeGen members.
+ */
+HRESULT Console::i_refreshLedTypeArrays(AutoReadLock *pReadLock)
+{
+ pReadLock->release();
+ AutoWriteLock alock(mLedLock COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Check that the refresh was already done by someone else while we
+ * acquired the write lock.
+ */
+ if (muLedTypeGen != muLedGen)
+ {
+ /*
+ * Reset the data.
+ */
+ for (size_t idxType = 0; idxType < RT_ELEMENTS(maLedTypes); idxType++)
+ maLedTypes[idxType].cLeds = 0;
+
+ /*
+ * Rebuild the data.
+ */
+ for (uint32_t idxSet = 0; idxSet < mcLedSets; idxSet++)
+ {
+ PLEDSET const pLS = &maLedSets[idxSet];
+ uint32_t const cLeds = pLS->cLeds;
+ PPDMLED volatile * const papSrcLeds = pLS->papLeds;
+ DeviceType_T * const paSubTypes = pLS->paSubTypes;
+ for (uint32_t idxLed = 0; idxLed < cLeds; idxLed++)
+ {
+ /** @todo If we make Console::i_drvStatus_UnitChanged() modify the generation
+ * too, we could skip NULL entries here and make it a bit more compact.
+ * OTOH, most unused LED entires have a paSubTypes of DeviceType_Null. */
+ DeviceType_T enmType = paSubTypes ? paSubTypes[idxLed] : (DeviceType_T)(ASMBitFirstSetU32(pLS->fTypes) - 1);
+ if (enmType > DeviceType_Null && enmType < DeviceType_End)
+ {
+ uint32_t const idxLedType = maLedTypes[enmType].cLeds;
+ if (idxLedType >= maLedTypes[enmType].cAllocated)
+ {
+ void *pvNew = RTMemRealloc(maLedTypes[enmType].pappLeds,
+ sizeof(maLedTypes[0].pappLeds[0]) * (idxLedType + 16));
+ if (!pvNew)
+ return E_OUTOFMEMORY;
+ maLedTypes[enmType].pappLeds = (PPDMLED volatile **)pvNew;
+ maLedTypes[enmType].cAllocated = idxLedType + 16;
+ }
+ maLedTypes[enmType].pappLeds[idxLedType] = &papSrcLeds[idxLed];
+ maLedTypes[enmType].cLeds = idxLedType + 1;
+ }
+ }
+ }
+ muLedTypeGen = muLedGen;
+ }
+
+ /*
+ * We have to release the write lock before re-acquiring the read-lock.
+ *
+ * This means there is a theoretical race here, however we ASSUME that
+ * LED sets are never removed and therefore we will be just fine
+ * accessing slightly dated per-type data.
+ */
+ alock.release();
+ pReadLock->acquire();
+ return S_OK;
+}
+
+/** read the value of a LED. */
+DECLINLINE(uint32_t) readAndClearLed(PPDMLED pLed)
+{
+ if (!pLed)
+ return 0;
+ uint32_t u32 = pLed->Actual.u32 | pLed->Asserted.u32;
+ pLed->Asserted.u32 = 0;
+ return u32;
+}
+
+HRESULT Console::getDeviceActivity(const std::vector<DeviceType_T> &aType, std::vector<DeviceActivity_T> &aActivity)
+{
+ /*
+ * Make a roadmap of which DeviceType_T LED types are wanted.
+ *
+ * Note! This approach means we'll return the same values in aActivity for
+ * duplicate aType entries.
+ */
+ uint32_t fRequestedTypes = 0;
+ AssertCompile(DeviceType_End <= 32);
+
+ for (size_t iType = 0; iType < aType.size(); ++iType)
+ {
+ DeviceType_T const enmType = aType[iType];
+ AssertCompile((unsigned)DeviceType_Null == 0 /* first */);
+ AssertReturn(enmType > DeviceType_Null && enmType < DeviceType_End,
+ setError(E_INVALIDARG, tr("Invalid DeviceType for getDeviceActivity in entry #%u: %d"), iType, enmType));
+ fRequestedTypes |= RT_BIT_32((unsigned)enmType);
+ }
+
+ /*
+ * Resize the result vector before making changes (may throw, paranoia).
+ */
+ aActivity.resize(aType.size());
+
+ /*
+ * Accumulate the per-type data for all the requested types.
+ * We will lazily refresh the per-type data collection here when needed.
+ */
+ PDMLEDCORE aLEDs[DeviceType_End] = { {0} };
+ Assert(aLEDs[1].u32 == 0 && aLEDs[DeviceType_End / 2].u32 == 0 && aLEDs[DeviceType_End - 1].u32 == 0); /* paranoia */
+ {
+ AutoReadLock alock(mLedLock COMMA_LOCKVAL_SRC_POS);
+ if (RT_LIKELY(muLedGen == muLedTypeGen))
+ { /* likely */ }
+ else
+ {
+ HRESULT hrc = i_refreshLedTypeArrays(&alock);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ AssertCompile((unsigned)DeviceType_Null == 0 /* we skip this one */);
+ for (uint32_t idxType = 1; idxType < RT_ELEMENTS(maLedTypes); idxType++)
+ if (fRequestedTypes & RT_BIT_32(idxType))
+ {
+ uint32_t const cLeds = maLedTypes[idxType].cLeds;
+ PPDMLED volatile ** const pappSrcLeds = maLedTypes[idxType].pappLeds;
+ for (size_t iLed = 0; iLed < cLeds; iLed++)
+ aLEDs[idxType].u32 |= readAndClearLed(*pappSrcLeds[iLed]);
+ }
+ }
+
+ /*
+ * Compose the result vector:
+ */
+ for (size_t iType = 0; iType < aActivity.size(); ++iType)
+ {
+ switch (aLEDs[aType[iType]].u32 & (PDMLED_READING | PDMLED_WRITING))
+ {
+ case 0:
+ aActivity[iType] = DeviceActivity_Idle;
+ break;
+ case PDMLED_READING:
+ aActivity[iType] = DeviceActivity_Reading;
+ break;
+ case PDMLED_WRITING:
+ case PDMLED_READING | PDMLED_WRITING:
+ aActivity[iType] = DeviceActivity_Writing;
+ break;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Console::attachUSBDevice(const com::Guid &aId, const com::Utf8Str &aCaptureFilename)
+{
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot attach a USB device to the machine which is not running or paused (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /* Don't proceed unless we have a USB controller. */
+ if (mfVMHasUsbController)
+ {
+ /* release the lock because the USB Proxy service may call us back
+ * (via onUSBDeviceAttach()) */
+ alock.release();
+
+ /* Request the device capture */
+ hrc = mControl->CaptureUSBDevice(Bstr(aId.toString()).raw(), Bstr(aCaptureFilename).raw());
+ }
+ else
+ hrc = setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+ }
+ return hrc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aCaptureFilename);
+ return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::detachUSBDevice(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice)
+{
+ RT_NOREF(aDevice);
+#ifdef VBOX_WITH_USB
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Find it. */
+ for (USBDeviceList::iterator it = mUSBDevices.begin(); it != mUSBDevices.end(); ++it)
+ if ((*it)->i_id() == aId)
+ {
+ /* Found it! */
+ ComObjPtr<OUSBDevice> pUSBDevice(*it);
+
+ /* Remove the device from the collection, it is re-added below for failures */
+ mUSBDevices.erase(it);
+
+ /*
+ * Inform the USB device and USB proxy about what's cooking.
+ */
+ alock.release();
+ HRESULT hrc = mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), false /* aDone */);
+ if (SUCCEEDED(hrc))
+ {
+ /* Request the PDM to detach the USB device. */
+ hrc = i_detachUSBDevice(pUSBDevice);
+ if (SUCCEEDED(hrc))
+ {
+ /* Request the device release. Even if it fails, the device will
+ * remain as held by proxy, which is OK for us (the VM process). */
+ return mControl->DetachUSBDevice(Bstr(aId.toString()).raw(), true /* aDone */);
+ }
+ }
+
+ /* Re-add the device to the collection */
+ alock.acquire();
+ mUSBDevices.push_back(pUSBDevice);
+ return hrc;
+ }
+
+ return setError(E_INVALIDARG, tr("USB device with UUID {%RTuuid} is not attached to this machine"), aId.raw());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aDevice);
+ return setError(VBOX_E_PDM_ERROR, tr("The virtual machine does not have a USB controller"));
+#endif /* !VBOX_WITH_USB */
+}
+
+
+HRESULT Console::findUSBDeviceByAddress(const com::Utf8Str &aName, ComPtr<IUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+ aDevice = NULL;
+
+ SafeIfaceArray<IUSBDevice> devsvec;
+ HRESULT hrc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(hrc))
+ return hrc;
+
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr bstrAddress;
+ hrc = devsvec[i]->COMGETTER(Address)(bstrAddress.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ if (bstrAddress == aName)
+ {
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ pUSBDevice->init(devsvec[i]);
+ return pUSBDevice.queryInterfaceTo(aDevice.asOutParam());
+ }
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with address '%s'"), aName.c_str());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aName, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::findUSBDeviceById(const com::Guid &aId, ComPtr<IUSBDevice> &aDevice)
+{
+#ifdef VBOX_WITH_USB
+ aDevice = NULL;
+
+ SafeIfaceArray<IUSBDevice> devsvec;
+ HRESULT hrc = COMGETTER(USBDevices)(ComSafeArrayAsOutParam(devsvec));
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str const strId = aId.toString();
+ for (size_t i = 0; i < devsvec.size(); ++i)
+ {
+ Bstr id;
+ hrc = devsvec[i]->COMGETTER(Id)(id.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ if (id == strId)
+ {
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ pUSBDevice->init(devsvec[i]);
+ ComObjPtr<IUSBDevice> iUSBDevice = static_cast <ComObjPtr<IUSBDevice> > (pUSBDevice);
+ return iUSBDevice.queryInterfaceTo(aDevice.asOutParam());
+ }
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND, tr("Could not find a USB device with uuid {%RTuuid}"), aId.raw());
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aDevice);
+ return E_NOTIMPL;
+#endif /* !VBOX_WITH_USB */
+}
+
+HRESULT Console::createSharedFolder(const com::Utf8Str &aName, const com::Utf8Str &aHostPath, BOOL aWritable,
+ BOOL aAutomount, const com::Utf8Str &aAutoMountPoint)
+{
+ LogFlowThisFunc(("Entering for '%s' -> '%s'\n", aName.c_str(), aHostPath.c_str()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo see @todo in AttachUSBDevice() about the Paused state
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot create a transient shared folder on a machine in a saved state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+ if ( mMachineState != MachineState_PoweredOff
+ && mMachineState != MachineState_Teleported
+ && mMachineState != MachineState_Aborted
+ && mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot create a transient shared folder on the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ ComObjPtr<ConsoleSharedFolder> pSharedFolder;
+ HRESULT hrc = i_findSharedFolder(aName, pSharedFolder, false /* aSetError */);
+ if (SUCCEEDED(hrc))
+ return setError(VBOX_E_FILE_ERROR, tr("Shared folder named '%s' already exists"), aName.c_str());
+
+ pSharedFolder.createObject();
+ hrc = pSharedFolder->init(this,
+ aName,
+ aHostPath,
+ !!aWritable,
+ !!aAutomount,
+ aAutoMountPoint,
+ true /* fFailOnError */);
+ if (FAILED(hrc))
+ return hrc;
+
+ /* 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))
+ {
+ hrc = i_removeSharedFolder(aName);
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ /* second, create the given folder */
+ hrc = i_createSharedFolder(aName, SharedFolderData(aHostPath, !!aWritable, !!aAutomount, aAutoMountPoint));
+ if (FAILED(hrc))
+ return hrc;
+ }
+
+ 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 hrc;
+}
+
+HRESULT Console::removeSharedFolder(const com::Utf8Str &aName)
+{
+ LogFlowThisFunc(("Entering for '%s'\n", aName.c_str()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /// @todo see @todo in AttachUSBDevice() about the Paused state
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot remove a transient shared folder from a machine in a saved state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));;
+ if ( mMachineState != MachineState_PoweredOff
+ && mMachineState != MachineState_Teleported
+ && mMachineState != MachineState_Aborted
+ && mMachineState != MachineState_Running
+ && mMachineState != MachineState_Paused
+ )
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot remove a transient shared folder from the machine while it is changing the state (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+ ComObjPtr<ConsoleSharedFolder> pSharedFolder;
+ HRESULT hrc = i_findSharedFolder(aName, pSharedFolder, true /* aSetError */);
+ if (FAILED(hrc))
+ return hrc;
+
+ /* 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 */
+ hrc = i_removeSharedFolder(aName);
+ if (FAILED(hrc))
+ return hrc;
+
+ /* first, remove the machine or the global folder if there is any */
+ SharedFolderDataMap::const_iterator it;
+ if (i_findOtherSharedFolder(aName, it))
+ {
+ hrc = i_createSharedFolder(aName, it->second);
+ /* don't check hrc 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 hrc;
+}
+
+HRESULT Console::addEncryptionPassword(const com::Utf8Str &aId, const com::Utf8Str &aPassword,
+ BOOL aClearOnSuspend)
+{
+ if ( aId.isEmpty()
+ || aPassword.isEmpty())
+ return setError(E_FAIL, tr("The ID and password must be both valid"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ size_t cbKey = aPassword.length() + 1; /* Include terminator */
+ const uint8_t *pbKey = (const uint8_t *)aPassword.c_str();
+
+ int vrc = m_pKeyStore->addSecretKey(aId, pbKey, cbKey);
+ if ( RT_SUCCESS(vrc)
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ || vrc == VERR_ALREADY_EXISTS /* Allow setting an existing key for encrypted VMs. */
+#endif
+ )
+ {
+ unsigned cDisksConfigured = 0;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_addPassword(aId, aPassword);
+
+ SecretKey *pKey = NULL;
+ vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ AssertRCReturn(vrc, E_FAIL);
+ pKey->setRemoveOnSuspend(!!aClearOnSuspend);
+ pKey->release();
+#endif
+
+ hrc = i_configureEncryptionForDisk(aId, &cDisksConfigured);
+ if (SUCCEEDED(hrc))
+ {
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ SecretKey *pKey = NULL;
+#endif
+ vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ AssertRCReturn(vrc, E_FAIL);
+
+ pKey->setUsers(cDisksConfigured);
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ pKey->setRemoveOnSuspend(!!aClearOnSuspend);
+ m_pKeyStore->releaseSecretKey(aId);
+#endif
+ m_cDisksPwProvided += cDisksConfigured;
+
+ if ( m_cDisksPwProvided == m_cDisksEncrypted
+ && mMachineState == MachineState_Paused)
+ {
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_RECONFIG);
+
+ hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc);
+ }
+ }
+ }
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ else if (vrc == VERR_ALREADY_EXISTS)
+ hrc = setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password with the given ID already exists"));
+#endif
+ else if (vrc == VERR_NO_MEMORY)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to allocate enough secure memory for the key"));
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Unknown error happened while adding a password (%Rrc)"), vrc);
+
+ return hrc;
+}
+
+HRESULT Console::addEncryptionPasswords(const std::vector<com::Utf8Str> &aIds, const std::vector<com::Utf8Str> &aPasswords,
+ BOOL aClearOnSuspend)
+{
+ HRESULT hrc = S_OK;
+
+ if ( aIds.empty()
+ || aPasswords.empty())
+ return setError(E_FAIL, tr("IDs and passwords must not be empty"));
+
+ if (aIds.size() != aPasswords.size())
+ return setError(E_FAIL, tr("The number of entries in the id and password arguments must match"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifndef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Check that the IDs do not exist already before changing anything. */
+ for (unsigned i = 0; i < aIds.size(); i++)
+ {
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(aIds[i], &pKey);
+ if (vrc != VERR_NOT_FOUND)
+ {
+ AssertPtr(pKey);
+ if (pKey)
+ pKey->release();
+ return setError(VBOX_E_OBJECT_IN_USE, tr("A password with the given ID already exists"));
+ }
+ }
+#else
+ /*
+ * Passwords for the same ID can be added in different ways because
+ * of encrypted VMs now. Just add them instead of generating an error.
+ */
+ /** @todo Check that passwords with the same ID match. */
+#endif
+
+ for (unsigned i = 0; i < aIds.size(); i++)
+ {
+ hrc = addEncryptionPassword(aIds[i], aPasswords[i], aClearOnSuspend);
+ if (FAILED(hrc))
+ {
+ /*
+ * Try to remove already successfully added passwords from the map to not
+ * change the state of the Console object.
+ */
+ ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */
+ for (unsigned ii = 0; ii < i; ii++)
+ {
+ i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(aIds[ii]);
+ removeEncryptionPassword(aIds[ii]);
+ }
+
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT Console::removeEncryptionPassword(const com::Utf8Str &aId)
+{
+ if (aId.isEmpty())
+ return setError(E_FAIL, tr("The ID must be valid"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(aId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ m_cDisksPwProvided -= pKey->getUsers();
+ m_pKeyStore->releaseSecretKey(aId);
+ vrc = m_pKeyStore->deleteSecretKey(aId);
+ AssertRCReturn(vrc, E_FAIL);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_removePassword(aId);
+#endif
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ return setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("A password with the ID \"%s\" does not exist"), aId.c_str());
+ else
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to remove password with ID \"%s\" (%Rrc)"), aId.c_str(), vrc);
+
+ return S_OK;
+}
+
+HRESULT Console::clearAllEncryptionPasswords()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (mptrNvramStore.isNotNull())
+ mptrNvramStore->i_removeAllPasswords();
+#endif
+
+ int vrc = m_pKeyStore->deleteAllSecretKeys(false /* fSuspend */, false /* fForce */);
+ if (vrc == VERR_RESOURCE_IN_USE)
+ return setErrorBoth(VBOX_E_OBJECT_IN_USE, vrc, tr("A password is still in use by the VM"));
+ else if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Deleting all passwords failed (%Rrc)"));
+
+ m_cDisksPwProvided = 0;
+ return S_OK;
+}
+
+// Non-interface public methods
+/////////////////////////////////////////////////////////////////////////////
+
+/*static*/
+HRESULT Console::i_setErrorStatic(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ getStaticClassIID(),
+ getStaticComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/*static*/
+HRESULT Console::i_setErrorStaticBoth(HRESULT aResultCode, int vrc, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ getStaticClassIID(),
+ getStaticComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc);
+ va_end(args);
+ return hrc;
+}
+
+HRESULT Console::i_setInvalidMachineStateError()
+{
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Invalid machine state: %s"),
+ Global::stringifyMachineState(mMachineState));
+}
+
+
+/**
+ * Converts to PDM device names.
+ */
+/* static */ const char *Console::i_storageControllerTypeToStr(StorageControllerType_T enmCtrlType)
+{
+ switch (enmCtrlType)
+ {
+ case StorageControllerType_LsiLogic:
+ return "lsilogicscsi";
+ case StorageControllerType_BusLogic:
+ return "buslogic";
+ case StorageControllerType_LsiLogicSas:
+ return "lsilogicsas";
+ case StorageControllerType_IntelAhci:
+ return "ahci";
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ return "piix3ide";
+ case StorageControllerType_I82078:
+ return "i82078";
+ case StorageControllerType_USB:
+ return "Msd";
+ case StorageControllerType_NVMe:
+ return "nvme";
+ case StorageControllerType_VirtioSCSI:
+ return "virtio-scsi";
+ default:
+ return NULL;
+ }
+}
+
+HRESULT Console::i_storageBusPortDeviceToLun(StorageBus_T enmBus, LONG port, LONG device, unsigned &uLun)
+{
+ switch (enmBus)
+ {
+ case StorageBus_IDE:
+ case StorageBus_Floppy:
+ {
+ AssertMsgReturn(port < 2 && port >= 0, ("%d\n", port), E_INVALIDARG);
+ AssertMsgReturn(device < 2 && device >= 0, ("%d\n", device), E_INVALIDARG);
+ uLun = 2 * port + device;
+ return S_OK;
+ }
+ case StorageBus_SATA:
+ case StorageBus_SCSI:
+ case StorageBus_SAS:
+ case StorageBus_PCIe:
+ case StorageBus_VirtioSCSI:
+ {
+ uLun = port;
+ return S_OK;
+ }
+ case StorageBus_USB:
+ {
+ /*
+ * It is always the first lun, the port denotes the device instance
+ * for the Msd device.
+ */
+ uLun = 0;
+ return S_OK;
+ }
+ default:
+ uLun = 0;
+ AssertMsgFailedReturn(("%d\n", enmBus), E_INVALIDARG);
+ }
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Suspend the VM before we do any medium or network attachment change.
+ *
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param pAlock The automatic lock instance. This is for when we have
+ * to leave it in order to avoid deadlocks.
+ * @param pfResume where to store the information if we need to resume
+ * afterwards.
+ */
+HRESULT Console::i_suspendBeforeConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock, bool *pfResume)
+{
+ *pfResume = false;
+
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ switch (enmVMState)
+ {
+ case VMSTATE_RUNNING:
+ case VMSTATE_RESETTING:
+ case VMSTATE_SOFT_RESETTING:
+ {
+ LogFlowFunc(("Suspending the VM...\n"));
+ /* disable the callback to prevent Console-level state change */
+ mVMStateChangeCallbackDisabled = true;
+ if (pAlock)
+ pAlock->release();
+ int vrc = pVMM->pfnVMR3Suspend(pUVM, VMSUSPENDREASON_RECONFIG);
+ if (pAlock)
+ pAlock->acquire();
+ mVMStateChangeCallbackDisabled = false;
+ if (RT_FAILURE(vrc))
+ return setErrorInternalF(VBOX_E_INVALID_VM_STATE,
+ COM_IIDOF(IConsole),
+ getStaticComponentName(),
+ false /*aWarning*/,
+ true /*aLogIt*/,
+ vrc,
+ tr("Could suspend VM for medium change (%Rrc)"), vrc);
+ *pfResume = true;
+ break;
+ }
+ case VMSTATE_SUSPENDED:
+ break;
+ default:
+ return setErrorInternalF(VBOX_E_INVALID_VM_STATE,
+ COM_IIDOF(IConsole),
+ getStaticComponentName(),
+ false /*aWarning*/,
+ true /*aLogIt*/,
+ 0 /* aResultDetail */,
+ tr("Invalid state '%s' for changing medium"),
+ pVMM->pfnVMR3GetStateName(enmVMState));
+ }
+
+ return S_OK;
+}
+
+/**
+ * Resume the VM after we did any medium or network attachment change.
+ * This is the counterpart to Console::suspendBeforeConfigChange().
+ *
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ */
+void Console::i_resumeAfterConfigChange(PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowFunc(("Resuming the VM...\n"));
+
+ /* disable the callback to prevent Console-level state change */
+ mVMStateChangeCallbackDisabled = true;
+ int vrc = pVMM->pfnVMR3Resume(pUVM, VMRESUMEREASON_RECONFIG);
+ mVMStateChangeCallbackDisabled = false;
+ AssertRC(vrc);
+ if (RT_FAILURE(vrc))
+ {
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ if (enmVMState == VMSTATE_SUSPENDED)
+ {
+ /* too bad, we failed. try to sync the console state with the VMM state */
+ i_vmstateChangeCallback(pUVM, pVMM, VMSTATE_SUSPENDED, enmVMState, this);
+ }
+ }
+}
+
+/**
+ * Process a medium change.
+ *
+ * @param aMediumAttachment The medium attachment with the new medium state.
+ * @param fForce Force medium chance, if it is locked or not.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doMediumChange(IMediumAttachment *aMediumAttachment, bool fForce, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ HRESULT hrc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(hrc);
+
+ IMedium *pMedium = NULL;
+ hrc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(hrc);
+
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ hrc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(hrc);
+ }
+
+ Bstr attCtrlName;
+ hrc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(hrc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ hrc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(hrc);
+ 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;
+ hrc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ ULONG uInstance;
+ hrc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(hrc);
+
+ BOOL fUseHostIOCache;
+ hrc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(hrc);
+
+ /*
+ * 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;
+ hrc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_changeRemovableMedium, 9,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fForce);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+/**
+ * Performs the medium change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O).
+ * @param aMediumAtt The medium attachment.
+ * @param fForce Force unmounting.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_changeRemovableMedium(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fForce)
+{
+ LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p, fForce=%d\n",
+ pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt, fForce));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ int vrc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ false /* fSetupMerge */,
+ false /* fBuiltinIOCache */,
+ false /* fInsertDiskIntegrityDrv. */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ aMediumAtt,
+ pThis->mMachineState,
+ NULL /* phrc */,
+ true /* fAttachDetach */,
+ fForce /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL /* ppLunL0 */);
+ LogFlowFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Attach a new storage device to the VM.
+ *
+ * @param aMediumAttachment The medium attachment which is added.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param fSilent Flag whether to notify the guest about the attached device.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doStorageDeviceAttach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ HRESULT hrc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(hrc);
+
+ IMedium *pMedium = NULL;
+ hrc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(hrc);
+
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ hrc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(hrc);
+ }
+
+ Bstr attCtrlName;
+ hrc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(hrc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ hrc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(hrc);
+ 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;
+ hrc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ ULONG uInstance;
+ hrc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(hrc);
+
+ BOOL fUseHostIOCache;
+ hrc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(hrc);
+
+ /*
+ * 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;
+ hrc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_attachStorageDevice, 9,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, fUseHostIOCache, aMediumAttachment, fSilent);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (!pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+
+/**
+ * Performs the storage attach operation in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Whether to use the host I/O cache (disable async I/O).
+ * @param aMediumAtt The medium attachment.
+ * @param fSilent Flag whether to inform the guest about the attached device.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_attachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ IMediumAttachment *aMediumAtt,
+ bool fSilent)
+{
+ LogFlowFunc(("pThis=%p uInstance=%u pszDevice=%p:{%s} enmBus=%u, aMediumAtt=%p\n",
+ pThis, uInstance, pcszDevice, pcszDevice, enmBus, aMediumAtt));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ int vrc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ false /* fSetupMerge */,
+ false /* fBuiltinIOCache */,
+ false /* fInsertDiskIntegrityDrv. */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ aMediumAtt,
+ pThis->mMachineState,
+ NULL /* phrc */,
+ true /* fAttachDetach */,
+ false /* fForceUnmount */,
+ !fSilent /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL);
+ LogFlowFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Attach a new storage device to the VM.
+ *
+ * @param aMediumAttachment The medium attachment which is added.
+ * @param pUVM Safe VM handle.
+ * @param pVMM Safe VMM vtable.
+ * @param fSilent Flag whether to notify the guest about the detached device.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_doStorageDeviceDetach(IMediumAttachment *aMediumAttachment, PUVM pUVM, PCVMMR3VTABLE pVMM, bool fSilent)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ /* We will need to release the write lock before calling EMT */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const char *pszDevice = NULL;
+
+ SafeIfaceArray<IStorageController> ctrls;
+ HRESULT hrc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(hrc);
+
+ IMedium *pMedium = NULL;
+ hrc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(hrc);
+
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ hrc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(hrc);
+ }
+
+ Bstr attCtrlName;
+ hrc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(hrc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ hrc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(hrc);
+ 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;
+ hrc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ pszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus = (StorageBus_T)0;
+ hrc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ ULONG uInstance = 0;
+ hrc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(hrc);
+
+ /*
+ * 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;
+ hrc = i_suspendBeforeConfigChange(pUVM, pVMM, &alock, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Call worker on EMT #0, that's faster and safer than doing everything
+ * using VMR3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ PVMREQ pReq;
+ int vrc = pVMM->pfnVMR3ReqCallU(pUVM, 0, &pReq, 0 /* no wait! */, VMREQFLAGS_VBOX_STATUS,
+ (PFNRT)i_detachStorageDevice, 8,
+ this, pUVM, pVMM, pszDevice, uInstance, enmBus, aMediumAttachment, fSilent);
+
+ /* release the lock before waiting for a result (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ if (vrc == VERR_TIMEOUT)
+ vrc = pVMM->pfnVMR3ReqWait(pReq, RT_INDEFINITE_WAIT);
+ AssertRC(vrc);
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->iStatus;
+ pVMM->pfnVMR3ReqFree(pReq);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Returns S_OK\n"));
+ return S_OK;
+ }
+
+ if (!pMedium)
+ return setErrorBoth(E_FAIL, vrc, tr("Could not mount the media/drive '%ls' (%Rrc)"), mediumLocation.raw(), vrc);
+ return setErrorBoth(E_FAIL, vrc, tr("Could not unmount the currently mounted media/drive (%Rrc)"), vrc);
+}
+
+/**
+ * Performs the storage detach operation in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param enmBus The storage bus type of the controller.
+ * @param pMediumAtt Pointer to the medium attachment.
+ * @param fSilent Flag whether to notify the guest about the detached device.
+ *
+ * @thread EMT
+ * @note The VM must not be running since it might have pending I/O to the drive which is being changed.
+ */
+DECLCALLBACK(int) Console::i_detachStorageDevice(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ IMediumAttachment *pMediumAtt,
+ bool fSilent)
+{
+ LogRelFlowFunc(("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.hrc(), VERR_ACCESS_DENIED);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ /* Determine the base path for the device instance. */
+ PCFGMNODE pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance);
+ AssertReturn(pCtlInst || enmBus == StorageBus_USB, VERR_INTERNAL_ERROR);
+
+#define H() AssertMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_GENERAL_FAILURE)
+
+ HRESULT hrc;
+ int vrc = VINF_SUCCESS;
+ 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();
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H();
+
+#undef H
+
+ PCFGMNODE pLunL0 = NULL;
+ if (enmBus != StorageBus_USB)
+ {
+ /* First check if the LUN really exists. */
+ pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ if (pLunL0)
+ {
+ uint32_t fFlags = 0;
+ if (fSilent)
+ fFlags |= PDM_TACH_FLAGS_NOT_HOT_PLUG;
+
+ vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, pcszDevice, uInstance, uLUN, fFlags);
+ if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertLogRelRCReturn(vrc, vrc);
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+
+ Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN);
+ pThis->mapMediumAttachments.erase(devicePath);
+ }
+ else
+ AssertLogRelFailedReturn(VERR_INTERNAL_ERROR);
+
+ pVMM->pfnCFGMR3Dump(pCtlInst);
+ }
+#ifdef VBOX_WITH_USB
+ else
+ {
+ /* Find the correct USB device in the list. */
+ USBStorageDeviceList::iterator it;
+ for (it = pThis->mUSBStorageDevices.begin(); it != pThis->mUSBStorageDevices.end(); ++it)
+ if (it->iPort == lPort)
+ break;
+ AssertLogRelReturn(it != pThis->mUSBStorageDevices.end(), VERR_INTERNAL_ERROR);
+
+ vrc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, &it->mUuid);
+ AssertLogRelRCReturn(vrc, vrc);
+ pThis->mUSBStorageDevices.erase(it);
+ }
+#endif
+
+ LogFlowFunc(("Returning VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * 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.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = 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;
+ hrc = aNetworkAdapter->COMGETTER(CableConnected)(&fCableConnected);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fTraceEnabled);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ ULONG ulInstance;
+ hrc = aNetworkAdapter->COMGETTER(Slot)(&ulInstance);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Find the adapter instance, get the config interface and update
+ * the link state.
+ */
+ NetworkAdapterType_T adapterType;
+ hrc = aNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ AssertComRC(hrc);
+ const char *pszAdapterName = networkAdapterTypeToName(adapterType);
+
+ // prevent cross-thread deadlocks, don't need the lock any more
+ alock.release();
+
+ PPDMIBASE pBase = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMINETWORKCONFIG pINetCfg;
+ pINetCfg = PDMIBASE_QUERY_INTERFACE(pBase, PDMINETWORKCONFIG);
+ if (pINetCfg)
+ {
+ Log(("Console::onNetworkAdapterChange: setting link state to %d\n",
+ fCableConnected));
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg,
+ fCableConnected ? PDMNETWORKLINKSTATE_UP
+ : PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
+ if (RT_SUCCESS(vrc) && changeAdapter)
+ {
+ VMSTATE enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if ( enmVMState == VMSTATE_RUNNING /** @todo LiveMigration: Forbid or deal
+ correctly with the _LS variants */
+ || enmVMState == VMSTATE_SUSPENDED)
+ {
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_DOWN);
+ ComAssertRC(vrc);
+ }
+
+ hrc = i_doNetworkAdapterChange(ptrVM.rawUVM(), ptrVM.vtable(), pszAdapterName,
+ ulInstance, 0, aNetworkAdapter);
+
+ if (fTraceEnabled && fCableConnected && pINetCfg)
+ {
+ vrc = pINetCfg->pfnSetLinkState(pINetCfg, PDMNETWORKLINKSTATE_UP);
+ ComAssertRC(vrc);
+ }
+ }
+ }
+ }
+ else if (vrc == VERR_PDM_DEVICE_INSTANCE_NOT_FOUND)
+ return setErrorBoth(E_FAIL, vrc, tr("The network adapter #%u is not enabled"), ulInstance);
+ else
+ ComAssertRC(vrc);
+
+ if (RT_FAILURE(vrc))
+ hrc = E_FAIL;
+
+ alock.acquire();
+ }
+ }
+ }
+ ptrVM.release();
+ }
+
+ // definitely don't need the lock any more
+ alock.release();
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireNetworkAdapterChangedEvent(mEventSource, aNetworkAdapter);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnNATEngineChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onNATRedirectRuleChanged(ULONG ulInstance, BOOL aNatRuleRemove, NATProtocol_T aProto, IN_BSTR aHostIP,
+ LONG aHostPort, IN_BSTR aGuestIP, LONG aGuestPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger NAT engine changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ do
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ hrc = i_machine()->GetNetworkAdapter(ulInstance, pNetworkAdapter.asOutParam());
+ if ( FAILED(hrc)
+ || pNetworkAdapter.isNull())
+ break;
+
+ /*
+ * Find the adapter instance, get the config interface and update
+ * the link state.
+ */
+ NetworkAdapterType_T adapterType;
+ hrc = pNetworkAdapter->COMGETTER(AdapterType)(&adapterType);
+ if (FAILED(hrc))
+ {
+ AssertComRC(hrc);
+ hrc = E_FAIL;
+ break;
+ }
+
+ const char *pszAdapterName = networkAdapterTypeToName(adapterType);
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryLun(ptrVM.rawUVM(), pszAdapterName, ulInstance, 0, &pBase);
+ if (RT_FAILURE(vrc))
+ {
+ /* This may happen if the NAT network adapter is currently not attached.
+ * This is a valid condition. */
+ if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ break;
+ ComAssertRC(vrc);
+ hrc = E_FAIL;
+ break;
+ }
+
+ NetworkAttachmentType_T attachmentType;
+ hrc = pNetworkAdapter->COMGETTER(AttachmentType)(&attachmentType);
+ if ( FAILED(hrc)
+ || attachmentType != NetworkAttachmentType_NAT)
+ {
+ hrc = 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))
+ hrc = E_FAIL;
+ } while (0); /* break loop */
+ ptrVM.release();
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+
+/*
+ * 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.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#if 0 /* XXX: We don't yet pass this down to pfnNotifyDnsChanged */
+ ComPtr<IVirtualBox> pVirtualBox;
+ hrc = mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ if (FAILED(hrc))
+ return S_OK;
+
+ ComPtr<IHost> pHost;
+ hrc = pVirtualBox->COMGETTER(Host)(pHost.asOutParam());
+ if (FAILED(hrc))
+ return S_OK;
+
+ SafeArray<BSTR> aNameServers;
+ hrc = pHost->COMGETTER(NameServers)(ComSafeArrayAsOutParam(aNameServers));
+ if (FAILED(hrc))
+ return S_OK;
+
+ const size_t cNameServers = aNameServers.size();
+ Log(("DNS change - %zu nameservers\n", cNameServers));
+
+ for (size_t i = 0; i < cNameServers; ++i)
+ {
+ com::Utf8Str strNameServer(aNameServers[i]);
+ Log(("- nameserver[%zu] = \"%s\"\n", i, strNameServer.c_str()));
+ }
+
+ com::Bstr domain;
+ pHost->COMGETTER(DomainName)(domain.asOutParam());
+ Log(("domain name = \"%s\"\n", com::Utf8Str(domain).c_str()));
+#endif /* 0 */
+
+ ChipsetType_T enmChipsetType;
+ hrc = mMachine->COMGETTER(ChipsetType)(&enmChipsetType);
+ if (!FAILED(hrc))
+ {
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ ULONG ulInstanceMax = (ULONG)Global::getMaxNetworkAdapters(enmChipsetType);
+
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "pcnet", ulInstanceMax);
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "e1000", ulInstanceMax);
+ notifyNatDnsChange(ptrVM.rawUVM(), ptrVM.vtable(), "virtio-net", ulInstanceMax);
+ }
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * This routine walks over all network device instances, checking if
+ * device instance has DrvNAT attachment and triggering DrvNAT DNS
+ * change callback.
+ */
+void Console::notifyNatDnsChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice, ULONG ulInstanceMax)
+{
+ Log(("notifyNatDnsChange: looking for DrvNAT attachment on %s device instances\n", pszDevice));
+ for (ULONG ulInstance = 0; ulInstance < ulInstanceMax; ulInstance++)
+ {
+ PPDMIBASE pBase;
+ int vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pszDevice, ulInstance, 0 /* iLun */, "NAT", &pBase);
+ if (RT_FAILURE(vrc))
+ 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)
+{
+ const char *pszKeyStart = psz;
+ while ( *psz != '='
+ && *psz)
+ psz++;
+
+ /* End of string at this point is invalid. */
+ if (*psz == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ size_t const cchKey = psz - pszKeyStart;
+
+ psz++; /* Skip '=' character */
+ const char *pszValStart = psz;
+
+ while ( *psz != ','
+ && *psz != '\n'
+ && *psz != '\r'
+ && *psz)
+ psz++;
+ size_t const cchVal = psz - pszValStart;
+
+ int vrc = VINF_SUCCESS;
+ if (cchKey && cchVal)
+ {
+ *ppszKey = RTStrDupN(pszKeyStart, cchKey);
+ if (*ppszKey)
+ {
+ *ppszVal = RTStrDupN(pszValStart, cchVal);
+ if (*ppszVal)
+ *ppszEnd = psz;
+ else
+ {
+ RTStrFree(*ppszKey);
+ vrc = VERR_NO_STR_MEMORY;
+ }
+ }
+ else
+ vrc = VERR_NO_STR_MEMORY;
+ }
+ else
+ vrc = VERR_INVALID_PARAMETER;
+
+ return vrc;
+}
+
+/**
+ * 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.hrc());
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ AssertComRCReturnRC(hrc);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ m_cDisksPwProvided = 0;
+#endif
+
+ /* Find the correct attachment. */
+ for (unsigned i = 0; i < sfaAttachments.size(); i++)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i];
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ ComPtr<IMedium> pMedium;
+ ComPtr<IMedium> pBase;
+
+ hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ AssertComRC(hrc);
+
+ bool fKeepSecIf = false;
+ /* Skip non hard disk attachments. */
+ if (pMedium.isNotNull())
+ {
+ /* Get the UUID of the base medium and compare. */
+ hrc = pMedium->COMGETTER(Base)(pBase.asOutParam());
+ AssertComRC(hrc);
+
+ Bstr bstrKeyId;
+ hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str strKeyId(bstrKeyId);
+ SecretKey *pKey = NULL;
+ int vrc = m_pKeyStore->retainSecretKey(strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ fKeepSecIf = true;
+ m_pKeyStore->releaseSecretKey(strKeyId);
+ }
+ }
+ }
+#endif
+
+ /*
+ * Query storage controller, port and device
+ * to identify the correct driver.
+ */
+ ComPtr<IStorageController> pStorageCtrl;
+ Bstr storageCtrlName;
+ LONG lPort, lDev;
+ ULONG ulStorageCtrlInst;
+
+ hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Port)(&lPort);
+ AssertComRC(hrc);
+
+ hrc = pAtt->COMGETTER(Device)(&lDev);
+ AssertComRC(hrc);
+
+ hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam());
+ AssertComRC(hrc);
+
+ hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst);
+ AssertComRC(hrc);
+
+ StorageControllerType_T enmCtrlType;
+ hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRC(hrc);
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, fKeepSecIf ? mpIfSecKey : NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED);
+ if (fKeepSecIf)
+ m_cDisksPwProvided++;
+#else
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED);
+#endif
+ }
+ }
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Removes the key interfaces from all disk attachments with the given key ID.
+ * Useful when changing the key store or dropping it.
+ *
+ * @returns COM status code.
+ * @param strId The ID to look for.
+ */
+HRESULT Console::i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(const Utf8Str &strId)
+{
+ HRESULT hrc = S_OK;
+ SafeIfaceArray<IMediumAttachment> sfaAttachments;
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ 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 vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, NULL, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(vrc) || vrc == 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.hrc());
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Find the correct attachment. */
+ for (unsigned i = 0; i < sfaAttachments.size(); i++)
+ {
+ const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[i];
+ ComPtr<IMedium> pMedium;
+ ComPtr<IMedium> pBase;
+ Bstr bstrKeyId;
+
+ hrc = pAtt->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ /* Skip non hard disk attachments. */
+ if (pMedium.isNull())
+ continue;
+
+ /* Get the UUID of the base medium and compare. */
+ hrc = pMedium->COMGETTER(Base)(pBase.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pBase->GetProperty(Bstr("CRYPT/KeyId").raw(), bstrKeyId.asOutParam());
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ {
+ hrc = S_OK;
+ continue;
+ }
+ else if (FAILED(hrc))
+ break;
+
+ if (strId.equals(Utf8Str(bstrKeyId)))
+ {
+ /*
+ * Found the matching medium, query storage controller, port and device
+ * to identify the correct driver.
+ */
+ ComPtr<IStorageController> pStorageCtrl;
+ Bstr storageCtrlName;
+ LONG lPort, lDev;
+ ULONG ulStorageCtrlInst;
+
+ hrc = pAtt->COMGETTER(Controller)(storageCtrlName.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pAtt->COMGETTER(Port)(&lPort);
+ if (FAILED(hrc))
+ break;
+
+ hrc = pAtt->COMGETTER(Device)(&lDev);
+ if (FAILED(hrc))
+ break;
+
+ hrc = mMachine->GetStorageControllerByName(storageCtrlName.raw(), pStorageCtrl.asOutParam());
+ if (FAILED(hrc))
+ break;
+
+ hrc = pStorageCtrl->COMGETTER(Instance)(&ulStorageCtrlInst);
+ if (FAILED(hrc))
+ break;
+
+ StorageControllerType_T enmCtrlType;
+ hrc = pStorageCtrl->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageCtrl->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRCReturnRC(hrc);
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, ulStorageCtrlInst, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (!pIMedium)
+ return setError(E_FAIL, tr("could not query medium interface of controller"));
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp);
+ if (vrc == VERR_VD_PASSWORD_INCORRECT)
+ {
+ hrc = setError(VBOX_E_PASSWORD_INCORRECT,
+ tr("The provided password for ID \"%s\" is not correct for at least one disk using this ID"),
+ strId.c_str());
+ break;
+ }
+ else if (RT_FAILURE(vrc))
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set the encryption key (%Rrc)"), vrc);
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ cDisksConfigured++;
+ }
+ else
+ return setError(E_FAIL, tr("could not query base interface of controller"));
+ }
+ }
+ }
+
+ if ( SUCCEEDED(hrc)
+ && pcDisksConfigured)
+ *pcDisksConfigured = cDisksConfigured;
+ else if (FAILED(hrc))
+ {
+ /* Clear disk encryption setup on successfully configured attachments. */
+ ErrorInfoKeeper eik; /* Keep current error info or it gets deestroyed in the IPC methods below. */
+ i_clearDiskEncryptionKeysOnAllAttachmentsWithKeyId(strId);
+ }
+
+ return hrc;
+}
+
+/**
+ * Parses the encryption configuration for one disk.
+ *
+ * @returns COM status code.
+ * @param psz Pointer to the configuration for the encryption of one disk.
+ * @param ppszEnd Pointer to the string following encrpytion configuration.
+ */
+HRESULT Console::i_consoleParseDiskEncryption(const char *psz, const char **ppszEnd)
+{
+ char *pszUuid = NULL;
+ char *pszKeyEnc = NULL;
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc = S_OK;
+
+ while ( *psz
+ && RT_SUCCESS(vrc))
+ {
+ char *pszKey = NULL;
+ char *pszVal = NULL;
+ const char *pszEnd = NULL;
+
+ vrc = i_consoleParseKeyValue(psz, &pszEnd, &pszKey, &pszVal);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!RTStrCmp(pszKey, "uuid"))
+ pszUuid = pszVal;
+ else if (!RTStrCmp(pszKey, "dek"))
+ pszKeyEnc = pszVal;
+ else
+ vrc = 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(vrc)
+ && pszUuid
+ && pszKeyEnc)
+ {
+ ssize_t cbKey = 0;
+
+ /* Decode the key. */
+ cbKey = RTBase64DecodedSize(pszKeyEnc, NULL);
+ if (cbKey != -1)
+ {
+ uint8_t *pbKey;
+ vrc = RTMemSaferAllocZEx((void **)&pbKey, cbKey, RTMEMSAFER_F_REQUIRE_NOT_PAGABLE);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTBase64Decode(pszKeyEnc, pbKey, cbKey, NULL, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m_pKeyStore->addSecretKey(Utf8Str(pszUuid), pbKey, cbKey);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = i_configureEncryptionForDisk(Utf8Str(pszUuid), NULL);
+ if (FAILED(hrc))
+ {
+ /* Delete the key from the map. */
+ vrc = m_pKeyStore->deleteSecretKey(Utf8Str(pszUuid));
+ AssertRC(vrc);
+ }
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to decode the key (%Rrc)"), vrc);
+
+ RTMemSaferFree(pbKey, cbKey);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to allocate secure memory for the key (%Rrc)"), vrc);
+ }
+ else
+ hrc = setError(E_FAIL, tr("The base64 encoding of the passed key is incorrect"));
+ }
+ else if (RT_SUCCESS(vrc))
+ 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 vrc = m_pKeyStore->deleteAllSecretKeys(true /* fSuspend */, true /* fForce */);
+ AssertRC(vrc);
+}
+
+/**
+ * Process a network adaptor change.
+ *
+ * @returns COM status code.
+ *
+ * @param pUVM The VM handle (caller hold this safely).
+ * @param pVMM The VMM vtable.
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ */
+HRESULT Console::i_doNetworkAdapterChange(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDevice,
+ unsigned uInstance, unsigned uLun, INetworkAdapter *aNetworkAdapter)
+{
+ LogFlowThisFunc(("pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n",
+ pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ /*
+ * Suspend the VM first.
+ */
+ bool fResume = false;
+ HRESULT hr = i_suspendBeforeConfigChange(pUVM, pVMM, NULL, &fResume);
+ if (FAILED(hr))
+ return hr;
+
+ /*
+ * Call worker in EMT, that's faster and safer than doing everything
+ * using VM3ReqCall. Note that we separate VMR3ReqCall from VMR3ReqWait
+ * here to make requests from under the lock in order to serialize them.
+ */
+ int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /*idDstCpu*/,
+ (PFNRT)i_changeNetworkAttachment, 7,
+ this, pUVM, pVMM, pszDevice, uInstance, uLun, aNetworkAdapter);
+
+ if (fResume)
+ i_resumeAfterConfigChange(pUVM, pVMM);
+
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+
+ return setErrorBoth(E_FAIL, vrc, tr("Could not change the network adaptor attachement type (%Rrc)"), vrc);
+}
+
+
+/**
+ * Performs the Network Adaptor change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ *
+ * @thread EMT
+ * @note Locks the Console object for writing.
+ * @note The VM must not be running.
+ */
+DECLCALLBACK(int) Console::i_changeNetworkAttachment(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pszDevice,
+ unsigned uInstance,
+ unsigned uLun,
+ INetworkAdapter *aNetworkAdapter)
+{
+ LogFlowFunc(("pThis=%p pszDevice=%p:{%s} uInstance=%u uLun=%u aNetworkAdapter=%p\n",
+ pThis, pszDevice, pszDevice, uInstance, uLun, aNetworkAdapter));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ ComPtr<IVirtualBox> pVirtualBox;
+ pThis->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ChipsetType_T chipsetType = ChipsetType_PIIX3;
+ pThis->mMachine->COMGETTER(ChipsetType)(&chipsetType);
+ ULONG maxNetworkAdapters = 0;
+ if (pSystemProperties)
+ pSystemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters);
+ AssertMsg( ( !strcmp(pszDevice, "pcnet")
+ || !strcmp(pszDevice, "e1000")
+ || !strcmp(pszDevice, "virtio-net"))
+ && uLun == 0
+ && uInstance < maxNetworkAdapters,
+ ("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance));
+ Log(("pszDevice=%s uLun=%d uInstance=%d\n", pszDevice, uLun, uInstance));
+
+ /*
+ * Check the VM for correct state.
+ */
+ PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%d/", pszDevice, uInstance);
+ AssertRelease(pInst);
+
+ int vrc = pThis->i_configNetwork(pszDevice, uInstance, uLun, aNetworkAdapter, pCfg, pLunL0, pInst,
+ true /*fAttachDetach*/, false /*fIgnoreConnectFailure*/, pUVM, pVMM);
+
+ LogFlowFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * 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.hrc());
+
+ 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 vrc = VINF_SUCCESS;
+
+ for (ULONG ulLUN = 0; ulLUN < 16 /** @todo Use a define */; ulLUN++)
+ {
+ PPDMIBASE pBase;
+ int vrc2 = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(),
+ i_getAudioAdapterDeviceName(aAudioAdapter).c_str(),
+ 0 /* iInstance */, ulLUN, "AUDIO", &pBase);
+ if (RT_FAILURE(vrc2))
+ continue;
+
+ if (pBase)
+ {
+ PPDMIAUDIOCONNECTOR pAudioCon = (PPDMIAUDIOCONNECTOR)pBase->pfnQueryInterface(pBase,
+ PDMIAUDIOCONNECTOR_IID);
+ if ( pAudioCon
+ && pAudioCon->pfnEnable)
+ {
+ int vrcIn = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_IN, RT_BOOL(fEnabledIn));
+ if (RT_FAILURE(vrcIn))
+ LogRel(("Audio: Failed to %s input of LUN#%RU32, vrcIn=%Rrc\n",
+ fEnabledIn ? "enable" : "disable", ulLUN, vrcIn));
+
+ if (RT_SUCCESS(vrc))
+ vrc = vrcIn;
+
+ int vrcOut = pAudioCon->pfnEnable(pAudioCon, PDMAUDIODIR_OUT, RT_BOOL(fEnabledOut));
+ if (RT_FAILURE(vrcOut))
+ LogRel(("Audio: Failed to %s output of LUN#%RU32, vrcOut=%Rrc\n",
+ fEnabledIn ? "enable" : "disable", ulLUN, vrcOut));
+
+ if (RT_SUCCESS(vrc))
+ vrc = vrcOut;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ 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 S_OKn"));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnHostAudioDeviceChange().
+ */
+HRESULT Console::i_onHostAudioDeviceChange(IHostAudioDevice *aDevice, BOOL aNew, AudioDeviceState_T aState,
+ IVirtualBoxErrorInfo *aErrInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /** @todo Implement logic here. */
+
+ alock.release();
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireHostAudioDeviceChangedEvent(mEventSource, aDevice, aNew, aState, aErrInfo);
+
+ LogFlowThisFunc(("Leaving S_OK\n"));
+ return S_OK;
+}
+
+/**
+ * Performs the Serial Port attachment change in EMT.
+ *
+ * @returns VBox status code.
+ *
+ * @param pThis Pointer to the Console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pSerialPort The serial port whose attachment needs to be changed
+ *
+ * @thread EMT
+ * @note Locks the Console object for writing.
+ * @note The VM must not be running.
+ */
+DECLCALLBACK(int) Console::i_changeSerialPortAttachment(Console *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, ISerialPort *pSerialPort)
+{
+ LogFlowFunc(("pThis=%p pUVM=%p pSerialPort=%p\n", pThis, pUVM, pSerialPort));
+
+ AssertReturn(pThis, VERR_INVALID_PARAMETER);
+
+ AutoCaller autoCaller(pThis);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Check the VM for correct state.
+ */
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ AssertReturn(enmVMState == VMSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ HRESULT hrc = S_OK;
+ int vrc = VINF_SUCCESS;
+ ULONG ulSlot;
+ hrc = pSerialPort->COMGETTER(Slot)(&ulSlot);
+ if (SUCCEEDED(hrc))
+ {
+ /* Check whether the port mode changed and act accordingly. */
+ Assert(ulSlot < 4);
+
+ PortMode_T eHostMode;
+ hrc = pSerialPort->COMGETTER(HostMode)(&eHostMode);
+ if (SUCCEEDED(hrc))
+ {
+ PCFGMNODE pInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/serial/%d/", ulSlot);
+ AssertRelease(pInst);
+
+ /* Remove old driver. */
+ if (pThis->m_aeSerialPortMode[ulSlot] != PortMode_Disconnected)
+ {
+ vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, "serial", ulSlot, 0, 0);
+ PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pInst, "LUN#0");
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ 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)
+ {
+ vrc = pThis->i_configSerialPort(pInst, eHostMode, Utf8Str(bstrPath).c_str(), RT_BOOL(fServer));
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Attach the driver.
+ */
+ PPDMIBASE pBase;
+ vrc = pVMM->pfnPDMR3DeviceAttach(pUVM, "serial", ulSlot, 0, 0, &pBase);
+
+ pVMM->pfnCFGMR3Dump(pInst);
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc) && FAILED(hrc))
+ vrc = VERR_INTERNAL_ERROR;
+
+ LogFlowFunc(("Returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Called by IInternalSessionControl::OnSerialPortChange().
+ */
+HRESULT Console::i_onSerialPortChange(ISerialPort *aSerialPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ 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 (SUCCEEDED(hrc) && m_aeSerialPortMode[ulSlot] != eHostMode)
+ {
+ /*
+ * Suspend the VM first.
+ */
+ bool fResume = false;
+ hrc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), NULL, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Call worker in EMT, that's faster and safer than doing everything
+ * using VM3ReqCallWait.
+ */
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /*idDstCpu*/,
+ (PFNRT)i_changeSerialPortAttachment, 4,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), aSerialPort);
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+ if (RT_SUCCESS(vrc))
+ m_aeSerialPortMode[ulSlot] = eHostMode;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to change the serial port attachment (%Rrc)"), vrc);
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ ::FireSerialPortChangedEvent(mEventSource, aSerialPort);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnParallelPortChange().
+ */
+HRESULT Console::i_onParallelPortChange(IParallelPort *aParallelPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ ::FireParallelPortChangedEvent(mEventSource, aParallelPort);
+
+ LogFlowThisFunc(("Leaving S_OK\n"));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnStorageControllerChange().
+ */
+HRESULT Console::i_onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ ::FireStorageControllerChangedEvent(mEventSource, aMachineId.toString(), aControllerName);
+
+ LogFlowThisFunc(("Leaving S_OK\n"));
+ return S_OK;
+}
+
+/**
+ * Called by IInternalSessionControl::OnMediumChange().
+ */
+HRESULT Console::i_onMediumChange(IMediumAttachment *aMediumAttachment, BOOL aForce)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger medium changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ hrc = i_doMediumChange(aMediumAttachment, !!aForce, ptrVM.rawUVM(), ptrVM.vtable());
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireMediumChangedEvent(mEventSource, aMediumAttachment);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * 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.hrc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger CPU changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (aRemove)
+ hrc = i_doCPURemove(aCPU, ptrVM.rawUVM(), ptrVM.vtable());
+ else
+ hrc = i_doCPUAdd(aCPU, ptrVM.rawUVM(), ptrVM.vtable());
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireCPUChangedEvent(mEventSource, aCPU, aRemove);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnCpuExecutionCapChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onCPUExecutionCapChange(ULONG aExecutionCap)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = 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. */
+ hrc = ptrVM.vtable()->pfnVMR3SetCpuExecutionCap(ptrVM.rawUVM(), aExecutionCap);
+ }
+ else
+ hrc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireCPUExecutionCapChangedEvent(mEventSource, aExecutionCap);
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnClipboardModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onClipboardModeChange(ClipboardMode_T aClipboardMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger the clipboard mode change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting)
+ {
+ int vrc = i_changeClipboardMode(aClipboardMode);
+ if (RT_FAILURE(vrc))
+ hrc = E_FAIL; /** @todo r=andy Set error info here! */
+ }
+ else
+ hrc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireClipboardModeChangedEvent(mEventSource, aClipboardMode);
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnClipboardFileTransferModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onClipboardFileTransferModeChange(bool aEnabled)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger the change if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting)
+ {
+ int vrc = i_changeClipboardFileTransferMode(aEnabled);
+ if (RT_FAILURE(vrc))
+ hrc = E_FAIL; /** @todo r=andy Set error info here! */
+ }
+ else
+ hrc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireClipboardFileTransferModeChangedEvent(mEventSource, aEnabled ? TRUE : FALSE);
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnDnDModeChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onDnDModeChange(DnDMode_T aDnDMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = 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
+ hrc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireDnDModeChangedEvent(mEventSource, aDnDMode);
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * Check the return code of mConsoleVRDPServer->Launch. LogRel() the error reason and
+ * return an error message appropriate for setError().
+ */
+Utf8Str Console::VRDPServerErrorToMsg(int vrc)
+{
+ Utf8Str errMsg;
+ if (vrc == VERR_NET_ADDRESS_IN_USE)
+ {
+ /* Not fatal if we start the VM, fatal if the VM is already running. */
+ Bstr bstr;
+ mVRDEServer->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam());
+ errMsg = Utf8StrFmt(tr("VirtualBox Remote Desktop Extension server can't bind to the port(s): %s"),
+ Utf8Str(bstr).c_str());
+ LogRel(("VRDE: Warning: failed to launch VRDE server (%Rrc): %s\n", vrc, errMsg.c_str()));
+ }
+ else if (vrc == VINF_NOT_SUPPORTED)
+ {
+ /* This means that the VRDE is not installed.
+ * Not fatal if we start the VM, fatal if the VM is already running. */
+ LogRel(("VRDE: VirtualBox Remote Desktop Extension is not available.\n"));
+ errMsg = Utf8Str(tr("VirtualBox Remote Desktop Extension is not available"));
+ }
+ else if (RT_FAILURE(vrc))
+ {
+ /* Fail if the server is installed but can't start. Always fatal. */
+ switch (vrc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ errMsg = Utf8StrFmt(tr("Could not find the VirtualBox Remote Desktop Extension library"));
+ break;
+ default:
+ errMsg = Utf8StrFmt(tr("Failed to launch the Remote Desktop Extension server (%Rrc)"), vrc);
+ break;
+ }
+ LogRel(("VRDE: Failed: (%Rrc): %s\n", vrc, errMsg.c_str()));
+ }
+
+ return errMsg;
+}
+
+/**
+ * Called by IInternalSessionControl::OnVRDEServerChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onVRDEServerChange(BOOL aRestart)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = 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;
+
+ hrc = mVRDEServer->COMGETTER(Enabled)(&vrdpEnabled);
+ ComAssertComRCRetRC(hrc);
+
+ 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);
+ hrc = setErrorBoth(E_FAIL, vrc, "%s", errMsg.c_str());
+ }
+ else
+ {
+#ifdef VBOX_WITH_AUDIO_VRDE
+ mAudioVRDE->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/);
+#endif
+ mConsoleVRDPServer->EnableConnections();
+ }
+ }
+ else
+ {
+ mConsoleVRDPServer->Stop();
+#ifdef VBOX_WITH_AUDIO_VRDE
+ mAudioVRDE->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), NULL /*alock is not held*/);
+#endif
+ }
+
+ alock.acquire();
+ }
+ }
+ else
+ hrc = i_setInvalidMachineStateError();
+
+ mfVRDEChangeInProcess = false;
+ } while (mfVRDEChangePending && SUCCEEDED(hrc));
+ }
+
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireVRDEServerChangedEvent(mEventSource);
+ }
+
+ return hrc;
+}
+
+void Console::i_onVRDEServerInfoChange()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::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.hrc();
+
+ // no need to release lock, as there are no cross-thread callbacks
+
+ /* get the acpi device interface and press the sleep button. */
+ PPDMIBASE pBase;
+ int vrc = ptrVM.vtable()->pfnPDMR3QueryDeviceLun(ptrVM.rawUVM(), "acpi", 0, 0, &pBase);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(pBase);
+ PPDMIACPIPORT pPort = PDMIBASE_QUERY_INTERFACE(pBase, PDMIACPIPORT);
+ if (pPort)
+ vrc = pPort->pfnMonitorHotPlugEvent(pPort);
+ else
+ vrc = VERR_PDM_MISSING_INTERFACE;
+ }
+
+ HRESULT hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_PDM_ERROR, vrc, tr("Sending monitor hot-plug event failed (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+#ifdef VBOX_WITH_RECORDING
+/**
+ * Enables or disables recording of a VM.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NO_CHANGE if the recording state has not been changed.
+ * @param fEnable Whether to enable or disable the recording.
+ * @param pAutoLock Pointer to auto write lock to use for attaching/detaching required driver(s) at runtime.
+ */
+int Console::i_recordingEnable(BOOL fEnable, util::AutoWriteLock *pAutoLock)
+{
+ AssertPtrReturn(pAutoLock, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+
+ Display *pDisplay = i_getDisplay();
+ if (pDisplay)
+ {
+ bool const fIsEnabled = mRecording.mCtx.IsStarted();
+
+ if (RT_BOOL(fEnable) != fIsEnabled)
+ {
+ LogRel(("Recording: %s\n", fEnable ? "Enabling" : "Disabling"));
+
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (fEnable)
+ {
+ vrc = i_recordingCreate();
+ if (RT_SUCCESS(vrc))
+ {
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Attach the video recording audio driver if required. */
+ if ( mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio)
+ && mRecording.mAudioRec)
+ {
+ vrc = mRecording.mAudioRec->applyConfiguration(mRecording.mCtx.GetConfig());
+ if (RT_SUCCESS(vrc))
+ vrc = mRecording.mAudioRec->doAttachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock);
+
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Attaching to audio recording driver failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+# endif
+ if ( RT_SUCCESS(vrc)
+ && mRecording.mCtx.IsReady()) /* Any video recording (audio and/or video) feature enabled? */
+ {
+ vrc = pDisplay->i_recordingInvalidate();
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_recordingStart(pAutoLock);
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording start failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+ }
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording initialization failed (%Rrc) -- please consult log file for details"), vrc);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Failed to enable with %Rrc\n", vrc));
+ }
+ else
+ {
+ vrc = i_recordingStop(pAutoLock);
+ if (RT_SUCCESS(vrc))
+ {
+# ifdef VBOX_WITH_AUDIO_RECORDING
+ if (mRecording.mAudioRec)
+ mRecording.mAudioRec->doDetachDriverViaEmt(ptrVM.rawUVM(), ptrVM.vtable(), pAutoLock);
+# endif
+ i_recordingDestroy();
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording stop failed (%Rrc) -- please consult log file for details"), vrc);
+ }
+ }
+ else
+ vrc = VERR_VM_INVALID_VM_STATE;
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: %s failed with %Rrc\n", fEnable ? "Enabling" : "Disabling", vrc));
+ }
+ else /* Should not happen. */
+ {
+ vrc = VERR_NO_CHANGE;
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recording already %s"), fIsEnabled ? tr("enabled") : tr("disabled"));
+ }
+ }
+
+ return vrc;
+}
+#endif /* VBOX_WITH_RECORDING */
+
+/**
+ * Called by IInternalSessionControl::OnRecordingChange().
+ */
+HRESULT Console::i_onRecordingChange(BOOL fEnabled)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+#ifdef VBOX_WITH_RECORDING
+ /* Don't trigger recording changes if the VM isn't running. */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ LogFlowThisFunc(("fEnabled=%RTbool\n", RT_BOOL(fEnabled)));
+
+ int vrc = i_recordingEnable(fEnabled, &alock);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.release();
+ ::FireRecordingChangedEvent(mEventSource);
+ }
+ else /* Error set via ErrorInfo within i_recordingEnable() already. */
+ hrc = VBOX_E_IPRT_ERROR;
+ ptrVM.release();
+ }
+#else
+ RT_NOREF(fEnabled);
+#endif /* VBOX_WITH_RECORDING */
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnUSBControllerChange().
+ */
+HRESULT Console::i_onUSBControllerChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ ::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.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = i_fetchSharedFolders(aGlobal);
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireSharedFolderChangedEvent(mEventSource, aGlobal ? Scope_Global : Scope_Machine);
+ }
+
+ return hrc;
+}
+
+/**
+ * Called by IInternalSessionControl::OnGuestDebugControlChange().
+ */
+HRESULT Console::i_onGuestDebugControlChange(IGuestDebugControl *aGuestDebugControl)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ /// @todo
+ }
+
+ if (SUCCEEDED(hrc))
+ ::FireGuestDebugControlChangedEvent(mEventSource, aGuestDebugControl);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+
+/**
+ * Called by IInternalSessionControl::OnUSBDeviceAttach() or locally by
+ * processRemoteUSBDevices() after IInternalMachineControl::RunUSBDeviceFilters()
+ * returns TRUE for a given remote USB device.
+ *
+ * @return S_OK if the device was attached to the VM.
+ * @return failure if not attached.
+ *
+ * @param aDevice The device in question.
+ * @param aError Error information.
+ * @param aMaskedIfs The interfaces to hide from the guest.
+ * @param aCaptureFilename File name where to store the USB traffic.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onUSBDeviceAttach(IUSBDevice *aDevice, IVirtualBoxErrorInfo *aError, ULONG aMaskedIfs,
+ const Utf8Str &aCaptureFilename)
+{
+#ifdef VBOX_WITH_USB
+ LogFlowThisFunc(("aDevice=%p aError=%p\n", aDevice, aError));
+
+ AutoCaller autoCaller(this);
+ ComAssertComRCRetRC(autoCaller.hrc());
+
+ 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.hrc() will return a failure in this case. */
+ LogFlowThisFunc(("Attach request ignored (mMachineState=%d).\n", mMachineState));
+ return ptrVM.hrc();
+ }
+
+ if (aError != NULL)
+ {
+ /* notify callbacks about the error */
+ alock.release();
+ i_onUSBDeviceStateChange(aDevice, true /* aAttached */, aError);
+ return S_OK;
+ }
+
+ /* Don't proceed unless there's at least one USB hub. */
+ if (!ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM()))
+ {
+ LogFlowThisFunc(("Attach request ignored (no USB controller).\n"));
+ return E_FAIL;
+ }
+
+ alock.release();
+ HRESULT hrc = i_attachUSBDevice(aDevice, aMaskedIfs, aCaptureFilename);
+ if (FAILED(hrc))
+ {
+ /* 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 hrc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aDevice, aError, aMaskedIfs, aCaptureFilename);
+ return E_FAIL;
+#endif /* !VBOX_WITH_USB */
+}
+
+/**
+ * Called by IInternalSessionControl::OnUSBDeviceDetach() and locally by
+ * processRemoteUSBDevices().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onUSBDeviceDetach(IN_BSTR aId,
+ IVirtualBoxErrorInfo *aError)
+{
+#ifdef VBOX_WITH_USB
+ Guid Uuid(aId);
+ LogFlowThisFunc(("aId={%RTuuid} aError=%p\n", Uuid.raw(), aError));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ 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::hrc() will return a
+ * failure in this case. */
+
+ AutoVMCallerQuiet autoVMCaller(this);
+ if (FAILED(autoVMCaller.hrc()))
+ {
+ LogFlowThisFunc(("Detach request ignored (mMachineState=%d).\n", mMachineState));
+ return autoVMCaller.hrc();
+ }
+
+ /* 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 hrc = i_detachUSBDevice(pUSBDevice);
+ if (FAILED(hrc))
+ {
+ /* 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 hrc;
+
+#else /* !VBOX_WITH_USB */
+ RT_NOREF(aId, aError);
+ return E_FAIL;
+#endif /* !VBOX_WITH_USB */
+}
+
+/**
+ * Called by IInternalSessionControl::OnBandwidthGroupChange().
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger bandwidth group changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if ( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Teleporting
+ || mMachineState == MachineState_LiveSnapshotting
+ )
+ {
+ /* No need to call in the EMT thread. */
+ Bstr bstrName;
+ hrc = aBandwidthGroup->COMGETTER(Name)(bstrName.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str const strName(bstrName);
+ LONG64 cMax;
+ hrc = aBandwidthGroup->COMGETTER(MaxBytesPerSec)(&cMax);
+ if (SUCCEEDED(hrc))
+ {
+ BandwidthGroupType_T enmType;
+ hrc = aBandwidthGroup->COMGETTER(Type)(&enmType);
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = VINF_SUCCESS;
+ if (enmType == BandwidthGroupType_Disk)
+ vrc = ptrVM.vtable()->pfnPDMR3AsyncCompletionBwMgrSetMaxForFile(ptrVM.rawUVM(), strName.c_str(),
+ (uint32_t)cMax);
+#ifdef VBOX_WITH_NETSHAPER
+ else if (enmType == BandwidthGroupType_Network)
+ vrc = ptrVM.vtable()->pfnPDMR3NsBwGroupSetLimit(ptrVM.rawUVM(), strName.c_str(), cMax);
+ else
+ hrc = E_NOTIMPL;
+#endif
+ AssertRC(vrc);
+ }
+ }
+ }
+ }
+ else
+ hrc = i_setInvalidMachineStateError();
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ {
+ alock.release();
+ ::FireBandwidthGroupChangedEvent(mEventSource, aBandwidthGroup);
+ }
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+/**
+ * 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.hrc());
+
+ HRESULT hrc = S_OK;
+
+ /* don't trigger medium changes if the VM isn't running */
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ if (aRemove)
+ hrc = i_doStorageDeviceDetach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent));
+ else
+ hrc = i_doStorageDeviceAttach(aMediumAttachment, ptrVM.rawUVM(), ptrVM.vtable(), RT_BOOL(aSilent));
+ ptrVM.release();
+ }
+
+ /* notify console callbacks on success */
+ if (SUCCEEDED(hrc))
+ ::FireStorageDeviceChangedEvent(mEventSource, aMediumAttachment, aRemove, aSilent);
+
+ LogFlowThisFunc(("Leaving hrc=%#x\n", hrc));
+ return hrc;
+}
+
+HRESULT Console::i_onExtraDataChange(const Bstr &aMachineId, const Bstr &aKey, const Bstr &aVal)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ if (aMachineId != i_getId())
+ return S_OK;
+
+ /* don't do anything if the VM isn't running */
+ if (aKey == "VBoxInternal2/TurnResetIntoPowerOff")
+ {
+ SafeVMPtrQuiet ptrVM(this);
+ if (ptrVM.isOk())
+ {
+ mfTurnResetIntoPowerOff = aVal == "1";
+ int vrc = ptrVM.vtable()->pfnVMR3SetPowerOffInsteadOfReset(ptrVM.rawUVM(), mfTurnResetIntoPowerOff);
+ AssertRC(vrc);
+
+ ptrVM.release();
+ }
+ }
+
+ /* notify console callbacks on success */
+ ::FireExtraDataChangedEvent(mEventSource, aMachineId.raw(), aKey.raw(), aVal.raw());
+
+ LogFlowThisFunc(("Leaving S_OK\n"));
+ return S_OK;
+}
+
+/**
+ * @note Temporarily locks this object for writing.
+ */
+HRESULT Console::i_getGuestProperty(const Utf8Str &aName, Utf8Str *aValue, LONG64 *aTimestamp, Utf8Str *aFlags)
+{
+#ifndef VBOX_WITH_GUEST_PROPS
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_PROPS */
+ if (!RT_VALID_PTR(aValue))
+ return E_POINTER;
+ if (aTimestamp != NULL && !RT_VALID_PTR(aTimestamp))
+ return E_POINTER;
+ if (aFlags != NULL && !RT_VALID_PTR(aFlags))
+ return E_POINTER;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.hrc()))
+ return ptrVM.hrc();
+
+ /* 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 hrc = 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];
+
+ hrc = S_OK;
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ {
+ *aValue = "";
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("The VBoxGuestPropSvc service call failed with the error %Rrc"), vrc);
+ }
+ catch(std::bad_alloc & /*e*/)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ return hrc;
+#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.hrc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.hrc()))
+ return ptrVM.hrc();
+
+ /* 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.hrc());
+
+ /* protect mpUVM (if not NULL) */
+ SafeVMPtrQuiet ptrVM(this);
+ if (FAILED(ptrVM.hrc()))
+ return ptrVM.hrc();
+
+ /* 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.hrc());
+
+ /* protect mpUVM (if not NULL) */
+ AutoVMCallerWeak autoVMCaller(this);
+ if (FAILED(autoVMCaller.hrc()))
+ return autoVMCaller.hrc();
+
+ /* 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 hrc = S_OK;
+ IProgress *pProgress = static_cast<IProgress *>(pvUser);
+ if (pProgress)
+ {
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, VERR_INVALID_PARAMETER);
+ hrc = pProgressControl->SetCurrentOperationProgress(uPercentage);
+ }
+ return SUCCEEDED(hrc) ? 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.hrc());
+
+ HRESULT hrc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* Get the VM - must be done before the read-locking. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ /* 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 = FALSE;
+ hrc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache);
+ AssertComRC(hrc);
+ SafeIfaceArray<IStorageController> ctrls;
+ hrc = mMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls));
+ AssertComRC(hrc);
+ LONG lDev = -1;
+ hrc = aMediumAttachment->COMGETTER(Device)(&lDev);
+ AssertComRC(hrc);
+ LONG lPort = -1;
+ hrc = aMediumAttachment->COMGETTER(Port)(&lPort);
+ AssertComRC(hrc);
+ IMedium *pMedium = NULL;
+ hrc = aMediumAttachment->COMGETTER(Medium)(&pMedium);
+ AssertComRC(hrc);
+ Bstr mediumLocation;
+ if (pMedium)
+ {
+ hrc = pMedium->COMGETTER(Location)(mediumLocation.asOutParam());
+ AssertComRC(hrc);
+ }
+
+ Bstr attCtrlName;
+ hrc = aMediumAttachment->COMGETTER(Controller)(attCtrlName.asOutParam());
+ AssertComRC(hrc);
+ ComPtr<IStorageController> pStorageController;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ Bstr ctrlName;
+ hrc = ctrls[i]->COMGETTER(Name)(ctrlName.asOutParam());
+ AssertComRC(hrc);
+ 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;
+ hrc = pStorageController->COMGETTER(ControllerType)(&enmCtrlType);
+ AssertComRC(hrc);
+ const char *pcszDevice = i_storageControllerTypeToStr(enmCtrlType);
+
+ StorageBus_T enmBus;
+ hrc = pStorageController->COMGETTER(Bus)(&enmBus);
+ AssertComRC(hrc);
+
+ ULONG uInstance = 0;
+ hrc = pStorageController->COMGETTER(Instance)(&uInstance);
+ AssertComRC(hrc);
+
+ BOOL fUseHostIOCache = TRUE;
+ hrc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ AssertComRC(hrc);
+
+ unsigned uLUN;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN);
+ AssertComRCReturnRC(hrc);
+
+ Assert(mMachineState == MachineState_DeletingSnapshotOnline);
+
+ /* Pause the VM, as it might have pending IO on this drive */
+ bool fResume = false;
+ hrc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(), strDiskIntegrityFlag.asOutParam());
+ if ( hrc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, true /* fSetupMerge */,
+ aSourceIdx, aTargetIdx, aMediumAttachment, mMachineState, &hrc);
+ /* error handling is after resuming the VM */
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(hrc))
+ return hrc;
+
+ PPDMIBASE pIBase = NULL;
+ PPDMIMEDIA pIMedium = NULL;
+ vrc = ptrVM.vtable()->pfnPDMR3QueryDriverOnLun(ptrVM.rawUVM(), pcszDevice, uInstance, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc))
+ {
+ if (pIBase)
+ {
+ pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (!pIMedium)
+ return setError(E_FAIL, tr("could not query medium interface of controller"));
+ }
+ else
+ return setError(E_FAIL, tr("could not query base interface of controller"));
+ }
+
+ /* Finally trigger the merge. */
+ vrc = pIMedium->pfnMerge(pIMedium, onlineMergeMediumProgress, aProgress);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to perform an online medium merge (%Rrc)"), vrc);
+
+ alock.acquire();
+ /* Pause the VM, as it might have pending IO on this drive */
+ hrc = i_suspendBeforeConfigChange(ptrVM.rawUVM(), ptrVM.vtable(), &alock, &fResume);
+ if (FAILED(hrc))
+ return hrc;
+ alock.release();
+
+ /* Update medium chain and state now, so that the VM can continue. */
+ hrc = mControl->FinishOnlineMergeMedium();
+
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, uInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv, false /* fSetupMerge */,
+ 0 /* uMergeSource */, 0 /* uMergeTarget */, aMediumAttachment, mMachineState, &hrc);
+ /* error handling is after resuming the VM */
+
+ if (fResume)
+ i_resumeAfterConfigChange(ptrVM.rawUVM(), ptrVM.vtable());
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(hrc))
+ return hrc;
+
+ return hrc;
+}
+
+HRESULT Console::i_reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments)
+{
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (size_t i = 0; i < aAttachments.size(); ++i)
+ {
+ /*
+ * 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.
+ */
+ Bstr controllerName;
+ HRESULT hrc = aAttachments[i]->COMGETTER(Controller)(controllerName.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ ComPtr<IStorageController> pStorageController;
+ hrc = mMachine->GetStorageControllerByName(controllerName.raw(), pStorageController.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ StorageControllerType_T enmController;
+ hrc = pStorageController->COMGETTER(ControllerType)(&enmController);
+ if (FAILED(hrc))
+ return hrc;
+ const char * const pcszDevice = i_storageControllerTypeToStr(enmController);
+
+ ULONG lInstance;
+ hrc = pStorageController->COMGETTER(Instance)(&lInstance);
+ if (FAILED(hrc))
+ return hrc;
+
+ StorageBus_T enmBus;
+ hrc = pStorageController->COMGETTER(Bus)(&enmBus);
+ if (FAILED(hrc))
+ return hrc;
+
+ BOOL fUseHostIOCache;
+ hrc = pStorageController->COMGETTER(UseHostIOCache)(&fUseHostIOCache);
+ if (FAILED(hrc))
+ return hrc;
+
+ BOOL fBuiltinIOCache;
+ hrc = mMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache);
+ if (FAILED(hrc))
+ return hrc;
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(),
+ strDiskIntegrityFlag.asOutParam());
+ if ( hrc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ alock.release();
+
+ hrc = S_OK;
+ IMediumAttachment *pAttachment = aAttachments[i];
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY,
+ (PFNRT)i_reconfigureMediumAttachment, 15,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pcszDevice, lInstance, enmBus,
+ fUseHostIOCache, fBuiltinIOCache, fInsertDiskIntegrityDrv,
+ false /* fSetupMerge */, 0 /* uMergeSource */, 0 /* uMergeTarget */,
+ pAttachment, mMachineState, &hrc);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, "%Rrc", vrc);
+ if (FAILED(hrc))
+ throw hrc;
+
+ alock.acquire();
+ }
+
+ return S_OK;
+}
+
+HRESULT Console::i_onVMProcessPriorityChange(VMProcPriority_T priority)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return hrc;
+
+ RTPROCPRIORITY enmProcPriority = RTPROCPRIORITY_DEFAULT;
+ switch (priority)
+ {
+ case VMProcPriority_Default:
+ enmProcPriority = RTPROCPRIORITY_DEFAULT;
+ break;
+ case VMProcPriority_Flat:
+ enmProcPriority = RTPROCPRIORITY_FLAT;
+ break;
+ case VMProcPriority_Low:
+ enmProcPriority = RTPROCPRIORITY_LOW;
+ break;
+ case VMProcPriority_Normal:
+ enmProcPriority = RTPROCPRIORITY_NORMAL;
+ break;
+ case VMProcPriority_High:
+ enmProcPriority = RTPROCPRIORITY_HIGH;
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Unsupported priority type (%d)"), priority);
+ }
+ int vrc = RTProcSetPriority(enmProcPriority);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc,
+ tr("Could not set the priority of the process (%Rrc). Try to set it when VM is not started."), vrc);
+
+ return hrc;
+}
+
+/**
+ * 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.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Teleporting:
+ case MachineState_LiveSnapshotting:
+ break;
+
+ case MachineState_Paused:
+ case MachineState_TeleportingPausedVM:
+ case MachineState_OnlineSnapshotting:
+ /* Remove any keys which are supposed to be removed on a suspend. */
+ if ( aReason == Reason_HostSuspend
+ || aReason == Reason_HostBatteryLow)
+ {
+ i_removeSecretKeysOnSuspend();
+ return S_OK;
+ }
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Already paused"));
+
+ default:
+ return i_setInvalidMachineStateError();
+ }
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ LogFlowThisFunc(("Sending PAUSE request...\n"));
+ if (aReason != Reason_Unspecified)
+ LogRel(("Pausing VM execution, reason '%s'\n", ::stringifyReason(aReason)));
+
+ /** @todo r=klaus make use of aReason */
+ VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER;
+ if (aReason == Reason_HostSuspend)
+ enmReason = VMSUSPENDREASON_HOST_SUSPEND;
+ else if (aReason == Reason_HostBatteryLow)
+ enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW;
+
+ int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason);
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc);
+ else if ( aReason == Reason_HostSuspend
+ || aReason == Reason_HostBatteryLow)
+ {
+ alock.acquire();
+ i_removeSecretKeysOnSuspend();
+ }
+ }
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Worker for Console::Resume and internal entry point for resuming a VM for
+ * a specific reason.
+ */
+HRESULT Console::i_resume(Reason_T aReason, AutoWriteLock &alock)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /* get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+
+ LogFlowThisFunc(("Sending RESUME request...\n"));
+ if (aReason != Reason_Unspecified)
+ LogRel(("Resuming VM execution, reason '%s'\n", ::stringifyReason(aReason)));
+
+ int vrc;
+ VMSTATE const enmVMState = mpVMM->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if (enmVMState == VMSTATE_CREATED)
+ {
+#ifdef VBOX_WITH_EXTPACK
+ vrc = mptrExtPackManager->i_callAllVmPowerOnHooks(this, ptrVM.vtable()->pfnVMR3GetVM(ptrVM.rawUVM()), ptrVM.vtable());
+#else
+ vrc = VINF_SUCCESS;
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = ptrVM.vtable()->pfnVMR3PowerOn(ptrVM.rawUVM()); /* (PowerUpPaused) */
+ }
+ else
+ {
+ VMRESUMEREASON enmReason;
+ if (aReason == Reason_HostResume)
+ {
+ /*
+ * Host resume may be called multiple times successively. We don't want to VMR3Resume->vmR3Resume->vmR3TrySetState()
+ * to assert on us, hence check for the VM state here and bail if it's not in the 'suspended' state.
+ * See @bugref{3495}.
+ *
+ * Also, don't resume the VM through a host-resume unless it was suspended due to a host-suspend.
+ */
+ if (enmVMState != VMSTATE_SUSPENDED)
+ {
+ LogRel(("Ignoring VM resume request, VM is currently not suspended (%d)\n", enmVMState));
+ return S_OK;
+ }
+ VMSUSPENDREASON const enmSuspendReason = ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM());
+ if (enmSuspendReason != VMSUSPENDREASON_HOST_SUSPEND)
+ {
+ LogRel(("Ignoring VM resume request, VM was not suspended due to host-suspend (%d)\n", enmSuspendReason));
+ return S_OK;
+ }
+
+ enmReason = VMRESUMEREASON_HOST_RESUME;
+ }
+ else
+ {
+ /*
+ * Any other reason to resume the VM throws an error when the VM was suspended due to a host suspend.
+ * See @bugref{7836}.
+ */
+ if ( enmVMState == VMSTATE_SUSPENDED
+ && ptrVM.vtable()->pfnVMR3GetSuspendReason(ptrVM.rawUVM()) == VMSUSPENDREASON_HOST_SUSPEND)
+ return setError(VBOX_E_INVALID_VM_STATE, tr("VM is paused due to host power management"));
+
+ enmReason = aReason == Reason_Snapshot ? VMRESUMEREASON_STATE_SAVED : VMRESUMEREASON_USER;
+ }
+
+ // for snapshots: no state change callback, VBoxSVC does everything
+ if (aReason == Reason_Snapshot)
+ mVMStateChangeCallbackDisabled = true;
+
+ vrc = ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), enmReason);
+
+ if (aReason == Reason_Snapshot)
+ mVMStateChangeCallbackDisabled = false;
+ }
+
+ HRESULT hrc = RT_SUCCESS(vrc) ? S_OK
+ : setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not resume the machine execution (%Rrc)"), vrc);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Internal entry point for saving state of a VM for a specific reason. This
+ * method is completely synchronous.
+ *
+ * The machine state is already set appropriately. It is only changed when
+ * saving state actually paused the VM (happens with live snapshots and
+ * teleportation), and in this case reflects the now paused variant.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_saveState(Reason_T aReason, const ComPtr<IProgress> &aProgress, const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath, bool aPauseVM, bool &aLeftPaused)
+{
+ LogFlowThisFuncEnter();
+ aLeftPaused = false;
+
+ AssertReturn(!aProgress.isNull(), E_INVALIDARG);
+ AssertReturn(!aStateFilePath.isEmpty(), E_INVALIDARG);
+ Assert(aSnapshot.isNull() || aReason == Reason_Snapshot);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+ if ( mMachineState != MachineState_Saving
+ && mMachineState != MachineState_LiveSnapshotting
+ && mMachineState != MachineState_OnlineSnapshotting
+ && mMachineState != MachineState_Teleporting
+ && mMachineState != MachineState_TeleportingPausedVM)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Cannot save the execution state as the machine is not running or paused (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+ bool fContinueAfterwards = mMachineState != MachineState_Saving;
+
+ Bstr strDisableSaveState;
+ mMachine->GetExtraData(Bstr("VBoxInternal2/DisableSaveState").raw(), strDisableSaveState.asOutParam());
+ if (strDisableSaveState == "1")
+ return setError(VBOX_E_VM_ERROR,
+ tr("Saving the execution state is disabled for this VM"));
+
+ if (aReason != Reason_Unspecified)
+ LogRel(("Saving state of VM, reason '%s'\n", ::stringifyReason(aReason)));
+
+ /* ensure the directory for the saved state file exists */
+ {
+ Utf8Str dir = aStateFilePath;
+ dir.stripFilename();
+ if (!RTDirExists(dir.c_str()))
+ {
+ int vrc = RTDirCreateFullPath(dir.c_str(), 0700);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Could not create a directory '%s' to save the state to (%Rrc)"),
+ dir.c_str(), vrc);
+ }
+ }
+
+ /* Get the VM handle early, we need it in several places. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ bool fPaused = false;
+ if (aPauseVM)
+ {
+ /* release the lock before a VMR3* call (EMT might wait for it, @bugref{7648})! */
+ alock.release();
+ VMSUSPENDREASON enmReason = VMSUSPENDREASON_USER;
+ if (aReason == Reason_HostSuspend)
+ enmReason = VMSUSPENDREASON_HOST_SUSPEND;
+ else if (aReason == Reason_HostBatteryLow)
+ enmReason = VMSUSPENDREASON_HOST_BATTERY_LOW;
+ int vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), enmReason);
+ alock.acquire();
+
+ if (RT_SUCCESS(vrc))
+ fPaused = true;
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not suspend the machine execution (%Rrc)"), vrc);
+ }
+
+ Bstr bstrStateKeyId;
+ Bstr bstrStateKeyStore;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (FAILED(hrc))
+ hrc = setError(hrc, tr("Could not get key store for state file(%Rhrc (0x%08X))"), hrc, hrc);
+ }
+ else
+ hrc = setError(hrc, tr("Could not get key id for state file(%Rhrc (0x%08X))"), hrc, hrc);
+ }
+#endif
+
+ if (SUCCEEDED(hrc))
+ {
+ LogFlowFunc(("Saving the state to '%s'...\n", aStateFilePath.c_str()));
+
+ mpVmm2UserMethods->pISnapshot = aSnapshot;
+ mptrCancelableProgress = aProgress;
+
+ SsmStream ssmStream(this, ptrVM.vtable(), m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+ int vrc = ssmStream.create(aStateFilePath.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps = NULL;
+ void *pvStreamOpsUser = NULL;
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.release();
+
+ vrc = ptrVM.vtable()->pfnVMR3Save(ptrVM.rawUVM(),
+ NULL /*pszFilename*/,
+ pStreamOps,
+ pvStreamOpsUser,
+ fContinueAfterwards,
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(aProgress),
+ &aLeftPaused);
+
+ alock.acquire();
+ }
+
+ ssmStream.close();
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = RTFileDelete(aStateFilePath.c_str());
+ AssertRC(vrc2);
+ }
+ }
+
+ mpVmm2UserMethods->pISnapshot = NULL;
+ mptrCancelableProgress.setNull();
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(fContinueAfterwards || !aLeftPaused);
+
+ if (!fContinueAfterwards)
+ {
+ /*
+ * The machine has been successfully saved, so power it down
+ * (vmstateChangeCallback() will set state to Saved on success).
+ * Note: we release the VM caller, otherwise it will deadlock.
+ */
+ ptrVM.release();
+ alock.release();
+ autoCaller.release();
+
+ HRESULT hrc2 = i_powerDown();
+ AssertComRC(hrc2);
+
+ autoCaller.add();
+ alock.acquire();
+ }
+ else if (fPaused)
+ aLeftPaused = true;
+ }
+ else
+ {
+ if (fPaused)
+ {
+ alock.release();
+ ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_STATE_RESTORED);
+ alock.acquire();
+ }
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to save the machine state to '%s' (%Rrc)"),
+ aStateFilePath.c_str(), vrc);
+ }
+ }
+ }
+
+ LogFlowFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Internal entry point for cancelling a VM save state.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_cancelSaveState()
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ ptrVM.vtable()->pfnSSMR3Cancel(ptrVM.rawUVM());
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Sends audio (frame) data to the recording routines.
+ *
+ * @returns HRESULT
+ * @param pvData Audio data to send.
+ * @param cbData Size (in bytes) of audio data to send.
+ * @param uTimestampMs Timestamp (in ms) of audio data.
+ */
+HRESULT Console::i_recordingSendAudio(const void *pvData, size_t cbData, uint64_t uTimestampMs)
+{
+ if ( mRecording.mCtx.IsStarted()
+ && mRecording.mCtx.IsFeatureEnabled(RecordingFeature_Audio))
+ return mRecording.mCtx.SendAudioFrame(pvData, cbData, uTimestampMs);
+
+ return S_OK;
+}
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+#ifdef VBOX_WITH_RECORDING
+
+int Console::i_recordingGetSettings(settings::RecordingSettings &recording)
+{
+ Assert(mMachine.isNotNull());
+
+ recording.applyDefaults();
+
+ ComPtr<IRecordingSettings> pRecordSettings;
+ HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ BOOL fTemp;
+ hrc = pRecordSettings->COMGETTER(Enabled)(&fTemp);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recording.common.fEnabled = RT_BOOL(fTemp);
+
+ SafeIfaceArray<IRecordingScreenSettings> paRecScreens;
+ hrc = pRecordSettings->COMGETTER(Screens)(ComSafeArrayAsOutParam(paRecScreens));
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ for (unsigned long i = 0; i < (unsigned long)paRecScreens.size(); ++i)
+ {
+ settings::RecordingScreenSettings recScreenSettings;
+ ComPtr<IRecordingScreenSettings> pRecScreenSettings = paRecScreens[i];
+
+ hrc = pRecScreenSettings->COMGETTER(Enabled)(&fTemp);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.fEnabled = RT_BOOL(fTemp);
+ com::SafeArray<RecordingFeature_T> vecFeatures;
+ hrc = pRecScreenSettings->COMGETTER(Features)(ComSafeArrayAsOutParam(vecFeatures));
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ /* Make sure to clear map first, as we want to (re-)set enabled features. */
+ recScreenSettings.featureMap.clear();
+ for (size_t f = 0; f < vecFeatures.size(); ++f)
+ {
+ if (vecFeatures[f] == RecordingFeature_Audio)
+ recScreenSettings.featureMap[RecordingFeature_Audio] = true;
+ else if (vecFeatures[f] == RecordingFeature_Video)
+ recScreenSettings.featureMap[RecordingFeature_Video] = true;
+ }
+ hrc = pRecScreenSettings->COMGETTER(MaxTime)((ULONG *)&recScreenSettings.ulMaxTimeS);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(MaxFileSize)((ULONG *)&recScreenSettings.File.ulMaxSizeMB);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ Bstr bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(Filename)(bstrTemp.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.File.strName = bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(Options)(bstrTemp.asOutParam());
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ recScreenSettings.strOptions = bstrTemp;
+ hrc = pRecScreenSettings->COMGETTER(AudioCodec)(&recScreenSettings.Audio.enmCodec);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioDeadline)(&recScreenSettings.Audio.enmDeadline);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioRateControlMode)(&recScreenSettings.Audio.enmRateCtlMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioHz)((ULONG *)&recScreenSettings.Audio.uHz);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioBits)((ULONG *)&recScreenSettings.Audio.cBits);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(AudioChannels)((ULONG *)&recScreenSettings.Audio.cChannels);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoCodec)(&recScreenSettings.Video.enmCodec);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoWidth)((ULONG *)&recScreenSettings.Video.ulWidth);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoHeight)((ULONG *)&recScreenSettings.Video.ulHeight);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoDeadline)(&recScreenSettings.Video.enmDeadline);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoRateControlMode)(&recScreenSettings.Video.enmRateCtlMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoScalingMode)(&recScreenSettings.Video.enmScalingMode);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoRate)((ULONG *)&recScreenSettings.Video.ulRate);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+ hrc = pRecScreenSettings->COMGETTER(VideoFPS)((ULONG *)&recScreenSettings.Video.ulFPS);
+ AssertComRCReturn(hrc, VERR_INVALID_PARAMETER);
+
+ recording.mapScreens[i] = recScreenSettings;
+ }
+
+ Assert(recording.mapScreens.size() == paRecScreens.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the recording context.
+ *
+ * @returns VBox status code.
+ */
+int Console::i_recordingCreate(void)
+{
+ settings::RecordingSettings recordingSettings;
+ int vrc = i_recordingGetSettings(recordingSettings);
+ if (RT_SUCCESS(vrc))
+ vrc = mRecording.mCtx.Create(this, recordingSettings);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Destroys the recording context.
+ */
+void Console::i_recordingDestroy(void)
+{
+ mRecording.mCtx.Destroy();
+}
+
+/**
+ * Starts recording. Does nothing if recording is already active.
+ *
+ * @returns VBox status code.
+ */
+int Console::i_recordingStart(util::AutoWriteLock *pAutoLock /* = NULL */)
+{
+ RT_NOREF(pAutoLock);
+
+ if (mRecording.mCtx.IsStarted())
+ return VINF_SUCCESS;
+
+ LogRel(("Recording: Starting ...\n"));
+
+ int vrc = mRecording.mCtx.Start();
+ if (RT_SUCCESS(vrc))
+ {
+ for (unsigned uScreen = 0; uScreen < mRecording.mCtx.GetStreamCount(); uScreen++)
+ mDisplay->i_recordingScreenChanged(uScreen);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Stops recording. Does nothing if recording is not active.
+ */
+int Console::i_recordingStop(util::AutoWriteLock *pAutoLock /* = NULL */)
+{
+ if (!mRecording.mCtx.IsStarted())
+ return VINF_SUCCESS;
+
+ LogRel(("Recording: Stopping ...\n"));
+
+ int vrc = mRecording.mCtx.Stop();
+ if (RT_SUCCESS(vrc))
+ {
+ const size_t cStreams = mRecording.mCtx.GetStreamCount();
+ for (unsigned uScreen = 0; uScreen < cStreams; ++uScreen)
+ mDisplay->i_recordingScreenChanged(uScreen);
+
+ if (pAutoLock)
+ pAutoLock->release();
+
+ ComPtr<IRecordingSettings> pRecordSettings;
+ HRESULT hrc = mMachine->COMGETTER(RecordingSettings)(pRecordSettings.asOutParam());
+ ComAssertComRC(hrc);
+ hrc = pRecordSettings->COMSETTER(Enabled)(FALSE);
+ ComAssertComRC(hrc);
+
+ if (pAutoLock)
+ pAutoLock->acquire();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#endif /* VBOX_WITH_RECORDING */
+
+/**
+ * Gets called by Session::UpdateMachineState()
+ * (IInternalSessionControl::updateMachineState()).
+ *
+ * Must be called only in certain cases (see the implementation).
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_updateMachineState(MachineState_T aMachineState)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ 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.hrc());
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ MachineState_T enmMachineState = MachineState_Null;
+ VMSTATE enmVMState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM());
+ switch (enmVMState)
+ {
+ case VMSTATE_CREATING:
+ case VMSTATE_CREATED:
+ case VMSTATE_POWERING_ON:
+ enmMachineState = MachineState_Starting;
+ break;
+ case VMSTATE_LOADING:
+ enmMachineState = MachineState_Restoring;
+ break;
+ case VMSTATE_RESUMING:
+ case VMSTATE_SUSPENDING:
+ case VMSTATE_SUSPENDING_LS:
+ case VMSTATE_SUSPENDING_EXT_LS:
+ case VMSTATE_SUSPENDED:
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDED_EXT_LS:
+ enmMachineState = MachineState_Paused;
+ break;
+ case VMSTATE_RUNNING:
+ case VMSTATE_RUNNING_LS:
+ case VMSTATE_RESETTING:
+ case VMSTATE_RESETTING_LS:
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ case VMSTATE_DEBUGGING:
+ case VMSTATE_DEBUGGING_LS:
+ enmMachineState = MachineState_Running;
+ break;
+ case VMSTATE_SAVING:
+ enmMachineState = MachineState_Saving;
+ break;
+ case VMSTATE_POWERING_OFF:
+ case VMSTATE_POWERING_OFF_LS:
+ case VMSTATE_DESTROYING:
+ enmMachineState = MachineState_Stopping;
+ break;
+ case VMSTATE_OFF:
+ case VMSTATE_OFF_LS:
+ case VMSTATE_FATAL_ERROR:
+ case VMSTATE_FATAL_ERROR_LS:
+ case VMSTATE_LOAD_FAILURE:
+ case VMSTATE_TERMINATED:
+ enmMachineState = MachineState_PoweredOff;
+ break;
+ case VMSTATE_GURU_MEDITATION:
+ case VMSTATE_GURU_MEDITATION_LS:
+ enmMachineState = MachineState_Stuck;
+ break;
+ default:
+ AssertMsgFailed(("%s\n", ptrVM.vtable()->pfnVMR3GetStateName(enmVMState)));
+ enmMachineState = MachineState_PoweredOff;
+ }
+ aNominalState = enmMachineState;
+
+ LogFlowFuncLeave();
+ return S_OK;
+}
+
+void Console::i_onMousePointerShapeChange(bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape,
+ uint32_t cbShape)
+{
+#if 0
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("fVisible=%d, fAlpha=%d, xHot = %d, yHot = %d, width=%d, height=%d, shape=%p\n",
+ fVisible, fAlpha, xHot, yHot, width, height, pShape));
+#endif
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ if (!mMouse.isNull())
+ mMouse->updateMousePointerShape(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape);
+
+ com::SafeArray<BYTE> shape(cbShape);
+ if (pu8Shape)
+ memcpy(shape.raw(), pu8Shape, cbShape);
+ ::FireMousePointerShapeChangedEvent(mEventSource, fVisible, fAlpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape));
+
+#if 0
+ LogFlowThisFuncLeave();
+#endif
+}
+
+void Console::i_onMouseCapabilityChange(BOOL supportsAbsolute, BOOL supportsRelative,
+ BOOL supportsTouchScreen, BOOL supportsTouchPad, BOOL needsHostCursor)
+{
+ LogFlowThisFunc(("supportsAbsolute=%d supportsRelative=%d supportsTouchScreen=%d supportsTouchPad=%d needsHostCursor=%d\n",
+ supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::FireMouseCapabilityChangedEvent(mEventSource, supportsAbsolute, supportsRelative, supportsTouchScreen, supportsTouchPad, needsHostCursor);
+}
+
+void Console::i_onStateChange(MachineState_T machineState)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+ ::FireStateChangedEvent(mEventSource, machineState);
+}
+
+void Console::i_onAdditionsStateChange()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::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.hrc());
+
+ /** @todo implement this */
+}
+
+void Console::i_onKeyboardLedsChange(bool fNumLock, bool fCapsLock, bool fScrollLock)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::FireKeyboardLedsChangedEvent(mEventSource, fNumLock, fCapsLock, fScrollLock);
+}
+
+void Console::i_onUSBDeviceStateChange(IUSBDevice *aDevice, bool aAttached,
+ IVirtualBoxErrorInfo *aError)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::FireUSBDeviceStateChangedEvent(mEventSource, aDevice, aAttached, aError);
+}
+
+void Console::i_onRuntimeError(BOOL aFatal, IN_BSTR aErrorID, IN_BSTR aMessage)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ ::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.hrc());
+
+ ComPtr<IEvent> ptrEvent;
+ if (aCheck)
+ {
+ *aCanShow = TRUE;
+ HRESULT hrc = ::CreateCanShowWindowEvent(ptrEvent.asOutParam(), mEventSource);
+ if (SUCCEEDED(hrc))
+ {
+ VBoxEventDesc EvtDesc(ptrEvent, mEventSource);
+ BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */
+ //Assert(fDelivered);
+ if (fDelivered)
+ {
+ // bit clumsy
+ ComPtr<ICanShowWindowEvent> ptrCanShowEvent = ptrEvent;
+ if (ptrCanShowEvent)
+ {
+ BOOL fVetoed = FALSE;
+ BOOL fApproved = FALSE;
+ ptrCanShowEvent->IsVetoed(&fVetoed);
+ ptrCanShowEvent->IsApproved(&fApproved);
+ *aCanShow = fApproved || !fVetoed;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+ else
+ {
+ HRESULT hrc = ::CreateShowWindowEvent(ptrEvent.asOutParam(), mEventSource, 0);
+ if (SUCCEEDED(hrc))
+ {
+ VBoxEventDesc EvtDesc(ptrEvent, mEventSource);
+ BOOL fDelivered = EvtDesc.fire(5000); /* Wait up to 5 secs for delivery */
+ //Assert(fDelivered);
+ if (fDelivered)
+ {
+ ComPtr<IShowWindowEvent> ptrShowEvent = ptrEvent;
+ if (ptrShowEvent)
+ {
+ LONG64 idWindow = 0;
+ ptrShowEvent->COMGETTER(WinId)(&idWindow);
+ if (idWindow != 0 && *aWinId == 0)
+ *aWinId = idWindow;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+// private methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Loads the VMM if needed.
+ *
+ * @returns COM status.
+ * @remarks Caller must write lock the console object.
+ */
+HRESULT Console::i_loadVMM(void) RT_NOEXCEPT
+{
+ if ( mhModVMM == NIL_RTLDRMOD
+ || mpVMM == NULL)
+ {
+ Assert(!mpVMM);
+
+ HRESULT hrc;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hModVMM = NIL_RTLDRMOD;
+ int vrc = SUPR3HardenedLdrLoadAppPriv("VBoxVMM", &hModVMM, RTLDRLOAD_FLAGS_LOCAL, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ PFNVMMGETVTABLE pfnGetVTable = NULL;
+ vrc = RTLdrGetSymbol(hModVMM, VMMR3VTABLE_GETTER_NAME, (void **)&pfnGetVTable);
+ if (pfnGetVTable)
+ {
+ PCVMMR3VTABLE pVMM = pfnGetVTable();
+ if (pVMM)
+ {
+ if (VMMR3VTABLE_IS_COMPATIBLE(pVMM->uMagicVersion))
+ {
+ if (pVMM->uMagicVersion == pVMM->uMagicVersionEnd)
+ {
+ mhModVMM = hModVMM;
+ mpVMM = pVMM;
+ LogFunc(("mhLdrVMM=%p phVMM=%p uMagicVersion=%#RX64\n", hModVMM, pVMM, pVMM->uMagicVersion));
+ return S_OK;
+ }
+
+ hrc = setErrorVrc(vrc, "Bogus VMM vtable: uMagicVersion=%#RX64 uMagicVersionEnd=%#RX64",
+ pVMM->uMagicVersion, pVMM->uMagicVersionEnd);
+ }
+ else
+ hrc = setErrorVrc(vrc, "Incompatible of bogus VMM version magic: %#RX64", pVMM->uMagicVersion);
+ }
+ else
+ hrc = setErrorVrc(vrc, "pfnGetVTable return NULL!");
+ }
+ else
+ hrc = setErrorVrc(vrc, "Failed to locate symbol '%s' in VBoxVMM: %Rrc", VMMR3VTABLE_GETTER_NAME, vrc);
+ RTLdrClose(hModVMM);
+ }
+ else
+ hrc = setErrorVrc(vrc, "Failed to load VBoxVMM: %#RTeic", &ErrInfo.Core);
+ return hrc;
+ }
+
+ return S_OK;
+}
+
+/**
+ * Increases the usage counter of the mpUVM pointer.
+ *
+ * Guarantees that VMR3Destroy() will not be called on it at least until
+ * releaseVMCaller() is called.
+ *
+ * If this method returns a failure, the caller is not allowed to use mpUVM and
+ * may return the failed result code to the upper level. This method sets the
+ * extended error info on failure if \a aQuiet is false.
+ *
+ * Setting \a aQuiet to true is useful for methods that don't want to return
+ * the failed result code to the caller when this method fails (e.g. need to
+ * silently check for the mpUVM availability).
+ *
+ * When mpUVM is NULL but \a aAllowNullVM is true, a corresponding error will be
+ * returned instead of asserting. Having it false is intended as a sanity check
+ * for methods that have checked mMachineState and expect mpUVM *NOT* to be
+ * NULL.
+ *
+ * @param aQuiet true to suppress setting error info
+ * @param aAllowNullVM true to accept mpUVM being NULL and return a failure
+ * (otherwise this method will assert if mpUVM is NULL)
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_addVMCaller(bool aQuiet /* = false */,
+ bool aAllowNullVM /* = false */)
+{
+ RT_NOREF(aAllowNullVM);
+ AutoCaller autoCaller(this);
+ /** @todo Fix race during console/VM reference destruction, refer @bugref{6318}
+ * comment 25. */
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ 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.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturnVoid(mpUVM != NULL);
+
+ Assert(mVMCallers > 0);
+ --mVMCallers;
+
+ if (mVMCallers == 0 && mVMDestroying)
+ {
+ /* inform powerDown() there are no more callers */
+ RTSemEventSignal(mVMZeroCallersSem);
+ }
+}
+
+
+/**
+ * Helper for SafeVMPtrBase.
+ */
+HRESULT Console::i_safeVMPtrRetainer(PUVM *a_ppUVM, PCVMMR3VTABLE *a_ppVMM, bool a_Quiet) RT_NOEXCEPT
+{
+ *a_ppUVM = NULL;
+ *a_ppVMM = NULL;
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Repeat the checks done by addVMCaller.
+ */
+ if (mVMDestroying) /* powerDown() is waiting for all callers to finish */
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is being powered down"));
+ PUVM const pUVM = mpUVM;
+ if (!pUVM)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is powered off"));
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ if (!pVMM)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("No VMM loaded!"));
+
+ /*
+ * Retain a reference to the user mode VM handle and get the global handle.
+ */
+ uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM);
+ if (cRefs == UINT32_MAX)
+ return a_Quiet
+ ? E_ACCESSDENIED
+ : setError(E_ACCESSDENIED, tr("The virtual machine is powered off"));
+
+ /* done */
+ *a_ppUVM = pUVM;
+ *a_ppVMM = pVMM;
+ return S_OK;
+}
+
+void Console::i_safeVMPtrReleaser(PUVM *a_ppUVM)
+{
+ PUVM const pUVM = *a_ppUVM;
+ *a_ppUVM = NULL;
+ if (pUVM)
+ {
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ if (pVMM)
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+}
+
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedOpen(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename, uint32_t fFlags)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+
+ int vrc = RTVfsFileOpenNormal(pszFilename, fFlags, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ vrc = pConsole->i_retainCryptoIf(&pCryptoIf);
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey = NULL;
+
+ vrc = pConsole->m_pKeyStore->retainSecretKey(pConsole->m_strLogKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFile, pConsole->m_strLogKeyStore.c_str(), pszPassword,
+ &pConsole->m_hVfsFileLog);
+ pKey->release();
+ }
+
+ /* On success we keep the reference to keep the cryptographic module loaded. */
+ if (RT_FAILURE(vrc))
+ pConsole->i_releaseCryptoIf(pCryptoIf);
+ }
+
+ /* Always do this because the encrypted log has retained a reference to the underlying file. */
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(vrc))
+ RTFileDelete(pszFilename);
+ }
+
+ return vrc;
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedClose(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ RTVfsFileRelease(pConsole->m_hVfsFileLog);
+ pConsole->m_hVfsFileLog = NIL_RTVFSFILE;
+ return VINF_SUCCESS;
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedDelete(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileDelete(pszFilename);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedRename(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilenameOld,
+ const char *pszFilenameNew, uint32_t fFlags)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileRename(pszFilenameOld, pszFilenameNew, fFlags);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedQuerySize(PCRTLOGOUTPUTIF pIf, void *pvUser, uint64_t *pcbSize)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileQuerySize(pConsole->m_hVfsFileLog, pcbSize);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedWrite(PCRTLOGOUTPUTIF pIf, void *pvUser, const void *pvBuf,
+ size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileWrite(pConsole->m_hVfsFileLog, pvBuf, cbWrite, pcbWritten);
+}
+
+
+/*static*/
+DECLCALLBACK(int) Console::i_logEncryptedFlush(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ Console *pConsole = static_cast<Console *>(pvUser);
+
+ return RTVfsFileFlush(pConsole->m_hVfsFileLog);
+}
+#endif
+
+
+/**
+ * Initialize the release logging facility. In case something
+ * goes wrong, there will be no release logging. Maybe in the future
+ * we can add some logic to use different file names in this case.
+ * Note that the logic must be in sync with Machine::DeleteSettings().
+ */
+HRESULT Console::i_consoleInitReleaseLog(const ComPtr<IMachine> aMachine)
+{
+ Bstr bstrLogFolder;
+ HRESULT hrc = aMachine->COMGETTER(LogFolder)(bstrLogFolder.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strLogDir = bstrLogFolder;
+
+ /* make sure the Logs folder exists */
+ Assert(strLogDir.length());
+ if (!RTDirExists(strLogDir.c_str()))
+ RTDirCreateFullPath(strLogDir.c_str(), 0700);
+
+ Utf8StrFmt logFile("%s%cVBox.log", strLogDir.c_str(), RTPATH_DELIMITER);
+ Utf8StrFmt pngFile("%s%cVBox.png", strLogDir.c_str(), RTPATH_DELIMITER);
+
+ /*
+ * Age the old log files.
+ * Rename .(n-1) to .(n), .(n-2) to .(n-1), ..., and the last log file to .1
+ * Overwrite target files in case they exist.
+ */
+ ComPtr<IVirtualBox> pVirtualBox;
+ aMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ ULONG cHistoryFiles = 3;
+ pSystemProperties->COMGETTER(LogHistoryCount)(&cHistoryFiles);
+ if (cHistoryFiles)
+ {
+ for (int i = cHistoryFiles - 1; i >= 0; i--)
+ {
+ Utf8Str *files[] = { &logFile, &pngFile };
+ Utf8Str oldName, newName;
+
+ for (unsigned int j = 0; j < RT_ELEMENTS(files); ++j)
+ {
+ if (i > 0)
+ oldName.printf("%s.%d", files[j]->c_str(), i);
+ else
+ oldName = *files[j];
+ newName.printf("%s.%d", files[j]->c_str(), i + 1);
+
+ /* If the old file doesn't exist, delete the new file (if it
+ * exists) to provide correct rotation even if the sequence is
+ * broken */
+ if (RTFileRename(oldName.c_str(), newName.c_str(), RTFILEMOVE_FLAGS_REPLACE) == VERR_FILE_NOT_FOUND)
+ RTFileDelete(newName.c_str());
+ }
+ }
+ }
+
+ Bstr bstrLogKeyId;
+ Bstr bstrLogKeyStore;
+ PCRTLOGOUTPUTIF pLogOutputIf = NULL;
+ void *pvLogOutputUser = NULL;
+ int vrc = VINF_SUCCESS;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ hrc = aMachine->COMGETTER(LogKeyId)(bstrLogKeyId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ hrc = aMachine->COMGETTER(LogKeyStore)(bstrLogKeyStore.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && bstrLogKeyId.isNotEmpty()
+ && bstrLogKeyStore.isNotEmpty())
+ {
+ m_LogOutputIf.pfnOpen = Console::i_logEncryptedOpen;
+ m_LogOutputIf.pfnClose = Console::i_logEncryptedClose;
+ m_LogOutputIf.pfnDelete = Console::i_logEncryptedDelete;
+ m_LogOutputIf.pfnRename = Console::i_logEncryptedRename;
+ m_LogOutputIf.pfnQuerySize = Console::i_logEncryptedQuerySize;
+ m_LogOutputIf.pfnWrite = Console::i_logEncryptedWrite;
+ m_LogOutputIf.pfnFlush = Console::i_logEncryptedFlush;
+
+ m_strLogKeyId = Utf8Str(bstrLogKeyId);
+ m_strLogKeyStore = Utf8Str(bstrLogKeyStore);
+
+ pLogOutputIf = &m_LogOutputIf;
+ pvLogOutputUser = this;
+ m_fEncryptedLog = true;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to set encryption for release log (%Rrc)"), vrc);
+ else
+#endif
+ {
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreateEx("VM", logFile.c_str(),
+ RTLOGFLAGS_PREFIX_TIME_PROG | RTLOGFLAGS_RESTRICT_GROUPS,
+ "all all.restrict -default.restrict",
+ "VBOX_RELEASE_LOG", RTLOGDEST_FILE,
+ 32768 /* cMaxEntriesPerGroup */,
+ 0 /* cHistory */, 0 /* uHistoryFileTime */,
+ 0 /* uHistoryFileSize */,
+ pLogOutputIf, pvLogOutputUser,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to open release log (%s, %Rrc)"), ErrInfo.Core.pszMsg, vrc);
+ }
+
+ /* If we've made any directory changes, flush the directory to increase
+ the likelihood that the log file will be usable after a system panic.
+
+ Tip: Try 'export VBOX_RELEASE_LOG_FLAGS=flush' if the last bits of the log
+ is missing. Just don't have too high hopes for this to help. */
+ if (SUCCEEDED(hrc) || cHistoryFiles)
+ RTDirFlush(strLogDir.c_str());
+
+ return hrc;
+}
+
+/**
+ * Common worker for PowerUp and PowerUpPaused.
+ *
+ * @returns COM status code.
+ *
+ * @param aProgress Where to return the progress object.
+ * @param aPaused true if PowerUpPaused called.
+ */
+HRESULT Console::i_powerUp(IProgress **aProgress, bool aPaused)
+{
+ LogFlowThisFuncEnter();
+
+ CheckComArgOutPointerValid(aProgress);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return hrc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+
+ if (Global::IsOnlineOrTransient(mMachineState))
+ return setError(VBOX_E_INVALID_VM_STATE, tr("The virtual machine is already running or busy (machine state: %s)"),
+ Global::stringifyMachineState(mMachineState));
+
+
+ /* Set up release logging as early as possible after the check if
+ * there is already a running VM which we shouldn't disturb. */
+ hrc = i_consoleInitReleaseLog(mMachine);
+ if (FAILED(hrc))
+ return hrc;
+
+#ifdef VBOX_OPENSSL_FIPS
+ LogRel(("crypto: FIPS mode %s\n", FIPS_mode() ? "enabled" : "FAILED"));
+#endif
+
+ /* test and clear the TeleporterEnabled property */
+ BOOL fTeleporterEnabled;
+ hrc = mMachine->COMGETTER(TeleporterEnabled)(&fTeleporterEnabled);
+ if (FAILED(hrc))
+ return hrc;
+
+#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)
+ {
+ hrc = mMachine->COMSETTER(TeleporterEnabled)(FALSE);
+ if (FAILED(hrc))
+ return hrc;
+ }
+#endif
+
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+
+ ComObjPtr<Progress> pPowerupProgress;
+ bool fBeganPoweringUp = false;
+
+ LONG cOperations = 1;
+ LONG ulTotalOperationsWeight = 1;
+ VMPowerUpTask *task = NULL;
+
+ try
+ {
+ /* Create a progress object to track progress of this operation. Must
+ * be done as early as possible (together with BeginPowerUp()) as this
+ * is vital for communicating as much as possible early powerup
+ * failure information to the API caller */
+ pPowerupProgress.createObject();
+ Bstr progressDesc;
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ progressDesc = tr("Restoring virtual machine");
+ else if (fTeleporterEnabled)
+ progressDesc = tr("Teleporting virtual machine");
+ else
+ progressDesc = tr("Starting virtual machine");
+
+ /*
+ * Saved VMs will have to prove that their saved states seem kosher.
+ */
+ Utf8Str strSavedStateFile;
+ Bstr bstrStateKeyId;
+ Bstr bstrStateKeyStore;
+
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ {
+ Bstr bstrSavedStateFile;
+ hrc = mMachine->COMGETTER(StateFilePath)(bstrSavedStateFile.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ strSavedStateFile = bstrSavedStateFile;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ hrc = mMachine->COMGETTER(StateKeyId)(bstrStateKeyId.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ hrc = mMachine->COMGETTER(StateKeyStore)(bstrStateKeyStore.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+#endif
+
+ ComAssertRet(bstrSavedStateFile.isNotEmpty(), E_FAIL);
+ SsmStream ssmStream(this, pVMM, m_pKeyStore, bstrStateKeyId, bstrStateKeyStore);
+ int vrc = ssmStream.open(strSavedStateFile.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps;
+ void *pvStreamOpsUser;
+
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnSSMR3ValidateFile(NULL /*pszFilename*/, pStreamOps, pvStreamOpsUser,
+ false /* fChecksumIt */);
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ Utf8Str errMsg;
+ switch (vrc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ errMsg.printf(tr("VM failed to start because the saved state file '%s' does not exist."),
+ strSavedStateFile.c_str());
+ break;
+ default:
+ errMsg.printf(tr("VM failed to start because the saved state file '%s' is invalid (%Rrc). "
+ "Delete the saved state prior to starting the VM."), strSavedStateFile.c_str(), vrc);
+ break;
+ }
+ throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, errMsg.c_str());
+ }
+
+ }
+
+ /* Read console data, including console shared folders, stored in the
+ * saved state file (if not yet done).
+ */
+ hrc = i_loadDataFromSavedState();
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* 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)
+ {
+ ConsoleSharedFolder *pSF = it->second;
+ AutoCaller sfCaller(pSF);
+ AutoReadLock sfLock(pSF COMMA_LOCKVAL_SRC_POS);
+ sharedFolders[it->first] = SharedFolderData(pSF->i_getHostPath(),
+ pSF->i_isWritable(),
+ pSF->i_isAutoMounted(),
+ pSF->i_getAutoMountPoint());
+ }
+ }
+
+
+ /* Setup task object and thread to carry out the operation
+ * asynchronously */
+ try { task = new VMPowerUpTask(this, pPowerupProgress); }
+ catch (std::bad_alloc &) { throw hrc = E_OUTOFMEMORY; }
+ if (!task->isOk())
+ throw task->hrc();
+
+ task->mpfnConfigConstructor = i_configConstructor;
+ task->mSharedFolders = sharedFolders;
+ task->mStartPaused = aPaused;
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ try { task->mSavedStateFile = strSavedStateFile; }
+ catch (std::bad_alloc &) { throw hrc = E_OUTOFMEMORY; }
+ task->mTeleporterEnabled = fTeleporterEnabled;
+
+ /* Reset differencing hard disks for which autoReset is true,
+ * but only if the machine has no snapshots OR the current snapshot
+ * is an OFFLINE snapshot; otherwise we would reset the current
+ * differencing image of an ONLINE snapshot which contains the disk
+ * state of the machine while it was previously running, but without
+ * the corresponding machine state, which is equivalent to powering
+ * off a running machine and not good idea
+ */
+ ComPtr<ISnapshot> pCurrentSnapshot;
+ hrc = mMachine->COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+
+ BOOL fCurrentSnapshotIsOnline = false;
+ if (pCurrentSnapshot)
+ {
+ hrc = pCurrentSnapshot->COMGETTER(Online)(&fCurrentSnapshotIsOnline);
+ if (FAILED(hrc))
+ throw hrc;
+ }
+
+ if (strSavedStateFile.isEmpty() && !fCurrentSnapshotIsOnline)
+ {
+ LogFlowThisFunc(("Looking for immutable images to reset\n"));
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = mMachine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts));
+ if (FAILED(hrc))
+ throw hrc;
+
+ for (size_t i = 0;
+ i < atts.size();
+ ++i)
+ {
+ DeviceType_T devType;
+ hrc = atts[i]->COMGETTER(Type)(&devType);
+ /** @todo later applies to floppies as well */
+ if (devType == DeviceType_HardDisk)
+ {
+ ComPtr<IMedium> pMedium;
+ hrc = atts[i]->COMGETTER(Medium)(pMedium.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* needs autoreset? */
+ BOOL autoReset = FALSE;
+ hrc = pMedium->COMGETTER(AutoReset)(&autoReset);
+ if (FAILED(hrc))
+ throw hrc;
+
+ if (autoReset)
+ {
+ ComPtr<IProgress> pResetProgress;
+ hrc = pMedium->Reset(pResetProgress.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* 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;
+ hrc = mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpEnabled").raw(), value.asOutParam());
+ if (SUCCEEDED(hrc) && value == "1")
+ {
+ Bstr coreDumpDir, coreDumpReplaceSys, coreDumpLive;
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpDir").raw(), coreDumpDir.asOutParam());
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpReplaceSystemDump").raw(), coreDumpReplaceSys.asOutParam());
+ mMachine->GetExtraData(Bstr("VBoxInternal2/CoreDumpLive").raw(), coreDumpLive.asOutParam());
+
+ uint32_t fCoreFlags = 0;
+ if ( coreDumpReplaceSys.isEmpty() == false
+ && Utf8Str(coreDumpReplaceSys).toUInt32() == 1)
+ fCoreFlags |= RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP;
+
+ if ( coreDumpLive.isEmpty() == false
+ && Utf8Str(coreDumpLive).toUInt32() == 1)
+ fCoreFlags |= RTCOREDUMPER_FLAGS_LIVE_CORE;
+
+ Utf8Str strDumpDir(coreDumpDir);
+ const char *pszDumpDir = strDumpDir.c_str();
+ if ( pszDumpDir
+ && *pszDumpDir == '\0')
+ pszDumpDir = NULL;
+
+ int vrc;
+ if ( pszDumpDir
+ && !RTDirExists(pszDumpDir))
+ {
+ /*
+ * Try create the directory.
+ */
+ vrc = RTDirCreateFullPath(pszDumpDir, 0700);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper. Couldn't create dump directory '%s' (%Rrc)"),
+ pszDumpDir, vrc);
+ }
+
+ vrc = RTCoreDumperSetup(pszDumpDir, fCoreFlags);
+ if (RT_FAILURE(vrc))
+ throw setErrorBoth(E_FAIL, vrc, tr("Failed to setup CoreDumper (%Rrc)"), vrc);
+ LogRel(("CoreDumper setup successful. pszDumpDir=%s fFlags=%#x\n", pszDumpDir ? pszDumpDir : ".", fCoreFlags));
+ }
+#endif
+
+
+ // If there is immutable drive the process that.
+ VMPowerUpTask::ProgressList progresses(task->hardDiskProgresses);
+ if (aProgress && !progresses.empty())
+ {
+ for (VMPowerUpTask::ProgressList::const_iterator it = progresses.begin(); it != progresses.end(); ++it)
+ {
+ ++cOperations;
+ ulTotalOperationsWeight += 1;
+ }
+ hrc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ TRUE, // Cancelable
+ cOperations,
+ ulTotalOperationsWeight,
+ tr("Starting Hard Disk operations"),
+ 1);
+ AssertComRCReturnRC(hrc);
+ }
+ else if ( mMachineState == MachineState_Saved
+ || mMachineState == MachineState_AbortedSaved
+ || !fTeleporterEnabled)
+ hrc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ FALSE /* aCancelable */);
+ else if (fTeleporterEnabled)
+ hrc = pPowerupProgress->init(static_cast<IConsole *>(this),
+ progressDesc.raw(),
+ TRUE /* aCancelable */,
+ 3 /* cOperations */,
+ 10 /* ulTotalOperationsWeight */,
+ tr("Teleporting virtual machine"),
+ 1 /* ulFirstOperationWeight */);
+
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* Tell VBoxSVC and Machine about the progress object so they can
+ combine/proxy it to any openRemoteSession caller. */
+ LogFlowThisFunc(("Calling BeginPowerUp...\n"));
+ hrc = mControl->BeginPowerUp(pPowerupProgress);
+ if (FAILED(hrc))
+ {
+ LogFlowThisFunc(("BeginPowerUp failed\n"));
+ throw hrc;
+ }
+ fBeganPoweringUp = true;
+
+ LogFlowThisFunc(("Checking if canceled...\n"));
+ BOOL fCanceled;
+ hrc = pPowerupProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc))
+ throw hrc;
+
+ 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)
+ {
+ hrc = pPowerupProgress.queryInterfaceTo(aProgress);
+ AssertComRCReturnRC(hrc);
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ task->mKeyStore = Utf8Str(bstrStateKeyStore);
+ task->mKeyId = Utf8Str(bstrStateKeyId);
+ task->m_pKeyStore = m_pKeyStore;
+#endif
+
+ hrc = task->createThread();
+ task = NULL;
+ if (FAILED(hrc))
+ throw hrc;
+
+ /* finally, set the state: no right to fail in this method afterwards
+ * since we've already started the thread and it is now responsible for
+ * any error reporting and appropriate state change! */
+ if (mMachineState == MachineState_Saved || mMachineState == MachineState_AbortedSaved)
+ i_setMachineState(MachineState_Restoring);
+ else if (fTeleporterEnabled)
+ i_setMachineState(MachineState_TeleportingIn);
+ else
+ i_setMachineState(MachineState_Starting);
+ }
+ catch (HRESULT aRC)
+ {
+ hrc = aRC;
+ }
+
+ if (FAILED(hrc) && fBeganPoweringUp)
+ {
+
+ /* The progress object will fetch the current error info */
+ if (!pPowerupProgress.isNull())
+ pPowerupProgress->i_notifyComplete(hrc);
+
+ /* 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(hrc);
+ }
+
+ if (task)
+ {
+ ErrorInfoKeeper eik;
+ delete task;
+ }
+
+ LogFlowThisFunc(("mMachineState=%d, hrc=%Rhrc\n", mMachineState, hrc));
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * 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.hrc());
+
+ 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 hrc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* sanity */
+ Assert(mVMDestroying == false);
+
+ PCVMMR3VTABLE const pVMM = mpVMM;
+ AssertPtrReturn(pVMM, E_UNEXPECTED);
+ PUVM pUVM = mpUVM;
+ AssertPtrReturn(pUVM, E_UNEXPECTED);
+
+ uint32_t cRefs = pVMM->pfnVMR3RetainUVM(pUVM);
+ Assert(cRefs != UINT32_MAX); NOREF(cRefs);
+
+ AssertMsg( mMachineState == MachineState_Running
+ || mMachineState == MachineState_Paused
+ || mMachineState == MachineState_Stuck
+ || mMachineState == MachineState_Starting
+ || mMachineState == MachineState_Stopping
+ || mMachineState == MachineState_Saving
+ || mMachineState == MachineState_Restoring
+ || mMachineState == MachineState_TeleportingPausedVM
+ || mMachineState == MachineState_TeleportingIn
+ , ("Invalid machine state: %s\n", ::stringifyMachineState(mMachineState)));
+
+ LogRel(("Console::powerDown(): A request to power off the VM has been issued (mMachineState=%s, InUninit=%d)\n",
+ ::stringifyMachineState(mMachineState), getObjectState().getState() == ObjectState::InUninit));
+
+ /* Check if we need to power off the VM. In case of mVMPoweredOff=true, the
+ * VM has already powered itself off in vmstateChangeCallback() and is just
+ * notifying Console about that. In case of Starting or Restoring,
+ * powerUpThread() is calling us on failure, so the VM is already off at
+ * that point. */
+ if ( !mVMPoweredOff
+ && ( mMachineState == MachineState_Starting
+ || mMachineState == MachineState_Restoring
+ || mMachineState == MachineState_TeleportingIn)
+ )
+ mVMPoweredOff = true;
+
+ /*
+ * Go to Stopping state if not already there.
+ *
+ * Note that we don't go from Saving/Restoring to Stopping because
+ * vmstateChangeCallback() needs it to set the state to Saved on
+ * VMSTATE_TERMINATED. In terms of protecting from inappropriate operations
+ * while leaving the lock below, Saving or Restoring should be fine too.
+ * Ditto for TeleportingPausedVM -> Teleported.
+ */
+ if ( mMachineState != MachineState_Saving
+ && mMachineState != MachineState_Restoring
+ && mMachineState != MachineState_Stopping
+ && mMachineState != MachineState_TeleportingIn
+ && mMachineState != MachineState_TeleportingPausedVM
+ )
+ i_setMachineState(MachineState_Stopping);
+
+ /* ----------------------------------------------------------------------
+ * DONE with necessary state changes, perform the power down actions (it's
+ * safe to release the object lock now if needed)
+ * ---------------------------------------------------------------------- */
+
+ if (mDisplay)
+ {
+ alock.release();
+
+ mDisplay->i_notifyPowerDown();
+
+ alock.acquire();
+ }
+
+ /* Stop the VRDP server to prevent new clients connection while VM is being
+ * powered off. */
+ if (mConsoleVRDPServer)
+ {
+ LogFlowThisFunc(("Stopping VRDP server...\n"));
+
+ /* Leave the lock since EMT could call us back as addVMCaller() */
+ alock.release();
+
+ mConsoleVRDPServer->Stop();
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+
+ /* ----------------------------------------------------------------------
+ * Now, wait for all mpUVM callers to finish their work if there are still
+ * some on other threads. NO methods that need mpUVM (or initiate other calls
+ * that need it) may be called after this point
+ * ---------------------------------------------------------------------- */
+
+ /* go to the destroying state to prevent from adding new callers */
+ mVMDestroying = true;
+
+ if (mVMCallers > 0)
+ {
+ /* lazy creation */
+ if (mVMZeroCallersSem == NIL_RTSEMEVENT)
+ RTSemEventCreate(&mVMZeroCallersSem);
+
+ LogFlowThisFunc(("Waiting for mpUVM callers (%d) to drop to zero...\n", mVMCallers));
+
+ alock.release();
+
+ RTSemEventWait(mVMZeroCallersSem, RT_INDEFINITE_WAIT);
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+ vrc = VINF_SUCCESS;
+
+ /*
+ * Power off the VM if not already done that.
+ * Leave the lock since EMT will call vmstateChangeCallback.
+ *
+ * Note that VMR3PowerOff() may fail here (invalid VMSTATE) if the
+ * VM-(guest-)initiated power off happened in parallel a ms before this
+ * call. So far, we let this error pop up on the user's side.
+ */
+ if (!mVMPoweredOff)
+ {
+ LogFlowThisFunc(("Powering off the VM...\n"));
+ alock.release();
+ vrc = pVMM->pfnVMR3PowerOff(pUVM);
+#ifdef VBOX_WITH_EXTPACK
+ mptrExtPackManager->i_callAllVmPowerOffHooks(this, pVMM->pfnVMR3GetVM(pUVM), pVMM);
+#endif
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+#ifdef VBOX_WITH_HGCM
+ /* Shutdown HGCM services before destroying the VM. */
+ if (m_pVMMDev)
+ {
+ LogFlowThisFunc(("Shutdown HGCM...\n"));
+
+ /* Leave the lock since EMT might wait for it and will call us back as addVMCaller() */
+ alock.release();
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /** @todo Deregister area callbacks? */
+# endif
+# ifdef VBOX_WITH_DRAG_AND_DROP
+ if (m_hHgcmSvcExtDragAndDrop)
+ {
+ HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtDragAndDrop);
+ m_hHgcmSvcExtDragAndDrop = NULL;
+ }
+# endif
+
+ m_pVMMDev->hgcmShutdown();
+
+ alock.acquire();
+ }
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+#endif /* VBOX_WITH_HGCM */
+
+ LogFlowThisFunc(("Ready for VM destruction.\n"));
+
+ /* If we are called from Console::uninit(), then try to destroy the VM even
+ * on failure (this will most likely fail too, but what to do?..) */
+ if (RT_SUCCESS(vrc) || getObjectState().getState() == ObjectState::InUninit)
+ {
+ /* If the machine has a USB controller, release all USB devices
+ * (symmetric to the code in captureUSBDevices()) */
+ if (mfVMHasUsbController)
+ {
+ alock.release();
+ i_detachAllUSBDevices(false /* aDone */);
+ alock.acquire();
+ }
+
+ /* Now we've got to destroy the VM as well. (mpUVM is not valid beyond
+ * this point). We release the lock before calling VMR3Destroy() because
+ * it will result into calling destructors of drivers associated with
+ * Console children which may in turn try to lock Console (e.g. by
+ * instantiating SafeVMPtr to access mpUVM). It's safe here because
+ * mVMDestroying is set which should prevent any activity. */
+
+ /* Set mpUVM to NULL early just in case if some old code is not using
+ * addVMCaller()/releaseVMCaller(). (We have our own ref on pUVM.) */
+ pVMM->pfnVMR3ReleaseUVM(mpUVM);
+ mpUVM = NULL;
+
+ LogFlowThisFunc(("Destroying the VM...\n"));
+
+ alock.release();
+
+ vrc = pVMM->pfnVMR3Destroy(pUVM);
+
+ /* take the lock again */
+ alock.acquire();
+
+ /* advance percent count */
+ if (pProgressControl)
+ pProgressControl->SetCurrentOperationProgress(99 * (++step) / StepCount);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Machine has been destroyed (mMachineState=%d)\n",
+ mMachineState));
+ /* Note: the Console-level machine state change happens on the
+ * VMSTATE_TERMINATE state change in vmstateChangeCallback(). If
+ * powerDown() is called from EMT (i.e. from vmstateChangeCallback()
+ * on receiving VM-initiated VMSTATE_OFF), VMSTATE_TERMINATE hasn't
+ * occurred yet. This is okay, because mMachineState is already
+ * Stopping in this case, so any other attempt to call PowerDown()
+ * will be rejected. */
+ }
+ else
+ {
+ /* bad bad bad, but what to do? (Give Console our UVM ref.) */
+ mpUVM = pUVM;
+ pUVM = NULL;
+ hrc = 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
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not power off the machine. (Error: %Rrc)"), vrc);
+
+ /*
+ * Finished with the destruction.
+ *
+ * Note that if something impossible happened and we've failed to destroy
+ * the VM, mVMDestroying will remain true and mMachineState will be
+ * something like Stopping, so most Console methods will return an error
+ * to the caller.
+ */
+ if (pUVM != NULL)
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ else
+ mVMDestroying = false;
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_setMachineState(MachineState_T aMachineState, bool aUpdateServer /* = true */)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ if (mMachineState != aMachineState)
+ {
+ LogThisFunc(("machineState=%s -> %s aUpdateServer=%RTbool\n",
+ ::stringifyMachineState(mMachineState), ::stringifyMachineState(aMachineState), aUpdateServer));
+ LogRel(("Console: Machine state changed to '%s'\n", ::stringifyMachineState(aMachineState)));
+ mMachineState = aMachineState;
+
+ /// @todo (dmik)
+ // possibly, we need to redo onStateChange() using the dedicated
+ // Event thread, like it is done in VirtualBox. This will make it
+ // much safer (no deadlocks possible if someone tries to use the
+ // console from the callback), however, listeners will lose the
+ // ability to synchronously react to state changes (is it really
+ // necessary??)
+ LogFlowThisFunc(("Doing onStateChange()...\n"));
+ i_onStateChange(aMachineState);
+ LogFlowThisFunc(("Done onStateChange()\n"));
+
+ if (aUpdateServer)
+ {
+ /* Server notification MUST be done from under the lock; otherwise
+ * the machine state here and on the server might go out of sync
+ * which can lead to various unexpected results (like the machine
+ * state being >= MachineState_Running on the server, while the
+ * session state is already SessionState_Unlocked at the same time
+ * there).
+ *
+ * Cross-lock conditions should be carefully watched out: calling
+ * UpdateState we will require Machine and SessionMachine locks
+ * (remember that here we're holding the Console lock here, and also
+ * all locks that have been acquire by the thread before calling
+ * this method).
+ */
+ LogFlowThisFunc(("Doing mControl->UpdateState()...\n"));
+ hrc = mControl->UpdateState(aMachineState);
+ LogFlowThisFunc(("mControl->UpdateState()=%Rhrc\n", hrc));
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * 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<ConsoleSharedFolder> &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 hrc = 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;
+ hrc = mMachine->COMGETTER(SharedFolders)(ComSafeArrayAsOutParam(folders));
+ if (FAILED(hrc)) throw hrc;
+
+ for (size_t i = 0; i < folders.size(); ++i)
+ {
+ ComPtr<ISharedFolder> pSharedFolder = folders[i];
+
+ Bstr bstr;
+ hrc = pSharedFolder->COMGETTER(Name)(bstr.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+ Utf8Str strName(bstr);
+
+ hrc = pSharedFolder->COMGETTER(HostPath)(bstr.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+ Utf8Str strHostPath(bstr);
+
+ BOOL writable;
+ hrc = pSharedFolder->COMGETTER(Writable)(&writable);
+ if (FAILED(hrc)) throw hrc;
+
+ BOOL autoMount;
+ hrc = pSharedFolder->COMGETTER(AutoMount)(&autoMount);
+ if (FAILED(hrc)) throw hrc;
+
+ hrc = pSharedFolder->COMGETTER(AutoMountPoint)(bstr.asOutParam());
+ if (FAILED(hrc)) throw hrc;
+ 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()
+ )
+ {
+ hrc = i_removeSharedFolder(strName);
+ if (FAILED(hrc)) throw hrc;
+ }
+
+ /* create the new machine folder */
+ hrc = i_createSharedFolder(strName,
+ SharedFolderData(strHostPath, !!writable, !!autoMount, strAutoMountPoint));
+ if (FAILED(hrc)) throw hrc;
+ }
+ }
+ /* 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 */
+ hrc = i_removeSharedFolder(it->first);
+ if (FAILED(hrc)) throw hrc;
+
+ /* create the global folder if there is any */
+ SharedFolderDataMap::const_iterator git =
+ m_mapGlobalSharedFolders.find(it->first);
+ if (git != m_mapGlobalSharedFolders.end())
+ {
+ hrc = i_createSharedFolder(git->first, git->second);
+ if (FAILED(hrc)) throw hrc;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (HRESULT hrc2)
+ {
+ hrc = hrc2;
+ if (online)
+ i_atVMRuntimeErrorCallbackF(0, "BrokenSharedFolder", N_("Broken shared folder!"));
+ }
+
+ LogFlowThisFunc(("Leaving\n"));
+
+ return hrc;
+}
+
+/**
+ * Searches for a shared folder with the given name in the list of machine
+ * shared folders and then in the list of the global shared folders.
+ *
+ * @param strName Name of the folder to search for.
+ * @param aIt Where to store the pointer to the found folder.
+ * @return @c true if the folder was found and @c false otherwise.
+ *
+ * @note The caller must lock this object for reading.
+ */
+bool Console::i_findOtherSharedFolder(const Utf8Str &strName,
+ SharedFolderDataMap::const_iterator &aIt)
+{
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), false);
+
+ /* first, search machine folders */
+ aIt = m_mapMachineSharedFolders.find(strName);
+ if (aIt != m_mapMachineSharedFolders.end())
+ return true;
+
+ /* second, search machine folders */
+ aIt = m_mapGlobalSharedFolders.find(strName);
+ if (aIt != m_mapGlobalSharedFolders.end())
+ return true;
+
+ return false;
+}
+
+/**
+ * Calls the HGCM service to add a shared folder definition.
+ *
+ * @param strName Shared folder name.
+ * @param aData Shared folder data.
+ *
+ * @note Must be called from under AutoVMCaller and when mpUVM != NULL!
+ * @note Doesn't lock anything.
+ */
+HRESULT Console::i_createSharedFolder(const Utf8Str &strName, const SharedFolderData &aData)
+{
+ Log(("Adding shared folder '%s' -> '%s'\n", strName.c_str(), aData.m_strHostPath.c_str()));
+
+ /*
+ * Sanity checks
+ */
+ ComAssertRet(strName.isNotEmpty(), E_FAIL);
+ ComAssertRet(aData.m_strHostPath.isNotEmpty(), E_FAIL);
+
+ AssertReturn(mpUVM, E_FAIL);
+ AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL);
+
+ /*
+ * Find out whether we should allow symbolic link creation.
+ */
+ Bstr bstrValue;
+ HRESULT hrc = mMachine->GetExtraData(BstrFmt("VBoxInternal2/SharedFoldersEnableSymlinksCreate/%s", strName.c_str()).raw(),
+ bstrValue.asOutParam());
+ bool fSymlinksCreate = hrc == S_OK && bstrValue == "1";
+
+ /*
+ * Check whether the path is valid and exists.
+ */
+ char szAbsHostPath[RTPATH_MAX];
+ int vrc = RTPathAbs(aData.m_strHostPath.c_str(), szAbsHostPath, sizeof(szAbsHostPath));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), aData.m_strHostPath.c_str(), vrc);
+
+ /* Check whether the path is full (absolute). ASSUMING a RTPATH_MAX of ~4K
+ this also checks that the length is within bounds of a SHFLSTRING. */
+ if (RTPathCompare(aData.m_strHostPath.c_str(), szAbsHostPath) != 0)
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), aData.m_strHostPath.c_str());
+
+ bool const fMissing = !RTPathExists(szAbsHostPath);
+
+ /*
+ * Check the other two string lengths before converting them all to SHFLSTRINGS.
+ */
+ if (strName.length() >= _2K)
+ return setError(E_INVALIDARG, tr("Shared folder name is too long: %zu bytes", "", strName.length()), strName.length());
+ if (aData.m_strAutoMountPoint.length() >= RTPATH_MAX)
+ return setError(E_INVALIDARG, tr("Shared folder mount point too long: %zu bytes", "",
+ (int)aData.m_strAutoMountPoint.length()),
+ aData.m_strAutoMountPoint.length());
+
+ PSHFLSTRING pHostPath = ShflStringDupUtf8AsUtf16(aData.m_strHostPath.c_str());
+ PSHFLSTRING pName = ShflStringDupUtf8AsUtf16(strName.c_str());
+ PSHFLSTRING pAutoMountPoint = ShflStringDupUtf8AsUtf16(aData.m_strAutoMountPoint.c_str());
+ if (pHostPath && pName && pAutoMountPoint)
+ {
+ /*
+ * Make a SHFL_FN_ADD_MAPPING call to tell the service about folder.
+ */
+ VBOXHGCMSVCPARM aParams[SHFL_CPARMS_ADD_MAPPING];
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[0], pHostPath);
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[1], pName);
+ HGCMSvcSetU32(&aParams[2],
+ (aData.m_fWritable ? SHFL_ADD_MAPPING_F_WRITABLE : 0)
+ | (aData.m_fAutoMount ? SHFL_ADD_MAPPING_F_AUTOMOUNT : 0)
+ | (fSymlinksCreate ? SHFL_ADD_MAPPING_F_CREATE_SYMLINKS : 0)
+ | (fMissing ? SHFL_ADD_MAPPING_F_MISSING : 0));
+ SHFLSTRING_TO_HGMC_PARAM(&aParams[3], pAutoMountPoint);
+ AssertCompile(SHFL_CPARMS_ADD_MAPPING == 4);
+
+ vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_ADD_MAPPING, SHFL_CPARMS_ADD_MAPPING, aParams);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Could not create a shared folder '%s' mapped to '%s' (%Rrc)"),
+ strName.c_str(), aData.m_strHostPath.c_str(), vrc);
+
+ else if (fMissing)
+ hrc = setError(E_INVALIDARG, tr("Shared folder path '%s' does not exist on the host"), aData.m_strHostPath.c_str());
+ else
+ hrc = S_OK;
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ RTMemFree(pAutoMountPoint);
+ RTMemFree(pName);
+ RTMemFree(pHostPath);
+ return hrc;
+}
+
+/**
+ * Calls the HGCM service to remove the shared folder definition.
+ *
+ * @param strName Shared folder name.
+ *
+ * @note Must be called from under AutoVMCaller and when mpUVM != NULL!
+ * @note Doesn't lock anything.
+ */
+HRESULT Console::i_removeSharedFolder(const Utf8Str &strName)
+{
+ ComAssertRet(strName.isNotEmpty(), E_FAIL);
+
+ /* sanity checks */
+ AssertReturn(mpUVM, E_FAIL);
+ AssertReturn(m_pVMMDev && m_pVMMDev->isShFlActive(), E_FAIL);
+
+ VBOXHGCMSVCPARM parms;
+ SHFLSTRING *pMapName;
+ size_t cbString;
+
+ Log(("Removing shared folder '%s'\n", strName.c_str()));
+
+ Bstr bstrName(strName);
+ cbString = (bstrName.length() + 1) * sizeof(RTUTF16);
+ if (cbString >= UINT16_MAX)
+ return setError(E_INVALIDARG, tr("The name is too long"));
+ pMapName = (SHFLSTRING *) RTMemAllocZ(SHFLSTRING_HEADER_SIZE + cbString);
+ Assert(pMapName);
+ memcpy(pMapName->String.ucs2, bstrName.raw(), cbString);
+
+ pMapName->u16Size = (uint16_t)cbString;
+ pMapName->u16Length = (uint16_t)(cbString - sizeof(RTUTF16));
+
+ parms.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.u.pointer.addr = pMapName;
+ parms.u.pointer.size = ShflStringSizeOfBuffer(pMapName);
+
+ int vrc = m_pVMMDev->hgcmHostCall("VBoxSharedFolders", SHFL_FN_REMOVE_MAPPING, 1, &parms);
+ RTMemFree(pMapName);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Could not remove the shared folder '%s' (%Rrc)"), strName.c_str(), vrc);
+
+ return S_OK;
+}
+
+/**
+ * Retains a reference to the default cryptographic interface.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if the VM is not configured for encryption.
+ * @param ppCryptoIf Where to store the pointer to the cryptographic interface on success.
+ *
+ * @note Locks this object for writing.
+ */
+int Console::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf)
+{
+ AssertReturn(ppCryptoIf != NULL, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+ if (mhLdrModCrypto == NIL_RTLDRMOD)
+ {
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Check that a crypto extension pack name is set and resolve it into a
+ * library path.
+ */
+ HRESULT hrc = S_OK;
+ Bstr bstrExtPack;
+
+ ComPtr<IVirtualBox> pVirtualBox;
+ mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ ComPtr<ISystemProperties> pSystemProperties;
+ if (pVirtualBox)
+ pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMGETTER(DefaultCryptoExtPack)(bstrExtPack.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ Utf8Str strExtPack(bstrExtPack);
+ if (strExtPack.isEmpty())
+ {
+ setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Ńo extension pack providing a cryptographic support module could be found"));
+ return VERR_NOT_FOUND;
+ }
+
+ Utf8Str strCryptoLibrary;
+ vrc = mptrExtPackManager->i_getCryptoLibraryPathForExtPack(&strExtPack, &strCryptoLibrary);
+ if (RT_SUCCESS(vrc))
+ {
+ RTERRINFOSTATIC ErrInfo;
+ vrc = SUPR3HardenedLdrLoadPlugIn(strCryptoLibrary.c_str(), &mhLdrModCrypto, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ /* Resolve the entry point and query the pointer to the cryptographic interface. */
+ PFNVBOXCRYPTOENTRY pfnCryptoEntry = NULL;
+ vrc = RTLdrGetSymbol(mhLdrModCrypto, VBOX_CRYPTO_MOD_ENTRY_POINT, (void **)&pfnCryptoEntry);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pfnCryptoEntry(&mpCryptoIf);
+ if (RT_FAILURE(vrc))
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to query the interface callback table from the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Failed to resolve the entry point for the cryptographic support module '%s' from extension pack '%s'"),
+ strCryptoLibrary.c_str(), strExtPack.c_str());
+
+ if (RT_FAILURE(vrc))
+ {
+ RTLdrClose(mhLdrModCrypto);
+ mhLdrModCrypto = NIL_RTLDRMOD;
+ }
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't load the cryptographic support module '%s' from extension pack '%s' (error: '%s')"),
+ strCryptoLibrary.c_str(), strExtPack.c_str(), ErrInfo.Core.pszMsg);
+ }
+ else
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Couldn't resolve the library path of the crpytographic support module for extension pack '%s'"),
+ strExtPack.c_str());
+#else
+ setError(VBOX_E_NOT_SUPPORTED,
+ tr("The cryptographic support module is not supported in this build because extension packs are not supported"));
+ vrc = VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ ASMAtomicIncU32(&mcRefsCrypto);
+ *ppCryptoIf = mpCryptoIf;
+ }
+
+ return vrc;
+}
+
+/**
+ * Releases the reference of the given cryptographic interface.
+ *
+ * @returns VBox status code.
+ * @param pCryptoIf Pointer to the cryptographic interface to release.
+ *
+ * @note Locks this object for writing.
+ */
+int Console::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf)
+{
+ AssertReturn(pCryptoIf == mpCryptoIf, VERR_INVALID_PARAMETER);
+
+ ASMAtomicDecU32(&mcRefsCrypto);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Tries to unload any loaded cryptographic support module if it is not in use currently.
+ *
+ * @returns COM status code.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT Console::i_unloadCryptoIfModule(void)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mcRefsCrypto)
+ return setError(E_ACCESSDENIED,
+ tr("The cryptographic support module is in use and can't be unloaded"));
+
+ if (mhLdrModCrypto != NIL_RTLDRMOD)
+ {
+ int vrc = RTLdrClose(mhLdrModCrypto);
+ AssertRC(vrc);
+ mhLdrModCrypto = NIL_RTLDRMOD;
+ }
+
+ return S_OK;
+}
+
+/** @callback_method_impl{FNVMATSTATE}
+ *
+ * @note Locks the Console object for writing.
+ * @remarks The @a pUVM parameter can be NULL in one case where powerUpThread()
+ * calls after the VM was destroyed.
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmstateChangeCallback(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState, VMSTATE enmOldState, void *pvUser)
+{
+ LogFlowFunc(("Changing state from %s to %s (pUVM=%p)\n",
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState), pUVM));
+ RT_NOREF(pVMM);
+
+ Console *that = static_cast<Console *>(pvUser);
+ AssertReturnVoid(that);
+
+ AutoCaller autoCaller(that);
+
+ /* Note that we must let this method proceed even if Console::uninit() has
+ * been already called. In such case this VMSTATE change is a result of:
+ * 1) powerDown() called from uninit() itself, or
+ * 2) VM-(guest-)initiated power off. */
+ AssertReturnVoid( autoCaller.isOk()
+ || that->getObjectState().getState() == ObjectState::InUninit);
+
+ switch (enmState)
+ {
+ /*
+ * The VM has terminated
+ */
+ case VMSTATE_OFF:
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (that->mfTurnResetIntoPowerOff)
+ {
+ Bstr strPowerOffReason;
+
+ if (that->mfPowerOffCausedByReset)
+ strPowerOffReason = Bstr("Reset");
+ else
+ strPowerOffReason = Bstr("PowerOff");
+
+ that->mMachine->DeleteGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw());
+ that->mMachine->SetGuestProperty(Bstr("/VirtualBox/HostInfo/VMPowerOffReason").raw(),
+ strPowerOffReason.raw(), Bstr("RDONLYGUEST").raw());
+ that->mMachine->SaveSettings();
+ }
+#endif
+
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ return;
+
+ /* Do we still think that it is running? It may happen if this is a
+ * VM-(guest-)initiated shutdown/poweroff.
+ */
+ if ( that->mMachineState != MachineState_Stopping
+ && that->mMachineState != MachineState_Saving
+ && that->mMachineState != MachineState_Restoring
+ && that->mMachineState != MachineState_TeleportingIn
+ && that->mMachineState != MachineState_TeleportingPausedVM
+ && !that->mVMIsAlreadyPoweringOff
+ )
+ {
+ LogFlowFunc(("VM has powered itself off but Console still thinks it is running. Notifying.\n"));
+
+ /*
+ * Prevent powerDown() from calling VMR3PowerOff() again if this was called from
+ * the power off state change.
+ * When called from the Reset state make sure to call VMR3PowerOff() first.
+ */
+ Assert(that->mVMPoweredOff == false);
+ that->mVMPoweredOff = true;
+
+ /*
+ * request a progress object from the server
+ * (this will set the machine state to Stopping on the server
+ * to block others from accessing this machine)
+ */
+ ComPtr<IProgress> pProgress;
+ HRESULT hrc = that->mControl->BeginPoweringDown(pProgress.asOutParam());
+ AssertComRC(hrc);
+
+ /* sync the state with the server */
+ that->i_setMachineStateLocally(MachineState_Stopping);
+
+ /*
+ * Setup task object and thread to carry out the operation
+ * asynchronously (if we call powerDown() right here but there
+ * is one or more mpUVM callers (added with addVMCaller()) we'll
+ * deadlock).
+ */
+ VMPowerDownTask *pTask = NULL;
+ try
+ {
+ pTask = new VMPowerDownTask(that, pProgress);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRelFunc(("E_OUTOFMEMORY creating VMPowerDownTask"));
+ hrc = E_OUTOFMEMORY;
+ break;
+ }
+
+ /*
+ * If creating a task failed, this can currently mean one of
+ * two: either Console::uninit() has been called just a ms
+ * before (so a powerDown() call is already on the way), or
+ * powerDown() itself is being already executed. Just do
+ * nothing.
+ */
+ if (pTask->isOk())
+ {
+ hrc = pTask->createThread();
+ pTask = NULL;
+ if (FAILED(hrc))
+ LogRelFunc(("Problem with creating thread for VMPowerDownTask.\n"));
+ }
+ else
+ {
+ LogFlowFunc(("Console is already being uninitialized. (%Rhrc)\n", pTask->hrc()));
+ delete pTask;
+ pTask = NULL;
+ hrc = E_FAIL;
+ }
+ }
+ break;
+ }
+
+ /* The VM has been completely destroyed.
+ *
+ * Note: This state change can happen at two points:
+ * 1) At the end of VMR3Destroy() if it was not called from EMT.
+ * 2) At the end of vmR3EmulationThread if VMR3Destroy() was
+ * called by EMT.
+ */
+ case VMSTATE_TERMINATED:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+#ifdef VBOX_WITH_CLOUD_NET
+ /*
+ * We stop cloud gateway here because we may have failed to connect to it,
+ * configure it, or establish a tunnel. We definitely do not want an orphaned
+ * instance running in the cloud.
+ */
+ if (!that->mGateway.mGatewayInstanceId.isEmpty())
+ {
+ ComPtr<IVirtualBox> pVirtualBox;
+ HRESULT hrc = that->mMachine->COMGETTER(Parent)(pVirtualBox.asOutParam());
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc) && !pVirtualBox.isNull())
+ stopCloudGateway(pVirtualBox, that->mGateway);
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+ /* Terminate host interface networking. If pUVM is NULL, we've been
+ * manually called from powerUpThread() either before calling
+ * VMR3Create() or after VMR3Create() failed, so no need to touch
+ * networking.
+ */
+ if (pUVM)
+ that->i_powerDownHostInterfaces();
+
+ /* From now on the machine is officially powered down or remains in
+ * the Saved state.
+ */
+ switch (that->mMachineState)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case MachineState_Stopping:
+ /* successfully powered down */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_Saving:
+ /* successfully saved */
+ that->i_setMachineState(MachineState_Saved);
+ break;
+ case MachineState_Starting:
+ /* failed to start, but be patient: set back to PoweredOff
+ * (for similarity with the below) */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_Restoring:
+ /* failed to load the saved state file, but be patient: set
+ * to AbortedSaved (to preserve the saved state file) */
+ that->i_setMachineState(MachineState_AbortedSaved);
+ break;
+ case MachineState_TeleportingIn:
+ /* Teleportation failed or was canceled. Back to powered off. */
+ that->i_setMachineState(MachineState_PoweredOff);
+ break;
+ case MachineState_TeleportingPausedVM:
+ /* Successfully teleported the VM. */
+ that->i_setMachineState(MachineState_Teleported);
+ break;
+ }
+ break;
+ }
+
+ case VMSTATE_RESETTING:
+ /** @todo shouldn't VMSTATE_RESETTING_LS be here? */
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ /* Do not take any read/write locks here! */
+ that->i_guestPropertiesHandleVMReset();
+#endif
+ break;
+ }
+
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ /* Shouldn't do anything here! */
+ break;
+
+ case VMSTATE_SUSPENDED:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ switch (that->mMachineState)
+ {
+ case MachineState_Teleporting:
+ that->i_setMachineState(MachineState_TeleportingPausedVM);
+ break;
+
+ case MachineState_LiveSnapshotting:
+ that->i_setMachineState(MachineState_OnlineSnapshotting);
+ break;
+
+ case MachineState_TeleportingPausedVM:
+ case MachineState_Saving:
+ case MachineState_Restoring:
+ case MachineState_Stopping:
+ case MachineState_TeleportingIn:
+ case MachineState_OnlineSnapshotting:
+ /* The worker thread handles the transition. */
+ break;
+
+ case MachineState_Running:
+ that->i_setMachineState(MachineState_Paused);
+ break;
+
+ case MachineState_Paused:
+ /* Nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%s\n", ::stringifyMachineState(that->mMachineState)));
+ }
+ break;
+ }
+
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDED_EXT_LS:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+ switch (that->mMachineState)
+ {
+ case MachineState_Teleporting:
+ that->i_setMachineState(MachineState_TeleportingPausedVM);
+ break;
+
+ case MachineState_LiveSnapshotting:
+ that->i_setMachineState(MachineState_OnlineSnapshotting);
+ break;
+
+ case MachineState_TeleportingPausedVM:
+ case MachineState_Saving:
+ /* ignore */
+ break;
+
+ default:
+ AssertMsgFailed(("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState),
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) ));
+ that->i_setMachineState(MachineState_Paused);
+ break;
+ }
+ break;
+ }
+
+ case VMSTATE_RUNNING:
+ {
+ if ( enmOldState == VMSTATE_POWERING_ON
+ || enmOldState == VMSTATE_RESUMING)
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ Assert( ( ( that->mMachineState == MachineState_Starting
+ || that->mMachineState == MachineState_Paused)
+ && enmOldState == VMSTATE_POWERING_ON)
+ || ( ( that->mMachineState == MachineState_Restoring
+ || that->mMachineState == MachineState_TeleportingIn
+ || that->mMachineState == MachineState_Paused
+ || that->mMachineState == MachineState_Saving
+ )
+ && enmOldState == VMSTATE_RESUMING));
+
+ that->i_setMachineState(MachineState_Running);
+ }
+
+ break;
+ }
+
+ case VMSTATE_RUNNING_LS:
+ AssertMsg( that->mMachineState == MachineState_LiveSnapshotting
+ || that->mMachineState == MachineState_Teleporting,
+ ("%s/%s -> %s\n", ::stringifyMachineState(that->mMachineState),
+ pVMM->pfnVMR3GetStateName(enmOldState), pVMM->pfnVMR3GetStateName(enmState) ));
+ break;
+
+ case VMSTATE_FATAL_ERROR:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ /* Fatal errors are only for running VMs. */
+ Assert(Global::IsOnline(that->mMachineState));
+
+ /* Note! 'Pause' is used here in want of something better. There
+ * are currently only two places where fatal errors might be
+ * raised, so it is not worth adding a new externally
+ * visible state for this yet. */
+ that->i_setMachineState(MachineState_Paused);
+ break;
+ }
+
+ case VMSTATE_GURU_MEDITATION:
+ {
+ AutoWriteLock alock(that COMMA_LOCKVAL_SRC_POS);
+
+ if (that->mVMStateChangeCallbackDisabled)
+ break;
+
+ /* Guru are only for running VMs */
+ Assert(Global::IsOnline(that->mMachineState));
+
+ that->i_setMachineState(MachineState_Stuck);
+ break;
+ }
+
+ case VMSTATE_CREATED:
+ {
+ /*
+ * We have to set the secret key helper interface for the VD drivers to
+ * get notified about missing keys.
+ */
+ that->i_initSecretKeyIfOnAllAttachments();
+ break;
+ }
+
+ default: /* shut up gcc */
+ break;
+ }
+}
+
+/**
+ * Changes the clipboard mode.
+ *
+ * @returns VBox status code.
+ * @param aClipboardMode new clipboard mode.
+ */
+int Console::i_changeClipboardMode(ClipboardMode_T aClipboardMode)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+
+ switch (aClipboardMode)
+ {
+ default:
+ case ClipboardMode_Disabled:
+ LogRel(("Shared Clipboard: Mode: Off\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_OFF;
+ break;
+ case ClipboardMode_GuestToHost:
+ LogRel(("Shared Clipboard: Mode: Guest to Host\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_GUEST_TO_HOST;
+ break;
+ case ClipboardMode_HostToGuest:
+ LogRel(("Shared Clipboard: Mode: Host to Guest\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_HOST_TO_GUEST;
+ break;
+ case ClipboardMode_Bidirectional:
+ LogRel(("Shared Clipboard: Mode: Bidirectional\n"));
+ parm.u.uint32 = VBOX_SHCL_MODE_BIDIRECTIONAL;
+ break;
+ }
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_MODE, 1, &parm);
+ if (RT_FAILURE(vrc))
+ LogRel(("Shared Clipboard: Error changing mode: %Rrc\n", vrc));
+
+ return vrc;
+#else
+ RT_NOREF(aClipboardMode);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Changes the clipboard file transfer mode.
+ *
+ * @returns VBox status code.
+ * @param aEnabled Whether clipboard file transfers are enabled or not.
+ */
+int Console::i_changeClipboardFileTransferMode(bool aEnabled)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ RT_ZERO(parm);
+
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+ parm.u.uint32 = aEnabled ? VBOX_SHCL_TRANSFER_MODE_ENABLED : VBOX_SHCL_TRANSFER_MODE_DISABLED;
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1 /* cParms */, &parm);
+ if (RT_FAILURE(vrc))
+ LogRel(("Shared Clipboard: Error changing file transfer mode: %Rrc\n", vrc));
+
+ return vrc;
+#else
+ RT_NOREF(aEnabled);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+/**
+ * Changes the drag and drop mode.
+ *
+ * @param aDnDMode new drag and drop mode.
+ */
+int Console::i_changeDnDMode(DnDMode_T aDnDMode)
+{
+ VMMDev *pVMMDev = m_pVMMDev;
+ AssertPtrReturn(pVMMDev, VERR_INVALID_POINTER);
+
+ VBOXHGCMSVCPARM parm;
+ RT_ZERO(parm);
+ parm.type = VBOX_HGCM_SVC_PARM_32BIT;
+
+ switch (aDnDMode)
+ {
+ default:
+ case DnDMode_Disabled:
+ LogRel(("Drag and drop mode: Off\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_OFF;
+ break;
+ case DnDMode_GuestToHost:
+ LogRel(("Drag and drop mode: Guest to Host\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST;
+ break;
+ case DnDMode_HostToGuest:
+ LogRel(("Drag and drop mode: Host to Guest\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST;
+ break;
+ case DnDMode_Bidirectional:
+ LogRel(("Drag and drop mode: Bidirectional\n"));
+ parm.u.uint32 = VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL;
+ break;
+ }
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", DragAndDropSvc::HOST_DND_FN_SET_MODE, 1 /* cParms */, &parm);
+ if (RT_FAILURE(vrc))
+ LogRel(("Error changing drag and drop mode: %Rrc\n", vrc));
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_USB
+/**
+ * @interface_method_impl{REMOTEUSBIF,pfnQueryRemoteUsbBackend}
+ */
+/*static*/ DECLCALLBACK(PREMOTEUSBCALLBACK)
+Console::i_usbQueryRemoteUsbBackend(void *pvUser, PCRTUUID pUuid, uint32_t idClient)
+{
+ Console *pConsole = (Console *)pvUser;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ Guid const uuid(*pUuid);
+ return (PREMOTEUSBCALLBACK)pConsole->i_consoleVRDPServer()->USBBackendRequestPointer(idClient, &uuid);
+}
+
+
+/**
+ * Sends a request to VMM to attach the given host device.
+ * After this method succeeds, the attached device will appear in the
+ * mUSBDevices collection.
+ *
+ * @param aHostDevice device to attach
+ *
+ * @note Synchronously calls EMT.
+ */
+HRESULT Console::i_attachUSBDevice(IUSBDevice *aHostDevice, ULONG aMaskedIfs, const Utf8Str &aCaptureFilename)
+{
+ AssertReturn(aHostDevice, E_FAIL);
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+
+ HRESULT hrc;
+
+ /*
+ * Get the address and the Uuid, and call the pfnCreateProxyDevice roothub
+ * method in EMT (using usbAttachCallback()).
+ */
+ Bstr bstrAddress;
+ hrc = aHostDevice->COMGETTER(Address)(bstrAddress.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Utf8Str const Address(bstrAddress);
+
+ Bstr id;
+ hrc = aHostDevice->COMGETTER(Id)(id.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Guid const uuid(id);
+
+ BOOL fRemote = FALSE;
+ hrc = aHostDevice->COMGETTER(Remote)(&fRemote);
+ ComAssertComRCRetRC(hrc);
+
+ Bstr bstrBackend;
+ hrc = aHostDevice->COMGETTER(Backend)(bstrBackend.asOutParam());
+ ComAssertComRCRetRC(hrc);
+ Utf8Str const strBackend(bstrBackend);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ LogFlowThisFunc(("Proxying USB device '%s' {%RTuuid}...\n", Address.c_str(), uuid.raw()));
+
+ PCFGMNODE pRemoteCfg = NULL;
+ if (fRemote)
+ {
+ RemoteUSBDevice *pRemoteUSBDevice = static_cast<RemoteUSBDevice *>(aHostDevice);
+
+ pRemoteCfg = mpVMM->pfnCFGMR3CreateTree(ptrVM.rawUVM());
+ if (pRemoteCfg)
+ {
+ int vrc = mpVMM->pfnCFGMR3InsertInteger(pRemoteCfg, "ClientId", pRemoteUSBDevice->clientId());
+ if (RT_FAILURE(vrc))
+ {
+ mpVMM->pfnCFGMR3DestroyTree(pRemoteCfg);
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to create configuration for USB device."));
+ }
+ }
+ else
+ return setErrorBoth(E_OUTOFMEMORY, VERR_NO_MEMORY, tr("Failed to allocate config tree for USB device."));
+ }
+
+ USBConnectionSpeed_T enmSpeed;
+ hrc = aHostDevice->COMGETTER(Speed)(&enmSpeed);
+ AssertComRCReturnRC(hrc);
+
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)i_usbAttachCallback, 11,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), aHostDevice, uuid.raw(),
+ strBackend.c_str(), Address.c_str(), pRemoteCfg, enmSpeed, aMaskedIfs,
+ aCaptureFilename.isEmpty() ? NULL : aCaptureFilename.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ /* Create a OUSBDevice and add it to the device list */
+ ComObjPtr<OUSBDevice> pUSBDevice;
+ pUSBDevice.createObject();
+ hrc = pUSBDevice->init(aHostDevice);
+ AssertComRC(hrc);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mUSBDevices.push_back(pUSBDevice);
+ LogFlowFunc(("Attached device {%RTuuid}\n", pUSBDevice->i_id().raw()));
+
+ /* notify callbacks */
+ alock.release();
+ i_onUSBDeviceStateChange(pUSBDevice, true /* aAttached */, NULL);
+ }
+ else
+ {
+ Log1WarningThisFunc(("Failed to create proxy device for '%s' {%RTuuid} (%Rrc)\n", Address.c_str(), uuid.raw(), vrc));
+ switch (vrc)
+ {
+ case VERR_VUSB_NO_PORTS:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to attach the USB device. (No available ports on the USB controller)."));
+ break;
+ case VERR_VUSB_USBFS_PERMISSION:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Not permitted to open the USB device, check usbfs options"));
+ break;
+ default:
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to create a proxy device for the USB device. (Error: %Rrc)"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * USB device attach callback used by AttachUSBDevice().
+ * Note that AttachUSBDevice() doesn't return until this callback is executed,
+ * so we don't use AutoCaller and don't care about reference counters of
+ * interface pointers passed in.
+ *
+ * @thread EMT
+ * @note Locks the console object for writing.
+ */
+//static
+DECLCALLBACK(int)
+Console::i_usbAttachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, IUSBDevice *aHostDevice, PCRTUUID aUuid,
+ const char *pszBackend, const char *aAddress, PCFGMNODE pRemoteCfg, USBConnectionSpeed_T aEnmSpeed,
+ ULONG aMaskedIfs, const char *pszCaptureFilename)
+{
+ RT_NOREF(aHostDevice);
+ LogFlowFuncEnter();
+ LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid));
+
+ AssertReturn(that && aUuid, VERR_INVALID_PARAMETER);
+ AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ VUSBSPEED enmSpeed = VUSB_SPEED_UNKNOWN;
+ switch (aEnmSpeed)
+ {
+ case USBConnectionSpeed_Low: enmSpeed = VUSB_SPEED_LOW; break;
+ case USBConnectionSpeed_Full: enmSpeed = VUSB_SPEED_FULL; break;
+ case USBConnectionSpeed_High: enmSpeed = VUSB_SPEED_HIGH; break;
+ case USBConnectionSpeed_Super: enmSpeed = VUSB_SPEED_SUPER; break;
+ case USBConnectionSpeed_SuperPlus: enmSpeed = VUSB_SPEED_SUPERPLUS; break;
+ default: AssertFailed(); break;
+ }
+
+ int vrc = pVMM->pfnPDMR3UsbCreateProxyDevice(pUVM, aUuid, pszBackend, aAddress, pRemoteCfg,
+ enmSpeed, aMaskedIfs, pszCaptureFilename);
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ LogFlowFuncLeave();
+ return vrc;
+}
+
+/**
+ * Sends a request to VMM to detach the given host device. After this method
+ * succeeds, the detached device will disappear from the mUSBDevices
+ * collection.
+ *
+ * @param aHostDevice device to attach
+ *
+ * @note Synchronously calls EMT.
+ */
+HRESULT Console::i_detachUSBDevice(const ComObjPtr<OUSBDevice> &aHostDevice)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+
+ /* Get the VM handle. */
+ SafeVMPtr ptrVM(this);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ /* if the device is attached, then there must at least one USB hub. */
+ AssertReturn(ptrVM.vtable()->pfnPDMR3UsbHasHub(ptrVM.rawUVM()), E_FAIL);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("Detaching USB proxy device {%RTuuid}...\n", aHostDevice->i_id().raw()));
+
+ /*
+ * If this was a remote device, release the backend pointer.
+ * The pointer was requested in usbAttachCallback.
+ */
+ BOOL fRemote = FALSE;
+
+ HRESULT hrc2 = aHostDevice->COMGETTER(Remote)(&fRemote);
+ if (FAILED(hrc2))
+ i_setErrorStatic(hrc2, "GetRemote() failed");
+
+ PCRTUUID pUuid = aHostDevice->i_id().raw();
+ if (fRemote)
+ {
+ Guid guid(*pUuid);
+ i_consoleVRDPServer()->USBBackendReleasePointer(&guid);
+ }
+
+ alock.release();
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)i_usbDetachCallback, 4,
+ this, ptrVM.rawUVM(), ptrVM.vtable(), pUuid);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Detached device {%RTuuid}\n", pUuid));
+
+ /* notify callbacks */
+ i_onUSBDeviceStateChange(aHostDevice, false /* aAttached */, NULL);
+ }
+
+ ComAssertRCRet(vrc, E_FAIL);
+
+ return S_OK;
+}
+
+/**
+ * USB device detach callback used by DetachUSBDevice().
+ *
+ * Note that DetachUSBDevice() doesn't return until this callback is executed,
+ * so we don't use AutoCaller and don't care about reference counters of
+ * interface pointers passed in.
+ *
+ * @thread EMT
+ */
+//static
+DECLCALLBACK(int)
+Console::i_usbDetachCallback(Console *that, PUVM pUVM, PCVMMR3VTABLE pVMM, PCRTUUID aUuid)
+{
+ LogFlowFuncEnter();
+ LogFlowFunc(("that={%p} aUuid={%RTuuid}\n", that, aUuid));
+
+ AssertReturn(that && aUuid, VERR_INVALID_PARAMETER);
+ AssertReturn(!that->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ int vrc = pVMM->pfnPDMR3UsbDetachDevice(pUVM, aUuid);
+
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ LogFlowFuncLeave();
+ return vrc;
+}
+#endif /* VBOX_WITH_USB */
+
+/* Note: FreeBSD needs this whether netflt is used or not. */
+#if ((defined(RT_OS_LINUX) && !defined(VBOX_WITH_NETFLT)) || defined(RT_OS_FREEBSD))
+
+/**
+ * Helper function to handle host interface device creation and attachment.
+ *
+ * @param networkAdapter the network adapter which attachment should be reset
+ * @return COM status code
+ *
+ * @note The caller must lock this object for writing.
+ *
+ * @todo Move this back into the driver!
+ */
+HRESULT Console::i_attachToTapInterface(INetworkAdapter *networkAdapter)
+{
+ LogFlowThisFunc(("\n"));
+ /* sanity check */
+ AssertReturn(isWriteLockOnCurrentThread(), E_FAIL);
+
+# ifdef VBOX_STRICT
+ /* paranoia */
+ NetworkAttachmentType_T attachment;
+ networkAdapter->COMGETTER(AttachmentType)(&attachment);
+ Assert(attachment == NetworkAttachmentType_Bridged);
+# endif /* VBOX_STRICT */
+
+ ULONG slot = 0;
+ HRESULT hrc = networkAdapter->COMGETTER(Slot)(&slot);
+ AssertComRCReturnRC(hrc);
+
+# 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;
+ hrc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(hrc))
+ tapDeviceName.setNull(); /* Is this necessary? */
+ if (tapDeviceName.isEmpty())
+ {
+ LogRel(("No TAP device name was supplied.\n"));
+ hrc = setError(E_FAIL, tr("No TAP device name was supplied for the host networking interface"));
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ /* 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()));
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Failed to open the host network interface %ls"), tapDeviceName.raw());
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * 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;
+ hrc = 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 vrc=%Rrc\n", vrc));
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* will be handled by our caller */
+ hrc = E_ACCESSDENIED;
+ break;
+ default:
+ hrc = 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;
+ hrc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(hrc))
+ tapDeviceName.setNull(); /* Is this necessary? */
+ if (tapDeviceName.isEmpty())
+ {
+ LogRel(("No TAP device name was supplied.\n"));
+ hrc = 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 */
+ hrc = E_ACCESSDENIED;
+ break;
+ default:
+ hrc = 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(hrc))
+ {
+ LogRel(("General failure attaching to host interface\n"));
+ hrc = setErrorBoth(E_FAIL, vrc, tr("General failure attaching to host interface"));
+ }
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/**
+ * 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);
+
+# ifdef VBOX_STRICT
+ /* paranoia */
+ NetworkAttachmentType_T attachment;
+ networkAdapter->COMGETTER(AttachmentType)(&attachment);
+ Assert(attachment == NetworkAttachmentType_Bridged);
+# endif /* VBOX_STRICT */
+
+ ULONG slot = 0;
+ HRESULT hrc = networkAdapter->COMGETTER(Slot)(&slot);
+ AssertComRCReturnRC(hrc);
+
+ /* is there an open TAP device? */
+ if (maTapFD[slot] != NIL_RTFILE)
+ {
+ /*
+ * Close the file handle.
+ */
+ Bstr tapDeviceName, tapTerminateApplication;
+ bool isStatic = true;
+ hrc = networkAdapter->COMGETTER(BridgedInterface)(tapDeviceName.asOutParam());
+ if (FAILED(hrc) || 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 vrc = RTFileClose(maTapFD[slot]);
+ AssertRC(vrc);
+ 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 vrc = RTFileClose(maTapFD[slot]);
+ AssertRC(vrc);
+ }
+ /* the TAP device name and handle are no longer valid */
+ maTapFD[slot] = NIL_RTFILE;
+ maTAPDeviceName[slot] = "";
+ }
+ LogFlowThisFunc(("returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+#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
+ */
+ 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);
+
+ HRESULT hrc = S_OK;
+ for (ULONG slot = 0; slot < maxNetworkAdapters; slot++)
+ {
+ ComPtr<INetworkAdapter> pNetworkAdapter;
+ hrc = mMachine->GetNetworkAdapter(slot, pNetworkAdapter.asOutParam());
+ if (FAILED(hrc)) 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 hrc2 = i_detachFromTapInterface(pNetworkAdapter);
+ if (FAILED(hrc2) && SUCCEEDED(hrc))
+ hrc = hrc2;
+#endif /* (RT_OS_LINUX || RT_OS_FREEBSD) && !VBOX_WITH_NETFLT */
+ }
+ }
+
+ return hrc;
+}
+
+
+/**
+ * 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 vrc, RT_SRC_POS_DECL, const char *pszFormat, va_list args)
+{
+ RT_SRC_POS_NOREF();
+ Utf8Str *pErrorText = (Utf8Str *)pvUser;
+ AssertPtr(pErrorText);
+
+ /* We ignore RT_SRC_POS_DECL arguments to avoid confusion of end-users. */
+ va_list va2;
+ va_copy(va2, args);
+
+ /* Append to any the existing error message. */
+ try
+ {
+ if (pErrorText->length())
+ pErrorText->appendPrintf(".\n%N (%Rrc)", pszFormat, &va2, vrc, vrc);
+ else
+ pErrorText->printf("%N (%Rrc)", pszFormat, &va2, vrc, vrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+
+ va_end(va2);
+
+ NOREF(pUVM);
+}
+
+/**
+ * VM runtime error callback function (FNVMATRUNTIMEERROR).
+ *
+ * See VMSetRuntimeError for the detailed description of parameters.
+ *
+ * @param pUVM The user mode VM handle. Ignored, so passing NULL
+ * is fine.
+ * @param pvUser The user argument, pointer to the Console instance.
+ * @param fFlags The action flags. See VMSETRTERR_FLAGS_*.
+ * @param pszErrorId Error ID string.
+ * @param pszFormat Error message format string.
+ * @param va Error message arguments.
+ * @thread EMT.
+ */
+/* static */ DECLCALLBACK(void)
+Console::i_atVMRuntimeErrorCallback(PUVM pUVM, void *pvUser, uint32_t fFlags,
+ const char *pszErrorId, const char *pszFormat, va_list va)
+{
+ bool const fFatal = !!(fFlags & VMSETRTERR_FLAGS_FATAL);
+ LogFlowFuncEnter();
+
+ Console *that = static_cast<Console *>(pvUser);
+ AssertReturnVoid(that);
+
+ Utf8Str message(pszFormat, va);
+
+ LogRel(("Console: VM runtime error: fatal=%RTbool, errorID=%s message=\"%s\"\n", fFatal, pszErrorId, message.c_str()));
+ try
+ {
+ that->i_onRuntimeError(BOOL(fFatal), Bstr(pszErrorId).raw(), Bstr(message).raw());
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+ LogFlowFuncLeave(); NOREF(pUVM);
+}
+
+/**
+ * Captures USB devices that match filters of the VM.
+ * Called at VM startup.
+ *
+ * @param pUVM The VM handle.
+ */
+HRESULT Console::i_captureUSBDevices(PUVM pUVM)
+{
+ RT_NOREF(pUVM);
+ LogFlowThisFunc(("\n"));
+
+ /* sanity check */
+ AssertReturn(!isWriteLockOnCurrentThread(), E_FAIL);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* If the machine has a USB controller, ask the USB proxy service to
+ * capture devices */
+ if (mfVMHasUsbController)
+ {
+ /* release the lock before calling Host in VBoxSVC since Host may call
+ * us back from under its lock (e.g. onUSBDeviceAttach()) which would
+ * produce an inter-process dead-lock otherwise. */
+ alock.release();
+
+ HRESULT hrc = mControl->AutoCaptureUSBDevices();
+ ComAssertComRCRetRC(hrc);
+ }
+
+ return S_OK;
+}
+
+
+/**
+ * Detach all USB device which are attached to the VM for the
+ * purpose of clean up and such like.
+ */
+void Console::i_detachAllUSBDevices(bool aDone)
+{
+ LogFlowThisFunc(("aDone=%RTbool\n", aDone));
+
+ /* sanity check */
+ AssertReturnVoid(!isWriteLockOnCurrentThread());
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mUSBDevices.clear();
+
+ /* release the lock before calling Host in VBoxSVC since Host may call
+ * us back from under its lock (e.g. onUSBDeviceAttach()) which would
+ * produce an inter-process dead-lock otherwise. */
+ alock.release();
+
+ mControl->DetachAllUSBDevices(aDone);
+}
+
+/* Make sure that the string is null-terminated and its size is less than cchMax bytes.
+ * Replace invalid UTF8 bytes with '?'.
+ */
+static int validateUtf8String(char *psz, size_t cchMax)
+{
+ for (;;)
+ {
+ RTUNICP Cp;
+ int vrc = RTStrGetCpNEx((const char **)&psz, &cchMax, &Cp);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!Cp)
+ break;
+ }
+ else
+ {
+ if (!cchMax)
+ return VERR_END_OF_STRING;
+
+ psz[-1] = '?';
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+static int validateRemoteUSBDeviceDesc(VRDEUSBDEVICEDESC const *e, uint32_t cbRemaining, bool fDescExt)
+{
+ uint32_t const cbDesc = fDescExt ? sizeof(VRDEUSBDEVICEDESCEXT) : sizeof(VRDEUSBDEVICEDESC);
+ if (cbDesc > cbRemaining)
+ return VERR_INVALID_PARAMETER;
+
+ if ( e->oNext > cbRemaining /* It is OK for oNext to point to the end of buffer. */
+ || e->oManufacturer >= cbRemaining
+ || e->oProduct >= cbRemaining
+ || e->oSerialNumber >= cbRemaining)
+ return VERR_INVALID_PARAMETER;
+
+ int vrc;
+ if (e->oManufacturer)
+ {
+ vrc = validateUtf8String((char *)e + e->oManufacturer, cbRemaining - e->oManufacturer);
+ if (RT_FAILURE(vrc))
+ return VERR_INVALID_PARAMETER;
+ }
+ if (e->oProduct)
+ {
+ vrc = validateUtf8String((char *)e + e->oProduct, cbRemaining - e->oProduct);
+ if (RT_FAILURE(vrc))
+ return VERR_INVALID_PARAMETER;
+ }
+ if (e->oSerialNumber)
+ {
+ vrc = validateUtf8String((char *)e + e->oSerialNumber, cbRemaining - e->oSerialNumber);
+ if (RT_FAILURE(vrc))
+ return VERR_INVALID_PARAMETER;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @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.
+ */
+ VRDEUSBDEVICEDESC *e = pDevList;
+ uint32_t cbRemaining = cbDevList;
+
+ /* The cbRemaining condition must be checked first, because the function can
+ * receive pDevList = NULL and cbDevList = 0 on client disconnect.
+ */
+ while (cbRemaining >= sizeof(e->oNext) && e->oNext)
+ {
+ int const vrc = validateRemoteUSBDeviceDesc(e, cbRemaining, fDescExt);
+ if (RT_FAILURE(vrc))
+ break; /* Consider the rest of the list invalid too. */
+
+ 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);
+ }
+ }
+ }
+
+ AssertBreak(cbRemaining >= e->oNext); /* validateRemoteUSBDeviceDesc ensures this. */
+ cbRemaining -= e->oNext;
+
+ e = (VRDEUSBDEVICEDESC *)((uint8_t *)e + e->oNext);
+ }
+
+ /*
+ * Remove dirty devices, that is those which are not reported by the server anymore.
+ */
+ for (;;)
+ {
+ ComObjPtr<RemoteUSBDevice> pUSBDevice;
+
+ RemoteUSBDeviceList::iterator it = mRemoteUSBDevices.begin();
+ while (it != mRemoteUSBDevices.end())
+ {
+ if ((*it)->dirty())
+ {
+ pUSBDevice = *it;
+ break;
+ }
+
+ ++it;
+ }
+
+ if (!pUSBDevice)
+ {
+ break;
+ }
+
+ USHORT vendorId = 0;
+ pUSBDevice->COMGETTER(VendorId)(&vendorId);
+
+ USHORT productId = 0;
+ pUSBDevice->COMGETTER(ProductId)(&productId);
+
+ Bstr product;
+ pUSBDevice->COMGETTER(Product)(product.asOutParam());
+
+ LogRel(("Remote USB: ---- Vendor %04x. Product %04x. Name = [%ls].\n", vendorId, productId, product.raw()));
+
+ /* Detach the device from VM. */
+ if (pUSBDevice->captured())
+ {
+ Bstr uuid;
+ pUSBDevice->COMGETTER(Id)(uuid.asOutParam());
+ alock.release();
+ i_onUSBDeviceDetach(uuid.raw(), NULL);
+ alock.acquire();
+ }
+
+ /* And remove it from the list. */
+ mRemoteUSBDevices.erase(it);
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+
+/**
+ * Worker called by VMPowerUpTask::handler to start the VM (also from saved
+ * state) and track progress.
+ *
+ * @param pTask The power up task.
+ *
+ * @note Locks the Console object for writing.
+ */
+/*static*/
+void Console::i_powerUpThreadTask(VMPowerUpTask *pTask)
+{
+ LogFlowFuncEnter();
+
+ AssertReturnVoid(pTask);
+ AssertReturnVoid(!pTask->mConsole.isNull());
+ AssertReturnVoid(!pTask->mProgress.isNull());
+
+ VirtualBoxBase::initializeComForThread();
+
+ HRESULT hrc = S_OK;
+ int vrc = VINF_SUCCESS;
+
+ /* Set up a build identifier so that it can be seen from core dumps what
+ * exact build was used to produce the core. */
+ static char s_szBuildID[48];
+ RTStrPrintf(s_szBuildID, sizeof(s_szBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s",
+ "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D");
+
+ ComObjPtr<Console> pConsole = pTask->mConsole;
+
+ /* Note: no need to use AutoCaller because VMPowerUpTask does that */
+
+ /* The lock is also used as a signal from the task initiator (which
+ * releases it only after RTThreadCreate()) that we can start the job */
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ /* sanity */
+ Assert(pConsole->mpUVM == NULL);
+
+ try
+ {
+ // Create the VMM device object, which starts the HGCM thread; do this only
+ // once for the console, for the pathological case that the same console
+ // object is used to power up a VM twice.
+ if (!pConsole->m_pVMMDev)
+ {
+ pConsole->m_pVMMDev = new VMMDev(pConsole);
+ AssertReturnVoid(pConsole->m_pVMMDev);
+ }
+
+ /* wait for auto reset ops to complete so that we can successfully lock
+ * the attached hard disks by calling LockMedia() below */
+ for (VMPowerUpTask::ProgressList::const_iterator
+ it = pTask->hardDiskProgresses.begin();
+ it != pTask->hardDiskProgresses.end(); ++it)
+ {
+ HRESULT hrc2 = (*it)->WaitForCompletion(-1);
+ AssertComRC(hrc2);
+
+ hrc = pTask->mProgress->SetNextOperation(BstrFmt(tr("Disk Image Reset Operation - Immutable Image")).raw(), 1);
+ AssertComRCReturnVoid(hrc);
+ }
+
+ /*
+ * 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)
+ {
+ hrc = pConsole->mControl->LockMedia();
+ if (FAILED(hrc)) throw hrc;
+ }
+
+ /* Create the VRDP server. In case of headless operation, this will
+ * also create the framebuffer, required at VM creation.
+ */
+ ConsoleVRDPServer *server = pConsole->i_consoleVRDPServer();
+ Assert(server);
+
+ /* Does VRDP server call Console from the other thread?
+ * Not sure (and can change), so release the lock just in case.
+ */
+ alock.release();
+ vrc = server->Launch();
+ alock.acquire();
+
+ if (vrc != VINF_SUCCESS)
+ {
+ Utf8Str errMsg = pConsole->VRDPServerErrorToMsg(vrc);
+ if ( RT_FAILURE(vrc)
+ && vrc != VERR_NET_ADDRESS_IN_USE) /* not fatal */
+ throw i_setErrorStaticBoth(E_FAIL, vrc, errMsg.c_str());
+ }
+
+ ComPtr<IMachine> pMachine = pConsole->i_machine();
+ ULONG cCpus = 1;
+ pMachine->COMGETTER(CPUCount)(&cCpus);
+
+ VMProcPriority_T enmVMPriority = VMProcPriority_Default;
+ pMachine->COMGETTER(VMProcessPriority)(&enmVMPriority);
+
+ /*
+ * Create the VM
+ *
+ * Note! Release the lock since EMT will call Console. It's safe because
+ * mMachineState is either Starting or Restoring state here.
+ */
+ alock.release();
+
+ if (enmVMPriority != VMProcPriority_Default)
+ pConsole->i_onVMProcessPriorityChange(enmVMPriority);
+
+ PCVMMR3VTABLE pVMM = pConsole->mpVMM;
+ PVM pVM = NULL;
+ vrc = pVMM->pfnVMR3Create(cCpus,
+ pConsole->mpVmm2UserMethods,
+ 0 /*fFlags*/,
+ Console::i_genericVMSetErrorCallback,
+ &pTask->mErrorMsg,
+ pTask->mpfnConfigConstructor,
+ static_cast<Console *>(pConsole),
+ &pVM, NULL);
+ alock.acquire();
+ if (RT_SUCCESS(vrc))
+ {
+ do /* break "loop" */
+ {
+ /*
+ * Register our load/save state file handlers
+ */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pConsole->mpUVM, sSSMConsoleUnit, 0 /*iInstance*/,
+ CONSOLE_SAVED_STATE_VERSION, 0 /* cbGuess */,
+ NULL, NULL, NULL,
+ NULL, i_saveStateFileExec, NULL,
+ NULL, i_loadStateFileExec, NULL,
+ static_cast<Console *>(pConsole));
+ AssertRCBreak(vrc);
+
+ vrc = static_cast<Console *>(pConsole)->i_getDisplay()->i_registerSSM(pConsole->mpUVM);
+ AssertRC(vrc);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /*
+ * Synchronize debugger settings
+ */
+ MachineDebugger *machineDebugger = pConsole->i_getMachineDebugger();
+ if (machineDebugger)
+ machineDebugger->i_flushQueuedSettings();
+
+ /*
+ * Shared Folders
+ */
+ if (pConsole->m_pVMMDev->isShFlActive())
+ {
+ /* Does the code below call Console from the other thread?
+ * Not sure, so release the lock just in case. */
+ alock.release();
+
+ for (SharedFolderDataMap::const_iterator it = pTask->mSharedFolders.begin();
+ it != pTask->mSharedFolders.end();
+ ++it)
+ {
+ const SharedFolderData &d = it->second;
+ hrc = pConsole->i_createSharedFolder(it->first, d);
+ if (FAILED(hrc))
+ {
+ 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(hrc))
+ hrc = S_OK; // do not fail with broken shared folders
+
+ /* acquire the lock again */
+ alock.acquire();
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ /*
+ * Attach the VRDE audio driver.
+ */
+ if (pConsole->i_getVRDEServer())
+ {
+ BOOL fVRDEEnabled = FALSE;
+ hrc = pConsole->i_getVRDEServer()->COMGETTER(Enabled)(&fVRDEEnabled);
+ AssertComRCBreak(hrc, RT_NOTHING);
+
+ if ( fVRDEEnabled
+ && pConsole->mAudioVRDE)
+ pConsole->mAudioVRDE->doAttachDriverViaEmt(pConsole->mpUVM, pVMM, &alock);
+ }
+#endif
+
+ /*
+ * Enable client connections to the VRDP server.
+ */
+ pConsole->i_consoleVRDPServer()->EnableConnections();
+
+#ifdef VBOX_WITH_RECORDING
+ /*
+ * Enable recording if configured.
+ */
+ BOOL fRecordingEnabled = FALSE;
+ {
+ ComPtr<IRecordingSettings> ptrRecordingSettings;
+ hrc = pConsole->mMachine->COMGETTER(RecordingSettings)(ptrRecordingSettings.asOutParam());
+ AssertComRCBreak(hrc, RT_NOTHING);
+
+ hrc = ptrRecordingSettings->COMGETTER(Enabled)(&fRecordingEnabled);
+ AssertComRCBreak(hrc, RT_NOTHING);
+ }
+ if (fRecordingEnabled)
+ {
+ vrc = pConsole->i_recordingEnable(fRecordingEnabled, &alock);
+ if (RT_SUCCESS(vrc))
+ ::FireRecordingChangedEvent(pConsole->mEventSource);
+ else
+ {
+ LogRel(("Recording: Failed with %Rrc on VM power up\n", vrc));
+ vrc = VINF_SUCCESS; /* do not fail with broken recording */
+ }
+ }
+#endif
+
+ /* release the lock before a lengthy operation */
+ alock.release();
+
+ /*
+ * Capture USB devices.
+ */
+ hrc = pConsole->i_captureUSBDevices(pConsole->mpUVM);
+ if (FAILED(hrc))
+ {
+ alock.acquire();
+ break;
+ }
+
+ /*
+ * Load saved state?
+ */
+ if (pTask->mSavedStateFile.length())
+ {
+ LogFlowFunc(("Restoring saved state from '%s'...\n", pTask->mSavedStateFile.c_str()));
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ SsmStream ssmStream(pConsole, pVMM, pTask->m_pKeyStore, pTask->mKeyId, pTask->mKeyStore);
+
+ vrc = ssmStream.open(pTask->mSavedStateFile.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ PCSSMSTRMOPS pStreamOps;
+ void *pvStreamOpsUser;
+
+ vrc = ssmStream.querySsmStrmOps(&pStreamOps, &pvStreamOpsUser);
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3LoadFromStream(pConsole->mpUVM,
+ pStreamOps, pvStreamOpsUser,
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(pTask->mProgress),
+ false /*fTeleporting*/);
+ }
+#else
+ vrc = pVMM->pfnVMR3LoadFromFile(pConsole->mpUVM,
+ pTask->mSavedStateFile.c_str(),
+ Console::i_stateProgressCallback,
+ static_cast<IProgress *>(pTask->mProgress));
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ if (pTask->mStartPaused)
+ /* done */
+ pConsole->i_setMachineState(MachineState_Paused);
+ else
+ {
+ /* Start/Resume the VM execution */
+#ifdef VBOX_WITH_EXTPACK
+ vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM);
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3Resume(pConsole->mpUVM, VMRESUMEREASON_STATE_RESTORED);
+ AssertLogRelRC(vrc);
+ }
+ }
+
+ /* Power off in case we failed loading or resuming the VM */
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2);
+#ifdef VBOX_WITH_EXTPACK
+ pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM);
+#endif
+ }
+ }
+ else if (pTask->mTeleporterEnabled)
+ {
+ /* -> ConsoleImplTeleporter.cpp */
+ bool fPowerOffOnFailure;
+ hrc = pConsole->i_teleporterTrg(pConsole->mpUVM, pConsole->mpVMM, pMachine, &pTask->mErrorMsg,
+ pTask->mStartPaused, pTask->mProgress, &fPowerOffOnFailure);
+ if (FAILED(hrc) && fPowerOffOnFailure)
+ {
+ ErrorInfoKeeper eik;
+ int vrc2 = pVMM->pfnVMR3PowerOff(pConsole->mpUVM); AssertLogRelRC(vrc2);
+#ifdef VBOX_WITH_EXTPACK
+ pConsole->mptrExtPackManager->i_callAllVmPowerOffHooks(pConsole, pVM, pVMM);
+#endif
+ }
+ }
+ else if (pTask->mStartPaused)
+ /* done */
+ pConsole->i_setMachineState(MachineState_Paused);
+ else
+ {
+ /* Power on the VM (i.e. start executing) */
+#ifdef VBOX_WITH_EXTPACK
+ vrc = pConsole->mptrExtPackManager->i_callAllVmPowerOnHooks(pConsole, pVM, pVMM);
+#endif
+ if (RT_SUCCESS(vrc))
+ vrc = pVMM->pfnVMR3PowerOn(pConsole->mpUVM);
+ AssertLogRelRC(vrc);
+ }
+
+ /* acquire the lock again */
+ alock.acquire();
+ }
+ while (0);
+
+ /* On failure, destroy the VM */
+ if (FAILED(hrc) || RT_FAILURE(vrc))
+ {
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ /* powerDown() will call VMR3Destroy() and do all necessary
+ * cleanup (VRDP, USB devices) */
+ alock.release();
+ HRESULT hrc2 = pConsole->i_powerDown();
+ alock.acquire();
+ AssertComRC(hrc2);
+ }
+ else
+ {
+ /*
+ * Deregister the VMSetError callback. This is necessary as the
+ * pfnVMAtError() function passed to VMR3Create() is supposed to
+ * be sticky but our error callback isn't.
+ */
+ alock.release();
+ pVMM->pfnVMR3AtErrorDeregister(pConsole->mpUVM, Console::i_genericVMSetErrorCallback, &pTask->mErrorMsg);
+ /** @todo register another VMSetError callback? */
+ alock.acquire();
+ }
+ }
+ else
+ {
+ /*
+ * If VMR3Create() failed it has released the VM memory.
+ */
+ if (pConsole->m_pVMMDev)
+ {
+ alock.release(); /* just to be on the safe side... */
+ pConsole->m_pVMMDev->hgcmShutdown(true /*fUvmIsInvalid*/);
+ alock.acquire();
+ }
+ pVMM->pfnVMR3ReleaseUVM(pConsole->mpUVM);
+ pConsole->mpUVM = NULL;
+ }
+
+ if (SUCCEEDED(hrc) && 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 hrc 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 hrcXcpt) { hrc = hrcXcpt; }
+
+ if ( pConsole->mMachineState == MachineState_Starting
+ || pConsole->mMachineState == MachineState_Restoring
+ || pConsole->mMachineState == MachineState_TeleportingIn
+ )
+ {
+ /* We are still in the Starting/Restoring state. This means one of:
+ *
+ * 1) we failed before VMR3Create() was called;
+ * 2) VMR3Create() failed.
+ *
+ * In both cases, there is no need to call powerDown(), but we still
+ * need to go back to the PoweredOff/Saved state. Reuse
+ * vmstateChangeCallback() for that purpose.
+ */
+
+ /* preserve existing error info */
+ ErrorInfoKeeper eik;
+
+ Assert(pConsole->mpUVM == NULL);
+ i_vmstateChangeCallback(NULL, pConsole->mpVMM, VMSTATE_TERMINATED, VMSTATE_CREATING, pConsole);
+ }
+
+ /*
+ * Evaluate the final result. Note that the appropriate mMachineState value
+ * is already set by vmstateChangeCallback() in all cases.
+ */
+
+ /* release the lock, don't need it any more */
+ alock.release();
+
+ if (SUCCEEDED(hrc))
+ {
+ /* 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(hrc);
+ LogRel(("Power up failed (vrc=%Rrc, hrc=%Rhrc (%#08X))\n", vrc, hrc, hrc));
+ }
+
+ /* Notify VBoxSVC and any waiting openRemoteSession progress object. */
+ pConsole->mControl->EndPowerUp(hrc);
+
+#if defined(RT_OS_WINDOWS)
+ /* uninitialize COM */
+ CoUninitialize();
+#endif
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Reconfigures a medium attachment (part of taking or deleting an online snapshot).
+ *
+ * @param pThis Reference to the console object.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pcszDevice The name of the controller type.
+ * @param uInstance The instance of the controller.
+ * @param enmBus The storage bus type of the controller.
+ * @param fUseHostIOCache Use the host I/O cache (disable async I/O).
+ * @param fBuiltinIOCache Use the builtin I/O cache.
+ * @param fInsertDiskIntegrityDrv Flag whether to insert the disk integrity driver into the chain
+ * for additionalk debugging aids.
+ * @param fSetupMerge Whether to set up a medium merge
+ * @param uMergeSource Merge source image index
+ * @param uMergeTarget Merge target image index
+ * @param aMediumAtt The medium attachment.
+ * @param aMachineState The current machine state.
+ * @param phrc Where to store com error - only valid if we return VERR_GENERAL_FAILURE.
+ * @return VBox status code.
+ */
+/* static */
+DECLCALLBACK(int) Console::i_reconfigureMediumAttachment(Console *pThis,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *aMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc)
+{
+ LogFlowFunc(("pUVM=%p aMediumAtt=%p phrc=%p\n", pUVM, aMediumAtt, phrc));
+
+ HRESULT hrc;
+ Bstr bstr;
+ *phrc = S_OK;
+#define H() do { if (FAILED(hrc)) { AssertMsgFailed(("hrc=%Rhrc (%#x)\n", hrc, hrc)); *phrc = hrc; return VERR_GENERAL_FAILURE; } } while (0)
+
+ /* Ignore attachments other than hard disks, since at the moment they are
+ * not subject to snapshotting in general. */
+ DeviceType_T lType;
+ hrc = aMediumAtt->COMGETTER(Type)(&lType); H();
+ if (lType != DeviceType_HardDisk)
+ return VINF_SUCCESS;
+
+ /* Update the device instance configuration. */
+ int vrc = pThis->i_configMediumAttachment(pcszDevice,
+ uInstance,
+ enmBus,
+ fUseHostIOCache,
+ fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ fSetupMerge,
+ uMergeSource,
+ uMergeTarget,
+ aMediumAtt,
+ aMachineState,
+ phrc,
+ true /* fAttachDetach */,
+ false /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ NULL /* paLedDevType */,
+ NULL /* ppLunL0)*/);
+ if (RT_FAILURE(vrc))
+ {
+ AssertMsgFailed(("vrc=%Rrc\n", vrc));
+ return vrc;
+ }
+
+#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 vrc = VINF_SUCCESS; /* only used in assertion */
+ LogFlowFuncEnter();
+ try
+ {
+ if (pTask->isOk() == false)
+ vrc = 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, vrc=%Rrc\n", e.what(), vrc));
+ NOREF(e); NOREF(vrc);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnSaveState}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_vmm2User_SaveState(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ /*
+ * For now, just call SaveState. We should probably try notify the GUI so
+ * it can pop up a progress object and stuff. The progress object created
+ * by the call isn't returned to anyone and thus gets updated without
+ * anyone noticing it.
+ */
+ ComPtr<IProgress> pProgress;
+ HRESULT hrc = pConsole->mMachine->SaveState(pProgress.asOutParam());
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc);
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtInit}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyEmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu)
+{
+ NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu);
+ VirtualBoxBase::initializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyEmtTerm}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyEmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM, PUVMCPU pUVCpu)
+{
+ NOREF(pThis); NOREF(pUVM); NOREF(pUVCpu);
+ VirtualBoxBase::uninitializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtInit}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyPdmtInit(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ NOREF(pThis); NOREF(pUVM);
+ VirtualBoxBase::initializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyPdmtTerm}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyPdmtTerm(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ NOREF(pThis); NOREF(pUVM);
+ VirtualBoxBase::uninitializeComForThread();
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnNotifyResetTurnedIntoPowerOff}
+ */
+/*static*/ DECLCALLBACK(void)
+Console::i_vmm2User_NotifyResetTurnedIntoPowerOff(PCVMM2USERMETHODS pThis, PUVM pUVM)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ pConsole->mfPowerOffCausedByReset = true;
+}
+
+/**
+ * Internal function to get LED set off of Console instance
+ *
+ * @returns pointer to PDMLED object
+ *
+ * @param iLedSet Index of LED set to fetch
+ */
+PPDMLED volatile *
+Console::i_getLedSet(uint32_t iLedSet)
+{
+ AssertReturn(iLedSet < RT_ELEMENTS(maLedSets), NULL);
+ return maLedSets[iLedSet].papLeds;
+}
+
+/**
+ * @interface_method_impl{VMM2USERMETHODS,pfnQueryGenericObject}
+ */
+/*static*/ DECLCALLBACK(void *)
+Console::i_vmm2User_QueryGenericObject(PCVMM2USERMETHODS pThis, PUVM pUVM, PCRTUUID pUuid)
+{
+ Console *pConsole = ((MYVMM2USERMETHODS *)pThis)->pConsole;
+ NOREF(pUVM);
+
+ /* To simplify comparison we copy the UUID into a com::Guid object. */
+ com::Guid const UuidCopy(*pUuid);
+
+ if (UuidCopy == COM_IIDOF(IConsole))
+ {
+ IConsole *pIConsole = static_cast<IConsole *>(pConsole);
+ return pIConsole;
+ }
+
+ if (UuidCopy == COM_IIDOF(IMachine))
+ {
+ IMachine *pIMachine = pConsole->mMachine;
+ return pIMachine;
+ }
+
+ if (UuidCopy == COM_IIDOF(IKeyboard))
+ {
+ IKeyboard *pIKeyboard = pConsole->mKeyboard;
+ return pIKeyboard;
+ }
+
+ if (UuidCopy == COM_IIDOF(IMouse))
+ {
+ IMouse *pIMouse = pConsole->mMouse;
+ return pIMouse;
+ }
+
+ if (UuidCopy == COM_IIDOF(IDisplay))
+ {
+ IDisplay *pIDisplay = pConsole->mDisplay;
+ return pIDisplay;
+ }
+
+ if (UuidCopy == COM_IIDOF(INvramStore))
+ {
+ NvramStore *pNvramStore = static_cast<NvramStore *>(pConsole->mptrNvramStore);
+ return pNvramStore;
+ }
+
+ if (UuidCopy == VMMDEV_OID)
+ return pConsole->m_pVMMDev;
+
+ if (UuidCopy == USBCARDREADER_OID)
+ return pConsole->mUsbCardReader;
+
+ if (UuidCopy == COM_IIDOF(ISnapshot))
+ return ((MYVMM2USERMETHODS *)pThis)->pISnapshot;
+
+ if (UuidCopy == REMOTEUSBIF_OID)
+ return &pConsole->mRemoteUsbIf;
+
+ if (UuidCopy == EMULATEDUSBIF_OID)
+ return pConsole->mEmulatedUSB->i_getEmulatedUsbIf();
+
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMISECKEY,pfnKeyRetain}
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_pdmIfSecKey_KeyRetain(PPDMISECKEY pInterface, const char *pszId, const uint8_t **ppbKey, size_t *pcbKey)
+{
+ Console *pConsole = ((MYPDMISECKEY *)pInterface)->pConsole;
+
+ AutoReadLock thatLock(pConsole COMMA_LOCKVAL_SRC_POS);
+ SecretKey *pKey = NULL;
+
+ int vrc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppbKey = (const uint8_t *)pKey->getKeyBuffer();
+ *pcbKey = pKey->getKeySize();
+ }
+
+ return vrc;
+}
+
+/**
+ * @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 vrc = pConsole->m_pKeyStore->retainSecretKey(Utf8Str(pszId), &pKey);
+ if (RT_SUCCESS(vrc))
+ *ppszPassword = (const char *)pKey->getKeyBuffer();
+
+ return vrc;
+}
+
+/**
+ * @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 volatile *papLeds;
+ /** The unit number corresponding to the first entry in the LED array. */
+ uint32_t iFirstLUN;
+ /** The unit number corresponding to the last entry in the LED array.
+ * (The size of the LED array is iLastLUN - iFirstLUN + 1.) */
+ uint32_t iLastLUN;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** The Media Notify interface. */
+ PDMIMEDIANOTIFY IMediaNotify;
+ /** Set if there potentially are medium attachments. */
+ bool fHasMediumAttachments;
+ /** Device name+instance for mapping */
+ char *pszDeviceInstance;
+ /** Pointer to the Console object, for driver triggered activities. */
+ Console *pConsole;
+} DRVMAINSTATUS;
+/** Pointer the instance data for a Main status driver. */
+typedef 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)
+ {
+ /*
+ * Query the pointer to the PDMLED field inside the target device
+ * structure (owned by the virtual hardware device).
+ */
+ PPDMLED pLed;
+ int vrc = pThis->pLedPorts->pfnQueryStatusLed(pThis->pLedPorts, iLUN, &pLed);
+ if (RT_FAILURE(vrc))
+ pLed = NULL;
+
+ /*
+ * Update the corresponding papLeds[] entry.
+ *
+ * papLeds[] points to the struct PDMLED of each of this driver's
+ * units. The entries are initialized here, called out of a loop
+ * in Console::i_drvStatus_Construct(), which previously called
+ * Console::i_attachStatusDriver() to allocate the array itself.
+ *
+ * The arrays (and thus individual LEDs) are eventually read out
+ * by Console::getDeviceActivity(), which is itself called from
+ * src/VBox/Frontends/VirtualBox/src/runtime/UIIndicatorsPool.cpp
+ */
+ /** @todo acquire Console::mLedLock here in exclusive mode? */
+ 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->fHasMediumAttachments)
+ {
+ Console * const pConsole = pThis->pConsole;
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IMediumAttachment> pMediumAtt;
+ Utf8Str devicePath = Utf8StrFmt("%s/LUN#%u", pThis->pszDeviceInstance, uLUN);
+ Console::MediumAttachmentMap::const_iterator end = pConsole->mapMediumAttachments.end();
+ Console::MediumAttachmentMap::const_iterator it = pConsole->mapMediumAttachments.find(devicePath);
+ if (it != end)
+ pMediumAtt = it->second;
+ Assert(!pMediumAtt.isNull());
+ if (!pMediumAtt.isNull())
+ {
+ IMedium *pMedium = NULL;
+ HRESULT hrc = pMediumAtt->COMGETTER(Medium)(&pMedium);
+ AssertComRC(hrc);
+ if (SUCCEEDED(hrc) && pMedium)
+ {
+ BOOL fHostDrive = FALSE;
+ hrc = pMedium->COMGETTER(HostDrive)(&fHostDrive);
+ AssertComRC(hrc);
+ if (!fHostDrive)
+ {
+ alock.release();
+
+ ComPtr<IMediumAttachment> pNewMediumAtt;
+ hrc = pThis->pConsole->mControl->EjectMedium(pMediumAtt, pNewMediumAtt.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ pThis->pConsole->mMachine->SaveSettings();
+ ::FireMediumChangedEvent(pThis->pConsole->mEventSource, pNewMediumAtt);
+ }
+
+ alock.acquire();
+ if (pNewMediumAtt != pMediumAtt)
+ {
+ pConsole->mapMediumAttachments.erase(devicePath);
+ pConsole->mapMediumAttachments.insert(std::make_pair(devicePath, pNewMediumAtt));
+ }
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Console::i_drvStatus_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDCONNECTORS, &pThis->ILedConnectors);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIANOTIFY, &pThis->IMediaNotify);
+ return NULL;
+}
+
+
+/**
+ * Destruct a status driver instance.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Console::i_drvStatus_Destruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->papLeds)
+ {
+ unsigned iLed = pThis->iLastLUN - pThis->iFirstLUN + 1;
+ while (iLed-- > 0)
+ ASMAtomicWriteNullPtr(&pThis->papLeds[iLed]);
+ }
+}
+
+
+/**
+ * Construct a status driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Console::i_drvStatus_Construct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINSTATUS pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINSTATUS);
+ LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Initialize data.
+ */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ pDrvIns->IBase.pfnQueryInterface = Console::i_drvStatus_QueryInterface;
+ pThis->ILedConnectors.pfnUnitChanged = Console::i_drvStatus_UnitChanged;
+ pThis->IMediaNotify.pfnEjected = Console::i_drvStatus_MediumEjected;
+ pThis->pDrvIns = pDrvIns;
+ pThis->pConsole = pConsole;
+ pThis->fHasMediumAttachments = false;
+ pThis->papLeds = NULL;
+ pThis->pszDeviceInstance = NULL;
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
+ "DeviceInstance|"
+ "iLedSet|"
+ "HasMediumAttachments|"
+ "First|"
+ "Last",
+ "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Read config.
+ */
+ PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
+
+ uint32_t iLedSet;
+ int vrc = pHlp->pfnCFGMQueryU32(pCfg, "iLedSet", &iLedSet);
+ AssertLogRelMsgRCReturn(vrc, ("Configuration error: Failed to query the \"iLedSet\" value! vrc=%Rrc\n", vrc), vrc);
+ pThis->papLeds = pConsole->i_getLedSet(iLedSet);
+
+ vrc = pHlp->pfnCFGMQueryBoolDef(pCfg, "HasMediumAttachments", &pThis->fHasMediumAttachments, false);
+ AssertLogRelMsgRCReturn(vrc, ("Configuration error: Failed to query the \"HasMediumAttachments\" value! vrc=%Rrc\n", vrc), vrc);
+
+ if (pThis->fHasMediumAttachments)
+ {
+ vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "DeviceInstance", &pThis->pszDeviceInstance);
+ AssertLogRelMsgRCReturn(vrc, ("Configuration error: Failed to query the \"DeviceInstance\" value! vrc=%Rrc\n", vrc), vrc);
+ }
+
+ vrc = pHlp->pfnCFGMQueryU32Def(pCfg, "First", &pThis->iFirstLUN, 0);
+ AssertLogRelMsgRCReturn(vrc, ("Configuration error: Failed to query the \"First\" value! vrc=%Rrc\n", vrc), vrc);
+
+ vrc = pHlp->pfnCFGMQueryU32Def(pCfg, "Last", &pThis->iLastLUN, 0);
+ AssertLogRelMsgRCReturn(vrc, ("Configuration error: Failed to query the \"Last\" value! vrc=%Rrc\n", vrc), vrc);
+
+ AssertLogRelMsgReturn(pThis->iFirstLUN <= pThis->iLastLUN,
+ ("Configuration error: Invalid unit range %u-%u\n", pThis->iFirstLUN, pThis->iLastLUN),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Get the ILedPorts interface of the above driver/device and
+ * query the LEDs we want.
+ */
+ pThis->pLedPorts = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
+ AssertMsgReturn(pThis->pLedPorts, ("Configuration error: No led ports interface above!\n"),
+ VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ for (unsigned i = pThis->iFirstLUN; i <= pThis->iLastLUN; ++i)
+ Console::i_drvStatus_UnitChanged(&pThis->ILedConnectors, i);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Console status driver (LED) registration record.
+ */
+const PDMDRVREG Console::DrvStatusReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainStatus",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main status driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STATUS,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINSTATUS),
+ /* pfnConstruct */
+ Console::i_drvStatus_Construct,
+ /* pfnDestruct */
+ Console::i_drvStatus_Destruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/ConsoleImpl2.cpp b/src/VBox/Main/src-client/ConsoleImpl2.cpp
new file mode 100644
index 00000000..3576c493
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImpl2.cpp
@@ -0,0 +1,6970 @@
+/* $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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+// VBoxNetCfg-win.h needs winsock2.h and thus MUST be included before any other
+// header file includes Windows.h.
+#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+# include <VBox/VBoxNetCfg-win.h>
+#endif
+
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "NvramStoreImpl.h"
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "GuestImpl.h"
+# include "GuestDnDPrivate.h"
+#endif
+#include "VMMDev.h"
+#include "Global.h"
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+# include "PCIRawDevImpl.h"
+#endif
+
+// generated header
+#include "SchemaDefs.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/cpp/exception.h>
+#if 0 /* enable to play with lots of memory. */
+# include <iprt/env.h>
+#endif
+#include <iprt/stream.h>
+
+#include <iprt/http.h>
+#include <iprt/socket.h>
+#include <iprt/uri.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <VBox/settings.h> /* For MachineConfigFile::getHostDefaultAudioDriver(). */
+#include <VBox/vmm/pdmapi.h> /* For PDMR3DriverAttach/PDMR3DriverDetach. */
+#include <VBox/vmm/pdmusb.h> /* For PDMR3UsbCreateEmulatedDevice. */
+#include <VBox/vmm/pdmdev.h> /* For PDMAPICMODE enum. */
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/vmm/gcm.h>
+#include <VBox/version.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+# include <VBox/HostServices/VBoxClipboardSvc.h>
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+# include <VBox/com/defs.h>
+# include <VBox/com/array.h>
+# include <vector>
+#endif /* VBOX_WITH_GUEST_PROPS */
+#include <VBox/intnet.h>
+
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+
+#ifdef VBOX_WITH_NETFLT
+# if defined(RT_OS_SOLARIS)
+# include <zone.h>
+# elif defined(RT_OS_LINUX)
+# include <unistd.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <linux/types.h>
+# include <linux/if.h>
+# elif defined(RT_OS_FREEBSD)
+# include <unistd.h>
+# include <sys/types.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <net80211/ieee80211_ioctl.h>
+# endif
+# if defined(RT_OS_WINDOWS)
+# include <iprt/win/ntddndis.h>
+# include <devguid.h>
+# else
+# include <HostNetworkInterfaceImpl.h>
+# include <netif.h>
+# include <stdlib.h>
+# endif
+#endif /* VBOX_WITH_NETFLT */
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#include "NetworkServiceRunner.h"
+#include "BusAssignmentManager.h"
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue);
+
+
+/* Darwin compile kludge */
+#undef PVM
+
+/* Comment out the following line to remove VMWare compatibility hack. */
+#define VMWARE_NET_IN_SLOT_11
+
+/**
+ * Translate IDE StorageControllerType_T to string representation.
+ */
+static const char* controllerString(StorageControllerType_T enmType)
+{
+ switch (enmType)
+ {
+ case StorageControllerType_PIIX3:
+ return "PIIX3";
+ case StorageControllerType_PIIX4:
+ return "PIIX4";
+ case StorageControllerType_ICH6:
+ return "ICH6";
+ default:
+ return "Unknown";
+ }
+}
+
+/**
+ * Simple class for storing network boot information.
+ */
+struct BootNic
+{
+ ULONG mInstance;
+ PCIBusAddress mPCIAddress;
+
+ ULONG mBootPrio;
+ bool operator < (const BootNic &rhs) const
+ {
+ ULONG lval = mBootPrio - 1; /* 0 will wrap around and get the lowest priority. */
+ ULONG rval = rhs.mBootPrio - 1;
+ return lval < rval; /* Zero compares as highest number (lowest prio). */
+ }
+};
+
+#ifndef VBOX_WITH_EFI_IN_DD2
+static int findEfiRom(IVirtualBox* vbox, FirmwareType_T aFirmwareType, Utf8Str *pEfiRomFile)
+{
+ Bstr aFilePath, empty;
+ BOOL fPresent = FALSE;
+ HRESULT hrc = vbox->CheckFirmwarePresent(aFirmwareType, empty.raw(),
+ empty.asOutParam(), aFilePath.asOutParam(), &fPresent);
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+
+ if (!fPresent)
+ {
+ LogRel(("Failed to find an EFI ROM file.\n"));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ *pEfiRomFile = Utf8Str(aFilePath);
+
+ return VINF_SUCCESS;
+}
+#endif
+
+/**
+ * @throws HRESULT on extra data retrival error.
+ */
+static int getSmcDeviceKey(IVirtualBox *pVirtualBox, IMachine *pMachine, Utf8Str *pStrKey, bool *pfGetKeyFromRealSMC)
+{
+ *pfGetKeyFromRealSMC = false;
+
+ /*
+ * The extra data takes precedence (if non-zero).
+ */
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/SmcDeviceKey", pStrKey);
+ if (pStrKey->isNotEmpty())
+ return VINF_SUCCESS;
+
+#ifdef RT_OS_DARWIN
+
+ /*
+ * Work done in EFI/DevSmc
+ */
+ *pfGetKeyFromRealSMC = true;
+ int vrc = VINF_SUCCESS;
+
+#else
+ /*
+ * Is it apple hardware in bootcamp?
+ */
+ /** @todo implement + test RTSYSDMISTR_MANUFACTURER on all hosts.
+ * Currently falling back on the product name. */
+ char szManufacturer[256];
+ szManufacturer[0] = '\0';
+ RTSystemQueryDmiString(RTSYSDMISTR_MANUFACTURER, szManufacturer, sizeof(szManufacturer));
+ if (szManufacturer[0] != '\0')
+ {
+ if ( !strcmp(szManufacturer, "Apple Computer, Inc.")
+ || !strcmp(szManufacturer, "Apple Inc.")
+ )
+ *pfGetKeyFromRealSMC = true;
+ }
+ else
+ {
+ char szProdName[256];
+ szProdName[0] = '\0';
+ RTSystemQueryDmiString(RTSYSDMISTR_PRODUCT_NAME, szProdName, sizeof(szProdName));
+ if ( ( !strncmp(szProdName, RT_STR_TUPLE("Mac"))
+ || !strncmp(szProdName, RT_STR_TUPLE("iMac"))
+ || !strncmp(szProdName, RT_STR_TUPLE("Xserve"))
+ )
+ && !strchr(szProdName, ' ') /* no spaces */
+ && RT_C_IS_DIGIT(szProdName[strlen(szProdName) - 1]) /* version number */
+ )
+ *pfGetKeyFromRealSMC = true;
+ }
+
+ int vrc = VINF_SUCCESS;
+#endif
+
+ return vrc;
+}
+
+
+/*
+ * VC++ 8 / amd64 has some serious trouble with the next functions.
+ * As a temporary measure, we'll drop global optimizations.
+ */
+#if defined(_MSC_VER) && defined(RT_ARCH_AMD64)
+# if _MSC_VER >= RT_MSC_VER_VC80 && _MSC_VER < RT_MSC_VER_VC100
+# pragma optimize("g", off)
+# endif
+#endif
+
+class ConfigError : public RTCError
+{
+public:
+
+ ConfigError(const char *pcszFunction,
+ int vrc,
+ const char *pcszName)
+ : RTCError(Utf8StrFmt(Console::tr("%s failed: vrc=%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 CFGMR3InsertString.
+ * @param pcszName See CFGMR3InsertString.
+ * @param pcszValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const char *pcszValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertString(pNode, pcszName, pcszValue);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertString", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertString and throws an RTCError if that
+ * fails (Utf8Str variant).
+ * @param pNode See CFGMR3InsertStringN.
+ * @param pcszName See CFGMR3InsertStringN.
+ * @param rStrValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertStringN(pNode, pcszName, rStrValue.c_str(), rStrValue.length());
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertStringLengthKnown", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertString and throws an RTCError if that
+ * fails (Bstr variant).
+ *
+ * @param pNode See CFGMR3InsertStringN.
+ * @param pcszName See CFGMR3InsertStringN.
+ * @param rBstrValue The string value.
+ */
+void Console::InsertConfigString(PCFGMNODE pNode, const char *pcszName, const Bstr &rBstrValue)
+{
+ InsertConfigString(pNode, pcszName, Utf8Str(rBstrValue));
+}
+
+/**
+ * Helper that calls CFGMR3InsertStringFV and throws an RTCError if that fails.
+ * @param pNode See CFGMR3InsertStringF.
+ * @param pcszName See CFGMR3InsertStringF.
+ * @param pszFormat See CFGMR3InsertStringF.
+ * @param ... See CFGMR3InsertStringF.
+ */
+void Console::InsertConfigStringF(PCFGMNODE pNode, const char *pcszName, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int vrc = mpVMM->pfnCFGMR3InsertStringFV(pNode, pcszName, pszFormat, va);
+ va_end(va);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertStringFV", vrc, pcszName);
+}
+
+
+/**
+ * Helper that calls CFGMR3InsertPassword and throws an RTCError if that
+ * fails (Utf8Str variant).
+ * @param pNode See CFGMR3InsertPasswordN.
+ * @param pcszName See CFGMR3InsertPasswordN.
+ * @param rStrValue The string value.
+ */
+void Console::InsertConfigPassword(PCFGMNODE pNode, const char *pcszName, const Utf8Str &rStrValue)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertPasswordN(pNode, pcszName, rStrValue.c_str(), rStrValue.length());
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertPasswordLengthKnown", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertBytes and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertBytes.
+ * @param pcszName See CFGMR3InsertBytes.
+ * @param pvBytes See CFGMR3InsertBytes.
+ * @param cbBytes See CFGMR3InsertBytes.
+ */
+void Console::InsertConfigBytes(PCFGMNODE pNode, const char *pcszName, const void *pvBytes, size_t cbBytes)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pcszName, pvBytes, cbBytes);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertBytes", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertInteger and throws an RTCError if that
+ * fails.
+ *
+ * @param pNode See CFGMR3InsertInteger.
+ * @param pcszName See CFGMR3InsertInteger.
+ * @param u64Integer See CFGMR3InsertInteger.
+ */
+void Console::InsertConfigInteger(PCFGMNODE pNode, const char *pcszName, uint64_t u64Integer)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pcszName, u64Integer);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertInteger", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertNode and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertNode.
+ * @param pcszName See CFGMR3InsertNode.
+ * @param ppChild See CFGMR3InsertNode.
+ */
+void Console::InsertConfigNode(PCFGMNODE pNode, const char *pcszName, PCFGMNODE *ppChild)
+{
+ int vrc = mpVMM->pfnCFGMR3InsertNode(pNode, pcszName, ppChild);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertNode", vrc, pcszName);
+}
+
+/**
+ * Helper that calls CFGMR3InsertNodeF and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3InsertNodeF.
+ * @param ppChild See CFGMR3InsertNodeF.
+ * @param pszNameFormat Name format string, see CFGMR3InsertNodeF.
+ * @param ... Format arguments.
+ */
+void Console::InsertConfigNodeF(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, ...)
+{
+ va_list va;
+ va_start(va, pszNameFormat);
+ int vrc = mpVMM->pfnCFGMR3InsertNodeF(pNode, ppChild, "%N", pszNameFormat, &va);
+ va_end(va);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3InsertNodeF", vrc, pszNameFormat);
+}
+
+/**
+ * Helper that calls CFGMR3RemoveValue and throws an RTCError if that fails.
+ *
+ * @param pNode See CFGMR3RemoveValue.
+ * @param pcszName See CFGMR3RemoveValue.
+ */
+void Console::RemoveConfigValue(PCFGMNODE pNode, const char *pcszName)
+{
+ int vrc = mpVMM->pfnCFGMR3RemoveValue(pNode, pcszName);
+ if (RT_FAILURE(vrc))
+ throw ConfigError("CFGMR3RemoveValue", vrc, pcszName);
+}
+
+/**
+ * Gets an extra data value, consulting both machine and global extra data.
+ *
+ * @throws HRESULT on failure
+ * @returns pStrValue for the callers convenience.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ * @param pszName The value to get.
+ * @param pStrValue Where to return it's value (empty string if not
+ * found).
+ */
+static Utf8Str *GetExtraDataBoth(IVirtualBox *pVirtualBox, IMachine *pMachine, const char *pszName, Utf8Str *pStrValue)
+{
+ pStrValue->setNull();
+
+ Bstr bstrName(pszName);
+ Bstr bstrValue;
+ HRESULT hrc = pMachine->GetExtraData(bstrName.raw(), bstrValue.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ if (bstrValue.isEmpty())
+ {
+ hrc = pVirtualBox->GetExtraData(bstrName.raw(), bstrValue.asOutParam());
+ if (FAILED(hrc))
+ throw hrc;
+ }
+
+ if (bstrValue.isNotEmpty())
+ *pStrValue = bstrValue;
+ return pStrValue;
+}
+
+
+/** Helper that finds out the next HBA port used
+ */
+static LONG GetNextUsedPort(LONG aPortUsed[30], LONG lBaseVal, uint32_t u32Size)
+{
+ LONG lNextPortUsed = 30;
+ for (size_t j = 0; j < u32Size; ++j)
+ {
+ if ( aPortUsed[j] > lBaseVal
+ && aPortUsed[j] <= lNextPortUsed)
+ lNextPortUsed = aPortUsed[j];
+ }
+ return lNextPortUsed;
+}
+
+#define MAX_BIOS_LUN_COUNT 4
+
+int Console::SetBiosDiskInfo(ComPtr<IMachine> pMachine, PCFGMNODE pCfg, PCFGMNODE pBiosCfg,
+ Bstr controllerName, const char * const s_apszBiosConfig[4])
+{
+ RT_NOREF(pCfg);
+ HRESULT hrc;
+#define MAX_DEVICES 30
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ LONG lPortLUN[MAX_BIOS_LUN_COUNT];
+ LONG lPortUsed[MAX_DEVICES];
+ uint32_t u32HDCount = 0;
+
+ /* init to max value */
+ lPortLUN[0] = MAX_DEVICES;
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+ size_t uNumAttachments = atts.size();
+ if (uNumAttachments > MAX_DEVICES)
+ {
+ LogRel(("Number of Attachments > Max=%d.\n", uNumAttachments));
+ uNumAttachments = MAX_DEVICES;
+ }
+
+ /* Find the relevant ports/IDs, i.e the ones to which a HD is attached. */
+ for (size_t j = 0; j < uNumAttachments; ++j)
+ {
+ IMediumAttachment *pMediumAtt = atts[j];
+ LONG lPortNum = 0;
+ hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H();
+ if (SUCCEEDED(hrc))
+ {
+ DeviceType_T lType;
+ hrc = pMediumAtt->COMGETTER(Type)(&lType); H();
+ if (SUCCEEDED(hrc) && lType == DeviceType_HardDisk)
+ {
+ /* find min port number used for HD */
+ if (lPortNum < lPortLUN[0])
+ lPortLUN[0] = lPortNum;
+ lPortUsed[u32HDCount++] = lPortNum;
+ LogFlowFunc(("HD port Count=%d\n", u32HDCount));
+ }
+ }
+ }
+
+
+ /* Pick only the top 4 used HD Ports as CMOS doesn't have space
+ * to save details for all 30 ports
+ */
+ uint32_t u32MaxPortCount = MAX_BIOS_LUN_COUNT;
+ if (u32HDCount < MAX_BIOS_LUN_COUNT)
+ u32MaxPortCount = u32HDCount;
+ for (size_t j = 1; j < u32MaxPortCount; j++)
+ lPortLUN[j] = GetNextUsedPort(lPortUsed, lPortLUN[j-1], u32HDCount);
+ if (pBiosCfg)
+ {
+ for (size_t j = 0; j < u32MaxPortCount; j++)
+ {
+ InsertConfigInteger(pBiosCfg, s_apszBiosConfig[j], lPortLUN[j]);
+ LogFlowFunc(("Top %d HBA ports = %s, %d\n", j, s_apszBiosConfig[j], lPortLUN[j]));
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+HRESULT Console::i_attachRawPCIDevices(PUVM pUVM, BusAssignmentManager *pBusMgr, PCFGMNODE pDevices)
+{
+# ifndef VBOX_WITH_EXTPACK
+ RT_NOREF(pUVM);
+# endif
+ HRESULT hrc = S_OK;
+ PCFGMNODE pInst, pCfg, pLunL0, pLunL1;
+
+ SafeIfaceArray<IPCIDeviceAttachment> assignments;
+ ComPtr<IMachine> aMachine = i_machine();
+
+ hrc = aMachine->COMGETTER(PCIDeviceAssignments)(ComSafeArrayAsOutParam(assignments));
+ if ( hrc != S_OK
+ || assignments.size() < 1)
+ return hrc;
+
+ /*
+ * PCI passthrough is only available if the proper ExtPack is installed.
+ *
+ * Note. Configuring PCI passthrough here and providing messages about
+ * the missing extpack isn't exactly clean, but it is a necessary evil
+ * to patch over legacy compatability issues introduced by the new
+ * distribution model.
+ */
+# ifdef VBOX_WITH_EXTPACK
+ static const char *s_pszPCIRawExtPackName = "Oracle VM VirtualBox Extension Pack";
+ if (!mptrExtPackManager->i_isExtPackUsable(s_pszPCIRawExtPackName))
+ /* Always fatal! */
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Implementation of the PCI passthrough framework not found!\n"
+ "The VM cannot be started. To fix this problem, either "
+ "install the '%s' or disable PCI passthrough via VBoxManage"),
+ s_pszPCIRawExtPackName);
+# endif
+
+ /* Now actually add devices */
+ PCFGMNODE pPCIDevs = NULL;
+
+ if (assignments.size() > 0)
+ {
+ InsertConfigNode(pDevices, "pciraw", &pPCIDevs);
+
+ PCFGMNODE pRoot = CFGMR3GetParent(pDevices); Assert(pRoot);
+
+ /* Tell PGM to tell GPCIRaw about guest mappings. */
+ CFGMR3InsertNode(pRoot, "PGM", NULL);
+ InsertConfigInteger(CFGMR3GetChild(pRoot, "PGM"), "PciPassThrough", 1);
+
+ /*
+ * Currently, using IOMMU needed for PCI passthrough
+ * requires RAM preallocation.
+ */
+ /** @todo check if we can lift this requirement */
+ CFGMR3RemoveValue(pRoot, "RamPreAlloc");
+ InsertConfigInteger(pRoot, "RamPreAlloc", 1);
+ }
+
+ for (size_t iDev = 0; iDev < assignments.size(); iDev++)
+ {
+ ComPtr<IPCIDeviceAttachment> const assignment = assignments[iDev];
+
+ LONG host;
+ hrc = assignment->COMGETTER(HostAddress)(&host); H();
+ LONG guest;
+ hrc = assignment->COMGETTER(GuestAddress)(&guest); H();
+ Bstr bstrDevName;
+ hrc = assignment->COMGETTER(Name)(bstrDevName.asOutParam()); H();
+
+ InsertConfigNodeF(pPCIDevs, &pInst, "%d", iDev);
+ InsertConfigInteger(pInst, "Trusted", 1);
+
+ PCIBusAddress HostPCIAddress(host);
+ Assert(HostPCIAddress.valid());
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigString(pCfg, "DeviceName", bstrDevName);
+
+ InsertConfigInteger(pCfg, "DetachHostDriver", 1);
+ InsertConfigInteger(pCfg, "HostPCIBusNo", HostPCIAddress.miBus);
+ InsertConfigInteger(pCfg, "HostPCIDeviceNo", HostPCIAddress.miDevice);
+ InsertConfigInteger(pCfg, "HostPCIFunctionNo", HostPCIAddress.miFn);
+
+ PCIBusAddress GuestPCIAddress(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);
+# error This is not allowed any more
+ InsertConfigInteger(pCfg, "Object", (uintptr_t)pMainDev);
+ }
+
+ return hrc;
+}
+#endif
+
+
+/**
+ * Updates the device type for a LED.
+ *
+ * @param penmSubTypeEntry The sub-type entry to update.
+ * @param enmNewType The new type.
+ */
+void Console::i_setLedType(DeviceType_T *penmSubTypeEntry, DeviceType_T enmNewType)
+{
+ /*
+ * ASSUMES no race conditions here wrt concurrent type updating.
+ */
+ if (*penmSubTypeEntry != enmNewType)
+ {
+ *penmSubTypeEntry = enmNewType;
+ ASMAtomicIncU32(&muLedGen);
+ }
+}
+
+
+/**
+ * Allocate a set of LEDs.
+ *
+ * This grabs a maLedSets entry and populates it with @a cLeds.
+ *
+ * @returns Index into maLedSets.
+ * @param cLeds The number of LEDs in the set.
+ * @param fTypes Bitmask of DeviceType_T values, e.g.
+ * RT_BIT_32(DeviceType_Network).
+ * @param ppaSubTypes When not NULL, subtypes for each LED and return the
+ * array pointer here.
+ */
+uint32_t Console::i_allocateDriverLeds(uint32_t cLeds, uint32_t fTypes, DeviceType_T **ppaSubTypes)
+{
+ Assert(cLeds > 0);
+ Assert(cLeds < 1024); /* Adjust if any driver supports >=1024 units! */
+ Assert(!(fTypes & (RT_BIT_32(DeviceType_Null) | ~(RT_BIT_32(DeviceType_End) - 1))));
+
+ /* Preallocate the arrays we need, bunching them together. */
+ AssertCompile((unsigned)DeviceType_Null == 0);
+ PPDMLED volatile *papLeds = (PPDMLED volatile *)RTMemAllocZ( (sizeof(PPDMLED) + (ppaSubTypes ? sizeof(**ppaSubTypes) : 0))
+ * cLeds);
+ AssertStmt(papLeds, throw E_OUTOFMEMORY);
+
+ /* Take the LED lock in allocation mode and see if there are more LED set entries availalbe. */
+ {
+ AutoWriteLock alock(mLedLock COMMA_LOCKVAL_SRC_POS);
+ uint32_t const idxLedSet = mcLedSets;
+ if (idxLedSet < RT_ELEMENTS(maLedSets))
+ {
+ /* Initialize the set and return the index. */
+ PLEDSET pLS = &maLedSets[idxLedSet];
+ pLS->papLeds = papLeds;
+ pLS->cLeds = cLeds;
+ pLS->fTypes = fTypes;
+ if (ppaSubTypes)
+ *ppaSubTypes = pLS->paSubTypes = (DeviceType_T *)&papLeds[cLeds];
+ else
+ pLS->paSubTypes = NULL;
+
+ mcLedSets = idxLedSet + 1;
+ LogRel2(("return idxLedSet=%d (mcLedSets=%u out of max %zu)\n", idxLedSet, mcLedSets, RT_ELEMENTS(maLedSets)));
+ return idxLedSet;
+ }
+ }
+
+ RTMemFree((void *)papLeds);
+ AssertFailed();
+ throw ConfigError("AllocateDriverPapLeds", VERR_OUT_OF_RANGE, "Too many LED sets");
+}
+
+
+/**
+ * @throws ConfigError and std::bad_alloc.
+ */
+void Console::i_attachStatusDriver(PCFGMNODE pCtlInst, uint32_t fTypes, uint32_t cLeds, DeviceType_T **ppaSubTypes,
+ Console::MediumAttachmentMap *pmapMediumAttachments,
+ const char *pcszDevice, unsigned uInstance)
+{
+ PCFGMNODE pLunL0;
+ InsertConfigNode(pCtlInst, "LUN#999", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MainStatus");
+ PCFGMNODE pCfg;
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ uint32_t const iLedSet = i_allocateDriverLeds(cLeds, fTypes, ppaSubTypes);
+ InsertConfigInteger(pCfg, "iLedSet", iLedSet);
+
+ InsertConfigInteger(pCfg, "HasMediumAttachments", pmapMediumAttachments != NULL);
+ if (pmapMediumAttachments)
+ {
+ AssertPtr(pcszDevice);
+ InsertConfigStringF(pCfg, "DeviceInstance", "%s/%u", pcszDevice, uInstance);
+ }
+ InsertConfigInteger(pCfg, "First", 0);
+ InsertConfigInteger(pCfg, "Last", cLeds - 1);
+}
+
+
+/**
+ * @throws ConfigError and std::bad_alloc.
+ */
+void Console::i_attachStatusDriver(PCFGMNODE pCtlInst, DeviceType_T enmType, uint32_t cLeds /*= 1*/)
+{
+ Assert(enmType > DeviceType_Null && enmType < DeviceType_End);
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(enmType), cLeds, NULL, NULL, NULL, 0);
+}
+
+
+/**
+ * Construct the VM configuration tree (CFGM).
+ *
+ * This is a callback for VMR3Create() call. It is called from CFGMR3Init() in
+ * the emulation thread (EMT). Any per thread COM/XPCOM initialization is done
+ * here.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVM The cross context VM handle.
+ * @param pVMM The VMM ring-3 vtable.
+ * @param pvConsole Pointer to the VMPowerUpTask object.
+ *
+ * @note Locks the Console object for writing.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_configConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvConsole)
+{
+ LogFlowFuncEnter();
+
+ AssertReturn(pvConsole, VERR_INVALID_POINTER);
+ ComObjPtr<Console> pConsole = static_cast<Console *>(pvConsole);
+
+ AutoCaller autoCaller(pConsole);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ /* lock the console because we widely use internal fields and methods */
+ AutoWriteLock alock(pConsole COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Set the VM handle and do the rest of the job in an worker method so we
+ * can easily reset the VM handle on failure.
+ */
+ pConsole->mpUVM = pUVM;
+ pVMM->pfnVMR3RetainUVM(pUVM);
+ int vrc;
+ try
+ {
+ vrc = pConsole->i_configConstructorInner(pUVM, pVM, pVMM, &alock);
+ }
+ catch (...)
+ {
+ vrc = VERR_UNEXPECTED_EXCEPTION;
+ }
+ if (RT_FAILURE(vrc))
+ {
+ pConsole->mpUVM = NULL;
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Worker for configConstructor.
+ *
+ * @return VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVM The cross context VM handle.
+ * @param pVMM The VMM vtable.
+ * @param pAlock The automatic lock instance. This is for when we have
+ * to leave it in order to avoid deadlocks (ext packs and
+ * more).
+ */
+int Console::i_configConstructorInner(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, AutoWriteLock *pAlock)
+{
+ RT_NOREF(pVM /* when everything is disabled */);
+ VMMDev *pVMMDev = m_pVMMDev; Assert(pVMMDev);
+ ComPtr<IMachine> pMachine = i_machine();
+
+ int vrc;
+ HRESULT hrc;
+ Utf8Str strTmp;
+ Bstr bstr;
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ /*
+ * Get necessary objects and frequently used parameters.
+ */
+ ComPtr<IVirtualBox> virtualBox;
+ hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H();
+
+ ComPtr<IHost> host;
+ hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H();
+
+ ComPtr<ISystemProperties> systemProperties;
+ hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam()); H();
+
+ ComPtr<IBIOSSettings> biosSettings;
+ hrc = pMachine->COMGETTER(BIOSSettings)(biosSettings.asOutParam()); H();
+
+ ComPtr<INvramStore> nvramStore;
+ hrc = pMachine->COMGETTER(NonVolatileStore)(nvramStore.asOutParam()); H();
+
+ hrc = pMachine->COMGETTER(HardwareUUID)(bstr.asOutParam()); H();
+ RTUUID HardwareUuid;
+ vrc = RTUuidFromUtf16(&HardwareUuid, bstr.raw());
+ AssertRCReturn(vrc, vrc);
+
+ ULONG cRamMBs;
+ hrc = pMachine->COMGETTER(MemorySize)(&cRamMBs); H();
+#if 0 /* enable to play with lots of memory. */
+ if (RTEnvExist("VBOX_RAM_SIZE"))
+ cRamMBs = RTStrToUInt64(RTEnvGet("VBOX_RAM_SIZE"));
+#endif
+ uint64_t const cbRam = cRamMBs * (uint64_t)_1M;
+ uint32_t cbRamHole = MM_RAM_HOLE_SIZE_DEFAULT;
+ uint64_t uMcfgBase = 0;
+ uint32_t cbMcfgLength = 0;
+
+ ParavirtProvider_T enmParavirtProvider;
+ hrc = pMachine->GetEffectiveParavirtProvider(&enmParavirtProvider); H();
+
+ Bstr strParavirtDebug;
+ hrc = pMachine->COMGETTER(ParavirtDebug)(strParavirtDebug.asOutParam()); H();
+
+ BOOL fIOAPIC;
+ uint32_t uIoApicPciAddress = NIL_PCIBDF;
+ hrc = biosSettings->COMGETTER(IOAPICEnabled)(&fIOAPIC); H();
+
+ ChipsetType_T chipsetType;
+ hrc = pMachine->COMGETTER(ChipsetType)(&chipsetType); H();
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ /* We'd better have 0x10000000 region, to cover 256 buses but this put
+ * too much load on hypervisor heap. Linux 4.8 currently complains with
+ * ``acpi PNP0A03:00: [Firmware Info]: MMCONFIG for domain 0000 [bus 00-3f]
+ * only partially covers this bridge'' */
+ cbMcfgLength = 0x4000000; //0x10000000;
+ cbRamHole += cbMcfgLength;
+ uMcfgBase = _4G - cbRamHole;
+ }
+
+ /* Get the CPU profile name. */
+ Bstr bstrCpuProfile;
+ hrc = pMachine->COMGETTER(CPUProfile)(bstrCpuProfile.asOutParam()); H();
+
+ /* Check if long mode is enabled. */
+ BOOL fIsGuest64Bit;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_LongMode, &fIsGuest64Bit); H();
+
+ /*
+ * Figure out the IOMMU config.
+ */
+#if defined(VBOX_WITH_IOMMU_AMD) || defined(VBOX_WITH_IOMMU_INTEL)
+ IommuType_T enmIommuType;
+ hrc = pMachine->COMGETTER(IommuType)(&enmIommuType); H();
+
+ /* Resolve 'automatic' type to an Intel or AMD IOMMU based on the host CPU. */
+ if (enmIommuType == IommuType_Automatic)
+ {
+ if ( bstrCpuProfile.startsWith("AMD")
+ || bstrCpuProfile.startsWith("Quad-Core AMD")
+ || bstrCpuProfile.startsWith("Hygon"))
+ enmIommuType = IommuType_AMD;
+ else if (bstrCpuProfile.startsWith("Intel"))
+ {
+ if ( bstrCpuProfile.equals("Intel 8086")
+ || bstrCpuProfile.equals("Intel 80186")
+ || bstrCpuProfile.equals("Intel 80286")
+ || bstrCpuProfile.equals("Intel 80386")
+ || bstrCpuProfile.equals("Intel 80486"))
+ enmIommuType = IommuType_None;
+ else
+ enmIommuType = IommuType_Intel;
+ }
+# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ else if (ASMIsAmdCpu())
+ enmIommuType = IommuType_AMD;
+ else if (ASMIsIntelCpu())
+ enmIommuType = IommuType_Intel;
+# endif
+ else
+ {
+ /** @todo Should we handle other CPUs like Shanghai, VIA etc. here? */
+ LogRel(("WARNING! Unrecognized CPU type, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+ }
+ }
+
+ if (enmIommuType == IommuType_AMD)
+ {
+# ifdef VBOX_WITH_IOMMU_AMD
+ /*
+ * Reserve the specific PCI address of the "SB I/O APIC" when using
+ * an AMD IOMMU. Required by Linux guests, see @bugref{9654#c23}.
+ */
+ uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC;
+# else
+ LogRel(("WARNING! AMD IOMMU not supported, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+# endif
+ }
+
+ if (enmIommuType == IommuType_Intel)
+ {
+# ifdef VBOX_WITH_IOMMU_INTEL
+ /*
+ * Reserve a unique PCI address for the I/O APIC when using
+ * an Intel IOMMU. For convenience we use the same address as
+ * we do on AMD, see @bugref{9967#c13}.
+ */
+ uIoApicPciAddress = VBOX_PCI_BDF_SB_IOAPIC;
+# else
+ LogRel(("WARNING! Intel IOMMU not supported, IOMMU disabled.\n"));
+ enmIommuType = IommuType_None;
+# endif
+ }
+
+ if ( enmIommuType == IommuType_AMD
+ || enmIommuType == IommuType_Intel)
+ {
+ if (chipsetType != ChipsetType_ICH9)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("IOMMU uses MSIs which requires the ICH9 chipset implementation."));
+ if (!fIOAPIC)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("IOMMU requires an I/O APIC for remapping interrupts."));
+ }
+#else
+ IommuType_T const enmIommuType = IommuType_None;
+#endif
+
+ /* Instantiate the bus assignment manager. */
+ Assert(enmIommuType != IommuType_Automatic);
+ BusAssignmentManager *pBusMgr = mBusMgr = BusAssignmentManager::createInstance(pVMM, chipsetType, enmIommuType);
+
+ ULONG cCpus = 1;
+ hrc = pMachine->COMGETTER(CPUCount)(&cCpus); H();
+
+ ULONG ulCpuExecutionCap = 100;
+ hrc = pMachine->COMGETTER(CPUExecutionCap)(&ulCpuExecutionCap); H();
+
+ Bstr osTypeId;
+ hrc = pMachine->COMGETTER(OSTypeId)(osTypeId.asOutParam()); H();
+ LogRel(("Guest OS type: '%s'\n", Utf8Str(osTypeId).c_str()));
+
+ APICMode_T apicMode;
+ hrc = biosSettings->COMGETTER(APICMode)(&apicMode); H();
+ uint32_t uFwAPIC;
+ switch (apicMode)
+ {
+ case APICMode_Disabled:
+ uFwAPIC = 0;
+ break;
+ case APICMode_APIC:
+ uFwAPIC = 1;
+ break;
+ case APICMode_X2APIC:
+ uFwAPIC = 2;
+ break;
+ default:
+ AssertMsgFailed(("Invalid APICMode=%d\n", apicMode));
+ uFwAPIC = 1;
+ break;
+ }
+
+ ComPtr<IGuestOSType> pGuestOSType;
+ virtualBox->GetGuestOSType(osTypeId.raw(), pGuestOSType.asOutParam());
+
+ BOOL fOsXGuest = FALSE;
+ BOOL fWinGuest = FALSE;
+ BOOL fOs2Guest = FALSE;
+ BOOL fW9xGuest = FALSE;
+ BOOL fDosGuest = FALSE;
+ if (!pGuestOSType.isNull())
+ {
+ Bstr guestTypeFamilyId;
+ hrc = pGuestOSType->COMGETTER(FamilyId)(guestTypeFamilyId.asOutParam()); H();
+ fOsXGuest = guestTypeFamilyId == Bstr("MacOS");
+ fWinGuest = guestTypeFamilyId == Bstr("Windows");
+ fOs2Guest = osTypeId.startsWith("OS2");
+ fW9xGuest = osTypeId.startsWith("Windows9"); /* Does not include Windows Me. */
+ fDosGuest = osTypeId.startsWith("DOS") || osTypeId.startsWith("Windows31");
+ }
+
+ ULONG maxNetworkAdapters;
+ hrc = systemProperties->GetMaxNetworkAdapters(chipsetType, &maxNetworkAdapters); H();
+
+ /*
+ * Get root node first.
+ * This is the only node in the tree.
+ */
+ PCFGMNODE pRoot = pVMM->pfnCFGMR3GetRootU(pUVM);
+ Assert(pRoot);
+
+ // catching throws from InsertConfigString and friends.
+ try
+ {
+
+ /*
+ * Set the root (and VMM) level values.
+ */
+ hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H();
+ InsertConfigString(pRoot, "Name", bstr);
+ InsertConfigBytes(pRoot, "UUID", &HardwareUuid, sizeof(HardwareUuid));
+ InsertConfigInteger(pRoot, "RamSize", cbRam);
+ InsertConfigInteger(pRoot, "RamHoleSize", cbRamHole);
+ InsertConfigInteger(pRoot, "NumCPUs", cCpus);
+ InsertConfigInteger(pRoot, "CpuExecutionCap", ulCpuExecutionCap);
+ InsertConfigInteger(pRoot, "TimerMillies", 10);
+
+ BOOL fPageFusion = FALSE;
+ hrc = pMachine->COMGETTER(PageFusionEnabled)(&fPageFusion); H();
+ InsertConfigInteger(pRoot, "PageFusionAllowed", fPageFusion); /* boolean */
+
+ /* Not necessary, but makes sure this setting ends up in the release log. */
+ ULONG ulBalloonSize = 0;
+ hrc = pMachine->COMGETTER(MemoryBalloonSize)(&ulBalloonSize); H();
+ InsertConfigInteger(pRoot, "MemBalloonSize", ulBalloonSize);
+
+ /*
+ * EM values (before CPUM as it may need to set IemExecutesAll).
+ */
+ PCFGMNODE pEM;
+ InsertConfigNode(pRoot, "EM", &pEM);
+
+ /* Triple fault behavior. */
+ BOOL fTripleFaultReset = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_TripleFaultReset, &fTripleFaultReset); H();
+ InsertConfigInteger(pEM, "TripleFaultReset", fTripleFaultReset);
+
+ /*
+ * CPUM values.
+ */
+ PCFGMNODE pCPUM;
+ InsertConfigNode(pRoot, "CPUM", &pCPUM);
+ PCFGMNODE pIsaExts;
+ InsertConfigNode(pCPUM, "IsaExts", &pIsaExts);
+
+ /* Host CPUID leaf overrides. */
+ for (uint32_t iOrdinal = 0; iOrdinal < _4K; iOrdinal++)
+ {
+ ULONG uLeaf, uSubLeaf, uEax, uEbx, uEcx, uEdx;
+ hrc = pMachine->GetCPUIDLeafByOrdinal(iOrdinal, &uLeaf, &uSubLeaf, &uEax, &uEbx, &uEcx, &uEdx);
+ if (hrc == E_INVALIDARG)
+ break;
+ H();
+ PCFGMNODE pLeaf;
+ InsertConfigNode(pCPUM, Utf8StrFmt("HostCPUID/%RX32", uLeaf).c_str(), &pLeaf);
+ /** @todo Figure out how to tell the VMM about uSubLeaf */
+ InsertConfigInteger(pLeaf, "eax", uEax);
+ InsertConfigInteger(pLeaf, "ebx", uEbx);
+ InsertConfigInteger(pLeaf, "ecx", uEcx);
+ InsertConfigInteger(pLeaf, "edx", uEdx);
+ }
+
+ /* We must limit CPUID count for Windows NT 4, as otherwise it stops
+ with error 0x3e (MULTIPROCESSOR_CONFIGURATION_NOT_SUPPORTED). */
+ if (osTypeId == "WindowsNT4")
+ {
+ LogRel(("Limiting CPUID leaf count for NT4 guests\n"));
+ InsertConfigInteger(pCPUM, "NT4LeafLimit", true);
+ }
+
+ if (fOsXGuest)
+ {
+ /* Expose extended MWAIT features to Mac OS X guests. */
+ LogRel(("Using MWAIT extensions\n"));
+ InsertConfigInteger(pIsaExts, "MWaitExtensions", true);
+
+ /* Fake the CPU family/model so the guest works. This is partly
+ because older mac releases really doesn't work on newer cpus,
+ and partly because mac os x expects more from systems with newer
+ cpus (MSRs, power features, whatever). */
+ uint32_t uMaxIntelFamilyModelStep = UINT32_MAX;
+ if ( osTypeId == "MacOS"
+ || osTypeId == "MacOS_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482. */
+ else if ( osTypeId == "MacOS106"
+ || osTypeId == "MacOS106_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */
+ else if ( osTypeId == "MacOS107"
+ || osTypeId == "MacOS107_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out
+ what is required here. */
+ else if ( osTypeId == "MacOS108"
+ || osTypeId == "MacOS108_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure out
+ what is required here. */
+ else if ( osTypeId == "MacOS109"
+ || osTypeId == "MacOS109_64")
+ uMaxIntelFamilyModelStep = RT_MAKE_U32_FROM_U8(1, 23, 6, 0); /* Penryn / X5482 */ /** @todo figure
+ out what is required here. */
+ if (uMaxIntelFamilyModelStep != UINT32_MAX)
+ InsertConfigInteger(pCPUM, "MaxIntelFamilyModelStep", uMaxIntelFamilyModelStep);
+ }
+
+ /* CPU Portability level, */
+ ULONG uCpuIdPortabilityLevel = 0;
+ hrc = pMachine->COMGETTER(CPUIDPortabilityLevel)(&uCpuIdPortabilityLevel); H();
+ InsertConfigInteger(pCPUM, "PortableCpuIdLevel", uCpuIdPortabilityLevel);
+
+ /* Physical Address Extension (PAE) */
+ BOOL fEnablePAE = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_PAE, &fEnablePAE); H();
+ fEnablePAE |= fIsGuest64Bit;
+ InsertConfigInteger(pRoot, "EnablePAE", fEnablePAE);
+
+ /* 64-bit guests (long mode) */
+ InsertConfigInteger(pCPUM, "Enable64bit", fIsGuest64Bit);
+
+ /* APIC/X2APIC configuration */
+ BOOL fEnableAPIC = true;
+ BOOL fEnableX2APIC = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_APIC, &fEnableAPIC); H();
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_X2APIC, &fEnableX2APIC); H();
+ if (fEnableX2APIC)
+ Assert(fEnableAPIC);
+
+ /* CPUM profile name. */
+ InsertConfigString(pCPUM, "GuestCpuName", bstrCpuProfile);
+
+ /*
+ * Temporary(?) hack to make sure we emulate the ancient 16-bit CPUs
+ * correctly. There are way too many #UDs we'll miss using VT-x,
+ * raw-mode or qemu for the 186 and 286, while we'll get undefined opcodes
+ * dead wrong on 8086 (see http://www.os2museum.com/wp/undocumented-8086-opcodes/).
+ */
+ if ( bstrCpuProfile.equals("Intel 80386") /* just for now */
+ || bstrCpuProfile.equals("Intel 80286")
+ || bstrCpuProfile.equals("Intel 80186")
+ || bstrCpuProfile.equals("Nec V20")
+ || bstrCpuProfile.equals("Intel 8086") )
+ {
+ InsertConfigInteger(pEM, "IemExecutesAll", true);
+ if (!bstrCpuProfile.equals("Intel 80386"))
+ {
+ fEnableAPIC = false;
+ fIOAPIC = false;
+ }
+ fEnableX2APIC = false;
+ }
+
+ /* Adjust firmware APIC handling to stay within the VCPU limits. */
+ if (uFwAPIC == 2 && !fEnableX2APIC)
+ {
+ if (fEnableAPIC)
+ uFwAPIC = 1;
+ else
+ uFwAPIC = 0;
+ LogRel(("Limiting the firmware APIC level from x2APIC to %s\n", fEnableAPIC ? "APIC" : "Disabled"));
+ }
+ else if (uFwAPIC == 1 && !fEnableAPIC)
+ {
+ uFwAPIC = 0;
+ LogRel(("Limiting the firmware APIC level from APIC to Disabled\n"));
+ }
+
+ /* Speculation Control. */
+ BOOL fSpecCtrl = FALSE;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrl, &fSpecCtrl); H();
+ InsertConfigInteger(pCPUM, "SpecCtrl", fSpecCtrl);
+
+ /* Nested VT-x / AMD-V. */
+ BOOL fNestedHWVirt = FALSE;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_HWVirt, &fNestedHWVirt); H();
+ InsertConfigInteger(pCPUM, "NestedHWVirt", fNestedHWVirt ? true : false);
+
+ /*
+ * Hardware virtualization extensions.
+ */
+ /* Sanitize valid/useful APIC combinations, see @bugref{8868}. */
+ if (!fEnableAPIC)
+ {
+ if (fIsGuest64Bit)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC for a 64-bit guest."));
+ if (cCpus > 1)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC for an SMP guest."));
+ if (fIOAPIC)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Cannot disable the APIC when the I/O APIC is present."));
+ }
+
+ BOOL fHMEnabled;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Enabled, &fHMEnabled); H();
+ if (cCpus > 1 && !fHMEnabled)
+ {
+ LogRel(("Forced fHMEnabled to TRUE by SMP guest.\n"));
+ fHMEnabled = TRUE;
+ }
+
+ BOOL fHMForced;
+ fHMEnabled = fHMForced = TRUE;
+ LogRel(("fHMForced=true - No raw-mode support in this build!\n"));
+ if (!fHMForced) /* No need to query if already forced above. */
+ {
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_Force, &fHMForced); H();
+ if (fHMForced)
+ LogRel(("fHMForced=true - HWVirtExPropertyType_Force\n"));
+ }
+ InsertConfigInteger(pRoot, "HMEnabled", fHMEnabled);
+
+ /* /HM/xyz */
+ PCFGMNODE pHM;
+ InsertConfigNode(pRoot, "HM", &pHM);
+ InsertConfigInteger(pHM, "HMForced", fHMForced);
+ if (fHMEnabled)
+ {
+ /* Indicate whether 64-bit guests are supported or not. */
+ InsertConfigInteger(pHM, "64bitEnabled", fIsGuest64Bit);
+
+ /** @todo Not exactly pretty to check strings; VBOXOSTYPE would be better,
+ but that requires quite a bit of API change in Main. */
+ if ( fIOAPIC
+ && ( osTypeId == "WindowsNT4"
+ || osTypeId == "Windows2000"
+ || osTypeId == "WindowsXP"
+ || osTypeId == "Windows2003"))
+ {
+ /* Only allow TPR patching for NT, Win2k, XP and Windows Server 2003. (32 bits mode)
+ * We may want to consider adding more guest OSes (Solaris) later on.
+ */
+ InsertConfigInteger(pHM, "TPRPatchingEnabled", 1);
+ }
+ }
+
+ /* HWVirtEx exclusive mode */
+ BOOL fHMExclusive = true;
+ hrc = systemProperties->COMGETTER(ExclusiveHwVirt)(&fHMExclusive); H();
+ InsertConfigInteger(pHM, "Exclusive", fHMExclusive);
+
+ /* Nested paging (VT-x/AMD-V) */
+ BOOL fEnableNestedPaging = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_NestedPaging, &fEnableNestedPaging); H();
+ InsertConfigInteger(pHM, "EnableNestedPaging", fEnableNestedPaging);
+
+ /* Large pages; requires nested paging */
+ BOOL fEnableLargePages = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_LargePages, &fEnableLargePages); H();
+ InsertConfigInteger(pHM, "EnableLargePages", fEnableLargePages);
+
+ /* VPID (VT-x) */
+ BOOL fEnableVPID = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_VPID, &fEnableVPID); H();
+ InsertConfigInteger(pHM, "EnableVPID", fEnableVPID);
+
+ /* Unrestricted execution aka UX (VT-x) */
+ BOOL fEnableUX = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UnrestrictedExecution, &fEnableUX); H();
+ InsertConfigInteger(pHM, "EnableUX", fEnableUX);
+
+ /* Virtualized VMSAVE/VMLOAD (AMD-V) */
+ BOOL fVirtVmsaveVmload = true;
+ hrc = host->GetProcessorFeature(ProcessorFeature_VirtVmsaveVmload, &fVirtVmsaveVmload); H();
+ InsertConfigInteger(pHM, "SvmVirtVmsaveVmload", fVirtVmsaveVmload);
+
+ /* Indirect branch prediction boundraries. */
+ BOOL fIBPBOnVMExit = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMExit, &fIBPBOnVMExit); H();
+ InsertConfigInteger(pHM, "IBPBOnVMExit", fIBPBOnVMExit);
+
+ BOOL fIBPBOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_IBPBOnVMEntry, &fIBPBOnVMEntry); H();
+ InsertConfigInteger(pHM, "IBPBOnVMEntry", fIBPBOnVMEntry);
+
+ BOOL fSpecCtrlByHost = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_SpecCtrlByHost, &fSpecCtrlByHost); H();
+ InsertConfigInteger(pHM, "SpecCtrlByHost", fSpecCtrlByHost);
+
+ BOOL fL1DFlushOnSched = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnEMTScheduling, &fL1DFlushOnSched); H();
+ InsertConfigInteger(pHM, "L1DFlushOnSched", fL1DFlushOnSched);
+
+ BOOL fL1DFlushOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_L1DFlushOnVMEntry, &fL1DFlushOnVMEntry); H();
+ InsertConfigInteger(pHM, "L1DFlushOnVMEntry", fL1DFlushOnVMEntry);
+
+ BOOL fMDSClearOnSched = true;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnEMTScheduling, &fMDSClearOnSched); H();
+ InsertConfigInteger(pHM, "MDSClearOnSched", fMDSClearOnSched);
+
+ BOOL fMDSClearOnVMEntry = false;
+ hrc = pMachine->GetCPUProperty(CPUPropertyType_MDSClearOnVMEntry, &fMDSClearOnVMEntry); H();
+ InsertConfigInteger(pHM, "MDSClearOnVMEntry", fMDSClearOnVMEntry);
+
+ /* Reset overwrite. */
+ mfTurnResetIntoPowerOff = GetExtraDataBoth(virtualBox, pMachine,
+ "VBoxInternal2/TurnResetIntoPowerOff", &strTmp)->equals("1");
+ if (mfTurnResetIntoPowerOff)
+ InsertConfigInteger(pRoot, "PowerOffInsteadOfReset", 1);
+
+ /* Use NEM rather than HM. */
+ BOOL fUseNativeApi = false;
+ hrc = pMachine->GetHWVirtExProperty(HWVirtExPropertyType_UseNativeApi, &fUseNativeApi); H();
+ InsertConfigInteger(pHM, "UseNEMInstead", fUseNativeApi);
+
+ /* Enable workaround for missing TLB flush for OS/2 guests, see ticketref:20625. */
+ if (osTypeId.startsWith("OS2"))
+ InsertConfigInteger(pHM, "MissingOS2TlbFlushWorkaround", 1);
+
+ /*
+ * NEM
+ */
+ PCFGMNODE pNEM;
+ InsertConfigNode(pRoot, "NEM", &pNEM);
+ InsertConfigInteger(pNEM, "Allow64BitGuests", fIsGuest64Bit);
+
+ /*
+ * Paravirt. provider.
+ */
+ PCFGMNODE pParavirtNode;
+ InsertConfigNode(pRoot, "GIM", &pParavirtNode);
+ const char *pcszParavirtProvider;
+ bool fGimDeviceNeeded = true;
+ switch (enmParavirtProvider)
+ {
+ case ParavirtProvider_None:
+ pcszParavirtProvider = "None";
+ fGimDeviceNeeded = false;
+ break;
+
+ case ParavirtProvider_Minimal:
+ pcszParavirtProvider = "Minimal";
+ break;
+
+ case ParavirtProvider_HyperV:
+ pcszParavirtProvider = "HyperV";
+ break;
+
+ case ParavirtProvider_KVM:
+ pcszParavirtProvider = "KVM";
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid enmParavirtProvider=%d\n", enmParavirtProvider));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS, N_("Invalid paravirt. provider '%d'"),
+ enmParavirtProvider);
+ }
+ InsertConfigString(pParavirtNode, "Provider", pcszParavirtProvider);
+
+ /*
+ * Parse paravirt. debug options.
+ */
+ bool fGimDebug = false;
+ com::Utf8Str strGimDebugAddress = "127.0.0.1";
+ uint32_t uGimDebugPort = 50000;
+ if (strParavirtDebug.isNotEmpty())
+ {
+ /* Hyper-V debug options. */
+ if (enmParavirtProvider == ParavirtProvider_HyperV)
+ {
+ bool fGimHvDebug = false;
+ com::Utf8Str strGimHvVendor;
+ bool fGimHvVsIf = false;
+ bool fGimHvHypercallIf = false;
+
+ size_t uPos = 0;
+ com::Utf8Str strDebugOptions = strParavirtDebug;
+ com::Utf8Str strKey;
+ com::Utf8Str strVal;
+ while ((uPos = strDebugOptions.parseKeyValue(strKey, strVal, uPos)) != com::Utf8Str::npos)
+ {
+ if (strKey == "enabled")
+ {
+ if (strVal.toUInt32() == 1)
+ {
+ /* Apply defaults.
+ The defaults are documented in the user manual,
+ changes need to be reflected accordingly. */
+ fGimHvDebug = true;
+ strGimHvVendor = "Microsoft Hv";
+ fGimHvVsIf = true;
+ fGimHvHypercallIf = false;
+ }
+ /* else: ignore, i.e. don't assert below with 'enabled=0'. */
+ }
+ else if (strKey == "address")
+ strGimDebugAddress = strVal;
+ else if (strKey == "port")
+ uGimDebugPort = strVal.toUInt32();
+ else if (strKey == "vendor")
+ strGimHvVendor = strVal;
+ else if (strKey == "vsinterface")
+ fGimHvVsIf = RT_BOOL(strVal.toUInt32());
+ else if (strKey == "hypercallinterface")
+ fGimHvHypercallIf = RT_BOOL(strVal.toUInt32());
+ else
+ {
+ AssertMsgFailed(("Unrecognized Hyper-V debug option '%s'\n", strKey.c_str()));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Unrecognized Hyper-V debug option '%s' in '%s'"), strKey.c_str(),
+ strDebugOptions.c_str());
+ }
+ }
+
+ /* Update HyperV CFGM node with active debug options. */
+ if (fGimHvDebug)
+ {
+ PCFGMNODE pHvNode;
+ InsertConfigNode(pParavirtNode, "HyperV", &pHvNode);
+ InsertConfigString(pHvNode, "VendorID", strGimHvVendor);
+ InsertConfigInteger(pHvNode, "VSInterface", fGimHvVsIf ? 1 : 0);
+ InsertConfigInteger(pHvNode, "HypercallDebugInterface", fGimHvHypercallIf ? 1 : 0);
+ fGimDebug = true;
+ }
+ }
+ }
+
+ /*
+ * Guest Compatibility Manager.
+ */
+ PCFGMNODE pGcmNode;
+ uint32_t u32FixerSet = 0;
+ InsertConfigNode(pRoot, "GCM", &pGcmNode);
+ /* OS/2 and Win9x guests can run DOS apps so they get
+ * the DOS specific fixes as well.
+ */
+ if (fOs2Guest)
+ u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_OS2;
+ else if (fW9xGuest)
+ u32FixerSet = GCMFIXER_DBZ_DOS | GCMFIXER_DBZ_WIN9X;
+ else if (fDosGuest)
+ u32FixerSet = GCMFIXER_DBZ_DOS;
+ InsertConfigInteger(pGcmNode, "FixerSet", u32FixerSet);
+
+
+ /*
+ * MM values.
+ */
+ PCFGMNODE pMM;
+ InsertConfigNode(pRoot, "MM", &pMM);
+ InsertConfigInteger(pMM, "CanUseLargerHeap", chipsetType == ChipsetType_ICH9);
+
+ /*
+ * PDM config.
+ * Load drivers in VBoxC.[so|dll]
+ */
+ PCFGMNODE pPDM;
+ PCFGMNODE pNode;
+ PCFGMNODE pMod;
+ InsertConfigNode(pRoot, "PDM", &pPDM);
+ InsertConfigNode(pPDM, "Devices", &pNode);
+ InsertConfigNode(pPDM, "Drivers", &pNode);
+ InsertConfigNode(pNode, "VBoxC", &pMod);
+#ifdef VBOX_WITH_XPCOM
+ // VBoxC is located in the components subdirectory
+ char szPathVBoxC[RTPATH_MAX];
+ vrc = RTPathAppPrivateArch(szPathVBoxC, RTPATH_MAX - sizeof("/components/VBoxC")); AssertRC(vrc);
+ strcat(szPathVBoxC, "/components/VBoxC");
+ InsertConfigString(pMod, "Path", szPathVBoxC);
+#else
+ InsertConfigString(pMod, "Path", "VBoxC");
+#endif
+
+
+ /*
+ * Block cache settings.
+ */
+ PCFGMNODE pPDMBlkCache;
+ InsertConfigNode(pPDM, "BlkCache", &pPDMBlkCache);
+
+ /* I/O cache size */
+ ULONG ioCacheSize = 5;
+ hrc = pMachine->COMGETTER(IOCacheSize)(&ioCacheSize); H();
+ InsertConfigInteger(pPDMBlkCache, "CacheSize", ioCacheSize * _1M);
+
+ /*
+ * Bandwidth groups.
+ */
+ ComPtr<IBandwidthControl> bwCtrl;
+
+ hrc = pMachine->COMGETTER(BandwidthControl)(bwCtrl.asOutParam()); H();
+
+ com::SafeIfaceArray<IBandwidthGroup> bwGroups;
+ hrc = bwCtrl->GetAllBandwidthGroups(ComSafeArrayAsOutParam(bwGroups)); H();
+
+ PCFGMNODE pAc;
+ InsertConfigNode(pPDM, "AsyncCompletion", &pAc);
+ PCFGMNODE pAcFile;
+ InsertConfigNode(pAc, "File", &pAcFile);
+ PCFGMNODE pAcFileBwGroups;
+ InsertConfigNode(pAcFile, "BwGroups", &pAcFileBwGroups);
+#ifdef VBOX_WITH_NETSHAPER
+ PCFGMNODE pNetworkShaper;
+ InsertConfigNode(pPDM, "NetworkShaper", &pNetworkShaper);
+ PCFGMNODE pNetworkBwGroups;
+ InsertConfigNode(pNetworkShaper, "BwGroups", &pNetworkBwGroups);
+#endif /* VBOX_WITH_NETSHAPER */
+
+ for (size_t i = 0; i < bwGroups.size(); i++)
+ {
+ Bstr strName;
+ hrc = bwGroups[i]->COMGETTER(Name)(strName.asOutParam()); H();
+ if (strName.isEmpty())
+ return pVMM->pfnVMR3SetError(pUVM, VERR_CFGM_NO_NODE, RT_SRC_POS, N_("No bandwidth group name specified"));
+
+ BandwidthGroupType_T enmType = BandwidthGroupType_Null;
+ hrc = bwGroups[i]->COMGETTER(Type)(&enmType); H();
+ LONG64 cMaxBytesPerSec = 0;
+ hrc = bwGroups[i]->COMGETTER(MaxBytesPerSec)(&cMaxBytesPerSec); H();
+
+ if (enmType == BandwidthGroupType_Disk)
+ {
+ PCFGMNODE pBwGroup;
+ InsertConfigNode(pAcFileBwGroups, Utf8Str(strName).c_str(), &pBwGroup);
+ InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec);
+ InsertConfigInteger(pBwGroup, "Start", cMaxBytesPerSec);
+ InsertConfigInteger(pBwGroup, "Step", 0);
+ }
+#ifdef VBOX_WITH_NETSHAPER
+ else if (enmType == BandwidthGroupType_Network)
+ {
+ /* Network bandwidth groups. */
+ PCFGMNODE pBwGroup;
+ InsertConfigNode(pNetworkBwGroups, Utf8Str(strName).c_str(), &pBwGroup);
+ InsertConfigInteger(pBwGroup, "Max", cMaxBytesPerSec);
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+ }
+
+ /*
+ * Devices
+ */
+ PCFGMNODE pDevices = NULL; /* /Devices */
+ PCFGMNODE pDev = NULL; /* /Devices/Dev/ */
+ PCFGMNODE pInst = NULL; /* /Devices/Dev/0/ */
+ PCFGMNODE pCfg = NULL; /* /Devices/Dev/.../Config/ */
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */
+ PCFGMNODE pBiosCfg = NULL; /* /Devices/pcbios/0/Config/ */
+ PCFGMNODE pNetBootCfg = NULL; /* /Devices/pcbios/0/Config/NetBoot/ */
+
+ InsertConfigNode(pRoot, "Devices", &pDevices);
+
+ /*
+ * GIM Device
+ */
+ if (fGimDeviceNeeded)
+ {
+ InsertConfigNode(pDevices, "GIMDev", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ //InsertConfigNode(pInst, "Config", &pCfg);
+
+ if (fGimDebug)
+ {
+ InsertConfigNode(pInst, "LUN#998", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "UDP");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "ServerAddress", strGimDebugAddress);
+ InsertConfigInteger(pLunL1, "ServerPort", uGimDebugPort);
+ }
+ }
+
+ /*
+ * PC Arch.
+ */
+ InsertConfigNode(pDevices, "pcarch", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ /*
+ * The time offset
+ */
+ LONG64 timeOffset;
+ hrc = biosSettings->COMGETTER(TimeOffset)(&timeOffset); H();
+ PCFGMNODE pTMNode;
+ InsertConfigNode(pRoot, "TM", &pTMNode);
+ InsertConfigInteger(pTMNode, "UTCOffset", timeOffset * 1000000);
+
+ /*
+ * DMA
+ */
+ InsertConfigNode(pDevices, "8237A", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+
+ /*
+ * PCI buses.
+ */
+ uint32_t uIocPCIAddress, uHbcPCIAddress;
+ switch (chipsetType)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case ChipsetType_PIIX3:
+ /* Create the base for adding bridges on demand */
+ InsertConfigNode(pDevices, "pcibridge", NULL);
+
+ InsertConfigNode(pDevices, "pci", &pDev);
+ uHbcPCIAddress = (0x0 << 16) | 0;
+ uIocPCIAddress = (0x1 << 16) | 0; // ISA controller
+ break;
+ case ChipsetType_ICH9:
+ /* Create the base for adding bridges on demand */
+ InsertConfigNode(pDevices, "ich9pcibridge", NULL);
+
+ InsertConfigNode(pDevices, "ich9pci", &pDev);
+ uHbcPCIAddress = (0x1e << 16) | 0;
+ uIocPCIAddress = (0x1f << 16) | 0; // LPC controller
+ break;
+ }
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ /* Provide MCFG info */
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+ /* Add PCI passthrough devices */
+ hrc = i_attachRawPCIDevices(pUVM, pBusMgr, pDevices); H();
+#endif
+
+ if (enmIommuType == IommuType_AMD)
+ {
+ /* AMD IOMMU. */
+ InsertConfigNode(pDevices, "iommu-amd", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("iommu-amd", pInst); H();
+
+ /* The AMD IOMMU device needs to know which PCI slot it's in, see @bugref{9654#c104}. */
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-amd", 0, Address))
+ {
+ uint32_t const u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "PCIAddress", u32IommuAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Failed to find PCI address of the assigned IOMMU device!"));
+ }
+
+ PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress);
+ hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H();
+ }
+ else if (enmIommuType == IommuType_Intel)
+ {
+ /* Intel IOMMU. */
+ InsertConfigNode(pDevices, "iommu-intel", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("iommu-intel", pInst); H();
+
+ PCIBusAddress PCIAddr = PCIBusAddress((int32_t)uIoApicPciAddress);
+ hrc = pBusMgr->assignPCIDevice("sb-ioapic", NULL /* pCfg */, PCIAddr, true /*fGuestAddressRequired*/); H();
+ }
+ }
+
+ /*
+ * Enable the following devices: HPET, SMC and LPC on MacOS X guests or on ICH9 chipset
+ */
+
+ /*
+ * High Precision Event Timer (HPET)
+ */
+ BOOL fHPETEnabled;
+ /* Other guests may wish to use HPET too, but MacOS X not functional without it */
+ hrc = pMachine->COMGETTER(HPETEnabled)(&fHPETEnabled); H();
+ /* so always enable HPET in extended profile */
+ fHPETEnabled |= fOsXGuest;
+ /* HPET is always present on ICH9 */
+ fHPETEnabled |= (chipsetType == ChipsetType_ICH9);
+ if (fHPETEnabled)
+ {
+ InsertConfigNode(pDevices, "hpet", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "ICH9", (chipsetType == ChipsetType_ICH9) ? 1 : 0); /* boolean */
+ }
+
+ /*
+ * System Management Controller (SMC)
+ */
+ BOOL fSmcEnabled;
+ fSmcEnabled = fOsXGuest;
+ if (fSmcEnabled)
+ {
+ InsertConfigNode(pDevices, "smc", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ bool fGetKeyFromRealSMC;
+ Utf8Str strKey;
+ vrc = getSmcDeviceKey(virtualBox, pMachine, &strKey, &fGetKeyFromRealSMC);
+ AssertRCReturn(vrc, vrc);
+
+ if (!fGetKeyFromRealSMC)
+ InsertConfigString(pCfg, "DeviceKey", strKey);
+ InsertConfigInteger(pCfg, "GetKeyFromRealSMC", fGetKeyFromRealSMC);
+ }
+
+ /*
+ * Low Pin Count (LPC) bus
+ */
+ BOOL fLpcEnabled;
+ /** @todo implement appropriate getter */
+ fLpcEnabled = fOsXGuest || (chipsetType == ChipsetType_ICH9);
+ if (fLpcEnabled)
+ {
+ InsertConfigNode(pDevices, "lpc", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ hrc = pBusMgr->assignPCIDevice("lpc", pInst); H();
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ }
+
+ BOOL fShowRtc;
+ fShowRtc = fOsXGuest || (chipsetType == ChipsetType_ICH9);
+
+ /*
+ * PS/2 keyboard & mouse.
+ */
+ InsertConfigNode(pDevices, "pckbd", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ KeyboardHIDType_T aKbdHID;
+ hrc = pMachine->COMGETTER(KeyboardHIDType)(&aKbdHID); H();
+ if (aKbdHID != KeyboardHIDType_None)
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "KeyboardQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 64);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainKeyboard");
+ }
+
+ PointingHIDType_T aPointingHID;
+ hrc = pMachine->COMGETTER(PointingHIDType)(&aPointingHID); H();
+ if (aPointingHID != PointingHIDType_None)
+ {
+ InsertConfigNode(pInst, "LUN#1", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+
+ /*
+ * i8254 Programmable Interval Timer And Dummy Speaker
+ */
+ InsertConfigNode(pDevices, "i8254", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+#ifdef DEBUG
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+#endif
+
+ /*
+ * i8259 Programmable Interrupt Controller.
+ */
+ InsertConfigNode(pDevices, "i8259", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ /*
+ * Advanced Programmable Interrupt Controller.
+ * SMP: Each CPU has a LAPIC, but we have a single device representing all LAPICs states,
+ * thus only single insert
+ */
+ if (fEnableAPIC)
+ {
+ InsertConfigNode(pDevices, "apic", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ PDMAPICMODE enmAPICMode = PDMAPICMODE_APIC;
+ if (fEnableX2APIC)
+ enmAPICMode = PDMAPICMODE_X2APIC;
+ else if (!fEnableAPIC)
+ enmAPICMode = PDMAPICMODE_NONE;
+ InsertConfigInteger(pCfg, "Mode", enmAPICMode);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+
+ if (fIOAPIC)
+ {
+ /*
+ * I/O Advanced Programmable Interrupt Controller.
+ */
+ InsertConfigNode(pDevices, "ioapic", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+ if (enmIommuType == IommuType_AMD)
+ InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress);
+ else if (enmIommuType == IommuType_Intel)
+ {
+ InsertConfigString(pCfg, "ChipType", "DMAR");
+ InsertConfigInteger(pCfg, "PCIAddress", uIoApicPciAddress);
+ }
+ }
+ }
+
+ /*
+ * RTC MC146818.
+ */
+ InsertConfigNode(pDevices, "mc146818", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ BOOL fRTCUseUTC;
+ hrc = pMachine->COMGETTER(RTCUseUTC)(&fRTCUseUTC); H();
+ InsertConfigInteger(pCfg, "UseUTC", fRTCUseUTC ? 1 : 0);
+
+ /*
+ * VGA.
+ */
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ hrc = pMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); H();
+ GraphicsControllerType_T enmGraphicsController;
+ hrc = pGraphicsAdapter->COMGETTER(GraphicsControllerType)(&enmGraphicsController); H();
+ switch (enmGraphicsController)
+ {
+ case GraphicsControllerType_Null:
+ break;
+#ifdef VBOX_WITH_VMSVGA
+ case GraphicsControllerType_VMSVGA:
+ InsertConfigInteger(pHM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */
+ InsertConfigInteger(pNEM, "LovelyMesaDrvWorkaround", 1); /* hits someone else logging backdoor. */
+ RT_FALL_THROUGH();
+ case GraphicsControllerType_VBoxSVGA:
+#endif
+ case GraphicsControllerType_VBoxVGA:
+ vrc = i_configGraphicsController(pDevices, enmGraphicsController, pBusMgr, pMachine, pGraphicsAdapter, biosSettings,
+ RT_BOOL(fHMEnabled));
+ if (FAILED(vrc))
+ return vrc;
+ break;
+ default:
+ AssertMsgFailed(("Invalid graphicsController=%d\n", enmGraphicsController));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid graphics controller type '%d'"), enmGraphicsController);
+ }
+
+ /*
+ * Firmware.
+ */
+ FirmwareType_T eFwType = FirmwareType_BIOS;
+ hrc = pMachine->COMGETTER(FirmwareType)(&eFwType); H();
+
+#ifdef VBOX_WITH_EFI
+ BOOL fEfiEnabled = (eFwType >= FirmwareType_EFI) && (eFwType <= FirmwareType_EFIDUAL);
+#else
+ BOOL fEfiEnabled = false;
+#endif
+ if (!fEfiEnabled)
+ {
+ /*
+ * PC Bios.
+ */
+ InsertConfigNode(pDevices, "pcbios", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pBiosCfg);
+ InsertConfigInteger(pBiosCfg, "NumCPUs", cCpus);
+ InsertConfigString(pBiosCfg, "HardDiskDevice", "piix3ide");
+ InsertConfigString(pBiosCfg, "FloppyDevice", "i82078");
+ InsertConfigInteger(pBiosCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pBiosCfg, "APIC", uFwAPIC);
+ BOOL fPXEDebug;
+ hrc = biosSettings->COMGETTER(PXEDebugEnabled)(&fPXEDebug); H();
+ InsertConfigInteger(pBiosCfg, "PXEDebug", fPXEDebug);
+ InsertConfigBytes(pBiosCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid));
+ BOOL fUuidLe;
+ hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H();
+ InsertConfigInteger(pBiosCfg, "UuidLe", fUuidLe);
+ InsertConfigNode(pBiosCfg, "NetBoot", &pNetBootCfg);
+ InsertConfigInteger(pBiosCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pBiosCfg, "McfgLength", cbMcfgLength);
+
+ AssertMsgReturn(SchemaDefs::MaxBootPosition <= 9, ("Too many boot devices %d\n", SchemaDefs::MaxBootPosition),
+ VERR_INVALID_PARAMETER);
+
+ for (ULONG pos = 1; pos <= SchemaDefs::MaxBootPosition; ++pos)
+ {
+ DeviceType_T enmBootDevice;
+ hrc = pMachine->GetBootOrder(pos, &enmBootDevice); H();
+
+ char szParamName[] = "BootDeviceX";
+ szParamName[sizeof(szParamName) - 2] = (char)(pos - 1 + '0');
+
+ const char *pszBootDevice;
+ switch (enmBootDevice)
+ {
+ case DeviceType_Null:
+ pszBootDevice = "NONE";
+ break;
+ case DeviceType_HardDisk:
+ pszBootDevice = "IDE";
+ break;
+ case DeviceType_DVD:
+ pszBootDevice = "DVD";
+ break;
+ case DeviceType_Floppy:
+ pszBootDevice = "FLOPPY";
+ break;
+ case DeviceType_Network:
+ pszBootDevice = "LAN";
+ break;
+ default:
+ AssertMsgFailed(("Invalid enmBootDevice=%d\n", enmBootDevice));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid boot device '%d'"), enmBootDevice);
+ }
+ InsertConfigString(pBiosCfg, szParamName, pszBootDevice);
+ }
+
+ /** @todo @bugref{7145}: We might want to enable this by default for new VMs. For now,
+ * this is required for Windows 2012 guests. */
+ if (osTypeId == "Windows2012_64")
+ InsertConfigInteger(pBiosCfg, "DmiExposeMemoryTable", 1); /* boolean */
+ }
+ else
+ {
+ /* Autodetect firmware type, basing on guest type */
+ if (eFwType == FirmwareType_EFI)
+ eFwType = fIsGuest64Bit ? FirmwareType_EFI64 : FirmwareType_EFI32;
+ bool const f64BitEntry = eFwType == FirmwareType_EFI64;
+
+ Assert(eFwType == FirmwareType_EFI64 || eFwType == FirmwareType_EFI32 || eFwType == FirmwareType_EFIDUAL);
+#ifdef VBOX_WITH_EFI_IN_DD2
+ const char *pszEfiRomFile = eFwType == FirmwareType_EFIDUAL ? "VBoxEFIDual.fd"
+ : eFwType == FirmwareType_EFI32 ? "VBoxEFI32.fd"
+ : "VBoxEFI64.fd";
+#else
+ Utf8Str efiRomFile;
+ vrc = findEfiRom(virtualBox, eFwType, &efiRomFile);
+ AssertRCReturn(vrc, vrc);
+ const char *pszEfiRomFile = efiRomFile.c_str();
+#endif
+
+ /* Get boot args */
+ Utf8Str bootArgs;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiBootArgs", &bootArgs);
+
+ /* Get device props */
+ Utf8Str deviceProps;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiDeviceProps", &deviceProps);
+
+ /* Get NVRAM file name */
+ Utf8Str strNvram = mptrNvramStore->i_getNonVolatileStorageFile();
+
+ BOOL fUuidLe;
+ hrc = biosSettings->COMGETTER(SMBIOSUuidLittleEndian)(&fUuidLe); H();
+
+ /* Get graphics mode settings */
+ uint32_t u32GraphicsMode = UINT32_MAX;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsMode", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGopMode", &strTmp);
+ if (!strTmp.isEmpty())
+ u32GraphicsMode = strTmp.toUInt32();
+
+ /* Get graphics resolution settings, with some sanity checking */
+ Utf8Str strResolution;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiGraphicsResolution", &strResolution);
+ if (!strResolution.isEmpty())
+ {
+ size_t pos = strResolution.find("x");
+ if (pos != strResolution.npos)
+ {
+ Utf8Str strH, strV;
+ strH.assignEx(strResolution, 0, pos);
+ strV.assignEx(strResolution, pos+1, strResolution.length()-pos-1);
+ uint32_t u32H = strH.toUInt32();
+ uint32_t u32V = strV.toUInt32();
+ if (u32H == 0 || u32V == 0)
+ strResolution.setNull();
+ }
+ else
+ strResolution.setNull();
+ }
+ else
+ {
+ uint32_t u32H = 0;
+ uint32_t u32V = 0;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiHorizontalResolution", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaHorizontalResolution", &strTmp);
+ if (!strTmp.isEmpty())
+ u32H = strTmp.toUInt32();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiVerticalResolution", &strTmp);
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/EfiUgaVerticalResolution", &strTmp);
+ if (!strTmp.isEmpty())
+ u32V = strTmp.toUInt32();
+ if (u32H != 0 && u32V != 0)
+ strResolution = Utf8StrFmt("%ux%u", u32H, u32V);
+ }
+
+ /*
+ * EFI subtree.
+ */
+ InsertConfigNode(pDevices, "efi", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+ InsertConfigString(pCfg, "EfiRom", pszEfiRomFile);
+ InsertConfigString(pCfg, "BootArgs", bootArgs);
+ InsertConfigString(pCfg, "DeviceProps", deviceProps);
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pCfg, "APIC", uFwAPIC);
+ InsertConfigBytes(pCfg, "UUID", &HardwareUuid,sizeof(HardwareUuid));
+ InsertConfigInteger(pCfg, "UuidLe", fUuidLe);
+ InsertConfigInteger(pCfg, "64BitEntry", f64BitEntry); /* boolean */
+ InsertConfigString(pCfg, "NvramFile", strNvram);
+ if (u32GraphicsMode != UINT32_MAX)
+ InsertConfigInteger(pCfg, "GraphicsMode", u32GraphicsMode);
+ if (!strResolution.isEmpty())
+ InsertConfigString(pCfg, "GraphicsResolution", strResolution);
+
+ /* For OS X guests we'll force passing host's DMI info to the guest */
+ if (fOsXGuest)
+ {
+ InsertConfigInteger(pCfg, "DmiUseHostInfo", 1);
+ InsertConfigInteger(pCfg, "DmiExposeMemoryTable", 1);
+ }
+
+ /* Attach the NVRAM storage driver. */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "NvramStore");
+ }
+
+ /*
+ * The USB Controllers.
+ */
+ com::SafeIfaceArray<IUSBController> usbCtrls;
+ hrc = pMachine->COMGETTER(USBControllers)(ComSafeArrayAsOutParam(usbCtrls));
+ bool fOhciPresent = false; /**< Flag whether at least one OHCI controller is present. */
+ bool fXhciPresent = false; /**< Flag whether at least one XHCI controller is present. */
+
+ if (SUCCEEDED(hrc))
+ {
+ for (size_t i = 0; i < usbCtrls.size(); ++i)
+ {
+ USBControllerType_T enmCtrlType;
+ vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H();
+ if (enmCtrlType == USBControllerType_OHCI)
+ {
+ fOhciPresent = true;
+ break;
+ }
+ else if (enmCtrlType == USBControllerType_XHCI)
+ {
+ fXhciPresent = true;
+ break;
+ }
+ }
+ }
+ else if (hrc != E_NOTIMPL)
+ {
+ H();
+ }
+
+ /*
+ * Currently EHCI is only enabled when an OHCI or XHCI controller is present as well.
+ */
+ if (fOhciPresent || fXhciPresent)
+ mfVMHasUsbController = true;
+
+ PCFGMNODE pUsbDevices = NULL; /**< Required for USB storage controller later. */
+ if (mfVMHasUsbController)
+ {
+ for (size_t i = 0; i < usbCtrls.size(); ++i)
+ {
+ USBControllerType_T enmCtrlType;
+ vrc = usbCtrls[i]->COMGETTER(Type)(&enmCtrlType); H();
+
+ if (enmCtrlType == USBControllerType_OHCI)
+ {
+ InsertConfigNode(pDevices, "usb-ohci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-ohci", pInst); H();
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB);
+ }
+#ifdef VBOX_WITH_EHCI
+ else if (enmCtrlType == USBControllerType_EHCI)
+ {
+ InsertConfigNode(pDevices, "usb-ehci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-ehci", pInst); H();
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB);
+ }
+#endif
+ else if (enmCtrlType == USBControllerType_XHCI)
+ {
+ InsertConfigNode(pDevices, "usb-xhci", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("usb-xhci", pInst); H();
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#1", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "VUSBRootHub");
+ InsertConfigNode(pLunL1, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_USB, 2);
+ }
+ } /* for every USB controller. */
+
+
+ /*
+ * Virtual USB Devices.
+ */
+ InsertConfigNode(pRoot, "USB", &pUsbDevices);
+
+#ifdef VBOX_WITH_USB
+ {
+ /*
+ * Global USB options, currently unused as we'll apply the 2.0 -> 1.1 morphing
+ * on a per device level now.
+ */
+ InsertConfigNode(pUsbDevices, "USBProxy", &pCfg);
+ InsertConfigNode(pCfg, "GlobalConfig", &pCfg);
+ // This globally enables the 2.0 -> 1.1 device morphing of proxied devices to keep windows quiet.
+ //InsertConfigInteger(pCfg, "Force11Device", true);
+ // The following breaks stuff, but it makes MSDs work in vista. (I include it here so
+ // that it's documented somewhere.) Users needing it can use:
+ // VBoxManage setextradata "myvm" "VBoxInternal/USB/USBProxy/GlobalConfig/Force11PacketSize" 1
+ //InsertConfigInteger(pCfg, "Force11PacketSize", true);
+ }
+#endif
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ BOOL aEmulatedUSBCardReaderEnabled = FALSE;
+ hrc = pMachine->COMGETTER(EmulatedUSBCardReaderEnabled)(&aEmulatedUSBCardReaderEnabled); H();
+ if (aEmulatedUSBCardReaderEnabled)
+ {
+ InsertConfigNode(pUsbDevices, "CardReader", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+# ifdef VBOX_WITH_USB_CARDREADER_TEST
+ InsertConfigString(pLunL0, "Driver", "DrvDirectCardReader");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+# else
+ InsertConfigString(pLunL0, "Driver", "UsbCardReader");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+# endif
+ }
+#endif
+
+ /* Virtual USB Mouse/Tablet */
+ if ( aPointingHID == PointingHIDType_USBMouse
+ || aPointingHID == PointingHIDType_USBTablet
+ || aPointingHID == PointingHIDType_USBMultiTouch
+ || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pUsbDevices, "HidMouse", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ if (aPointingHID == PointingHIDType_USBMouse)
+ InsertConfigString(pCfg, "Mode", "relative");
+ else
+ InsertConfigString(pCfg, "Mode", "absolute");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+ if ( aPointingHID == PointingHIDType_USBMultiTouch
+ || aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pDev, "1", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigString(pCfg, "Mode", "multitouch");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+ if (aPointingHID == PointingHIDType_USBMultiTouchScreenPlusPad)
+ {
+ InsertConfigNode(pDev, "2", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigString(pCfg, "Mode", "touchpad");
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MouseQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 128);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainMouse");
+ }
+
+ /* Virtual USB Keyboard */
+ if (aKbdHID == KeyboardHIDType_USBKeyboard)
+ {
+ InsertConfigNode(pUsbDevices, "HidKeyboard", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "KeyboardQueue");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "QueueSize", 64);
+
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "MainKeyboard");
+ }
+ }
+
+ /*
+ * Storage controllers.
+ */
+ com::SafeIfaceArray<IStorageController> ctrls;
+ PCFGMNODE aCtrlNodes[StorageControllerType_VirtioSCSI + 1] = {};
+ hrc = pMachine->COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(ctrls)); H();
+
+ bool fFdcEnabled = false;
+ for (size_t i = 0; i < ctrls.size(); ++i)
+ {
+ DeviceType_T *paLedDevType = NULL;
+
+ StorageControllerType_T enmCtrlType;
+ hrc = ctrls[i]->COMGETTER(ControllerType)(&enmCtrlType); H();
+ AssertRelease((unsigned)enmCtrlType < RT_ELEMENTS(aCtrlNodes)
+ || enmCtrlType == StorageControllerType_USB);
+
+ StorageBus_T enmBus;
+ hrc = ctrls[i]->COMGETTER(Bus)(&enmBus); H();
+
+ Bstr controllerName;
+ hrc = ctrls[i]->COMGETTER(Name)(controllerName.asOutParam()); H();
+
+ ULONG ulInstance = 999;
+ hrc = ctrls[i]->COMGETTER(Instance)(&ulInstance); H();
+
+ BOOL fUseHostIOCache;
+ hrc = ctrls[i]->COMGETTER(UseHostIOCache)(&fUseHostIOCache); H();
+
+ BOOL fBootable;
+ hrc = ctrls[i]->COMGETTER(Bootable)(&fBootable); H();
+
+ PCFGMNODE pCtlInst = NULL;
+ const char *pszCtrlDev = i_storageControllerTypeToStr(enmCtrlType);
+ if (enmCtrlType != StorageControllerType_USB)
+ {
+ /* /Devices/<ctrldev>/ */
+ pDev = aCtrlNodes[enmCtrlType];
+ if (!pDev)
+ {
+ InsertConfigNode(pDevices, pszCtrlDev, &pDev);
+ aCtrlNodes[enmCtrlType] = pDev; /* IDE variants are handled in the switch */
+ }
+
+ /* /Devices/<ctrldev>/<instance>/ */
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pCtlInst);
+
+ /* Device config: /Devices/<ctrldev>/<instance>/<values> & /ditto/Config/<values> */
+ InsertConfigInteger(pCtlInst, "Trusted", 1);
+ InsertConfigNode(pCtlInst, "Config", &pCfg);
+ }
+
+ static const char * const apszBiosConfigScsi[MAX_BIOS_LUN_COUNT] =
+ { "ScsiLUN1", "ScsiLUN2", "ScsiLUN3", "ScsiLUN4" };
+
+ static const char * const apszBiosConfigSata[MAX_BIOS_LUN_COUNT] =
+ { "SataLUN1", "SataLUN2", "SataLUN3", "SataLUN4" };
+
+ switch (enmCtrlType)
+ {
+ case StorageControllerType_LsiLogic:
+ {
+ hrc = pBusMgr->assignPCIDevice("lsilogic", pCtlInst); H();
+
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 1)
+ && !pBusMgr->hasPCIDevice("buslogic", 0)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 0)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicscsi");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD),
+ 16, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_BusLogic:
+ {
+ hrc = pBusMgr->assignPCIDevice("buslogic", pCtlInst); H();
+
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 0)
+ && !pBusMgr->hasPCIDevice("buslogic", 1)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 0)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "buslogic");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD),
+ 16, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_IntelAhci:
+ {
+ hrc = pBusMgr->assignPCIDevice("ahci", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "PortCount", cPorts);
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+
+ /* Configure the hotpluggable flag for the port. */
+ for (unsigned idxAtt = 0; idxAtt < atts.size(); ++idxAtt)
+ {
+ IMediumAttachment *pMediumAtt = atts[idxAtt];
+
+ LONG lPortNum = 0;
+ hrc = pMediumAtt->COMGETTER(Port)(&lPortNum); H();
+
+ BOOL fHotPluggable = FALSE;
+ hrc = pMediumAtt->COMGETTER(HotPluggable)(&fHotPluggable); H();
+ if (SUCCEEDED(hrc))
+ {
+ PCFGMNODE pPortCfg;
+ char szName[24];
+ RTStrPrintf(szName, sizeof(szName), "Port%d", lPortNum);
+
+ InsertConfigNode(pCfg, szName, &pPortCfg);
+ InsertConfigInteger(pPortCfg, "Hotpluggable", fHotPluggable ? 1 : 0);
+ }
+ }
+
+ /* BIOS configuration values, first AHCI controller only. */
+ if ( !pBusMgr->hasPCIDevice("ahci", 1)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "SataHardDiskDevice", "ahci");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigSata); H();
+ }
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD),
+ cPorts, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_PIIX3:
+ case StorageControllerType_PIIX4:
+ case StorageControllerType_ICH6:
+ {
+ /*
+ * IDE (update this when the main interface changes)
+ */
+ hrc = pBusMgr->assignPCIDevice("piix3ide", pCtlInst); H();
+ InsertConfigString(pCfg, "Type", controllerString(enmCtrlType));
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD),
+ 4, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+
+ /* IDE flavors */
+ aCtrlNodes[StorageControllerType_PIIX3] = pDev;
+ aCtrlNodes[StorageControllerType_PIIX4] = pDev;
+ aCtrlNodes[StorageControllerType_ICH6] = pDev;
+ break;
+ }
+
+ case StorageControllerType_I82078:
+ {
+ /*
+ * i82078 Floppy drive controller
+ */
+ fFdcEnabled = true;
+ InsertConfigInteger(pCfg, "IRQ", 6);
+ InsertConfigInteger(pCfg, "DMA", 2);
+ InsertConfigInteger(pCfg, "MemMapped", 0 );
+ InsertConfigInteger(pCfg, "IOBase", 0x3f0);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_Floppy),
+ 2, NULL, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_LsiLogicSas:
+ {
+ hrc = pBusMgr->assignPCIDevice("lsilogicsas", pCtlInst); H();
+
+ InsertConfigString(pCfg, "ControllerType", "SAS1068");
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* BIOS configuration values, first SCSI controller only. */
+ if ( !pBusMgr->hasPCIDevice("lsilogic", 0)
+ && !pBusMgr->hasPCIDevice("buslogic", 0)
+ && !pBusMgr->hasPCIDevice("lsilogicsas", 1)
+ && pBiosCfg)
+ {
+ InsertConfigString(pBiosCfg, "ScsiHardDiskDevice", "lsilogicsas");
+ hrc = SetBiosDiskInfo(pMachine, pCfg, pBiosCfg, controllerName, apszBiosConfigScsi); H();
+ }
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NumPorts", cPorts);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD) /*?*/,
+ 8, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_USB:
+ {
+ if (pUsbDevices)
+ {
+ /*
+ * USB MSDs are handled a bit different as the device instance
+ * doesn't match the storage controller instance but the port.
+ */
+ InsertConfigNode(pUsbDevices, "Msd", &pDev);
+ pCtlInst = pDev;
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("There is no USB controller enabled but there\n"
+ "is at least one USB storage device configured for this VM.\n"
+ "To fix this problem either enable the USB controller or remove\n"
+ "the storage device from the VM"));
+ break;
+ }
+
+ case StorageControllerType_NVMe:
+ {
+ hrc = pBusMgr->assignPCIDevice("nvme", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NamespacesMax", cPorts);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk),
+ cPorts, NULL, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ case StorageControllerType_VirtioSCSI:
+ {
+ hrc = pBusMgr->assignPCIDevice("virtio-scsi", pCtlInst); H();
+
+ ULONG cPorts = 0;
+ hrc = ctrls[i]->COMGETTER(PortCount)(&cPorts); H();
+ InsertConfigInteger(pCfg, "NumTargets", cPorts);
+ InsertConfigInteger(pCfg, "Bootable", fBootable);
+
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk) | RT_BIT_32(DeviceType_DVD) /*?*/,
+ cPorts, &paLedDevType, &mapMediumAttachments, pszCtrlDev, ulInstance);
+ break;
+ }
+
+ default:
+ AssertLogRelMsgFailedReturn(("invalid storage controller type: %d\n", enmCtrlType), VERR_MAIN_CONFIG_CONSTRUCTOR_IPE);
+ }
+
+ /* Attach the media to the storage controllers. */
+ com::SafeIfaceArray<IMediumAttachment> atts;
+ hrc = pMachine->GetMediumAttachmentsOfController(controllerName.raw(),
+ ComSafeArrayAsOutParam(atts)); H();
+
+ /* Builtin I/O cache - per device setting. */
+ BOOL fBuiltinIOCache = true;
+ hrc = pMachine->COMGETTER(IOCacheEnabled)(&fBuiltinIOCache); H();
+
+ bool fInsertDiskIntegrityDrv = false;
+ Bstr strDiskIntegrityFlag;
+ hrc = pMachine->GetExtraData(Bstr("VBoxInternal2/EnableDiskIntegrityDriver").raw(),
+ strDiskIntegrityFlag.asOutParam());
+ if ( hrc == S_OK
+ && strDiskIntegrityFlag == "1")
+ fInsertDiskIntegrityDrv = true;
+
+ for (size_t j = 0; j < atts.size(); ++j)
+ {
+ IMediumAttachment *pMediumAtt = atts[j];
+ vrc = i_configMediumAttachment(pszCtrlDev,
+ ulInstance,
+ enmBus,
+ !!fUseHostIOCache,
+ enmCtrlType == StorageControllerType_NVMe ? false : !!fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ false /* fSetupMerge */,
+ 0 /* uMergeSource */,
+ 0 /* uMergeTarget */,
+ pMediumAtt,
+ mMachineState,
+ NULL /* phrc */,
+ false /* fAttachDetach */,
+ false /* fForceUnmount */,
+ false /* fHotplug */,
+ pUVM,
+ pVMM,
+ paLedDevType,
+ NULL /* ppLunL0 */);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ H();
+ }
+ H();
+
+ /*
+ * Network adapters
+ */
+#ifdef VMWARE_NET_IN_SLOT_11
+ bool fSwapSlots3and11 = false;
+#endif
+ PCFGMNODE pDevPCNet = NULL; /* PCNet-type devices */
+ InsertConfigNode(pDevices, "pcnet", &pDevPCNet);
+#ifdef VBOX_WITH_E1000
+ PCFGMNODE pDevE1000 = NULL; /* E1000-type devices */
+ InsertConfigNode(pDevices, "e1000", &pDevE1000);
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ PCFGMNODE pDevVirtioNet = NULL; /* Virtio network devices */
+ InsertConfigNode(pDevices, "virtio-net", &pDevVirtioNet);
+#endif /* VBOX_WITH_VIRTIO */
+ PCFGMNODE pDevDP8390 = NULL; /* DP8390-type devices */
+ InsertConfigNode(pDevices, "dp8390", &pDevDP8390);
+ PCFGMNODE pDev3C501 = NULL; /* EtherLink-type devices */
+ InsertConfigNode(pDevices, "3c501", &pDev3C501);
+
+ std::list<BootNic> llBootNics;
+ for (ULONG uInstance = 0; uInstance < maxNetworkAdapters; ++uInstance)
+ {
+ ComPtr<INetworkAdapter> networkAdapter;
+ hrc = pMachine->GetNetworkAdapter(uInstance, networkAdapter.asOutParam()); H();
+ BOOL fEnabledNetAdapter = FALSE;
+ hrc = networkAdapter->COMGETTER(Enabled)(&fEnabledNetAdapter); H();
+ if (!fEnabledNetAdapter)
+ continue;
+
+ /*
+ * The virtual hardware type. Create appropriate device first.
+ */
+ const char *pszAdapterName = "pcnet";
+ NetworkAdapterType_T adapterType;
+ hrc = networkAdapter->COMGETTER(AdapterType)(&adapterType); H();
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ case NetworkAdapterType_Am79C973:
+ case NetworkAdapterType_Am79C960:
+ pDev = pDevPCNet;
+ break;
+#ifdef VBOX_WITH_E1000
+ case NetworkAdapterType_I82540EM:
+ case NetworkAdapterType_I82543GC:
+ case NetworkAdapterType_I82545EM:
+ pDev = pDevE1000;
+ pszAdapterName = "e1000";
+ break;
+#endif
+#ifdef VBOX_WITH_VIRTIO
+ case NetworkAdapterType_Virtio:
+ pDev = pDevVirtioNet;
+ pszAdapterName = "virtio-net";
+ break;
+#endif /* VBOX_WITH_VIRTIO */
+ case NetworkAdapterType_NE1000:
+ case NetworkAdapterType_NE2000:
+ case NetworkAdapterType_WD8003:
+ case NetworkAdapterType_WD8013:
+ case NetworkAdapterType_ELNK2:
+ pDev = pDevDP8390;
+ break;
+ case NetworkAdapterType_ELNK1:
+ pDev = pDev3C501;
+ break;
+ default:
+ AssertMsgFailed(("Invalid network adapter type '%d' for slot '%d'", adapterType, uInstance));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Invalid network adapter type '%d' for slot '%d'"), adapterType, uInstance);
+ }
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", uInstance).c_str(), &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ /* the first network card gets the PCI ID 3, the next 3 gets 8..10,
+ * next 4 get 16..19. */
+ int iPCIDeviceNo;
+ switch (uInstance)
+ {
+ case 0:
+ iPCIDeviceNo = 3;
+ break;
+ case 1: case 2: case 3:
+ iPCIDeviceNo = uInstance - 1 + 8;
+ break;
+ case 4: case 5: case 6: case 7:
+ iPCIDeviceNo = uInstance - 4 + 16;
+ break;
+ default:
+ /* auto assignment */
+ iPCIDeviceNo = -1;
+ break;
+ }
+#ifdef VMWARE_NET_IN_SLOT_11
+ /*
+ * Dirty hack for PCI slot compatibility with VMWare,
+ * it assigns slot 0x11 to the first network controller.
+ */
+ if (iPCIDeviceNo == 3 && adapterType == NetworkAdapterType_I82545EM)
+ {
+ iPCIDeviceNo = 0x11;
+ fSwapSlots3and11 = true;
+ }
+ else if (iPCIDeviceNo == 0x11 && fSwapSlots3and11)
+ iPCIDeviceNo = 3;
+#endif
+ PCIBusAddress PCIAddr = PCIBusAddress(0, iPCIDeviceNo, 0);
+ hrc = pBusMgr->assignPCIDevice(pszAdapterName, pInst, PCIAddr); H();
+
+ InsertConfigNode(pInst, "Config", &pCfg);
+#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE /* not safe here yet. */ /** @todo Make PCNet ring-0 safe on 32-bit mac kernels! */
+ if (pDev == pDevPCNet)
+ InsertConfigInteger(pCfg, "R0Enabled", false);
+#endif
+ /*
+ * Collect information needed for network booting and add it to the list.
+ */
+ BootNic nic;
+
+ nic.mInstance = uInstance;
+ /* Could be updated by reference, if auto assigned */
+ nic.mPCIAddress = PCIAddr;
+
+ hrc = networkAdapter->COMGETTER(BootPriority)(&nic.mBootPrio); H();
+
+ llBootNics.push_back(nic);
+
+ /*
+ * The virtual hardware type. PCNet supports three types, E1000 three,
+ * but VirtIO only one.
+ */
+ switch (adapterType)
+ {
+ case NetworkAdapterType_Am79C970A:
+ InsertConfigString(pCfg, "ChipType", "Am79C970A");
+ break;
+ case NetworkAdapterType_Am79C973:
+ InsertConfigString(pCfg, "ChipType", "Am79C973");
+ break;
+ case NetworkAdapterType_Am79C960:
+ InsertConfigString(pCfg, "ChipType", "Am79C960");
+ break;
+ case NetworkAdapterType_I82540EM:
+ InsertConfigInteger(pCfg, "AdapterType", 0);
+ break;
+ case NetworkAdapterType_I82543GC:
+ InsertConfigInteger(pCfg, "AdapterType", 1);
+ break;
+ case NetworkAdapterType_I82545EM:
+ InsertConfigInteger(pCfg, "AdapterType", 2);
+ break;
+ case NetworkAdapterType_Virtio:
+ break;
+ case NetworkAdapterType_NE1000:
+ InsertConfigString(pCfg, "DeviceType", "NE1000");
+ break;
+ case NetworkAdapterType_NE2000:
+ InsertConfigString(pCfg, "DeviceType", "NE2000");
+ break;
+ case NetworkAdapterType_WD8003:
+ InsertConfigString(pCfg, "DeviceType", "WD8003");
+ break;
+ case NetworkAdapterType_WD8013:
+ InsertConfigString(pCfg, "DeviceType", "WD8013");
+ break;
+ case NetworkAdapterType_ELNK2:
+ InsertConfigString(pCfg, "DeviceType", "3C503");
+ break;
+ case NetworkAdapterType_ELNK1:
+ break;
+ case NetworkAdapterType_Null: AssertFailedBreak(); /* (compiler warnings) */
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case NetworkAdapterType_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+
+ /*
+ * Get the MAC address and convert it to binary representation
+ */
+ Bstr macAddr;
+ hrc = networkAdapter->COMGETTER(MACAddress)(macAddr.asOutParam()); H();
+ Assert(!macAddr.isEmpty());
+ Utf8Str macAddrUtf8 = macAddr;
+#ifdef VBOX_WITH_CLOUD_NET
+ NetworkAttachmentType_T eAttachmentType;
+ hrc = networkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H();
+ if (eAttachmentType == NetworkAttachmentType_Cloud)
+ {
+ mGateway.setLocalMacAddress(macAddrUtf8);
+ /* We'll insert cloud MAC later, when it becomes known. */
+ }
+ else
+ {
+#endif
+ char *macStr = (char*)macAddrUtf8.c_str();
+ Assert(strlen(macStr) == 12);
+ RTMAC Mac;
+ RT_ZERO(Mac);
+ char *pMac = (char*)&Mac;
+ for (uint32_t i = 0; i < 6; ++i)
+ {
+ int c1 = *macStr++ - '0';
+ if (c1 > 9)
+ c1 -= 7;
+ int c2 = *macStr++ - '0';
+ if (c2 > 9)
+ c2 -= 7;
+ *pMac++ = (char)(((c1 & 0x0f) << 4) | (c2 & 0x0f));
+ }
+ InsertConfigBytes(pCfg, "MAC", &Mac, sizeof(Mac));
+#ifdef VBOX_WITH_CLOUD_NET
+ }
+#endif
+ /*
+ * Check if the cable is supposed to be unplugged
+ */
+ BOOL fCableConnected;
+ hrc = networkAdapter->COMGETTER(CableConnected)(&fCableConnected); H();
+ InsertConfigInteger(pCfg, "CableConnected", fCableConnected ? 1 : 0);
+
+ /*
+ * Line speed to report from custom drivers
+ */
+ ULONG ulLineSpeed;
+ hrc = networkAdapter->COMGETTER(LineSpeed)(&ulLineSpeed); H();
+ InsertConfigInteger(pCfg, "LineSpeed", ulLineSpeed);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_Network);
+
+ /*
+ * Configure the network card now
+ */
+ bool fIgnoreConnectFailure = mMachineState == MachineState_Restoring;
+ vrc = i_configNetwork(pszAdapterName,
+ uInstance,
+ 0,
+ networkAdapter,
+ pCfg,
+ pLunL0,
+ pInst,
+ false /*fAttachDetach*/,
+ fIgnoreConnectFailure,
+ pUVM,
+ pVMM);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ /*
+ * Build network boot information and transfer it to the BIOS.
+ */
+ if (pNetBootCfg && !llBootNics.empty()) /* NetBoot node doesn't exist for EFI! */
+ {
+ llBootNics.sort(); /* Sort the list by boot priority. */
+
+ char achBootIdx[] = "0";
+ unsigned uBootIdx = 0;
+
+ for (std::list<BootNic>::iterator it = llBootNics.begin(); it != llBootNics.end(); ++it)
+ {
+ /* A NIC with priority 0 is only used if it's first in the list. */
+ if (it->mBootPrio == 0 && uBootIdx != 0)
+ break;
+
+ PCFGMNODE pNetBtDevCfg;
+ achBootIdx[0] = (char)('0' + uBootIdx++); /* Boot device order. */
+ InsertConfigNode(pNetBootCfg, achBootIdx, &pNetBtDevCfg);
+ InsertConfigInteger(pNetBtDevCfg, "NIC", it->mInstance);
+ InsertConfigInteger(pNetBtDevCfg, "PCIBusNo", it->mPCIAddress.miBus);
+ InsertConfigInteger(pNetBtDevCfg, "PCIDeviceNo", it->mPCIAddress.miDevice);
+ InsertConfigInteger(pNetBtDevCfg, "PCIFunctionNo", it->mPCIAddress.miFn);
+ }
+ }
+
+ /*
+ * Serial (UART) Ports
+ */
+ /* serial enabled mask to be passed to dev ACPI */
+ uint16_t auSerialIoPortBase[SchemaDefs::SerialPortCount] = {0};
+ uint8_t auSerialIrq[SchemaDefs::SerialPortCount] = {0};
+ InsertConfigNode(pDevices, "serial", &pDev);
+ for (ULONG ulInstance = 0; ulInstance < SchemaDefs::SerialPortCount; ++ulInstance)
+ {
+ ComPtr<ISerialPort> serialPort;
+ hrc = pMachine->GetSerialPort(ulInstance, serialPort.asOutParam()); H();
+ BOOL fEnabledSerPort = FALSE;
+ if (serialPort)
+ {
+ hrc = serialPort->COMGETTER(Enabled)(&fEnabledSerPort); H();
+ }
+ if (!fEnabledSerPort)
+ {
+ m_aeSerialPortMode[ulInstance] = PortMode_Disconnected;
+ continue;
+ }
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ ULONG ulIRQ;
+ hrc = serialPort->COMGETTER(IRQ)(&ulIRQ); H();
+ InsertConfigInteger(pCfg, "IRQ", ulIRQ);
+ auSerialIrq[ulInstance] = (uint8_t)ulIRQ;
+
+ ULONG ulIOBase;
+ hrc = serialPort->COMGETTER(IOBase)(&ulIOBase); H();
+ InsertConfigInteger(pCfg, "IOBase", ulIOBase);
+ auSerialIoPortBase[ulInstance] = (uint16_t)ulIOBase;
+
+ BOOL fServer;
+ hrc = serialPort->COMGETTER(Server)(&fServer); H();
+ hrc = serialPort->COMGETTER(Path)(bstr.asOutParam()); H();
+ UartType_T eUartType;
+ const char *pszUartType;
+ hrc = serialPort->COMGETTER(UartType)(&eUartType); H();
+ switch (eUartType)
+ {
+ case UartType_U16450: pszUartType = "16450"; break;
+ case UartType_U16750: pszUartType = "16750"; break;
+ default: AssertFailed(); RT_FALL_THRU();
+ case UartType_U16550A: pszUartType = "16550A"; break;
+ }
+ InsertConfigString(pCfg, "UartType", pszUartType);
+
+ PortMode_T eHostMode;
+ hrc = serialPort->COMGETTER(HostMode)(&eHostMode); H();
+
+ m_aeSerialPortMode[ulInstance] = eHostMode;
+ if (eHostMode != PortMode_Disconnected)
+ {
+ vrc = i_configSerialPort(pInst, eHostMode, Utf8Str(bstr).c_str(), RT_BOOL(fServer));
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ }
+
+ /*
+ * Parallel (LPT) Ports
+ */
+ /* parallel enabled mask to be passed to dev ACPI */
+ uint16_t auParallelIoPortBase[SchemaDefs::ParallelPortCount] = {0};
+ uint8_t auParallelIrq[SchemaDefs::ParallelPortCount] = {0};
+ InsertConfigNode(pDevices, "parallel", &pDev);
+ for (ULONG ulInstance = 0; ulInstance < SchemaDefs::ParallelPortCount; ++ulInstance)
+ {
+ ComPtr<IParallelPort> parallelPort;
+ hrc = pMachine->GetParallelPort(ulInstance, parallelPort.asOutParam()); H();
+ BOOL fEnabledParPort = FALSE;
+ if (parallelPort)
+ {
+ hrc = parallelPort->COMGETTER(Enabled)(&fEnabledParPort); H();
+ }
+ if (!fEnabledParPort)
+ continue;
+
+ InsertConfigNode(pDev, Utf8StrFmt("%u", ulInstance).c_str(), &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+
+ ULONG ulIRQ;
+ hrc = parallelPort->COMGETTER(IRQ)(&ulIRQ); H();
+ InsertConfigInteger(pCfg, "IRQ", ulIRQ);
+ auParallelIrq[ulInstance] = (uint8_t)ulIRQ;
+ ULONG ulIOBase;
+ hrc = parallelPort->COMGETTER(IOBase)(&ulIOBase); H();
+ InsertConfigInteger(pCfg, "IOBase", ulIOBase);
+ auParallelIoPortBase[ulInstance] = (uint16_t)ulIOBase;
+
+ hrc = parallelPort->COMGETTER(Path)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "HostParallel");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "DevicePath", bstr);
+ }
+ }
+
+ /*
+ * VMM Device
+ */
+ InsertConfigNode(pDevices, "VMMDev", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice("VMMDev", pInst); H();
+
+ Bstr hwVersion;
+ hrc = pMachine->COMGETTER(HardwareVersion)(hwVersion.asOutParam()); H();
+ if (hwVersion.compare(Bstr("1").raw()) == 0) /* <= 2.0.x */
+ InsertConfigInteger(pCfg, "HeapEnabled", 0);
+ Bstr snapshotFolder;
+ hrc = pMachine->COMGETTER(SnapshotFolder)(snapshotFolder.asOutParam()); H();
+ InsertConfigString(pCfg, "GuestCoreDumpDir", snapshotFolder);
+
+ /* the VMM device's Main driver */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "HGCM");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /*
+ * Attach the status driver.
+ */
+ i_attachStatusDriver(pInst, DeviceType_SharedFolder);
+
+ /*
+ * Audio configuration.
+ */
+
+ /*
+ * AC'97 ICH / SoundBlaster16 audio / Intel HD Audio.
+ */
+ ComPtr<IAudioSettings> audioSettings;
+ hrc = pMachine->COMGETTER(AudioSettings)(audioSettings.asOutParam()); H();
+
+ BOOL fAudioEnabled = FALSE;
+ ComPtr<IAudioAdapter> audioAdapter;
+ hrc = audioSettings->COMGETTER(Adapter)(audioAdapter.asOutParam()); H();
+ if (audioAdapter)
+ {
+ hrc = audioAdapter->COMGETTER(Enabled)(&fAudioEnabled); H();
+ }
+
+ if (fAudioEnabled)
+ {
+ AudioControllerType_T enmAudioController;
+ hrc = audioAdapter->COMGETTER(AudioController)(&enmAudioController); H();
+ AudioCodecType_T enmAudioCodec;
+ hrc = audioAdapter->COMGETTER(AudioCodec)(&enmAudioCodec); H();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/TimerHz", &strTmp);
+ const uint64_t uTimerHz = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeInMs", &strTmp);
+ const uint64_t uBufSizeInMs = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Device/BufSizeOutMs", &strTmp);
+ const uint64_t uBufSizeOutMs = strTmp.toUInt64();
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp);
+ const bool fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/Level", &strTmp);
+ const uint32_t uDebugLevel = strTmp.toUInt32();
+
+ Utf8Str strDebugPathOut;
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut);
+
+#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/VaKit/Enabled", &strTmp); /* Deprecated; do not use! */
+ if (strTmp.isEmpty())
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/ValKit/Enabled", &strTmp);
+ /* Whether the Validation Kit audio backend runs as the primary backend.
+ * Can also be used with VBox release builds. */
+ const bool fValKitEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+#endif
+ /** @todo Implement an audio device class, similar to the audio backend class, to construct the common stuff
+ * without duplicating (more) code. */
+
+ const char *pszAudioDevice;
+ switch (enmAudioController)
+ {
+ case AudioControllerType_AC97:
+ {
+ /* ICH AC'97. */
+ pszAudioDevice = "ichac97";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ switch (enmAudioCodec)
+ {
+ case AudioCodecType_STAC9700:
+ InsertConfigString(pCfg, "Codec", "STAC9700");
+ break;
+ case AudioCodecType_AD1980:
+ InsertConfigString(pCfg, "Codec", "AD1980");
+ break;
+ default: AssertFailedBreak();
+ }
+ if (uTimerHz)
+ InsertConfigInteger(pCfg, "TimerHz", uTimerHz);
+ if (uBufSizeInMs)
+ InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs);
+ if (uBufSizeOutMs)
+ InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+ break;
+ }
+ case AudioControllerType_SB16:
+ {
+ /* Legacy SoundBlaster16. */
+ pszAudioDevice = "sb16";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "IRQ", 5);
+ InsertConfigInteger(pCfg, "DMA", 1);
+ InsertConfigInteger(pCfg, "DMA16", 5);
+ InsertConfigInteger(pCfg, "Port", 0x220);
+ InsertConfigInteger(pCfg, "Version", 0x0405);
+ if (uTimerHz)
+ InsertConfigInteger(pCfg, "TimerHz", uTimerHz);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+ break;
+ }
+ case AudioControllerType_HDA:
+ {
+ /* Intel HD Audio. */
+ pszAudioDevice = "hda";
+
+ InsertConfigNode(pDevices, pszAudioDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ hrc = pBusMgr->assignPCIDevice(pszAudioDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ if (uBufSizeInMs)
+ InsertConfigInteger(pCfg, "BufSizeInMs", uBufSizeInMs);
+ if (uBufSizeOutMs)
+ InsertConfigInteger(pCfg, "BufSizeOutMs", uBufSizeOutMs);
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+ if (strDebugPathOut.isNotEmpty())
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut);
+
+ /* macOS guests uses a different HDA variant to make 10.14+ (or maybe 10.13?) recognize the device. */
+ if (fOsXGuest)
+ InsertConfigString(pCfg, "DeviceName", "Intel Sunrise Point");
+ break;
+ }
+ default:
+ pszAudioDevice = "oops";
+ AssertFailedBreak();
+ }
+
+ PCFGMNODE pCfgAudioAdapter = NULL;
+ InsertConfigNode(pInst, "AudioConfig", &pCfgAudioAdapter);
+ SafeArray<BSTR> audioProps;
+ hrc = audioAdapter->COMGETTER(PropertiesList)(ComSafeArrayAsOutParam(audioProps)); H();
+
+ std::list<Utf8Str> audioPropertyNamesList;
+ for (size_t i = 0; i < audioProps.size(); ++i)
+ {
+ Bstr bstrValue;
+ audioPropertyNamesList.push_back(Utf8Str(audioProps[i]));
+ hrc = audioAdapter->GetProperty(audioProps[i], bstrValue.asOutParam());
+ Utf8Str strKey(audioProps[i]);
+ InsertConfigString(pCfgAudioAdapter, strKey.c_str(), bstrValue);
+ }
+
+ /*
+ * The audio driver.
+ */
+ const char *pszAudioDriver = NULL;
+#ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ if (fValKitEnabled)
+ {
+ pszAudioDriver = "ValidationKitAudio";
+ LogRel(("Audio: ValidationKit driver active\n"));
+ }
+#endif
+ /* If nothing else was selected before, ask the API. */
+ if (pszAudioDriver == NULL)
+ {
+ AudioDriverType_T enmAudioDriver;
+ hrc = audioAdapter->COMGETTER(AudioDriver)(&enmAudioDriver); H();
+
+ /* The "Default" audio driver needs special treatment, as we need to figure out which driver to use
+ * by default on the current platform. */
+ bool const fUseDefaultDrv = enmAudioDriver == AudioDriverType_Default;
+
+ AudioDriverType_T const enmDefaultAudioDriver = settings::MachineConfigFile::getHostDefaultAudioDriver();
+
+ if (fUseDefaultDrv)
+ {
+ enmAudioDriver = enmDefaultAudioDriver;
+ if (enmAudioDriver == AudioDriverType_Null)
+ LogRel(("Audio: Warning: No default driver detected for current platform -- defaulting to Null audio backend\n"));
+ }
+
+ switch (enmAudioDriver)
+ {
+ case AudioDriverType_Default: /* Can't happen, but handle it anyway. */
+ RT_FALL_THROUGH();
+ case AudioDriverType_Null:
+ pszAudioDriver = "NullAudio";
+ break;
+#ifdef RT_OS_WINDOWS
+# ifdef VBOX_WITH_WINMM
+ case AudioDriverType_WinMM:
+# error "Port WinMM audio backend!" /** @todo Still needed? */
+ break;
+# endif
+ case AudioDriverType_DirectSound:
+ /* Use the Windows Audio Session (WAS) API rather than Direct Sound on Windows
+ versions we've tested it on (currently W7+). Since Vista, Direct Sound has
+ been emulated on top of WAS according to the docs, so better use WAS directly.
+
+ Set extradata value "VBoxInternal2/Audio/WindowsDrv" "dsound" to no use WasAPI.
+
+ Keep this hack for backwards compatibility (introduced < 7.0).
+ */
+ GetExtraDataBoth(virtualBox, pMachine, "VBoxInternal2/Audio/WindowsDrv", &strTmp); H();
+ if ( enmDefaultAudioDriver == AudioDriverType_WAS
+ && ( strTmp.isEmpty()
+ || strTmp.equalsIgnoreCase("was")
+ || strTmp.equalsIgnoreCase("wasapi")) )
+ {
+ /* Nothing to do here, fall through to WAS driver. */
+ }
+ else
+ {
+ pszAudioDriver = "DSoundAudio";
+ break;
+ }
+ RT_FALL_THROUGH();
+ case AudioDriverType_WAS:
+ if (enmDefaultAudioDriver == AudioDriverType_WAS) /* WAS supported? */
+ pszAudioDriver = "HostAudioWas";
+ else if (enmDefaultAudioDriver == AudioDriverType_DirectSound)
+ {
+ LogRel(("Audio: Warning: Windows Audio Session (WAS) not supported, defaulting to DirectSound backend\n"));
+ pszAudioDriver = "DSoundAudio";
+ }
+ break;
+#endif /* RT_OS_WINDOWS */
+#ifdef RT_OS_SOLARIS
+ case AudioDriverType_SolAudio:
+ /* Should not happen, as the Solaris Audio backend is not around anymore.
+ * Remove this sometime later. */
+ LogRel(("Audio: Warning: Solaris Audio is deprecated, please switch to OSS!\n"));
+ LogRel(("Audio: Automatically setting host audio backend to OSS\n"));
+
+ /* Manually set backend to OSS for now. */
+ pszAudioDriver = "OSSAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_OSS
+ case AudioDriverType_OSS:
+ pszAudioDriver = "OSSAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_ALSA
+ case AudioDriverType_ALSA:
+ pszAudioDriver = "ALSAAudio";
+ break;
+#endif
+#ifdef VBOX_WITH_AUDIO_PULSE
+ case AudioDriverType_Pulse:
+ pszAudioDriver = "PulseAudio";
+ break;
+#endif
+#ifdef RT_OS_DARWIN
+ case AudioDriverType_CoreAudio:
+ pszAudioDriver = "CoreAudio";
+ break;
+#endif
+ default:
+ pszAudioDriver = "oops";
+ AssertFailedBreak();
+ }
+
+ if (fUseDefaultDrv)
+ LogRel(("Audio: Detected default audio driver type is '%s'\n", pszAudioDriver));
+ }
+
+ BOOL fAudioEnabledIn = FALSE;
+ hrc = audioAdapter->COMGETTER(EnabledIn)(&fAudioEnabledIn); H();
+ BOOL fAudioEnabledOut = FALSE;
+ hrc = audioAdapter->COMGETTER(EnabledOut)(&fAudioEnabledOut); H();
+
+ unsigned idxAudioLun = 0;
+
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, pszAudioDriver, !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ /* Insert dummy audio driver to have the LUN configured. */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ InsertConfigString(pLunL0, "Driver", "AUDIO");
+ {
+ AudioDriverCfg DrvCfgVRDE(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVRDE",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ vrc = mAudioVRDE->InitializeConfig(&DrvCfgVRDE);
+ AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "mAudioVRDE->InitializeConfig failed"));
+ }
+ idxAudioLun++;
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Insert dummy audio driver to have the LUN configured. */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ InsertConfigString(pLunL0, "Driver", "AUDIO");
+ {
+ AudioDriverCfg DrvCfgVideoRec(pszAudioDevice, 0 /* Instance */, idxAudioLun, "AudioVideoRec",
+ false /*a_fEnabledIn*/, true /*a_fEnabledOut*/);
+ vrc = mRecording.mAudioRec->InitializeConfig(&DrvCfgVideoRec);
+ AssertRCStmt(vrc, throw ConfigError(__FUNCTION__, vrc, "Recording.mAudioRec->InitializeConfig failed"));
+ }
+ idxAudioLun++;
+#endif
+
+ if (fDebugEnabled)
+ {
+#ifdef VBOX_WITH_AUDIO_DEBUG
+# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ /*
+ * When both, ValidationKit and Debug mode (for audio) are enabled,
+ * skip configuring the Debug audio driver, as both modes can
+ * mess with the audio data and would lead to side effects.
+ *
+ * The ValidationKit audio driver has precedence over the Debug audio driver.
+ *
+ * This also can (and will) be used in VBox release builds.
+ */
+ if (fValKitEnabled)
+ {
+ LogRel(("Audio: Warning: ValidationKit running and Debug mode enabled -- disabling Debug driver\n"));
+ }
+ else /* Debug mode active -- run both (nice for catching errors / doing development). */
+ {
+ /*
+ * The ValidationKit backend.
+ */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, "ValidationKitAudio",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */
+ /*
+ * The Debug audio backend.
+ */
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", idxAudioLun);
+ i_configAudioDriver(virtualBox, pMachine, pLunL0, "DebugAudio",
+ !!fAudioEnabledIn, !!fAudioEnabledOut);
+ idxAudioLun++;
+# ifdef VBOX_WITH_AUDIO_VALIDATIONKIT
+ }
+# endif /* VBOX_WITH_AUDIO_VALIDATIONKIT */
+#endif /* VBOX_WITH_AUDIO_DEBUG */
+
+ /*
+ * Tweak the logging groups.
+ */
+ Utf8Str strGroups("drv_audio.e.l.l2.l3.f"
+ " audio_mixer.e.l.l2.l3.f"
+ " dev_hda_codec.e.l.l2.l3.f"
+ " dev_hda.e.l.l2.l3.f"
+ " dev_ac97.e.l.l2.l3.f"
+ " dev_sb16.e.l.l2.l3.f");
+
+ LogRel(("Audio: Debug level set to %RU32\n", uDebugLevel));
+
+ switch (uDebugLevel)
+ {
+ case 0:
+ strGroups += " drv_host_audio.e.l.l2.l3.f";
+ break;
+ case 1:
+ RT_FALL_THROUGH();
+ case 2:
+ RT_FALL_THROUGH();
+ case 3:
+ strGroups += " drv_host_audio.e.l.l2.l3.f+audio_test.e.l.l2.l3.f";
+ break;
+ case 4:
+ RT_FALL_THROUGH();
+ default:
+ strGroups += " drv_host_audio.e.l.l2.l3.l4.f+audio_test.e.l.l2.l3.l4.f";
+ break;
+ }
+
+ vrc = RTLogGroupSettings(RTLogRelGetDefaultInstance(), strGroups.c_str());
+ if (RT_FAILURE(vrc))
+ LogRel(("Audio: Setting debug logging failed, vrc=%Rrc\n", vrc));
+ }
+ }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ /*
+ * Shared Clipboard.
+ */
+ {
+ ClipboardMode_T enmClipboardMode = ClipboardMode_Disabled;
+ hrc = pMachine->COMGETTER(ClipboardMode)(&enmClipboardMode); H();
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ BOOL fFileTransfersEnabled;
+ hrc = pMachine->COMGETTER(ClipboardFileTransfersEnabled)(&fFileTransfersEnabled); H();
+#endif
+
+ /* Load the service */
+ vrc = pVMMDev->hgcmLoadService("VBoxSharedClipboard", "VBoxSharedClipboard");
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Shared Clipboard: Service loaded\n"));
+
+ /* Set initial clipboard mode. */
+ vrc = i_changeClipboardMode(enmClipboardMode);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial clipboard mode (%d): vrc=%Rrc\n",
+ enmClipboardMode, vrc));
+
+ /* Setup the service. */
+ VBOXHGCMSVCPARM parm;
+ HGCMSvcSetU32(&parm, !i_useHostClipboard());
+ vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &parm);
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial headless mode (%RTbool): vrc=%Rrc\n",
+ !i_useHostClipboard(), vrc));
+
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ vrc = i_changeClipboardFileTransferMode(RT_BOOL(fFileTransfersEnabled));
+ AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set initial file transfers mode (%u): vrc=%Rrc\n",
+ fFileTransfersEnabled, vrc));
+
+ /** @todo Register area callbacks? (See also deregistration todo in Console::i_powerDown.) */
+# endif
+ }
+ else
+ LogRel(("Shared Clipboard: Not available, vrc=%Rrc\n", vrc));
+ vrc = VINF_SUCCESS; /* None of the potential failures above are fatal. */
+ }
+#endif /* VBOX_WITH_SHARED_CLIPBOARD */
+
+ /*
+ * HGCM HostChannel.
+ */
+ {
+ Bstr value;
+ hrc = pMachine->GetExtraData(Bstr("HGCM/HostChannel").raw(),
+ value.asOutParam());
+
+ if ( hrc == S_OK
+ && value == "1")
+ {
+ vrc = pVMMDev->hgcmLoadService("VBoxHostChannel", "VBoxHostChannel");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VBoxHostChannel is not available, vrc=%Rrc\n", vrc));
+ /* That is not a fatal failure. */
+ vrc = VINF_SUCCESS;
+ }
+ }
+ }
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ /*
+ * Drag and Drop.
+ */
+ {
+ DnDMode_T enmMode = DnDMode_Disabled;
+ hrc = pMachine->COMGETTER(DnDMode)(&enmMode); H();
+
+ /* Load the service */
+ vrc = pVMMDev->hgcmLoadService("VBoxDragAndDropSvc", "VBoxDragAndDropSvc");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Drag and drop service is not available, vrc=%Rrc\n", vrc));
+ /* That is not a fatal failure. */
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = HGCMHostRegisterServiceExtension(&m_hHgcmSvcExtDragAndDrop, "VBoxDragAndDropSvc",
+ &GuestDnD::notifyDnDDispatcher,
+ GuestDnDInst());
+ if (RT_FAILURE(vrc))
+ Log(("Cannot register VBoxDragAndDropSvc extension, vrc=%Rrc\n", vrc));
+ else
+ {
+ LogRel(("Drag and drop service loaded\n"));
+ vrc = i_changeDnDMode(enmMode);
+ }
+ }
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+
+#if defined(VBOX_WITH_TPM)
+ /*
+ * Configure the Trusted Platform Module.
+ */
+ ComObjPtr<ITrustedPlatformModule> ptrTpm;
+ TpmType_T enmTpmType = TpmType_None;
+
+ hrc = pMachine->COMGETTER(TrustedPlatformModule)(ptrTpm.asOutParam()); H();
+ hrc = ptrTpm->COMGETTER(Type)(&enmTpmType); H();
+ if (enmTpmType != TpmType_None)
+ {
+ InsertConfigNode(pDevices, "tpm", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+
+ switch (enmTpmType)
+ {
+ case TpmType_v1_2:
+ case TpmType_v2_0:
+ InsertConfigString(pLunL0, "Driver", "TpmEmuTpms");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "TpmVersion", enmTpmType == TpmType_v1_2 ? 1 : 2);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "NvramStore");
+ break;
+ case TpmType_Host:
+#if defined(RT_OS_LINUX) || defined(RT_OS_WINDOWS)
+ InsertConfigString(pLunL0, "Driver", "TpmHost");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+#endif
+ break;
+ case TpmType_Swtpm:
+ hrc = ptrTpm->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pLunL0, "Driver", "TpmEmu");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Location", bstr);
+ break;
+ default:
+ AssertFailedBreak();
+ }
+ }
+#endif
+
+ /*
+ * ACPI
+ */
+ BOOL fACPI;
+ hrc = biosSettings->COMGETTER(ACPIEnabled)(&fACPI); H();
+ if (fACPI)
+ {
+ /* Always show the CPU leafs when we have multiple VCPUs or when the IO-APIC is enabled.
+ * The Windows SMP kernel needs a CPU leaf or else its idle loop will burn cpu cycles; the
+ * intelppm driver refuses to register an idle state handler.
+ * Always show CPU leafs for OS X guests. */
+ BOOL fShowCpu = fOsXGuest;
+ if (cCpus > 1 || fIOAPIC)
+ fShowCpu = true;
+
+ BOOL fCpuHotPlug;
+ hrc = pMachine->COMGETTER(CPUHotPlugEnabled)(&fCpuHotPlug); H();
+
+ InsertConfigNode(pDevices, "acpi", &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+ InsertConfigNode(pInst, "Config", &pCfg);
+ hrc = pBusMgr->assignPCIDevice("acpi", pInst); H();
+
+ InsertConfigInteger(pCfg, "NumCPUs", cCpus);
+
+ InsertConfigInteger(pCfg, "IOAPIC", fIOAPIC);
+ InsertConfigInteger(pCfg, "FdcEnabled", fFdcEnabled);
+ InsertConfigInteger(pCfg, "HpetEnabled", fHPETEnabled);
+ InsertConfigInteger(pCfg, "SmcEnabled", fSmcEnabled);
+ InsertConfigInteger(pCfg, "ShowRtc", fShowRtc);
+ if (fOsXGuest && !llBootNics.empty())
+ {
+ BootNic aNic = llBootNics.front();
+ uint32_t u32NicPCIAddr = (aNic.mPCIAddress.miDevice << 16) | aNic.mPCIAddress.miFn;
+ InsertConfigInteger(pCfg, "NicPciAddress", u32NicPCIAddr);
+ }
+ if (fOsXGuest && fAudioEnabled)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("hda", 0, Address))
+ {
+ uint32_t u32AudioPCIAddr = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "AudioPciAddress", u32AudioPCIAddr);
+ }
+ }
+ if (fOsXGuest)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("nvme", 0, Address))
+ {
+ uint32_t u32NvmePCIAddr = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "NvmePciAddress", u32NvmePCIAddr);
+ }
+ }
+ if (enmIommuType == IommuType_AMD)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-amd", 0, Address))
+ {
+ uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "IommuAmdEnabled", true);
+ InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress);
+ if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address))
+ {
+ uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("AMD IOMMU is enabled, but the I/O APIC is not assigned a PCI address!"));
+ }
+ }
+ else if (enmIommuType == IommuType_Intel)
+ {
+ PCIBusAddress Address;
+ if (pBusMgr->findPCIAddress("iommu-intel", 0, Address))
+ {
+ uint32_t u32IommuAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "IommuIntelEnabled", true);
+ InsertConfigInteger(pCfg, "IommuPciAddress", u32IommuAddress);
+ if (pBusMgr->findPCIAddress("sb-ioapic", 0, Address))
+ {
+ uint32_t const u32SbIoapicAddress = (Address.miDevice << 16) | Address.miFn;
+ InsertConfigInteger(pCfg, "SbIoApicPciAddress", u32SbIoapicAddress);
+ }
+ else
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Intel IOMMU is enabled, but the I/O APIC is not assigned a PCI address!"));
+ }
+ }
+
+ InsertConfigInteger(pCfg, "IocPciAddress", uIocPCIAddress);
+ if (chipsetType == ChipsetType_ICH9)
+ {
+ InsertConfigInteger(pCfg, "McfgBase", uMcfgBase);
+ InsertConfigInteger(pCfg, "McfgLength", cbMcfgLength);
+ /* 64-bit prefetch window root resource: Only for ICH9 and if PAE or Long Mode is enabled (@bugref{5454}). */
+ if (fIsGuest64Bit || fEnablePAE)
+ InsertConfigInteger(pCfg, "PciPref64Enabled", 1);
+ }
+ InsertConfigInteger(pCfg, "HostBusPciAddress", uHbcPCIAddress);
+ InsertConfigInteger(pCfg, "ShowCpu", fShowCpu);
+ InsertConfigInteger(pCfg, "CpuHotPlug", fCpuHotPlug);
+
+ InsertConfigInteger(pCfg, "Serial0IoPortBase", auSerialIoPortBase[0]);
+ InsertConfigInteger(pCfg, "Serial0Irq", auSerialIrq[0]);
+
+ InsertConfigInteger(pCfg, "Serial1IoPortBase", auSerialIoPortBase[1]);
+ InsertConfigInteger(pCfg, "Serial1Irq", auSerialIrq[1]);
+
+ if (auSerialIoPortBase[2])
+ {
+ InsertConfigInteger(pCfg, "Serial2IoPortBase", auSerialIoPortBase[2]);
+ InsertConfigInteger(pCfg, "Serial2Irq", auSerialIrq[2]);
+ }
+
+ if (auSerialIoPortBase[3])
+ {
+ InsertConfigInteger(pCfg, "Serial3IoPortBase", auSerialIoPortBase[3]);
+ InsertConfigInteger(pCfg, "Serial3Irq", auSerialIrq[3]);
+ }
+
+ InsertConfigInteger(pCfg, "Parallel0IoPortBase", auParallelIoPortBase[0]);
+ InsertConfigInteger(pCfg, "Parallel0Irq", auParallelIrq[0]);
+
+ InsertConfigInteger(pCfg, "Parallel1IoPortBase", auParallelIoPortBase[1]);
+ InsertConfigInteger(pCfg, "Parallel1Irq", auParallelIrq[1]);
+
+#if defined(VBOX_WITH_TPM)
+ switch (enmTpmType)
+ {
+ case TpmType_v1_2:
+ InsertConfigString(pCfg, "TpmMode", "tis1.2");
+ break;
+ case TpmType_v2_0:
+ InsertConfigString(pCfg, "TpmMode", "fifo2.0");
+ break;
+ /** @todo Host and swtpm. */
+ default:
+ break;
+ }
+#endif
+
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "ACPIHost");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /* Attach the dummy CPU drivers */
+ for (ULONG iCpuCurr = 1; iCpuCurr < cCpus; iCpuCurr++)
+ {
+ BOOL fCpuAttached = true;
+
+ if (fCpuHotPlug)
+ {
+ hrc = pMachine->GetCPUStatus(iCpuCurr, &fCpuAttached); H();
+ }
+
+ if (fCpuAttached)
+ {
+ InsertConfigNode(pInst, Utf8StrFmt("LUN#%u", iCpuCurr).c_str(), &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "ACPICpu");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ }
+ }
+ }
+
+ /*
+ * Configure DBGF (Debug(ger) Facility) and DBGC (Debugger Console).
+ */
+ {
+ PCFGMNODE pDbgf;
+ InsertConfigNode(pRoot, "DBGF", &pDbgf);
+
+ /* Paths to search for debug info and such things. */
+ hrc = pMachine->COMGETTER(SettingsFilePath)(bstr.asOutParam()); H();
+ Utf8Str strSettingsPath(bstr);
+ bstr.setNull();
+ strSettingsPath.stripFilename();
+ strSettingsPath.append("/");
+
+ char szHomeDir[RTPATH_MAX + 1];
+ int vrc2 = RTPathUserHome(szHomeDir, sizeof(szHomeDir) - 1);
+ if (RT_FAILURE(vrc2))
+ szHomeDir[0] = '\0';
+ RTPathEnsureTrailingSeparator(szHomeDir, sizeof(szHomeDir));
+
+
+ Utf8Str strPath;
+ strPath.append(strSettingsPath).append("debug/;");
+ strPath.append(strSettingsPath).append(";");
+ strPath.append("cache*").append(strSettingsPath).append("dbgcache/;"); /* handy for symlinking to actual cache */
+ strPath.append(szHomeDir);
+
+ InsertConfigString(pDbgf, "Path", strPath.c_str());
+
+ /* Tracing configuration. */
+ BOOL fTracingEnabled;
+ hrc = pMachine->COMGETTER(TracingEnabled)(&fTracingEnabled); H();
+ if (fTracingEnabled)
+ InsertConfigInteger(pDbgf, "TracingEnabled", 1);
+
+ hrc = pMachine->COMGETTER(TracingConfig)(bstr.asOutParam()); H();
+ if (fTracingEnabled)
+ InsertConfigString(pDbgf, "TracingConfig", bstr);
+
+ BOOL fAllowTracingToAccessVM;
+ hrc = pMachine->COMGETTER(AllowTracingToAccessVM)(&fAllowTracingToAccessVM); H();
+ if (fAllowTracingToAccessVM)
+ InsertConfigInteger(pPDM, "AllowTracingToAccessVM", 1);
+
+ /* Debugger console config. */
+ PCFGMNODE pDbgc;
+ InsertConfigNode(pRoot, "DBGC", &pDbgc);
+
+ hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H();
+ Utf8Str strVBoxHome = bstr;
+ bstr.setNull();
+ if (strVBoxHome.isNotEmpty())
+ strVBoxHome.append("/");
+ else
+ {
+ strVBoxHome = szHomeDir;
+ strVBoxHome.append("/.vbox");
+ }
+
+ Utf8Str strFile(strVBoxHome);
+ strFile.append("dbgc-history");
+ InsertConfigString(pDbgc, "HistoryFile", strFile);
+
+ strFile = strSettingsPath;
+ strFile.append("dbgc-init");
+ InsertConfigString(pDbgc, "LocalInitScript", strFile);
+
+ strFile = strVBoxHome;
+ strFile.append("dbgc-init");
+ InsertConfigString(pDbgc, "GlobalInitScript", strFile);
+
+ /*
+ * Configure guest debug settings.
+ */
+ ComObjPtr<IGuestDebugControl> ptrGstDbgCtrl;
+ GuestDebugProvider_T enmGstDbgProvider = GuestDebugProvider_None;
+
+ hrc = pMachine->COMGETTER(GuestDebugControl)(ptrGstDbgCtrl.asOutParam()); H();
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugProvider)(&enmGstDbgProvider); H();
+ if (enmGstDbgProvider != GuestDebugProvider_None)
+ {
+ GuestDebugIoProvider_T enmGstDbgIoProvider = GuestDebugIoProvider_None;
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugIoProvider)(&enmGstDbgIoProvider); H();
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugAddress)(bstr.asOutParam()); H();
+ Utf8Str strAddress = bstr;
+ bstr.setNull();
+
+ ULONG ulPort = 0;
+ hrc = ptrGstDbgCtrl->COMGETTER(DebugPort)(&ulPort); H();
+
+ PCFGMNODE pDbgSettings;
+ InsertConfigNode(pDbgc, "Dbg", &pDbgSettings);
+ InsertConfigString(pDbgSettings, "Address", strAddress);
+ InsertConfigInteger(pDbgSettings, "Port", ulPort);
+
+ switch (enmGstDbgProvider)
+ {
+ case GuestDebugProvider_Native:
+ InsertConfigString(pDbgSettings, "StubType", "Native");
+ break;
+ case GuestDebugProvider_GDB:
+ InsertConfigString(pDbgSettings, "StubType", "Gdb");
+ break;
+ case GuestDebugProvider_KD:
+ InsertConfigString(pDbgSettings, "StubType", "Kd");
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+
+ switch (enmGstDbgIoProvider)
+ {
+ case GuestDebugIoProvider_TCP:
+ InsertConfigString(pDbgSettings, "Provider", "tcp");
+ break;
+ case GuestDebugIoProvider_UDP:
+ InsertConfigString(pDbgSettings, "Provider", "udp");
+ break;
+ case GuestDebugIoProvider_IPC:
+ InsertConfigString(pDbgSettings, "Provider", "ipc");
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ }
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ pVMM->pfnVMR3SetError(pUVM, x.m_vrc, RT_SRC_POS, "Caught ConfigError: %Rrc - %s", x.m_vrc, x.what());
+ return x.m_vrc;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ AssertLogRelMsgFailedReturn(("hrc=%Rhrc\n", hrcXcpt), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+
+#ifdef VBOX_WITH_EXTPACK
+ /*
+ * Call the extension pack hooks if everything went well thus far.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ pAlock->release();
+ vrc = mptrExtPackManager->i_callAllVmConfigureVmmHooks(this, pVM, pVMM);
+ pAlock->acquire();
+ }
+#endif
+
+ /*
+ * Apply the CFGM overlay.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_configCfgmOverlay(pRoot, virtualBox, pMachine);
+
+ /*
+ * Dump all extradata API settings tweaks, both global and per VM.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_configDumpAPISettingsTweaks(virtualBox, pMachine);
+
+#undef H
+
+ pAlock->release(); /* Avoid triggering the lock order inversion check. */
+
+ /*
+ * Register VM state change handler.
+ */
+ int vrc2 = pVMM->pfnVMR3AtStateRegister(pUVM, Console::i_vmstateChangeCallback, this);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ /*
+ * Register VM runtime error handler.
+ */
+ vrc2 = pVMM->pfnVMR3AtRuntimeErrorRegister(pUVM, Console::i_atVMRuntimeErrorCallback, this);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ pAlock->acquire();
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ LogFlowFuncLeave();
+
+ return vrc;
+}
+
+/**
+ * Configures an audio driver via CFGM by getting (optional) values from extra data.
+ *
+ * @param pVirtualBox Pointer to IVirtualBox instance.
+ * @param pMachine Pointer to IMachine instance.
+ * @param pLUN Pointer to CFGM node of LUN (the driver) to configure.
+ * @param pszDrvName Name of the driver to configure.
+ * @param fAudioEnabledIn IAudioAdapter::enabledIn value.
+ * @param fAudioEnabledOut IAudioAdapter::enabledOut value.
+ *
+ * @throws ConfigError or HRESULT on if there is trouble.
+ */
+void Console::i_configAudioDriver(IVirtualBox *pVirtualBox, IMachine *pMachine, PCFGMNODE pLUN, const char *pszDrvName,
+ bool fAudioEnabledIn, bool fAudioEnabledOut)
+{
+#define H() AssertLogRelMsgStmt(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), \
+ throw ConfigError(__FUNCTION__, VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR, "line: " RT_XSTR(__LINE__)))
+
+ InsertConfigString(pLUN, "Driver", "AUDIO");
+
+ PCFGMNODE pCfg;
+ InsertConfigNode(pLUN, "Config", &pCfg);
+ InsertConfigString(pCfg, "DriverName", pszDrvName);
+ InsertConfigInteger(pCfg, "InputEnabled", fAudioEnabledIn);
+ InsertConfigInteger(pCfg, "OutputEnabled", fAudioEnabledOut);
+
+ Utf8Str strTmp;
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/Enabled", &strTmp);
+ const uint64_t fDebugEnabled = strTmp.equalsIgnoreCase("true") || strTmp.equalsIgnoreCase("1");
+ if (fDebugEnabled)
+ {
+ InsertConfigInteger(pCfg, "DebugEnabled", fDebugEnabled);
+
+ Utf8Str strDebugPathOut;
+ GetExtraDataBoth(pVirtualBox, pMachine, "VBoxInternal2/Audio/Debug/PathOut", &strDebugPathOut);
+ InsertConfigString(pCfg, "DebugPathOut", strDebugPathOut.c_str());
+ }
+
+ /*
+ * PCM input parameters (playback + recording).
+ * We have host driver specific ones as: VBoxInternal2/Audio/<DrvName>/<Value>
+ * And global ones for all host drivers: VBoxInternal2/Audio/<Value>
+ */
+ for (unsigned iDir = 0; iDir < 2; iDir++)
+ {
+ static const struct
+ {
+ const char *pszExtraName;
+ const char *pszCfgmName;
+ } s_aToCopy[] =
+ { /* PCM parameters: */
+ { "PCMSampleBit", "PCMSampleBit" },
+ { "PCMSampleHz", "PCMSampleHz" },
+ { "PCMSampleSigned", "PCMSampleSigned" },
+ { "PCMSampleSwapEndian", "PCMSampleSwapEndian" },
+ { "PCMSampleChannels", "PCMSampleChannels" },
+ /* Buffering stuff: */
+ { "PeriodSizeMs", "PeriodSizeMs" },
+ { "BufferSizeMs", "BufferSizeMs" },
+ { "PreBufferSizeMs", "PreBufferSizeMs" },
+ };
+
+ PCFGMNODE pDirNode = NULL;
+ const char *pszDir = iDir == 0 ? "In" : "Out";
+ for (size_t i = 0; i < RT_ELEMENTS(s_aToCopy); i++)
+ {
+ char szExtra[128];
+ RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s/%s%s", pszDrvName, s_aToCopy[i].pszExtraName, pszDir);
+ GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp); /* throws hrc */
+ if (strTmp.isEmpty())
+ {
+ RTStrPrintf(szExtra, sizeof(szExtra), "VBoxInternal2/Audio/%s%s", s_aToCopy[i].pszExtraName, pszDir);
+ GetExtraDataBoth(pVirtualBox, pMachine, szExtra, &strTmp);
+ if (strTmp.isEmpty())
+ continue;
+ }
+
+ uint32_t uValue;
+ int vrc = RTStrToUInt32Full(strTmp.c_str(), 0, &uValue);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!pDirNode)
+ InsertConfigNode(pCfg, pszDir, &pDirNode);
+ InsertConfigInteger(pDirNode, s_aToCopy[i].pszCfgmName, uValue);
+ }
+ else
+ LogRel(("Ignoring malformed 32-bit unsigned integer config value '%s' = '%s': %Rrc\n", szExtra, strTmp.c_str(), vrc));
+ }
+ }
+
+ PCFGMNODE pLunL1;
+ InsertConfigNode(pLUN, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", pszDrvName);
+ InsertConfigNode(pLunL1, "Config", &pCfg);
+
+#ifdef RT_OS_WINDOWS
+ if (strcmp(pszDrvName, "HostAudioWas") == 0)
+ {
+ Bstr bstrTmp;
+ HRESULT hrc = pMachine->COMGETTER(Id)(bstrTmp.asOutParam()); H();
+ InsertConfigString(pCfg, "VmUuid", bstrTmp);
+ }
+#endif
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX)
+ if ( strcmp(pszDrvName, "HostAudioWas") == 0
+ || strcmp(pszDrvName, "PulseAudio") == 0)
+ {
+ Bstr bstrTmp;
+ HRESULT hrc = pMachine->COMGETTER(Name)(bstrTmp.asOutParam()); H();
+ InsertConfigString(pCfg, "VmName", bstrTmp);
+ }
+#endif
+
+ LogFlowFunc(("szDrivName=%s\n", pszDrvName));
+
+#undef H
+}
+
+/**
+ * Applies the CFGM overlay as specified by VBoxInternal/XXX extra data
+ * values.
+ *
+ * @returns VBox status code.
+ * @param pRoot The root of the configuration tree.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ */
+/* static */
+int Console::i_configCfgmOverlay(PCFGMNODE pRoot, IVirtualBox *pVirtualBox, IMachine *pMachine)
+{
+ /*
+ * CFGM overlay handling.
+ *
+ * Here we check the extra data entries for CFGM values
+ * and create the nodes and insert the values on the fly. Existing
+ * values will be removed and reinserted. CFGM is typed, so by default
+ * we will guess whether it's a string or an integer (byte arrays are
+ * not currently supported). It's possible to override this autodetection
+ * by adding "string:", "integer:" or "bytes:" (future).
+ *
+ * We first perform a run on global extra data, then on the machine
+ * extra data to support global settings with local overrides.
+ */
+ int vrc = VINF_SUCCESS;
+ bool fFirst = true;
+ try
+ {
+ /** @todo add support for removing nodes and byte blobs. */
+ /*
+ * Get the next key
+ */
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ SafeArray<BSTR> aMachineExtraDataKeys;
+ HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+
+ // remember the no. of global values so we can call the correct method below
+ size_t cGlobalValues = aGlobalExtraDataKeys.size();
+
+ hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc));
+
+ // build a combined list from global keys...
+ std::list<Utf8Str> llExtraDataKeys;
+
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); ++i)
+ llExtraDataKeys.push_back(Utf8Str(aGlobalExtraDataKeys[i]));
+ // ... and machine keys
+ for (size_t i = 0; i < aMachineExtraDataKeys.size(); ++i)
+ llExtraDataKeys.push_back(Utf8Str(aMachineExtraDataKeys[i]));
+
+ size_t i2 = 0;
+ for (std::list<Utf8Str>::const_iterator it = llExtraDataKeys.begin();
+ it != llExtraDataKeys.end();
+ ++it, ++i2)
+ {
+ const Utf8Str &strKey = *it;
+
+ /*
+ * We only care about keys starting with "VBoxInternal/" (skip "G:" or "M:")
+ */
+ if (!strKey.startsWith("VBoxInternal/"))
+ continue;
+
+ const char *pszExtraDataKey = strKey.c_str() + sizeof("VBoxInternal/") - 1;
+
+ // get the value
+ Bstr bstrExtraDataValue;
+ if (i2 < cGlobalValues)
+ // this is still one of the global values:
+ hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam());
+ else
+ hrc = pMachine->GetExtraData(Bstr(strKey).raw(), bstrExtraDataValue.asOutParam());
+ if (FAILED(hrc))
+ LogRel(("Warning: Cannot get extra data key %s, hrc = %Rhrc\n", strKey.c_str(), hrc));
+
+ if (fFirst)
+ {
+ fFirst = false;
+ LogRel(("Extradata overrides:\n"));
+ }
+ LogRel((" %s=\"%ls\"%s\n", strKey.c_str(), bstrExtraDataValue.raw(), i2 < cGlobalValues ? " (global)" : ""));
+
+ /*
+ * The key will be in the format "Node1/Node2/Value" or simply "Value".
+ * Split the two and get the node, delete the value and create the node
+ * if necessary.
+ */
+ PCFGMNODE pNode;
+ const char *pszCFGMValueName = strrchr(pszExtraDataKey, '/');
+ if (pszCFGMValueName)
+ {
+ /* terminate the node and advance to the value (Utf8Str might not
+ offically like this but wtf) */
+ *(char *)pszCFGMValueName = '\0';
+ ++pszCFGMValueName;
+
+ /* does the node already exist? */
+ pNode = mpVMM->pfnCFGMR3GetChild(pRoot, pszExtraDataKey);
+ if (pNode)
+ mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName);
+ else
+ {
+ /* create the node */
+ vrc = mpVMM->pfnCFGMR3InsertNode(pRoot, pszExtraDataKey, &pNode);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsgRC(vrc, ("failed to insert node '%s'\n", pszExtraDataKey));
+ continue;
+ }
+ Assert(pNode);
+ }
+ }
+ else
+ {
+ /* root value (no node path). */
+ pNode = pRoot;
+ pszCFGMValueName = pszExtraDataKey;
+ pszExtraDataKey--;
+ mpVMM->pfnCFGMR3RemoveValue(pNode, pszCFGMValueName);
+ }
+
+ /*
+ * Now let's have a look at the value.
+ * Empty strings means that we should remove the value, which we've
+ * already done above.
+ */
+ Utf8Str strCFGMValueUtf8(bstrExtraDataValue);
+ if (strCFGMValueUtf8.isNotEmpty())
+ {
+ uint64_t u64Value;
+
+ /* check for type prefix first. */
+ if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("string:")))
+ vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str() + sizeof("string:") - 1);
+ else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("integer:")))
+ {
+ vrc = RTStrToUInt64Full(strCFGMValueUtf8.c_str() + sizeof("integer:") - 1, 0, &u64Value);
+ if (RT_SUCCESS(vrc))
+ vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value);
+ }
+ else if (!strncmp(strCFGMValueUtf8.c_str(), RT_STR_TUPLE("bytes:")))
+ {
+ char const *pszBase64 = strCFGMValueUtf8.c_str() + sizeof("bytes:") - 1;
+ ssize_t cbValue = RTBase64DecodedSize(pszBase64, NULL);
+ if (cbValue > 0)
+ {
+ void *pvBytes = RTMemTmpAlloc(cbValue);
+ if (pvBytes)
+ {
+ vrc = RTBase64Decode(pszBase64, pvBytes, cbValue, NULL, NULL);
+ if (RT_SUCCESS(vrc))
+ vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, pvBytes, cbValue);
+ RTMemTmpFree(pvBytes);
+ }
+ else
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ else if (cbValue == 0)
+ vrc = mpVMM->pfnCFGMR3InsertBytes(pNode, pszCFGMValueName, NULL, 0);
+ else
+ vrc = VERR_INVALID_BASE64_ENCODING;
+ }
+ /* auto detect type. */
+ else if (RT_SUCCESS(RTStrToUInt64Full(strCFGMValueUtf8.c_str(), 0, &u64Value)))
+ vrc = mpVMM->pfnCFGMR3InsertInteger(pNode, pszCFGMValueName, u64Value);
+ else
+ vrc = mpVMM->pfnCFGMR3InsertString(pNode, pszCFGMValueName, strCFGMValueUtf8.c_str());
+ AssertLogRelMsgRCBreak(vrc, ("failed to insert CFGM value '%s' to key '%s'\n",
+ strCFGMValueUtf8.c_str(), pszExtraDataKey));
+ }
+ }
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+ return vrc;
+}
+
+/**
+ * Dumps the API settings tweaks as specified by VBoxInternal2/XXX extra data
+ * values.
+ *
+ * @returns VBox status code.
+ * @param pVirtualBox Pointer to the IVirtualBox interface.
+ * @param pMachine Pointer to the IMachine interface.
+ */
+/* static */
+int Console::i_configDumpAPISettingsTweaks(IVirtualBox *pVirtualBox, IMachine *pMachine)
+{
+ {
+ SafeArray<BSTR> aGlobalExtraDataKeys;
+ HRESULT hrc = pVirtualBox->GetExtraDataKeys(ComSafeArrayAsOutParam(aGlobalExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("VirtualBox::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ bool hasKey = false;
+ for (size_t i = 0; i < aGlobalExtraDataKeys.size(); i++)
+ {
+ Utf8Str strKey(aGlobalExtraDataKeys[i]);
+ if (!strKey.startsWith("VBoxInternal2/"))
+ continue;
+
+ Bstr bstrValue;
+ hrc = pVirtualBox->GetExtraData(Bstr(strKey).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ continue;
+ if (!hasKey)
+ LogRel(("Global extradata API settings:\n"));
+ LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw()));
+ hasKey = true;
+ }
+ }
+
+ {
+ SafeArray<BSTR> aMachineExtraDataKeys;
+ HRESULT hrc = pMachine->GetExtraDataKeys(ComSafeArrayAsOutParam(aMachineExtraDataKeys));
+ AssertMsg(SUCCEEDED(hrc), ("Machine::GetExtraDataKeys failed with %Rhrc\n", hrc));
+ bool hasKey = false;
+ for (size_t i = 0; i < aMachineExtraDataKeys.size(); i++)
+ {
+ Utf8Str strKey(aMachineExtraDataKeys[i]);
+ if (!strKey.startsWith("VBoxInternal2/"))
+ continue;
+
+ Bstr bstrValue;
+ hrc = pMachine->GetExtraData(Bstr(strKey).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ continue;
+ if (!hasKey)
+ LogRel(("Per-VM extradata API settings:\n"));
+ LogRel((" %s=\"%ls\"\n", strKey.c_str(), bstrValue.raw()));
+ hasKey = true;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+int Console::i_configGraphicsController(PCFGMNODE pDevices,
+ const GraphicsControllerType_T enmGraphicsController,
+ BusAssignmentManager *pBusMgr,
+ const ComPtr<IMachine> &ptrMachine,
+ const ComPtr<IGraphicsAdapter> &ptrGraphicsAdapter,
+ const ComPtr<IBIOSSettings> &ptrBiosSettings,
+ bool fHMEnabled)
+{
+ // InsertConfig* throws
+ try
+ {
+ PCFGMNODE pDev, pInst, pCfg, pLunL0;
+ HRESULT hrc;
+ Bstr bstr;
+ const char *pcszDevice = "vga";
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+ InsertConfigNode(pDevices, pcszDevice, &pDev);
+ InsertConfigNode(pDev, "0", &pInst);
+ InsertConfigInteger(pInst, "Trusted", 1); /* boolean */
+
+ hrc = pBusMgr->assignPCIDevice(pcszDevice, pInst); H();
+ InsertConfigNode(pInst, "Config", &pCfg);
+ ULONG cVRamMBs;
+ hrc = ptrGraphicsAdapter->COMGETTER(VRAMSize)(&cVRamMBs); H();
+ InsertConfigInteger(pCfg, "VRamSize", cVRamMBs * _1M);
+ ULONG cMonitorCount;
+ hrc = ptrGraphicsAdapter->COMGETTER(MonitorCount)(&cMonitorCount); H();
+ InsertConfigInteger(pCfg, "MonitorCount", cMonitorCount);
+#ifdef VBOX_WITH_2X_4GB_ADDR_SPACE
+ InsertConfigInteger(pCfg, "R0Enabled", fHMEnabled);
+#else
+ NOREF(fHMEnabled);
+#endif
+ BOOL f3DEnabled;
+ hrc = ptrGraphicsAdapter->COMGETTER(Accelerate3DEnabled)(&f3DEnabled); H();
+ InsertConfigInteger(pCfg, "3DEnabled", f3DEnabled);
+
+ i_attachStatusDriver(pInst, DeviceType_Graphics3D);
+
+#ifdef VBOX_WITH_VMSVGA
+ if ( enmGraphicsController == GraphicsControllerType_VMSVGA
+ || enmGraphicsController == GraphicsControllerType_VBoxSVGA)
+ {
+ InsertConfigInteger(pCfg, "VMSVGAEnabled", true);
+ if (enmGraphicsController == GraphicsControllerType_VMSVGA)
+ {
+ InsertConfigInteger(pCfg, "VMSVGAPciBarLayout", true);
+ InsertConfigInteger(pCfg, "VMSVGAPciId", true);
+ }
+# ifdef VBOX_WITH_VMSVGA3D
+ InsertConfigInteger(pCfg, "VMSVGA3dEnabled", f3DEnabled);
+# else
+ LogRel(("VMSVGA3d not available in this build!\n"));
+# endif /* VBOX_WITH_VMSVGA3D */
+ }
+#else
+ RT_NOREF(enmGraphicsController);
+#endif /* VBOX_WITH_VMSVGA */
+
+ /* Custom VESA mode list */
+ unsigned cModes = 0;
+ for (unsigned iMode = 1; iMode <= 16; ++iMode)
+ {
+ char szExtraDataKey[sizeof("CustomVideoModeXX")];
+ RTStrPrintf(szExtraDataKey, sizeof(szExtraDataKey), "CustomVideoMode%u", iMode);
+ hrc = ptrMachine->GetExtraData(Bstr(szExtraDataKey).raw(), bstr.asOutParam()); H();
+ if (bstr.isEmpty())
+ break;
+ InsertConfigString(pCfg, szExtraDataKey, bstr);
+ ++cModes;
+ }
+ InsertConfigInteger(pCfg, "CustomVideoModes", cModes);
+
+ /* VESA height reduction */
+ ULONG ulHeightReduction;
+ IFramebuffer *pFramebuffer = NULL;
+ hrc = i_getDisplay()->QueryFramebuffer(0, &pFramebuffer);
+ if (SUCCEEDED(hrc) && pFramebuffer)
+ {
+ hrc = pFramebuffer->COMGETTER(HeightReduction)(&ulHeightReduction); H();
+ pFramebuffer->Release();
+ pFramebuffer = NULL;
+ }
+ else
+ {
+ /* If framebuffer is not available, there is no height reduction. */
+ ulHeightReduction = 0;
+ }
+ InsertConfigInteger(pCfg, "HeightReduction", ulHeightReduction);
+
+ /*
+ * BIOS logo
+ */
+ BOOL fFadeIn;
+ hrc = ptrBiosSettings->COMGETTER(LogoFadeIn)(&fFadeIn); H();
+ InsertConfigInteger(pCfg, "FadeIn", fFadeIn ? 1 : 0);
+ BOOL fFadeOut;
+ hrc = ptrBiosSettings->COMGETTER(LogoFadeOut)(&fFadeOut); H();
+ InsertConfigInteger(pCfg, "FadeOut", fFadeOut ? 1: 0);
+ ULONG logoDisplayTime;
+ hrc = ptrBiosSettings->COMGETTER(LogoDisplayTime)(&logoDisplayTime); H();
+ InsertConfigInteger(pCfg, "LogoTime", logoDisplayTime);
+ Bstr bstrLogoImagePath;
+ hrc = ptrBiosSettings->COMGETTER(LogoImagePath)(bstrLogoImagePath.asOutParam()); H();
+ InsertConfigString(pCfg, "LogoFile", bstrLogoImagePath);
+
+ /*
+ * Boot menu
+ */
+ BIOSBootMenuMode_T eBootMenuMode;
+ int iShowBootMenu;
+ hrc = ptrBiosSettings->COMGETTER(BootMenuMode)(&eBootMenuMode); H();
+ switch (eBootMenuMode)
+ {
+ case BIOSBootMenuMode_Disabled: iShowBootMenu = 0; break;
+ case BIOSBootMenuMode_MenuOnly: iShowBootMenu = 1; break;
+ default: iShowBootMenu = 2; break;
+ }
+ InsertConfigInteger(pCfg, "ShowBootMenu", iShowBootMenu);
+
+ /* Attach the display. */
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ InsertConfigString(pLunL0, "Driver", "MainDisplay");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Ellipsis to va_list wrapper for calling setVMRuntimeErrorCallback.
+ */
+void Console::i_atVMRuntimeErrorCallbackF(uint32_t fFlags, const char *pszErrorId, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ i_atVMRuntimeErrorCallback(NULL, this, fFlags, pszErrorId, pszFormat, va);
+ va_end(va);
+}
+
+/* XXX introduce RT format specifier */
+static uint64_t formatDiskSize(uint64_t u64Size, const char **pszUnit)
+{
+ if (u64Size > INT64_C(5000)*_1G)
+ {
+ *pszUnit = "TB";
+ return u64Size / _1T;
+ }
+ else if (u64Size > INT64_C(5000)*_1M)
+ {
+ *pszUnit = "GB";
+ return u64Size / _1G;
+ }
+ else
+ {
+ *pszUnit = "MB";
+ return u64Size / _1M;
+ }
+}
+
+/**
+ * Checks the location of the given medium for known bugs affecting the usage
+ * of the host I/O cache setting.
+ *
+ * @returns VBox status code.
+ * @param pMedium The medium to check.
+ * @param pfUseHostIOCache Where to store the suggested host I/O cache setting.
+ */
+int Console::i_checkMediumLocation(IMedium *pMedium, bool *pfUseHostIOCache)
+{
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+ /*
+ * Some sanity checks.
+ */
+ RT_NOREF(pfUseHostIOCache);
+ ComPtr<IMediumFormat> pMediumFormat;
+ HRESULT hrc = pMedium->COMGETTER(MediumFormat)(pMediumFormat.asOutParam()); H();
+ ULONG uCaps = 0;
+ com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
+ hrc = pMediumFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap)); H();
+
+ for (ULONG j = 0; j < mediumFormatCap.size(); j++)
+ uCaps |= mediumFormatCap[j];
+
+ if (uCaps & MediumFormatCapabilities_File)
+ {
+ Bstr bstrFile;
+ hrc = pMedium->COMGETTER(Location)(bstrFile.asOutParam()); H();
+ Utf8Str const strFile(bstrFile);
+
+ Bstr bstrSnap;
+ ComPtr<IMachine> pMachine = i_machine();
+ hrc = pMachine->COMGETTER(SnapshotFolder)(bstrSnap.asOutParam()); H();
+ Utf8Str const strSnap(bstrSnap);
+
+ RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN;
+ int vrc2 = RTFsQueryType(strFile.c_str(), &enmFsTypeFile);
+ AssertMsgRCReturn(vrc2, ("Querying the file type of '%s' failed!\n", strFile.c_str()), vrc2);
+
+ /* Any VM which hasn't created a snapshot or saved the current state of the VM
+ * won't have a Snapshot folder yet so no need to log anything about the file system
+ * type of the non-existent directory in such cases. */
+ RTFSTYPE enmFsTypeSnap = RTFSTYPE_UNKNOWN;
+ vrc2 = RTFsQueryType(strSnap.c_str(), &enmFsTypeSnap);
+ if (RT_SUCCESS(vrc2) && !mfSnapshotFolderDiskTypeShown)
+ {
+ LogRel(("File system of '%s' (snapshots) is %s\n", strSnap.c_str(), RTFsTypeName(enmFsTypeSnap)));
+ mfSnapshotFolderDiskTypeShown = true;
+ }
+ LogRel(("File system of '%s' is %s\n", strFile.c_str(), RTFsTypeName(enmFsTypeFile)));
+ LONG64 i64Size;
+ hrc = pMedium->COMGETTER(LogicalSize)(&i64Size); H();
+#ifdef RT_OS_WINDOWS
+ if ( enmFsTypeFile == RTFSTYPE_FAT
+ && i64Size >= _4G)
+ {
+ const char *pszUnit;
+ uint64_t u64Print = formatDiskSize((uint64_t)i64Size, &pszUnit);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected",
+ N_("The medium '%s' has a logical size of %RU64%s "
+ "but the file system the medium is located on seems "
+ "to be FAT(32) which cannot handle files bigger than 4GB.\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto an NTFS partition"),
+ strFile.c_str(), u64Print, pszUnit);
+ }
+#else /* !RT_OS_WINDOWS */
+ if ( enmFsTypeFile == RTFSTYPE_FAT
+ || enmFsTypeFile == RTFSTYPE_EXT
+ || enmFsTypeFile == RTFSTYPE_EXT2
+ || enmFsTypeFile == RTFSTYPE_EXT3
+ || enmFsTypeFile == RTFSTYPE_EXT4)
+ {
+ RTFILE file;
+ int vrc = RTFileOpen(&file, strFile.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFOFF maxSize;
+ /* Careful: This function will work only on selected local file systems! */
+ vrc = RTFileQueryMaxSizeEx(file, &maxSize);
+ RTFileClose(file);
+ if ( RT_SUCCESS(vrc)
+ && maxSize > 0
+ && i64Size > (LONG64)maxSize)
+ {
+ const char *pszUnitSiz;
+ const char *pszUnitMax;
+ uint64_t u64PrintSiz = formatDiskSize((LONG64)i64Size, &pszUnitSiz);
+ uint64_t u64PrintMax = formatDiskSize(maxSize, &pszUnitMax);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected", /* <= not exact but ... */
+ N_("The medium '%s' has a logical size of %RU64%s "
+ "but the file system the medium is located on can "
+ "only handle files up to %RU64%s in theory.\n"
+ "We strongly recommend to put all your virtual disk "
+ "images and the snapshot folder onto a proper "
+ "file system (e.g. ext3) with a sufficient size"),
+ strFile.c_str(), u64PrintSiz, pszUnitSiz, u64PrintMax, pszUnitMax);
+ }
+ }
+ }
+#endif /* !RT_OS_WINDOWS */
+
+ /*
+ * Snapshot folder:
+ * Here we test only for a FAT partition as we had to create a dummy file otherwise
+ */
+ if ( enmFsTypeSnap == RTFSTYPE_FAT
+ && i64Size >= _4G
+ && !mfSnapshotFolderSizeWarningShown)
+ {
+ const char *pszUnit;
+ uint64_t u64Print = formatDiskSize(i64Size, &pszUnit);
+ i_atVMRuntimeErrorCallbackF(0, "FatPartitionDetected",
+#ifdef RT_OS_WINDOWS
+ N_("The snapshot folder of this VM '%s' seems to be located on "
+ "a FAT(32) file system. The logical size of the medium '%s' "
+ "(%RU64%s) is bigger than the maximum file size this file "
+ "system can handle (4GB).\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto an NTFS partition"),
+#else
+ N_("The snapshot folder of this VM '%s' seems to be located on "
+ "a FAT(32) file system. The logical size of the medium '%s' "
+ "(%RU64%s) is bigger than the maximum file size this file "
+ "system can handle (4GB).\n"
+ "We strongly recommend to put all your virtual disk images and "
+ "the snapshot folder onto a proper file system (e.g. ext3)"),
+#endif
+ strSnap.c_str(), strFile.c_str(), u64Print, pszUnit);
+ /* Show this particular warning only once */
+ mfSnapshotFolderSizeWarningShown = true;
+ }
+
+#ifdef RT_OS_LINUX
+ /*
+ * Ext4 bug: Check if the host I/O cache is disabled and the disk image is located
+ * on an ext4 partition.
+ * This bug apparently applies to the XFS file system as well.
+ * Linux 2.6.36 is known to be fixed (tested with 2.6.36-rc4).
+ */
+
+ char szOsRelease[128];
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOsRelease, sizeof(szOsRelease));
+ bool fKernelHasODirectBug = RT_FAILURE(vrc)
+ || (RTStrVersionCompare(szOsRelease, "2.6.36-rc4") < 0);
+
+ if ( (uCaps & MediumFormatCapabilities_Asynchronous)
+ && !*pfUseHostIOCache
+ && fKernelHasODirectBug)
+ {
+ if ( enmFsTypeFile == RTFSTYPE_EXT4
+ || enmFsTypeFile == RTFSTYPE_XFS)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected",
+ N_("The host I/O cache for at least one controller is disabled "
+ "and the medium '%s' for this VM "
+ "is located on an %s partition. There is a known Linux "
+ "kernel bug which can lead to the corruption of the virtual "
+ "disk image under these conditions.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or put the disk image and the snapshot folder "
+ "onto a different file system.\n"
+ "The host I/O cache will now be enabled for this medium"),
+ strFile.c_str(), enmFsTypeFile == RTFSTYPE_EXT4 ? "ext4" : "xfs");
+ *pfUseHostIOCache = true;
+ }
+ else if ( ( enmFsTypeSnap == RTFSTYPE_EXT4
+ || enmFsTypeSnap == RTFSTYPE_XFS)
+ && !mfSnapshotFolderExt4WarningShown)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Ext4PartitionDetected",
+ N_("The host I/O cache for at least one controller is disabled "
+ "and the snapshot folder for this VM "
+ "is located on an %s partition. There is a known Linux "
+ "kernel bug which can lead to the corruption of the virtual "
+ "disk image under these conditions.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or put the disk image and the snapshot folder "
+ "onto a different file system.\n"
+ "The host I/O cache will now be enabled for this medium"),
+ enmFsTypeSnap == RTFSTYPE_EXT4 ? "ext4" : "xfs");
+ *pfUseHostIOCache = true;
+ mfSnapshotFolderExt4WarningShown = true;
+ }
+ }
+
+ /*
+ * 2.6.18 bug: Check if the host I/O cache is disabled and the host is running
+ * Linux 2.6.18. See @bugref{8690}. Apparently the same problem as
+ * documented in https://lkml.org/lkml/2007/2/1/14. We saw such
+ * kernel oopses on Linux 2.6.18-416.el5. We don't know when this
+ * was fixed but we _know_ that 2.6.18 EL5 kernels are affected.
+ */
+ bool fKernelAsyncUnreliable = RT_FAILURE(vrc)
+ || (RTStrVersionCompare(szOsRelease, "2.6.19") < 0);
+ if ( (uCaps & MediumFormatCapabilities_Asynchronous)
+ && !*pfUseHostIOCache
+ && fKernelAsyncUnreliable)
+ {
+ i_atVMRuntimeErrorCallbackF(0, "Linux2618TooOld",
+ N_("The host I/O cache for at least one controller is disabled. "
+ "There is a known Linux kernel bug which can lead to kernel "
+ "oopses under heavy load. To our knowledge this bug affects "
+ "all 2.6.18 kernels.\n"
+ "Either enable the host I/O cache permanently in the VM "
+ "settings or switch to a newer host kernel.\n"
+ "The host I/O cache will now be enabled for this medium"));
+ *pfUseHostIOCache = true;
+ }
+#endif
+ }
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unmounts the specified medium from the specified device.
+ *
+ * @returns VBox status code.
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ * @param enmBus The storage bus.
+ * @param enmDevType The device type.
+ * @param pcszDevice The device emulation.
+ * @param uInstance Instance of the device.
+ * @param uLUN The LUN on the device.
+ * @param fForceUnmount Whether to force unmounting.
+ */
+int Console::i_unmountMediumFromGuest(PUVM pUVM, PCVMMR3VTABLE pVMM, StorageBus_T enmBus, DeviceType_T enmDevType,
+ const char *pcszDevice, unsigned uInstance, unsigned uLUN,
+ bool fForceUnmount) RT_NOEXCEPT
+{
+ /* Unmount existing media only for floppy and DVD drives. */
+ int vrc = VINF_SUCCESS;
+ PPDMIBASE pBase;
+ if (enmBus == StorageBus_USB)
+ vrc = pVMM->pfnPDMR3UsbQueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase);
+ else if ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI)
+ || (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD))
+ vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "SCSI", &pBase);
+ else /* IDE or Floppy */
+ vrc = pVMM->pfnPDMR3QueryLun(pUVM, pcszDevice, uInstance, uLUN, &pBase);
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_PDM_LUN_NOT_FOUND || vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertRC(vrc);
+ }
+ else
+ {
+ PPDMIMOUNT pIMount = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMOUNT);
+ AssertReturn(pIMount, VERR_INVALID_POINTER);
+
+ /* Unmount the media (but do not eject the medium!) */
+ vrc = pIMount->pfnUnmount(pIMount, fForceUnmount, false /*=fEject*/);
+ if (vrc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ vrc = VINF_SUCCESS;
+ /* for example if the medium is locked */
+ else if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return vrc;
+}
+
+/**
+ * Removes the currently attached medium driver form the specified device
+ * taking care of the controlelr specific configs wrt. to the attached driver chain.
+ *
+ * @returns VBox status code.
+ * @param pCtlInst The controler instance node in the CFGM tree.
+ * @param pcszDevice The device name.
+ * @param uInstance The device instance.
+ * @param uLUN The device LUN.
+ * @param enmBus The storage bus.
+ * @param fAttachDetach Flag whether this is a change while the VM is running
+ * @param fHotplug Flag whether the guest should be notified about the device change.
+ * @param fForceUnmount Flag whether to force unmounting the medium even if it is locked.
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ * @param enmDevType The device type.
+ * @param ppLunL0 Where to store the node to attach the new config to on success.
+ */
+int Console::i_removeMediumDriverFromVm(PCFGMNODE pCtlInst,
+ const char *pcszDevice,
+ unsigned uInstance,
+ unsigned uLUN,
+ StorageBus_T enmBus,
+ bool fAttachDetach,
+ bool fHotplug,
+ bool fForceUnmount,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T enmDevType,
+ PCFGMNODE *ppLunL0)
+{
+ int vrc = VINF_SUCCESS;
+ bool fAddLun = false;
+
+ /* First check if the LUN already exists. */
+ PCFGMNODE pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ AssertReturn(!RT_VALID_PTR(pLunL0) || fAttachDetach, VERR_INTERNAL_ERROR);
+
+ if (pLunL0)
+ {
+ /*
+ * Unmount the currently mounted medium if we don't just hot remove the
+ * complete device (SATA) and it supports unmounting (DVD).
+ */
+ if ( (enmDevType != DeviceType_HardDisk)
+ && !fHotplug)
+ {
+ vrc = i_unmountMediumFromGuest(pUVM, pVMM, enmBus, enmDevType, pcszDevice, uInstance, uLUN, fForceUnmount);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ /*
+ * Don't detach the SCSI driver when unmounting the current medium
+ * (we are not ripping out the device but only eject the medium).
+ */
+ char *pszDriverDetach = NULL;
+ if ( !fHotplug
+ && ( (enmBus == StorageBus_SATA && enmDevType == DeviceType_DVD)
+ || enmBus == StorageBus_SAS
+ || enmBus == StorageBus_SCSI
+ || enmBus == StorageBus_VirtioSCSI
+ || enmBus == StorageBus_USB))
+ {
+ /* Get the current attached driver we have to detach. */
+ PCFGMNODE pDrvLun = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u/AttachedDriver/", uLUN);
+ if (pDrvLun)
+ {
+ char szDriver[128];
+ RT_ZERO(szDriver);
+ vrc = pVMM->pfnCFGMR3QueryString(pDrvLun, "Driver", &szDriver[0], sizeof(szDriver));
+ if (RT_SUCCESS(vrc))
+ pszDriverDetach = RTStrDup(&szDriver[0]);
+
+ pLunL0 = pDrvLun;
+ }
+ }
+
+ if (enmBus == StorageBus_USB)
+ vrc = pVMM->pfnPDMR3UsbDriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach,
+ 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ else
+ vrc = pVMM->pfnPDMR3DriverDetach(pUVM, pcszDevice, uInstance, uLUN, pszDriverDetach,
+ 0 /* iOccurence */, fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG);
+
+ if (pszDriverDetach)
+ {
+ RTStrFree(pszDriverDetach);
+ /* Remove the complete node and create new for the new config. */
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ pLunL0 = pVMM->pfnCFGMR3GetChildF(pCtlInst, "LUN#%u", uLUN);
+ if (pLunL0)
+ {
+ try
+ {
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+ }
+ }
+ if (vrc == VERR_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Don't remove the LUN except for IDE/floppy/NVMe (which connects directly to the medium driver
+ * even for DVD devices) or if there is a hotplug event which rips out the complete device.
+ */
+ if ( fHotplug
+ || enmBus == StorageBus_IDE
+ || enmBus == StorageBus_Floppy
+ || enmBus == StorageBus_PCIe
+ || (enmBus == StorageBus_SATA && enmDevType != DeviceType_DVD))
+ {
+ fAddLun = true;
+ pVMM->pfnCFGMR3RemoveNode(pLunL0);
+ }
+ }
+ else
+ fAddLun = true;
+
+ try
+ {
+ if (fAddLun)
+ InsertConfigNodeF(pCtlInst, &pLunL0, "LUN#%u", uLUN);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+ if (ppLunL0)
+ *ppLunL0 = pLunL0;
+
+ return vrc;
+}
+
+int Console::i_configMediumAttachment(const char *pcszDevice,
+ unsigned uInstance,
+ StorageBus_T enmBus,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ IMediumAttachment *pMediumAtt,
+ MachineState_T aMachineState,
+ HRESULT *phrc,
+ bool fAttachDetach,
+ bool fForceUnmount,
+ bool fHotplug,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ DeviceType_T *paLedDevType,
+ PCFGMNODE *ppLunL0)
+{
+ // InsertConfig* throws
+ try
+ {
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+ Bstr bstr;
+ PCFGMNODE pCtlInst = NULL;
+
+// #define RC_CHECK() AssertMsgReturn(RT_SUCCESS(vrc), ("vrc=%Rrc\n", vrc), vrc)
+#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 enmType;
+ hrc = pMediumAtt->COMGETTER(Type)(&enmType); H();
+ BOOL fNonRotational;
+ hrc = pMediumAtt->COMGETTER(NonRotational)(&fNonRotational); H();
+ BOOL fDiscard;
+ hrc = pMediumAtt->COMGETTER(Discard)(&fDiscard); H();
+
+ if (enmType == DeviceType_DVD)
+ fInsertDiskIntegrityDrv = false;
+
+ unsigned uLUN;
+ PCFGMNODE pLunL0 = NULL;
+ hrc = Console::i_storageBusPortDeviceToLun(enmBus, lPort, lDev, uLUN); H();
+
+ /* Determine the base path for the device instance. */
+ if (enmBus != StorageBus_USB)
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "Devices/%s/%u/", pcszDevice, uInstance);
+ else
+ {
+ /* If we hotplug a USB device create a new CFGM tree. */
+ if (!fHotplug)
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pVMM->pfnCFGMR3GetRootU(pUVM), "USB/%s/", pcszDevice);
+ else
+ pCtlInst = pVMM->pfnCFGMR3CreateTree(pUVM); /** @todo r=bird: Leaked in error paths! */
+ }
+ AssertReturn(pCtlInst, VERR_INTERNAL_ERROR);
+
+ if (enmBus == StorageBus_USB)
+ {
+ PCFGMNODE pCfg = NULL;
+
+ /* Create correct instance. */
+ if (!fHotplug)
+ {
+ if (!fAttachDetach)
+ InsertConfigNodeF(pCtlInst, &pCtlInst, "%d", lPort);
+ else
+ pCtlInst = pVMM->pfnCFGMR3GetChildF(pCtlInst, "%d/", lPort);
+ }
+
+ if (!fAttachDetach)
+ InsertConfigNode(pCtlInst, "Config", &pCfg);
+
+ uInstance = lPort; /* Overwrite uInstance with the correct one. */
+
+ /** @todo No LED after hotplugging. */
+ if (!fHotplug && !fAttachDetach)
+ {
+ USBStorageDevice UsbMsd;
+ UsbMsd.iPort = uInstance;
+ vrc = RTUuidCreate(&UsbMsd.mUuid);
+ AssertRCReturn(vrc, vrc);
+
+ InsertConfigStringF(pCtlInst, "UUID", "%RTuuid", &UsbMsd.mUuid);
+
+ mUSBStorageDevices.push_back(UsbMsd);
+
+ /** @todo This LED set is not freed if the device is unplugged. We could
+ * keep the LED set index in the UsbMsd structure and clean it up in
+ * i_detachStorageDevice. */
+ /* Attach the status driver */
+ i_attachStatusDriver(pCtlInst, RT_BIT_32(DeviceType_HardDisk),
+ 8, &paLedDevType, &mapMediumAttachments, pcszDevice, 0);
+ }
+ }
+
+ vrc = i_removeMediumDriverFromVm(pCtlInst, pcszDevice, uInstance, uLUN, enmBus, fAttachDetach,
+ fHotplug, fForceUnmount, pUVM, pVMM, enmType, &pLunL0);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ if (ppLunL0)
+ *ppLunL0 = pLunL0;
+
+ Utf8StrFmt devicePath("%s/%u/LUN#%u", pcszDevice, uInstance, uLUN);
+ mapMediumAttachments[devicePath] = pMediumAtt;
+
+ ComPtr<IMedium> ptrMedium;
+ hrc = pMediumAtt->COMGETTER(Medium)(ptrMedium.asOutParam()); H();
+
+ /*
+ * 1. Only check this for hard disk images.
+ * 2. Only check during VM creation and not later, especially not during
+ * taking an online snapshot!
+ */
+ if ( enmType == DeviceType_HardDisk
+ && ( aMachineState == MachineState_Starting
+ || aMachineState == MachineState_Restoring))
+ {
+ vrc = i_checkMediumLocation(ptrMedium, &fUseHostIOCache);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ BOOL fPassthrough = FALSE;
+ if (ptrMedium.isNotNull())
+ {
+ BOOL fHostDrive;
+ hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H();
+ if ( ( enmType == DeviceType_DVD
+ || enmType == DeviceType_Floppy)
+ && !fHostDrive)
+ {
+ /*
+ * Informative logging.
+ */
+ Bstr bstrFile;
+ hrc = ptrMedium->COMGETTER(Location)(bstrFile.asOutParam()); H();
+ Utf8Str strFile(bstrFile);
+ RTFSTYPE enmFsTypeFile = RTFSTYPE_UNKNOWN;
+ (void)RTFsQueryType(strFile.c_str(), &enmFsTypeFile);
+ LogRel(("File system of '%s' (%s) is %s\n",
+ strFile.c_str(), enmType == DeviceType_DVD ? "DVD" : "Floppy", RTFsTypeName(enmFsTypeFile)));
+ }
+
+ if (fHostDrive)
+ {
+ hrc = pMediumAtt->COMGETTER(Passthrough)(&fPassthrough); H();
+ }
+ }
+
+ ComObjPtr<IBandwidthGroup> pBwGroup;
+ Bstr bstrBwGroup;
+ hrc = pMediumAtt->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H();
+
+ if (!pBwGroup.isNull())
+ {
+ hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H();
+ }
+
+ /*
+ * Insert the SCSI driver for hotplug events on the SCSI/USB based storage controllers
+ * or for SATA if the new device is a CD/DVD drive.
+ */
+ if ( (fHotplug || !fAttachDetach)
+ && ( enmBus == StorageBus_SCSI
+ || enmBus == StorageBus_SAS
+ || enmBus == StorageBus_USB
+ || enmBus == StorageBus_VirtioSCSI
+ || (enmBus == StorageBus_SATA && enmType == DeviceType_DVD && !fPassthrough)))
+ {
+ InsertConfigString(pLunL0, "Driver", "SCSI");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ vrc = i_configMedium(pLunL0,
+ !!fPassthrough,
+ enmType,
+ fUseHostIOCache,
+ fBuiltinIOCache,
+ fInsertDiskIntegrityDrv,
+ fSetupMerge,
+ uMergeSource,
+ uMergeTarget,
+ bstrBwGroup.isEmpty() ? NULL : Utf8Str(bstrBwGroup).c_str(),
+ !!fDiscard,
+ !!fNonRotational,
+ ptrMedium,
+ aMachineState,
+ phrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (fAttachDetach)
+ {
+ /* Attach the new driver. */
+ if (enmBus == StorageBus_USB)
+ {
+ if (fHotplug)
+ {
+ USBStorageDevice UsbMsd;
+ RTUuidCreate(&UsbMsd.mUuid);
+ UsbMsd.iPort = uInstance;
+ vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, pcszDevice, pCtlInst, &UsbMsd.mUuid, NULL);
+ if (RT_SUCCESS(vrc))
+ mUSBStorageDevices.push_back(UsbMsd);
+ }
+ else
+ vrc = pVMM->pfnPDMR3UsbDriverAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ }
+ else if ( !fHotplug
+ && ( (enmBus == StorageBus_SAS || enmBus == StorageBus_SCSI || enmBus == StorageBus_VirtioSCSI)
+ || (enmBus == StorageBus_SATA && enmType == DeviceType_DVD)))
+ vrc = pVMM->pfnPDMR3DriverAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ else
+ vrc = pVMM->pfnPDMR3DeviceAttach(pUVM, pcszDevice, uInstance, uLUN,
+ fHotplug ? 0 : PDM_TACH_FLAGS_NOT_HOT_PLUG, NULL /*ppBase*/);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Make the secret key helper interface known to the VD driver if it is attached,
+ * so we can get notified about missing keys.
+ */
+ PPDMIBASE pIBase = NULL;
+ vrc = pVMM->pfnPDMR3QueryDriverOnLun(pUVM, pcszDevice, uInstance, uLUN, "VD", &pIBase);
+ if (RT_SUCCESS(vrc) && pIBase)
+ {
+ PPDMIMEDIA pIMedium = (PPDMIMEDIA)pIBase->pfnQueryInterface(pIBase, PDMIMEDIA_IID);
+ if (pIMedium)
+ {
+ vrc = pIMedium->pfnSetSecKeyIf(pIMedium, mpIfSecKey, mpIfSecKeyHlp);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_NOT_SUPPORTED);
+ }
+ }
+
+ /* There is no need to handle removable medium mounting, as we
+ * unconditionally replace everthing including the block driver level.
+ * This means the new medium will be picked up automatically. */
+ }
+
+ if (paLedDevType)
+ i_setLedType(&paLedDevType[uLUN], enmType);
+
+ /* Dump the changed LUN if possible, dump the complete device otherwise */
+ if ( aMachineState != MachineState_Starting
+ && aMachineState != MachineState_Restoring)
+ pVMM->pfnCFGMR3Dump(pLunL0 ? pLunL0 : pCtlInst);
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+int Console::i_configMedium(PCFGMNODE pLunL0,
+ bool fPassthrough,
+ DeviceType_T enmType,
+ bool fUseHostIOCache,
+ bool fBuiltinIOCache,
+ bool fInsertDiskIntegrityDrv,
+ bool fSetupMerge,
+ unsigned uMergeSource,
+ unsigned uMergeTarget,
+ const char *pcszBwGroup,
+ bool fDiscard,
+ bool fNonRotational,
+ ComPtr<IMedium> ptrMedium,
+ MachineState_T aMachineState,
+ HRESULT *phrc)
+{
+ // InsertConfig* throws
+ try
+ {
+ HRESULT hrc;
+ Bstr bstr;
+ PCFGMNODE pCfg = NULL;
+
+#define H() \
+ AssertMsgReturnStmt(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), if (phrc) *phrc = hrc, Global::vboxStatusCodeFromCOM(hrc))
+
+
+ BOOL fHostDrive = FALSE;
+ MediumType_T mediumType = MediumType_Normal;
+ if (ptrMedium.isNotNull())
+ {
+ hrc = ptrMedium->COMGETTER(HostDrive)(&fHostDrive); H();
+ hrc = ptrMedium->COMGETTER(Type)(&mediumType); H();
+ }
+
+ if (fHostDrive)
+ {
+ Assert(ptrMedium.isNotNull());
+ if (enmType == DeviceType_DVD)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostDVD");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+
+ InsertConfigInteger(pCfg, "Passthrough", fPassthrough);
+ }
+ else if (enmType == DeviceType_Floppy)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostFloppy");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+ }
+ }
+ else
+ {
+ if (fInsertDiskIntegrityDrv)
+ {
+ /*
+ * The actual configuration is done through CFGM extra data
+ * for each inserted driver separately.
+ */
+ InsertConfigString(pLunL0, "Driver", "DiskIntegrity");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ InsertConfigString(pLunL0, "Driver", "VD");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ switch (enmType)
+ {
+ case DeviceType_DVD:
+ InsertConfigString(pCfg, "Type", "DVD");
+ InsertConfigInteger(pCfg, "Mountable", 1);
+ break;
+ case DeviceType_Floppy:
+ InsertConfigString(pCfg, "Type", "Floppy 1.44");
+ InsertConfigInteger(pCfg, "Mountable", 1);
+ break;
+ case DeviceType_HardDisk:
+ default:
+ InsertConfigString(pCfg, "Type", "HardDisk");
+ InsertConfigInteger(pCfg, "Mountable", 0);
+ }
+
+ if ( ptrMedium.isNotNull()
+ && ( enmType == DeviceType_DVD
+ || enmType == DeviceType_Floppy)
+ )
+ {
+ // if this medium represents an ISO image and this image is inaccessible,
+ // the ignore it instead of causing a failure; this can happen when we
+ // restore a VM state and the ISO has disappeared, e.g. because the Guest
+ // Additions were mounted and the user upgraded VirtualBox. Previously
+ // we failed on startup, but that's not good because the only way out then
+ // would be to discard the VM state...
+ MediumState_T mediumState;
+ hrc = ptrMedium->RefreshState(&mediumState); H();
+ if (mediumState == MediumState_Inaccessible)
+ {
+ Bstr loc;
+ hrc = ptrMedium->COMGETTER(Location)(loc.asOutParam()); H();
+ i_atVMRuntimeErrorCallbackF(0, "DvdOrFloppyImageInaccessible",
+ N_("The image file '%ls' is inaccessible and is being ignored. "
+ "Please select a different image file for the virtual %s drive."),
+ loc.raw(),
+ enmType == DeviceType_DVD ? "DVD" : "floppy");
+ ptrMedium.setNull();
+ }
+ }
+
+ if (ptrMedium.isNotNull())
+ {
+ /* Start with length of parent chain, as the list is reversed */
+ unsigned uImage = 0;
+ ComPtr<IMedium> ptrTmp = ptrMedium;
+ while (ptrTmp.isNotNull())
+ {
+ uImage++;
+ ComPtr<IMedium> ptrParent;
+ hrc = ptrTmp->COMGETTER(Parent)(ptrParent.asOutParam()); H();
+ ptrTmp = ptrParent;
+ }
+ /* Index of last image */
+ uImage--;
+
+# ifdef VBOX_WITH_EXTPACK
+ if (mptrExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME))
+ {
+ /* Configure loading the VDPlugin. */
+ static const char s_szVDPlugin[] = "VDPluginCrypt";
+ PCFGMNODE pCfgPlugins = NULL;
+ PCFGMNODE pCfgPlugin = NULL;
+ Utf8Str strPlugin;
+ hrc = mptrExtPackManager->i_getLibraryPathForExtPack(s_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin);
+ // Don't fail, this is optional!
+ if (SUCCEEDED(hrc))
+ {
+ InsertConfigNode(pCfg, "Plugins", &pCfgPlugins);
+ InsertConfigNode(pCfgPlugins, s_szVDPlugin, &pCfgPlugin);
+ InsertConfigString(pCfgPlugin, "Path", strPlugin);
+ }
+ }
+# endif
+
+ hrc = ptrMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Path", bstr);
+
+ hrc = ptrMedium->COMGETTER(Format)(bstr.asOutParam()); H();
+ InsertConfigString(pCfg, "Format", bstr);
+
+ if (mediumType == MediumType_Readonly)
+ InsertConfigInteger(pCfg, "ReadOnly", 1);
+ else if (enmType == DeviceType_Floppy)
+ InsertConfigInteger(pCfg, "MaybeReadOnly", 1);
+
+ /* Start without exclusive write access to the images. */
+ /** @todo Live Migration: I don't quite like this, we risk screwing up when
+ * we're resuming the VM if some 3rd dude have any of the VDIs open
+ * with write sharing denied. However, if the two VMs are sharing a
+ * image it really is necessary....
+ *
+ * So, on the "lock-media" command, the target teleporter should also
+ * make DrvVD undo TempReadOnly. It gets interesting if we fail after
+ * that. Grumble. */
+ if ( enmType == DeviceType_HardDisk
+ && aMachineState == MachineState_TeleportingIn)
+ InsertConfigInteger(pCfg, "TempReadOnly", 1);
+
+ /* Flag for opening the medium for sharing between VMs. This
+ * is done at the moment only for the first (and only) medium
+ * in the chain, as shared media can have no diffs. */
+ if (mediumType == MediumType_Shareable)
+ InsertConfigInteger(pCfg, "Shareable", 1);
+
+ if (!fUseHostIOCache)
+ {
+ InsertConfigInteger(pCfg, "UseNewIo", 1);
+ /*
+ * Activate the builtin I/O cache for harddisks only.
+ * It caches writes only which doesn't make sense for DVD drives
+ * and just increases the overhead.
+ */
+ if ( fBuiltinIOCache
+ && (enmType == DeviceType_HardDisk))
+ InsertConfigInteger(pCfg, "BlockCache", 1);
+ }
+
+ if (fSetupMerge)
+ {
+ InsertConfigInteger(pCfg, "SetupMerge", 1);
+ if (uImage == uMergeSource)
+ InsertConfigInteger(pCfg, "MergeSource", 1);
+ else if (uImage == uMergeTarget)
+ InsertConfigInteger(pCfg, "MergeTarget", 1);
+ }
+
+ if (pcszBwGroup)
+ InsertConfigString(pCfg, "BwGroup", pcszBwGroup);
+
+ if (fDiscard)
+ InsertConfigInteger(pCfg, "Discard", 1);
+
+ if (fNonRotational)
+ InsertConfigInteger(pCfg, "NonRotationalMedium", 1);
+
+ /* Pass all custom parameters. */
+ bool fHostIP = true;
+ bool fEncrypted = false;
+ hrc = i_configMediumProperties(pCfg, ptrMedium, &fHostIP, &fEncrypted); H();
+
+ /* Create an inverted list of parents. */
+ uImage--;
+ ComPtr<IMedium> ptrParentMedium = ptrMedium;
+ for (PCFGMNODE pParent = pCfg;; uImage--)
+ {
+ ComPtr<IMedium> ptrCurMedium;
+ hrc = ptrParentMedium->COMGETTER(Parent)(ptrCurMedium.asOutParam()); H();
+ if (ptrCurMedium.isNull())
+ break;
+
+ PCFGMNODE pCur;
+ InsertConfigNode(pParent, "Parent", &pCur);
+ hrc = ptrCurMedium->COMGETTER(Location)(bstr.asOutParam()); H();
+ InsertConfigString(pCur, "Path", bstr);
+
+ hrc = ptrCurMedium->COMGETTER(Format)(bstr.asOutParam()); H();
+ InsertConfigString(pCur, "Format", bstr);
+
+ if (fSetupMerge)
+ {
+ if (uImage == uMergeSource)
+ InsertConfigInteger(pCur, "MergeSource", 1);
+ else if (uImage == uMergeTarget)
+ InsertConfigInteger(pCur, "MergeTarget", 1);
+ }
+
+ /* Configure medium properties. */
+ hrc = i_configMediumProperties(pCur, ptrCurMedium, &fHostIP, &fEncrypted); H();
+
+ /* next */
+ pParent = pCur;
+ ptrParentMedium = ptrCurMedium;
+ }
+
+ /* Custom code: put marker to not use host IP stack to driver
+ * configuration node. Simplifies life of DrvVD a bit. */
+ if (!fHostIP)
+ InsertConfigInteger(pCfg, "HostIPStack", 0);
+
+ if (fEncrypted)
+ m_cDisksEncrypted++;
+ }
+ else
+ {
+ /* Set empty drive flag for DVD or floppy without media. */
+ if ( enmType == DeviceType_DVD
+ || enmType == DeviceType_Floppy)
+ InsertConfigInteger(pCfg, "EmptyDrive", 1);
+ }
+ }
+#undef H
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds the medium properties to the CFGM tree.
+ *
+ * @returns VBox status code.
+ * @param pCur The current CFGM node.
+ * @param pMedium The medium object to configure.
+ * @param pfHostIP Where to return the value of the \"HostIPStack\" property if found.
+ * @param pfEncrypted Where to return whether the medium is encrypted.
+ */
+int Console::i_configMediumProperties(PCFGMNODE pCur, IMedium *pMedium, bool *pfHostIP, bool *pfEncrypted)
+{
+ /* Pass all custom parameters. */
+ SafeArray<BSTR> aNames;
+ SafeArray<BSTR> aValues;
+ HRESULT hrc = pMedium->GetProperties(NULL, ComSafeArrayAsOutParam(aNames), ComSafeArrayAsOutParam(aValues));
+ if ( SUCCEEDED(hrc)
+ && aNames.size() != 0)
+ {
+ PCFGMNODE pVDC;
+ InsertConfigNode(pCur, "VDConfig", &pVDC);
+ for (size_t ii = 0; ii < aNames.size(); ++ii)
+ {
+ if (aValues[ii] && *aValues[ii])
+ {
+ Utf8Str const strName = aNames[ii];
+ Utf8Str const strValue = aValues[ii];
+ size_t offSlash = strName.find("/", 0);
+ if ( offSlash != strName.npos
+ && !strName.startsWith("Special/"))
+ {
+ com::Utf8Str strFilter;
+ hrc = strFilter.assignEx(strName, 0, offSlash);
+ if (FAILED(hrc))
+ break;
+
+ com::Utf8Str strKey;
+ hrc = strKey.assignEx(strName, offSlash + 1, strName.length() - offSlash - 1); /* Skip slash */
+ if (FAILED(hrc))
+ break;
+
+ PCFGMNODE pCfgFilterConfig = mpVMM->pfnCFGMR3GetChild(pVDC, strFilter.c_str());
+ if (!pCfgFilterConfig)
+ InsertConfigNode(pVDC, strFilter.c_str(), &pCfgFilterConfig);
+
+ InsertConfigString(pCfgFilterConfig, strKey.c_str(), strValue);
+ }
+ else
+ {
+ InsertConfigString(pVDC, strName.c_str(), strValue);
+ if ( strName.compare("HostIPStack") == 0
+ && strValue.compare("0") == 0)
+ *pfHostIP = false;
+ }
+
+ if ( strName.compare("CRYPT/KeyId") == 0
+ && pfEncrypted)
+ *pfEncrypted = true;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Configure proxy parameters the Network configuration tree.
+ *
+ * Parameters may differ depending on the IP address being accessed.
+ *
+ * @returns VBox status code.
+ *
+ * @param virtualBox The VirtualBox object.
+ * @param pCfg Configuration node for the driver.
+ * @param pcszPrefix The prefix for CFGM parameters: "Primary" or "Secondary".
+ * @param strIpAddr The public IP address to be accessed via a proxy.
+ *
+ * @thread EMT
+ */
+int Console::i_configProxy(ComPtr<IVirtualBox> virtualBox, PCFGMNODE pCfg, const char *pcszPrefix, const com::Utf8Str &strIpAddr)
+{
+/** @todo r=bird: This code doesn't handle cleanup correctly and may leak
+ * when hitting errors or throwing exceptions (bad_alloc). */
+ RTHTTPPROXYINFO ProxyInfo;
+ ComPtr<ISystemProperties> systemProperties;
+ ProxyMode_T enmProxyMode;
+ HRESULT hrc = virtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain system properties. hrc=%x\n", hrc));
+ return false;
+ }
+ hrc = systemProperties->COMGETTER(ProxyMode)(&enmProxyMode);
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain default machine folder. hrc=%x\n", hrc));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ RTHTTP hHttp;
+ int vrc = RTHttpCreate(&hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to create HTTP context (vrc=%Rrc)\n", vrc));
+ return vrc;
+ }
+
+ char *pszProxyType = NULL;
+
+ if (enmProxyMode == ProxyMode_Manual)
+ {
+ /*
+ * Unfortunately we cannot simply call RTHttpSetProxyByUrl because it never
+ * exposes proxy settings. Calling RTHttpQueryProxyInfoForUrl afterward
+ * won't help either as it uses system-wide proxy settings instead of
+ * parameters we would have set with RTHttpSetProxyByUrl. Hence we parse
+ * proxy URL ourselves here.
+ */
+ Bstr proxyUrl;
+ hrc = systemProperties->COMGETTER(ProxyURL)(proxyUrl.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("CLOUD-NET: Failed to obtain proxy URL. hrc=%x\n", hrc));
+ return false;
+ }
+ Utf8Str strProxyUrl = proxyUrl;
+ if (!strProxyUrl.contains("://"))
+ strProxyUrl = "http://" + strProxyUrl;
+ const char *pcszProxyUrl = strProxyUrl.c_str();
+ RTURIPARSED Parsed;
+ vrc = RTUriParse(pcszProxyUrl, &Parsed);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to parse proxy URL: %ls (vrc=%Rrc)\n", proxyUrl.raw(), vrc));
+ return false;
+ }
+
+ pszProxyType = RTUriParsedScheme(pcszProxyUrl, &Parsed);
+ if (!pszProxyType)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy scheme from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ RTStrToUpper(pszProxyType);
+
+ ProxyInfo.pszProxyHost = RTUriParsedAuthorityHost(pcszProxyUrl, &Parsed);
+ if (!ProxyInfo.pszProxyHost)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy host name from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ ProxyInfo.uProxyPort = RTUriParsedAuthorityPort(pcszProxyUrl, &Parsed);
+ if (ProxyInfo.uProxyPort == UINT32_MAX)
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy port from proxy URL: %s\n", pcszProxyUrl));
+ return false;
+ }
+ ProxyInfo.pszProxyUsername = RTUriParsedAuthorityUsername(pcszProxyUrl, &Parsed);
+ ProxyInfo.pszProxyPassword = RTUriParsedAuthorityPassword(pcszProxyUrl, &Parsed);
+ }
+ else if (enmProxyMode == ProxyMode_System)
+ {
+ vrc = RTHttpUseSystemProxySettings(hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("%s: RTHttpUseSystemProxySettings() failed: %Rrc", __FUNCTION__, vrc));
+ RTHttpDestroy(hHttp);
+ return vrc;
+ }
+ vrc = RTHttpQueryProxyInfoForUrl(hHttp, ("http://" + strIpAddr).c_str(), &ProxyInfo);
+ RTHttpDestroy(hHttp);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("CLOUD-NET: Failed to get proxy for %s (vrc=%Rrc)\n", strIpAddr.c_str(), vrc));
+ return vrc;
+ }
+
+ switch (ProxyInfo.enmProxyType)
+ {
+ case RTHTTPPROXYTYPE_NOPROXY:
+ /* Nothing to do */
+ return VINF_SUCCESS;
+ case RTHTTPPROXYTYPE_HTTP:
+ pszProxyType = RTStrDup("HTTP");
+ break;
+ case RTHTTPPROXYTYPE_HTTPS:
+ case RTHTTPPROXYTYPE_SOCKS4:
+ case RTHTTPPROXYTYPE_SOCKS5:
+ /* break; -- Fall through until support is implemented */
+ case RTHTTPPROXYTYPE_UNKNOWN:
+ case RTHTTPPROXYTYPE_INVALID:
+ case RTHTTPPROXYTYPE_END:
+ case RTHTTPPROXYTYPE_32BIT_HACK:
+ LogRel(("CLOUD-NET: Unsupported proxy type %u\n", ProxyInfo.enmProxyType));
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ Assert(enmProxyMode == ProxyMode_NoProxy);
+ return VINF_SUCCESS;
+ }
+
+ /* Resolve proxy host name to IP address if necessary */
+ RTNETADDR addr;
+ RTSocketParseInetAddress(ProxyInfo.pszProxyHost, ProxyInfo.uProxyPort, &addr);
+ if (addr.enmType != RTNETADDRTYPE_IPV4)
+ {
+ LogRel(("CLOUD-NET: Unsupported address type %u\n", addr.enmType));
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ InsertConfigString( pCfg, Utf8StrFmt("%sProxyType", pcszPrefix).c_str(), pszProxyType);
+ InsertConfigInteger( pCfg, Utf8StrFmt("%sProxyPort", pcszPrefix).c_str(), ProxyInfo.uProxyPort);
+ if (ProxyInfo.pszProxyHost)
+ InsertConfigStringF( pCfg, Utf8StrFmt("%sProxyHost", pcszPrefix).c_str(), "%RTnaipv4", addr.uAddr.IPv4);
+ if (ProxyInfo.pszProxyUsername)
+ InsertConfigString( pCfg, Utf8StrFmt("%sProxyUser", pcszPrefix).c_str(), ProxyInfo.pszProxyUsername);
+ if (ProxyInfo.pszProxyPassword)
+ InsertConfigPassword(pCfg, Utf8StrFmt("%sProxyPassword", pcszPrefix).c_str(), ProxyInfo.pszProxyPassword);
+
+ RTHttpFreeProxyInfo(&ProxyInfo);
+ RTStrFree(pszProxyType);
+ return vrc;
+}
+
+
+/**
+ * Construct the Network configuration tree
+ *
+ * @returns VBox status code.
+ *
+ * @param pszDevice The PDM device name.
+ * @param uInstance The PDM device instance.
+ * @param uLun The PDM LUN number of the drive.
+ * @param aNetworkAdapter The network adapter whose attachment needs to be changed
+ * @param pCfg Configuration node for the device
+ * @param pLunL0 To store the pointer to the LUN#0.
+ * @param pInst The instance CFGM node
+ * @param fAttachDetach To determine if the network attachment should
+ * be attached/detached after/before
+ * configuration.
+ * @param fIgnoreConnectFailure
+ * True if connection failures should be ignored
+ * (makes only sense for bridged/host-only networks).
+ * @param pUVM The usermode VM handle.
+ * @param pVMM The VMM vtable.
+ *
+ * @note Locks this object for writing.
+ * @thread EMT
+ */
+int Console::i_configNetwork(const char *pszDevice,
+ unsigned uInstance,
+ unsigned uLun,
+ INetworkAdapter *aNetworkAdapter,
+ PCFGMNODE pCfg,
+ PCFGMNODE pLunL0,
+ PCFGMNODE pInst,
+ bool fAttachDetach,
+ bool fIgnoreConnectFailure,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM)
+{
+ RT_NOREF(fIgnoreConnectFailure);
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.hrc(), VERR_ACCESS_DENIED);
+
+ // InsertConfig* throws
+ try
+ {
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+ Bstr bstr;
+
+#ifdef VBOX_WITH_CLOUD_NET
+ /* We'll need device's pCfg for cloud attachments */
+ PCFGMNODE pDevCfg = pCfg;
+#endif /* VBOX_WITH_CLOUD_NET */
+
+#define H() AssertLogRelMsgReturn(!FAILED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR)
+
+ /*
+ * Locking the object before doing VMR3* calls is quite safe here, since
+ * we're on EMT. Write lock is necessary because we indirectly modify the
+ * meAttachmentType member.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IMachine> pMachine = i_machine();
+
+ ComPtr<IVirtualBox> virtualBox;
+ hrc = pMachine->COMGETTER(Parent)(virtualBox.asOutParam()); H();
+
+ ComPtr<IHost> host;
+ hrc = virtualBox->COMGETTER(Host)(host.asOutParam()); H();
+
+ BOOL fSniffer;
+ hrc = aNetworkAdapter->COMGETTER(TraceEnabled)(&fSniffer); H();
+
+ NetworkAdapterPromiscModePolicy_T enmPromiscModePolicy;
+ hrc = aNetworkAdapter->COMGETTER(PromiscModePolicy)(&enmPromiscModePolicy); H();
+ const char *pszPromiscuousGuestPolicy;
+ switch (enmPromiscModePolicy)
+ {
+ case NetworkAdapterPromiscModePolicy_Deny: pszPromiscuousGuestPolicy = "deny"; break;
+ case NetworkAdapterPromiscModePolicy_AllowNetwork: pszPromiscuousGuestPolicy = "allow-network"; break;
+ case NetworkAdapterPromiscModePolicy_AllowAll: pszPromiscuousGuestPolicy = "allow-all"; break;
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_4);
+ }
+
+ if (fAttachDetach)
+ {
+ vrc = pVMM->pfnPDMR3DeviceDetach(pUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/);
+ if (vrc == VINF_PDM_NO_DRIVER_ATTACHED_TO_LUN)
+ vrc = VINF_SUCCESS;
+ AssertLogRelRCReturn(vrc, vrc);
+
+ /* Nuke anything which might have been left behind. */
+ pVMM->pfnCFGMR3RemoveNode(pVMM->pfnCFGMR3GetChildF(pInst, "LUN#%u", uLun));
+ }
+
+ Bstr networkName, trunkName, trunkType;
+ NetworkAttachmentType_T eAttachmentType;
+ hrc = aNetworkAdapter->COMGETTER(AttachmentType)(&eAttachmentType); H();
+
+#ifdef VBOX_WITH_NETSHAPER
+ ComObjPtr<IBandwidthGroup> pBwGroup;
+ Bstr bstrBwGroup;
+ hrc = aNetworkAdapter->COMGETTER(BandwidthGroup)(pBwGroup.asOutParam()); H();
+
+ if (!pBwGroup.isNull())
+ {
+ hrc = pBwGroup->COMGETTER(Name)(bstrBwGroup.asOutParam()); H();
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+
+ AssertMsg(uLun == 0, ("Network attachments with LUN > 0 are not supported yet\n"));
+ InsertConfigNodeF(pInst, &pLunL0, "LUN#%u", uLun);
+
+ /*
+ * Do not insert neither a shaper nor a sniffer if we are not attached to anything.
+ * This way we can easily detect if we are attached to anything at the device level.
+ */
+#ifdef VBOX_WITH_NETSHAPER
+ if (bstrBwGroup.isNotEmpty() && eAttachmentType != NetworkAttachmentType_Null)
+ {
+ InsertConfigString(pLunL0, "Driver", "NetShaper");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "BwGroup", bstrBwGroup);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+#endif /* VBOX_WITH_NETSHAPER */
+
+ if (fSniffer && eAttachmentType != NetworkAttachmentType_Null)
+ {
+ InsertConfigString(pLunL0, "Driver", "NetSniffer");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ hrc = aNetworkAdapter->COMGETTER(TraceFile)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty()) /* check convention for indicating default file. */
+ InsertConfigString(pCfg, "File", bstr);
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL0);
+ }
+
+ switch (eAttachmentType)
+ {
+ case NetworkAttachmentType_Null:
+ break;
+
+ case NetworkAttachmentType_NAT:
+ {
+ ComPtr<INATEngine> natEngine;
+ hrc = aNetworkAdapter->COMGETTER(NATEngine)(natEngine.asOutParam()); H();
+ InsertConfigString(pLunL0, "Driver", "NAT");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ /* Configure TFTP prefix and boot filename. */
+ hrc = virtualBox->COMGETTER(HomeFolder)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigStringF(pCfg, "TFTPPrefix", "%ls%c%s", bstr.raw(), RTPATH_DELIMITER, "TFTP");
+ hrc = pMachine->COMGETTER(Name)(bstr.asOutParam()); H();
+ InsertConfigStringF(pCfg, "BootFile", "%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();
+ InsertConfigStringF(pCfg, "Network", "10.0.%d.0/24", uSlot+2);
+ }
+ hrc = natEngine->COMGETTER(HostIP)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "BindIP", bstr);
+ ULONG mtu = 0;
+ ULONG sockSnd = 0;
+ ULONG sockRcv = 0;
+ ULONG tcpSnd = 0;
+ ULONG tcpRcv = 0;
+ hrc = natEngine->GetNetworkSettings(&mtu, &sockSnd, &sockRcv, &tcpSnd, &tcpRcv); H();
+ if (mtu)
+ InsertConfigInteger(pCfg, "SlirpMTU", mtu);
+ if (sockRcv)
+ InsertConfigInteger(pCfg, "SockRcv", sockRcv);
+ if (sockSnd)
+ InsertConfigInteger(pCfg, "SockSnd", sockSnd);
+ if (tcpRcv)
+ InsertConfigInteger(pCfg, "TcpRcv", tcpRcv);
+ if (tcpSnd)
+ InsertConfigInteger(pCfg, "TcpSnd", tcpSnd);
+ hrc = natEngine->COMGETTER(TFTPPrefix)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ RemoveConfigValue(pCfg, "TFTPPrefix");
+ InsertConfigString(pCfg, "TFTPPrefix", bstr);
+ }
+ hrc = natEngine->COMGETTER(TFTPBootFile)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ RemoveConfigValue(pCfg, "BootFile");
+ InsertConfigString(pCfg, "BootFile", bstr);
+ }
+ hrc = natEngine->COMGETTER(TFTPNextServer)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ InsertConfigString(pCfg, "NextServer", bstr);
+ BOOL fDNSFlag;
+ hrc = natEngine->COMGETTER(DNSPassDomain)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "PassDomain", fDNSFlag);
+ hrc = natEngine->COMGETTER(DNSProxy)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "DNSProxy", fDNSFlag);
+ hrc = natEngine->COMGETTER(DNSUseHostResolver)(&fDNSFlag); H();
+ InsertConfigInteger(pCfg, "UseHostResolver", fDNSFlag);
+
+ ULONG aliasMode;
+ hrc = natEngine->COMGETTER(AliasMode)(&aliasMode); H();
+ InsertConfigInteger(pCfg, "AliasMode", aliasMode);
+
+ BOOL fLocalhostReachable;
+ hrc = natEngine->COMGETTER(LocalhostReachable)(&fLocalhostReachable); H();
+ InsertConfigInteger(pCfg, "LocalhostReachable", fLocalhostReachable);
+
+ /* port-forwarding */
+ SafeArray<BSTR> pfs;
+ hrc = natEngine->COMGETTER(Redirects)(ComSafeArrayAsOutParam(pfs)); H();
+
+ PCFGMNODE pPFTree = NULL;
+ if (pfs.size() > 0)
+ InsertConfigNode(pCfg, "PortForwarding", &pPFTree);
+
+ for (unsigned int i = 0; i < pfs.size(); ++i)
+ {
+ PCFGMNODE pPF = NULL; /* /Devices/Dev/.../Config/PortForwarding/$n/ */
+
+ uint16_t port = 0;
+ Utf8Str utf = pfs[i];
+ Utf8Str strName;
+ Utf8Str strProto;
+ Utf8Str strHostPort;
+ Utf8Str strHostIP;
+ Utf8Str strGuestPort;
+ Utf8Str strGuestIP;
+ size_t pos, ppos;
+ pos = ppos = 0;
+#define ITERATE_TO_NEXT_TERM(res, str, pos, ppos) \
+ { \
+ pos = str.find(",", ppos); \
+ if (pos == Utf8Str::npos) \
+ { \
+ Log(( #res " extracting from %s is failed\n", str.c_str())); \
+ continue; \
+ } \
+ res = str.substr(ppos, pos - ppos); \
+ Log2((#res " %s pos:%d, ppos:%d\n", res.c_str(), pos, ppos)); \
+ ppos = pos + 1; \
+ } /* no do { ... } while because of 'continue' */
+ ITERATE_TO_NEXT_TERM(strName, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strProto, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strHostIP, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strHostPort, utf, pos, ppos);
+ ITERATE_TO_NEXT_TERM(strGuestIP, utf, pos, ppos);
+ strGuestPort = utf.substr(ppos, utf.length() - ppos);
+#undef ITERATE_TO_NEXT_TERM
+
+ uint32_t proto = strProto.toUInt32();
+ bool fValid = true;
+ switch (proto)
+ {
+ case NATProtocol_UDP:
+ strProto = "UDP";
+ break;
+ case NATProtocol_TCP:
+ strProto = "TCP";
+ break;
+ default:
+ fValid = false;
+ }
+ /* continue with next rule if no valid proto was passed */
+ if (!fValid)
+ continue;
+
+ InsertConfigNodeF(pPFTree, &pPF, "%u", i);
+
+ if (!strName.isEmpty())
+ InsertConfigString(pPF, "Name", strName);
+
+ InsertConfigString(pPF, "Protocol", strProto);
+
+ if (!strHostIP.isEmpty())
+ InsertConfigString(pPF, "BindIP", strHostIP);
+
+ if (!strGuestIP.isEmpty())
+ InsertConfigString(pPF, "GuestIP", strGuestIP);
+
+ port = RTStrToUInt16(strHostPort.c_str());
+ if (port)
+ InsertConfigInteger(pPF, "HostPort", port);
+
+ port = RTStrToUInt16(strGuestPort.c_str());
+ if (port)
+ InsertConfigInteger(pPF, "GuestPort", port);
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_Bridged:
+ {
+#if (defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)) && !defined(VBOX_WITH_NETFLT)
+ hrc = i_attachToTapInterface(aNetworkAdapter);
+ if (FAILED(hrc))
+ {
+ switch (hrc)
+ {
+ case E_ACCESSDENIED:
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_(
+ "Failed to open '/dev/net/tun' for read/write access. Please check the "
+ "permissions of that node. Either run 'chmod 0666 /dev/net/tun' or "
+ "change the group of that node and make yourself a member of that group. "
+ "Make sure that these changes are permanent, especially if you are "
+ "using udev"));
+ default:
+ AssertMsgFailed(("Could not attach to host interface! Bad!\n"));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS,
+ N_("Failed to initialize Host Interface Networking"));
+ }
+ }
+
+ Assert((intptr_t)maTapFD[uInstance] >= 0);
+ if ((intptr_t)maTapFD[uInstance] >= 0)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostInterface");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]);
+ }
+
+#elif defined(VBOX_WITH_NETFLT)
+ /*
+ * This is the new VBoxNetFlt+IntNet stuff.
+ */
+ Bstr BridgedIfName;
+ hrc = aNetworkAdapter->COMGETTER(BridgedInterface)(BridgedIfName.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(BridgedInterface) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ Utf8Str BridgedIfNameUtf8(BridgedIfName);
+ const char *pszBridgedIfName = BridgedIfNameUtf8.c_str();
+
+ ComPtr<IHostNetworkInterface> hostInterface;
+ hrc = host->FindHostNetworkInterfaceByName(BridgedIfName.raw(),
+ hostInterface.asOutParam());
+ if (!SUCCEEDED(hrc))
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: FindByName failed, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host networking interface, name '%ls'"),
+ BridgedIfName.raw());
+ }
+
+# if defined(RT_OS_DARWIN)
+ /* The name is in the format 'ifX: long name', chop it off at the colon. */
+ char szTrunk[INTNET_MAX_TRUNK_NAME];
+ RTStrCopy(szTrunk, sizeof(szTrunk), pszBridgedIfName);
+ char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk));
+// Quick fix for @bugref{5633}
+// if (!pszColon)
+// {
+// /*
+// * Dynamic changing of attachment causes an attempt to configure
+// * network with invalid host adapter (as it is must be changed before
+// * the attachment), calling Detach here will cause a deadlock.
+// * See @bugref{4750}.
+// * hrc = aNetworkAdapter->Detach(); H();
+// */
+// return VMSetError(VMR3GetVM(mpUVM), VERR_INTERNAL_ERROR, RT_SRC_POS,
+// N_("Malformed host interface networking name '%ls'"),
+// BridgedIfName.raw());
+// }
+ if (pszColon)
+ *pszColon = '\0';
+ const char *pszTrunk = szTrunk;
+
+# elif defined(RT_OS_SOLARIS)
+ /* The name is in the format 'ifX[:1] - long name, chop it off at space. */
+ char szTrunk[256];
+ strlcpy(szTrunk, pszBridgedIfName, sizeof(szTrunk));
+ char *pszSpace = (char *)memchr(szTrunk, ' ', sizeof(szTrunk));
+
+ /*
+ * Currently don't bother about malformed names here for the sake of people using
+ * VBoxManage and setting only the NIC name from there. If there is a space we
+ * chop it off and proceed, otherwise just use whatever we've got.
+ */
+ if (pszSpace)
+ *pszSpace = '\0';
+
+ /* Chop it off at the colon (zone naming eg: e1000g:1 we need only the e1000g) */
+ char *pszColon = (char *)memchr(szTrunk, ':', sizeof(szTrunk));
+ if (pszColon)
+ *pszColon = '\0';
+
+ const char *pszTrunk = szTrunk;
+
+# elif defined(RT_OS_WINDOWS)
+ HostNetworkInterfaceType_T eIfType;
+ hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ if (eIfType != HostNetworkInterfaceType_Bridged)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Interface ('%ls') is not a Bridged Adapter interface"),
+ BridgedIfName.raw());
+
+ hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Id) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ Guid hostIFGuid(bstr);
+
+ INetCfg *pNc;
+ ComPtr<INetCfgComponent> pAdaptorComponent;
+ LPWSTR pszApp;
+
+ hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp);
+ Assert(hrc == S_OK);
+ if (hrc != S_OK)
+ {
+ LogRel(("NetworkAttachmentType_Bridged: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+
+ /* get the adapter's INetCfgComponent*/
+ hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(),
+ pAdaptorComponent.asOutParam());
+ if (hrc != S_OK)
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ LogRel(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\"
+ char szTrunkName[INTNET_MAX_TRUNK_NAME];
+ char *pszTrunkName = szTrunkName;
+ wchar_t * pswzBindName;
+ hrc = pAdaptorComponent->GetBindName(&pswzBindName);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ int cwBindName = (int)wcslen(pswzBindName) + 1;
+ int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX);
+ if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName)
+ {
+ strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX);
+ pszTrunkName += cbFullBindNamePrefix-1;
+ if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName,
+ sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL))
+ {
+ DWORD err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertMsgFailed(("hrc=%Rhrc %#x\n", hrc, hrc));
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: insufficient szTrunkName buffer space\n"));
+ /** @todo set appropriate error code */
+ hrc = E_FAIL;
+ }
+
+ if (hrc != S_OK)
+ {
+ AssertFailed();
+ CoTaskMemFree(pswzBindName);
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ H();
+ }
+
+ /* we're not freeing the bind name since we'll use it later for detecting wireless*/
+ }
+ else
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_Bridged: VBoxNetCfgWinGetComponentByGuid failed, hrc (0x%x)",
+ hrc));
+ H();
+ }
+
+ const char *pszTrunk = szTrunkName;
+ /* we're not releasing the INetCfg stuff here since we use it later to figure out whether it is wireless */
+
+# elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+# if defined(RT_OS_FREEBSD)
+ /*
+ * If we bridge to a tap interface open it the `old' direct way.
+ * This works and performs better than bridging a physical
+ * interface via the current FreeBSD vboxnetflt implementation.
+ */
+ if (!strncmp(pszBridgedIfName, RT_STR_TUPLE("tap"))) {
+ hrc = i_attachToTapInterface(aNetworkAdapter);
+ if (FAILED(hrc))
+ {
+ switch (hrc)
+ {
+ case E_ACCESSDENIED:
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS, N_(
+ "Failed to open '/dev/%s' for read/write access. Please check the "
+ "permissions of that node, and that the net.link.tap.user_open "
+ "sysctl is set. Either run 'chmod 0666 /dev/%s' or change the "
+ "group of that node to vboxusers and make yourself a member of "
+ "that group. Make sure that these changes are permanent."),
+ pszBridgedIfName, pszBridgedIfName);
+ default:
+ AssertMsgFailed(("Could not attach to tap interface! Bad!\n"));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_HOSTIF_INIT_FAILED, RT_SRC_POS,
+ N_("Failed to initialize Host Interface Networking"));
+ }
+ }
+
+ Assert((intptr_t)maTapFD[uInstance] >= 0);
+ if ((intptr_t)maTapFD[uInstance] >= 0)
+ {
+ InsertConfigString(pLunL0, "Driver", "HostInterface");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigInteger(pCfg, "FileHandle", (intptr_t)maTapFD[uInstance]);
+ }
+ break;
+ }
+# endif
+ /** @todo Check for malformed names. */
+ const char *pszTrunk = pszBridgedIfName;
+
+ /* Issue a warning if the interface is down */
+ {
+ int iSock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (iSock >= 0)
+ {
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pszBridgedIfName);
+ if (ioctl(iSock, SIOCGIFFLAGS, &Req) >= 0)
+ if ((Req.ifr_flags & IFF_UP) == 0)
+ i_atVMRuntimeErrorCallbackF(0, "BridgedInterfaceDown",
+ N_("Bridged interface %s is down. Guest will not be able to use this interface"),
+ pszBridgedIfName);
+
+ close(iSock);
+ }
+ }
+
+# else
+# error "PORTME (VBOX_WITH_NETFLT)"
+# endif
+
+# if defined(RT_OS_DARWIN) && defined(VBOX_WITH_VMNET)
+ InsertConfigString(pLunL0, "Driver", "VMNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+# else
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+ InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ char szNetwork[INTNET_MAX_NETWORK_NAME];
+
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN)
+ /*
+ * 'pszTrunk' contains just the interface name required in ring-0, while 'pszBridgedIfName' contains
+ * interface name + optional description. We must not pass any description to the VM as it can differ
+ * for the same interface name, eg: "nge0 - ethernet" (GUI) vs "nge0" (VBoxManage).
+ */
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszTrunk);
+# else
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszBridgedIfName);
+# endif
+ InsertConfigString(pCfg, "Network", szNetwork);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszTrunk);
+ trunkType = Bstr(TRUNKTYPE_NETFLT);
+
+ BOOL fSharedMacOnWire = false;
+ hrc = hostInterface->COMGETTER(Wireless)(&fSharedMacOnWire);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_Bridged: COMGETTER(Wireless) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ else if (fSharedMacOnWire)
+ {
+ InsertConfigInteger(pCfg, "SharedMacOnWire", true);
+ Log(("Set SharedMacOnWire\n"));
+ }
+
+# if defined(RT_OS_SOLARIS)
+# if 0 /* bird: this is a bit questionable and might cause more trouble than its worth. */
+ /* Zone access restriction, don't allow snooping the global zone. */
+ zoneid_t ZoneId = getzoneid();
+ if (ZoneId != GLOBAL_ZONEID)
+ {
+ InsertConfigInteger(pCfg, "IgnoreAllPromisc", true);
+ }
+# endif
+# endif
+# endif
+
+#elif defined(RT_OS_WINDOWS) /* not defined NetFlt */
+ /* NOTHING TO DO HERE */
+#elif defined(RT_OS_LINUX)
+/// @todo aleksey: is there anything to be done here?
+#elif defined(RT_OS_FREEBSD)
+/** @todo FreeBSD: Check out this later (HIF networking). */
+#else
+# error "Port me"
+#endif
+ break;
+ }
+
+ case NetworkAttachmentType_Internal:
+ {
+ hrc = aNetworkAdapter->COMGETTER(InternalNetwork)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Network", bstr);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_HostOnly:
+ {
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+
+ Bstr HostOnlyName;
+ hrc = aNetworkAdapter->COMGETTER(HostOnlyInterface)(HostOnlyName.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(HostOnlyInterface) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ Utf8Str HostOnlyNameUtf8(HostOnlyName);
+ const char *pszHostOnlyName = HostOnlyNameUtf8.c_str();
+#ifdef VBOX_WITH_VMNET
+ /* Check if the matching host-only network has already been created. */
+ Bstr bstrLowerIP, bstrUpperIP, bstrNetworkMask;
+ BstrFmt bstrNetworkName("Legacy %s Network", pszHostOnlyName);
+ ComPtr<IHostOnlyNetwork> hostOnlyNetwork;
+ hrc = virtualBox->FindHostOnlyNetworkByName(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam());
+ if (FAILED(hrc))
+ {
+ /*
+ * With VMNET there is no VBoxNetAdp to create vboxnetX adapters,
+ * which means that the Host object won't be able to re-create
+ * them from extra data. Go through existing DHCP/adapter config
+ * to derive the parameters for the new network.
+ */
+ BstrFmt bstrOldNetworkName("HostInterfaceNetworking-%s", pszHostOnlyName);
+ ComPtr<IDHCPServer> dhcpServer;
+ hrc = virtualBox->FindDHCPServerByNetworkName(bstrOldNetworkName.raw(),
+ dhcpServer.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* There is a DHCP server available for this network. */
+ hrc = dhcpServer->COMGETTER(LowerIP)(bstrLowerIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(LowerIP) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ hrc = dhcpServer->COMGETTER(UpperIP)(bstrUpperIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(UpperIP) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ hrc = dhcpServer->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("Console::i_configNetwork: COMGETTER(NetworkMask) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+ }
+ else
+ {
+ /* No DHCP server for this hostonly interface, let's look at extra data */
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ pszHostOnlyName).raw(),
+ bstrLowerIP.asOutParam());
+ if (SUCCEEDED(hrc) && !bstrLowerIP.isEmpty())
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ pszHostOnlyName).raw(),
+ bstrNetworkMask.asOutParam());
+
+ }
+ RTNETADDRIPV4 ipAddr, ipMask;
+ vrc = bstrLowerIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr);
+ if (RT_FAILURE(vrc))
+ {
+ /* We failed to locate any valid config of this vboxnetX interface, assume defaults. */
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing lower IP '%ls', using '%ls' instead.\n",
+ bstrLowerIP.raw(), getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw()));
+ bstrLowerIP = getDefaultIPv4Address(Bstr(pszHostOnlyName));
+ bstrNetworkMask.setNull();
+ bstrUpperIP.setNull();
+ vrc = RTNetStrToIPv4Addr(Utf8Str(bstrLowerIP).c_str(), &ipAddr);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrLowerIP.raw(), vrc),
+ VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+ vrc = bstrNetworkMask.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing network mask '%ls', using '%s' instead.\n",
+ bstrNetworkMask.raw(), VBOXNET_IPV4MASK_DEFAULT));
+ bstrNetworkMask = VBOXNET_IPV4MASK_DEFAULT;
+ vrc = RTNetStrToIPv4Addr(Utf8Str(bstrNetworkMask).c_str(), &ipMask);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc), ("RTNetStrToIPv4Addr(%ls) failed, vrc=%Rrc\n", bstrNetworkMask.raw(), vrc),
+ VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ }
+ vrc = bstrUpperIP.isEmpty() ? VERR_MISSING : RTNetStrToIPv4Addr(Utf8Str(bstrUpperIP).c_str(), &ipAddr);
+ if (RT_FAILURE(vrc))
+ {
+ ipAddr.au32[0] = RT_H2N_U32((RT_N2H_U32(ipAddr.au32[0]) | ~RT_N2H_U32(ipMask.au32[0])) - 1); /* Do we need to exlude the last IP? */
+ LogRel(("NetworkAttachmentType_HostOnly: Invalid or missing upper IP '%ls', using '%RTnaipv4' instead.\n",
+ bstrUpperIP.raw(), ipAddr));
+ bstrUpperIP = BstrFmt("%RTnaipv4", ipAddr);
+ }
+
+ /* All parameters are set, create the new network. */
+ hrc = virtualBox->CreateHostOnlyNetwork(bstrNetworkName.raw(), hostOnlyNetwork.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: failed to create host-only network, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(NetworkMask)(bstrNetworkMask.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(NetworkMask) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(LowerIP)(bstrLowerIP.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(LowerIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMSETTER(UpperIP)(bstrUpperIP.raw());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMSETTER(UpperIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ LogRel(("Console: created host-only network '%ls' with mask '%ls' and range '%ls'-'%ls'\n",
+ bstrNetworkName.raw(), bstrNetworkMask.raw(), bstrLowerIP.raw(), bstrUpperIP.raw()));
+ }
+ else
+ {
+ /* The matching host-only network already exists. Tell the user to switch to it. */
+ hrc = hostOnlyNetwork->COMGETTER(NetworkMask)(bstrNetworkMask.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(NetworkMask) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMGETTER(LowerIP)(bstrLowerIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(LowerIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ hrc = hostOnlyNetwork->COMGETTER(UpperIP)(bstrUpperIP.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(UpperIP) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ }
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Host-only adapters are no longer supported!\n"
+ "For your convenience a host-only network named '%ls' has been "
+ "created with network mask '%ls' and IP address range '%ls' - '%ls'.\n"
+ "To fix this problem, switch to 'Host-only Network' "
+ "attachment type in the VM settings.\n"),
+ bstrNetworkName.raw(), bstrNetworkMask.raw(),
+ bstrLowerIP.raw(), bstrUpperIP.raw());
+#endif /* VBOX_WITH_VMNET */
+ ComPtr<IHostNetworkInterface> hostInterface;
+ hrc = host->FindHostNetworkInterfaceByName(HostOnlyName.raw(),
+ hostInterface.asOutParam());
+ if (!SUCCEEDED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: FindByName failed, vrc=%Rrc\n", vrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host networking interface, name '%ls'"), HostOnlyName.raw());
+ }
+
+ char szNetwork[INTNET_MAX_NETWORK_NAME];
+ RTStrPrintf(szNetwork, sizeof(szNetwork), "HostInterfaceNetworking-%s", pszHostOnlyName);
+
+#if defined(RT_OS_WINDOWS)
+# ifndef VBOX_WITH_NETFLT
+ hrc = E_NOTIMPL;
+ LogRel(("NetworkAttachmentType_HostOnly: Not Implemented\n"));
+ H();
+# else /* defined VBOX_WITH_NETFLT*/
+ /** @todo r=bird: Put this in a function. */
+
+ HostNetworkInterfaceType_T eIfType;
+ hrc = hostInterface->COMGETTER(InterfaceType)(&eIfType);
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(InterfaceType) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+
+ if (eIfType != HostNetworkInterfaceType_HostOnly)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Interface ('%ls') is not a Host-Only Adapter interface"),
+ HostOnlyName.raw());
+
+ hrc = hostInterface->COMGETTER(Id)(bstr.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: COMGETTER(Id) failed, hrc (0x%x)\n", hrc));
+ H();
+ }
+ Guid hostIFGuid(bstr);
+
+ INetCfg *pNc;
+ ComPtr<INetCfgComponent> pAdaptorComponent;
+ LPWSTR pszApp;
+ hrc = VBoxNetCfgWinQueryINetCfg(&pNc, FALSE, L"VirtualBox", 10, &pszApp);
+ Assert(hrc == S_OK);
+ if (hrc != S_OK)
+ {
+ LogRel(("NetworkAttachmentType_HostOnly: Failed to get NetCfg, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+
+ /* get the adapter's INetCfgComponent*/
+ hrc = VBoxNetCfgWinGetComponentByGuid(pNc, &GUID_DEVCLASS_NET, (GUID*)hostIFGuid.raw(),
+ pAdaptorComponent.asOutParam());
+ if (hrc != S_OK)
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ LogRel(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n", hrc, hrc));
+ H();
+ }
+# define VBOX_WIN_BINDNAME_PREFIX "\\DEVICE\\"
+ char szTrunkName[INTNET_MAX_TRUNK_NAME];
+ bool fNdis6 = false;
+ wchar_t * pwszHelpText;
+ hrc = pAdaptorComponent->GetHelpText(&pwszHelpText);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ Log(("help-text=%ls\n", pwszHelpText));
+ if (!wcscmp(pwszHelpText, L"VirtualBox NDIS 6.0 Miniport Driver"))
+ fNdis6 = true;
+ CoTaskMemFree(pwszHelpText);
+ }
+ if (fNdis6)
+ {
+ strncpy(szTrunkName, pszHostOnlyName, sizeof(szTrunkName) - 1);
+ Log(("trunk=%s\n", szTrunkName));
+ }
+ else
+ {
+ char *pszTrunkName = szTrunkName;
+ wchar_t * pswzBindName;
+ hrc = pAdaptorComponent->GetBindName(&pswzBindName);
+ Assert(hrc == S_OK);
+ if (hrc == S_OK)
+ {
+ int cwBindName = (int)wcslen(pswzBindName) + 1;
+ int cbFullBindNamePrefix = sizeof(VBOX_WIN_BINDNAME_PREFIX);
+ if (sizeof(szTrunkName) > cbFullBindNamePrefix + cwBindName)
+ {
+ strcpy(szTrunkName, VBOX_WIN_BINDNAME_PREFIX);
+ pszTrunkName += cbFullBindNamePrefix-1;
+ if (!WideCharToMultiByte(CP_ACP, 0, pswzBindName, cwBindName, pszTrunkName,
+ sizeof(szTrunkName) - cbFullBindNamePrefix + 1, NULL, NULL))
+ {
+ DWORD err = GetLastError();
+ hrc = HRESULT_FROM_WIN32(err);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: WideCharToMultiByte failed, hr=%Rhrc (0x%x) err=%u\n",
+ hrc, hrc, err));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: insufficient szTrunkName buffer space\n"));
+ /** @todo set appropriate error code */
+ hrc = E_FAIL;
+ }
+
+ if (hrc != S_OK)
+ {
+ AssertFailed();
+ CoTaskMemFree(pswzBindName);
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ H();
+ }
+ }
+ else
+ {
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+ AssertLogRelMsgFailed(("NetworkAttachmentType_HostOnly: VBoxNetCfgWinGetComponentByGuid failed, hrc=%Rhrc (0x%x)\n",
+ hrc, hrc));
+ H();
+ }
+
+
+ CoTaskMemFree(pswzBindName);
+ }
+
+ trunkType = TRUNKTYPE_NETADP;
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+
+ pAdaptorComponent.setNull();
+ /* release the pNc finally */
+ VBoxNetCfgWinReleaseINetCfg(pNc, FALSE /*fHasWriteLock*/);
+
+ const char *pszTrunk = szTrunkName;
+
+ InsertConfigString(pCfg, "Trunk", pszTrunk);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "IgnoreConnectFailure", (uint64_t)fIgnoreConnectFailure); /** @todo why is this
+ windows only?? */
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszTrunk);
+# endif /* defined VBOX_WITH_NETFLT*/
+#elif defined(RT_OS_DARWIN)
+ InsertConfigString(pCfg, "Trunk", pszHostOnlyName);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszHostOnlyName);
+ trunkType = TRUNKTYPE_NETADP;
+#else
+ InsertConfigString(pCfg, "Trunk", pszHostOnlyName);
+ InsertConfigString(pCfg, "Network", szNetwork);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetFlt);
+ networkName = Bstr(szNetwork);
+ trunkName = Bstr(pszHostOnlyName);
+ trunkType = TRUNKTYPE_NETFLT;
+#endif
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+
+#if !defined(RT_OS_WINDOWS) && defined(VBOX_WITH_NETFLT)
+
+ Bstr tmpAddr, tmpMask;
+
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPAddress",
+ pszHostOnlyName).raw(),
+ tmpAddr.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpAddr.isEmpty())
+ {
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPNetMask",
+ pszHostOnlyName).raw(),
+ tmpMask.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpMask.isEmpty())
+ hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(),
+ tmpMask.raw());
+ else
+ hrc = hostInterface->EnableStaticIPConfig(tmpAddr.raw(),
+ Bstr(VBOXNET_IPV4MASK_DEFAULT).raw());
+ }
+ else
+ {
+ /* Grab the IP number from the 'vboxnetX' instance number (see netif.h) */
+ hrc = hostInterface->EnableStaticIPConfig(getDefaultIPv4Address(Bstr(pszHostOnlyName)).raw(),
+ Bstr(VBOXNET_IPV4MASK_DEFAULT).raw());
+ }
+
+ ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */
+
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6Address",
+ pszHostOnlyName).raw(),
+ tmpAddr.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = virtualBox->GetExtraData(BstrFmt("HostOnly/%s/IPV6NetMask", pszHostOnlyName).raw(),
+ tmpMask.asOutParam());
+ if (SUCCEEDED(hrc) && !tmpAddr.isEmpty() && !tmpMask.isEmpty())
+ {
+ hrc = hostInterface->EnableStaticIPConfigV6(tmpAddr.raw(),
+ Utf8Str(tmpMask).toUInt32());
+ ComAssertComRC(hrc); /** @todo r=bird: Why this isn't fatal? (H()) */
+ }
+#endif
+ break;
+ }
+
+ case NetworkAttachmentType_Generic:
+ {
+ hrc = aNetworkAdapter->COMGETTER(GenericDriver)(bstr.asOutParam()); H();
+ SafeArray<BSTR> names;
+ SafeArray<BSTR> values;
+ hrc = aNetworkAdapter->GetProperties(Bstr().raw(),
+ ComSafeArrayAsOutParam(names),
+ ComSafeArrayAsOutParam(values)); H();
+
+ InsertConfigString(pLunL0, "Driver", bstr);
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ for (size_t ii = 0; ii < names.size(); ++ii)
+ {
+ if (values[ii] && *values[ii])
+ {
+ Utf8Str const strName(names[ii]);
+ Utf8Str const strValue(values[ii]);
+ InsertConfigString(pCfg, strName.c_str(), strValue);
+ }
+ }
+ break;
+ }
+
+ case NetworkAttachmentType_NATNetwork:
+ {
+ hrc = aNetworkAdapter->COMGETTER(NATNetwork)(bstr.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ /** @todo add intnet prefix to separate namespaces, and add trunk if dealing with vboxnatX */
+ InsertConfigString(pLunL0, "Driver", "IntNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigString(pCfg, "Network", bstr);
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_WhateverNone);
+ InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+
+#ifdef VBOX_WITH_CLOUD_NET
+ case NetworkAttachmentType_Cloud:
+ {
+ static const char *s_pszCloudExtPackName = "Oracle VM VirtualBox Extension Pack";
+ /*
+ * Cloud network attachments do not work wihout installed extpack.
+ * Without extpack support they won't work either.
+ */
+# ifdef VBOX_WITH_EXTPACK
+ if (!mptrExtPackManager->i_isExtPackUsable(s_pszCloudExtPackName))
+# endif
+ {
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Implementation of the cloud network attachment not found!\n"
+ "To fix this problem, either install the '%s' or switch to "
+ "another network attachment type in the VM settings."),
+ s_pszCloudExtPackName);
+ }
+
+ ComPtr<ICloudNetwork> network;
+ hrc = aNetworkAdapter->COMGETTER(CloudNetwork)(bstr.asOutParam()); H();
+ hrc = pMachine->COMGETTER(Name)(mGateway.mTargetVM.asOutParam()); H();
+ hrc = virtualBox->FindCloudNetworkByName(bstr.raw(), network.asOutParam()); H();
+ hrc = generateKeys(mGateway);
+ if (FAILED(hrc))
+ {
+ if (hrc == E_NOTIMPL)
+ return pVMM->pfnVMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("Failed to generate a key pair due to missing libssh\n"
+ "To fix this problem, either build VirtualBox with libssh "
+ "support or switch to another network attachment type in "
+ "the VM settings."));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Failed to generate a key pair due to libssh error!"));
+ }
+ hrc = startCloudGateway(virtualBox, network, mGateway);
+ if (FAILED(hrc))
+ {
+ if (hrc == VBOX_E_OBJECT_NOT_FOUND)
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to start cloud gateway instance.\nCould not find suitable "
+ "standard cloud images. Make sure you ran 'VBoxManage cloud network setup' "
+ "with correct '--gateway-os-name' and '--gateway-os-version' parameters. "
+ "Check VBoxSVC.log for actual values used to look up cloud images."));
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to start cloud gateway instance.\nMake sure you set up "
+ "cloud networking properly with 'VBoxManage cloud network setup'. "
+ "Check VBoxSVC.log for details."));
+ }
+ InsertConfigBytes(pDevCfg, "MAC", &mGateway.mCloudMacAddress, sizeof(mGateway.mCloudMacAddress));
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "CloudTunnel");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ InsertConfigPassword(pCfg, "SshKey", mGateway.mPrivateSshKey);
+ InsertConfigString(pCfg, "PrimaryIP", mGateway.mCloudPublicIp);
+ InsertConfigString(pCfg, "SecondaryIP", mGateway.mCloudSecondaryPublicIp);
+ InsertConfigBytes(pCfg, "TargetMAC", &mGateway.mLocalMacAddress, sizeof(mGateway.mLocalMacAddress));
+ hrc = i_configProxy(virtualBox, pCfg, "Primary", mGateway.mCloudPublicIp);
+ if (FAILED(hrc))
+ {
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to configure proxy for accessing cloud gateway instance via primary VNIC.\n"
+ "Check VirtualBox.log for details."));
+ }
+ hrc = i_configProxy(virtualBox, pCfg, "Secondary", mGateway.mCloudSecondaryPublicIp);
+ if (FAILED(hrc))
+ {
+ return pVMM->pfnVMR3SetError(pUVM, hrc, RT_SRC_POS,
+ N_("Failed to configure proxy for accessing cloud gateway instance via secondary VNIC.\n"
+ "Check VirtualBox.log for details."));
+ }
+ networkName = bstr;
+ trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+#endif /* VBOX_WITH_CLOUD_NET */
+
+#ifdef VBOX_WITH_VMNET
+ case NetworkAttachmentType_HostOnlyNetwork:
+ {
+ Bstr bstrId, bstrNetMask, bstrLowerIP, bstrUpperIP;
+ ComPtr<IHostOnlyNetwork> network;
+ hrc = aNetworkAdapter->COMGETTER(HostOnlyNetwork)(bstr.asOutParam()); H();
+ hrc = virtualBox->FindHostOnlyNetworkByName(bstr.raw(), network.asOutParam());
+ if (FAILED(hrc))
+ {
+ LogRel(("NetworkAttachmentType_HostOnlyNetwork: FindByName failed, hrc (0x%x)\n", hrc));
+ return pVMM->pfnVMR3SetError(pUVM, VERR_INTERNAL_ERROR, RT_SRC_POS,
+ N_("Nonexistent host-only network '%ls'"), bstr.raw());
+ }
+ hrc = network->COMGETTER(Id)(bstrId.asOutParam()); H();
+ hrc = network->COMGETTER(NetworkMask)(bstrNetMask.asOutParam()); H();
+ hrc = network->COMGETTER(LowerIP)(bstrLowerIP.asOutParam()); H();
+ hrc = network->COMGETTER(UpperIP)(bstrUpperIP.asOutParam()); H();
+ if (!bstr.isEmpty())
+ {
+ InsertConfigString(pLunL0, "Driver", "VMNet");
+ InsertConfigNode(pLunL0, "Config", &pCfg);
+ // InsertConfigString(pCfg, "Trunk", bstr);
+ // InsertConfigStringF(pCfg, "Network", "HostOnlyNetworking-%ls", bstr.raw());
+ InsertConfigInteger(pCfg, "TrunkType", kIntNetTrunkType_NetAdp);
+ InsertConfigString(pCfg, "Id", bstrId);
+ InsertConfigString(pCfg, "NetworkMask", bstrNetMask);
+ InsertConfigString(pCfg, "LowerIP", bstrLowerIP);
+ InsertConfigString(pCfg, "UpperIP", bstrUpperIP);
+ // InsertConfigString(pCfg, "IfPolicyPromisc", pszPromiscuousGuestPolicy);
+ networkName.setNull(); // We do not want DHCP server on our network!
+ // trunkType = Bstr(TRUNKTYPE_WHATEVER);
+ }
+ break;
+ }
+#endif /* VBOX_WITH_VMNET */
+
+ default:
+ AssertMsgFailed(("should not get here!\n"));
+ break;
+ }
+
+ /*
+ * Attempt to attach the driver.
+ */
+ switch (eAttachmentType)
+ {
+ case NetworkAttachmentType_Null:
+ break;
+
+ case NetworkAttachmentType_Bridged:
+ case NetworkAttachmentType_Internal:
+ case NetworkAttachmentType_HostOnly:
+#ifdef VBOX_WITH_VMNET
+ case NetworkAttachmentType_HostOnlyNetwork:
+#endif /* VBOX_WITH_VMNET */
+ case NetworkAttachmentType_NAT:
+ case NetworkAttachmentType_Generic:
+ case NetworkAttachmentType_NATNetwork:
+#ifdef VBOX_WITH_CLOUD_NET
+ case NetworkAttachmentType_Cloud:
+#endif /* VBOX_WITH_CLOUD_NET */
+ {
+ if (SUCCEEDED(hrc) && RT_SUCCESS(vrc))
+ {
+ if (fAttachDetach)
+ {
+ vrc = pVMM->pfnPDMR3DriverAttach(mpUVM, pszDevice, uInstance, uLun, 0 /*fFlags*/, NULL /* ppBase */);
+ //AssertRC(vrc);
+ }
+
+ {
+ /** @todo pritesh: get the dhcp server name from the
+ * previous network configuration and then stop the server
+ * else it may conflict with the dhcp server running with
+ * the current attachment type
+ */
+ /* Stop the hostonly DHCP Server */
+ }
+
+ /*
+ * NAT networks start their DHCP server theirself, see NATNetwork::Start()
+ */
+ if ( !networkName.isEmpty()
+ && eAttachmentType != NetworkAttachmentType_NATNetwork)
+ {
+ /*
+ * Until we implement service reference counters DHCP Server will be stopped
+ * by DHCPServerRunner destructor.
+ */
+ ComPtr<IDHCPServer> dhcpServer;
+ hrc = virtualBox->FindDHCPServerByNetworkName(networkName.raw(), dhcpServer.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /* there is a DHCP server available for this network */
+ BOOL fEnabledDhcp;
+ hrc = dhcpServer->COMGETTER(Enabled)(&fEnabledDhcp);
+ if (FAILED(hrc))
+ {
+ LogRel(("DHCP svr: COMGETTER(Enabled) failed, hrc (%Rhrc)\n", hrc));
+ H();
+ }
+
+ if (fEnabledDhcp)
+ hrc = dhcpServer->Start(trunkName.raw(), trunkType.raw());
+ }
+ else
+ hrc = S_OK;
+ }
+ }
+
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("should not get here!\n"));
+ break;
+ }
+
+ meAttachmentType[uInstance] = eAttachmentType;
+ }
+ catch (ConfigError &x)
+ {
+ // InsertConfig threw something:
+ return x.m_vrc;
+ }
+
+#undef H
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Configures the serial port at the given CFGM node with the supplied parameters.
+ *
+ * @returns VBox status code.
+ * @param pInst The instance CFGM node.
+ * @param ePortMode The port mode to sue.
+ * @param pszPath The serial port path.
+ * @param fServer Flag whether the port should act as a server
+ * for the pipe and TCP mode or connect as a client.
+ */
+int Console::i_configSerialPort(PCFGMNODE pInst, PortMode_T ePortMode, const char *pszPath, bool fServer)
+{
+ PCFGMNODE pLunL0 = NULL; /* /Devices/Dev/0/LUN#0/ */
+ PCFGMNODE pLunL1 = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/ */
+ PCFGMNODE pLunL1Cfg = NULL; /* /Devices/Dev/0/LUN#0/AttachedDriver/Config */
+
+ try
+ {
+ InsertConfigNode(pInst, "LUN#0", &pLunL0);
+ if (ePortMode == PortMode_HostPipe)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "NamedPipe");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ InsertConfigInteger(pLunL1Cfg, "IsServer", fServer);
+ }
+ else if (ePortMode == PortMode_HostDevice)
+ {
+ InsertConfigString(pLunL0, "Driver", "Host Serial");
+ InsertConfigNode(pLunL0, "Config", &pLunL1);
+ InsertConfigString(pLunL1, "DevicePath", pszPath);
+ }
+ else if (ePortMode == PortMode_TCP)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "TCP");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ InsertConfigInteger(pLunL1Cfg, "IsServer", fServer);
+ }
+ else if (ePortMode == PortMode_RawFile)
+ {
+ InsertConfigString(pLunL0, "Driver", "Char");
+ InsertConfigNode(pLunL0, "AttachedDriver", &pLunL1);
+ InsertConfigString(pLunL1, "Driver", "RawFile");
+ InsertConfigNode(pLunL1, "Config", &pLunL1Cfg);
+ InsertConfigString(pLunL1Cfg, "Location", pszPath);
+ }
+ }
+ catch (ConfigError &x)
+ {
+ /* InsertConfig threw something */
+ return x.m_vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp
new file mode 100644
index 00000000..b568a20d
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleImplTeleporter.cpp
@@ -0,0 +1,1483 @@
+/* $Id: ConsoleImplTeleporter.cpp $ */
+/** @file
+ * VBox Console COM Class implementation, The Teleporter Part.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include "AutoCaller.h"
+#include "HashedPw.h"
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/rand.h>
+#include <iprt/socket.h>
+#include <iprt/tcp.h>
+#include <iprt/timer.h>
+
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include <VBox/com/string.h>
+#include "VBox/com/ErrorInfo.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Base class for the teleporter state.
+ *
+ * These classes are used as advanced structs, not as proper classes.
+ */
+class TeleporterState
+{
+public:
+ ComPtr<Console> mptrConsole;
+ PUVM mpUVM;
+ PCVMMR3VTABLE mpVMM;
+ ComObjPtr<Progress> mptrProgress;
+ Utf8Str mstrPassword;
+ bool const mfIsSource;
+
+ /** @name stream stuff
+ * @{ */
+ RTSOCKET mhSocket;
+ uint64_t moffStream;
+ uint32_t mcbReadBlock;
+ bool volatile mfStopReading;
+ bool volatile mfEndOfStream;
+ bool volatile mfIOError;
+ /** @} */
+
+ TeleporterState(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, bool fIsSource)
+ : mptrConsole(pConsole)
+ , mpUVM(pUVM)
+ , mpVMM(pVMM)
+ , mptrProgress(pProgress)
+ , mfIsSource(fIsSource)
+ , mhSocket(NIL_RTSOCKET)
+ , moffStream(UINT64_MAX / 2)
+ , mcbReadBlock(0)
+ , mfStopReading(false)
+ , mfEndOfStream(false)
+ , mfIOError(false)
+ {
+ pVMM->pfnVMR3RetainUVM(mpUVM);
+ }
+
+ ~TeleporterState()
+ {
+ if (mpVMM)
+ mpVMM->pfnVMR3ReleaseUVM(mpUVM);
+ mpUVM = NULL;
+ }
+};
+
+
+/**
+ * Teleporter state used by the source side.
+ */
+class TeleporterStateSrc : public TeleporterState
+{
+public:
+ Utf8Str mstrHostname;
+ uint32_t muPort;
+ uint32_t mcMsMaxDowntime;
+ MachineState_T menmOldMachineState;
+ bool mfSuspendedByUs;
+ bool mfUnlockedMedia;
+
+ TeleporterStateSrc(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress, MachineState_T enmOldMachineState)
+ : TeleporterState(pConsole, pUVM, pVMM, pProgress, true /*fIsSource*/)
+ , muPort(UINT32_MAX)
+ , mcMsMaxDowntime(250)
+ , menmOldMachineState(enmOldMachineState)
+ , mfSuspendedByUs(false)
+ , mfUnlockedMedia(false)
+ {
+ }
+};
+
+
+/**
+ * Teleporter state used by the destination side.
+ */
+class TeleporterStateTrg : public TeleporterState
+{
+public:
+ IMachine *mpMachine;
+ IInternalMachineControl *mpControl;
+ PRTTCPSERVER mhServer;
+ PRTTIMERLR mphTimerLR;
+ bool mfLockedMedia;
+ int mRc;
+ Utf8Str mErrorText;
+
+ TeleporterStateTrg(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, Progress *pProgress,
+ IMachine *pMachine, IInternalMachineControl *pControl,
+ PRTTIMERLR phTimerLR, bool fStartPaused)
+ : TeleporterState(pConsole, pUVM, pVMM, pProgress, false /*fIsSource*/)
+ , mpMachine(pMachine)
+ , mpControl(pControl)
+ , mhServer(NULL)
+ , mphTimerLR(phTimerLR)
+ , mfLockedMedia(false)
+ , mRc(VINF_SUCCESS)
+ , mErrorText()
+ {
+ RT_NOREF(fStartPaused); /** @todo figure out why fStartPaused isn't used */
+ }
+};
+
+
+/**
+ * TCP stream header.
+ *
+ * This is an extra layer for fixing the problem with figuring out when the SSM
+ * stream ends.
+ */
+typedef struct TELEPORTERTCPHDR
+{
+ /** Magic value. */
+ uint32_t u32Magic;
+ /** The size of the data block following this header.
+ * 0 indicates the end of the stream, while UINT32_MAX indicates
+ * cancelation. */
+ uint32_t cb;
+} TELEPORTERTCPHDR;
+/** Magic value for TELEPORTERTCPHDR::u32Magic. (Egberto Gismonti Amin) */
+#define TELEPORTERTCPHDR_MAGIC UINT32_C(0x19471205)
+/** The max block size. */
+#define TELEPORTERTCPHDR_MAX_SIZE UINT32_C(0x00fffff8)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char g_szWelcome[] = "VirtualBox-Teleporter-1.0\n";
+
+
+/**
+ * Reads a string from the socket.
+ *
+ * @returns VBox status code.
+ *
+ * @param pState The teleporter state structure.
+ * @param pszBuf The output buffer.
+ * @param cchBuf The size of the output buffer.
+ *
+ */
+static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf)
+{
+ char *pszStart = pszBuf;
+ RTSOCKET hSocket = pState->mhSocket;
+
+ AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR);
+ *pszBuf = '\0';
+
+ /* dead simple approach. */
+ for (;;)
+ {
+ char ch;
+ int vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", vrc, pszStart));
+ return vrc;
+ }
+ if ( ch == '\n'
+ || ch == '\0')
+ return VINF_SUCCESS;
+ if (cchBuf <= 1)
+ {
+ LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ *pszBuf++ = ch;
+ *pszBuf = '\0';
+ cchBuf--;
+ }
+}
+
+
+/**
+ * Reads an ACK or NACK.
+ *
+ * @returns S_OK on ACK, E_FAIL+setError() on failure or NACK.
+ * @param pState The teleporter source state.
+ * @param pszWhich Which ACK is this this?
+ * @param pszNAckMsg Optional NACK message.
+ *
+ * @remarks the setError laziness forces this to be a Console member.
+ */
+HRESULT
+Console::i_teleporterSrcReadACK(TeleporterStateSrc *pState, const char *pszWhich, const char *pszNAckMsg /*= NULL*/)
+{
+ char szMsg[256];
+ int vrc = teleporterTcpReadLine(pState, szMsg, sizeof(szMsg));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed reading ACK(%s): %Rrc"), pszWhich, vrc);
+
+ if (!strcmp(szMsg, "ACK"))
+ return S_OK;
+
+ if (!strncmp(szMsg, RT_STR_TUPLE("NACK=")))
+ {
+ char *pszMsgText = strchr(szMsg, ';');
+ if (pszMsgText)
+ *pszMsgText++ = '\0';
+
+ int32_t vrc2;
+ vrc = RTStrToInt32Full(&szMsg[sizeof("NACK=") - 1], 10, &vrc2);
+ if (vrc == VINF_SUCCESS)
+ {
+ /*
+ * Well formed NACK, transform it into an error.
+ */
+ if (pszNAckMsg)
+ {
+ LogRel(("Teleporter: %s: NACK=%Rrc (%d)\n", pszWhich, vrc2, vrc2));
+ return setError(E_FAIL, pszNAckMsg);
+ }
+
+ if (pszMsgText)
+ {
+ pszMsgText = RTStrStrip(pszMsgText);
+ for (size_t off = 0; pszMsgText[off]; off++)
+ if (pszMsgText[off] == '\r')
+ pszMsgText[off] = '\n';
+
+ LogRel(("Teleporter: %s: NACK=%Rrc (%d) - '%s'\n", pszWhich, vrc2, vrc2, pszMsgText));
+ if (strlen(pszMsgText) > 4)
+ return setError(E_FAIL, "%s", pszMsgText);
+ return setError(E_FAIL, "NACK(%s) - %Rrc (%d) '%s'", pszWhich, vrc2, vrc2, pszMsgText);
+ }
+
+ return setError(E_FAIL, "NACK(%s) - %Rrc (%d)", pszWhich, vrc2, vrc2);
+ }
+
+ if (pszMsgText)
+ pszMsgText[-1] = ';';
+ }
+ return setError(E_FAIL, tr("%s: Expected ACK or NACK, got '%s'"), pszWhich, szMsg);
+}
+
+
+/**
+ * Submitts a command to the destination and waits for the ACK.
+ *
+ * @returns S_OK on ACKed command, E_FAIL+setError() on failure.
+ *
+ * @param pState The teleporter source state.
+ * @param pszCommand The command.
+ * @param fWaitForAck Whether to wait for the ACK.
+ *
+ * @remarks the setError laziness forces this to be a Console member.
+ */
+HRESULT Console::i_teleporterSrcSubmitCommand(TeleporterStateSrc *pState, const char *pszCommand, bool fWaitForAck /*= true*/)
+{
+ int vrc = RTTcpSgWriteL(pState->mhSocket, 2, pszCommand, strlen(pszCommand), "\n", sizeof("\n") - 1);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed writing command '%s': %Rrc"), pszCommand, vrc);
+ if (!fWaitForAck)
+ return S_OK;
+ return i_teleporterSrcReadACK(pState, pszCommand);
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnWrite
+ */
+static DECLCALLBACK(int) teleporterTcpOpWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
+{
+ RT_NOREF(offStream);
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ AssertReturn(cbToWrite > 0, VINF_SUCCESS);
+ AssertReturn(cbToWrite < UINT32_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(pState->mfIsSource, VERR_INVALID_HANDLE);
+
+ for (;;)
+ {
+ TELEPORTERTCPHDR Hdr;
+ Hdr.u32Magic = TELEPORTERTCPHDR_MAGIC;
+ Hdr.cb = RT_MIN((uint32_t)cbToWrite, TELEPORTERTCPHDR_MAX_SIZE);
+ int vrc = RTTcpSgWriteL(pState->mhSocket, 2, &Hdr, sizeof(Hdr), pvBuf, (size_t)Hdr.cb);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter/TCP: Write error: %Rrc (cb=%#x)\n", vrc, Hdr.cb));
+ return vrc;
+ }
+ pState->moffStream += Hdr.cb;
+ if (Hdr.cb == cbToWrite)
+ return VINF_SUCCESS;
+
+ /* advance */
+ cbToWrite -= Hdr.cb;
+ pvBuf = (uint8_t const *)pvBuf + Hdr.cb;
+ }
+}
+
+
+/**
+ * Selects and poll for close condition.
+ *
+ * We can use a relatively high poll timeout here since it's only used to get
+ * us out of error paths. In the normal cause of events, we'll get a
+ * end-of-stream header.
+ *
+ * @returns VBox status code.
+ *
+ * @param pState The teleporter state data.
+ */
+static int teleporterTcpReadSelect(TeleporterState *pState)
+{
+ int vrc;
+ do
+ {
+ vrc = RTTcpSelectOne(pState->mhSocket, 1000);
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Header select error: %Rrc\n", vrc));
+ break;
+ }
+ if (pState->mfStopReading)
+ {
+ vrc = VERR_EOF;
+ break;
+ }
+ } while (vrc == VERR_TIMEOUT);
+ return vrc;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnRead
+ */
+static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ RT_NOREF(offStream);
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE);
+
+ for (;;)
+ {
+ int vrc;
+
+ /*
+ * Check for various conditions and may have been signalled.
+ */
+ if (pState->mfEndOfStream)
+ return VERR_EOF;
+ if (pState->mfStopReading)
+ return VERR_EOF;
+ if (pState->mfIOError)
+ return VERR_IO_GEN_FAILURE;
+
+ /*
+ * If there is no more data in the current block, read the next
+ * block header.
+ */
+ if (!pState->mcbReadBlock)
+ {
+ vrc = teleporterTcpReadSelect(pState);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ TELEPORTERTCPHDR Hdr;
+ vrc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL);
+ if (RT_FAILURE(vrc))
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Header read error: %Rrc\n", vrc));
+ return vrc;
+ }
+
+ if (RT_UNLIKELY( Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC
+ || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE
+ || Hdr.cb == 0))
+ {
+ if ( Hdr.u32Magic == TELEPORTERTCPHDR_MAGIC
+ && ( Hdr.cb == 0
+ || Hdr.cb == UINT32_MAX)
+ )
+ {
+ pState->mfEndOfStream = true;
+ pState->mcbReadBlock = 0;
+ return Hdr.cb ? VERR_SSM_CANCELLED : VERR_EOF;
+ }
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb));
+ return VERR_IO_GEN_FAILURE;
+ }
+
+ pState->mcbReadBlock = Hdr.cb;
+ if (pState->mfStopReading)
+ return VERR_EOF;
+ }
+
+ /*
+ * Read more data.
+ */
+ vrc = teleporterTcpReadSelect(pState);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead);
+ vrc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead);
+ if (RT_FAILURE(vrc))
+ {
+ pState->mfIOError = true;
+ LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", vrc, cb));
+ return vrc;
+ }
+ if (pcbRead)
+ {
+ cb = (uint32_t)*pcbRead;
+ pState->moffStream += cb;
+ pState->mcbReadBlock -= cb;
+ return VINF_SUCCESS;
+ }
+ pState->moffStream += cb;
+ pState->mcbReadBlock -= cb;
+ if (cbToRead == cb)
+ return VINF_SUCCESS;
+
+ /* Advance to the next block. */
+ cbToRead -= cb;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnSeek
+ */
+static DECLCALLBACK(int) teleporterTcpOpSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
+{
+ RT_NOREF(pvUser, offSeek, uMethod, poffActual);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnTell
+ */
+static DECLCALLBACK(uint64_t) teleporterTcpOpTell(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ return pState->moffStream;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnSize
+ */
+static DECLCALLBACK(int) teleporterTcpOpSize(void *pvUser, uint64_t *pcb)
+{
+ RT_NOREF(pvUser, pcb);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnIsOk
+ */
+static DECLCALLBACK(int) teleporterTcpOpIsOk(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ if (pState->mfIsSource)
+ {
+ /* Poll for incoming NACKs and errors from the other side */
+ int vrc = RTTcpSelectOne(pState->mhSocket, 0);
+ if (vrc != VERR_TIMEOUT)
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n"));
+ vrc = VERR_SSM_CANCELLED;
+ }
+ else
+ LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", vrc));
+ return vrc;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc SSMSTRMOPS::pfnClose
+ */
+static DECLCALLBACK(int) teleporterTcpOpClose(void *pvUser, bool fCancelled)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+
+ if (pState->mfIsSource)
+ {
+ TELEPORTERTCPHDR EofHdr;
+ EofHdr.u32Magic = TELEPORTERTCPHDR_MAGIC;
+ EofHdr.cb = fCancelled ? UINT32_MAX : 0;
+ int vrc = RTTcpWrite(pState->mhSocket, &EofHdr, sizeof(EofHdr));
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter/TCP: EOF Header write error: %Rrc\n", vrc));
+ return vrc;
+ }
+ }
+ else
+ {
+ ASMAtomicWriteBool(&pState->mfStopReading, true);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Method table for a TCP based stream.
+ */
+static SSMSTRMOPS const g_teleporterTcpOps =
+{
+ SSMSTRMOPS_VERSION,
+ teleporterTcpOpWrite,
+ teleporterTcpOpRead,
+ teleporterTcpOpSeek,
+ teleporterTcpOpTell,
+ teleporterTcpOpSize,
+ teleporterTcpOpIsOk,
+ teleporterTcpOpClose,
+ SSMSTRMOPS_VERSION
+};
+
+
+/**
+ * Progress cancelation callback.
+ */
+static void teleporterProgressCancelCallback(void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM);
+ if (!pState->mfIsSource)
+ {
+ TeleporterStateTrg *pStateTrg = (TeleporterStateTrg *)pState;
+ RTTcpServerShutdown(pStateTrg->mhServer);
+ }
+}
+
+/**
+ * @copydoc PFNVMPROGRESS
+ */
+static DECLCALLBACK(int) teleporterProgressCallback(PUVM pUVM, unsigned uPercent, void *pvUser)
+{
+ TeleporterState *pState = (TeleporterState *)pvUser;
+ if (pState->mptrProgress)
+ {
+ HRESULT hrc = pState->mptrProgress->SetCurrentOperationProgress(uPercent);
+ if (FAILED(hrc))
+ {
+ /* check if the failure was caused by cancellation. */
+ BOOL fCanceled;
+ hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled);
+ if (SUCCEEDED(hrc) && fCanceled)
+ {
+ pState->mpVMM->pfnSSMR3Cancel(pState->mpUVM);
+ return VERR_SSM_CANCELLED;
+ }
+ }
+ }
+
+ NOREF(pUVM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc FNRTTIMERLR
+ */
+static DECLCALLBACK(void) teleporterDstTimeout(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
+{
+ RT_NOREF(hTimerLR, iTick);
+ /* This is harmless for any open connections. */
+ RTTcpServerShutdown((PRTTCPSERVER)pvUser);
+}
+
+
+/**
+ * Do the teleporter.
+ *
+ * @returns VBox status code.
+ * @param pState The teleporter state.
+ */
+HRESULT Console::i_teleporterSrc(TeleporterStateSrc *pState)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /*
+ * Wait for Console::Teleport to change the state.
+ */
+ { AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); }
+
+ BOOL fCanceled = TRUE;
+ HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc))
+ return hrc;
+ if (fCanceled)
+ return setError(E_FAIL, tr("canceled"));
+
+ /*
+ * Try connect to the destination machine, disable Nagle.
+ * (Note. The caller cleans up mhSocket, so we can return without worries.)
+ */
+ int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to connect to port %u on '%s': %Rrc"),
+ pState->muPort, pState->mstrHostname.c_str(), vrc);
+ vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
+ AssertRC(vrc);
+
+ /* Read and check the welcome message. */
+ char szLine[RT_MAX(128, sizeof(g_szWelcome))];
+ RT_ZERO(szLine);
+ vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to read welcome message: %Rrc"), vrc);
+ if (strcmp(szLine, g_szWelcome))
+ return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine);
+
+ /* password */
+ pState->mstrPassword.append('\n');
+ vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length());
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to send password: %Rrc"), vrc);
+
+ /* ACK */
+ hrc = i_teleporterSrcReadACK(pState, "password", tr("Invalid password"));
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Start loading the state.
+ *
+ * Note! The saved state includes vital configuration data which will be
+ * verified against the VM config on the other end. This is all done
+ * in the first pass, so we should fail pretty promptly on misconfig.
+ */
+ hrc = i_teleporterSrcSubmitCommand(pState, "load");
+ if (FAILED(hrc))
+ return hrc;
+
+ RTSocketRetain(pState->mhSocket);
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ vrc = pState->mpVMM->pfnVMR3Teleport(pState->mpUVM,
+ pState->mcMsMaxDowntime,
+ &g_teleporterTcpOps, pvUser,
+ teleporterProgressCallback, pvUser,
+ &pState->mfSuspendedByUs);
+ RTSocketRelease(pState->mhSocket);
+ if (RT_FAILURE(vrc))
+ {
+ if ( vrc == VERR_SSM_CANCELLED
+ && RT_SUCCESS(RTTcpSelectOne(pState->mhSocket, 1)))
+ {
+ hrc = i_teleporterSrcReadACK(pState, "load-complete");
+ if (FAILED(hrc))
+ return hrc;
+ }
+ return setErrorBoth(E_FAIL, vrc, "VMR3Teleport -> %Rrc", vrc);
+ }
+
+ hrc = i_teleporterSrcReadACK(pState, "load-complete");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * We're at the point of no return.
+ */
+ if (FAILED(pState->mptrProgress->NotifyPointOfNoReturn()))
+ {
+ i_teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/);
+ return E_FAIL;
+ }
+
+ /*
+ * Hand over any media which we might be sharing.
+ *
+ * Note! This is only important on localhost teleportations.
+ */
+ /** @todo Maybe we should only do this if it's a local teleportation... */
+ hrc = mControl->UnlockMedia();
+ if (FAILED(hrc))
+ return hrc;
+ pState->mfUnlockedMedia = true;
+
+ hrc = i_teleporterSrcSubmitCommand(pState, "lock-media");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * The FINAL step is giving the target instructions how to proceed with the VM.
+ */
+ if ( vrc == VINF_SSM_LIVE_SUSPENDED
+ || pState->menmOldMachineState == MachineState_Paused)
+ hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-paused");
+ else
+ hrc = i_teleporterSrcSubmitCommand(pState, "hand-over-resume");
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * teleporterSrcThreadWrapper will do the automatic power off because it
+ * has to release the AutoVMCaller.
+ */
+ return S_OK;
+}
+
+
+/**
+ * Static thread method wrapper.
+ *
+ * @returns VINF_SUCCESS (ignored).
+ * @param hThreadSelf The thread.
+ * @param pvUser Pointer to a TeleporterStateSrc instance.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_teleporterSrcThreadWrapper(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ TeleporterStateSrc *pState = (TeleporterStateSrc *)pvUser;
+
+ /*
+ * Console::teleporterSrc does the work, we just grab onto the VM handle
+ * and do the cleanups afterwards.
+ */
+ SafeVMPtr ptrVM(pState->mptrConsole);
+ HRESULT hrc = ptrVM.hrc();
+
+ if (SUCCEEDED(hrc))
+ hrc = pState->mptrConsole->i_teleporterSrc(pState);
+
+ /* Close the connection ASAP on so that the other side can complete. */
+ if (pState->mhSocket != NIL_RTSOCKET)
+ {
+ RTTcpClientClose(pState->mhSocket);
+ pState->mhSocket = NIL_RTSOCKET;
+ }
+
+ /* Aaarg! setMachineState trashes error info on Windows, so we have to
+ complete things here on failure instead of right before cleanup. */
+ if (FAILED(hrc))
+ pState->mptrProgress->i_notifyComplete(hrc);
+
+ /* We can no longer be canceled (success), or it doesn't matter any longer (failure). */
+ pState->mptrProgress->i_setCancelCallback(NULL, NULL);
+
+ /*
+ * Write lock the console before resetting mptrCancelableProgress and
+ * fixing the state.
+ */
+ AutoWriteLock autoLock(pState->mptrConsole COMMA_LOCKVAL_SRC_POS);
+ pState->mptrConsole->mptrCancelableProgress.setNull();
+
+ VMSTATE const enmVMState = pState->mpVMM->pfnVMR3GetStateU(pState->mpUVM);
+ MachineState_T const enmMachineState = pState->mptrConsole->mMachineState;
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Automatically shut down the VM on success.
+ *
+ * Note! We have to release the VM caller object or we'll deadlock in
+ * powerDown.
+ */
+ AssertLogRelMsg(enmVMState == VMSTATE_SUSPENDED, ("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState)));
+ AssertLogRelMsg(enmMachineState == MachineState_TeleportingPausedVM, ("%s\n", ::stringifyMachineState(enmMachineState)));
+
+ ptrVM.release();
+
+ pState->mptrConsole->mVMIsAlreadyPoweringOff = true; /* (Make sure we stick in the TeleportingPausedVM state.) */
+ autoLock.release();
+
+ hrc = pState->mptrConsole->i_powerDown();
+
+ autoLock.acquire();
+ pState->mptrConsole->mVMIsAlreadyPoweringOff = false;
+
+ pState->mptrProgress->i_notifyComplete(hrc);
+ }
+ else
+ {
+ /*
+ * Work the state machinery on failure.
+ *
+ * If the state is no longer 'Teleporting*', some other operation has
+ * canceled us and there is nothing we need to do here. In all other
+ * cases, we've failed one way or another.
+ */
+ if ( enmMachineState == MachineState_Teleporting
+ || enmMachineState == MachineState_TeleportingPausedVM
+ )
+ {
+ if (pState->mfUnlockedMedia)
+ {
+ ErrorInfoKeeper Oak;
+ HRESULT hrc2 = pState->mptrConsole->mControl->LockMedia();
+ if (FAILED(hrc2))
+ {
+ uint64_t StartMS = RTTimeMilliTS();
+ do
+ {
+ RTThreadSleep(2);
+ hrc2 = pState->mptrConsole->mControl->LockMedia();
+ } while ( FAILED(hrc2)
+ && RTTimeMilliTS() - StartMS < 2000);
+ }
+ if (SUCCEEDED(hrc2))
+ pState->mfUnlockedMedia = true;
+ else
+ LogRel(("FATAL ERROR: Failed to re-take the media locks. hrc2=%Rhrc\n", hrc2));
+ }
+
+ switch (enmVMState)
+ {
+ case VMSTATE_RUNNING:
+ case VMSTATE_RUNNING_LS:
+ case VMSTATE_DEBUGGING:
+ case VMSTATE_DEBUGGING_LS:
+ case VMSTATE_POWERING_OFF:
+ case VMSTATE_POWERING_OFF_LS:
+ case VMSTATE_RESETTING:
+ case VMSTATE_RESETTING_LS:
+ case VMSTATE_SOFT_RESETTING:
+ case VMSTATE_SOFT_RESETTING_LS:
+ Assert(!pState->mfSuspendedByUs);
+ Assert(!pState->mfUnlockedMedia);
+ pState->mptrConsole->i_setMachineState(MachineState_Running);
+ break;
+
+ case VMSTATE_GURU_MEDITATION:
+ case VMSTATE_GURU_MEDITATION_LS:
+ pState->mptrConsole->i_setMachineState(MachineState_Stuck);
+ break;
+
+ case VMSTATE_FATAL_ERROR:
+ case VMSTATE_FATAL_ERROR_LS:
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ break;
+
+ default:
+ AssertMsgFailed(("%s\n", pState->mpVMM->pfnVMR3GetStateName(enmVMState)));
+ RT_FALL_THRU();
+ case VMSTATE_SUSPENDED:
+ case VMSTATE_SUSPENDED_LS:
+ case VMSTATE_SUSPENDING:
+ case VMSTATE_SUSPENDING_LS:
+ case VMSTATE_SUSPENDING_EXT_LS:
+ if (!pState->mfUnlockedMedia)
+ {
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ if (pState->mfSuspendedByUs)
+ {
+ autoLock.release();
+ int vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORT_FAILED);
+ AssertLogRelMsgRC(vrc, ("VMR3Resume -> %Rrc\n", vrc));
+ autoLock.acquire();
+ }
+ }
+ else
+ {
+ /* Faking a guru meditation is the best I can think of doing here... */
+ pState->mptrConsole->i_setMachineState(MachineState_Stuck);
+ }
+ break;
+ }
+ }
+ }
+ autoLock.release();
+
+ /*
+ * Cleanup.
+ */
+ Assert(pState->mhSocket == NIL_RTSOCKET);
+ delete pState;
+
+ return VINF_SUCCESS; /* ignored */
+}
+
+
+/**
+ * Start teleporter to the specified target.
+ *
+ * @returns COM status code.
+ *
+ * @param aHostname The name of the target host.
+ * @param aTcpport The TCP port number.
+ * @param aPassword The password.
+ * @param aMaxDowntime Max allowed "downtime" in milliseconds.
+ * @param aProgress Where to return the progress object.
+ */
+HRESULT Console::teleport(const com::Utf8Str &aHostname, ULONG aTcpport, const com::Utf8Str &aPassword,
+ ULONG aMaxDowntime, ComPtr<IProgress> &aProgress)
+{
+ /*
+ * Validate parameters, check+hold object status, write lock the object
+ * and validate the state.
+ */
+ Utf8Str strPassword(aPassword);
+ if (!strPassword.isEmpty())
+ {
+ if (VBoxIsPasswordHashed(&strPassword))
+ return setError(E_INVALIDARG, tr("The specified password resembles a hashed password, expected plain text"));
+ VBoxHashPassword(&strPassword);
+ }
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("mMachineState=%d\n", mMachineState));
+
+ switch (mMachineState)
+ {
+ case MachineState_Running:
+ case MachineState_Paused:
+ break;
+
+ default:
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Invalid machine state: %s (must be Running or Paused)"),
+ Global::stringifyMachineState(mMachineState));
+ }
+
+
+ /*
+ * Create a progress object, spawn a worker thread and change the state.
+ * Note! The thread won't start working until we release the lock.
+ */
+ LogFlowThisFunc(("Initiating TELEPORT request...\n"));
+
+ ComObjPtr<Progress> ptrProgress;
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress->init(static_cast<IConsole *>(this),
+ Bstr(tr("Teleporter")).raw(),
+ TRUE /*aCancelable*/);
+ if (FAILED(hrc))
+ return hrc;
+
+ TeleporterStateSrc *pState = new TeleporterStateSrc(this, mpUVM, mpVMM, ptrProgress, mMachineState);
+ pState->mstrPassword = strPassword;
+ pState->mstrHostname = aHostname;
+ pState->muPort = aTcpport;
+ pState->mcMsMaxDowntime = aMaxDowntime;
+
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ ptrProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser);
+
+ int vrc = RTThreadCreate(NULL, Console::i_teleporterSrcThreadWrapper, (void *)pState, 0 /*cbStack*/,
+ RTTHREADTYPE_EMULATION, 0 /*fFlags*/, "Teleport");
+ if (RT_SUCCESS(vrc))
+ {
+ if (mMachineState == MachineState_Running)
+ hrc = i_setMachineState(MachineState_Teleporting);
+ else
+ hrc = i_setMachineState(MachineState_TeleportingPausedVM);
+ if (SUCCEEDED(hrc))
+ {
+ ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ mptrCancelableProgress = aProgress;
+ }
+ else
+ ptrProgress->Cancel();
+ }
+ else
+ {
+ ptrProgress->i_setCancelCallback(NULL, NULL);
+ delete pState;
+ hrc = setErrorBoth(E_FAIL, vrc, "RTThreadCreate -> %Rrc", vrc);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Creates a TCP server that listens for the source machine and passes control
+ * over to Console::teleporterTrgServeConnection().
+ *
+ * @returns VBox status code.
+ * @param pUVM The user-mode VM handle
+ * @param pVMM The VMM vtable.
+ * @param pMachine The IMachine for the virtual machine.
+ * @param pErrorMsg Pointer to the error string for VMSetError.
+ * @param fStartPaused Whether to start it in the Paused (true) or
+ * Running (false) state,
+ * @param pProgress Pointer to the progress object.
+ * @param pfPowerOffOnFailure Whether the caller should power off
+ * the VM on failure.
+ *
+ * @remarks The caller expects error information to be set on failure.
+ * @todo Check that all the possible failure paths sets error info...
+ */
+HRESULT Console::i_teleporterTrg(PUVM pUVM, PCVMMR3VTABLE pVMM, IMachine *pMachine, Utf8Str *pErrorMsg, bool fStartPaused,
+ Progress *pProgress, bool *pfPowerOffOnFailure)
+{
+ LogThisFunc(("pUVM=%p pVMM=%p pMachine=%p fStartPaused=%RTbool pProgress=%p\n", pUVM, pVMM, pMachine, fStartPaused, pProgress));
+
+ *pfPowerOffOnFailure = true;
+
+ /*
+ * Get the config.
+ */
+ ULONG uPort;
+ HRESULT hrc = pMachine->COMGETTER(TeleporterPort)(&uPort);
+ if (FAILED(hrc))
+ return hrc;
+ ULONG const uPortOrg = uPort;
+
+ Bstr bstrAddress;
+ hrc = pMachine->COMGETTER(TeleporterAddress)(bstrAddress.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strAddress(bstrAddress);
+ const char *pszAddress = strAddress.isEmpty() ? NULL : strAddress.c_str();
+
+ Bstr bstrPassword;
+ hrc = pMachine->COMGETTER(TeleporterPassword)(bstrPassword.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+ Utf8Str strPassword(bstrPassword);
+ strPassword.append('\n'); /* To simplify password checking. */
+
+ /*
+ * Create the TCP server.
+ */
+ int vrc = VINF_SUCCESS; /* Shut up MSC */
+ PRTTCPSERVER hServer = NULL; /* ditto */
+ if (uPort)
+ vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
+ else
+ {
+ for (int cTries = 10240; cTries > 0; cTries--)
+ {
+ uPort = RTRandU32Ex(cTries >= 8192 ? 49152 : 1024, 65534);
+ vrc = RTTcpServerCreateEx(pszAddress, uPort, &hServer);
+ if (vrc != VERR_NET_ADDRESS_IN_USE)
+ break;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = pMachine->COMSETTER(TeleporterPort)(uPort);
+ if (FAILED(hrc))
+ {
+ RTTcpServerDestroy(hServer);
+ return hrc;
+ }
+ }
+ }
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_FAIL, vrc, tr("RTTcpServerCreateEx failed with status %Rrc"), vrc);
+
+ /*
+ * Create a one-shot timer for timing out after 5 mins.
+ */
+ RTTIMERLR hTimerLR;
+ vrc = RTTimerLRCreateEx(&hTimerLR, 0 /*ns*/, RTTIMER_FLAGS_CPU_ANY, teleporterDstTimeout, hServer);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTTimerLRStart(hTimerLR, 5*60*UINT64_C(1000000000) /*ns*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Do the job, when it returns we're done.
+ */
+ TeleporterStateTrg theState(this, pUVM, pVMM, pProgress, pMachine, mControl, &hTimerLR, fStartPaused);
+ theState.mstrPassword = strPassword;
+ theState.mhServer = hServer;
+
+ void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(&theState));
+ if (pProgress->i_setCancelCallback(teleporterProgressCancelCallback, pvUser))
+ {
+ LogRel(("Teleporter: Waiting for incoming VM...\n"));
+ hrc = pProgress->SetNextOperation(Bstr(tr("Waiting for incoming VM")).raw(), 1);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = RTTcpServerListen(hServer, Console::i_teleporterTrgServeConnection, &theState);
+ pProgress->i_setCancelCallback(NULL, NULL);
+
+ if (vrc == VERR_TCP_SERVER_STOP)
+ {
+ vrc = theState.mRc;
+ /* Power off the VM on failure unless the state callback
+ already did that. */
+ *pfPowerOffOnFailure = false;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ {
+ VMSTATE enmVMState = pVMM->pfnVMR3GetStateU(pUVM);
+ if ( enmVMState != VMSTATE_OFF
+ && enmVMState != VMSTATE_POWERING_OFF)
+ *pfPowerOffOnFailure = true;
+
+ /* Set error. */
+ if (pErrorMsg->length())
+ hrc = setError(E_FAIL, "%s", pErrorMsg->c_str());
+ else
+ hrc = setError(E_FAIL, tr("Teleporation failed (%Rrc)"), vrc);
+ }
+ }
+ else if (vrc == VERR_TCP_SERVER_SHUTDOWN)
+ {
+ BOOL fCanceled = TRUE;
+ hrc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc) || fCanceled)
+ hrc = setError(E_FAIL, tr("Teleporting canceled"));
+ else
+ hrc = setError(E_FAIL, tr("Teleporter timed out waiting for incoming connection"));
+ LogRel(("Teleporter: RTTcpServerListen aborted - %Rrc\n", vrc));
+ }
+ else
+ {
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Unexpected RTTcpServerListen status code %Rrc"), vrc);
+ LogRel(("Teleporter: Unexpected RTTcpServerListen vrc: %Rrc\n", vrc));
+ }
+ }
+ else
+ LogThisFunc(("SetNextOperation failed, %Rhrc\n", hrc));
+ }
+ else
+ {
+ LogThisFunc(("Canceled - check point #1\n"));
+ hrc = setError(E_FAIL, tr("Teleporting canceled"));
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRStart -> %Rrc", vrc);
+
+ RTTimerLRDestroy(hTimerLR);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, "RTTimerLRCreate -> %Rrc", vrc);
+ RTTcpServerDestroy(hServer);
+
+ /*
+ * If we change TeleporterPort above, set it back to it's original
+ * value before returning.
+ */
+ if (uPortOrg != uPort)
+ {
+ ErrorInfoKeeper Eik;
+ pMachine->COMSETTER(TeleporterPort)(uPortOrg);
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Unlock the media.
+ *
+ * This is used in error paths.
+ *
+ * @param pState The teleporter state.
+ */
+static void teleporterTrgUnlockMedia(TeleporterStateTrg *pState)
+{
+ if (pState->mfLockedMedia)
+ {
+ pState->mpControl->UnlockMedia();
+ pState->mfLockedMedia = false;
+ }
+}
+
+
+static int teleporterTcpWriteACK(TeleporterStateTrg *pState, bool fAutomaticUnlock = true)
+{
+ int vrc = RTTcpWrite(pState->mhSocket, "ACK\n", sizeof("ACK\n") - 1);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: RTTcpWrite(,ACK,) -> %Rrc\n", vrc));
+ if (fAutomaticUnlock)
+ teleporterTrgUnlockMedia(pState);
+ }
+ return vrc;
+}
+
+
+static int teleporterTcpWriteNACK(TeleporterStateTrg *pState, int32_t rc2, const char *pszMsgText = NULL)
+{
+ /*
+ * Unlock media sending the NACK. That way the other doesn't have to spin
+ * waiting to regain the locks.
+ */
+ teleporterTrgUnlockMedia(pState);
+
+ char szMsg[256];
+ size_t cch;
+ if (pszMsgText && *pszMsgText)
+ {
+ cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d;%s\n", rc2, pszMsgText);
+ for (size_t off = 6; off + 1 < cch; off++)
+ if (szMsg[off] == '\n')
+ szMsg[off] = '\r';
+ }
+ else
+ cch = RTStrPrintf(szMsg, sizeof(szMsg), "NACK=%d\n", rc2);
+ int vrc = RTTcpWrite(pState->mhSocket, szMsg, cch);
+ if (RT_FAILURE(vrc))
+ LogRel(("Teleporter: RTTcpWrite(,%s,%zu) -> %Rrc\n", szMsg, cch, vrc));
+ return vrc;
+}
+
+
+/**
+ * @copydoc FNRTTCPSERVE
+ *
+ * @returns VINF_SUCCESS or VERR_TCP_SERVER_STOP.
+ */
+/*static*/ DECLCALLBACK(int)
+Console::i_teleporterTrgServeConnection(RTSOCKET hSocket, void *pvUser)
+{
+ TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser;
+ pState->mhSocket = hSocket;
+
+ /*
+ * Disable Nagle and say hello.
+ */
+ int vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
+ AssertRC(vrc);
+ vrc = RTTcpWrite(hSocket, g_szWelcome, sizeof(g_szWelcome) - 1);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Password (includes '\n', see i_teleporterTrg).
+ */
+ const char *pszPassword = pState->mstrPassword.c_str();
+ unsigned off = 0;
+ while (pszPassword[off])
+ {
+ char ch;
+ vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ if ( RT_FAILURE(vrc)
+ || pszPassword[off] != ch)
+ {
+ if (RT_FAILURE(vrc))
+ LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc));
+ else
+ {
+ /* Must read the whole password before NACK'ing it. */
+ size_t const cchMaxRead = RT_ALIGN_Z(pState->mstrPassword.length() * 3, _1K);
+ while (off < cchMaxRead && RT_SUCCESS(vrc) && ch != '\n')
+ {
+ vrc = RTTcpRead(hSocket, &ch, sizeof(ch), NULL);
+ off++;
+ }
+ LogRel(("Teleporter: Invalid password\n"));
+ }
+ RTThreadSleep(RTRandU32Ex(64, 1024)); /* stagger retries */
+ teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE);
+ return VINF_SUCCESS;
+ }
+ off++;
+ }
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_FAILURE(vrc))
+ return VINF_SUCCESS;
+
+ /*
+ * Update the progress bar, with peer name if available.
+ */
+ HRESULT hrc;
+ RTNETADDR Addr;
+ vrc = RTTcpGetPeerAddress(hSocket, &Addr);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Teleporter: Incoming VM from %RTnaddr!\n", &Addr));
+ hrc = pState->mptrProgress->SetNextOperation(BstrFmt(tr("Teleporting VM from %RTnaddr"), &Addr).raw(), 8);
+ }
+ else
+ {
+ LogRel(("Teleporter: Incoming VM!\n"));
+ hrc = pState->mptrProgress->SetNextOperation(Bstr(tr("Teleporting VM")).raw(), 8);
+ }
+ AssertMsg(SUCCEEDED(hrc) || hrc == E_FAIL, ("%Rhrc\n", hrc));
+
+ /*
+ * Stop the server and cancel the timeout timer.
+ *
+ * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
+ * to it we must not return that value!
+ */
+ RTTcpServerShutdown(pState->mhServer);
+ RTTimerLRDestroy(*pState->mphTimerLR);
+ *pState->mphTimerLR = NIL_RTTIMERLR;
+
+ /*
+ * Command processing loop.
+ */
+ bool fDone = false;
+ for (;;)
+ {
+ char szCmd[128];
+ vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd));
+ if (RT_FAILURE(vrc))
+ break;
+
+ if (!strcmp(szCmd, "load"))
+ {
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_FAILURE(vrc))
+ break;
+
+ int vrc2 = pState->mpVMM->pfnVMR3AtErrorRegister(pState->mpUVM, Console::i_genericVMSetErrorCallback,
+ &pState->mErrorText);
+ AssertRC(vrc2);
+ RTSocketRetain(pState->mhSocket); /* For concurrent access by I/O thread and EMT. */
+ pState->moffStream = 0;
+
+ void *pvUser2 = static_cast<void *>(static_cast<TeleporterState *>(pState));
+ vrc = pState->mpVMM->pfnVMR3LoadFromStream(pState->mpUVM,
+ &g_teleporterTcpOps, pvUser2,
+ teleporterProgressCallback, pvUser2,
+ true /*fTeleporting*/);
+
+ RTSocketRelease(pState->mhSocket);
+ vrc2 = pState->mpVMM->pfnVMR3AtErrorDeregister(pState->mpUVM, Console::i_genericVMSetErrorCallback, &pState->mErrorText);
+ AssertRC(vrc2);
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc));
+ teleporterTcpWriteNACK(pState, vrc, pState->mErrorText.c_str());
+ break;
+ }
+
+ /* The EOS might not have been read, make sure it is. */
+ pState->mfStopReading = false;
+ size_t cbRead;
+ vrc = teleporterTcpOpRead(pvUser2, pState->moffStream, szCmd, 1, &cbRead);
+ if (vrc != VERR_EOF)
+ {
+ LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc));
+ teleporterTcpWriteNACK(pState, vrc);
+ break;
+ }
+
+ vrc = teleporterTcpWriteACK(pState);
+ }
+ else if (!strcmp(szCmd, "cancel"))
+ {
+ /* Don't ACK this. */
+ LogRel(("Teleporter: Received cancel command.\n"));
+ vrc = VERR_SSM_CANCELLED;
+ }
+ else if (!strcmp(szCmd, "lock-media"))
+ {
+ hrc = pState->mpControl->LockMedia();
+ if (SUCCEEDED(hrc))
+ {
+ pState->mfLockedMedia = true;
+ vrc = teleporterTcpWriteACK(pState);
+ }
+ else
+ {
+ vrc = VERR_FILE_LOCK_FAILED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+ }
+ else if ( !strcmp(szCmd, "hand-over-resume")
+ || !strcmp(szCmd, "hand-over-paused"))
+ {
+ /*
+ * Point of no return.
+ *
+ * Note! Since we cannot tell whether a VMR3Resume failure is
+ * destructive for the source or not, we have little choice
+ * but to ACK it first and take any failures locally.
+ *
+ * Ideally, we should try resume it first and then ACK (or
+ * NACK) the request since this would reduce latency and
+ * make it possible to recover from some VMR3Resume failures.
+ */
+ if ( SUCCEEDED(pState->mptrProgress->NotifyPointOfNoReturn())
+ && pState->mfLockedMedia)
+ {
+ vrc = teleporterTcpWriteACK(pState);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!strcmp(szCmd, "hand-over-resume"))
+ vrc = pState->mpVMM->pfnVMR3Resume(pState->mpUVM, VMRESUMEREASON_TELEPORTED);
+ else
+ pState->mptrConsole->i_setMachineState(MachineState_Paused);
+ fDone = true;
+ break;
+ }
+ }
+ else
+ {
+ vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+ }
+ else
+ {
+ LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd));
+ vrc = VERR_NOT_IMPLEMENTED;
+ teleporterTcpWriteNACK(pState, vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if (RT_SUCCESS(vrc) && !fDone)
+ vrc = VERR_WRONG_ORDER;
+ if (RT_FAILURE(vrc))
+ teleporterTrgUnlockMedia(pState);
+
+ pState->mRc = vrc;
+ pState->mhSocket = NIL_RTSOCKET;
+ LogFlowFunc(("returns mRc=%Rrc\n", vrc));
+ return VERR_TCP_SERVER_STOP;
+}
+
diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp
new file mode 100644
index 00000000..a0b3c522
--- /dev/null
+++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp
@@ -0,0 +1,4059 @@
+/* $Id: ConsoleVRDPServer.cpp $ */
+/** @file
+ * VBox Console VRDP helper class.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_CONSOLE
+#include "LoggingNew.h"
+
+#include "ConsoleVRDPServer.h"
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "KeyboardImpl.h"
+#include "MouseImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+#include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_EXTPACK
+# include "ExtPackManagerImpl.h"
+#endif
+#include "VMMDev.h"
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "UsbWebcamInterface.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+
+#include <iprt/asm.h>
+#include <iprt/alloca.h>
+#include <iprt/ldr.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/cpp/utils.h>
+
+#include <VBox/err.h>
+#include <VBox/RemoteDesktop/VRDEOrders.h>
+#include <VBox/com/listeners.h>
+
+
+class VRDPConsoleListener
+{
+public:
+ VRDPConsoleListener()
+ {
+ }
+
+ virtual ~VRDPConsoleListener()
+ {
+ }
+
+ HRESULT init(ConsoleVRDPServer *server)
+ {
+ m_server = server;
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnMousePointerShapeChanged:
+ {
+ ComPtr<IMousePointerShapeChangedEvent> mpscev = aEvent;
+ Assert(mpscev);
+ BOOL visible, alpha;
+ ULONG xHot, yHot, width, height;
+ com::SafeArray <BYTE> shape;
+
+ mpscev->COMGETTER(Visible)(&visible);
+ mpscev->COMGETTER(Alpha)(&alpha);
+ mpscev->COMGETTER(Xhot)(&xHot);
+ mpscev->COMGETTER(Yhot)(&yHot);
+ mpscev->COMGETTER(Width)(&width);
+ mpscev->COMGETTER(Height)(&height);
+ mpscev->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
+
+ m_server->onMousePointerShapeChange(visible, alpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape));
+ break;
+ }
+ case VBoxEventType_OnMouseCapabilityChanged:
+ {
+ ComPtr<IMouseCapabilityChangedEvent> mccev = aEvent;
+ Assert(mccev);
+ if (m_server)
+ {
+ BOOL fAbsoluteMouse;
+ mccev->COMGETTER(SupportsAbsolute)(&fAbsoluteMouse);
+ m_server->NotifyAbsoluteMouse(!!fAbsoluteMouse);
+ }
+ break;
+ }
+ case VBoxEventType_OnKeyboardLedsChanged:
+ {
+ ComPtr<IKeyboardLedsChangedEvent> klcev = aEvent;
+ Assert(klcev);
+
+ if (m_server)
+ {
+ BOOL fNumLock, fCapsLock, fScrollLock;
+ klcev->COMGETTER(NumLock)(&fNumLock);
+ klcev->COMGETTER(CapsLock)(&fCapsLock);
+ klcev->COMGETTER(ScrollLock)(&fScrollLock);
+ m_server->NotifyKeyboardLedsChange(fNumLock, fCapsLock, fScrollLock);
+ }
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+
+private:
+ ConsoleVRDPServer *m_server;
+};
+
+typedef ListenerImpl<VRDPConsoleListener, ConsoleVRDPServer*> VRDPConsoleListenerImpl;
+
+VBOX_LISTENER_DECLARE(VRDPConsoleListenerImpl)
+
+#ifdef DEBUG_sunlover
+#define LOGDUMPPTR Log
+void dumpPointer(const uint8_t *pu8Shape, uint32_t width, uint32_t height, bool fXorMaskRGB32)
+{
+ unsigned i;
+
+ const uint8_t *pu8And = pu8Shape;
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu8And));
+ for (j = 0; j < (width + 7) / 8; j++)
+ {
+ unsigned k;
+ for (k = 0; k < 8; k++)
+ {
+ LOGDUMPPTR(("%d", ((*pu8And) & (1 << (7 - k)))? 1: 0));
+ }
+
+ pu8And++;
+ }
+ LOGDUMPPTR(("\n"));
+ }
+
+ if (fXorMaskRGB32)
+ {
+ uint32_t *pu32Xor = (uint32_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3));
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu32Xor));
+ for (j = 0; j < width; j++)
+ {
+ LOGDUMPPTR(("%08X", *pu32Xor++));
+ }
+ LOGDUMPPTR(("\n"));
+ }
+ }
+ else
+ {
+ /* RDP 24 bit RGB mask. */
+ uint8_t *pu8Xor = (uint8_t*)(pu8Shape + ((((width + 7) / 8) * height + 3) & ~3));
+ for (i = 0; i < height; i++)
+ {
+ unsigned j;
+ LOGDUMPPTR(("%p: ", pu8Xor));
+ for (j = 0; j < width; j++)
+ {
+ LOGDUMPPTR(("%02X%02X%02X", pu8Xor[2], pu8Xor[1], pu8Xor[0]));
+ pu8Xor += 3;
+ }
+ LOGDUMPPTR(("\n"));
+ }
+ }
+}
+#else
+#define dumpPointer(a, b, c, d) do {} while (0)
+#endif /* DEBUG_sunlover */
+
+static void findTopLeftBorder(const uint8_t *pu8AndMask, const uint8_t *pu8XorMask, uint32_t width,
+ uint32_t height, uint32_t *pxSkip, uint32_t *pySkip)
+{
+ /*
+ * Find the top border of the AND mask. First assign to special value.
+ */
+ uint32_t ySkipAnd = UINT32_MAX;
+
+ const uint8_t *pu8And = pu8AndMask;
+ const uint32_t cbAndRow = (width + 7) / 8;
+ const uint8_t maskLastByte = (uint8_t)( 0xFF << (cbAndRow * 8 - width) );
+
+ Assert(cbAndRow > 0);
+
+ unsigned y;
+ unsigned x;
+
+ for (y = 0; y < height && ySkipAnd == ~(uint32_t)0; y++, pu8And += cbAndRow)
+ {
+ /* For each complete byte in the row. */
+ for (x = 0; x < cbAndRow - 1; x++)
+ {
+ if (pu8And[x] != 0xFF)
+ {
+ ySkipAnd = y;
+ break;
+ }
+ }
+
+ if (ySkipAnd == ~(uint32_t)0)
+ {
+ /* Last byte. */
+ if ((pu8And[cbAndRow - 1] & maskLastByte) != maskLastByte)
+ {
+ ySkipAnd = y;
+ }
+ }
+ }
+
+ if (ySkipAnd == ~(uint32_t)0)
+ {
+ ySkipAnd = 0;
+ }
+
+ /*
+ * Find the left border of the AND mask.
+ */
+ uint32_t xSkipAnd = UINT32_MAX;
+
+ /* For all bit columns. */
+ for (x = 0; x < width && xSkipAnd == ~(uint32_t)0; x++)
+ {
+ pu8And = pu8AndMask + x/8; /* Currently checking byte. */
+ uint8_t mask = 1 << (7 - x%8); /* Currently checking bit in the byte. */
+
+ for (y = ySkipAnd; y < height; y++, pu8And += cbAndRow)
+ {
+ if ((*pu8And & mask) == 0)
+ {
+ xSkipAnd = x;
+ break;
+ }
+ }
+ }
+
+ if (xSkipAnd == ~(uint32_t)0)
+ {
+ xSkipAnd = 0;
+ }
+
+ /*
+ * Find the XOR mask top border.
+ */
+ uint32_t ySkipXor = UINT32_MAX;
+
+ uint32_t *pu32XorStart = (uint32_t *)pu8XorMask;
+
+ uint32_t *pu32Xor = pu32XorStart;
+
+ for (y = 0; y < height && ySkipXor == ~(uint32_t)0; y++, pu32Xor += width)
+ {
+ for (x = 0; x < width; x++)
+ {
+ if (pu32Xor[x] != 0)
+ {
+ ySkipXor = y;
+ break;
+ }
+ }
+ }
+
+ if (ySkipXor == ~(uint32_t)0)
+ {
+ ySkipXor = 0;
+ }
+
+ /*
+ * Find the left border of the XOR mask.
+ */
+ uint32_t xSkipXor = ~(uint32_t)0;
+
+ /* For all columns. */
+ for (x = 0; x < width && xSkipXor == ~(uint32_t)0; x++)
+ {
+ pu32Xor = pu32XorStart + x; /* Currently checking dword. */
+
+ for (y = ySkipXor; y < height; y++, pu32Xor += width)
+ {
+ if (*pu32Xor != 0)
+ {
+ xSkipXor = x;
+ break;
+ }
+ }
+ }
+
+ if (xSkipXor == ~(uint32_t)0)
+ {
+ xSkipXor = 0;
+ }
+
+ *pxSkip = RT_MIN(xSkipAnd, xSkipXor);
+ *pySkip = RT_MIN(ySkipAnd, ySkipXor);
+}
+
+/* Generate an AND mask for alpha pointers here, because
+ * guest driver does not do that correctly for Vista pointers.
+ * Similar fix, changing the alpha threshold, could be applied
+ * for the guest driver, but then additions reinstall would be
+ * necessary, which we try to avoid.
+ */
+static void mousePointerGenerateANDMask(uint8_t *pu8DstAndMask, int cbDstAndMask, const uint8_t *pu8SrcAlpha, int w, int h)
+{
+ memset(pu8DstAndMask, 0xFF, cbDstAndMask);
+
+ int y;
+ for (y = 0; y < h; y++)
+ {
+ uint8_t bitmask = 0x80;
+
+ int x;
+ for (x = 0; x < w; x++, bitmask >>= 1)
+ {
+ if (bitmask == 0)
+ {
+ bitmask = 0x80;
+ }
+
+ /* Whether alpha channel value is not transparent enough for the pixel to be seen. */
+ if (pu8SrcAlpha[x * 4 + 3] > 0x7f)
+ {
+ pu8DstAndMask[x / 8] &= ~bitmask;
+ }
+ }
+
+ /* Point to next source and dest scans. */
+ pu8SrcAlpha += w * 4;
+ pu8DstAndMask += (w + 7) / 8;
+ }
+}
+
+void ConsoleVRDPServer::onMousePointerShapeChange(BOOL visible,
+ BOOL alpha,
+ ULONG xHot,
+ ULONG yHot,
+ ULONG width,
+ ULONG height,
+ ComSafeArrayIn(BYTE,inShape))
+{
+ Log9(("VRDPConsoleListener::OnMousePointerShapeChange: %d, %d, %lux%lu, @%lu,%lu\n",
+ visible, alpha, width, height, xHot, yHot));
+
+ com::SafeArray <BYTE> aShape(ComSafeArrayInArg(inShape));
+ if (aShape.size() == 0)
+ {
+ if (!visible)
+ {
+ MousePointerHide();
+ }
+ }
+ else if (width != 0 && height != 0)
+ {
+ uint8_t* shape = aShape.raw();
+
+ dumpPointer(shape, width, height, true);
+
+ /* Try the new interface. */
+ if (MousePointer(alpha, xHot, yHot, width, height, shape) == VINF_SUCCESS)
+ {
+ return;
+ }
+
+ /* Continue with the old interface. */
+
+ /* Pointer consists of 1 bpp AND and 24 BPP XOR masks.
+ * 'shape' AND mask followed by XOR mask.
+ * XOR mask contains 32 bit (lsb)BGR0(msb) values.
+ *
+ * We convert this to RDP color format which consist of
+ * one bpp AND mask and 24 BPP (BGR) color XOR image.
+ *
+ * RDP clients expect 8 aligned width and height of
+ * pointer (preferably 32x32).
+ *
+ * They even contain bugs which do not appear for
+ * 32x32 pointers but would appear for a 41x32 one.
+ *
+ * So set pointer size to 32x32. This can be done safely
+ * because most pointers are 32x32.
+ */
+
+ int cbDstAndMask = (((width + 7) / 8) * height + 3) & ~3;
+
+ uint8_t *pu8AndMask = shape;
+ uint8_t *pu8XorMask = shape + cbDstAndMask;
+
+ if (alpha)
+ {
+ pu8AndMask = (uint8_t*)alloca(cbDstAndMask);
+
+ mousePointerGenerateANDMask(pu8AndMask, cbDstAndMask, pu8XorMask, width, height);
+ }
+
+ /* Windows guest alpha pointers are wider than 32 pixels.
+ * Try to find out the top-left border of the pointer and
+ * then copy only meaningful bits. All complete top rows
+ * and all complete left columns where (AND == 1 && XOR == 0)
+ * are skipped. Hot spot is adjusted.
+ */
+ uint32_t ySkip = 0; /* How many rows to skip at the top. */
+ uint32_t xSkip = 0; /* How many columns to skip at the left. */
+
+ findTopLeftBorder(pu8AndMask, pu8XorMask, width, height, &xSkip, &ySkip);
+
+ /* Must not skip the hot spot. */
+ xSkip = RT_MIN(xSkip, xHot);
+ ySkip = RT_MIN(ySkip, yHot);
+
+ /*
+ * Compute size and allocate memory for the pointer.
+ */
+ const uint32_t dstwidth = 32;
+ const uint32_t dstheight = 32;
+
+ VRDECOLORPOINTER *pointer = NULL;
+
+ uint32_t dstmaskwidth = (dstwidth + 7) / 8;
+
+ uint32_t rdpmaskwidth = dstmaskwidth;
+ uint32_t rdpmasklen = dstheight * rdpmaskwidth;
+
+ uint32_t rdpdatawidth = dstwidth * 3;
+ uint32_t rdpdatalen = dstheight * rdpdatawidth;
+
+ pointer = (VRDECOLORPOINTER *)RTMemTmpAlloc(sizeof(VRDECOLORPOINTER) + rdpmasklen + rdpdatalen);
+
+ if (pointer)
+ {
+ uint8_t *maskarray = (uint8_t*)pointer + sizeof(VRDECOLORPOINTER);
+ uint8_t *dataarray = maskarray + rdpmasklen;
+
+ memset(maskarray, 0xFF, rdpmasklen);
+ memset(dataarray, 0x00, rdpdatalen);
+
+ uint32_t srcmaskwidth = (width + 7) / 8;
+ uint32_t srcdatawidth = width * 4;
+
+ /* Copy AND mask. */
+ uint8_t *src = pu8AndMask + ySkip * srcmaskwidth;
+ uint8_t *dst = maskarray + (dstheight - 1) * rdpmaskwidth;
+
+ uint32_t minheight = RT_MIN(height - ySkip, dstheight);
+ uint32_t minwidth = RT_MIN(width - xSkip, dstwidth);
+
+ unsigned x, y;
+
+ for (y = 0; y < minheight; y++)
+ {
+ for (x = 0; x < minwidth; x++)
+ {
+ uint32_t byteIndex = (x + xSkip) / 8;
+ uint32_t bitIndex = (x + xSkip) % 8;
+
+ bool bit = (src[byteIndex] & (1 << (7 - bitIndex))) != 0;
+
+ if (!bit)
+ {
+ byteIndex = x / 8;
+ bitIndex = x % 8;
+
+ dst[byteIndex] &= ~(1 << (7 - bitIndex));
+ }
+ }
+
+ src += srcmaskwidth;
+ dst -= rdpmaskwidth;
+ }
+
+ /* Point src to XOR mask */
+ src = pu8XorMask + ySkip * srcdatawidth;
+ dst = dataarray + (dstheight - 1) * rdpdatawidth;
+
+ for (y = 0; y < minheight ; y++)
+ {
+ for (x = 0; x < minwidth; x++)
+ {
+ memcpy(dst + x * 3, &src[4 * (x + xSkip)], 3);
+ }
+
+ src += srcdatawidth;
+ dst -= rdpdatawidth;
+ }
+
+ pointer->u16HotX = (uint16_t)(xHot - xSkip);
+ pointer->u16HotY = (uint16_t)(yHot - ySkip);
+
+ pointer->u16Width = (uint16_t)dstwidth;
+ pointer->u16Height = (uint16_t)dstheight;
+
+ pointer->u16MaskLen = (uint16_t)rdpmasklen;
+ pointer->u16DataLen = (uint16_t)rdpdatalen;
+
+ dumpPointer((uint8_t*)pointer + sizeof(*pointer), dstwidth, dstheight, false);
+
+ MousePointerUpdate(pointer);
+
+ RTMemTmpFree(pointer);
+ }
+ }
+}
+
+
+// ConsoleVRDPServer
+////////////////////////////////////////////////////////////////////////////////
+
+RTLDRMOD ConsoleVRDPServer::mVRDPLibrary = NIL_RTLDRMOD;
+
+PFNVRDECREATESERVER ConsoleVRDPServer::mpfnVRDECreateServer = NULL;
+
+VRDEENTRYPOINTS_4 ConsoleVRDPServer::mEntryPoints; /* A copy of the server entry points. */
+VRDEENTRYPOINTS_4 *ConsoleVRDPServer::mpEntryPoints = NULL;
+
+VRDECALLBACKS_4 ConsoleVRDPServer::mCallbacks =
+{
+ { VRDE_INTERFACE_VERSION_4, sizeof(VRDECALLBACKS_4) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint,
+ ConsoleVRDPServer::VRDECallbackAudioIn
+};
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackQueryProperty(void *pvCallback, uint32_t index, void *pvBuffer,
+ uint32_t cbBuffer, uint32_t *pcbOut)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ int vrc = VERR_NOT_SUPPORTED;
+
+ switch (index)
+ {
+ case VRDE_QP_NETWORK_PORT:
+ {
+ /* This is obsolete, the VRDE server uses VRDE_QP_NETWORK_PORT_RANGE instead. */
+ ULONG port = 0;
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)port;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_NETWORK_ADDRESS:
+ {
+ com::Bstr bstr;
+ server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Address").raw(), bstr.asOutParam());
+
+ /* The server expects UTF8. */
+ com::Utf8Str address = bstr;
+
+ size_t cbAddress = address.length() + 1;
+
+ if (cbAddress >= 0x10000)
+ {
+ /* More than 64K seems to be an invalid address. */
+ vrc = VERR_TOO_MUCH_DATA;
+ break;
+ }
+
+ if ((size_t)cbBuffer >= cbAddress)
+ {
+ memcpy(pvBuffer, address.c_str(), cbAddress);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cbAddress;
+ } break;
+
+ case VRDE_QP_NUMBER_MONITORS:
+ {
+ uint32_t cMonitors = server->mConsole->i_getDisplay()->i_getMonitorCount();
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)cMonitors;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_NETWORK_PORT_RANGE:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("TCP/Ports").raw(), bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ if (bstr == "0")
+ {
+ bstr = "3389";
+ }
+
+ /* The server expects UTF8. */
+ com::Utf8Str portRange = bstr;
+
+ size_t cbPortRange = portRange.length() + 1;
+
+ if (cbPortRange >= _64K)
+ {
+ /* More than 64K seems to be an invalid port range string. */
+ vrc = VERR_TOO_MUCH_DATA;
+ break;
+ }
+
+ if ((size_t)cbBuffer >= cbPortRange)
+ {
+ memcpy(pvBuffer, portRange.c_str(), cbPortRange);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cbPortRange;
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Enabled").raw(),
+ bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ com::Utf8Str value = bstr;
+
+ BOOL fVideoEnabled = RTStrICmp(value.c_str(), "true") == 0
+ || RTStrICmp(value.c_str(), "1") == 0;
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)fVideoEnabled;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL_QUALITY:
+ {
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("VideoChannel/Quality").raw(),
+ bstr.asOutParam());
+
+ if (hrc != S_OK)
+ {
+ bstr = "";
+ }
+
+ com::Utf8Str value = bstr;
+
+ ULONG ulQuality = RTStrToUInt32(value.c_str()); /* This returns 0 on invalid string which is ok. */
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)ulQuality;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_VIDEO_CHANNEL_SUNFLSH:
+ {
+ ULONG ulSunFlsh = 1;
+
+ com::Bstr bstr;
+ HRESULT hrc = server->mConsole->i_machine()->GetExtraData(Bstr("VRDP/SunFlsh").raw(),
+ bstr.asOutParam());
+ if (hrc == S_OK && !bstr.isEmpty())
+ {
+ com::Utf8Str sunFlsh = bstr;
+ if (!sunFlsh.isEmpty())
+ {
+ ulSunFlsh = sunFlsh.toUInt32();
+ }
+ }
+
+ if (cbBuffer >= sizeof(uint32_t))
+ {
+ *(uint32_t *)pvBuffer = (uint32_t)ulSunFlsh;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = sizeof(uint32_t);
+ } break;
+
+ case VRDE_QP_FEATURE:
+ {
+ if (cbBuffer < sizeof(VRDEFEATURE))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cbInfo = cbBuffer - RT_UOFFSETOF(VRDEFEATURE, achInfo);
+
+ VRDEFEATURE *pFeature = (VRDEFEATURE *)pvBuffer;
+
+ size_t cchInfo = 0;
+ vrc = RTStrNLenEx(pFeature->achInfo, cbInfo, &cchInfo);
+
+ if (RT_FAILURE(vrc))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ Log(("VRDE_QP_FEATURE [%s]\n", pFeature->achInfo));
+
+ com::Bstr bstrValue;
+
+ if ( RTStrICmp(pFeature->achInfo, "Client/DisableDisplay") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableInput") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableAudio") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableUSB") == 0
+ || RTStrICmp(pFeature->achInfo, "Client/DisableClipboard") == 0
+ )
+ {
+ /** @todo these features should be per client. */
+ NOREF(pFeature->u32ClientId);
+
+ /* These features are mapped to "VRDE/Feature/NAME" extra data. */
+ com::Utf8Str extraData("VRDE/Feature/");
+ extraData += pFeature->achInfo;
+
+ HRESULT hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc) || bstrValue.isEmpty())
+ {
+ /* Also try the old "VRDP/Feature/NAME" */
+ extraData = "VRDP/Feature/";
+ extraData += pFeature->achInfo;
+
+ hrc = server->mConsole->i_machine()->GetExtraData(com::Bstr(extraData).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+ else if (RTStrNCmp(pFeature->achInfo, "Property/", 9) == 0)
+ {
+ /* Generic properties. */
+ const char *pszPropertyName = &pFeature->achInfo[9];
+ HRESULT hrc = server->mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr(pszPropertyName).raw(),
+ bstrValue.asOutParam());
+ if (FAILED(hrc))
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ /* Copy the value string to the callers buffer. */
+ if (vrc == VINF_SUCCESS)
+ {
+ com::Utf8Str value = bstrValue;
+
+ size_t cb = value.length() + 1;
+
+ if ((size_t)cbInfo >= cb)
+ {
+ memcpy(pFeature->achInfo, value.c_str(), cb);
+ }
+ else
+ {
+ vrc = VINF_BUFFER_OVERFLOW;
+ }
+
+ *pcbOut = (uint32_t)cb;
+ }
+ } break;
+
+ case VRDE_SP_NETWORK_BIND_PORT:
+ {
+ if (cbBuffer != sizeof(uint32_t))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ ULONG port = *(uint32_t *)pvBuffer;
+
+ server->mVRDPBindPort = port;
+
+ vrc = VINF_SUCCESS;
+
+ if (pcbOut)
+ {
+ *pcbOut = sizeof(uint32_t);
+ }
+
+ server->mConsole->i_onVRDEServerInfoChange();
+ } break;
+
+ case VRDE_SP_CLIENT_STATUS:
+ {
+ if (cbBuffer < sizeof(VRDECLIENTSTATUS))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cbStatus = cbBuffer - RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus);
+
+ VRDECLIENTSTATUS *pStatus = (VRDECLIENTSTATUS *)pvBuffer;
+
+ if (cbBuffer < RT_UOFFSETOF(VRDECLIENTSTATUS, achStatus) + pStatus->cbStatus)
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ size_t cchStatus = 0;
+ vrc = RTStrNLenEx(pStatus->achStatus, cbStatus, &cchStatus);
+
+ if (RT_FAILURE(vrc))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ Log(("VRDE_SP_CLIENT_STATUS [%s]\n", pStatus->achStatus));
+
+ server->mConsole->i_VRDPClientStatusChange(pStatus->u32ClientId, pStatus->achStatus);
+
+ vrc = VINF_SUCCESS;
+
+ if (pcbOut)
+ {
+ *pcbOut = cbBuffer;
+ }
+
+ server->mConsole->i_onVRDEServerInfoChange();
+ } break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClientLogon(void *pvCallback, uint32_t u32ClientId, const char *pszUser,
+ const char *pszPassword, const char *pszDomain)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ return server->mConsole->i_VRDPClientLogon(u32ClientId, pszUser, pszPassword, pszDomain);
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientConnect(void *pvCallback, uint32_t u32ClientId)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ pServer->mConsole->i_VRDPClientConnect(u32ClientId);
+
+ /* Should the server report usage of an interface for each client?
+ * Similar to Intercept.
+ */
+ int c = ASMAtomicIncS32(&pServer->mcClients);
+ if (c == 1)
+ {
+ /* Features which should be enabled only if there is a client. */
+ pServer->remote3DRedirect(true);
+ }
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ pVRDE->onVRDEClientConnect(u32ClientId);
+#endif
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackClientDisconnect(void *pvCallback, uint32_t u32ClientId,
+ uint32_t fu32Intercepted)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturnVoid(pServer);
+
+ pServer->mConsole->i_VRDPClientDisconnect(u32ClientId, fu32Intercepted);
+
+ if (ASMAtomicReadU32(&pServer->mu32AudioInputClientId) == u32ClientId)
+ {
+ LogFunc(("Disconnected client %u\n", u32ClientId));
+ ASMAtomicWriteU32(&pServer->mu32AudioInputClientId, 0);
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ {
+ pVRDE->onVRDEInputIntercept(false /* fIntercept */);
+ pVRDE->onVRDEClientDisconnect(u32ClientId);
+ }
+#endif
+ }
+
+ int32_t cClients = ASMAtomicDecS32(&pServer->mcClients);
+ if (cClients == 0)
+ {
+ /* Features which should be enabled only if there is a client. */
+ pServer->remote3DRedirect(false);
+ }
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackIntercept(void *pvCallback, uint32_t u32ClientId, uint32_t fu32Intercept,
+ void **ppvIntercept)
+{
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturn(pServer, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("%x\n", fu32Intercept));
+
+ int vrc = VERR_NOT_SUPPORTED;
+
+ switch (fu32Intercept)
+ {
+ case VRDE_CLIENT_INTERCEPT_AUDIO:
+ {
+ pServer->mConsole->i_VRDPInterceptAudio(u32ClientId);
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pServer;
+ }
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_USB:
+ {
+ pServer->mConsole->i_VRDPInterceptUSB(u32ClientId, ppvIntercept);
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_CLIPBOARD:
+ {
+ pServer->mConsole->i_VRDPInterceptClipboard(u32ClientId);
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pServer;
+ }
+ vrc = VINF_SUCCESS;
+ } break;
+
+ case VRDE_CLIENT_INTERCEPT_AUDIO_INPUT:
+ {
+ /*
+ * This request is processed internally by the ConsoleVRDPServer.
+ * Only one client is allowed to intercept audio input.
+ */
+ if (ASMAtomicCmpXchgU32(&pServer->mu32AudioInputClientId, u32ClientId, 0) == true)
+ {
+ LogFunc(("Intercepting audio input by client %RU32\n", u32ClientId));
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (pVRDE)
+ pVRDE->onVRDEInputIntercept(true /* fIntercept */);
+#endif
+ }
+ else
+ {
+ Log(("AUDIOIN: ignored client %RU32, active client %RU32\n", u32ClientId, pServer->mu32AudioInputClientId));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackUSB(void *pvCallback, void *pvIntercept, uint32_t u32ClientId,
+ uint8_t u8Code, const void *pvRet, uint32_t cbRet)
+{
+ RT_NOREF(pvCallback);
+#ifdef VBOX_WITH_USB
+ return USBClientResponseCallback(pvIntercept, u32ClientId, u8Code, pvRet, cbRet);
+#else
+ RT_NOREF(pvCallback, pvIntercept, u32ClientId, u8Code, pvRet, cbRet);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::VRDPCallbackClipboard(void *pvCallback, void *pvIntercept, uint32_t u32ClientId,
+ uint32_t u32Function, uint32_t u32Format,
+ const void *pvData, uint32_t cbData)
+{
+ RT_NOREF(pvCallback);
+ return ClipboardCallback(pvIntercept, u32ClientId, u32Function, u32Format, pvData, cbData);
+}
+
+DECLCALLBACK(bool) ConsoleVRDPServer::VRDPCallbackFramebufferQuery(void *pvCallback, unsigned uScreenId,
+ VRDEFRAMEBUFFERINFO *pInfo)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ bool fAvailable = false;
+
+ /* Obtain the new screen bitmap. */
+ HRESULT hr = server->mConsole->i_getDisplay()->QuerySourceBitmap(uScreenId, server->maSourceBitmaps[uScreenId].asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ LONG xOrigin = 0;
+ LONG yOrigin = 0;
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ hr = server->maSourceBitmaps[uScreenId]->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+
+ if (SUCCEEDED(hr))
+ {
+ ULONG dummy;
+ GuestMonitorStatus_T monitorStatus;
+ hr = server->mConsole->i_getDisplay()->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
+ &xOrigin, &yOrigin, &monitorStatus);
+
+ if (SUCCEEDED(hr))
+ {
+ /* Now fill the information as requested by the caller. */
+ pInfo->pu8Bits = pAddress;
+ pInfo->xOrigin = xOrigin;
+ pInfo->yOrigin = yOrigin;
+ pInfo->cWidth = ulWidth;
+ pInfo->cHeight = ulHeight;
+ pInfo->cBitsPerPixel = ulBitsPerPixel;
+ pInfo->cbLine = ulBytesPerLine;
+
+ fAvailable = true;
+ }
+ }
+ }
+
+ return fAvailable;
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferLock(void *pvCallback, unsigned uScreenId)
+{
+ NOREF(pvCallback);
+ NOREF(uScreenId);
+ /* Do nothing */
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackFramebufferUnlock(void *pvCallback, unsigned uScreenId)
+{
+ NOREF(pvCallback);
+ NOREF(uScreenId);
+ /* Do nothing */
+}
+
+static void fixKbdLockStatus(VRDPInputSynch *pInputSynch, IKeyboard *pKeyboard)
+{
+ if ( pInputSynch->cGuestNumLockAdaptions
+ && (pInputSynch->fGuestNumLock != pInputSynch->fClientNumLock))
+ {
+ pInputSynch->cGuestNumLockAdaptions--;
+ pKeyboard->PutScancode(0x45);
+ pKeyboard->PutScancode(0x45 | 0x80);
+ }
+ if ( pInputSynch->cGuestCapsLockAdaptions
+ && (pInputSynch->fGuestCapsLock != pInputSynch->fClientCapsLock))
+ {
+ pInputSynch->cGuestCapsLockAdaptions--;
+ pKeyboard->PutScancode(0x3a);
+ pKeyboard->PutScancode(0x3a | 0x80);
+ }
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackInput(void *pvCallback, int type, const void *pvInput, unsigned cbInput)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+ Console *pConsole = server->mConsole;
+
+ switch (type)
+ {
+ case VRDE_INPUT_SCANCODE:
+ {
+ if (cbInput == sizeof(VRDEINPUTSCANCODE))
+ {
+ IKeyboard *pKeyboard = pConsole->i_getKeyboard();
+
+ const VRDEINPUTSCANCODE *pInputScancode = (VRDEINPUTSCANCODE *)pvInput;
+
+ /* Track lock keys. */
+ if (pInputScancode->uScancode == 0x45)
+ {
+ server->m_InputSynch.fClientNumLock = !server->m_InputSynch.fClientNumLock;
+ }
+ else if (pInputScancode->uScancode == 0x3a)
+ {
+ server->m_InputSynch.fClientCapsLock = !server->m_InputSynch.fClientCapsLock;
+ }
+ else if (pInputScancode->uScancode == 0x46)
+ {
+ server->m_InputSynch.fClientScrollLock = !server->m_InputSynch.fClientScrollLock;
+ }
+ else if ((pInputScancode->uScancode & 0x80) == 0)
+ {
+ /* Key pressed. */
+ fixKbdLockStatus(&server->m_InputSynch, pKeyboard);
+ }
+
+ pKeyboard->PutScancode((LONG)pInputScancode->uScancode);
+ }
+ } break;
+
+ case VRDE_INPUT_POINT:
+ {
+ if (cbInput == sizeof(VRDEINPUTPOINT))
+ {
+ const VRDEINPUTPOINT *pInputPoint = (VRDEINPUTPOINT *)pvInput;
+
+ int mouseButtons = 0;
+ int iWheel = 0;
+
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON1)
+ {
+ mouseButtons |= MouseButtonState_LeftButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON2)
+ {
+ mouseButtons |= MouseButtonState_RightButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_BUTTON3)
+ {
+ mouseButtons |= MouseButtonState_MiddleButton;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_UP)
+ {
+ mouseButtons |= MouseButtonState_WheelUp;
+ iWheel = -1;
+ }
+ if (pInputPoint->uButtons & VRDE_INPUT_POINT_WHEEL_DOWN)
+ {
+ mouseButtons |= MouseButtonState_WheelDown;
+ iWheel = 1;
+ }
+
+ if (server->m_fGuestWantsAbsolute)
+ {
+ pConsole->i_getMouse()->PutMouseEventAbsolute(pInputPoint->x + 1, pInputPoint->y + 1, iWheel,
+ 0 /* Horizontal wheel */, mouseButtons);
+ } else
+ {
+ pConsole->i_getMouse()->PutMouseEvent(pInputPoint->x - server->m_mousex,
+ pInputPoint->y - server->m_mousey,
+ iWheel, 0 /* Horizontal wheel */, mouseButtons);
+ server->m_mousex = pInputPoint->x;
+ server->m_mousey = pInputPoint->y;
+ }
+ }
+ } break;
+
+ case VRDE_INPUT_CAD:
+ {
+ pConsole->i_getKeyboard()->PutCAD();
+ } break;
+
+ case VRDE_INPUT_RESET:
+ {
+ pConsole->Reset();
+ } break;
+
+ case VRDE_INPUT_SYNCH:
+ {
+ if (cbInput == sizeof(VRDEINPUTSYNCH))
+ {
+ IKeyboard *pKeyboard = pConsole->i_getKeyboard();
+
+ const VRDEINPUTSYNCH *pInputSynch = (VRDEINPUTSYNCH *)pvInput;
+
+ server->m_InputSynch.fClientNumLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_NUMLOCK) != 0;
+ server->m_InputSynch.fClientCapsLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_CAPITAL) != 0;
+ server->m_InputSynch.fClientScrollLock = (pInputSynch->uLockStatus & VRDE_INPUT_SYNCH_SCROLL) != 0;
+
+ /* The client initiated synchronization. Always make the guest to reflect the client state.
+ * Than means, when the guest changes the state itself, it is forced to return to the client
+ * state.
+ */
+ if (server->m_InputSynch.fClientNumLock != server->m_InputSynch.fGuestNumLock)
+ {
+ server->m_InputSynch.cGuestNumLockAdaptions = 2;
+ }
+
+ if (server->m_InputSynch.fClientCapsLock != server->m_InputSynch.fGuestCapsLock)
+ {
+ server->m_InputSynch.cGuestCapsLockAdaptions = 2;
+ }
+
+ fixKbdLockStatus(&server->m_InputSynch, pKeyboard);
+ }
+ } break;
+
+ default:
+ break;
+ }
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDPCallbackVideoModeHint(void *pvCallback, unsigned cWidth, unsigned cHeight,
+ unsigned cBitsPerPixel, unsigned uScreenId)
+{
+ ConsoleVRDPServer *server = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ server->mConsole->i_getDisplay()->SetVideoModeHint(uScreenId, TRUE /*=enabled*/,
+ FALSE /*=changeOrigin*/, 0/*=OriginX*/, 0/*=OriginY*/,
+ cWidth, cHeight, cBitsPerPixel, TRUE /*=notify*/);
+}
+
+DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackAudioIn(void *pvCallback,
+ void *pvCtx,
+ uint32_t u32ClientId,
+ uint32_t u32Event,
+ const void *pvData,
+ uint32_t cbData)
+{
+ RT_NOREF(u32ClientId);
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvCallback);
+ AssertPtrReturnVoid(pServer);
+
+#ifdef VBOX_WITH_AUDIO_VRDE
+ AudioVRDE *pVRDE = pServer->mConsole->i_getAudioVRDE();
+ if (!pVRDE) /* Nothing to do, bail out early. */
+ return;
+
+ switch (u32Event)
+ {
+ case VRDE_AUDIOIN_BEGIN:
+ {
+ pVRDE->onVRDEInputBegin(pvCtx, (PVRDEAUDIOINBEGIN)pvData);
+ break;
+ }
+
+ case VRDE_AUDIOIN_DATA:
+ pVRDE->onVRDEInputData(pvCtx, pvData, cbData);
+ break;
+
+ case VRDE_AUDIOIN_END:
+ pVRDE->onVRDEInputEnd(pvCtx);
+ break;
+
+ default:
+ break;
+ }
+#else
+ RT_NOREF(pvCtx, u32Event, pvData, cbData);
+#endif /* VBOX_WITH_AUDIO_VRDE */
+}
+
+ConsoleVRDPServer::ConsoleVRDPServer(Console *console)
+ : mhClipboard(NULL)
+{
+ mConsole = console;
+
+ int vrc = RTCritSectInit(&mCritSect);
+ AssertRC(vrc);
+
+ mcClipboardRefs = 0;
+ mpfnClipboardCallback = NULL;
+#ifdef VBOX_WITH_USB
+ mUSBBackends.pHead = NULL;
+ mUSBBackends.pTail = NULL;
+
+ mUSBBackends.thread = NIL_RTTHREAD;
+ mUSBBackends.fThreadRunning = false;
+ mUSBBackends.event = 0;
+#endif
+
+ mhServer = 0;
+ mServerInterfaceVersion = 0;
+
+ mcInResize = 0;
+
+ m_fGuestWantsAbsolute = false;
+ m_mousex = 0;
+ m_mousey = 0;
+
+ m_InputSynch.cGuestNumLockAdaptions = 2;
+ m_InputSynch.cGuestCapsLockAdaptions = 2;
+
+ m_InputSynch.fGuestNumLock = false;
+ m_InputSynch.fGuestCapsLock = false;
+ m_InputSynch.fGuestScrollLock = false;
+
+ m_InputSynch.fClientNumLock = false;
+ m_InputSynch.fClientCapsLock = false;
+ m_InputSynch.fClientScrollLock = false;
+
+ {
+ ComPtr<IEventSource> es;
+ console->COMGETTER(EventSource)(es.asOutParam());
+ ComObjPtr<VRDPConsoleListenerImpl> aConsoleListener;
+ aConsoleListener.createObject();
+ aConsoleListener->init(new VRDPConsoleListener(), this);
+ mConsoleListener = aConsoleListener;
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnMousePointerShapeChanged);
+ eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
+ eventTypes.push_back(VBoxEventType_OnKeyboardLedsChanged);
+ es->RegisterListener(mConsoleListener, ComSafeArrayAsInParam(eventTypes), true);
+ }
+
+ mVRDPBindPort = -1;
+
+#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ RT_ZERO(mAuthLibCtx);
+#endif
+
+ mu32AudioInputClientId = 0;
+ mcClients = 0;
+
+ /*
+ * Optional interfaces.
+ */
+ m_fInterfaceImage = false;
+ RT_ZERO(m_interfaceImage);
+ RT_ZERO(m_interfaceCallbacksImage);
+ RT_ZERO(m_interfaceMousePtr);
+ RT_ZERO(m_interfaceSCard);
+ RT_ZERO(m_interfaceCallbacksSCard);
+ RT_ZERO(m_interfaceTSMF);
+ RT_ZERO(m_interfaceCallbacksTSMF);
+ RT_ZERO(m_interfaceVideoIn);
+ RT_ZERO(m_interfaceCallbacksVideoIn);
+ RT_ZERO(m_interfaceInput);
+ RT_ZERO(m_interfaceCallbacksInput);
+
+ vrc = RTCritSectInit(&mTSMFLock);
+ AssertRC(vrc);
+
+ mEmWebcam = new EmWebcam(this);
+ AssertPtr(mEmWebcam);
+}
+
+ConsoleVRDPServer::~ConsoleVRDPServer()
+{
+ Stop();
+
+ if (mConsoleListener)
+ {
+ ComPtr<IEventSource> es;
+ mConsole->COMGETTER(EventSource)(es.asOutParam());
+ es->UnregisterListener(mConsoleListener);
+ mConsoleListener.setNull();
+ }
+
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(maSourceBitmaps); i++)
+ {
+ maSourceBitmaps[i].setNull();
+ }
+
+ if (mEmWebcam)
+ {
+ delete mEmWebcam;
+ mEmWebcam = NULL;
+ }
+
+ if (RTCritSectIsInitialized(&mCritSect))
+ {
+ RTCritSectDelete(&mCritSect);
+ RT_ZERO(mCritSect);
+ }
+
+ if (RTCritSectIsInitialized(&mTSMFLock))
+ {
+ RTCritSectDelete(&mTSMFLock);
+ RT_ZERO(mTSMFLock);
+ }
+}
+
+int ConsoleVRDPServer::Launch(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ IVRDEServer *server = mConsole->i_getVRDEServer();
+ AssertReturn(server, VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Check if VRDE is enabled.
+ */
+ BOOL fEnabled;
+ HRESULT hrc = server->COMGETTER(Enabled)(&fEnabled);
+ AssertComRCReturn(hrc, Global::vboxStatusCodeFromCOM(hrc));
+ if (!fEnabled)
+ return VINF_SUCCESS;
+
+ /*
+ * Check that a VRDE extension pack name is set and resolve it into a
+ * library path.
+ */
+ Bstr bstrExtPack;
+ hrc = server->COMGETTER(VRDEExtPack)(bstrExtPack.asOutParam());
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ if (bstrExtPack.isEmpty())
+ return VINF_NOT_SUPPORTED;
+
+ Utf8Str strExtPack(bstrExtPack);
+ Utf8Str strVrdeLibrary;
+ int vrc = VINF_SUCCESS;
+ if (strExtPack.equals(VBOXVRDP_KLUDGE_EXTPACK_NAME))
+ strVrdeLibrary = "VBoxVRDP";
+ else
+ {
+#ifdef VBOX_WITH_EXTPACK
+ ExtPackManager *pExtPackMgr = mConsole->i_getExtPackManager();
+ vrc = pExtPackMgr->i_getVrdeLibraryPathForExtPack(&strExtPack, &strVrdeLibrary);
+#else
+ vrc = VERR_FILE_NOT_FOUND;
+#endif
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Load the VRDE library and start the server, if it is enabled.
+ */
+ vrc = loadVRDPLibrary(strVrdeLibrary.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ VRDEENTRYPOINTS_4 *pEntryPoints4;
+ vrc = mpfnVRDECreateServer(&mCallbacks.header, this, (VRDEINTERFACEHDR **)&pEntryPoints4, &mhServer);
+
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 4;
+ mEntryPoints = *pEntryPoints4;
+ mpEntryPoints = &mEntryPoints;
+ }
+ else if (vrc == VERR_VERSION_MISMATCH)
+ {
+ /* An older version of VRDE is installed, try version 3. */
+ VRDEENTRYPOINTS_3 *pEntryPoints3;
+
+ static VRDECALLBACKS_3 sCallbacks3 =
+ {
+ { VRDE_INTERFACE_VERSION_3, sizeof(VRDECALLBACKS_3) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint,
+ ConsoleVRDPServer::VRDECallbackAudioIn
+ };
+
+ vrc = mpfnVRDECreateServer(&sCallbacks3.header, this, (VRDEINTERFACEHDR **)&pEntryPoints3, &mhServer);
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 3;
+ mEntryPoints.header = pEntryPoints3->header;
+ mEntryPoints.VRDEDestroy = pEntryPoints3->VRDEDestroy;
+ mEntryPoints.VRDEEnableConnections = pEntryPoints3->VRDEEnableConnections;
+ mEntryPoints.VRDEDisconnect = pEntryPoints3->VRDEDisconnect;
+ mEntryPoints.VRDEResize = pEntryPoints3->VRDEResize;
+ mEntryPoints.VRDEUpdate = pEntryPoints3->VRDEUpdate;
+ mEntryPoints.VRDEColorPointer = pEntryPoints3->VRDEColorPointer;
+ mEntryPoints.VRDEHidePointer = pEntryPoints3->VRDEHidePointer;
+ mEntryPoints.VRDEAudioSamples = pEntryPoints3->VRDEAudioSamples;
+ mEntryPoints.VRDEAudioVolume = pEntryPoints3->VRDEAudioVolume;
+ mEntryPoints.VRDEUSBRequest = pEntryPoints3->VRDEUSBRequest;
+ mEntryPoints.VRDEClipboard = pEntryPoints3->VRDEClipboard;
+ mEntryPoints.VRDEQueryInfo = pEntryPoints3->VRDEQueryInfo;
+ mEntryPoints.VRDERedirect = pEntryPoints3->VRDERedirect;
+ mEntryPoints.VRDEAudioInOpen = pEntryPoints3->VRDEAudioInOpen;
+ mEntryPoints.VRDEAudioInClose = pEntryPoints3->VRDEAudioInClose;
+ mEntryPoints.VRDEGetInterface = NULL;
+ mpEntryPoints = &mEntryPoints;
+ }
+ else if (vrc == VERR_VERSION_MISMATCH)
+ {
+ /* An older version of VRDE is installed, try version 1. */
+ VRDEENTRYPOINTS_1 *pEntryPoints1;
+
+ static VRDECALLBACKS_1 sCallbacks1 =
+ {
+ { VRDE_INTERFACE_VERSION_1, sizeof(VRDECALLBACKS_1) },
+ ConsoleVRDPServer::VRDPCallbackQueryProperty,
+ ConsoleVRDPServer::VRDPCallbackClientLogon,
+ ConsoleVRDPServer::VRDPCallbackClientConnect,
+ ConsoleVRDPServer::VRDPCallbackClientDisconnect,
+ ConsoleVRDPServer::VRDPCallbackIntercept,
+ ConsoleVRDPServer::VRDPCallbackUSB,
+ ConsoleVRDPServer::VRDPCallbackClipboard,
+ ConsoleVRDPServer::VRDPCallbackFramebufferQuery,
+ ConsoleVRDPServer::VRDPCallbackFramebufferLock,
+ ConsoleVRDPServer::VRDPCallbackFramebufferUnlock,
+ ConsoleVRDPServer::VRDPCallbackInput,
+ ConsoleVRDPServer::VRDPCallbackVideoModeHint
+ };
+
+ vrc = mpfnVRDECreateServer(&sCallbacks1.header, this, (VRDEINTERFACEHDR **)&pEntryPoints1, &mhServer);
+ if (RT_SUCCESS(vrc))
+ {
+ mServerInterfaceVersion = 1;
+ mEntryPoints.header = pEntryPoints1->header;
+ mEntryPoints.VRDEDestroy = pEntryPoints1->VRDEDestroy;
+ mEntryPoints.VRDEEnableConnections = pEntryPoints1->VRDEEnableConnections;
+ mEntryPoints.VRDEDisconnect = pEntryPoints1->VRDEDisconnect;
+ mEntryPoints.VRDEResize = pEntryPoints1->VRDEResize;
+ mEntryPoints.VRDEUpdate = pEntryPoints1->VRDEUpdate;
+ mEntryPoints.VRDEColorPointer = pEntryPoints1->VRDEColorPointer;
+ mEntryPoints.VRDEHidePointer = pEntryPoints1->VRDEHidePointer;
+ mEntryPoints.VRDEAudioSamples = pEntryPoints1->VRDEAudioSamples;
+ mEntryPoints.VRDEAudioVolume = pEntryPoints1->VRDEAudioVolume;
+ mEntryPoints.VRDEUSBRequest = pEntryPoints1->VRDEUSBRequest;
+ mEntryPoints.VRDEClipboard = pEntryPoints1->VRDEClipboard;
+ mEntryPoints.VRDEQueryInfo = pEntryPoints1->VRDEQueryInfo;
+ mEntryPoints.VRDERedirect = NULL;
+ mEntryPoints.VRDEAudioInOpen = NULL;
+ mEntryPoints.VRDEAudioInClose = NULL;
+ mEntryPoints.VRDEGetInterface = NULL;
+ mpEntryPoints = &mEntryPoints;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: loaded version %d of the server.\n", mServerInterfaceVersion));
+
+ if (mServerInterfaceVersion >= 4)
+ {
+ /* The server supports optional interfaces. */
+ Assert(mpEntryPoints->VRDEGetInterface != NULL);
+
+ /* Image interface. */
+ m_interfaceImage.header.u64Version = 1;
+ m_interfaceImage.header.u64Size = sizeof(m_interfaceImage);
+
+ m_interfaceCallbacksImage.header.u64Version = 1;
+ m_interfaceCallbacksImage.header.u64Size = sizeof(m_interfaceCallbacksImage);
+ m_interfaceCallbacksImage.VRDEImageCbNotify = VRDEImageCbNotify;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_IMAGE_INTERFACE_NAME,
+ &m_interfaceImage.header,
+ &m_interfaceCallbacksImage.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_IMAGE_INTERFACE_NAME));
+ m_fInterfaceImage = true;
+ }
+
+ /* Mouse pointer interface. */
+ m_interfaceMousePtr.header.u64Version = 1;
+ m_interfaceMousePtr.header.u64Size = sizeof(m_interfaceMousePtr);
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_MOUSEPTR_INTERFACE_NAME,
+ &m_interfaceMousePtr.header,
+ NULL,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_MOUSEPTR_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceMousePtr);
+ }
+
+ /* Smartcard interface. */
+ m_interfaceSCard.header.u64Version = 1;
+ m_interfaceSCard.header.u64Size = sizeof(m_interfaceSCard);
+
+ m_interfaceCallbacksSCard.header.u64Version = 1;
+ m_interfaceCallbacksSCard.header.u64Size = sizeof(m_interfaceCallbacksSCard);
+ m_interfaceCallbacksSCard.VRDESCardCbNotify = VRDESCardCbNotify;
+ m_interfaceCallbacksSCard.VRDESCardCbResponse = VRDESCardCbResponse;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_SCARD_INTERFACE_NAME,
+ &m_interfaceSCard.header,
+ &m_interfaceCallbacksSCard.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_SCARD_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceSCard);
+ }
+
+ /* Raw TSMF interface. */
+ m_interfaceTSMF.header.u64Version = 1;
+ m_interfaceTSMF.header.u64Size = sizeof(m_interfaceTSMF);
+
+ m_interfaceCallbacksTSMF.header.u64Version = 1;
+ m_interfaceCallbacksTSMF.header.u64Size = sizeof(m_interfaceCallbacksTSMF);
+ m_interfaceCallbacksTSMF.VRDETSMFCbNotify = VRDETSMFCbNotify;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_TSMF_INTERFACE_NAME,
+ &m_interfaceTSMF.header,
+ &m_interfaceCallbacksTSMF.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_TSMF_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceTSMF);
+ }
+
+ /* VideoIn interface. */
+ m_interfaceVideoIn.header.u64Version = 1;
+ m_interfaceVideoIn.header.u64Size = sizeof(m_interfaceVideoIn);
+
+ m_interfaceCallbacksVideoIn.header.u64Version = 1;
+ m_interfaceCallbacksVideoIn.header.u64Size = sizeof(m_interfaceCallbacksVideoIn);
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInNotify = VRDECallbackVideoInNotify;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInDeviceDesc = VRDECallbackVideoInDeviceDesc;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInControl = VRDECallbackVideoInControl;
+ m_interfaceCallbacksVideoIn.VRDECallbackVideoInFrame = VRDECallbackVideoInFrame;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_VIDEOIN_INTERFACE_NAME,
+ &m_interfaceVideoIn.header,
+ &m_interfaceCallbacksVideoIn.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_VIDEOIN_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceVideoIn);
+ }
+
+ /* Input interface. */
+ m_interfaceInput.header.u64Version = 1;
+ m_interfaceInput.header.u64Size = sizeof(m_interfaceInput);
+
+ m_interfaceCallbacksInput.header.u64Version = 1;
+ m_interfaceCallbacksInput.header.u64Size = sizeof(m_interfaceCallbacksInput);
+ m_interfaceCallbacksInput.VRDECallbackInputSetup = VRDECallbackInputSetup;
+ m_interfaceCallbacksInput.VRDECallbackInputEvent = VRDECallbackInputEvent;
+
+ vrc = mpEntryPoints->VRDEGetInterface(mhServer,
+ VRDE_INPUT_INTERFACE_NAME,
+ &m_interfaceInput.header,
+ &m_interfaceCallbacksInput.header,
+ this);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDE: [%s]\n", VRDE_INPUT_INTERFACE_NAME));
+ }
+ else
+ {
+ RT_ZERO(m_interfaceInput);
+ }
+
+ /* Since these interfaces are optional, it is always a success here. */
+ vrc = VINF_SUCCESS;
+ }
+#ifdef VBOX_WITH_USB
+ remoteUSBThreadStart();
+#endif
+
+ /*
+ * Re-init the server current state, which is usually obtained from events.
+ */
+ fetchCurrentState();
+ }
+ else
+ {
+ if (vrc != VERR_NET_ADDRESS_IN_USE)
+ LogRel(("VRDE: Could not start the server vrc = %Rrc\n", vrc));
+ /* Don't unload the lib, because it prevents us trying again or
+ because there may be other users? */
+ }
+ }
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::fetchCurrentState(void)
+{
+ ComPtr<IMousePointerShape> mps;
+ mConsole->i_getMouse()->COMGETTER(PointerShape)(mps.asOutParam());
+ if (!mps.isNull())
+ {
+ BOOL visible, alpha;
+ ULONG hotX, hotY, width, height;
+ com::SafeArray <BYTE> shape;
+
+ mps->COMGETTER(Visible)(&visible);
+ mps->COMGETTER(Alpha)(&alpha);
+ mps->COMGETTER(HotX)(&hotX);
+ mps->COMGETTER(HotY)(&hotY);
+ mps->COMGETTER(Width)(&width);
+ mps->COMGETTER(Height)(&height);
+ mps->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
+
+ onMousePointerShapeChange(visible, alpha, hotX, hotY, width, height, ComSafeArrayAsInParam(shape));
+ }
+}
+
+#if 0 /** @todo Chromium got removed (see @bugref{9529}) and this is not available for VMSVGA yet. */
+typedef struct H3DORInstance
+{
+ ConsoleVRDPServer *pThis;
+ HVRDEIMAGE hImageBitmap;
+ int32_t x;
+ int32_t y;
+ uint32_t w;
+ uint32_t h;
+ bool fCreated;
+ bool fFallback;
+ bool fTopDown;
+} H3DORInstance;
+
+#define H3DORLOG Log
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORBegin(const void *pvContext, void **ppvInstance,
+ const char *pszFormat)
+{
+ H3DORLOG(("H3DORBegin: ctx %p [%s]\n", pvContext, pszFormat));
+
+ H3DORInstance *p = (H3DORInstance *)RTMemAlloc(sizeof(H3DORInstance));
+
+ if (p)
+ {
+ p->pThis = (ConsoleVRDPServer *)pvContext;
+ p->hImageBitmap = NULL;
+ p->x = 0;
+ p->y = 0;
+ p->w = 0;
+ p->h = 0;
+ p->fCreated = false;
+ p->fFallback = false;
+
+ /* Host 3D service passes the actual format of data in this redirect instance.
+ * That is what will be in the H3DORFrame's parameters pvData and cbData.
+ */
+ if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA_TOPDOWN) == 0)
+ {
+ /* Accept it. */
+ p->fTopDown = true;
+ }
+ else if (RTStrICmp(pszFormat, H3DOR_FMT_RGBA) == 0)
+ {
+ /* Accept it. */
+ p->fTopDown = false;
+ }
+ else
+ {
+ RTMemFree(p);
+ p = NULL;
+ }
+ }
+
+ H3DORLOG(("H3DORBegin: ins %p\n", p));
+
+ /* Caller checks this for NULL. */
+ *ppvInstance = p;
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORGeometry(void *pvInstance,
+ int32_t x, int32_t y, uint32_t w, uint32_t h)
+{
+ H3DORLOG(("H3DORGeometry: ins %p %d,%d %dx%d\n", pvInstance, x, y, w, h));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ /** @todo find out what to do if size changes to 0x0 from non zero */
+ if (w == 0 || h == 0)
+ {
+ /* Do nothing. */
+ return;
+ }
+
+ RTRECT rect;
+ rect.xLeft = x;
+ rect.yTop = y;
+ rect.xRight = x + w;
+ rect.yBottom = y + h;
+
+ if (p->hImageBitmap)
+ {
+ /* An image handle has been already created,
+ * check if it has the same size as the reported geometry.
+ */
+ if ( p->x == x
+ && p->y == y
+ && p->w == w
+ && p->h == h)
+ {
+ H3DORLOG(("H3DORGeometry: geometry not changed\n"));
+ /* Do nothing. Continue using the existing handle. */
+ }
+ else
+ {
+ int vrc = p->fFallback
+ ? VERR_NOT_SUPPORTED /* Try to go out of fallback mode. */
+ : p->pThis->m_interfaceImage.VRDEImageGeometrySet(p->hImageBitmap, &rect);
+ if (RT_SUCCESS(vrc))
+ {
+ p->x = x;
+ p->y = y;
+ p->w = w;
+ p->h = h;
+ }
+ else
+ {
+ /* The handle must be recreated. Delete existing handle here. */
+ p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap);
+ p->hImageBitmap = NULL;
+ }
+ }
+ }
+
+ if (!p->hImageBitmap)
+ {
+ /* Create a new bitmap handle. */
+ uint32_t u32ScreenId = 0; /** @todo clip to corresponding screens.
+ * Clipping can be done here or in VRDP server.
+ * If VRDP does clipping, then uScreenId parameter
+ * is not necessary and coords must be global.
+ * (have to check which coords are used in opengl service).
+ * Since all VRDE API uses a ScreenId,
+ * the clipping must be done here in ConsoleVRDPServer
+ */
+ uint32_t fu32CompletionFlags = 0;
+ p->fFallback = false;
+ int vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer,
+ &p->hImageBitmap,
+ p,
+ u32ScreenId,
+ VRDE_IMAGE_F_CREATE_CONTENT_3D
+ | VRDE_IMAGE_F_CREATE_WINDOW,
+ &rect,
+ VRDE_IMAGE_FMT_ID_BITMAP_BGRA8,
+ NULL,
+ 0,
+ &fu32CompletionFlags);
+ if (RT_FAILURE(vrc))
+ {
+ /* No support for a 3D + WINDOW. Try bitmap updates. */
+ H3DORLOG(("H3DORGeometry: Fallback to bitmaps\n"));
+ fu32CompletionFlags = 0;
+ p->fFallback = true;
+ vrc = p->pThis->m_interfaceImage.VRDEImageHandleCreate(p->pThis->mhServer,
+ &p->hImageBitmap,
+ p,
+ u32ScreenId,
+ 0,
+ &rect,
+ VRDE_IMAGE_FMT_ID_BITMAP_BGRA8,
+ NULL,
+ 0,
+ &fu32CompletionFlags);
+ }
+
+ H3DORLOG(("H3DORGeometry: Image handle create %Rrc, flags 0x%RX32\n", vrc, fu32CompletionFlags));
+
+ if (RT_SUCCESS(vrc))
+ {
+ p->x = x;
+ p->y = y;
+ p->w = w;
+ p->h = h;
+
+ if ((fu32CompletionFlags & VRDE_IMAGE_F_COMPLETE_ASYNC) == 0)
+ {
+ p->fCreated = true;
+ }
+ }
+ else
+ {
+ p->hImageBitmap = NULL;
+ p->w = 0;
+ p->h = 0;
+ }
+ }
+
+ H3DORLOG(("H3DORGeometry: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORVisibleRegion(void *pvInstance,
+ uint32_t cRects, const RTRECT *paRects)
+{
+ H3DORLOG(("H3DORVisibleRegion: ins %p %d\n", pvInstance, cRects));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ if (cRects == 0)
+ {
+ /* Complete image is visible. */
+ RTRECT rect;
+ rect.xLeft = p->x;
+ rect.yTop = p->y;
+ rect.xRight = p->x + p->w;
+ rect.yBottom = p->y + p->h;
+ p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap,
+ 1,
+ &rect);
+ }
+ else
+ {
+ p->pThis->m_interfaceImage.VRDEImageRegionSet (p->hImageBitmap,
+ cRects,
+ paRects);
+ }
+
+ H3DORLOG(("H3DORVisibleRegion: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DORFrame(void *pvInstance,
+ void *pvData, uint32_t cbData)
+{
+ H3DORLOG(("H3DORFrame: ins %p %p %d\n", pvInstance, pvData, cbData));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ /* Currently only a topdown BGR0 bitmap format is supported. */
+ VRDEIMAGEBITMAP image;
+
+ image.cWidth = p->w;
+ image.cHeight = p->h;
+ image.pvData = pvData;
+ image.cbData = cbData;
+ image.pvScanLine0 = (uint8_t *)pvData + (p->h - 1) * p->w * 4;
+ image.iScanDelta = 4 * p->w;
+ if (p->fTopDown)
+ {
+ image.iScanDelta = -image.iScanDelta;
+ }
+
+ p->pThis->m_interfaceImage.VRDEImageUpdate (p->hImageBitmap,
+ p->x,
+ p->y,
+ p->w,
+ p->h,
+ &image,
+ sizeof(VRDEIMAGEBITMAP));
+
+ H3DORLOG(("H3DORFrame: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::H3DOREnd(void *pvInstance)
+{
+ H3DORLOG(("H3DOREnd: ins %p\n", pvInstance));
+
+ H3DORInstance *p = (H3DORInstance *)pvInstance;
+ AssertPtrReturnVoid(p);
+ AssertPtrReturnVoid(p->pThis);
+
+ p->pThis->m_interfaceImage.VRDEImageHandleClose(p->hImageBitmap);
+
+ RT_ZERO(*p);
+ RTMemFree(p);
+
+ H3DORLOG(("H3DOREnd: ins %p completed\n", pvInstance));
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::H3DORContextProperty(const void *pvContext, uint32_t index,
+ void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut)
+{
+ RT_NOREF(pvContext, pvBuffer);
+ int vrc = VINF_SUCCESS;
+
+ H3DORLOG(("H3DORContextProperty: index %d\n", index));
+
+ if (index == H3DOR_PROP_FORMATS)
+ {
+ /* Return a comma separated list of supported formats. */
+ uint32_t cbOut = (uint32_t)strlen(H3DOR_FMT_RGBA_TOPDOWN) + 1
+ + (uint32_t)strlen(H3DOR_FMT_RGBA) + 1;
+ if (cbOut <= cbBuffer)
+ {
+ char *pch = (char *)pvBuffer;
+ memcpy(pch, H3DOR_FMT_RGBA_TOPDOWN, strlen(H3DOR_FMT_RGBA_TOPDOWN));
+ pch += strlen(H3DOR_FMT_RGBA_TOPDOWN);
+ *pch++ = ',';
+ memcpy(pch, H3DOR_FMT_RGBA, strlen(H3DOR_FMT_RGBA));
+ pch += strlen(H3DOR_FMT_RGBA);
+ *pch++ = '\0';
+ }
+ else
+ {
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+ *pcbOut = cbOut;
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ H3DORLOG(("H3DORContextProperty: %Rrc\n", vrc));
+ return vrc;
+}
+#endif
+
+void ConsoleVRDPServer::remote3DRedirect(bool fEnable)
+{
+ if (!m_fInterfaceImage)
+ {
+ /* No redirect without corresponding interface. */
+ return;
+ }
+
+ /* Check if 3D redirection has been enabled. It is enabled by default. */
+ com::Bstr bstr;
+ HRESULT hrc = mConsole->i_getVRDEServer()->GetVRDEProperty(Bstr("H3DRedirect/Enabled").raw(), bstr.asOutParam());
+
+ com::Utf8Str value = hrc == S_OK? bstr: "";
+
+ bool fAllowed = RTStrICmp(value.c_str(), "true") == 0
+ || RTStrICmp(value.c_str(), "1") == 0
+ || value.c_str()[0] == 0;
+
+ if (!fAllowed && fEnable)
+ {
+ return;
+ }
+
+#if 0 /** @todo Implement again for VMSVGA. */
+ /* Tell the host 3D service to redirect output using the ConsoleVRDPServer callbacks. */
+ H3DOUTPUTREDIRECT outputRedirect =
+ {
+ this,
+ H3DORBegin,
+ H3DORGeometry,
+ H3DORVisibleRegion,
+ H3DORFrame,
+ H3DOREnd,
+ H3DORContextProperty
+ };
+
+ if (!fEnable)
+ {
+ /* This will tell the service to disable rediection. */
+ RT_ZERO(outputRedirect);
+ }
+#endif
+
+ return;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDEImageCbNotify (void *pvContext,
+ void *pvUser,
+ HVRDEIMAGE hVideo,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData)
+{
+ RT_NOREF(hVideo);
+ Log(("H3DOR: VRDEImageCbNotify: pvContext %p, pvUser %p, hVideo %p, u32Id %u, pvData %p, cbData %d\n",
+ pvContext, pvUser, hVideo, u32Id, pvData, cbData));
+
+ ConsoleVRDPServer *pServer = static_cast<ConsoleVRDPServer*>(pvContext); NOREF(pServer);
+
+#if 0 /** @todo Implement again for VMSVGA. */
+ H3DORInstance *p = (H3DORInstance *)pvUser;
+ Assert(p);
+ Assert(p->pThis);
+ Assert(p->pThis == pServer);
+
+ if (u32Id == VRDE_IMAGE_NOTIFY_HANDLE_CREATE)
+ {
+ if (cbData != sizeof(uint32_t))
+ {
+ AssertFailed();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ uint32_t u32StreamId = *(uint32_t *)pvData;
+ Log(("H3DOR: VRDE_IMAGE_NOTIFY_HANDLE_CREATE u32StreamId %d\n",
+ u32StreamId));
+
+ if (u32StreamId != 0)
+ {
+ p->fCreated = true; /// @todo not needed?
+ }
+ else
+ {
+ /* The stream has not been created. */
+ }
+ }
+#else
+ RT_NOREF(pvUser, u32Id, pvData, cbData);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+#undef H3DORLOG
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbNotify(void *pvContext,
+ uint32_t u32Id,
+ void *pvData,
+ uint32_t cbData)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+ UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader();
+ return pReader->VRDENotify(u32Id, pvData, cbData);
+#else
+ NOREF(pvContext);
+ NOREF(u32Id);
+ NOREF(pvData);
+ NOREF(cbData);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::VRDESCardCbResponse(void *pvContext,
+ int vrcRequest,
+ void *pvUser,
+ uint32_t u32Function,
+ void *pvData,
+ uint32_t cbData)
+{
+#ifdef VBOX_WITH_USB_CARDREADER
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+ UsbCardReader *pReader = pThis->mConsole->i_getUsbCardReader();
+ return pReader->VRDEResponse(vrcRequest, pvUser, u32Function, pvData, cbData);
+#else
+ NOREF(pvContext);
+ NOREF(vrcRequest);
+ NOREF(pvUser);
+ NOREF(u32Function);
+ NOREF(pvData);
+ NOREF(cbData);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+int ConsoleVRDPServer::SCardRequest(void *pvUser, uint32_t u32Function, const void *pvData, uint32_t cbData)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mhServer && mpEntryPoints && m_interfaceSCard.VRDESCardRequest)
+ {
+ vrc = m_interfaceSCard.VRDESCardRequest(mhServer, pvUser, u32Function, pvData, cbData);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+
+struct TSMFHOSTCHCTX;
+struct TSMFVRDPCTX;
+
+typedef struct TSMFHOSTCHCTX
+{
+ ConsoleVRDPServer *pThis;
+
+ struct TSMFVRDPCTX *pVRDPCtx; /* NULL if no corresponding host channel context. */
+
+ void *pvDataReceived;
+ uint32_t cbDataReceived;
+ uint32_t cbDataAllocated;
+} TSMFHOSTCHCTX;
+
+typedef struct TSMFVRDPCTX
+{
+ ConsoleVRDPServer *pThis;
+
+ VBOXHOSTCHANNELCALLBACKS *pCallbacks;
+ void *pvCallbacks;
+
+ TSMFHOSTCHCTX *pHostChCtx; /* NULL if no corresponding host channel context. */
+
+ uint32_t u32ChannelHandle;
+} TSMFVRDPCTX;
+
+static int tsmfContextsAlloc(TSMFHOSTCHCTX **ppHostChCtx, TSMFVRDPCTX **ppVRDPCtx)
+{
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)RTMemAllocZ(sizeof(TSMFHOSTCHCTX));
+ if (!pHostChCtx)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)RTMemAllocZ(sizeof(TSMFVRDPCTX));
+ if (!pVRDPCtx)
+ {
+ RTMemFree(pHostChCtx);
+ return VERR_NO_MEMORY;
+ }
+
+ *ppHostChCtx = pHostChCtx;
+ *ppVRDPCtx = pVRDPCtx;
+ return VINF_SUCCESS;
+}
+
+int ConsoleVRDPServer::tsmfLock(void)
+{
+ int vrc = RTCritSectEnter(&mTSMFLock);
+ AssertRC(vrc);
+ return vrc;
+}
+
+void ConsoleVRDPServer::tsmfUnlock(void)
+{
+ RTCritSectLeave(&mTSMFLock);
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelAttach(void *pvProvider,
+ void **ppvChannel,
+ uint32_t u32Flags,
+ VBOXHOSTCHANNELCALLBACKS *pCallbacks,
+ void *pvCallbacks)
+{
+ LogFlowFunc(("\n"));
+
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvProvider);
+
+ /* Create 2 context structures: for the VRDP server and for the host service. */
+ TSMFHOSTCHCTX *pHostChCtx = NULL;
+ TSMFVRDPCTX *pVRDPCtx = NULL;
+
+ int vrc = tsmfContextsAlloc(&pHostChCtx, &pVRDPCtx);
+ if (RT_FAILURE(vrc))
+ {
+ return vrc;
+ }
+
+ pHostChCtx->pThis = pThis;
+ pHostChCtx->pVRDPCtx = pVRDPCtx;
+
+ pVRDPCtx->pThis = pThis;
+ pVRDPCtx->pCallbacks = pCallbacks;
+ pVRDPCtx->pvCallbacks = pvCallbacks;
+ pVRDPCtx->pHostChCtx = pHostChCtx;
+
+ vrc = pThis->m_interfaceTSMF.VRDETSMFChannelCreate(pThis->mhServer, pVRDPCtx, u32Flags);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo contexts should be in a list for accounting. */
+ *ppvChannel = pHostChCtx;
+ }
+ else
+ {
+ RTMemFree(pHostChCtx);
+ RTMemFree(pVRDPCtx);
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::tsmfHostChannelDetach(void *pvChannel)
+{
+ LogFlowFunc(("\n"));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ bool fClose = false;
+ uint32_t u32ChannelHandle = 0;
+
+ if (pHostChCtx->pVRDPCtx)
+ {
+ /* There is still a VRDP context for this channel. */
+ pHostChCtx->pVRDPCtx->pHostChCtx = NULL;
+ u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle;
+ fClose = true;
+ }
+
+ pThis->tsmfUnlock();
+
+ RTMemFree(pHostChCtx);
+
+ if (fClose)
+ {
+ LogFlowFunc(("Closing VRDE channel %d.\n", u32ChannelHandle));
+ pThis->m_interfaceTSMF.VRDETSMFChannelClose(pThis->mhServer, u32ChannelHandle);
+ }
+ else
+ {
+ LogFlowFunc(("No VRDE channel.\n"));
+ }
+ }
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelSend(void *pvChannel,
+ const void *pvData,
+ uint32_t cbData)
+{
+ LogFlowFunc(("cbData %d\n", cbData));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ bool fSend = false;
+ uint32_t u32ChannelHandle = 0;
+
+ if (pHostChCtx->pVRDPCtx)
+ {
+ u32ChannelHandle = pHostChCtx->pVRDPCtx->u32ChannelHandle;
+ fSend = true;
+ }
+
+ pThis->tsmfUnlock();
+
+ if (fSend)
+ {
+ LogFlowFunc(("Send to VRDE channel %d.\n", u32ChannelHandle));
+ vrc = pThis->m_interfaceTSMF.VRDETSMFChannelSend(pThis->mhServer, u32ChannelHandle,
+ pvData, cbData);
+ }
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelRecv(void *pvChannel,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pcbReceived,
+ uint32_t *pcbRemaining)
+{
+ LogFlowFunc(("cbData %d\n", cbData));
+
+ TSMFHOSTCHCTX *pHostChCtx = (TSMFHOSTCHCTX *)pvChannel;
+ ConsoleVRDPServer *pThis = pHostChCtx->pThis;
+
+ int vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbToCopy = RT_MIN(cbData, pHostChCtx->cbDataReceived);
+ uint32_t cbRemaining = pHostChCtx->cbDataReceived - cbToCopy;
+
+ LogFlowFunc(("cbToCopy %d, cbRemaining %d\n", cbToCopy, cbRemaining));
+
+ if (cbToCopy != 0)
+ {
+ memcpy(pvData, pHostChCtx->pvDataReceived, cbToCopy);
+
+ if (cbRemaining != 0)
+ {
+ memmove(pHostChCtx->pvDataReceived, (uint8_t *)pHostChCtx->pvDataReceived + cbToCopy, cbRemaining);
+ }
+
+ pHostChCtx->cbDataReceived = cbRemaining;
+ }
+
+ pThis->tsmfUnlock();
+
+ *pcbRemaining = cbRemaining;
+ *pcbReceived = cbToCopy;
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int) ConsoleVRDPServer::tsmfHostChannelControl(void *pvChannel,
+ uint32_t u32Code,
+ const void *pvParm,
+ uint32_t cbParm,
+ const void *pvData,
+ uint32_t cbData,
+ uint32_t *pcbDataReturned)
+{
+ RT_NOREF(pvParm, cbParm, pvData, cbData);
+ LogFlowFunc(("u32Code %u\n", u32Code));
+
+ if (!pvChannel)
+ {
+ /* Special case, the provider must answer rather than a channel instance. */
+ if (u32Code == VBOX_HOST_CHANNEL_CTRL_EXISTS)
+ {
+ *pcbDataReturned = 0;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /* Channels do not support this. */
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+void ConsoleVRDPServer::setupTSMF(void)
+{
+ if (m_interfaceTSMF.header.u64Size == 0)
+ {
+ return;
+ }
+
+ /* Register with the host channel service. */
+ VBOXHOSTCHANNELINTERFACE hostChannelInterface =
+ {
+ this,
+ tsmfHostChannelAttach,
+ tsmfHostChannelDetach,
+ tsmfHostChannelSend,
+ tsmfHostChannelRecv,
+ tsmfHostChannelControl
+ };
+
+ VBoxHostChannelHostRegister parms;
+
+ static char szProviderName[] = "/vrde/tsmf";
+
+ parms.name.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.name.u.pointer.addr = &szProviderName[0];
+ parms.name.u.pointer.size = sizeof(szProviderName);
+
+ parms.iface.type = VBOX_HGCM_SVC_PARM_PTR;
+ parms.iface.u.pointer.addr = &hostChannelInterface;
+ parms.iface.u.pointer.size = sizeof(hostChannelInterface);
+
+ VMMDev *pVMMDev = mConsole->i_getVMMDev();
+
+ if (!pVMMDev)
+ {
+ AssertMsgFailed(("setupTSMF no vmmdev\n"));
+ return;
+ }
+
+ int vrc = pVMMDev->hgcmHostCall("VBoxHostChannel",
+ VBOX_HOST_CHANNEL_HOST_FN_REGISTER,
+ 2,
+ &parms.name);
+
+ if (!RT_SUCCESS(vrc))
+ {
+ Log(("VBOX_HOST_CHANNEL_HOST_FN_REGISTER failed with %Rrc\n", vrc));
+ return;
+ }
+
+ LogRel(("VRDE: Enabled TSMF channel.\n"));
+
+ return;
+}
+
+/** @todo these defines must be in a header, which is used by guest component as well. */
+#define VBOX_TSMF_HCH_CREATE_ACCEPTED (VBOX_HOST_CHANNEL_EVENT_USER + 0)
+#define VBOX_TSMF_HCH_CREATE_DECLINED (VBOX_HOST_CHANNEL_EVENT_USER + 1)
+#define VBOX_TSMF_HCH_DISCONNECTED (VBOX_HOST_CHANNEL_EVENT_USER + 2)
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDETSMFCbNotify(void *pvContext,
+ uint32_t u32Notification,
+ void *pvChannel,
+ const void *pvParm,
+ uint32_t cbParm)
+{
+ RT_NOREF(cbParm);
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvContext);
+
+ TSMFVRDPCTX *pVRDPCtx = (TSMFVRDPCTX *)pvChannel;
+
+ Assert(pVRDPCtx->pThis == pThis);
+
+ if (pVRDPCtx->pCallbacks == NULL)
+ {
+ LogFlowFunc(("tsmfHostChannel: Channel disconnected. Skipping.\n"));
+ return;
+ }
+
+ switch (u32Notification)
+ {
+ case VRDE_TSMF_N_CREATE_ACCEPTED:
+ {
+ VRDETSMFNOTIFYCREATEACCEPTED *p = (VRDETSMFNOTIFYCREATEACCEPTED *)pvParm;
+ Assert(cbParm == sizeof(VRDETSMFNOTIFYCREATEACCEPTED));
+
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_ACCEPTED(%p): p->u32ChannelHandle %d\n",
+ pVRDPCtx, p->u32ChannelHandle));
+
+ pVRDPCtx->u32ChannelHandle = p->u32ChannelHandle;
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_CREATE_ACCEPTED,
+ NULL, 0);
+ } break;
+
+ case VRDE_TSMF_N_CREATE_DECLINED:
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_CREATE_DECLINED(%p)\n", pVRDPCtx));
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_CREATE_DECLINED,
+ NULL, 0);
+ } break;
+
+ case VRDE_TSMF_N_DATA:
+ {
+ /* Save the data in the intermediate buffer and send the event. */
+ VRDETSMFNOTIFYDATA *p = (VRDETSMFNOTIFYDATA *)pvParm;
+ Assert(cbParm == sizeof(VRDETSMFNOTIFYDATA));
+
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA(%p): p->cbData %d\n", pVRDPCtx, p->cbData));
+
+ VBOXHOSTCHANNELEVENTRECV ev;
+ ev.u32SizeAvailable = 0;
+
+ vrc = pThis->tsmfLock();
+
+ if (RT_SUCCESS(vrc))
+ {
+ TSMFHOSTCHCTX *pHostChCtx = pVRDPCtx->pHostChCtx;
+
+ if (pHostChCtx)
+ {
+ if (pHostChCtx->pvDataReceived)
+ {
+ uint32_t cbAlloc = p->cbData + pHostChCtx->cbDataReceived;
+ pHostChCtx->pvDataReceived = RTMemRealloc(pHostChCtx->pvDataReceived, cbAlloc);
+ memcpy((uint8_t *)pHostChCtx->pvDataReceived + pHostChCtx->cbDataReceived, p->pvData, p->cbData);
+
+ pHostChCtx->cbDataReceived += p->cbData;
+ pHostChCtx->cbDataAllocated = cbAlloc;
+ }
+ else
+ {
+ pHostChCtx->pvDataReceived = RTMemAlloc(p->cbData);
+ memcpy(pHostChCtx->pvDataReceived, p->pvData, p->cbData);
+
+ pHostChCtx->cbDataReceived = p->cbData;
+ pHostChCtx->cbDataAllocated = p->cbData;
+ }
+
+ ev.u32SizeAvailable = p->cbData;
+ }
+ else
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DATA: no host channel. Skipping\n"));
+ }
+
+ pThis->tsmfUnlock();
+ }
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_HOST_CHANNEL_EVENT_RECV,
+ &ev, sizeof(ev));
+ } break;
+
+ case VRDE_TSMF_N_DISCONNECTED:
+ {
+ LogFlowFunc(("tsmfHostChannel: VRDE_TSMF_N_DISCONNECTED(%p)\n", pVRDPCtx));
+
+ pVRDPCtx->pCallbacks->HostChannelCallbackEvent(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx,
+ VBOX_TSMF_HCH_DISCONNECTED,
+ NULL, 0);
+
+ /* The callback context will not be used anymore. */
+ pVRDPCtx->pCallbacks->HostChannelCallbackDeleted(pVRDPCtx->pvCallbacks, pVRDPCtx->pHostChCtx);
+ pVRDPCtx->pCallbacks = NULL;
+ pVRDPCtx->pvCallbacks = NULL;
+
+ vrc = pThis->tsmfLock();
+ if (RT_SUCCESS(vrc))
+ {
+ if (pVRDPCtx->pHostChCtx)
+ {
+ /* There is still a host channel context for this channel. */
+ pVRDPCtx->pHostChCtx->pVRDPCtx = NULL;
+ }
+
+ pThis->tsmfUnlock();
+
+ RT_ZERO(*pVRDPCtx);
+ RTMemFree(pVRDPCtx);
+ }
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ } break;
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInNotify(void *pvCallback,
+ uint32_t u32Id,
+ const void *pvData,
+ uint32_t cbData)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbNotify(u32Id, pvData, cbData);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInDeviceDesc(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc,
+ uint32_t cbDevice)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbDeviceDesc(vrcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDevice);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInControl(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl,
+ uint32_t cbControl)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbControl(vrcRequest, pDeviceCtx, pvUser, pControl, cbControl);
+ }
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackVideoInFrame(void *pvCallback,
+ int vrcRequest,
+ void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame,
+ uint32_t cbFrame)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+ if (pThis->mEmWebcam)
+ {
+ pThis->mEmWebcam->EmWebcamCbFrame(vrcRequest, pDeviceCtx, pFrame, cbFrame);
+ }
+}
+
+int ConsoleVRDPServer::VideoInDeviceAttach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle, void *pvDeviceCtx)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceAttach)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInDeviceAttach(mhServer, pDeviceHandle, pvDeviceCtx);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInDeviceDetach(const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInDeviceDetach)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInDeviceDetach(mhServer, pDeviceHandle);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInGetDeviceDesc(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInGetDeviceDesc)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInGetDeviceDesc(mhServer, pvUser, pDeviceHandle);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+int ConsoleVRDPServer::VideoInControl(void *pvUser, const VRDEVIDEOINDEVICEHANDLE *pDeviceHandle,
+ const VRDEVIDEOINCTRLHDR *pReq, uint32_t cbReq)
+{
+ int vrc;
+
+ if (mhServer && mpEntryPoints && m_interfaceVideoIn.VRDEVideoInControl)
+ {
+ vrc = m_interfaceVideoIn.VRDEVideoInControl(mhServer, pvUser, pDeviceHandle, pReq, cbReq);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputSetup(void *pvCallback,
+ int vrcRequest,
+ uint32_t u32Method,
+ const void *pvResult,
+ uint32_t cbResult)
+{
+ NOREF(pvCallback);
+ NOREF(vrcRequest);
+ NOREF(u32Method);
+ NOREF(pvResult);
+ NOREF(cbResult);
+}
+
+/* static */ DECLCALLBACK(void) ConsoleVRDPServer::VRDECallbackInputEvent(void *pvCallback,
+ uint32_t u32Method,
+ const void *pvEvent,
+ uint32_t cbEvent)
+{
+ ConsoleVRDPServer *pThis = static_cast<ConsoleVRDPServer*>(pvCallback);
+
+ if (u32Method == VRDE_INPUT_METHOD_TOUCH)
+ {
+ if (cbEvent >= sizeof(VRDEINPUTHEADER))
+ {
+ VRDEINPUTHEADER *pHeader = (VRDEINPUTHEADER *)pvEvent;
+
+ if (pHeader->u16EventId == VRDEINPUT_EVENTID_TOUCH)
+ {
+ IMouse *pMouse = pThis->mConsole->i_getMouse();
+
+ VRDEINPUT_TOUCH_EVENT_PDU *p = (VRDEINPUT_TOUCH_EVENT_PDU *)pHeader;
+
+ uint16_t iFrame;
+ for (iFrame = 0; iFrame < p->u16FrameCount; iFrame++)
+ {
+ VRDEINPUT_TOUCH_FRAME *pFrame = &p->aFrames[iFrame];
+
+ com::SafeArray<LONG64> aContacts(pFrame->u16ContactCount);
+
+ uint16_t iContact;
+ for (iContact = 0; iContact < pFrame->u16ContactCount; iContact++)
+ {
+ VRDEINPUT_CONTACT_DATA *pContact = &pFrame->aContacts[iContact];
+
+ int16_t x = (int16_t)(pContact->i32X + 1);
+ int16_t y = (int16_t)(pContact->i32Y + 1);
+ uint8_t contactId = pContact->u8ContactId;
+ uint8_t contactState = TouchContactState_None;
+
+ if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INRANGE)
+ {
+ contactState |= TouchContactState_InRange;
+ }
+ if (pContact->u32ContactFlags & VRDEINPUT_CONTACT_FLAG_INCONTACT)
+ {
+ contactState |= TouchContactState_InContact;
+ }
+
+ aContacts[iContact] = RT_MAKE_U64_FROM_U16((uint16_t)x,
+ (uint16_t)y,
+ RT_MAKE_U16(contactId, contactState),
+ 0);
+ }
+
+ if (pFrame->u64FrameOffset == 0)
+ {
+ pThis->mu64TouchInputTimestampMCS = 0;
+ }
+ else
+ {
+ pThis->mu64TouchInputTimestampMCS += pFrame->u64FrameOffset;
+ }
+
+ pMouse->PutEventMultiTouch(pFrame->u16ContactCount,
+ ComSafeArrayAsInParam(aContacts),
+ true /* isTouchScreen */,
+ (ULONG)(pThis->mu64TouchInputTimestampMCS / 1000)); /* Micro->milliseconds. */
+ }
+ }
+ else if (pHeader->u16EventId == VRDEINPUT_EVENTID_DISMISS_HOVERING_CONTACT)
+ {
+ /** @todo */
+ }
+ else
+ {
+ AssertMsgFailed(("EventId %d\n", pHeader->u16EventId));
+ }
+ }
+ }
+}
+
+
+void ConsoleVRDPServer::EnableConnections(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEEnableConnections(mhServer, true);
+
+ /* Setup the generic TSMF channel. */
+ setupTSMF();
+ }
+}
+
+void ConsoleVRDPServer::DisconnectClient(uint32_t u32ClientId, bool fReconnect)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEDisconnect(mhServer, u32ClientId, fReconnect);
+ }
+}
+
+int ConsoleVRDPServer::MousePointer(BOOL alpha,
+ ULONG xHot,
+ ULONG yHot,
+ ULONG width,
+ ULONG height,
+ const uint8_t *pu8Shape)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mhServer && mpEntryPoints && m_interfaceMousePtr.VRDEMousePtr)
+ {
+ size_t cbMask = (((width + 7) / 8) * height + 3) & ~3;
+ size_t cbData = width * height * 4;
+
+ size_t cbDstMask = alpha? 0: cbMask;
+
+ size_t cbPointer = sizeof(VRDEMOUSEPTRDATA) + cbDstMask + cbData;
+ uint8_t *pu8Pointer = (uint8_t *)RTMemAlloc(cbPointer);
+ if (pu8Pointer != NULL)
+ {
+ VRDEMOUSEPTRDATA *pPointer = (VRDEMOUSEPTRDATA *)pu8Pointer;
+
+ pPointer->u16HotX = (uint16_t)xHot;
+ pPointer->u16HotY = (uint16_t)yHot;
+ pPointer->u16Width = (uint16_t)width;
+ pPointer->u16Height = (uint16_t)height;
+ pPointer->u16MaskLen = (uint16_t)cbDstMask;
+ pPointer->u32DataLen = (uint32_t)cbData;
+
+ /* AND mask. */
+ uint8_t *pu8Mask = pu8Pointer + sizeof(VRDEMOUSEPTRDATA);
+ if (cbDstMask)
+ {
+ memcpy(pu8Mask, pu8Shape, cbDstMask);
+ }
+
+ /* XOR mask */
+ uint8_t *pu8Data = pu8Mask + pPointer->u16MaskLen;
+ memcpy(pu8Data, pu8Shape + cbMask, cbData);
+
+ m_interfaceMousePtr.VRDEMousePtr(mhServer, pPointer);
+
+ RTMemFree(pu8Pointer);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::MousePointerUpdate(const VRDECOLORPOINTER *pPointer)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEColorPointer(mhServer, pPointer);
+ }
+}
+
+void ConsoleVRDPServer::MousePointerHide(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEHidePointer(mhServer);
+ }
+}
+
+void ConsoleVRDPServer::Stop(void)
+{
+ AssertPtr(this); /** @todo r=bird: there are(/was) some odd cases where this buster was invalid on
+ * linux. Just remove this when it's 100% sure that problem has been fixed. */
+
+#ifdef VBOX_WITH_USB
+ remoteUSBThreadStop();
+#endif /* VBOX_WITH_USB */
+
+ if (mhServer)
+ {
+ HVRDESERVER hServer = mhServer;
+
+ /* Reset the handle to avoid further calls to the server. */
+ mhServer = 0;
+
+ /* Workaround for VM process hangs on termination.
+ *
+ * Make sure that the server is not currently processing a resize.
+ * mhServer 0 will not allow to enter the server again.
+ * Wait until any current resize returns from the server.
+ */
+ if (mcInResize)
+ {
+ LogRel(("VRDP: waiting for resize %d\n", mcInResize));
+
+ int i = 0;
+ while (mcInResize && ++i < 100)
+ {
+ RTThreadSleep(10);
+ }
+ }
+
+ if (mpEntryPoints && hServer)
+ {
+ mpEntryPoints->VRDEDestroy(hServer);
+ }
+ }
+
+#ifndef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ AuthLibUnload(&mAuthLibCtx);
+#endif
+}
+
+/* Worker thread for Remote USB. The thread polls the clients for
+ * the list of attached USB devices.
+ * The thread is also responsible for attaching/detaching devices
+ * to/from the VM.
+ *
+ * It is expected that attaching/detaching is not a frequent operation.
+ *
+ * The thread is always running when the VRDP server is active.
+ *
+ * The thread scans backends and requests the device list every 2 seconds.
+ *
+ * When device list is available, the thread calls the Console to process it.
+ *
+ */
+#define VRDP_DEVICE_LIST_PERIOD_MS (2000)
+
+#ifdef VBOX_WITH_USB
+static DECLCALLBACK(int) threadRemoteUSB(RTTHREAD self, void *pvUser)
+{
+ ConsoleVRDPServer *pOwner = (ConsoleVRDPServer *)pvUser;
+
+ LogFlow(("Console::threadRemoteUSB: start. owner = %p.\n", pOwner));
+
+ pOwner->notifyRemoteUSBThreadRunning(self);
+
+ while (pOwner->isRemoteUSBThreadRunning())
+ {
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ while ((pRemoteUSBBackend = pOwner->usbBackendGetNext(pRemoteUSBBackend)) != NULL)
+ {
+ pRemoteUSBBackend->PollRemoteDevices();
+ }
+
+ pOwner->waitRemoteUSBThreadEvent(VRDP_DEVICE_LIST_PERIOD_MS);
+
+ LogFlow(("Console::threadRemoteUSB: iteration. owner = %p.\n", pOwner));
+ }
+
+ return VINF_SUCCESS;
+}
+
+void ConsoleVRDPServer::notifyRemoteUSBThreadRunning(RTTHREAD thread)
+{
+ mUSBBackends.thread = thread;
+ mUSBBackends.fThreadRunning = true;
+ int vrc = RTThreadUserSignal(thread);
+ AssertRC(vrc);
+}
+
+bool ConsoleVRDPServer::isRemoteUSBThreadRunning(void)
+{
+ return mUSBBackends.fThreadRunning;
+}
+
+void ConsoleVRDPServer::waitRemoteUSBThreadEvent(RTMSINTERVAL cMillies)
+{
+ int vrc = RTSemEventWait(mUSBBackends.event, cMillies);
+ Assert(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT);
+ NOREF(vrc);
+}
+
+void ConsoleVRDPServer::remoteUSBThreadStart(void)
+{
+ int vrc = RTSemEventCreate(&mUSBBackends.event);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ mUSBBackends.event = 0;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTThreadCreate(&mUSBBackends.thread, threadRemoteUSB, this, 65536,
+ RTTHREADTYPE_VRDP_IO, RTTHREADFLAGS_WAITABLE, "remote usb");
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Warning: could not start the remote USB thread, vrc = %Rrc!!!\n", vrc));
+ mUSBBackends.thread = NIL_RTTHREAD;
+ }
+ else
+ {
+ /* Wait until the thread is ready. */
+ vrc = RTThreadUserWait(mUSBBackends.thread, 60000);
+ AssertRC(vrc);
+ Assert (mUSBBackends.fThreadRunning || RT_FAILURE(vrc));
+ }
+}
+
+void ConsoleVRDPServer::remoteUSBThreadStop(void)
+{
+ mUSBBackends.fThreadRunning = false;
+
+ if (mUSBBackends.thread != NIL_RTTHREAD)
+ {
+ Assert (mUSBBackends.event != 0);
+
+ RTSemEventSignal(mUSBBackends.event);
+
+ int vrc = RTThreadWait(mUSBBackends.thread, 60000, NULL);
+ AssertRC(vrc);
+
+ mUSBBackends.thread = NIL_RTTHREAD;
+ }
+
+ if (mUSBBackends.event)
+ {
+ RTSemEventDestroy(mUSBBackends.event);
+ mUSBBackends.event = 0;
+ }
+}
+#endif /* VBOX_WITH_USB */
+
+AuthResult ConsoleVRDPServer::Authenticate(const Guid &uuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId)
+{
+ LogFlowFunc(("uuid = %RTuuid, guestJudgement = %d, pszUser = %s, pszPassword = %s, pszDomain = %s, u32ClientId = %d\n",
+ uuid.raw(), guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId));
+
+ AuthResult result = AuthResultAccessDenied;
+
+#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ try
+ {
+ /* Init auth parameters. Order is important. */
+ SafeArray<BSTR> authParams;
+ Bstr("VRDEAUTH" ).detachTo(authParams.appendedRaw());
+ Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", guestJudgement).detachTo(authParams.appendedRaw());
+ Bstr(pszUser ).detachTo(authParams.appendedRaw());
+ Bstr(pszPassword ).detachTo(authParams.appendedRaw());
+ Bstr(pszDomain ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw());
+
+ Bstr authResult;
+ HRESULT hr = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams),
+ authResult.asOutParam());
+ LogFlowFunc(("%Rhrc [%ls]\n", hr, authResult.raw()));
+
+ size_t cbPassword = RTUtf16Len((PRTUTF16)authParams[4]) * sizeof(RTUTF16);
+ if (cbPassword)
+ RTMemWipeThoroughly(authParams[4], cbPassword, 10 /* cPasses */);
+
+ if (SUCCEEDED(hr) && authResult == "granted")
+ result = AuthResultAccessGranted;
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+#else
+ /*
+ * Called only from VRDP input thread. So thread safety is not required.
+ */
+
+ if (!mAuthLibCtx.hAuthLibrary)
+ {
+ /* Load the external authentication library. */
+ Bstr authLibrary;
+ mConsole->i_getVRDEServer()->COMGETTER(AuthLibrary)(authLibrary.asOutParam());
+
+ Utf8Str filename = authLibrary;
+
+ int vrc = AuthLibLoad(&mAuthLibCtx, filename.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ mConsole->setErrorBoth(E_FAIL, vrc, tr("Could not load the external authentication library '%s' (%Rrc)"),
+ filename.c_str(), vrc);
+ return AuthResultAccessDenied;
+ }
+ }
+
+ result = AuthLibAuthenticate(&mAuthLibCtx,
+ uuid.raw(), guestJudgement,
+ pszUser, pszPassword, pszDomain,
+ u32ClientId);
+#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */
+
+ switch (result)
+ {
+ case AuthResultAccessDenied:
+ LogRel(("AUTH: external authentication module returned 'access denied'\n"));
+ break;
+ case AuthResultAccessGranted:
+ LogRel(("AUTH: external authentication module returned 'access granted'\n"));
+ break;
+ case AuthResultDelegateToGuest:
+ LogRel(("AUTH: external authentication module returned 'delegate request to guest'\n"));
+ break;
+ default:
+ LogRel(("AUTH: external authentication module returned incorrect return code %d\n", result));
+ result = AuthResultAccessDenied;
+ }
+
+ LogFlowFunc(("result = %d\n", result));
+
+ return result;
+}
+
+void ConsoleVRDPServer::AuthDisconnect(const Guid &uuid, uint32_t u32ClientId)
+{
+ LogFlow(("ConsoleVRDPServer::AuthDisconnect: uuid = %RTuuid, u32ClientId = %d\n",
+ uuid.raw(), u32ClientId));
+
+#ifdef VBOX_WITH_VRDEAUTH_IN_VBOXSVC
+ try
+ {
+ /* Init auth parameters. Order is important. */
+ SafeArray<BSTR> authParams;
+ Bstr("VRDEAUTHDISCONNECT").detachTo(authParams.appendedRaw());
+ Bstr(uuid.toUtf16() ).detachTo(authParams.appendedRaw());
+ BstrFmt("%u", u32ClientId).detachTo(authParams.appendedRaw());
+
+ Bstr authResult;
+ HRESULT hrc = mConsole->mControl->AuthenticateExternal(ComSafeArrayAsInParam(authParams),
+ authResult.asOutParam());
+ LogFlowFunc(("%Rhrc [%ls]\n", hrc, authResult.raw())); NOREF(hrc);
+ }
+ catch (std::bad_alloc &)
+ {
+ }
+#else
+ AuthLibDisconnect(&mAuthLibCtx, uuid.raw(), u32ClientId);
+#endif /* !VBOX_WITH_VRDEAUTH_IN_VBOXSVC */
+}
+
+int ConsoleVRDPServer::lockConsoleVRDPServer(void)
+{
+ int vrc = RTCritSectEnter(&mCritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+void ConsoleVRDPServer::unlockConsoleVRDPServer(void)
+{
+ RTCritSectLeave(&mCritSect);
+}
+
+DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback,
+ uint32_t u32ClientId,
+ uint32_t u32Function,
+ uint32_t u32Format,
+ const void *pvData,
+ uint32_t cbData)
+{
+ LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n",
+ pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData));
+
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvCallback);
+
+ RT_NOREF(u32ClientId);
+
+ switch (u32Function)
+ {
+ case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE:
+ {
+ if (pServer->mpfnClipboardCallback)
+ {
+ vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE,
+ u32Format,
+ (void *)pvData,
+ cbData);
+ }
+ } break;
+
+ case VRDE_CLIPBOARD_FUNCTION_DATA_READ:
+ {
+ if (pServer->mpfnClipboardCallback)
+ {
+ vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ,
+ u32Format,
+ (void *)pvData,
+ cbData);
+ }
+ } break;
+
+ default:
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+ConsoleVRDPServer::ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms)
+{
+ RT_NOREF(cbParms);
+ LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n",
+ pvExtension, u32Function, pvParms, cbParms));
+
+ int vrc = VINF_SUCCESS;
+
+ ConsoleVRDPServer *pServer = static_cast <ConsoleVRDPServer *>(pvExtension);
+
+ SHCLEXTPARMS *pParms = (SHCLEXTPARMS *)pvParms;
+
+ switch (u32Function)
+ {
+ case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK:
+ {
+ pServer->mpfnClipboardCallback = pParms->u.pfnCallback;
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE:
+ {
+ /* The guest announces clipboard formats. This must be delivered to all clients. */
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE,
+ pParms->uFormat,
+ NULL,
+ 0,
+ NULL);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_READ:
+ {
+ /* The clipboard service expects that the pvData buffer will be filled
+ * with clipboard data. The server returns the data from the client that
+ * announced the requested format most recently.
+ */
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_DATA_READ,
+ pParms->uFormat,
+ pParms->u.pvData,
+ pParms->cbData,
+ &pParms->cbData);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE:
+ {
+ if (mpEntryPoints && pServer->mhServer)
+ {
+ mpEntryPoints->VRDEClipboard(pServer->mhServer,
+ VRDE_CLIPBOARD_FUNCTION_DATA_WRITE,
+ pParms->uFormat,
+ pParms->u.pvData,
+ pParms->cbData,
+ NULL);
+ }
+ } break;
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ return vrc;
+}
+
+void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+
+ int vrc = lockConsoleVRDPServer();
+ if (RT_SUCCESS(vrc))
+ {
+ if (mcClipboardRefs == 0)
+ {
+ vrc = HGCMHostRegisterServiceExtension(&mhClipboard, "VBoxSharedClipboard", ClipboardServiceExtension, this);
+ AssertRC(vrc);
+ }
+
+ mcClipboardRefs++;
+ unlockConsoleVRDPServer();
+ }
+}
+
+void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId)
+{
+ RT_NOREF(u32ClientId);
+
+ int vrc = lockConsoleVRDPServer();
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(mcClipboardRefs);
+ if (mcClipboardRefs > 0)
+ {
+ mcClipboardRefs--;
+
+ if (mcClipboardRefs == 0 && mhClipboard)
+ {
+ HGCMHostUnregisterServiceExtension(mhClipboard);
+ mhClipboard = NULL;
+ }
+ }
+
+ unlockConsoleVRDPServer();
+ }
+}
+
+/* That is called on INPUT thread of the VRDP server.
+ * The ConsoleVRDPServer keeps a list of created backend instances.
+ */
+void ConsoleVRDPServer::USBBackendCreate(uint32_t u32ClientId, void **ppvIntercept)
+{
+#ifdef VBOX_WITH_USB
+ LogFlow(("ConsoleVRDPServer::USBBackendCreate: u32ClientId = %d\n", u32ClientId));
+
+ /* Create a new instance of the USB backend for the new client. */
+ RemoteUSBBackend *pRemoteUSBBackend = new RemoteUSBBackend(mConsole, this, u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->AddRef(); /* 'Release' called in USBBackendDelete. */
+
+ /* Append the new instance in the list. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend->pNext = mUSBBackends.pHead;
+ if (mUSBBackends.pHead)
+ {
+ mUSBBackends.pHead->pPrev = pRemoteUSBBackend;
+ }
+ else
+ {
+ mUSBBackends.pTail = pRemoteUSBBackend;
+ }
+
+ mUSBBackends.pHead = pRemoteUSBBackend;
+
+ unlockConsoleVRDPServer();
+
+ if (ppvIntercept)
+ {
+ *ppvIntercept = pRemoteUSBBackend;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#else
+ RT_NOREF(u32ClientId, ppvIntercept);
+#endif /* VBOX_WITH_USB */
+}
+
+void ConsoleVRDPServer::USBBackendDelete(uint32_t u32ClientId)
+{
+#ifdef VBOX_WITH_USB
+ LogFlow(("ConsoleVRDPServer::USBBackendDelete: u32ClientId = %d\n", u32ClientId));
+
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFind(u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ /* Notify that it will be deleted. */
+ pRemoteUSBBackend->NotifyDelete();
+ }
+
+ unlockConsoleVRDPServer();
+ }
+
+ if (pRemoteUSBBackend)
+ {
+ /* Here the instance has been excluded from the list and can be dereferenced. */
+ pRemoteUSBBackend->Release();
+ }
+#else
+ RT_NOREF(u32ClientId);
+#endif
+}
+
+void *ConsoleVRDPServer::USBBackendRequestPointer(uint32_t u32ClientId, const Guid *pGuid)
+{
+#ifdef VBOX_WITH_USB
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFind(u32ClientId);
+
+ if (pRemoteUSBBackend)
+ {
+ /* Inform the backend instance that it is referenced by the Guid. */
+ bool fAdded = pRemoteUSBBackend->addUUID(pGuid);
+
+ if (fAdded)
+ {
+ /* Reference the instance because its pointer is being taken. */
+ pRemoteUSBBackend->AddRef(); /* 'Release' is called in USBBackendReleasePointer. */
+ }
+ else
+ {
+ pRemoteUSBBackend = NULL;
+ }
+ }
+
+ unlockConsoleVRDPServer();
+ }
+
+ if (pRemoteUSBBackend)
+ {
+ return pRemoteUSBBackend->GetBackendCallbackPointer();
+ }
+#else
+ RT_NOREF(u32ClientId, pGuid);
+#endif
+ return NULL;
+}
+
+void ConsoleVRDPServer::USBBackendReleasePointer(const Guid *pGuid)
+{
+#ifdef VBOX_WITH_USB
+ RemoteUSBBackend *pRemoteUSBBackend = NULL;
+
+ /* Find the instance. */
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pRemoteUSBBackend = usbBackendFindByUUID(pGuid);
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->removeUUID(pGuid);
+ }
+
+ unlockConsoleVRDPServer();
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#else
+ RT_NOREF(pGuid);
+#endif
+}
+
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendGetNext(RemoteUSBBackend *pRemoteUSBBackend)
+{
+ LogFlow(("ConsoleVRDPServer::usbBackendGetNext: pBackend = %p\n", pRemoteUSBBackend));
+
+ RemoteUSBBackend *pNextRemoteUSBBackend = NULL;
+#ifdef VBOX_WITH_USB
+
+ int vrc = lockConsoleVRDPServer();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pRemoteUSBBackend == NULL)
+ {
+ /* The first backend in the list is requested. */
+ pNextRemoteUSBBackend = mUSBBackends.pHead;
+ }
+ else
+ {
+ /* Get pointer to the next backend. */
+ pNextRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ if (pNextRemoteUSBBackend)
+ {
+ pNextRemoteUSBBackend->AddRef();
+ }
+
+ unlockConsoleVRDPServer();
+
+ if (pRemoteUSBBackend)
+ {
+ pRemoteUSBBackend->Release();
+ }
+ }
+#endif
+
+ return pNextRemoteUSBBackend;
+}
+
+#ifdef VBOX_WITH_USB
+/* Internal method. Called under the ConsoleVRDPServerLock. */
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendFind(uint32_t u32ClientId)
+{
+ RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead;
+
+ while (pRemoteUSBBackend)
+ {
+ if (pRemoteUSBBackend->ClientId() == u32ClientId)
+ {
+ break;
+ }
+
+ pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ return pRemoteUSBBackend;
+}
+
+/* Internal method. Called under the ConsoleVRDPServerLock. */
+RemoteUSBBackend *ConsoleVRDPServer::usbBackendFindByUUID(const Guid *pGuid)
+{
+ RemoteUSBBackend *pRemoteUSBBackend = mUSBBackends.pHead;
+
+ while (pRemoteUSBBackend)
+ {
+ if (pRemoteUSBBackend->findUUID(pGuid))
+ {
+ break;
+ }
+
+ pRemoteUSBBackend = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ return pRemoteUSBBackend;
+}
+#endif
+
+/* Internal method. Called by the backend destructor. */
+void ConsoleVRDPServer::usbBackendRemoveFromList(RemoteUSBBackend *pRemoteUSBBackend)
+{
+#ifdef VBOX_WITH_USB
+ int vrc = lockConsoleVRDPServer();
+ AssertRC(vrc);
+
+ /* Exclude the found instance from the list. */
+ if (pRemoteUSBBackend->pNext)
+ {
+ pRemoteUSBBackend->pNext->pPrev = pRemoteUSBBackend->pPrev;
+ }
+ else
+ {
+ mUSBBackends.pTail = (RemoteUSBBackend *)pRemoteUSBBackend->pPrev;
+ }
+
+ if (pRemoteUSBBackend->pPrev)
+ {
+ pRemoteUSBBackend->pPrev->pNext = pRemoteUSBBackend->pNext;
+ }
+ else
+ {
+ mUSBBackends.pHead = (RemoteUSBBackend *)pRemoteUSBBackend->pNext;
+ }
+
+ pRemoteUSBBackend->pNext = pRemoteUSBBackend->pPrev = NULL;
+
+ unlockConsoleVRDPServer();
+#else
+ RT_NOREF(pRemoteUSBBackend);
+#endif
+}
+
+
+void ConsoleVRDPServer::SendUpdate(unsigned uScreenId, void *pvUpdate, uint32_t cbUpdate) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUpdate(mhServer, uScreenId, pvUpdate, cbUpdate);
+ }
+}
+
+void ConsoleVRDPServer::SendResize(void)
+{
+ if (mpEntryPoints && mhServer)
+ {
+ ++mcInResize;
+ mpEntryPoints->VRDEResize(mhServer);
+ --mcInResize;
+ }
+}
+
+void ConsoleVRDPServer::SendUpdateBitmap(unsigned uScreenId, uint32_t x, uint32_t y, uint32_t w, uint32_t h) const
+{
+ VRDEORDERHDR update;
+ update.x = (uint16_t)x;
+ update.y = (uint16_t)y;
+ update.w = (uint16_t)w;
+ update.h = (uint16_t)h;
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUpdate(mhServer, uScreenId, &update, sizeof(update));
+ }
+}
+
+void ConsoleVRDPServer::SendAudioSamples(void const *pvSamples, uint32_t cSamples, VRDEAUDIOFORMAT format) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEAudioSamples(mhServer, pvSamples, cSamples, format);
+ }
+}
+
+void ConsoleVRDPServer::SendAudioVolume(uint16_t left, uint16_t right) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEAudioVolume(mhServer, left, right);
+ }
+}
+
+void ConsoleVRDPServer::SendUSBRequest(uint32_t u32ClientId, void *pvParms, uint32_t cbParms) const
+{
+ if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEUSBRequest(mhServer, u32ClientId, pvParms, cbParms);
+ }
+}
+
+int ConsoleVRDPServer::SendAudioInputBegin(void **ppvUserCtx,
+ void *pvContext,
+ uint32_t cSamples,
+ uint32_t iSampleHz,
+ uint32_t cChannels,
+ uint32_t cBits)
+{
+ if ( mhServer
+ && mpEntryPoints && mpEntryPoints->VRDEAudioInOpen)
+ {
+ uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId);
+ if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */
+ {
+ VRDEAUDIOFORMAT audioFormat = VRDE_AUDIO_FMT_MAKE(iSampleHz, cChannels, cBits, 0);
+ mpEntryPoints->VRDEAudioInOpen(mhServer,
+ pvContext,
+ u32ClientId,
+ audioFormat,
+ cSamples);
+ if (ppvUserCtx)
+ *ppvUserCtx = NULL; /* This is the ConsoleVRDPServer context.
+ * Currently not used because only one client is allowed to
+ * do audio input and the client ID is saved by the ConsoleVRDPServer.
+ */
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Not supported or no client connected.
+ */
+ return VERR_NOT_SUPPORTED;
+}
+
+void ConsoleVRDPServer::SendAudioInputEnd(void *pvUserCtx)
+{
+ RT_NOREF(pvUserCtx);
+ if (mpEntryPoints && mhServer && mpEntryPoints->VRDEAudioInClose)
+ {
+ uint32_t u32ClientId = ASMAtomicReadU32(&mu32AudioInputClientId);
+ if (u32ClientId != 0) /* 0 would mean broadcast to all clients. */
+ {
+ mpEntryPoints->VRDEAudioInClose(mhServer, u32ClientId);
+ }
+ }
+}
+
+void ConsoleVRDPServer::QueryInfo(uint32_t index, void *pvBuffer, uint32_t cbBuffer, uint32_t *pcbOut) const
+{
+ if (index == VRDE_QI_PORT)
+ {
+ uint32_t cbOut = sizeof(int32_t);
+
+ if (cbBuffer >= cbOut)
+ {
+ *pcbOut = cbOut;
+ *(int32_t *)pvBuffer = (int32_t)mVRDPBindPort;
+ }
+ }
+ else if (mpEntryPoints && mhServer)
+ {
+ mpEntryPoints->VRDEQueryInfo(mhServer, index, pvBuffer, cbBuffer, pcbOut);
+ }
+}
+
+/* static */ int ConsoleVRDPServer::loadVRDPLibrary(const char *pszLibraryName)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (mVRDPLibrary == NIL_RTLDRMOD)
+ {
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+
+ if (RTPathHavePath(pszLibraryName))
+ vrc = SUPR3HardenedLdrLoadPlugIn(pszLibraryName, &mVRDPLibrary, &ErrInfo.Core);
+ else
+ vrc = SUPR3HardenedLdrLoadAppPriv(pszLibraryName, &mVRDPLibrary, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core);
+ if (RT_SUCCESS(vrc))
+ {
+ struct SymbolEntry
+ {
+ const char *name;
+ void **ppfn;
+ };
+
+ #define DEFSYMENTRY(a) { #a, (void**)&mpfn##a }
+
+ static const struct SymbolEntry s_aSymbols[] =
+ {
+ DEFSYMENTRY(VRDECreateServer)
+ };
+
+ #undef DEFSYMENTRY
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
+ {
+ vrc = RTLdrGetSymbol(mVRDPLibrary, s_aSymbols[i].name, s_aSymbols[i].ppfn);
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VRDE: Error resolving symbol '%s', vrc %Rrc.\n", s_aSymbols[i].name, vrc));
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ LogRel(("VRDE: Error loading the library '%s': %s (%Rrc)\n", pszLibraryName, ErrInfo.Core.pszMsg, vrc));
+ else
+ LogRel(("VRDE: Error loading the library '%s' vrc = %Rrc.\n", pszLibraryName, vrc));
+
+ mVRDPLibrary = NIL_RTLDRMOD;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (mVRDPLibrary != NIL_RTLDRMOD)
+ {
+ RTLdrClose(mVRDPLibrary);
+ mVRDPLibrary = NIL_RTLDRMOD;
+ }
+ }
+
+ return vrc;
+}
+
+/*
+ * IVRDEServerInfo implementation.
+ */
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+VRDEServerInfo::VRDEServerInfo()
+ : mParent(NULL)
+{
+}
+
+VRDEServerInfo::~VRDEServerInfo()
+{
+}
+
+
+HRESULT VRDEServerInfo::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void VRDEServerInfo::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the guest object.
+ */
+HRESULT VRDEServerInfo::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void VRDEServerInfo::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+}
+
+// IVRDEServerInfo properties
+/////////////////////////////////////////////////////////////////////////////
+
+#define IMPL_GETTER_BOOL(_aType, _aName, _aIndex) \
+ HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ uint32_t value; \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, &value, sizeof(value), &cbOut); \
+ \
+ *a##_aName = cbOut? !!value: FALSE; \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_BOOL_DUMMY(void)
+
+#define IMPL_GETTER_SCALAR(_aType, _aName, _aIndex, _aValueMask) \
+ HRESULT VRDEServerInfo::get##_aName(_aType *a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ _aType value; \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, &value, sizeof(value), &cbOut); \
+ \
+ if (_aValueMask) value &= (_aValueMask); \
+ *a##_aName = cbOut? value: 0; \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_SCALAR_DUMMY(void)
+
+#define IMPL_GETTER_UTF8STR(_aType, _aName, _aIndex) \
+ HRESULT VRDEServerInfo::get##_aName(_aType &a##_aName) \
+ { \
+ /** @todo Not sure if a AutoReadLock would be sufficient. */ \
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); \
+ \
+ uint32_t cbOut = 0; \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, NULL, 0, &cbOut); \
+ \
+ if (cbOut == 0) \
+ { \
+ a##_aName = Utf8Str::Empty; \
+ return S_OK; \
+ } \
+ \
+ char *pchBuffer = (char *)RTMemTmpAlloc(cbOut); \
+ \
+ if (!pchBuffer) \
+ { \
+ Log(("VRDEServerInfo::" \
+ #_aName \
+ ": Failed to allocate memory %d bytes\n", cbOut)); \
+ return E_OUTOFMEMORY; \
+ } \
+ \
+ mParent->i_consoleVRDPServer()->QueryInfo \
+ (_aIndex, pchBuffer, cbOut, &cbOut); \
+ \
+ a##_aName = pchBuffer; \
+ \
+ RTMemTmpFree(pchBuffer); \
+ \
+ return S_OK; \
+ } \
+ extern void IMPL_GETTER_BSTR_DUMMY(void)
+
+IMPL_GETTER_BOOL (BOOL, Active, VRDE_QI_ACTIVE);
+IMPL_GETTER_SCALAR (LONG, Port, VRDE_QI_PORT, 0);
+IMPL_GETTER_SCALAR (ULONG, NumberOfClients, VRDE_QI_NUMBER_OF_CLIENTS, 0);
+IMPL_GETTER_SCALAR (LONG64, BeginTime, VRDE_QI_BEGIN_TIME, 0);
+IMPL_GETTER_SCALAR (LONG64, EndTime, VRDE_QI_END_TIME, 0);
+IMPL_GETTER_SCALAR (LONG64, BytesSent, VRDE_QI_BYTES_SENT, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesSentTotal, VRDE_QI_BYTES_SENT_TOTAL, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesReceived, VRDE_QI_BYTES_RECEIVED, INT64_MAX);
+IMPL_GETTER_SCALAR (LONG64, BytesReceivedTotal, VRDE_QI_BYTES_RECEIVED_TOTAL, INT64_MAX);
+IMPL_GETTER_UTF8STR(Utf8Str, User, VRDE_QI_USER);
+IMPL_GETTER_UTF8STR(Utf8Str, Domain, VRDE_QI_DOMAIN);
+IMPL_GETTER_UTF8STR(Utf8Str, ClientName, VRDE_QI_CLIENT_NAME);
+IMPL_GETTER_UTF8STR(Utf8Str, ClientIP, VRDE_QI_CLIENT_IP);
+IMPL_GETTER_SCALAR (ULONG, ClientVersion, VRDE_QI_CLIENT_VERSION, 0);
+IMPL_GETTER_SCALAR (ULONG, EncryptionStyle, VRDE_QI_ENCRYPTION_STYLE, 0);
+
+#undef IMPL_GETTER_UTF8STR
+#undef IMPL_GETTER_SCALAR
+#undef IMPL_GETTER_BOOL
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplayImpl.cpp b/src/VBox/Main/src-client/DisplayImpl.cpp
new file mode 100644
index 00000000..5a4beefe
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplayImpl.cpp
@@ -0,0 +1,3871 @@
+/* $Id: DisplayImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+#include "DisplayUtils.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "GuestImpl.h"
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+
+/* generated header */
+#include "VBoxEvents.h"
+
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include <iprt/time.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/alloca.h>
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/pdmdrv.h>
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+# include <VBoxVideo.h>
+#endif
+#include <VBoxVideo3D.h>
+
+#include <VBox/com/array.h>
+
+#ifdef VBOX_WITH_RECORDING
+# include <iprt/path.h>
+# include "Recording.h"
+
+# include <VBox/vmm/pdmapi.h>
+# include <VBox/vmm/pdmaudioifs.h>
+#endif
+
+/**
+ * Display driver instance data.
+ *
+ * @implements PDMIDISPLAYCONNECTOR
+ */
+typedef struct DRVMAINDISPLAY
+{
+ /** Pointer to the display object. */
+ Display *pDisplay;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the display port interface of the driver/device above us. */
+ PPDMIDISPLAYPORT pUpPort;
+ /** Our display connector interface. */
+ PDMIDISPLAYCONNECTOR IConnector;
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ /** VBVA callbacks */
+ PPDMIDISPLAYVBVACALLBACKS pVBVACallbacks;
+#endif
+} DRVMAINDISPLAY, *PDRVMAINDISPLAY;
+
+/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */
+#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) RT_FROM_MEMBER(pInterface, DRVMAINDISPLAY, IConnector)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Display::Display()
+ : mParent(NULL)
+{
+}
+
+Display::~Display()
+{
+}
+
+
+HRESULT Display::FinalConstruct()
+{
+ int vrc = videoAccelConstruct(&mVideoAccelLegacy);
+ AssertRC(vrc);
+
+ mfVideoAccelVRDP = false;
+ mfu32SupportedOrders = 0;
+ mcVRDPRefs = 0;
+
+ mfSeamlessEnabled = false;
+ mpRectVisibleRegion = NULL;
+ mcRectVisibleRegion = 0;
+
+ mpDrv = NULL;
+
+ vrc = RTCritSectInit(&mVideoAccelLock);
+ AssertRC(vrc);
+
+#ifdef VBOX_WITH_HGSMI
+ mu32UpdateVBVAFlags = 0;
+ mfVMMDevSupportsGraphics = false;
+ mfGuestVBVACapabilities = 0;
+ mfHostCursorCapabilities = 0;
+#endif
+
+#ifdef VBOX_WITH_RECORDING
+ vrc = RTCritSectInit(&mVideoRecLock);
+ AssertRC(vrc);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(maRecordingEnabled); i++)
+ maRecordingEnabled[i] = true;
+#endif
+
+ return BaseFinalConstruct();
+}
+
+void Display::FinalRelease()
+{
+ uninit();
+
+#ifdef VBOX_WITH_RECORDING
+ if (RTCritSectIsInitialized(&mVideoRecLock))
+ {
+ RTCritSectDelete(&mVideoRecLock);
+ RT_ZERO(mVideoRecLock);
+ }
+#endif
+
+ videoAccelDestroy(&mVideoAccelLegacy);
+ i_saveVisibleRegion(0, NULL);
+
+ if (RTCritSectIsInitialized(&mVideoAccelLock))
+ {
+ RTCritSectDelete(&mVideoAccelLock);
+ RT_ZERO(mVideoAccelLock);
+ }
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+#define kMaxSizeThumbnail 64
+
+/**
+ * Save thumbnail and screenshot of the guest screen.
+ */
+static int displayMakeThumbnail(uint8_t *pbData, uint32_t cx, uint32_t cy,
+ uint8_t **ppu8Thumbnail, uint32_t *pcbThumbnail, uint32_t *pcxThumbnail, uint32_t *pcyThumbnail)
+{
+ int vrc = VINF_SUCCESS;
+
+ uint8_t *pu8Thumbnail = NULL;
+ uint32_t cbThumbnail = 0;
+ uint32_t cxThumbnail = 0;
+ uint32_t cyThumbnail = 0;
+
+ if (cx > cy)
+ {
+ cxThumbnail = kMaxSizeThumbnail;
+ cyThumbnail = (kMaxSizeThumbnail * cy) / cx;
+ }
+ else
+ {
+ cyThumbnail = kMaxSizeThumbnail;
+ cxThumbnail = (kMaxSizeThumbnail * cx) / cy;
+ }
+
+ LogRelFlowFunc(("%dx%d -> %dx%d\n", cx, cy, cxThumbnail, cyThumbnail));
+
+ cbThumbnail = cxThumbnail * 4 * cyThumbnail;
+ pu8Thumbnail = (uint8_t *)RTMemAlloc(cbThumbnail);
+
+ if (pu8Thumbnail)
+ {
+ uint8_t *dst = pu8Thumbnail;
+ uint8_t *src = pbData;
+ int dstW = cxThumbnail;
+ int dstH = cyThumbnail;
+ int srcW = cx;
+ int srcH = cy;
+ int iDeltaLine = cx * 4;
+
+ BitmapScale32(dst,
+ dstW, dstH,
+ src,
+ iDeltaLine,
+ srcW, srcH);
+
+ *ppu8Thumbnail = pu8Thumbnail;
+ *pcbThumbnail = cbThumbnail;
+ *pcxThumbnail = cxThumbnail;
+ *pcyThumbnail = cyThumbnail;
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTSAVEEXEC}
+ */
+DECLCALLBACK(int) Display::i_displaySSMSaveScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ /* 32bpp small RGB image. */
+ uint8_t *pu8Thumbnail = NULL;
+ uint32_t cbThumbnail = 0;
+ uint32_t cxThumbnail = 0;
+ uint32_t cyThumbnail = 0;
+
+ /* PNG screenshot. */
+ uint8_t *pu8PNG = NULL;
+ uint32_t cbPNG = 0;
+ uint32_t cxPNG = 0;
+ uint32_t cyPNG = 0;
+
+ Console::SafeVMPtr ptrVM(pThat->mParent);
+ if (ptrVM.isOk())
+ {
+ /* Query RGB bitmap. */
+ /* SSM code is executed on EMT(0), therefore no need to use VMR3ReqCallWait. */
+ uint8_t *pbData = NULL;
+ size_t cbData = 0;
+ uint32_t cx = 0;
+ uint32_t cy = 0;
+ bool fFreeMem = false;
+ int vrc = Display::i_displayTakeScreenshotEMT(pThat, VBOX_VIDEO_PRIMARY_SCREEN, &pbData, &cbData, &cx, &cy, &fFreeMem);
+
+ /*
+ * It is possible that success is returned but everything is 0 or NULL.
+ * (no display attached if a VM is running with VBoxHeadless on OSE for example)
+ */
+ if (RT_SUCCESS(vrc) && pbData)
+ {
+ Assert(cx && cy);
+
+ /* Prepare a small thumbnail and a PNG screenshot. */
+ displayMakeThumbnail(pbData, cx, cy, &pu8Thumbnail, &cbThumbnail, &cxThumbnail, &cyThumbnail);
+ vrc = DisplayMakePNG(pbData, cx, cy, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 1);
+ if (RT_FAILURE(vrc))
+ {
+ if (pu8PNG)
+ {
+ RTMemFree(pu8PNG);
+ pu8PNG = NULL;
+ }
+ cbPNG = 0;
+ cxPNG = 0;
+ cyPNG = 0;
+ }
+
+ if (fFreeMem)
+ RTMemFree(pbData);
+ else
+ pThat->mpDrv->pUpPort->pfnFreeScreenshot(pThat->mpDrv->pUpPort, pbData);
+ }
+ }
+ else
+ {
+ LogFunc(("Failed to get VM pointer 0x%x\n", ptrVM.hrc()));
+ }
+
+ /* Regardless of vrc, save what is available:
+ * Data format:
+ * uint32_t cBlocks;
+ * [blocks]
+ *
+ * Each block is:
+ * uint32_t cbBlock; if 0 - no 'block data'.
+ * uint32_t typeOfBlock; 0 - 32bpp RGB bitmap, 1 - PNG, ignored if 'cbBlock' is 0.
+ * [block data]
+ *
+ * Block data for bitmap and PNG:
+ * uint32_t cx;
+ * uint32_t cy;
+ * [image data]
+ */
+ pVMM->pfnSSMR3PutU32(pSSM, 2); /* Write thumbnail and PNG screenshot. */
+
+ /* First block. */
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbThumbnail + 2 * sizeof(uint32_t)));
+ pVMM->pfnSSMR3PutU32(pSSM, 0); /* Block type: thumbnail. */
+
+ if (cbThumbnail)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, cxThumbnail);
+ pVMM->pfnSSMR3PutU32(pSSM, cyThumbnail);
+ pVMM->pfnSSMR3PutMem(pSSM, pu8Thumbnail, cbThumbnail);
+ }
+
+ /* Second block. */
+ pVMM->pfnSSMR3PutU32(pSSM, (uint32_t)(cbPNG + 2 * sizeof(uint32_t)));
+ pVMM->pfnSSMR3PutU32(pSSM, 1); /* Block type: png. */
+
+ if (cbPNG)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, cxPNG);
+ pVMM->pfnSSMR3PutU32(pSSM, cyPNG);
+ pVMM->pfnSSMR3PutMem(pSSM, pu8PNG, cbPNG);
+ }
+
+ RTMemFree(pu8PNG);
+ RTMemFree(pu8Thumbnail);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTLOADEXEC}
+ */
+DECLCALLBACK(int)
+Display::i_displaySSMLoadScreenshot(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+ Assert(uPass == SSM_PASS_FINAL); RT_NOREF_PV(uPass);
+
+ if (uVersion != sSSMDisplayScreenshotVer)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Skip data. */
+ uint32_t cBlocks;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cBlocks);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < cBlocks; i++)
+ {
+ uint32_t cbBlock;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cbBlock);
+ AssertRCReturn(vrc, vrc);
+
+ uint32_t typeOfBlock;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &typeOfBlock);
+ AssertRCReturn(vrc, vrc);
+
+ LogRelFlowFunc(("[%d] type %d, size %d bytes\n", i, typeOfBlock, cbBlock));
+
+ /* Note: displaySSMSaveScreenshot writes size of a block = 8 and
+ * do not write any data if the image size was 0.
+ */
+ /** @todo Fix and increase saved state version. */
+ if (cbBlock > 2 * sizeof(uint32_t))
+ {
+ vrc = pVMM->pfnSSMR3Skip(pSSM, cbBlock);
+ AssertRCReturn(vrc, vrc);
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTSAVEEXEC, Save some important guest state}
+ */
+/*static*/ DECLCALLBACK(int)
+Display::i_displaySSMSave(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->mcMonitors);
+ for (unsigned i = 0; i < pThat->mcMonitors; i++)
+ {
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32Offset);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32MaxFramebufferSize);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].u32InformationSize);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].w);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].h);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].xOrigin);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->maFramebuffers[i].yOrigin);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->maFramebuffers[i].flags);
+ }
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->xInputMappingOrigin);
+ pVMM->pfnSSMR3PutS32(pSSM, pThat->yInputMappingOrigin);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->cxInputMapping);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->cyInputMapping);
+ pVMM->pfnSSMR3PutU32(pSSM, pThat->mfGuestVBVACapabilities);
+ return pVMM->pfnSSMR3PutU32(pSSM, pThat->mfHostCursorCapabilities);
+}
+
+/**
+ * @callback_method_impl{FNSSMEXTLOADEXEC, Load some important guest state}
+ */
+/*static*/ DECLCALLBACK(int)
+Display::i_displaySSMLoad(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, void *pvUser, uint32_t uVersion, uint32_t uPass)
+{
+ Display * const pThat = static_cast<Display *>(pvUser);
+ AssertPtrReturn(pThat, VERR_INVALID_POINTER);
+
+ if ( uVersion != sSSMDisplayVer
+ && uVersion != sSSMDisplayVer2
+ && uVersion != sSSMDisplayVer3
+ && uVersion != sSSMDisplayVer4
+ && uVersion != sSSMDisplayVer5)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ uint32_t cMonitors;
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &cMonitors);
+ AssertRCReturn(vrc, vrc);
+ if (cMonitors != pThat->mcMonitors)
+ return pVMM->pfnSSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Number of monitors changed (%d->%d)!"), cMonitors, pThat->mcMonitors);
+
+ for (uint32_t i = 0; i < cMonitors; i++)
+ {
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32Offset);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32MaxFramebufferSize);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->maFramebuffers[i].u32InformationSize);
+ if ( uVersion == sSSMDisplayVer2
+ || uVersion == sSSMDisplayVer3
+ || uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ uint32_t w;
+ uint32_t h;
+ pVMM->pfnSSMR3GetU32(pSSM, &w);
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &h);
+ AssertRCReturn(vrc, vrc);
+ pThat->maFramebuffers[i].w = w;
+ pThat->maFramebuffers[i].h = h;
+ }
+ if ( uVersion == sSSMDisplayVer3
+ || uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ int32_t xOrigin;
+ int32_t yOrigin;
+ uint32_t flags;
+ pVMM->pfnSSMR3GetS32(pSSM, &xOrigin);
+ pVMM->pfnSSMR3GetS32(pSSM, &yOrigin);
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &flags);
+ AssertRCReturn(vrc, vrc);
+ pThat->maFramebuffers[i].xOrigin = xOrigin;
+ pThat->maFramebuffers[i].yOrigin = yOrigin;
+ pThat->maFramebuffers[i].flags = (uint16_t)flags;
+ pThat->maFramebuffers[i].fDisabled = (pThat->maFramebuffers[i].flags & VBVA_SCREEN_F_DISABLED) != 0;
+ }
+ }
+ if ( uVersion == sSSMDisplayVer4
+ || uVersion == sSSMDisplayVer5)
+ {
+ pVMM->pfnSSMR3GetS32(pSSM, &pThat->xInputMappingOrigin);
+ pVMM->pfnSSMR3GetS32(pSSM, &pThat->yInputMappingOrigin);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->cxInputMapping);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->cyInputMapping);
+ }
+ if (uVersion == sSSMDisplayVer5)
+ {
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfGuestVBVACapabilities);
+ pVMM->pfnSSMR3GetU32(pSSM, &pThat->mfHostCursorCapabilities);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes the display object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT Display::init(Console *aParent)
+{
+ ComAssertRet(aParent, E_INVALIDARG);
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ mfSourceBitmapEnabled = true;
+ fVGAResizing = false;
+
+ ComPtr<IGraphicsAdapter> pGraphicsAdapter;
+ HRESULT hrc = mParent->i_machine()->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
+ AssertComRCReturnRC(hrc);
+ AssertReturn(!pGraphicsAdapter.isNull(), E_FAIL);
+
+ ULONG ul;
+ pGraphicsAdapter->COMGETTER(MonitorCount)(&ul);
+ mcMonitors = ul;
+ xInputMappingOrigin = 0;
+ yInputMappingOrigin = 0;
+ cxInputMapping = 0;
+ cyInputMapping = 0;
+
+ for (ul = 0; ul < mcMonitors; ul++)
+ {
+ maFramebuffers[ul].u32Offset = 0;
+ maFramebuffers[ul].u32MaxFramebufferSize = 0;
+ maFramebuffers[ul].u32InformationSize = 0;
+
+ maFramebuffers[ul].pFramebuffer = NULL;
+ /* All secondary monitors are disabled at startup. */
+ maFramebuffers[ul].fDisabled = ul > 0;
+
+ maFramebuffers[ul].u32Caps = 0;
+
+ maFramebuffers[ul].updateImage.pu8Address = NULL;
+ maFramebuffers[ul].updateImage.cbLine = 0;
+
+ maFramebuffers[ul].xOrigin = 0;
+ maFramebuffers[ul].yOrigin = 0;
+
+ maFramebuffers[ul].w = 0;
+ maFramebuffers[ul].h = 0;
+
+ maFramebuffers[ul].flags = maFramebuffers[ul].fDisabled? VBVA_SCREEN_F_DISABLED: 0;
+
+ maFramebuffers[ul].u16BitsPerPixel = 0;
+ maFramebuffers[ul].pu8FramebufferVRAM = NULL;
+ maFramebuffers[ul].u32LineSize = 0;
+
+ maFramebuffers[ul].pHostEvents = NULL;
+
+ maFramebuffers[ul].fDefaultFormat = false;
+
+#ifdef VBOX_WITH_HGSMI
+ maFramebuffers[ul].fVBVAEnabled = false;
+ maFramebuffers[ul].fVBVAForceResize = false;
+ maFramebuffers[ul].pVBVAHostFlags = NULL;
+#endif /* VBOX_WITH_HGSMI */
+ }
+
+ {
+ // register listener for state change events
+ ComPtr<IEventSource> es;
+ mParent->COMGETTER(EventSource)(es.asOutParam());
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnStateChanged);
+ es->RegisterListener(this, ComSafeArrayAsInParam(eventTypes), true);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Display::uninit()
+{
+ LogRelFlowFunc(("this=%p\n", this));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ maFramebuffers[uScreenId].pSourceBitmap.setNull();
+ maFramebuffers[uScreenId].updateImage.pSourceBitmap.setNull();
+ maFramebuffers[uScreenId].updateImage.pu8Address = NULL;
+ maFramebuffers[uScreenId].updateImage.cbLine = 0;
+ maFramebuffers[uScreenId].pFramebuffer.setNull();
+#ifdef VBOX_WITH_RECORDING
+ maFramebuffers[uScreenId].Recording.pSourceBitmap.setNull();
+#endif
+ }
+
+ if (mParent)
+ {
+ ComPtr<IEventSource> es;
+ mParent->COMGETTER(EventSource)(es.asOutParam());
+ es->UnregisterListener(this);
+ }
+
+ unconst(mParent) = NULL;
+
+ if (mpDrv)
+ mpDrv->pDisplay = NULL;
+
+ mpDrv = NULL;
+}
+
+/**
+ * Register the SSM methods. Called by the power up thread to be able to
+ * pass pVM
+ */
+int Display::i_registerSSM(PUVM pUVM)
+{
+ PCVMMR3VTABLE const pVMM = mParent->i_getVMMVTable();
+ AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3);
+
+ /* Version 2 adds width and height of the framebuffer; version 3 adds
+ * the framebuffer offset in the virtual desktop and the framebuffer flags;
+ * version 4 adds guest to host input event mapping and version 5 adds
+ * guest VBVA and host cursor capabilities.
+ */
+ int vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 0, sSSMDisplayVer5,
+ mcMonitors * sizeof(uint32_t) * 8 + sizeof(uint32_t),
+ NULL, NULL, NULL,
+ NULL, i_displaySSMSave, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Register loaders for old saved states where iInstance was
+ * 3 * sizeof(uint32_t *) due to a code mistake.
+ */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 12 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayData", 24 /*uInstance*/, sSSMDisplayVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMLoad, NULL, this);
+ AssertRCReturn(vrc, vrc);
+
+ /* uInstance is an arbitrary value greater than 1024. Such a value will ensure a quick seek in saved state file. */
+ vrc = pVMM->pfnSSMR3RegisterExternal(pUVM, "DisplayScreenshot", 1100 /*uInstance*/, sSSMDisplayScreenshotVer, 0 /*cbGuess*/,
+ NULL, NULL, NULL,
+ NULL, i_displaySSMSaveScreenshot, NULL,
+ NULL, i_displaySSMLoadScreenshot, NULL, this);
+
+ AssertRCReturn(vrc, vrc);
+
+ return VINF_SUCCESS;
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handles display resize event.
+ *
+ * @param uScreenId Screen ID
+ * @param bpp New bits per pixel.
+ * @param pvVRAM VRAM pointer.
+ * @param cbLine New bytes per line.
+ * @param w New display width.
+ * @param h New display height.
+ * @param flags Flags of the new video mode.
+ * @param xOrigin New display origin X.
+ * @param yOrigin New display origin Y.
+ * @param fVGAResize Whether the resize is originated from the VGA device (DevVGA).
+ */
+int Display::i_handleDisplayResize(unsigned uScreenId, uint32_t bpp, void *pvVRAM,
+ uint32_t cbLine, uint32_t w, uint32_t h, uint16_t flags,
+ int32_t xOrigin, int32_t yOrigin, bool fVGAResize)
+{
+ LogRel2(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X\n", uScreenId,
+ pvVRAM, w, h, bpp, cbLine, flags));
+
+ /* Caller must not hold the object lock. */
+ AssertReturn(!isWriteLockOnCurrentThread(), VERR_INVALID_STATE);
+
+ /* Note: the old code checked if the video mode was actually changed and
+ * did not invalidate the source bitmap if the mode did not change.
+ * The new code always invalidates the source bitmap, i.e. it will
+ * notify the frontend even if nothing actually changed.
+ *
+ * Implementing the filtering is possible but might lead to pfnSetRenderVRAM races
+ * between this method and QuerySourceBitmap. Such races can be avoided by implementing
+ * the @todo below.
+ */
+
+ /* Make sure that the VGA device does not access the source bitmap. */
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN && mpDrv)
+ {
+ /// @todo It is probably more convenient to implement
+ // mpDrv->pUpPort->pfnSetOutputBitmap(pvVRAM, cbScanline, cBits, cx, cy, bool fSet);
+ // and remove IConnector.pbData, cbScanline, cBits, cx, cy.
+ // fSet = false disables rendering and VGA can check
+ // if it is already rendering to a different bitmap, avoiding
+ // enable/disable rendering races.
+ mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, false);
+
+ mpDrv->IConnector.pbData = NULL;
+ mpDrv->IConnector.cbScanline = 0;
+ mpDrv->IConnector.cBits = 32; /* DevVGA does not work with cBits == 0. */
+ mpDrv->IConnector.cx = 0;
+ mpDrv->IConnector.cy = 0;
+ }
+
+ /* Update maFramebuffers[uScreenId] under lock. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (uScreenId >= mcMonitors)
+ {
+ LogRel(("Display::i_handleDisplayResize: mcMonitors=%u < uScreenId=%u (pvVRAM=%p w=%u h=%u bpp=%d cbLine=0x%X flags=0x%X)\n",
+ mcMonitors, uScreenId, pvVRAM, w, h, bpp, cbLine, flags));
+ return VINF_SUCCESS;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ /* Whether the monitor position has changed.
+ * A resize initiated by the VGA device does not change the monitor position.
+ */
+ const bool fNewOrigin = !fVGAResize
+ && ( pFBInfo->xOrigin != xOrigin
+ || pFBInfo->yOrigin != yOrigin);
+
+ /* The event for disabled->enabled transition.
+ * VGA resizes also come when the guest uses VBVA mode. They do not affect pFBInfo->fDisabled.
+ * The primary screen is re-enabled when the guest leaves the VBVA mode in i_displayVBVADisable.
+ */
+ const bool fGuestMonitorChangedEvent = !fVGAResize
+ && (pFBInfo->fDisabled != RT_BOOL(flags & VBVA_SCREEN_F_DISABLED));
+
+ /* Reset the update mode. */
+ pFBInfo->updateImage.pSourceBitmap.setNull();
+ pFBInfo->updateImage.pu8Address = NULL;
+ pFBInfo->updateImage.cbLine = 0;
+
+ /* Release the current source bitmap. */
+ pFBInfo->pSourceBitmap.setNull();
+
+ /* VGA blanking is signaled as w=0, h=0, bpp=0 and cbLine=0, and it's
+ * best to keep the old resolution, as otherwise the window size would
+ * change before the new resolution is known. */
+ const bool fVGABlank = fVGAResize && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && w == 0 && h == 0 && bpp == 0 && cbLine == 0;
+ if (fVGABlank)
+ {
+ w = pFBInfo->w;
+ h = pFBInfo->h;
+ }
+
+ /* Log changes. */
+ if ( pFBInfo->w != w
+ || pFBInfo->h != h
+ || pFBInfo->u32LineSize != cbLine
+ /*|| pFBInfo->pu8FramebufferVRAM != (uint8_t *)pvVRAM - too noisy */
+ || ( !fVGAResize
+ && ( pFBInfo->xOrigin != xOrigin
+ || pFBInfo->yOrigin != yOrigin
+ || pFBInfo->flags != flags)))
+ LogRel(("Display::i_handleDisplayResize: uScreenId=%d pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X flags=0x%X origin=%d,%d\n",
+ uScreenId, pvVRAM, w, h, bpp, cbLine, flags, xOrigin, yOrigin));
+
+ /* Update the video mode information. */
+ pFBInfo->w = w;
+ pFBInfo->h = h;
+ pFBInfo->u16BitsPerPixel = (uint16_t)bpp;
+ pFBInfo->pu8FramebufferVRAM = (uint8_t *)pvVRAM;
+ pFBInfo->u32LineSize = cbLine;
+ if (!fVGAResize)
+ {
+ /* Fields which are not used in not VBVA modes and not affected by a VGA resize. */
+ pFBInfo->flags = flags;
+ pFBInfo->xOrigin = xOrigin;
+ pFBInfo->yOrigin = yOrigin;
+ pFBInfo->fDisabled = RT_BOOL(flags & VBVA_SCREEN_F_DISABLED);
+ pFBInfo->fVBVAForceResize = false;
+ }
+ else
+ {
+ pFBInfo->flags = VBVA_SCREEN_F_ACTIVE;
+ if (fVGABlank)
+ pFBInfo->flags |= VBVA_SCREEN_F_BLANK;
+ pFBInfo->fDisabled = false;
+ }
+
+ /* Prepare local vars for the notification code below. */
+ ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer;
+ const bool fDisabled = pFBInfo->fDisabled;
+
+ alock.release();
+
+ if (!pFramebuffer.isNull())
+ {
+ HRESULT hr = pFramebuffer->NotifyChange(uScreenId, 0, 0, w, h); /** @todo origin */
+ LogFunc(("NotifyChange hr %08X\n", hr));
+ NOREF(hr);
+ }
+
+ if (fGuestMonitorChangedEvent)
+ {
+ if (fDisabled)
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_Disabled, uScreenId, 0, 0, 0, 0);
+ else
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_Enabled, uScreenId, xOrigin, yOrigin, w, h);
+ }
+
+ if (fNewOrigin)
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(),
+ GuestMonitorChangedEventType_NewOrigin, uScreenId, xOrigin, yOrigin, 0, 0);
+
+ /* Inform the VRDP server about the change of display parameters. */
+ LogRelFlowFunc(("Calling VRDP\n"));
+ mParent->i_consoleVRDPServer()->SendResize();
+
+ /* And re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+
+#ifdef VBOX_WITH_RECORDING
+ i_recordingScreenChanged(uScreenId);
+#endif
+
+ LogRelFlowFunc(("[%d]: default format %d\n", uScreenId, pFBInfo->fDefaultFormat));
+
+ return VINF_SUCCESS;
+}
+
+static void i_checkCoordBounds(int *px, int *py, int *pw, int *ph, int cx, int cy)
+{
+ /* Correct negative x and y coordinates. */
+ if (*px < 0)
+ {
+ *px += *pw; /* Compute xRight which is also the new width. */
+
+ *pw = (*px < 0)? 0: *px;
+
+ *px = 0;
+ }
+
+ if (*py < 0)
+ {
+ *py += *ph; /* Compute xBottom, which is also the new height. */
+
+ *ph = (*py < 0)? 0: *py;
+
+ *py = 0;
+ }
+
+ /* Also check if coords are greater than the display resolution. */
+ if (*px + *pw > cx)
+ {
+ *pw = cx > *px? cx - *px: 0;
+ }
+
+ if (*py + *ph > cy)
+ {
+ *ph = cy > *py? cy - *py: 0;
+ }
+}
+
+void Display::i_handleDisplayUpdate(unsigned uScreenId, int x, int y, int w, int h)
+{
+ /*
+ * Always runs under either VBVA lock or, for HGSMI, DevVGA lock.
+ * Safe to use VBVA vars and take the framebuffer lock.
+ */
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("[%d] %d,%d %dx%d\n",
+ uScreenId, x, y, w, h));
+#endif /* DEBUG_sunlover */
+
+ /* No updates for a disabled guest screen. */
+ if (maFramebuffers[uScreenId].fDisabled)
+ return;
+
+ /* No updates for a blank guest screen. */
+ /** @note Disabled for now, as the GUI does not update the picture when we
+ * first blank. */
+ /* if (maFramebuffers[uScreenId].flags & VBVA_SCREEN_F_BLANK)
+ return; */
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ AutoReadLock alockr(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IFramebuffer> pFramebuffer = pFBInfo->pFramebuffer;
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap = pFBInfo->updateImage.pSourceBitmap;
+
+ alockr.release();
+
+ if (RT_LIKELY(!pFramebuffer.isNull()))
+ {
+ if (RT_LIKELY(!RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_UpdateImage)))
+ {
+ i_checkCoordBounds(&x, &y, &w, &h, pFBInfo->w, pFBInfo->h);
+
+ if (w != 0 && h != 0)
+ {
+ pFramebuffer->NotifyUpdate(x, y, w, h);
+ }
+ }
+ else
+ {
+ if (RT_LIKELY(!pSourceBitmap.isNull()))
+ { /* likely */ }
+ else
+ {
+ /* Create a source bitmap if UpdateImage mode is used. */
+ HRESULT hr = QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam());
+ if (SUCCEEDED(hr))
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ hr = pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hr))
+ {
+ AutoWriteLock alockw(this COMMA_LOCKVAL_SRC_POS);
+
+ if (pFBInfo->updateImage.pSourceBitmap.isNull())
+ {
+ pFBInfo->updateImage.pSourceBitmap = pSourceBitmap;
+ pFBInfo->updateImage.pu8Address = pAddress;
+ pFBInfo->updateImage.cbLine = ulBytesPerLine;
+ }
+
+ pSourceBitmap = pFBInfo->updateImage.pSourceBitmap;
+
+ alockw.release();
+ }
+ }
+ }
+
+ if (RT_LIKELY(!pSourceBitmap.isNull()))
+ {
+ BYTE *pbAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hr = pSourceBitmap->QueryBitmapInfo(&pbAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hr))
+ {
+ /* Make sure that the requested update is within the source bitmap dimensions. */
+ i_checkCoordBounds(&x, &y, &w, &h, ulWidth, ulHeight);
+
+ if (w != 0 && h != 0)
+ {
+ const size_t cbData = w * h * 4;
+ com::SafeArray<BYTE> image(cbData);
+
+ uint8_t *pu8Dst = image.raw();
+ const uint8_t *pu8Src = pbAddress + ulBytesPerLine * y + x * 4;
+
+ int i;
+ for (i = y; i < y + h; ++i)
+ {
+ memcpy(pu8Dst, pu8Src, w * 4);
+ pu8Dst += w * 4;
+ pu8Src += ulBytesPerLine;
+ }
+
+ pFramebuffer->NotifyUpdateImage(x, y, w, h, ComSafeArrayAsInParam(image));
+ }
+ }
+ }
+ }
+ }
+
+#ifndef VBOX_WITH_HGSMI
+ if (!mVideoAccelLegacy.fVideoAccelEnabled)
+#else
+ if (!mVideoAccelLegacy.fVideoAccelEnabled && !maFramebuffers[uScreenId].fVBVAEnabled)
+#endif
+ {
+ /* When VBVA is enabled, the VRDP server is informed
+ * either in VideoAccelFlush or displayVBVAUpdateProcess.
+ * Inform the server here only if VBVA is disabled.
+ */
+ mParent->i_consoleVRDPServer()->SendUpdateBitmap(uScreenId, x, y, w, h);
+ }
+}
+
+void Display::i_updateGuestGraphicsFacility(void)
+{
+ Guest* pGuest = mParent->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+ /* The following is from GuestImpl.cpp. */
+ /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
+ * to move the graphics and seamless capability -> facility translation to
+ * VMMDev so this could be saved. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ if ( mfVMMDevSupportsGraphics
+ || (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS) != 0)
+ pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics,
+ VBoxGuestFacilityStatus_Active,
+ 0 /*fFlags*/, &TimeSpecTS);
+ else
+ pGuest->i_setAdditionsStatus(VBoxGuestFacilityType_Graphics,
+ VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS);
+}
+
+void Display::i_handleUpdateVMMDevSupportsGraphics(bool fSupportsGraphics)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (mfVMMDevSupportsGraphics == fSupportsGraphics)
+ return;
+ mfVMMDevSupportsGraphics = fSupportsGraphics;
+ i_updateGuestGraphicsFacility();
+ /* The VMMDev interface notifies the console. */
+}
+
+void Display::i_handleUpdateGuestVBVACapabilities(uint32_t fNewCapabilities)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ bool fNotify = (fNewCapabilities & VBVACAPS_VIDEO_MODE_HINTS) != (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS);
+
+ mfGuestVBVACapabilities = fNewCapabilities;
+ if (!fNotify)
+ return;
+ i_updateGuestGraphicsFacility();
+ /* Tell the console about it */
+ mParent->i_onAdditionsStateChange();
+}
+
+void Display::i_handleUpdateVBVAInputMapping(int32_t xOrigin, int32_t yOrigin, uint32_t cx, uint32_t cy)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ xInputMappingOrigin = xOrigin;
+ yInputMappingOrigin = yOrigin;
+ cxInputMapping = cx;
+ cyInputMapping = cy;
+
+ /* Re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+}
+
+/**
+ * Returns the upper left and lower right corners of the virtual framebuffer.
+ * The lower right is "exclusive" (i.e. first pixel beyond the framebuffer),
+ * and the origin is (0, 0), not (1, 1) like the GUI returns.
+ */
+void Display::i_getFramebufferDimensions(int32_t *px1, int32_t *py1,
+ int32_t *px2, int32_t *py2)
+{
+ int32_t x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertPtrReturnVoid(px1);
+ AssertPtrReturnVoid(py1);
+ AssertPtrReturnVoid(px2);
+ AssertPtrReturnVoid(py2);
+ LogRelFlowFunc(("\n"));
+
+ if (!mpDrv)
+ return;
+
+ if (maFramebuffers[0].fVBVAEnabled && cxInputMapping && cyInputMapping)
+ {
+ /* Guest uses VBVA with explicit mouse mapping dimensions. */
+ x1 = xInputMappingOrigin;
+ y1 = yInputMappingOrigin;
+ x2 = xInputMappingOrigin + cxInputMapping;
+ y2 = yInputMappingOrigin + cyInputMapping;
+ }
+ else
+ {
+ /* If VBVA is not in use then this flag will not be set and this
+ * will still work as it should. */
+ if (!maFramebuffers[0].fDisabled)
+ {
+ x1 = (int32_t)maFramebuffers[0].xOrigin;
+ y1 = (int32_t)maFramebuffers[0].yOrigin;
+ x2 = (int32_t)maFramebuffers[0].w + (int32_t)maFramebuffers[0].xOrigin;
+ y2 = (int32_t)maFramebuffers[0].h + (int32_t)maFramebuffers[0].yOrigin;
+ }
+
+ for (unsigned i = 1; i < mcMonitors; ++i)
+ {
+ if (!maFramebuffers[i].fDisabled)
+ {
+ x1 = RT_MIN(x1, maFramebuffers[i].xOrigin);
+ y1 = RT_MIN(y1, maFramebuffers[i].yOrigin);
+ x2 = RT_MAX(x2, maFramebuffers[i].xOrigin + (int32_t)maFramebuffers[i].w);
+ y2 = RT_MAX(y2, maFramebuffers[i].yOrigin + (int32_t)maFramebuffers[i].h);
+ }
+ }
+ }
+
+ *px1 = x1;
+ *py1 = y1;
+ *px2 = x2;
+ *py2 = y2;
+}
+
+/** Updates the device's view of the host cursor handling capabilities.
+ * Calls into mpDrv->pUpPort. */
+void Display::i_UpdateDeviceCursorCapabilities(void)
+{
+ bool fRenderCursor = true;
+ bool fMoveCursor = mcVRDPRefs == 0;
+#ifdef VBOX_WITH_RECORDING
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+
+ if ( pCtx
+ && pCtx->IsStarted()
+ && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+ fRenderCursor = fMoveCursor = false;
+ else
+#endif /* VBOX_WITH_RECORDING */
+ {
+ for (unsigned uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ if (!(pFBInfo->u32Caps & FramebufferCapabilities_RenderCursor))
+ fRenderCursor = false;
+ if (!(pFBInfo->u32Caps & FramebufferCapabilities_MoveCursor))
+ fMoveCursor = false;
+ }
+ }
+
+ if (mpDrv)
+ mpDrv->pUpPort->pfnReportHostCursorCapabilities(mpDrv->pUpPort, fRenderCursor, fMoveCursor);
+}
+
+HRESULT Display::i_reportHostCursorCapabilities(uint32_t fCapabilitiesAdded, uint32_t fCapabilitiesRemoved)
+{
+ /* Do we need this to access mParent? I presume that the safe VM pointer
+ * ensures that mpDrv will remain valid. */
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ uint32_t fHostCursorCapabilities = (mfHostCursorCapabilities | fCapabilitiesAdded)
+ & ~fCapabilitiesRemoved;
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+ if (mfHostCursorCapabilities == fHostCursorCapabilities)
+ return S_OK;
+ CHECK_CONSOLE_DRV(mpDrv);
+ alock.release(); /* Release before calling up for lock order reasons. */
+ mfHostCursorCapabilities = fHostCursorCapabilities;
+ i_UpdateDeviceCursorCapabilities();
+ return S_OK;
+}
+
+HRESULT Display::i_reportHostCursorPosition(int32_t x, int32_t y, bool fOutOfRange)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ uint32_t xAdj = (uint32_t)RT_MAX(x - xInputMappingOrigin, 0);
+ uint32_t yAdj = (uint32_t)RT_MAX(y - yInputMappingOrigin, 0);
+ xAdj = RT_MIN(xAdj, cxInputMapping);
+ yAdj = RT_MIN(yAdj, cyInputMapping);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+ CHECK_CONSOLE_DRV(mpDrv);
+ alock.release(); /* Release before calling up for lock order reasons. */
+ if (fOutOfRange)
+ mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, 0, 0, true);
+ else
+ mpDrv->pUpPort->pfnReportHostCursorPosition(mpDrv->pUpPort, xAdj, yAdj, false);
+ return S_OK;
+}
+
+static bool displayIntersectRect(RTRECT *prectResult,
+ const RTRECT *prect1,
+ const RTRECT *prect2)
+{
+ /* Initialize result to an empty record. */
+ memset(prectResult, 0, sizeof(RTRECT));
+
+ int xLeftResult = RT_MAX(prect1->xLeft, prect2->xLeft);
+ int xRightResult = RT_MIN(prect1->xRight, prect2->xRight);
+
+ if (xLeftResult < xRightResult)
+ {
+ /* There is intersection by X. */
+
+ int yTopResult = RT_MAX(prect1->yTop, prect2->yTop);
+ int yBottomResult = RT_MIN(prect1->yBottom, prect2->yBottom);
+
+ if (yTopResult < yBottomResult)
+ {
+ /* There is intersection by Y. */
+
+ prectResult->xLeft = xLeftResult;
+ prectResult->yTop = yTopResult;
+ prectResult->xRight = xRightResult;
+ prectResult->yBottom = yBottomResult;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int Display::i_saveVisibleRegion(uint32_t cRect, PRTRECT pRect)
+{
+ RTRECT *pRectVisibleRegion = NULL;
+
+ if (pRect == mpRectVisibleRegion)
+ return VINF_SUCCESS;
+ if (cRect != 0)
+ {
+ pRectVisibleRegion = (RTRECT *)RTMemAlloc(cRect * sizeof(RTRECT));
+ if (!pRectVisibleRegion)
+ {
+ return VERR_NO_MEMORY;
+ }
+ memcpy(pRectVisibleRegion, pRect, cRect * sizeof(RTRECT));
+ }
+ if (mpRectVisibleRegion)
+ RTMemFree(mpRectVisibleRegion);
+ mcRectVisibleRegion = cRect;
+ mpRectVisibleRegion = pRectVisibleRegion;
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleSetVisibleRegion(uint32_t cRect, PRTRECT pRect)
+{
+ RTRECT *pVisibleRegion = (RTRECT *)RTMemTmpAlloc( RT_MAX(cRect, 1)
+ * sizeof(RTRECT));
+ LogRel2(("%s: cRect=%u\n", __PRETTY_FUNCTION__, cRect));
+ if (!pVisibleRegion)
+ {
+ return VERR_NO_TMP_MEMORY;
+ }
+ int vrc = i_saveVisibleRegion(cRect, pRect);
+ if (RT_FAILURE(vrc))
+ {
+ RTMemTmpFree(pVisibleRegion);
+ return vrc;
+ }
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ if ( !pFBInfo->pFramebuffer.isNull()
+ && RT_BOOL(pFBInfo->u32Caps & FramebufferCapabilities_VisibleRegion))
+ {
+ /* Prepare a new array of rectangles which intersect with the framebuffer.
+ */
+ RTRECT rectFramebuffer;
+ rectFramebuffer.xLeft = pFBInfo->xOrigin - xInputMappingOrigin;
+ rectFramebuffer.yTop = pFBInfo->yOrigin - yInputMappingOrigin;
+ rectFramebuffer.xRight = rectFramebuffer.xLeft + pFBInfo->w;
+ rectFramebuffer.yBottom = rectFramebuffer.yTop + pFBInfo->h;
+
+ uint32_t cRectVisibleRegion = 0;
+
+ uint32_t i;
+ for (i = 0; i < cRect; i++)
+ {
+ if (displayIntersectRect(&pVisibleRegion[cRectVisibleRegion], &pRect[i], &rectFramebuffer))
+ {
+ pVisibleRegion[cRectVisibleRegion].xLeft -= rectFramebuffer.xLeft;
+ pVisibleRegion[cRectVisibleRegion].yTop -= rectFramebuffer.yTop;
+ pVisibleRegion[cRectVisibleRegion].xRight -= rectFramebuffer.xLeft;
+ pVisibleRegion[cRectVisibleRegion].yBottom -= rectFramebuffer.yTop;
+
+ cRectVisibleRegion++;
+ }
+ }
+ pFBInfo->pFramebuffer->SetVisibleRegion((BYTE *)pVisibleRegion, cRectVisibleRegion);
+ }
+ }
+
+ RTMemTmpFree(pVisibleRegion);
+
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleUpdateMonitorPositions(uint32_t cPositions, PCRTPOINT paPositions)
+{
+ AssertMsgReturn(paPositions, ("Empty monitor position array\n"), E_INVALIDARG);
+ for (unsigned i = 0; i < cPositions; ++i)
+ LogRel2(("Display::i_handleUpdateMonitorPositions: uScreenId=%d xOrigin=%d yOrigin=%dX\n",
+ i, paPositions[i].x, paPositions[i].y));
+
+ if (mpDrv && mpDrv->pUpPort->pfnReportMonitorPositions)
+ mpDrv->pUpPort->pfnReportMonitorPositions(mpDrv->pUpPort, cPositions, paPositions);
+ return VINF_SUCCESS;
+}
+
+int Display::i_handleQueryVisibleRegion(uint32_t *pcRects, PRTRECT paRects)
+{
+ /// @todo Currently not used by the guest and is not implemented in
+ /// framebuffers. Remove?
+ RT_NOREF(pcRects, paRects);
+ return VERR_NOT_SUPPORTED;
+}
+
+#ifdef VBOX_WITH_HGSMI
+static void vbvaSetMemoryFlagsHGSMI(unsigned uScreenId,
+ uint32_t fu32SupportedOrders,
+ bool fVideoAccelVRDP,
+ DISPLAYFBINFO *pFBInfo)
+{
+ LogRelFlowFunc(("HGSMI[%d]: %p\n", uScreenId, pFBInfo->pVBVAHostFlags));
+
+ if (pFBInfo->pVBVAHostFlags)
+ {
+ uint32_t fu32HostEvents = VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET;
+
+ if (pFBInfo->fVBVAEnabled)
+ {
+ fu32HostEvents |= VBVA_F_MODE_ENABLED;
+
+ if (fVideoAccelVRDP)
+ {
+ fu32HostEvents |= VBVA_F_MODE_VRDP;
+ }
+ }
+
+ ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32HostEvents, fu32HostEvents);
+ ASMAtomicWriteU32(&pFBInfo->pVBVAHostFlags->u32SupportedOrders, fu32SupportedOrders);
+
+ LogRelFlowFunc((" fu32HostEvents = 0x%08X, fu32SupportedOrders = 0x%08X\n", fu32HostEvents, fu32SupportedOrders));
+ }
+}
+
+static void vbvaSetMemoryFlagsAllHGSMI(uint32_t fu32SupportedOrders,
+ bool fVideoAccelVRDP,
+ DISPLAYFBINFO *paFBInfos,
+ unsigned cFBInfos)
+{
+ unsigned uScreenId;
+
+ for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++)
+ {
+ vbvaSetMemoryFlagsHGSMI(uScreenId, fu32SupportedOrders, fVideoAccelVRDP, &paFBInfos[uScreenId]);
+ }
+}
+#endif /* VBOX_WITH_HGSMI */
+
+int Display::VideoAccelEnableVMMDev(bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory));
+ int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort);
+ videoAccelLeaveVMMDev(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave %Rrc\n", vrc));
+ return vrc;
+}
+
+int Display::VideoAccelEnableVGA(bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ LogFlowFunc(("%d %p\n", fEnable, pVbvaMemory));
+ int vrc = videoAccelEnterVGA(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_VideoAccelEnable(fEnable, pVbvaMemory, mpDrv->pUpPort);
+ videoAccelLeaveVGA(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave %Rrc\n", vrc));
+ return vrc;
+}
+
+void Display::VideoAccelFlushVMMDev(void)
+{
+ LogFlowFunc(("enter\n"));
+ int vrc = videoAccelEnterVMMDev(&mVideoAccelLegacy);
+ if (RT_SUCCESS(vrc))
+ {
+ i_VideoAccelFlush(mpDrv->pUpPort);
+ videoAccelLeaveVMMDev(&mVideoAccelLegacy);
+ }
+ LogFlowFunc(("leave\n"));
+}
+
+/* Called always by one VRDP server thread. Can be thread-unsafe.
+ */
+void Display::i_VRDPConnectionEvent(bool fConnect)
+{
+ LogRelFlowFunc(("fConnect = %d\n", fConnect));
+
+ int c = fConnect?
+ ASMAtomicIncS32(&mcVRDPRefs):
+ ASMAtomicDecS32(&mcVRDPRefs);
+
+ i_VideoAccelVRDP(fConnect, c);
+ i_UpdateDeviceCursorCapabilities();
+}
+
+
+void Display::i_VideoAccelVRDP(bool fEnable, int c)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ Assert (c >= 0);
+ RT_NOREF(fEnable);
+
+ /* This can run concurrently with Display videoaccel state change. */
+ RTCritSectEnter(&mVideoAccelLock);
+
+ if (c == 0)
+ {
+ /* The last client has disconnected, and the accel can be
+ * disabled.
+ */
+ Assert(fEnable == false);
+
+ mfVideoAccelVRDP = false;
+ mfu32SupportedOrders = 0;
+
+ i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders,
+ maFramebuffers, mcMonitors);
+#ifdef VBOX_WITH_HGSMI
+ /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */
+ ASMAtomicIncU32(&mu32UpdateVBVAFlags);
+#endif /* VBOX_WITH_HGSMI */
+
+ LogRel(("VBVA: VRDP acceleration has been disabled.\n"));
+ }
+ else if ( c == 1
+ && !mfVideoAccelVRDP)
+ {
+ /* The first client has connected. Enable the accel.
+ */
+ Assert(fEnable == true);
+
+ mfVideoAccelVRDP = true;
+ /* Supporting all orders. */
+ mfu32SupportedOrders = UINT32_MAX;
+
+ i_vbvaSetMemoryFlags(pVideoAccel->pVbvaMemory, pVideoAccel->fVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders,
+ maFramebuffers, mcMonitors);
+#ifdef VBOX_WITH_HGSMI
+ /* Here is VRDP-IN thread. Process the request in vbvaUpdateBegin under DevVGA lock on an EMT. */
+ ASMAtomicIncU32(&mu32UpdateVBVAFlags);
+#endif /* VBOX_WITH_HGSMI */
+
+ LogRel(("VBVA: VRDP acceleration has been requested.\n"));
+ }
+ else
+ {
+ /* A client is connected or disconnected but there is no change in the
+ * accel state. It remains enabled.
+ */
+ Assert(mfVideoAccelVRDP == true);
+ }
+
+ RTCritSectLeave(&mVideoAccelLock);
+}
+
+void Display::i_notifyPowerDown(void)
+{
+ LogRelFlowFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Source bitmaps are not available anymore. */
+ mfSourceBitmapEnabled = false;
+
+ alock.release();
+
+ /* Resize all displays to tell framebuffers to forget current source bitmap. */
+ unsigned uScreenId = mcMonitors;
+ while (uScreenId > 0)
+ {
+ --uScreenId;
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+ if (!pFBInfo->fDisabled)
+ {
+ i_handleDisplayResize(uScreenId, 32,
+ pFBInfo->pu8FramebufferVRAM,
+ pFBInfo->u32LineSize,
+ pFBInfo->w,
+ pFBInfo->h,
+ pFBInfo->flags,
+ pFBInfo->xOrigin,
+ pFBInfo->yOrigin,
+ false);
+ }
+ }
+}
+
+// Wrapped IDisplay methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Display::getScreenResolution(ULONG aScreenId, ULONG *aWidth, ULONG *aHeight, ULONG *aBitsPerPixel,
+ LONG *aXOrigin, LONG *aYOrigin, GuestMonitorStatus_T *aGuestMonitorStatus)
+{
+ LogRelFlowFunc(("aScreenId=%RU32\n", aScreenId));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return E_INVALIDARG;
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled;
+
+ if (pFBInfo->flags & VBVA_SCREEN_F_DISABLED)
+ guestMonitorStatus = GuestMonitorStatus_Disabled;
+ else if (pFBInfo->flags & (VBVA_SCREEN_F_BLANK | VBVA_SCREEN_F_BLANK2))
+ guestMonitorStatus = GuestMonitorStatus_Blank;
+
+ if (aWidth)
+ *aWidth = pFBInfo->w;
+ if (aHeight)
+ *aHeight = pFBInfo->h;
+ if (aBitsPerPixel)
+ *aBitsPerPixel = pFBInfo->u16BitsPerPixel;
+ if (aXOrigin)
+ *aXOrigin = pFBInfo->xOrigin;
+ if (aYOrigin)
+ *aYOrigin = pFBInfo->yOrigin;
+ if (aGuestMonitorStatus)
+ *aGuestMonitorStatus = guestMonitorStatus;
+
+ return S_OK;
+}
+
+
+HRESULT Display::attachFramebuffer(ULONG aScreenId, const ComPtr<IFramebuffer> &aFramebuffer, com::Guid &aId)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("AttachFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+ if (!pFBInfo->pFramebuffer.isNull())
+ return setError(E_FAIL, tr("AttachFramebuffer: Framebuffer already attached to %d"),
+ aScreenId);
+
+ pFBInfo->pFramebuffer = aFramebuffer;
+ pFBInfo->framebufferId.create();
+ aId = pFBInfo->framebufferId;
+
+ SafeArray<FramebufferCapabilities_T> caps;
+ pFBInfo->pFramebuffer->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(caps));
+ pFBInfo->u32Caps = 0;
+ size_t i;
+ for (i = 0; i < caps.size(); ++i)
+ pFBInfo->u32Caps |= caps[i];
+
+ alock.release();
+
+ /* The driver might not have been constructed yet */
+ if (mpDrv)
+ {
+ /* Inform the framebuffer about the actual screen size. */
+ HRESULT hr = aFramebuffer->NotifyChange(aScreenId, 0, 0, pFBInfo->w, pFBInfo->h); /** @todo origin */
+ LogFunc(("NotifyChange hr %08X\n", hr)); NOREF(hr);
+
+ /* Re-send the seamless rectangles if necessary. */
+ if (mfSeamlessEnabled)
+ i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
+ }
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+
+ LogRelFlowFunc(("Attached to %d %RTuuid\n", aScreenId, aId.raw()));
+ return S_OK;
+}
+
+HRESULT Display::detachFramebuffer(ULONG aScreenId, const com::Guid &aId)
+{
+ LogRelFlowFunc(("aScreenId = %d %RTuuid\n", aScreenId, aId.raw()));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("DetachFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ if (pFBInfo->framebufferId != aId)
+ {
+ LogRelFlowFunc(("Invalid framebuffer aScreenId = %d, attached %p\n", aScreenId, pFBInfo->framebufferId.raw()));
+ return setError(E_FAIL, tr("DetachFramebuffer: Invalid framebuffer object"));
+ }
+
+ pFBInfo->pFramebuffer.setNull();
+ pFBInfo->framebufferId.clear();
+
+ alock.release();
+ return S_OK;
+}
+
+HRESULT Display::queryFramebuffer(ULONG aScreenId, ComPtr<IFramebuffer> &aFramebuffer)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("QueryFramebuffer: Invalid screen %d (total %d)"),
+ aScreenId, mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ pFBInfo->pFramebuffer.queryInterfaceTo(aFramebuffer.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Display::setVideoModeHint(ULONG aDisplay, BOOL aEnabled,
+ BOOL aChangeOrigin, LONG aOriginX, LONG aOriginY,
+ ULONG aWidth, ULONG aHeight, ULONG aBitsPerPixel,
+ BOOL aNotify)
+{
+ if (aWidth == 0 || aHeight == 0 || aBitsPerPixel == 0)
+ {
+ /* Some of parameters must not change. Query current mode. */
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ HRESULT hr = getScreenResolution(aDisplay, &ulWidth, &ulHeight, &ulBitsPerPixel, NULL, NULL, NULL);
+ if (FAILED(hr))
+ return hr;
+
+ /* Assign current values to not changing parameters. */
+ if (aWidth == 0)
+ aWidth = ulWidth;
+ if (aHeight == 0)
+ aHeight = ulHeight;
+ if (aBitsPerPixel == 0)
+ aBitsPerPixel = ulBitsPerPixel;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aDisplay >= mcMonitors)
+ return E_INVALIDARG;
+
+ VMMDevDisplayDef d;
+ d.idDisplay = aDisplay;
+ d.xOrigin = aOriginX;
+ d.yOrigin = aOriginY;
+ d.cx = aWidth;
+ d.cy = aHeight;
+ d.cBitsPerPixel = aBitsPerPixel;
+ d.fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP;
+ if (!aEnabled)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_DISABLED;
+ if (aChangeOrigin)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN;
+ if (aDisplay == 0)
+ d.fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY;
+
+ /* Remember the monitor information. */
+ maFramebuffers[aDisplay].monitorDesc = d;
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ /*
+ * It is up to the guest to decide whether the hint is
+ * valid. Therefore don't do any VRAM sanity checks here.
+ */
+
+ /* Have to release the lock because the pfnRequestDisplayChange
+ * will call EMT. */
+ alock.release();
+
+ /* We always send the hint to the graphics card in case the guest enables
+ * support later. For now we notify exactly when support is enabled. */
+ mpDrv->pUpPort->pfnSendModeHint(mpDrv->pUpPort, aWidth, aHeight,
+ aBitsPerPixel, aDisplay,
+ aChangeOrigin ? aOriginX : ~0,
+ aChangeOrigin ? aOriginY : ~0,
+ RT_BOOL(aEnabled),
+ (mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS)
+ && aNotify);
+ if ( mfGuestVBVACapabilities & VBVACAPS_VIDEO_MODE_HINTS
+ && !(mfGuestVBVACapabilities & VBVACAPS_IRQ)
+ && aNotify)
+ mParent->i_sendACPIMonitorHotPlugEvent();
+
+ /* We currently never suppress the VMMDev hint if the guest has requested
+ * it. Specifically the video graphics driver may not be responsible for
+ * screen positioning in the guest virtual desktop, and the component
+ * responsible may want to get the hint from VMMDev. */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, 1, &d, false, RT_BOOL(aNotify));
+ }
+ /* Notify listeners. */
+ ::FireGuestMonitorInfoChangedEvent(mParent->i_getEventSource(), aDisplay);
+ return S_OK;
+}
+
+HRESULT Display::getVideoModeHint(ULONG cDisplay, BOOL *pfEnabled,
+ BOOL *pfChangeOrigin, LONG *pxOrigin, LONG *pyOrigin,
+ ULONG *pcx, ULONG *pcy, ULONG *pcBitsPerPixel)
+{
+ if (cDisplay >= mcMonitors)
+ return E_INVALIDARG;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (pfEnabled)
+ *pfEnabled = !( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags
+ & VMMDEV_DISPLAY_DISABLED);
+ if (pfChangeOrigin)
+ *pfChangeOrigin = RT_BOOL( maFramebuffers[cDisplay].monitorDesc.fDisplayFlags
+ & VMMDEV_DISPLAY_ORIGIN);
+ if (pxOrigin)
+ *pxOrigin = maFramebuffers[cDisplay].monitorDesc.xOrigin;
+ if (pyOrigin)
+ *pyOrigin = maFramebuffers[cDisplay].monitorDesc.yOrigin;
+ if (pcx)
+ *pcx = maFramebuffers[cDisplay].monitorDesc.cx;
+ if (pcy)
+ *pcy = maFramebuffers[cDisplay].monitorDesc.cy;
+ if (pcBitsPerPixel)
+ *pcBitsPerPixel = maFramebuffers[cDisplay].monitorDesc.cBitsPerPixel;
+ return S_OK;
+}
+
+HRESULT Display::setSeamlessMode(BOOL enabled)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Have to release the lock because the pfnRequestSeamlessChange will call EMT. */
+ alock.release();
+
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnRequestSeamlessChange(pVMMDevPort, !!enabled);
+ }
+ mfSeamlessEnabled = RT_BOOL(enabled);
+ return S_OK;
+}
+
+/*static*/ DECLCALLBACK(int)
+Display::i_displayTakeScreenshotEMT(Display *pDisplay, ULONG aScreenId, uint8_t **ppbData, size_t *pcbData,
+ uint32_t *pcx, uint32_t *pcy, bool *pfMemFree)
+{
+ int vrc;
+ if ( aScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && pDisplay->maFramebuffers[aScreenId].fVBVAEnabled == false) /* A non-VBVA mode. */
+ {
+ if (pDisplay->mpDrv)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy);
+ *pfMemFree = false;
+ }
+ else
+ {
+ /* No image. */
+ *ppbData = NULL;
+ *pcbData = 0;
+ *pcx = 0;
+ *pcy = 0;
+ *pfMemFree = true;
+ vrc = VINF_SUCCESS;
+ }
+ }
+ else if (aScreenId < pDisplay->mcMonitors)
+ {
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId];
+
+ uint32_t width = pFBInfo->w;
+ uint32_t height = pFBInfo->h;
+
+ /* Allocate 32 bit per pixel bitmap. */
+ size_t cbRequired = width * 4 * height;
+
+ if (cbRequired)
+ {
+ uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRequired);
+ if (pbDst != NULL)
+ {
+ if (pFBInfo->flags & VBVA_SCREEN_F_ACTIVE)
+ {
+ /* Copy guest VRAM to the allocated 32bpp buffer. */
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = width;
+ uint32_t u32SrcHeight = height;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ int32_t xDst = 0;
+ int32_t yDst = 0;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pbDst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ else
+ {
+ memset(pbDst, 0, cbRequired);
+ vrc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ *ppbData = pbDst;
+ *pcbData = cbRequired;
+ *pcx = width;
+ *pcy = height;
+ *pfMemFree = true;
+ }
+ else
+ {
+ RTMemFree(pbDst);
+
+ /* CopyRect can fail if VBVA was paused in VGA device, retry using the generic method. */
+ if ( vrc == VERR_INVALID_STATE
+ && aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnTakeScreenshot(pDisplay->mpDrv->pUpPort, ppbData, pcbData, pcx, pcy);
+ *pfMemFree = false;
+ }
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* No image. */
+ *ppbData = NULL;
+ *pcbData = 0;
+ *pcx = 0;
+ *pcy = 0;
+ *pfMemFree = true;
+ vrc = VINF_SUCCESS;
+ }
+ }
+ else
+ vrc = VERR_INVALID_PARAMETER;
+ return vrc;
+}
+
+static int i_displayTakeScreenshot(PUVM pUVM, PCVMMR3VTABLE pVMM, Display *pDisplay, struct DRVMAINDISPLAY *pDrv,
+ ULONG aScreenId, BYTE *address, ULONG width, ULONG height)
+{
+ uint8_t *pbData = NULL;
+ size_t cbData = 0;
+ uint32_t cx = 0;
+ uint32_t cy = 0;
+ bool fFreeMem = false;
+ int vrc = VINF_SUCCESS;
+
+ int cRetries = 5;
+ while (cRetries-- > 0)
+ {
+ /* Note! Not sure if the priority call is such a good idea here, but
+ it would be nice to have an accurate screenshot for the bug
+ report if the VM deadlocks. */
+ vrc = pVMM->pfnVMR3ReqPriorityCallWaitU(pUVM, VMCPUID_ANY, (PFNRT)Display::i_displayTakeScreenshotEMT, 7,
+ pDisplay, aScreenId, &pbData, &cbData, &cx, &cy, &fFreeMem);
+ if (vrc != VERR_TRY_AGAIN)
+ {
+ break;
+ }
+
+ RTThreadSleep(10);
+ }
+
+ if (RT_SUCCESS(vrc) && pbData)
+ {
+ if (cx == width && cy == height)
+ {
+ /* No scaling required. */
+ memcpy(address, pbData, cbData);
+ }
+ else
+ {
+ /* Scale. */
+ LogRelFlowFunc(("SCALE: %dx%d -> %dx%d\n", cx, cy, width, height));
+
+ uint8_t *dst = address;
+ uint8_t *src = pbData;
+ int dstW = width;
+ int dstH = height;
+ int srcW = cx;
+ int srcH = cy;
+ int iDeltaLine = cx * 4;
+
+ BitmapScale32(dst,
+ dstW, dstH,
+ src,
+ iDeltaLine,
+ srcW, srcH);
+ }
+
+ if (fFreeMem)
+ RTMemFree(pbData);
+ else
+ {
+ /* This can be called from any thread. */
+ pDrv->pUpPort->pfnFreeScreenshot(pDrv->pUpPort, pbData);
+ }
+ }
+
+ return vrc;
+}
+
+HRESULT Display::takeScreenShotWorker(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ ULONG *pcbOut)
+{
+ HRESULT hrc = S_OK;
+
+ /* Do not allow too small and too large screenshots. This also filters out negative
+ * values passed as either 'aWidth' or 'aHeight'.
+ */
+ CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767);
+ CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767);
+
+ if ( aBitmapFormat != BitmapFormat_BGR0
+ && aBitmapFormat != BitmapFormat_BGRA
+ && aBitmapFormat != BitmapFormat_RGBA
+ && aBitmapFormat != BitmapFormat_PNG)
+ {
+ return setError(E_NOTIMPL,
+ tr("Unsupported screenshot format 0x%08X"), aBitmapFormat);
+ }
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ int vrc = i_displayTakeScreenshot(ptrVM.rawUVM(), ptrVM.vtable(), this, mpDrv, aScreenId, aAddress, aWidth, aHeight);
+ if (RT_SUCCESS(vrc))
+ {
+ const size_t cbData = aWidth * 4 * aHeight;
+
+ /* Most of uncompressed formats. */
+ *pcbOut = (ULONG)cbData;
+
+ if (aBitmapFormat == BitmapFormat_BGR0)
+ {
+ /* Do nothing. */
+ }
+ else if (aBitmapFormat == BitmapFormat_BGRA)
+ {
+ uint32_t *pu32 = (uint32_t *)aAddress;
+ size_t cPixels = aWidth * aHeight;
+ while (cPixels--)
+ *pu32++ |= UINT32_C(0xFF000000);
+ }
+ else if (aBitmapFormat == BitmapFormat_RGBA)
+ {
+ uint8_t *pu8 = aAddress;
+ size_t cPixels = aWidth * aHeight;
+ while (cPixels--)
+ {
+ uint8_t u8 = pu8[0];
+ pu8[0] = pu8[2];
+ pu8[2] = u8;
+ pu8[3] = 0xFF;
+
+ pu8 += 4;
+ }
+ }
+ else if (aBitmapFormat == BitmapFormat_PNG)
+ {
+ uint8_t *pu8PNG = NULL;
+ uint32_t cbPNG = 0;
+ uint32_t cxPNG = 0;
+ uint32_t cyPNG = 0;
+
+ vrc = DisplayMakePNG(aAddress, aWidth, aHeight, &pu8PNG, &cbPNG, &cxPNG, &cyPNG, 0);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbPNG <= cbData)
+ {
+ memcpy(aAddress, pu8PNG, cbPNG);
+ *pcbOut = cbPNG;
+ }
+ else
+ hrc = setError(E_FAIL, tr("PNG is larger than 32bpp bitmap"));
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not convert screenshot to PNG (%Rrc)"), vrc);
+ RTMemFree(pu8PNG);
+ }
+ }
+ else if (vrc == VERR_TRY_AGAIN)
+ hrc = setErrorBoth(E_UNEXPECTED, vrc, tr("Screenshot is not available at this time"));
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not take a screenshot (%Rrc)"), vrc);
+
+ return hrc;
+}
+
+HRESULT Display::takeScreenShot(ULONG aScreenId,
+ BYTE *aAddress,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat)
+{
+ LogRelFlowFunc(("[%d] address=%p, width=%d, height=%d, format 0x%08X\n",
+ aScreenId, aAddress, aWidth, aHeight, aBitmapFormat));
+
+ ULONG cbOut = 0;
+ HRESULT hrc = takeScreenShotWorker(aScreenId, aAddress, aWidth, aHeight, aBitmapFormat, &cbOut);
+ NOREF(cbOut);
+
+ LogRelFlowFunc(("%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::takeScreenShotToArray(ULONG aScreenId,
+ ULONG aWidth,
+ ULONG aHeight,
+ BitmapFormat_T aBitmapFormat,
+ std::vector<BYTE> &aScreenData)
+{
+ LogRelFlowFunc(("[%d] width=%d, height=%d, format 0x%08X\n",
+ aScreenId, aWidth, aHeight, aBitmapFormat));
+
+ /* Do not allow too small and too large screenshots. This also filters out negative
+ * values passed as either 'aWidth' or 'aHeight'.
+ */
+ CheckComArgExpr(aWidth, aWidth != 0 && aWidth <= 32767);
+ CheckComArgExpr(aHeight, aHeight != 0 && aHeight <= 32767);
+
+ const size_t cbData = aWidth * 4 * aHeight;
+ aScreenData.resize(cbData);
+
+ ULONG cbOut = 0;
+ HRESULT hrc = takeScreenShotWorker(aScreenId, &aScreenData.front(), aWidth, aHeight, aBitmapFormat, &cbOut);
+ if (FAILED(hrc))
+ cbOut = 0;
+
+ aScreenData.resize(cbOut);
+
+ LogRelFlowFunc(("%Rhrc\n", hrc));
+ return hrc;
+}
+
+#ifdef VBOX_WITH_RECORDING
+/**
+ * Invalidates the recording configuration.
+ *
+ * @returns IPRT status code.
+ */
+int Display::i_recordingInvalidate(void)
+{
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+ if (!pCtx || !pCtx->IsStarted())
+ return VINF_SUCCESS;
+
+ /*
+ * Invalidate screens.
+ */
+ for (unsigned uScreen = 0; uScreen < mcMonitors; uScreen++)
+ {
+ RecordingStream *pRecordingStream = pCtx->GetStream(uScreen);
+
+ const bool fStreamEnabled = pRecordingStream->IsReady();
+ bool fChanged = maRecordingEnabled[uScreen] != fStreamEnabled;
+
+ maRecordingEnabled[uScreen] = fStreamEnabled;
+
+ if (fChanged && uScreen < mcMonitors)
+ i_recordingScreenChanged(uScreen);
+ }
+
+ return VINF_SUCCESS;
+}
+
+void Display::i_recordingScreenChanged(unsigned uScreenId)
+{
+ RecordingContext *pCtx = mParent->i_recordingGetContext();
+
+ i_UpdateDeviceCursorCapabilities();
+ if ( RT_LIKELY(!maRecordingEnabled[uScreenId])
+ || !pCtx || !pCtx->IsStarted())
+ {
+ /* Skip recording this screen. */
+ return;
+ }
+
+ /* Get a new source bitmap which will be used by video recording code. */
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam());
+
+ int vrc2 = RTCritSectEnter(&mVideoRecLock);
+ if (RT_SUCCESS(vrc2))
+ {
+ maFramebuffers[uScreenId].Recording.pSourceBitmap = pSourceBitmap;
+
+ vrc2 = RTCritSectLeave(&mVideoRecLock);
+ AssertRC(vrc2);
+ }
+}
+#endif /* VBOX_WITH_RECORDING */
+
+/*static*/ DECLCALLBACK(int)
+Display::i_drawToScreenEMT(Display *pDisplay, ULONG aScreenId, BYTE *address, ULONG x, ULONG y, ULONG width, ULONG height)
+{
+ int vrc = VINF_SUCCESS;
+
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[aScreenId];
+
+ if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ vrc = pDisplay->mpDrv->pUpPort->pfnDisplayBlt(pDisplay->mpDrv->pUpPort, address, x, y, width, height);
+ }
+ else if (aScreenId < pDisplay->mcMonitors)
+ {
+ /* Copy the bitmap to the guest VRAM. */
+ const uint8_t *pu8Src = address;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = width;
+ uint32_t u32SrcHeight = height;
+ uint32_t u32SrcLineSize = width * 4;
+ uint32_t u32SrcBitsPerPixel = 32;
+
+ uint8_t *pu8Dst = pFBInfo->pu8FramebufferVRAM;
+ int32_t xDst = x;
+ int32_t yDst = y;
+ uint32_t u32DstWidth = pFBInfo->w;
+ uint32_t u32DstHeight = pFBInfo->h;
+ uint32_t u32DstLineSize = pFBInfo->u32LineSize;
+ uint32_t u32DstBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ vrc = pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!pFBInfo->pSourceBitmap.isNull())
+ {
+ /* Update the changed screen area. When source bitmap uses VRAM directly, just notify
+ * frontend to update. And for default format, render the guest VRAM to the source bitmap.
+ */
+ if ( pFBInfo->fDefaultFormat
+ && !pFBInfo->fDisabled)
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ pu8Src = pFBInfo->pu8FramebufferVRAM;
+ xSrc = x;
+ ySrc = y;
+ u32SrcWidth = pFBInfo->w;
+ u32SrcHeight = pFBInfo->h;
+ u32SrcLineSize = pFBInfo->u32LineSize;
+ u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ /* Default format is 32 bpp. */
+ pu8Dst = pAddress;
+ xDst = xSrc;
+ yDst = ySrc;
+ u32DstWidth = u32SrcWidth;
+ u32DstHeight = u32SrcHeight;
+ u32DstLineSize = u32DstWidth * 4;
+ u32DstBitsPerPixel = 32;
+
+ pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+ }
+
+ pDisplay->i_handleDisplayUpdate(aScreenId, x, y, width, height);
+ }
+ }
+ else
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ pDisplay->mParent->i_consoleVRDPServer()->SendUpdateBitmap(aScreenId, x, y, width, height);
+
+ return vrc;
+}
+
+HRESULT Display::drawToScreen(ULONG aScreenId, BYTE *aAddress, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight)
+{
+ /// @todo (r=dmik) this function may take too long to complete if the VM
+ // is doing something like saving state right now. Which, in case if it
+ // is called on the GUI thread, will make it unresponsive. We should
+ // check the machine state here (by enclosing the check and VMRequCall
+ // within the Console lock to make it atomic).
+
+ LogRelFlowFunc(("aAddress=%p, x=%d, y=%d, width=%d, height=%d\n",
+ (void *)aAddress, aX, aY, aWidth, aHeight));
+
+ CheckComArgExpr(aWidth, aWidth != 0);
+ CheckComArgExpr(aHeight, aHeight != 0);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ /* Release lock because the call scheduled on EMT may also try to take it. */
+ alock.release();
+
+ /*
+ * Again we're lazy and make the graphics device do all the
+ * dirty conversion work.
+ */
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_drawToScreenEMT, 7,
+ this, aScreenId, aAddress, aX, aY, aWidth, aHeight);
+
+ /*
+ * If the function returns not supported, we'll have to do all the
+ * work ourselves using the framebuffer.
+ */
+ HRESULT hrc = S_OK;
+ if (vrc == VERR_NOT_SUPPORTED || vrc == VERR_NOT_IMPLEMENTED)
+ {
+ /** @todo implement generic fallback for screen blitting. */
+ hrc = E_NOTIMPL;
+ }
+ else if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not draw to the screen (%Rrc)"), vrc);
+/// @todo
+// else
+// {
+// /* All ok. Redraw the screen. */
+// handleDisplayUpdate(x, y, width, height);
+// }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+/** @todo r=bird: cannot quite see why this would be required to run on an
+ * EMT any more. It's not an issue in the COM methods, but for the
+ * VGA device interface it is an issue, see querySourceBitmap. */
+/*static*/ DECLCALLBACK(int) Display::i_InvalidateAndUpdateEMT(Display *pDisplay, unsigned uId, bool fUpdateAll)
+{
+ LogRelFlowFunc(("uId=%d, fUpdateAll %d\n", uId, fUpdateAll));
+
+ unsigned uScreenId;
+ for (uScreenId = (fUpdateAll ? 0 : uId); uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId];
+
+ if ( !pFBInfo->fVBVAEnabled
+ && uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ pDisplay->mpDrv->pUpPort->pfnUpdateDisplayAll(pDisplay->mpDrv->pUpPort, /* fFailOnResize = */ true);
+ else
+ {
+ if (!pFBInfo->fDisabled)
+ {
+ /* Render complete VRAM screen to the framebuffer.
+ * When framebuffer uses VRAM directly, just notify it to update.
+ */
+ if (pFBInfo->fDefaultFormat && !pFBInfo->pSourceBitmap.isNull())
+ {
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t width = pFBInfo->w;
+ uint32_t height = pFBInfo->h;
+
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = 0;
+ int32_t ySrc = 0;
+ uint32_t u32SrcWidth = pFBInfo->w;
+ uint32_t u32SrcHeight = pFBInfo->h;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ /* Default format is 32 bpp. */
+ uint8_t *pu8Dst = pAddress;
+ int32_t xDst = xSrc;
+ int32_t yDst = ySrc;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ /* if uWidth != pFBInfo->w and uHeight != pFBInfo->h
+ * implies resize of Framebuffer is in progress and
+ * copyrect should not be called.
+ */
+ if (ulWidth == pFBInfo->w && ulHeight == pFBInfo->h)
+ pDisplay->mpDrv->pUpPort->pfnCopyRect(pDisplay->mpDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+
+ pDisplay->i_handleDisplayUpdate(uScreenId, 0, 0, pFBInfo->w, pFBInfo->h);
+ }
+ }
+ if (!fUpdateAll)
+ break;
+ }
+ LogRelFlowFunc(("done\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Does a full invalidation of the VM display and instructs the VM
+ * to update it immediately.
+ *
+ * @returns COM status code
+ */
+
+HRESULT Display::invalidateAndUpdate()
+{
+ LogRelFlowFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ LogRelFlowFunc(("Sending DPYUPDATE request\n"));
+
+ /* Have to release the lock when calling EMT. */
+ alock.release();
+
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, 0, true);
+ alock.acquire();
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("Could not invalidate and update the screen (%Rrc)"), vrc);
+ }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::invalidateAndUpdateScreen(ULONG aScreenId)
+{
+ LogRelFlowFunc(("\n"));
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not invalidate and update the screen %d (%Rrc)"), aScreenId, vrc);
+ }
+
+ LogRelFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+}
+
+HRESULT Display::completeVHWACommand(BYTE *aCommand)
+{
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ mpDrv->pVBVACallbacks->pfnVHWACommandCompleteAsync(mpDrv->pVBVACallbacks, (VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)aCommand);
+ return S_OK;
+#else
+ RT_NOREF(aCommand);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Display::viewportChanged(ULONG aScreenId, ULONG aX, ULONG aY, ULONG aWidth, ULONG aHeight)
+{
+ AssertMsgReturn(aScreenId < mcMonitors, ("aScreendId=%d mcMonitors=%d\n", aScreenId, mcMonitors), E_INVALIDARG);
+
+ /* The driver might not have been constructed yet */
+ if (mpDrv && mpDrv->pUpPort->pfnSetViewport)
+ mpDrv->pUpPort->pfnSetViewport(mpDrv->pUpPort, aScreenId, aX, aY, aWidth, aHeight);
+
+ return S_OK;
+}
+
+HRESULT Display::querySourceBitmap(ULONG aScreenId,
+ ComPtr<IDisplaySourceBitmap> &aDisplaySourceBitmap)
+{
+ LogRelFlowFunc(("aScreenId = %d\n", aScreenId));
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return ptrVM.hrc();
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ bool fSetRenderVRAM = false;
+ bool fInvalidate = false;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aScreenId >= mcMonitors)
+ return setError(E_INVALIDARG, tr("QuerySourceBitmap: Invalid screen %d (total %d)"), aScreenId, mcMonitors);
+
+ if (!mfSourceBitmapEnabled)
+ {
+ aDisplaySourceBitmap = NULL;
+ return E_FAIL;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[aScreenId];
+
+ /* No source bitmap for a blank guest screen. */
+ if (pFBInfo->flags & VBVA_SCREEN_F_BLANK)
+ {
+ aDisplaySourceBitmap = NULL;
+ return E_FAIL;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (pFBInfo->pSourceBitmap.isNull())
+ {
+ /* Create a new object. */
+ ComObjPtr<DisplaySourceBitmap> obj;
+ hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ hr = obj->init(this, aScreenId, pFBInfo);
+
+ if (SUCCEEDED(hr))
+ {
+ pFBInfo->pSourceBitmap = obj;
+ pFBInfo->fDefaultFormat = !obj->i_usesVRAM();
+
+ if (aScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Start buffer updates. */
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+
+ mpDrv->IConnector.pbData = pAddress;
+ mpDrv->IConnector.cbScanline = ulBytesPerLine;
+ mpDrv->IConnector.cBits = ulBitsPerPixel;
+ mpDrv->IConnector.cx = ulWidth;
+ mpDrv->IConnector.cy = ulHeight;
+
+ fSetRenderVRAM = pFBInfo->fDefaultFormat;
+ }
+
+ /* Make sure that the bitmap contains the latest image. */
+ fInvalidate = pFBInfo->fDefaultFormat;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ pFBInfo->pSourceBitmap.queryInterfaceTo(aDisplaySourceBitmap.asOutParam());
+ }
+
+ /* Leave the IDisplay lock because the VGA device must not be called under it. */
+ alock.release();
+
+ if (SUCCEEDED(hr))
+ {
+ if (fSetRenderVRAM)
+ mpDrv->pUpPort->pfnSetRenderVRAM(mpDrv->pUpPort, true);
+
+ if (fInvalidate)
+#if 1 /* bird: Cannot see why this needs to run on an EMT. It deadlocks now with timer callback moving to non-EMT worker threads. */
+ Display::i_InvalidateAndUpdateEMT(this, aScreenId, false /*fUpdateAll*/);
+#else
+ VMR3ReqCallWaitU(ptrVM.rawUVM(), VMCPUID_ANY, (PFNRT)Display::i_InvalidateAndUpdateEMT,
+ 3, this, aScreenId, false);
+#endif
+ }
+
+ LogRelFlowFunc(("%Rhrc\n", hr));
+ return hr;
+}
+
+HRESULT Display::getGuestScreenLayout(std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenLayout)
+{
+ NOREF(aGuestScreenLayout);
+ return E_NOTIMPL;
+}
+
+HRESULT Display::setScreenLayout(ScreenLayoutMode_T aScreenLayoutMode,
+ const std::vector<ComPtr<IGuestScreenInfo> > &aGuestScreenInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aGuestScreenInfo.size() != mcMonitors)
+ return E_INVALIDARG;
+
+ CHECK_CONSOLE_DRV(mpDrv);
+
+ /*
+ * It is up to the guest to decide whether the hint is
+ * valid. Therefore don't do any VRAM sanity checks here.
+ */
+
+ /* Have to release the lock because the pfnRequestDisplayChange
+ * will call EMT. */
+ alock.release();
+
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ {
+ uint32_t const cDisplays = (uint32_t)aGuestScreenInfo.size();
+
+ size_t const cbAlloc = cDisplays * sizeof(VMMDevDisplayDef);
+ VMMDevDisplayDef *paDisplayDefs = (VMMDevDisplayDef *)RTMemAlloc(cbAlloc);
+ if (paDisplayDefs)
+ {
+ for (uint32_t i = 0; i < cDisplays; ++i)
+ {
+ VMMDevDisplayDef *p = &paDisplayDefs[i];
+ ComPtr<IGuestScreenInfo> pScreenInfo = aGuestScreenInfo[i];
+
+ ULONG screenId = 0;
+ GuestMonitorStatus_T guestMonitorStatus = GuestMonitorStatus_Enabled;
+ BOOL origin = FALSE;
+ BOOL primary = FALSE;
+ LONG originX = 0;
+ LONG originY = 0;
+ ULONG width = 0;
+ ULONG height = 0;
+ ULONG bitsPerPixel = 0;
+
+ pScreenInfo->COMGETTER(ScreenId) (&screenId);
+ pScreenInfo->COMGETTER(GuestMonitorStatus)(&guestMonitorStatus);
+ pScreenInfo->COMGETTER(Primary) (&primary);
+ pScreenInfo->COMGETTER(Origin) (&origin);
+ pScreenInfo->COMGETTER(OriginX) (&originX);
+ pScreenInfo->COMGETTER(OriginY) (&originY);
+ pScreenInfo->COMGETTER(Width) (&width);
+ pScreenInfo->COMGETTER(Height) (&height);
+ pScreenInfo->COMGETTER(BitsPerPixel)(&bitsPerPixel);
+
+ LogFlowFunc(("%d %d,%d %dx%d\n", screenId, originX, originY, width, height));
+
+ p->idDisplay = screenId;
+ p->xOrigin = originX;
+ p->yOrigin = originY;
+ p->cx = width;
+ p->cy = height;
+ p->cBitsPerPixel = bitsPerPixel;
+ p->fDisplayFlags = VMMDEV_DISPLAY_CX | VMMDEV_DISPLAY_CY | VMMDEV_DISPLAY_BPP;
+ if (guestMonitorStatus == GuestMonitorStatus_Disabled)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_DISABLED;
+ if (origin)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_ORIGIN;
+ if (primary)
+ p->fDisplayFlags |= VMMDEV_DISPLAY_PRIMARY;
+ }
+
+ bool const fForce = aScreenLayoutMode == ScreenLayoutMode_Reset
+ || aScreenLayoutMode == ScreenLayoutMode_Apply;
+ bool const fNotify = aScreenLayoutMode != ScreenLayoutMode_Silent;
+ pVMMDevPort->pfnRequestDisplayChange(pVMMDevPort, cDisplays, paDisplayDefs, fForce, fNotify);
+
+ RTMemFree(paDisplayDefs);
+ }
+ }
+ }
+ return S_OK;
+}
+
+HRESULT Display::detachScreens(const std::vector<LONG> &aScreenIds)
+{
+ NOREF(aScreenIds);
+ return E_NOTIMPL;
+}
+
+HRESULT Display::createGuestScreenInfo(ULONG aDisplay,
+ GuestMonitorStatus_T aStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel,
+ ComPtr<IGuestScreenInfo> &aGuestScreenInfo)
+{
+ /* Create a new object. */
+ ComObjPtr<GuestScreenInfo> obj;
+ HRESULT hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ hr = obj->init(aDisplay, aStatus, aPrimary, aChangeOrigin, aOriginX, aOriginY,
+ aWidth, aHeight, aBitsPerPixel);
+ if (SUCCEEDED(hr))
+ obj.queryInterfaceTo(aGuestScreenInfo.asOutParam());
+
+ return hr;
+}
+
+
+/*
+ * GuestScreenInfo implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(GuestScreenInfo)
+
+HRESULT GuestScreenInfo::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void GuestScreenInfo::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT GuestScreenInfo::init(ULONG aDisplay,
+ GuestMonitorStatus_T aGuestMonitorStatus,
+ BOOL aPrimary,
+ BOOL aChangeOrigin,
+ LONG aOriginX,
+ LONG aOriginY,
+ ULONG aWidth,
+ ULONG aHeight,
+ ULONG aBitsPerPixel)
+{
+ LogFlowThisFunc(("[%u]\n", aDisplay));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ mScreenId = aDisplay;
+ mGuestMonitorStatus = aGuestMonitorStatus;
+ mPrimary = aPrimary;
+ mOrigin = aChangeOrigin;
+ mOriginX = aOriginX;
+ mOriginY = aOriginY;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mBitsPerPixel = aBitsPerPixel;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void GuestScreenInfo::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("[%u]\n", mScreenId));
+}
+
+HRESULT GuestScreenInfo::getScreenId(ULONG *aScreenId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aScreenId = mScreenId;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getGuestMonitorStatus(GuestMonitorStatus_T *aGuestMonitorStatus)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aGuestMonitorStatus = mGuestMonitorStatus;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getPrimary(BOOL *aPrimary)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aPrimary = mPrimary;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOrigin(BOOL *aOrigin)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOrigin = mOrigin;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOriginX(LONG *aOriginX)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOriginX = mOriginX;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getOriginY(LONG *aOriginY)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aOriginY = mOriginY;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getWidth(ULONG *aWidth)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aWidth = mWidth;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getHeight(ULONG *aHeight)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aHeight = mHeight;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getBitsPerPixel(ULONG *aBitsPerPixel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aBitsPerPixel = mBitsPerPixel;
+ return S_OK;
+}
+
+HRESULT GuestScreenInfo::getExtendedInfo(com::Utf8Str &aExtendedInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aExtendedInfo = com::Utf8Str();
+ return S_OK;
+}
+
+// wrapped IEventListener method
+HRESULT Display::handleEvent(const ComPtr<IEvent> &aEvent)
+{
+ VBoxEventType_T aType = VBoxEventType_Invalid;
+
+ aEvent->COMGETTER(Type)(&aType);
+ switch (aType)
+ {
+ case VBoxEventType_OnStateChanged:
+ {
+ ComPtr<IStateChangedEvent> scev = aEvent;
+ Assert(scev);
+ MachineState_T machineState;
+ scev->COMGETTER(State)(&machineState);
+ if ( machineState == MachineState_Running
+ || machineState == MachineState_Teleporting
+ || machineState == MachineState_LiveSnapshotting
+ || machineState == MachineState_DeletingSnapshotOnline
+ )
+ {
+ LogRelFlowFunc(("Machine is running.\n"));
+
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+}
+
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handle display resize event issued by the VGA device for the primary screen.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnResize
+ */
+DECLCALLBACK(int) Display::i_displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ LogRelFlowFunc(("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n",
+ bpp, pvVRAM, cbLine, cx, cy));
+
+ bool f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, true, false);
+ if (!f)
+ {
+ /* This is a result of recursive call when the source bitmap is being updated
+ * during a VGA resize. Tell the VGA device to ignore the call.
+ *
+ * @todo It is a workaround, actually pfnUpdateDisplayAll must
+ * fail on resize.
+ */
+ LogRel(("displayResizeCallback: already processing\n"));
+ return VINF_VGA_RESIZE_IN_PROGRESS;
+ }
+
+ int vrc = pThis->i_handleDisplayResize(VBOX_VIDEO_PRIMARY_SCREEN, bpp, pvVRAM, cbLine, cx, cy, 0, 0, 0, true);
+
+ /* Restore the flag. */
+ f = ASMAtomicCmpXchgBool(&pThis->fVGAResizing, false, true);
+ AssertRelease(f);
+
+ return vrc;
+}
+
+/**
+ * Handle display update.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect
+ */
+DECLCALLBACK(void) Display::i_displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ uint32_t x, uint32_t y, uint32_t cx, uint32_t cy)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("fVideoAccelEnabled = %d, %d,%d %dx%d\n",
+ pDrv->pDisplay->mVideoAccelLegacy.fVideoAccelEnabled, x, y, cx, cy));
+#endif /* DEBUG_sunlover */
+
+ /* This call does update regardless of VBVA status.
+ * But in VBVA mode this is called only as result of
+ * pfnUpdateDisplayAll in the VGA device.
+ */
+
+ pDrv->pDisplay->i_handleDisplayUpdate(VBOX_VIDEO_PRIMARY_SCREEN, x, y, cx, cy);
+}
+
+/**
+ * Periodic display refresh callback.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnRefresh
+ * @thread EMT
+ */
+/*static*/ DECLCALLBACK(void) Display::i_displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("pDrv->pDisplay->mfVideoAccelEnabled = %d\n",
+ pDrv->pDisplay->mfVideoAccelEnabled));
+#endif /* DEBUG_sunlover_2 */
+
+ Display *pDisplay = pDrv->pDisplay;
+ unsigned uScreenId;
+
+ int vrc = pDisplay->i_videoAccelRefreshProcess(pDrv->pUpPort);
+ if (vrc != VINF_TRY_AGAIN) /* Means 'do nothing' here. */
+ {
+ if (vrc == VWRN_INVALID_STATE)
+ {
+ /* No VBVA do a display update. */
+ pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort);
+ }
+
+ /* Inform the VRDP server that the current display update sequence is
+ * completed. At this moment the framebuffer memory contains a definite
+ * image, that is synchronized with the orders already sent to VRDP client.
+ * The server can now process redraw requests from clients or initial
+ * fullscreen updates for new clients.
+ */
+ for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ Assert(pDisplay->mParent && pDisplay->mParent->i_consoleVRDPServer());
+ pDisplay->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, NULL, 0);
+ }
+ }
+
+#ifdef VBOX_WITH_RECORDING
+ AssertPtr(pDisplay->mParent);
+ RecordingContext *pCtx = pDisplay->mParent->i_recordingGetContext();
+
+ if ( pCtx
+ && pCtx->IsStarted()
+ && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+ {
+ do
+ {
+ /* If the recording context has reached the configured recording
+ * limit, disable recording. */
+ if (pCtx->IsLimitReached())
+ {
+ pDisplay->mParent->i_onRecordingChange(FALSE /* Disable */);
+ break;
+ }
+
+ uint64_t tsNowMs = RTTimeProgramMilliTS();
+ for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++)
+ {
+ if (!pDisplay->maRecordingEnabled[uScreenId])
+ continue;
+
+ if (!pCtx->NeedsUpdate(uScreenId, tsNowMs))
+ continue;
+
+ DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId];
+ if (!pFBInfo->fDisabled)
+ {
+ ComPtr<IDisplaySourceBitmap> pSourceBitmap;
+ int vrc2 = RTCritSectEnter(&pDisplay->mVideoRecLock);
+ if (RT_SUCCESS(vrc2))
+ {
+ pSourceBitmap = pFBInfo->Recording.pSourceBitmap;
+ RTCritSectLeave(&pDisplay->mVideoRecLock);
+ }
+
+ if (!pSourceBitmap.isNull())
+ {
+ BYTE *pbAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+ HRESULT hrc = pSourceBitmap->QueryBitmapInfo(&pbAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc) && pbAddress)
+ vrc = pCtx->SendVideoFrame(uScreenId, 0, 0, BitmapFormat_BGR,
+ ulBitsPerPixel, ulBytesPerLine, ulWidth, ulHeight,
+ pbAddress, tsNowMs);
+ else
+ vrc = VERR_NOT_SUPPORTED;
+
+ pSourceBitmap.setNull();
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+
+ if (vrc == VINF_TRY_AGAIN)
+ break;
+ }
+ }
+ } while (0);
+ }
+#endif /* VBOX_WITH_RECORDING */
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("leave\n"));
+#endif /* DEBUG_sunlover_2 */
+}
+
+/**
+ * Reset notification
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnReset
+ */
+DECLCALLBACK(void) Display::i_displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ LogRelFlowFunc(("\n"));
+
+ /* Disable VBVA mode. */
+ pDrv->pDisplay->VideoAccelEnableVGA(false, NULL);
+}
+
+/**
+ * LFBModeChange notification
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
+ */
+DECLCALLBACK(void) Display::i_displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ LogRelFlowFunc(("fEnabled=%d\n", fEnabled));
+
+ NOREF(fEnabled);
+
+ /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
+ pDrv->pDisplay->VideoAccelEnableVGA(false, NULL);
+}
+
+/**
+ * Adapter information change notification.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnProcessAdapterData
+ */
+DECLCALLBACK(void) Display::i_displayProcessAdapterDataCallback(PPDMIDISPLAYCONNECTOR pInterface, void *pvVRAM,
+ uint32_t u32VRAMSize)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ pDrv->pDisplay->processAdapterData(pvVRAM, u32VRAMSize);
+}
+
+/**
+ * Display information change notification.
+ *
+ * @see PDMIDISPLAYCONNECTOR::pfnProcessDisplayData
+ */
+DECLCALLBACK(void) Display::i_displayProcessDisplayDataCallback(PPDMIDISPLAYCONNECTOR pInterface,
+ void *pvVRAM, unsigned uScreenId)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ pDrv->pDisplay->processDisplayData(pvVRAM, uScreenId);
+}
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+
+int Display::i_handleVHWACommandProcess(int enmCmd, bool fGuestCmd, VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
+{
+ /* bugref:9691 Disable the legacy VHWA interface.
+ * Keep the host commands enabled because they are needed when an old saved state is loaded.
+ */
+ if (fGuestCmd)
+ return VERR_NOT_IMPLEMENTED;
+
+ unsigned id = (unsigned)pCommand->iDisplay;
+ if (id >= mcMonitors)
+ return VERR_INVALID_PARAMETER;
+
+ ComPtr<IFramebuffer> pFramebuffer;
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ pFramebuffer = maFramebuffers[id].pFramebuffer;
+ bool fVHWASupported = RT_BOOL(maFramebuffers[id].u32Caps & FramebufferCapabilities_VHWA);
+ arlock.release();
+
+ if (pFramebuffer == NULL || !fVHWASupported)
+ return VERR_NOT_IMPLEMENTED; /* Implementation is not available. */
+
+ HRESULT hr = pFramebuffer->ProcessVHWACommand((BYTE *)pCommand, enmCmd, fGuestCmd);
+ if (hr == S_FALSE)
+ return VINF_SUCCESS;
+ if (SUCCEEDED(hr))
+ return VINF_CALLBACK_RETURN;
+ if (hr == E_ACCESSDENIED)
+ return VERR_INVALID_STATE; /* notify we can not handle request atm */
+ if (hr == E_NOTIMPL)
+ return VERR_NOT_IMPLEMENTED;
+ return VERR_GENERAL_FAILURE;
+}
+
+DECLCALLBACK(int) Display::i_displayVHWACommandProcess(PPDMIDISPLAYCONNECTOR pInterface, int enmCmd, bool fGuestCmd,
+ VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCommand)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ return pDrv->pDisplay->i_handleVHWACommandProcess(enmCmd, fGuestCmd, pCommand);
+}
+
+#endif /* VBOX_WITH_VIDEOHWACCEL */
+
+int Display::i_handle3DNotifyProcess(VBOX3DNOTIFY *p3DNotify)
+{
+ unsigned const id = (unsigned)p3DNotify->iDisplay;
+ if (id >= mcMonitors)
+ return VERR_INVALID_PARAMETER;
+
+ ComPtr<IFramebuffer> pFramebuffer;
+ AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS);
+ pFramebuffer = maFramebuffers[id].pFramebuffer;
+ arlock.release();
+
+ int vrc = VINF_SUCCESS;
+
+ if (!pFramebuffer.isNull())
+ {
+ if (p3DNotify->enmNotification == VBOX3D_NOTIFY_TYPE_HW_OVERLAY_GET_ID)
+ {
+ LONG64 winId = 0;
+ HRESULT hrc = pFramebuffer->COMGETTER(WinId)(&winId);
+ if (SUCCEEDED(hrc))
+ {
+ *(uint64_t *)&p3DNotify->au8Data[0] = winId;
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ com::SafeArray<BYTE> data;
+ data.initFrom((BYTE *)&p3DNotify->au8Data[0], p3DNotify->cbData);
+
+ HRESULT hrc = pFramebuffer->Notify3DEvent((ULONG)p3DNotify->enmNotification, ComSafeArrayAsInParam(data));
+ if (FAILED(hrc))
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ vrc = VERR_NOT_IMPLEMENTED;
+
+ return vrc;
+}
+
+DECLCALLBACK(int) Display::i_display3DNotifyProcess(PPDMIDISPLAYCONNECTOR pInterface,
+ VBOX3DNOTIFY *p3DNotify)
+{
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ return pDrv->pDisplay->i_handle3DNotifyProcess(p3DNotify);
+}
+
+HRESULT Display::notifyScaleFactorChange(ULONG aScreenId, ULONG aScaleFactorWMultiplied, ULONG aScaleFactorHMultiplied)
+{
+ RT_NOREF(aScreenId, aScaleFactorWMultiplied, aScaleFactorHMultiplied);
+# if 0 /** @todo Thank you so very much from anyone using VMSVGA3d! */
+ AssertMsgFailed(("Attempt to specify OpenGL content scale factor while 3D acceleration is disabled in VM config. Ignored.\n"));
+# else
+ /* Need an interface like this here (and the #ifdefs needs adjusting):
+ PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL;
+ if (pUpPort && pUpPort->pfnSetScaleFactor)
+ pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */
+# endif
+ return S_OK;
+}
+
+HRESULT Display::notifyHiDPIOutputPolicyChange(BOOL fUnscaledHiDPI)
+{
+ RT_NOREF(fUnscaledHiDPI);
+
+ /* Need an interface like this here (and the #ifdefs needs adjusting):
+ PPDMIDISPLAYPORT pUpPort = mpDrv ? mpDrv->pUpPort : NULL;
+ if (pUpPort && pUpPort->pfnSetScaleFactor)
+ pUpPort->pfnSetScaleFactor(pUpPort, aScreeId, aScaleFactorWMultiplied, aScaleFactorHMultiplied); */
+
+ return S_OK;
+}
+
+#ifdef VBOX_WITH_HGSMI
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAEnable}
+ */
+DECLCALLBACK(int) Display::i_displayVBVAEnable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pHostFlags)
+{
+ LogRelFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ AssertReturn(uScreenId < pThis->mcMonitors, VERR_INVALID_PARAMETER);
+
+ if (pThis->maFramebuffers[uScreenId].fVBVAEnabled)
+ {
+ LogRel(("Enabling different vbva mode\n"));
+#ifdef DEBUG_misha
+ AssertMsgFailed(("enabling different vbva mode\n"));
+#endif
+ return VERR_INVALID_STATE;
+ }
+
+ pThis->maFramebuffers[uScreenId].fVBVAEnabled = true;
+ pThis->maFramebuffers[uScreenId].pVBVAHostFlags = pHostFlags;
+ pThis->maFramebuffers[uScreenId].fVBVAForceResize = true;
+
+ vbvaSetMemoryFlagsHGSMI(uScreenId, pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, &pThis->maFramebuffers[uScreenId]);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVADisable}
+ */
+DECLCALLBACK(void) Display::i_displayVBVADisable(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId)
+{
+ LogRelFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ DISPLAYFBINFO *pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Make sure that the primary screen is visible now.
+ * The guest can't use VBVA anymore, so only only the VGA device output works.
+ */
+ pFBInfo->flags = 0;
+ if (pFBInfo->fDisabled)
+ {
+ pFBInfo->fDisabled = false;
+ ::FireGuestMonitorChangedEvent(pThis->mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId,
+ pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h);
+ }
+ }
+
+ pFBInfo->fVBVAEnabled = false;
+ pFBInfo->fVBVAForceResize = false;
+
+ vbvaSetMemoryFlagsHGSMI(uScreenId, 0, false, pFBInfo);
+
+ pFBInfo->pVBVAHostFlags = NULL;
+
+ if (uScreenId == VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Force full screen update, because VGA device must take control, do resize, etc. */
+ pThis->mpDrv->pUpPort->pfnUpdateDisplayAll(pThis->mpDrv->pUpPort, /* fFailOnResize = */ false);
+ }
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAUpdateBegin(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId)
+{
+ RT_NOREF(uScreenId);
+ LogFlowFunc(("uScreenId %d\n", uScreenId));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ if (ASMAtomicReadU32(&pThis->mu32UpdateVBVAFlags) > 0)
+ {
+ vbvaSetMemoryFlagsAllHGSMI(pThis->mfu32SupportedOrders, pThis->mfVideoAccelVRDP, pThis->maFramebuffers,
+ pThis->mcMonitors);
+ ASMAtomicDecU32(&pThis->mu32UpdateVBVAFlags);
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIDISPLAYCONNECTOR,pfnVBVAUpdateProcess}
+ */
+DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId,
+ struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd)
+{
+ LogFlowFunc(("uScreenId %d pCmd %p cbCmd %d, @%d,%d %dx%d\n", uScreenId, pCmd, cbCmd, pCmd->x, pCmd->y, pCmd->w, pCmd->h));
+ VBVACMDHDR hdrSaved;
+ RT_COPY_VOLATILE(hdrSaved, *pCmd);
+ RT_UNTRUSTED_NONVOLATILE_COPY_FENCE();
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ DISPLAYFBINFO *pFBInfo;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ if (pFBInfo->fDefaultFormat)
+ {
+ /* Make sure that framebuffer contains the same image as the guest VRAM. */
+ if ( uScreenId == VBOX_VIDEO_PRIMARY_SCREEN
+ && !pFBInfo->fDisabled)
+ {
+ pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdrSaved.x, hdrSaved.y, hdrSaved.w, hdrSaved.h);
+ }
+ else if ( !pFBInfo->pSourceBitmap.isNull()
+ && !pFBInfo->fDisabled)
+ {
+ /* Render VRAM content to the framebuffer. */
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ HRESULT hrc = pFBInfo->pSourceBitmap->QueryBitmapInfo(&pAddress,
+ &ulWidth,
+ &ulHeight,
+ &ulBitsPerPixel,
+ &ulBytesPerLine,
+ &bitmapFormat);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t width = hdrSaved.w;
+ uint32_t height = hdrSaved.h;
+
+ const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM;
+ int32_t xSrc = hdrSaved.x - pFBInfo->xOrigin;
+ int32_t ySrc = hdrSaved.y - pFBInfo->yOrigin;
+ uint32_t u32SrcWidth = pFBInfo->w;
+ uint32_t u32SrcHeight = pFBInfo->h;
+ uint32_t u32SrcLineSize = pFBInfo->u32LineSize;
+ uint32_t u32SrcBitsPerPixel = pFBInfo->u16BitsPerPixel;
+
+ uint8_t *pu8Dst = pAddress;
+ int32_t xDst = xSrc;
+ int32_t yDst = ySrc;
+ uint32_t u32DstWidth = u32SrcWidth;
+ uint32_t u32DstHeight = u32SrcHeight;
+ uint32_t u32DstLineSize = u32DstWidth * 4;
+ uint32_t u32DstBitsPerPixel = 32;
+
+ pDrv->pUpPort->pfnCopyRect(pDrv->pUpPort,
+ width, height,
+ pu8Src,
+ xSrc, ySrc,
+ u32SrcWidth, u32SrcHeight,
+ u32SrcLineSize, u32SrcBitsPerPixel,
+ pu8Dst,
+ xDst, yDst,
+ u32DstWidth, u32DstHeight,
+ u32DstLineSize, u32DstBitsPerPixel);
+ }
+ }
+ }
+
+ /*
+ * Here is your classic 'temporary' solution.
+ */
+ /** @todo New SendUpdate entry which can get a separate cmd header or coords. */
+ VBVACMDHDR *pHdrUnconst = (VBVACMDHDR *)pCmd;
+
+ pHdrUnconst->x -= (int16_t)pFBInfo->xOrigin;
+ pHdrUnconst->y -= (int16_t)pFBInfo->yOrigin;
+
+ pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, pHdrUnconst, (uint32_t)cbCmd);
+
+ *pHdrUnconst = hdrSaved;
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y,
+ uint32_t cx, uint32_t cy)
+{
+ LogFlowFunc(("uScreenId %d %d,%d %dx%d\n", uScreenId, x, y, cx, cy));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+ DISPLAYFBINFO *pFBInfo;
+ AssertReturnVoid(uScreenId < pThis->mcMonitors);
+
+ pFBInfo = &pThis->maFramebuffers[uScreenId];
+
+ /** @todo handleFramebufferUpdate (uScreenId,
+ * x - pThis->maFramebuffers[uScreenId].xOrigin,
+ * y - pThis->maFramebuffers[uScreenId].yOrigin,
+ * cx, cy);
+ */
+ pThis->i_handleDisplayUpdate(uScreenId, x - pFBInfo->xOrigin, y - pFBInfo->yOrigin, cx, cy);
+}
+
+#ifdef DEBUG_sunlover
+static void logVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, const DISPLAYFBINFO *pFBInfo)
+{
+ LogRel(("displayVBVAResize: [%d] %s\n"
+ " pView->u32ViewIndex %d\n"
+ " pView->u32ViewOffset 0x%08X\n"
+ " pView->u32ViewSize 0x%08X\n"
+ " pView->u32MaxScreenSize 0x%08X\n"
+ " pScreen->i32OriginX %d\n"
+ " pScreen->i32OriginY %d\n"
+ " pScreen->u32StartOffset 0x%08X\n"
+ " pScreen->u32LineSize 0x%08X\n"
+ " pScreen->u32Width %d\n"
+ " pScreen->u32Height %d\n"
+ " pScreen->u16BitsPerPixel %d\n"
+ " pScreen->u16Flags 0x%04X\n"
+ " pFBInfo->u32Offset 0x%08X\n"
+ " pFBInfo->u32MaxFramebufferSize 0x%08X\n"
+ " pFBInfo->u32InformationSize 0x%08X\n"
+ " pFBInfo->fDisabled %d\n"
+ " xOrigin, yOrigin, w, h: %d,%d %dx%d\n"
+ " pFBInfo->u16BitsPerPixel %d\n"
+ " pFBInfo->pu8FramebufferVRAM %p\n"
+ " pFBInfo->u32LineSize 0x%08X\n"
+ " pFBInfo->flags 0x%04X\n"
+ " pFBInfo->pHostEvents %p\n"
+ " pFBInfo->fDefaultFormat %d\n"
+ " pFBInfo->fVBVAEnabled %d\n"
+ " pFBInfo->fVBVAForceResize %d\n"
+ " pFBInfo->pVBVAHostFlags %p\n"
+ "",
+ pScreen->u32ViewIndex,
+ (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED)? "DISABLED": "ENABLED",
+ pView->u32ViewIndex,
+ pView->u32ViewOffset,
+ pView->u32ViewSize,
+ pView->u32MaxScreenSize,
+ pScreen->i32OriginX,
+ pScreen->i32OriginY,
+ pScreen->u32StartOffset,
+ pScreen->u32LineSize,
+ pScreen->u32Width,
+ pScreen->u32Height,
+ pScreen->u16BitsPerPixel,
+ pScreen->u16Flags,
+ pFBInfo->u32Offset,
+ pFBInfo->u32MaxFramebufferSize,
+ pFBInfo->u32InformationSize,
+ pFBInfo->fDisabled,
+ pFBInfo->xOrigin,
+ pFBInfo->yOrigin,
+ pFBInfo->w,
+ pFBInfo->h,
+ pFBInfo->u16BitsPerPixel,
+ pFBInfo->pu8FramebufferVRAM,
+ pFBInfo->u32LineSize,
+ pFBInfo->flags,
+ pFBInfo->pHostEvents,
+ pFBInfo->fDefaultFormat,
+ pFBInfo->fVBVAEnabled,
+ pFBInfo->fVBVAForceResize,
+ pFBInfo->pVBVAHostFlags
+ ));
+}
+#endif /* DEBUG_sunlover */
+
+DECLCALLBACK(int) Display::i_displayVBVAResize(PPDMIDISPLAYCONNECTOR pInterface, PCVBVAINFOVIEW pView,
+ PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping)
+{
+ LogRelFlowFunc(("pScreen %p, pvVRAM %p\n", pScreen, pvVRAM));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ return pThis->processVBVAResize(pView, pScreen, pvVRAM, fResetInputMapping);
+}
+
+int Display::processVBVAResize(PCVBVAINFOVIEW pView, PCVBVAINFOSCREEN pScreen, void *pvVRAM, bool fResetInputMapping)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RT_NOREF(pView);
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[pScreen->u32ViewIndex];
+
+#ifdef DEBUG_sunlover
+ logVBVAResize(pView, pScreen, pFBInfo);
+#endif
+
+ if (pScreen->u16Flags & VBVA_SCREEN_F_DISABLED)
+ {
+ /* Ask the framebuffer to resize using a default format. The framebuffer will be black.
+ * So if the frontend does not support GuestMonitorChangedEventType_Disabled event,
+ * the VM window will be black. */
+ uint32_t u32Width = pFBInfo->w ? pFBInfo->w : 640;
+ uint32_t u32Height = pFBInfo->h ? pFBInfo->h : 480;
+ int32_t xOrigin = pFBInfo->xOrigin;
+ int32_t yOrigin = pFBInfo->yOrigin;
+
+ alock.release();
+
+ i_handleDisplayResize(pScreen->u32ViewIndex, 0, (uint8_t *)NULL, 0,
+ u32Width, u32Height, pScreen->u16Flags, xOrigin, yOrigin, false);
+
+ return VINF_SUCCESS;
+ }
+
+ VBVAINFOSCREEN screenInfo;
+ RT_ZERO(screenInfo);
+
+ if (pScreen->u16Flags & VBVA_SCREEN_F_BLANK2)
+ {
+ /* Init a local VBVAINFOSCREEN structure, which will be used instead of
+ * the original pScreen. Set VBVA_SCREEN_F_BLANK, which will force
+ * the code below to choose the "blanking" branches.
+ */
+ screenInfo.u32ViewIndex = pScreen->u32ViewIndex;
+ screenInfo.i32OriginX = pFBInfo->xOrigin;
+ screenInfo.i32OriginY = pFBInfo->yOrigin;
+ screenInfo.u32StartOffset = 0; /* Irrelevant */
+ screenInfo.u32LineSize = pFBInfo->u32LineSize;
+ screenInfo.u32Width = pFBInfo->w;
+ screenInfo.u32Height = pFBInfo->h;
+ screenInfo.u16BitsPerPixel = pFBInfo->u16BitsPerPixel;
+ screenInfo.u16Flags = pScreen->u16Flags | VBVA_SCREEN_F_BLANK;
+
+ pScreen = &screenInfo;
+ }
+
+ if (fResetInputMapping)
+ {
+ /// @todo Rename to m* and verify whether some kind of lock is required.
+ xInputMappingOrigin = 0;
+ yInputMappingOrigin = 0;
+ cxInputMapping = 0;
+ cyInputMapping = 0;
+ }
+
+ alock.release();
+
+ return i_handleDisplayResize(pScreen->u32ViewIndex, pScreen->u16BitsPerPixel,
+ (uint8_t *)pvVRAM + pScreen->u32StartOffset,
+ pScreen->u32LineSize, pScreen->u32Width, pScreen->u32Height, pScreen->u16Flags,
+ pScreen->i32OriginX, pScreen->i32OriginY, false);
+}
+
+DECLCALLBACK(int) Display::i_displayVBVAMousePointerShape(PPDMIDISPLAYCONNECTOR pInterface, bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t cx, uint32_t cy,
+ const void *pvShape)
+{
+ LogFlowFunc(("\n"));
+ LogRel2(("%s: fVisible=%RTbool\n", __PRETTY_FUNCTION__, fVisible));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+
+ uint32_t cbShape = 0;
+ if (pvShape)
+ {
+ cbShape = (cx + 7) / 8 * cy; /* size of the AND mask */
+ cbShape = ((cbShape + 3) & ~3) + cx * 4 * cy; /* + gap + size of the XOR mask */
+ }
+
+ /* Tell the console about it */
+ pDrv->pDisplay->mParent->i_onMousePointerShapeChange(fVisible, fAlpha,
+ xHot, yHot, cx, cy, (uint8_t *)pvShape, cbShape);
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAGuestCapabilityUpdate(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fCapabilities)
+{
+ LogFlowFunc(("\n"));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ pThis->i_handleUpdateGuestVBVACapabilities(fCapabilities);
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAInputMappingUpdate(PPDMIDISPLAYCONNECTOR pInterface, int32_t xOrigin, int32_t yOrigin,
+ uint32_t cx, uint32_t cy)
+{
+ LogFlowFunc(("\n"));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ pThis->i_handleUpdateVBVAInputMapping(xOrigin, yOrigin, cx, cy);
+}
+
+DECLCALLBACK(void) Display::i_displayVBVAReportCursorPosition(PPDMIDISPLAYCONNECTOR pInterface, uint32_t fFlags, uint32_t aScreenId, uint32_t x, uint32_t y)
+{
+ LogFlowFunc(("\n"));
+ LogRel2(("%s: fFlags=%RU32, aScreenId=%RU32, x=%RU32, y=%RU32\n",
+ __PRETTY_FUNCTION__, fFlags, aScreenId, x, y));
+
+ PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
+ Display *pThis = pDrv->pDisplay;
+
+ if (fFlags & VBVA_CURSOR_SCREEN_RELATIVE)
+ {
+ AssertReturnVoid(aScreenId < pThis->mcMonitors);
+
+ x += pThis->maFramebuffers[aScreenId].xOrigin;
+ y += pThis->maFramebuffers[aScreenId].yOrigin;
+ }
+ ::FireCursorPositionChangedEvent(pThis->mParent->i_getEventSource(), RT_BOOL(fFlags & VBVA_CURSOR_VALID_DATA), x, y);
+}
+
+#endif /* VBOX_WITH_HGSMI */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Display::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINDISPLAY pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIDISPLAYCONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff,
+ * Tries to ensure no client calls gets to HGCM or the VGA device from here on.}
+ */
+DECLCALLBACK(void) Display::i_drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Do much of the work that i_drvDestruct does.
+ */
+ if (pThis->pUpPort)
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+
+ pThis->IConnector.pbData = NULL;
+ pThis->IConnector.cbScanline = 0;
+ pThis->IConnector.cBits = 32;
+ pThis->IConnector.cx = 0;
+ pThis->IConnector.cy = 0;
+
+ if (pThis->pDisplay)
+ {
+ AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_WITH_RECORDING
+ pThis->pDisplay->mParent->i_recordingStop();
+#endif
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+ }
+}
+
+
+/**
+ * Destruct a display driver instance.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Display::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * We repeat much of what i_drvPowerOff does in case it wasn't called.
+ * In addition we sever the connection between us and the display.
+ */
+ if (pThis->pUpPort)
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+
+ pThis->IConnector.pbData = NULL;
+ pThis->IConnector.cbScanline = 0;
+ pThis->IConnector.cBits = 32;
+ pThis->IConnector.cx = 0;
+ pThis->IConnector.cy = 0;
+
+ if (pThis->pDisplay)
+ {
+ AutoWriteLock displayLock(pThis->pDisplay COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_WITH_RECORDING
+ pThis->pDisplay->mParent->i_recordingStop();
+#endif
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+
+ pThis->pDisplay->mpDrv = NULL;
+ pThis->pDisplay = NULL;
+ }
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = NULL;
+#endif
+}
+
+
+/**
+ * Construct a display driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Display::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINDISPLAY pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINDISPLAY);
+ LogRelFlowFunc(("iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init Interfaces.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Display::i_drvQueryInterface;
+
+ pThis->IConnector.pfnResize = Display::i_displayResizeCallback;
+ pThis->IConnector.pfnUpdateRect = Display::i_displayUpdateCallback;
+ pThis->IConnector.pfnRefresh = Display::i_displayRefreshCallback;
+ pThis->IConnector.pfnReset = Display::i_displayResetCallback;
+ pThis->IConnector.pfnLFBModeChange = Display::i_displayLFBModeChangeCallback;
+ pThis->IConnector.pfnProcessAdapterData = Display::i_displayProcessAdapterDataCallback;
+ pThis->IConnector.pfnProcessDisplayData = Display::i_displayProcessDisplayDataCallback;
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ pThis->IConnector.pfnVHWACommandProcess = Display::i_displayVHWACommandProcess;
+#endif
+#ifdef VBOX_WITH_HGSMI
+ pThis->IConnector.pfnVBVAEnable = Display::i_displayVBVAEnable;
+ pThis->IConnector.pfnVBVADisable = Display::i_displayVBVADisable;
+ pThis->IConnector.pfnVBVAUpdateBegin = Display::i_displayVBVAUpdateBegin;
+ pThis->IConnector.pfnVBVAUpdateProcess = Display::i_displayVBVAUpdateProcess;
+ pThis->IConnector.pfnVBVAUpdateEnd = Display::i_displayVBVAUpdateEnd;
+ pThis->IConnector.pfnVBVAResize = Display::i_displayVBVAResize;
+ pThis->IConnector.pfnVBVAMousePointerShape = Display::i_displayVBVAMousePointerShape;
+ pThis->IConnector.pfnVBVAGuestCapabilityUpdate = Display::i_displayVBVAGuestCapabilityUpdate;
+ pThis->IConnector.pfnVBVAInputMappingUpdate = Display::i_displayVBVAInputMappingUpdate;
+ pThis->IConnector.pfnVBVAReportCursorPosition = Display::i_displayVBVAReportCursorPosition;
+#endif
+ pThis->IConnector.pfn3DNotifyProcess = Display::i_display3DNotifyProcess;
+
+ /*
+ * Get the IDisplayPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYPORT);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No display port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+#if defined(VBOX_WITH_VIDEOHWACCEL)
+ pThis->pVBVACallbacks = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIDISPLAYVBVACALLBACKS);
+ if (!pThis->pVBVACallbacks)
+ {
+ AssertMsgFailed(("Configuration error: No VBVA callback interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+#endif
+ /*
+ * Get the Display object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IDisplay));
+ IDisplay *pIDisplay = (IDisplay *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIDisplay)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pDisplay = static_cast<Display *>(pIDisplay);
+ pThis->pDisplay->mpDrv = pThis;
+
+ /* Disable VRAM to a buffer copy initially. */
+ pThis->pUpPort->pfnSetRenderVRAM(pThis->pUpPort, false);
+ pThis->IConnector.cBits = 32; /* DevVGA does nothing otherwise. */
+
+ /*
+ * Start periodic screen refreshes
+ */
+ pThis->pUpPort->pfnSetRefreshRate(pThis->pUpPort, 20);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Display driver registration record.
+ */
+const PDMDRVREG Display::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainDisplay",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main display driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_DISPLAY,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINDISPLAY),
+ /* pfnConstruct */
+ Display::i_drvConstruct,
+ /* pfnDestruct */
+ Display::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ Display::i_drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp
new file mode 100644
index 00000000..ecba1355
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp
@@ -0,0 +1,1018 @@
+/* $Id: DisplayImplLegacy.cpp $ */
+/** @file
+ * VirtualBox IDisplay implementation, helpers for legacy GAs.
+ *
+ * Methods and helpers to support old Guest Additions 3.x or older.
+ * This is not used by the current Guest Additions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "VMMDev.h"
+#include <VBox/VMMDev.h>
+
+/* generated header */
+#include "VBoxEvents.h"
+
+
+int videoAccelConstruct(VIDEOACCEL *pVideoAccel)
+{
+ pVideoAccel->pVbvaMemory = NULL;
+ pVideoAccel->fVideoAccelEnabled = false;
+
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+
+ pVideoAccel->hXRoadsVideoAccel = NIL_RTSEMXROADS;
+ int vrc = RTSemXRoadsCreate(&pVideoAccel->hXRoadsVideoAccel);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+void videoAccelDestroy(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsDestroy(pVideoAccel->hXRoadsVideoAccel);
+ RT_ZERO(*pVideoAccel);
+}
+
+static unsigned mapCoordsToScreen(DISPLAYFBINFO *pInfos, unsigned cInfos, int *px, int *py, int *pw, int *ph)
+{
+ RT_NOREF(pw, ph);
+
+ DISPLAYFBINFO *pInfo = pInfos;
+ unsigned uScreenId;
+ Log9(("mapCoordsToScreen: %d,%d %dx%d\n", *px, *py, *pw, *ph));
+ for (uScreenId = 0; uScreenId < cInfos; uScreenId++, pInfo++)
+ {
+ Log9((" [%d] %d,%d %dx%d\n", uScreenId, pInfo->xOrigin, pInfo->yOrigin, pInfo->w, pInfo->h));
+ if ( (pInfo->xOrigin <= *px && *px < pInfo->xOrigin + (int)pInfo->w)
+ && (pInfo->yOrigin <= *py && *py < pInfo->yOrigin + (int)pInfo->h))
+ {
+ /* The rectangle belongs to the screen. Correct coordinates. */
+ *px -= pInfo->xOrigin;
+ *py -= pInfo->yOrigin;
+ Log9((" -> %d,%d", *px, *py));
+ break;
+ }
+ }
+ if (uScreenId == cInfos)
+ {
+ /* Map to primary screen. */
+ uScreenId = 0;
+ }
+ Log9((" scr %d\n", uScreenId));
+ return uScreenId;
+}
+
+
+typedef struct _VBVADIRTYREGION
+{
+ /* Copies of object's pointers used by vbvaRgn functions. */
+ DISPLAYFBINFO *paFramebuffers;
+ unsigned cMonitors;
+ Display *pDisplay;
+ PPDMIDISPLAYPORT pPort;
+
+ /* The rectangle that includes all dirty rectangles. */
+ RTRECT aDirtyRects[SchemaDefs::MaxGuestMonitors];
+
+} VBVADIRTYREGION;
+
+static void vbvaRgnInit(VBVADIRTYREGION *prgn, DISPLAYFBINFO *paFramebuffers, unsigned cMonitors,
+ Display *pd, PPDMIDISPLAYPORT pp)
+{
+ prgn->paFramebuffers = paFramebuffers;
+ prgn->cMonitors = cMonitors;
+ prgn->pDisplay = pd;
+ prgn->pPort = pp;
+
+ RT_ZERO(prgn->aDirtyRects);
+}
+
+static void vbvaRgnDirtyRect(VBVADIRTYREGION *prgn, unsigned uScreenId, VBVACMDHDR *phdr)
+{
+ Log9(("x = %d, y = %d, w = %d, h = %d\n", phdr->x, phdr->y, phdr->w, phdr->h));
+
+ /*
+ * Here update rectangles are accumulated to form an update area.
+ */
+ /** @todo
+ * Now the simplest method is used which builds one rectangle that
+ * includes all update areas. A bit more advanced method can be
+ * employed here. The method should be fast however.
+ */
+ if (phdr->w == 0 || phdr->h == 0)
+ {
+ /* Empty rectangle. */
+ return;
+ }
+
+ int32_t xRight = phdr->x + phdr->w;
+ int32_t yBottom = phdr->y + phdr->h;
+
+ RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId];
+ DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId];
+
+ if (pDirtyRect->xRight == 0)
+ {
+ /* This is the first rectangle to be added. */
+ pDirtyRect->xLeft = phdr->x;
+ pDirtyRect->yTop = phdr->y;
+ pDirtyRect->xRight = xRight;
+ pDirtyRect->yBottom = yBottom;
+ }
+ else
+ {
+ /* Adjust region coordinates. */
+ if (pDirtyRect->xLeft > phdr->x)
+ {
+ pDirtyRect->xLeft = phdr->x;
+ }
+
+ if (pDirtyRect->yTop > phdr->y)
+ {
+ pDirtyRect->yTop = phdr->y;
+ }
+
+ if (pDirtyRect->xRight < xRight)
+ {
+ pDirtyRect->xRight = xRight;
+ }
+
+ if (pDirtyRect->yBottom < yBottom)
+ {
+ pDirtyRect->yBottom = yBottom;
+ }
+ }
+
+ if (pFBInfo->fDefaultFormat)
+ {
+ /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer
+ prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, phdr->x, phdr->y, phdr->w, phdr->h);
+ prgn->pDisplay->i_handleDisplayUpdate(uScreenId, phdr->x, phdr->y, phdr->w, phdr->h);
+ }
+
+ return;
+}
+
+static void vbvaRgnUpdateFramebuffer(VBVADIRTYREGION *prgn, unsigned uScreenId)
+{
+ RTRECT *pDirtyRect = &prgn->aDirtyRects[uScreenId];
+ DISPLAYFBINFO *pFBInfo = &prgn->paFramebuffers[uScreenId];
+
+ uint32_t w = pDirtyRect->xRight - pDirtyRect->xLeft;
+ uint32_t h = pDirtyRect->yBottom - pDirtyRect->yTop;
+
+ if (!pFBInfo->fDefaultFormat && w != 0 && h != 0)
+ {
+ /// @todo pfnUpdateDisplayRect must take the vram offset parameter for the framebuffer
+ prgn->pPort->pfnUpdateDisplayRect(prgn->pPort, pDirtyRect->xLeft, pDirtyRect->yTop, w, h);
+ prgn->pDisplay->i_handleDisplayUpdate(uScreenId, pDirtyRect->xLeft, pDirtyRect->yTop, w, h);
+ }
+}
+
+void i_vbvaSetMemoryFlags(VBVAMEMORY *pVbvaMemory,
+ bool fVideoAccelEnabled,
+ bool fVideoAccelVRDP,
+ uint32_t fu32SupportedOrders,
+ DISPLAYFBINFO *paFBInfos,
+ unsigned cFBInfos)
+{
+ if (pVbvaMemory)
+ {
+ /* This called only on changes in mode. So reset VRDP always. */
+ uint32_t fu32Flags = VBVA_F_MODE_VRDP_RESET;
+
+ if (fVideoAccelEnabled)
+ {
+ fu32Flags |= VBVA_F_MODE_ENABLED;
+
+ if (fVideoAccelVRDP)
+ {
+ fu32Flags |= VBVA_F_MODE_VRDP | VBVA_F_MODE_VRDP_ORDER_MASK;
+
+ pVbvaMemory->fu32SupportedOrders = fu32SupportedOrders;
+ }
+ }
+
+ pVbvaMemory->fu32ModeFlags = fu32Flags;
+ }
+
+ unsigned uScreenId;
+ for (uScreenId = 0; uScreenId < cFBInfos; uScreenId++)
+ {
+ if (paFBInfos[uScreenId].pHostEvents)
+ {
+ paFBInfos[uScreenId].pHostEvents->fu32Events |= VBOX_VIDEO_INFO_HOST_EVENTS_F_VRDP_RESET;
+ }
+ }
+}
+
+bool Display::i_VideoAccelAllowed(void)
+{
+ return true;
+}
+
+int videoAccelEnterVGA(VIDEOACCEL *pVideoAccel)
+{
+ return RTSemXRoadsNSEnter(pVideoAccel->hXRoadsVideoAccel);
+}
+
+void videoAccelLeaveVGA(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsNSLeave(pVideoAccel->hXRoadsVideoAccel);
+}
+
+int videoAccelEnterVMMDev(VIDEOACCEL *pVideoAccel)
+{
+ return RTSemXRoadsEWEnter(pVideoAccel->hXRoadsVideoAccel);
+}
+
+void videoAccelLeaveVMMDev(VIDEOACCEL *pVideoAccel)
+{
+ RTSemXRoadsEWLeave(pVideoAccel->hXRoadsVideoAccel);
+}
+
+/**
+ * @thread EMT
+ */
+int Display::i_VideoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort)
+{
+ LogRelFlowFunc(("fEnable = %d\n", fEnable));
+
+ int vrc = i_videoAccelEnable(fEnable, pVbvaMemory, pUpPort);
+
+ LogRelFlowFunc(("%Rrc.\n", vrc));
+ return vrc;
+}
+
+int Display::i_videoAccelEnable(bool fEnable, VBVAMEMORY *pVbvaMemory, PPDMIDISPLAYPORT pUpPort)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ /* Called each time the guest wants to use acceleration,
+ * or when the VGA device disables acceleration,
+ * or when restoring the saved state with accel enabled.
+ *
+ * VGA device disables acceleration on each video mode change
+ * and on reset.
+ *
+ * Guest enabled acceleration at will. And it has to enable
+ * acceleration after a mode change.
+ */
+ LogRelFlowFunc(("mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n",
+ pVideoAccel->fVideoAccelEnabled, fEnable, pVbvaMemory));
+
+ /* Strictly check parameters. Callers must not pass anything in the case. */
+ Assert((fEnable && pVbvaMemory) || (!fEnable && pVbvaMemory == NULL));
+
+ if (!i_VideoAccelAllowed ())
+ return VERR_NOT_SUPPORTED;
+
+ /* Check that current status is not being changed */
+ if (pVideoAccel->fVideoAccelEnabled == fEnable)
+ return VINF_SUCCESS;
+
+ if (pVideoAccel->fVideoAccelEnabled)
+ {
+ /* Process any pending orders and empty the VBVA ring buffer. */
+ i_videoAccelFlush (pUpPort);
+ }
+
+ if (!fEnable && pVideoAccel->pVbvaMemory)
+ pVideoAccel->pVbvaMemory->fu32ModeFlags &= ~VBVA_F_MODE_ENABLED;
+
+ if (fEnable)
+ {
+ /* Process any pending VGA device changes, resize. */
+ pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false);
+ }
+
+ /* Protect the videoaccel state transition. */
+ RTCritSectEnter(&mVideoAccelLock);
+
+ if (fEnable)
+ {
+ /* Initialize the hardware memory. */
+ i_vbvaSetMemoryFlags(pVbvaMemory, true, mfVideoAccelVRDP,
+ mfu32SupportedOrders, maFramebuffers, mcMonitors);
+ pVbvaMemory->off32Data = 0;
+ pVbvaMemory->off32Free = 0;
+
+ memset(pVbvaMemory->aRecords, 0, sizeof(pVbvaMemory->aRecords));
+ pVbvaMemory->indexRecordFirst = 0;
+ pVbvaMemory->indexRecordFree = 0;
+
+ pVideoAccel->pVbvaMemory = pVbvaMemory;
+ pVideoAccel->fVideoAccelEnabled = true;
+
+ LogRel(("VBVA: Enabled.\n"));
+ }
+ else
+ {
+ pVideoAccel->pVbvaMemory = NULL;
+ pVideoAccel->fVideoAccelEnabled = false;
+
+ LogRel(("VBVA: Disabled.\n"));
+ }
+
+ RTCritSectLeave(&mVideoAccelLock);
+
+ if (!fEnable)
+ {
+ pUpPort->pfnUpdateDisplayAll(pUpPort, /* fFailOnResize = */ false);
+ }
+
+ /* Notify the VMMDev, which saves VBVA status in the saved state,
+ * and needs to know current status.
+ */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnVBVAChange(pVMMDevPort, fEnable);
+ }
+
+ LogRelFlowFunc(("VINF_SUCCESS.\n"));
+ return VINF_SUCCESS;
+}
+
+static bool i_vbvaVerifyRingBuffer(VBVAMEMORY *pVbvaMemory)
+{
+ RT_NOREF(pVbvaMemory);
+ return true;
+}
+
+static void i_vbvaFetchBytes(VBVAMEMORY *pVbvaMemory, uint8_t *pu8Dst, uint32_t cbDst)
+{
+ if (cbDst >= VBVA_RING_BUFFER_SIZE)
+ {
+ AssertMsgFailed(("cbDst = 0x%08X, ring buffer size 0x%08X\n", cbDst, VBVA_RING_BUFFER_SIZE));
+ return;
+ }
+
+ uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
+ uint8_t *src = &pVbvaMemory->au8RingBuffer[pVbvaMemory->off32Data];
+ int32_t i32Diff = cbDst - u32BytesTillBoundary;
+
+ if (i32Diff <= 0)
+ {
+ /* Chunk will not cross buffer boundary. */
+ memcpy (pu8Dst, src, cbDst);
+ }
+ else
+ {
+ /* Chunk crosses buffer boundary. */
+ memcpy(pu8Dst, src, u32BytesTillBoundary);
+ memcpy(pu8Dst + u32BytesTillBoundary, &pVbvaMemory->au8RingBuffer[0], i32Diff);
+ }
+
+ /* Advance data offset. */
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbDst) % VBVA_RING_BUFFER_SIZE;
+
+ return;
+}
+
+
+static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory)
+{
+ uint8_t *pu8New;
+
+ LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n",
+ *ppu8, *pcb, cbRecord));
+
+ if (*ppu8)
+ {
+ Assert (*pcb);
+ pu8New = (uint8_t *)RTMemRealloc(*ppu8, cbRecord);
+ }
+ else
+ {
+ Assert (!*pcb);
+ pu8New = (uint8_t *)RTMemAlloc(cbRecord);
+ }
+
+ if (!pu8New)
+ {
+ /* Memory allocation failed, fail the function. */
+ Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n",
+ cbRecord));
+
+ if (*ppu8)
+ {
+ RTMemFree(*ppu8);
+ }
+
+ *ppu8 = NULL;
+ *pcb = 0;
+
+ return false;
+ }
+
+ /* Fetch data from the ring buffer. */
+ i_vbvaFetchBytes(pVbvaMemory, pu8New + *pcb, cbRecord - *pcb);
+
+ *ppu8 = pu8New;
+ *pcb = cbRecord;
+
+ return true;
+}
+
+/* For contiguous chunks just return the address in the buffer.
+ * For crossing boundary - allocate a buffer from heap.
+ */
+static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t *pcbCmd)
+{
+ VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory;
+
+ uint32_t indexRecordFirst = pVbvaMemory->indexRecordFirst;
+ uint32_t indexRecordFree = pVbvaMemory->indexRecordFree;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("first = %d, free = %d\n",
+ indexRecordFirst, indexRecordFree));
+#endif /* DEBUG_sunlover */
+
+ if (!i_vbvaVerifyRingBuffer(pVbvaMemory))
+ {
+ return false;
+ }
+
+ if (indexRecordFirst == indexRecordFree)
+ {
+ /* No records to process. Return without assigning output variables. */
+ return true;
+ }
+
+ uint32_t cbRecordCurrent = ASMAtomicReadU32(&pVbvaMemory->aRecords[indexRecordFirst].cbRecord);
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("cbRecord = 0x%08X\n", cbRecordCurrent));
+#endif /* DEBUG_sunlover */
+
+ uint32_t cbRecord = cbRecordCurrent & ~VBVA_F_RECORD_PARTIAL;
+
+ if (pVideoAccel->cbVbvaPartial)
+ {
+ /* There is a partial read in process. Continue with it. */
+
+ Assert(pVideoAccel->pu8VbvaPartial);
+
+ LogFlowFunc(("continue partial record cbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n",
+ pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree));
+
+ if (cbRecord > pVideoAccel->cbVbvaPartial)
+ {
+ /* New data has been added to the record. */
+ if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory))
+ {
+ return false;
+ }
+ }
+
+ if (!(cbRecordCurrent & VBVA_F_RECORD_PARTIAL))
+ {
+ /* The record is completed by guest. Return it to the caller. */
+ *ppHdr = (VBVACMDHDR *)pVideoAccel->pu8VbvaPartial;
+ *pcbCmd = pVideoAccel->cbVbvaPartial;
+
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+
+ /* Advance the record index. */
+ pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("partial done ok, data = %d, free = %d\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover */
+ }
+
+ return true;
+ }
+
+ /* A new record need to be processed. */
+ if (cbRecordCurrent & VBVA_F_RECORD_PARTIAL)
+ {
+ /* Current record is being written by guest. '=' is important here. */
+ if (cbRecord >= VBVA_RING_BUFFER_SIZE - VBVA_RING_BUFFER_THRESHOLD)
+ {
+ /* Partial read must be started. */
+ if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, pVbvaMemory))
+ {
+ return false;
+ }
+
+ LogFlowFunc(("started partial record cbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n",
+ pVideoAccel->cbVbvaPartial, cbRecordCurrent, indexRecordFirst, indexRecordFree));
+ }
+
+ return true;
+ }
+
+ /* Current record is complete. If it is not empty, process it. */
+ if (cbRecord)
+ {
+ /* The size of largest contiguous chunk in the ring biffer. */
+ uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
+
+ /* The ring buffer pointer. */
+ uint8_t *au8RingBuffer = &pVbvaMemory->au8RingBuffer[0];
+
+ /* The pointer to data in the ring buffer. */
+ uint8_t *src = &au8RingBuffer[pVbvaMemory->off32Data];
+
+ /* Fetch or point the data. */
+ if (u32BytesTillBoundary >= cbRecord)
+ {
+ /* The command does not cross buffer boundary. Return address in the buffer. */
+ *ppHdr = (VBVACMDHDR *)src;
+
+ /* Advance data offset. */
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
+ }
+ else
+ {
+ /* The command crosses buffer boundary. Rare case, so not optimized. */
+ uint8_t *dst = (uint8_t *)RTMemAlloc(cbRecord);
+
+ if (!dst)
+ {
+ LogRelFlowFunc(("could not allocate %d bytes from heap!!!\n", cbRecord));
+ pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
+ return false;
+ }
+
+ i_vbvaFetchBytes(pVbvaMemory, dst, cbRecord);
+
+ *ppHdr = (VBVACMDHDR *)dst;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("Allocated from heap %p\n", dst));
+#endif /* DEBUG_sunlover */
+ }
+ }
+
+ *pcbCmd = cbRecord;
+
+ /* Advance the record index. */
+ pVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("done ok, data = %d, free = %d\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover */
+
+ return true;
+}
+
+static void i_vbvaReleaseCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR *pHdr, int32_t cbCmd)
+{
+ RT_NOREF(cbCmd);
+ uint8_t *au8RingBuffer = pVideoAccel->pVbvaMemory->au8RingBuffer;
+
+ if ( (uint8_t *)pHdr >= au8RingBuffer
+ && (uint8_t *)pHdr < &au8RingBuffer[VBVA_RING_BUFFER_SIZE])
+ {
+ /* The pointer is inside ring buffer. Must be continuous chunk. */
+ Assert(VBVA_RING_BUFFER_SIZE - ((uint8_t *)pHdr - au8RingBuffer) >= cbCmd);
+
+ /* Do nothing. */
+
+ Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0);
+ }
+ else
+ {
+ /* The pointer is outside. It is then an allocated copy. */
+
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("Free heap %p\n", pHdr));
+#endif /* DEBUG_sunlover */
+
+ if ((uint8_t *)pHdr == pVideoAccel->pu8VbvaPartial)
+ {
+ pVideoAccel->pu8VbvaPartial = NULL;
+ pVideoAccel->cbVbvaPartial = 0;
+ }
+ else
+ {
+ Assert(!pVideoAccel->pu8VbvaPartial && pVideoAccel->cbVbvaPartial == 0);
+ }
+
+ RTMemFree(pHdr);
+ }
+
+ return;
+}
+
+
+/**
+ * Called regularly on the DisplayRefresh timer.
+ * Also on behalf of guest, when the ring buffer is full.
+ *
+ * @thread EMT
+ */
+void Display::i_VideoAccelFlush(PPDMIDISPLAYPORT pUpPort)
+{
+ int vrc = i_videoAccelFlush(pUpPort);
+ if (RT_FAILURE(vrc))
+ {
+ /* Disable on errors. */
+ i_videoAccelEnable(false, NULL, pUpPort);
+ }
+}
+
+int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort)
+{
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+ VBVAMEMORY *pVbvaMemory = pVideoAccel->pVbvaMemory;
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("fVideoAccelEnabled = %d\n", pVideoAccel->fVideoAccelEnabled));
+#endif /* DEBUG_sunlover_2 */
+
+ if (!pVideoAccel->fVideoAccelEnabled)
+ {
+ Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n"));
+ return VINF_SUCCESS;
+ }
+
+ /* Here VBVA is enabled and we have the accelerator memory pointer. */
+ Assert(pVbvaMemory);
+
+#ifdef DEBUG_sunlover_2
+ LogFlowFunc(("indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n",
+ pVbvaMemory->indexRecordFirst, pVbvaMemory->indexRecordFree,
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+#endif /* DEBUG_sunlover_2 */
+
+ /* Quick check for "nothing to update" case. */
+ if (pVbvaMemory->indexRecordFirst == pVbvaMemory->indexRecordFree)
+ {
+ return VINF_SUCCESS;
+ }
+
+ /* Process the ring buffer */
+ unsigned uScreenId;
+
+ /* Initialize dirty rectangles accumulator. */
+ VBVADIRTYREGION rgn;
+ vbvaRgnInit(&rgn, maFramebuffers, mcMonitors, this, pUpPort);
+
+ for (;;)
+ {
+ VBVACMDHDR *phdr = NULL;
+ uint32_t cbCmd = UINT32_MAX;
+
+ /* Fetch the command data. */
+ if (!i_vbvaFetchCmd(pVideoAccel, &phdr, &cbCmd))
+ {
+ Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n",
+ pVbvaMemory->off32Data, pVbvaMemory->off32Free));
+ return VERR_INVALID_STATE;
+ }
+
+ if (cbCmd == uint32_t(~0))
+ {
+ /* No more commands yet in the queue. */
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("no command\n"));
+#endif /* DEBUG_sunlover */
+ break;
+ }
+
+ if (cbCmd != 0)
+ {
+#ifdef DEBUG_sunlover
+ LogFlowFunc(("hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n",
+ cbCmd, phdr->x, phdr->y, phdr->w, phdr->h));
+#endif /* DEBUG_sunlover */
+
+ VBVACMDHDR hdrSaved = *phdr;
+
+ int x = phdr->x;
+ int y = phdr->y;
+ int w = phdr->w;
+ int h = phdr->h;
+
+ uScreenId = mapCoordsToScreen(maFramebuffers, mcMonitors, &x, &y, &w, &h);
+
+ phdr->x = (int16_t)x;
+ phdr->y = (int16_t)y;
+ phdr->w = (uint16_t)w;
+ phdr->h = (uint16_t)h;
+
+ /* Handle the command.
+ *
+ * Guest is responsible for updating the guest video memory.
+ * The Windows guest does all drawing using Eng*.
+ *
+ * For local output, only dirty rectangle information is used
+ * to update changed areas.
+ *
+ * Dirty rectangles are accumulated to exclude overlapping updates and
+ * group small updates to a larger one.
+ */
+
+ /* Accumulate the update. */
+ vbvaRgnDirtyRect(&rgn, uScreenId, phdr);
+
+ /* Forward the command to VRDP server. */
+ mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, phdr, cbCmd);
+
+ *phdr = hdrSaved;
+ }
+
+ i_vbvaReleaseCmd(pVideoAccel, phdr, cbCmd);
+ }
+
+ for (uScreenId = 0; uScreenId < mcMonitors; uScreenId++)
+ {
+ /* Draw the framebuffer. */
+ vbvaRgnUpdateFramebuffer(&rgn, uScreenId);
+ }
+ return VINF_SUCCESS;
+}
+
+int Display::i_videoAccelRefreshProcess(PPDMIDISPLAYPORT pUpPort)
+{
+ int vrc = VWRN_INVALID_STATE; /* Default is to do a display update in VGA device. */
+
+ VIDEOACCEL *pVideoAccel = &mVideoAccelLegacy;
+
+ videoAccelEnterVGA(pVideoAccel);
+
+ if (pVideoAccel->fVideoAccelEnabled)
+ {
+ Assert(pVideoAccel->pVbvaMemory);
+ vrc = i_videoAccelFlush(pUpPort);
+ if (RT_FAILURE(vrc))
+ {
+ /* Disable on errors. */
+ i_videoAccelEnable(false, NULL, pUpPort);
+ vrc = VWRN_INVALID_STATE; /* Do a display update in VGA device. */
+ }
+ else
+ {
+ vrc = VINF_SUCCESS;
+ }
+ }
+
+ videoAccelLeaveVGA(pVideoAccel);
+
+ return vrc;
+}
+
+void Display::processAdapterData(void *pvVRAM, uint32_t u32VRAMSize)
+{
+ RT_NOREF(u32VRAMSize);
+ if (pvVRAM == NULL)
+ {
+ unsigned i;
+ for (i = 0; i < mcMonitors; i++)
+ {
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[i];
+
+ pFBInfo->u32Offset = 0;
+ pFBInfo->u32MaxFramebufferSize = 0;
+ pFBInfo->u32InformationSize = 0;
+ }
+ }
+#ifndef VBOX_WITH_HGSMI
+ else
+ {
+ uint8_t *pu8 = (uint8_t *)pvVRAM;
+ pu8 += u32VRAMSize - VBOX_VIDEO_ADAPTER_INFORMATION_SIZE;
+
+ /// @todo
+ uint8_t *pu8End = pu8 + VBOX_VIDEO_ADAPTER_INFORMATION_SIZE;
+
+ VBOXVIDEOINFOHDR *pHdr;
+
+ for (;;)
+ {
+ pHdr = (VBOXVIDEOINFOHDR *)pu8;
+ pu8 += sizeof(VBOXVIDEOINFOHDR);
+
+ if (pu8 >= pu8End)
+ {
+ LogRel(("VBoxVideo: Guest adapter information overflow!!!\n"));
+ break;
+ }
+
+ if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_DISPLAY)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFODISPLAY))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "DISPLAY", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFODISPLAY *pDisplay = (VBOXVIDEOINFODISPLAY *)pu8;
+
+ if (pDisplay->u32Index >= mcMonitors)
+ {
+ LogRel(("VBoxVideo: Guest adapter information invalid display index %d!!!\n", pDisplay->u32Index));
+ break;
+ }
+
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[pDisplay->u32Index];
+
+ pFBInfo->u32Offset = pDisplay->u32Offset;
+ pFBInfo->u32MaxFramebufferSize = pDisplay->u32FramebufferSize;
+ pFBInfo->u32InformationSize = pDisplay->u32InformationSize;
+
+ LogRelFlow(("VBOX_VIDEO_INFO_TYPE_DISPLAY: %d: at 0x%08X, size 0x%08X, info 0x%08X\n", pDisplay->u32Index,
+ pDisplay->u32Offset, pDisplay->u32FramebufferSize, pDisplay->u32InformationSize));
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_QUERY_CONF32)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOQUERYCONF32))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "CONF32", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOQUERYCONF32 *pConf32 = (VBOXVIDEOINFOQUERYCONF32 *)pu8;
+
+ switch (pConf32->u32Index)
+ {
+ case VBOX_VIDEO_QCI32_MONITOR_COUNT:
+ {
+ pConf32->u32Value = mcMonitors;
+ } break;
+
+ case VBOX_VIDEO_QCI32_OFFSCREEN_HEAP_SIZE:
+ {
+ /** @todo make configurable. */
+ pConf32->u32Value = _1M;
+ } break;
+
+ default:
+ LogRel(("VBoxVideo: CONF32 %d not supported!!! Skipping.\n", pConf32->u32Index));
+ }
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END)
+ {
+ if (pHdr->u16Length != 0)
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length));
+ break;
+ }
+
+ break;
+ }
+ else if (pHdr->u8Type != VBOX_VIDEO_INFO_TYPE_NV_HEAP)
+ {
+ /** @todo why is Additions/WINNT/Graphics/Miniport/VBoxVideo. cpp pushing this to us? */
+ LogRel(("Guest adapter information contains unsupported type %d. The block has been skipped.\n", pHdr->u8Type));
+ }
+
+ pu8 += pHdr->u16Length;
+ }
+ }
+#endif /* !VBOX_WITH_HGSMI */
+}
+
+void Display::processDisplayData(void *pvVRAM, unsigned uScreenId)
+{
+ if (uScreenId >= mcMonitors)
+ {
+ LogRel(("VBoxVideo: Guest display information invalid display index %d!!!\n", uScreenId));
+ return;
+ }
+
+ /* Get the display information structure. */
+ DISPLAYFBINFO *pFBInfo = &maFramebuffers[uScreenId];
+
+ uint8_t *pu8 = (uint8_t *)pvVRAM;
+ pu8 += pFBInfo->u32Offset + pFBInfo->u32MaxFramebufferSize;
+
+ /// @todo
+ uint8_t *pu8End = pu8 + pFBInfo->u32InformationSize;
+
+ VBOXVIDEOINFOHDR *pHdr;
+
+ for (;;)
+ {
+ pHdr = (VBOXVIDEOINFOHDR *)pu8;
+ pu8 += sizeof(VBOXVIDEOINFOHDR);
+
+ if (pu8 >= pu8End)
+ {
+ LogRel(("VBoxVideo: Guest display information overflow!!!\n"));
+ break;
+ }
+
+ if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_SCREEN)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOSCREEN))
+ {
+ LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "SCREEN", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOSCREEN *pScreen = (VBOXVIDEOINFOSCREEN *)pu8;
+
+ pFBInfo->xOrigin = pScreen->xOrigin;
+ pFBInfo->yOrigin = pScreen->yOrigin;
+
+ pFBInfo->w = pScreen->u16Width;
+ pFBInfo->h = pScreen->u16Height;
+
+ LogRelFlow(("VBOX_VIDEO_INFO_TYPE_SCREEN: (%p) %d: at %d,%d, linesize 0x%X, size %dx%d, bpp %d, flags 0x%02X\n",
+ pHdr, uScreenId, pScreen->xOrigin, pScreen->yOrigin, pScreen->u32LineSize, pScreen->u16Width,
+ pScreen->u16Height, pScreen->bitsPerPixel, pScreen->u8Flags));
+
+ if (uScreenId != VBOX_VIDEO_PRIMARY_SCREEN)
+ {
+ /* Primary screen resize is eeeeeeeee by the VGA device. */
+ if (pFBInfo->fDisabled)
+ {
+ pFBInfo->fDisabled = false;
+ ::FireGuestMonitorChangedEvent(mParent->i_getEventSource(), GuestMonitorChangedEventType_Enabled, uScreenId,
+ pFBInfo->xOrigin, pFBInfo->yOrigin, pFBInfo->w, pFBInfo->h);
+ }
+
+ i_handleDisplayResize(uScreenId, pScreen->bitsPerPixel,
+ (uint8_t *)pvVRAM + pFBInfo->u32Offset,
+ pScreen->u32LineSize,
+ pScreen->u16Width, pScreen->u16Height,
+ VBVA_SCREEN_F_ACTIVE,
+ pScreen->xOrigin, pScreen->yOrigin, false);
+ }
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_END)
+ {
+ if (pHdr->u16Length != 0)
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "END", pHdr->u16Length));
+ break;
+ }
+
+ break;
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_HOST_EVENTS)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOHOSTEVENTS))
+ {
+ LogRel(("VBoxVideo: Guest display information %s invalid length %d!!!\n", "HOST_EVENTS", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOHOSTEVENTS *pHostEvents = (VBOXVIDEOINFOHOSTEVENTS *)pu8;
+
+ pFBInfo->pHostEvents = pHostEvents;
+
+ LogFlow(("VBOX_VIDEO_INFO_TYPE_HOSTEVENTS: (%p)\n",
+ pHostEvents));
+ }
+ else if (pHdr->u8Type == VBOX_VIDEO_INFO_TYPE_LINK)
+ {
+ if (pHdr->u16Length != sizeof(VBOXVIDEOINFOLINK))
+ {
+ LogRel(("VBoxVideo: Guest adapter information %s invalid length %d!!!\n", "LINK", pHdr->u16Length));
+ break;
+ }
+
+ VBOXVIDEOINFOLINK *pLink = (VBOXVIDEOINFOLINK *)pu8;
+ pu8 += pLink->i32Offset;
+ }
+ else
+ {
+ LogRel(("Guest display information contains unsupported type %d\n", pHdr->u8Type));
+ }
+
+ pu8 += pHdr->u16Length;
+ }
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp
new file mode 100644
index 00000000..0efa502c
--- /dev/null
+++ b/src/VBox/Main/src-client/DisplaySourceBitmapImpl.cpp
@@ -0,0 +1,196 @@
+/* $Id: DisplaySourceBitmapImpl.cpp $ */
+/** @file
+ * Bitmap of a guest screen implementation.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAYSOURCEBITMAP
+#include "LoggingNew.h"
+
+#include "DisplayImpl.h"
+
+/*
+ * DisplaySourceBitmap implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(DisplaySourceBitmap)
+
+HRESULT DisplaySourceBitmap::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void DisplaySourceBitmap::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT DisplaySourceBitmap::init(ComObjPtr<Display> pDisplay, unsigned uScreenId, DISPLAYFBINFO *pFBInfo)
+{
+ LogFlowThisFunc(("[%u]\n", uScreenId));
+
+ ComAssertRet(!pDisplay.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pDisplay = pDisplay;
+ m.uScreenId = uScreenId;
+ m.pFBInfo = pFBInfo;
+
+ m.pu8Allocated = NULL;
+
+ m.pu8Address = NULL;
+ m.ulWidth = 0;
+ m.ulHeight = 0;
+ m.ulBitsPerPixel = 0;
+ m.ulBytesPerLine = 0;
+ m.bitmapFormat = BitmapFormat_Opaque;
+
+ int vrc = initSourceBitmap(uScreenId, pFBInfo);
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void DisplaySourceBitmap::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("[%u]\n", m.uScreenId));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m.pDisplay.setNull();
+ RTMemFree(m.pu8Allocated);
+}
+
+HRESULT DisplaySourceBitmap::getScreenId(ULONG *aScreenId)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aScreenId = m.uScreenId;
+ return hrc;
+}
+
+HRESULT DisplaySourceBitmap::queryBitmapInfo(BYTE **aAddress,
+ ULONG *aWidth,
+ ULONG *aHeight,
+ ULONG *aBitsPerPixel,
+ ULONG *aBytesPerLine,
+ BitmapFormat_T *aBitmapFormat)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAddress = m.pu8Address;
+ *aWidth = m.ulWidth;
+ *aHeight = m.ulHeight;
+ *aBitsPerPixel = m.ulBitsPerPixel;
+ *aBytesPerLine = m.ulBytesPerLine;
+ *aBitmapFormat = m.bitmapFormat;
+
+ return hrc;
+}
+
+int DisplaySourceBitmap::initSourceBitmap(unsigned aScreenId,
+ DISPLAYFBINFO *pFBInfo)
+{
+ RT_NOREF(aScreenId);
+ int vrc = VINF_SUCCESS;
+
+ if (pFBInfo->w == 0 || pFBInfo->h == 0)
+ {
+ return VERR_NOT_SUPPORTED;
+ }
+
+ BYTE *pAddress = NULL;
+ ULONG ulWidth = 0;
+ ULONG ulHeight = 0;
+ ULONG ulBitsPerPixel = 0;
+ ULONG ulBytesPerLine = 0;
+ BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
+
+ if (pFBInfo->pu8FramebufferVRAM && pFBInfo->u16BitsPerPixel == 32 && !pFBInfo->fDisabled)
+ {
+ /* From VRAM. */
+ LogFunc(("%d from VRAM\n", aScreenId));
+ pAddress = pFBInfo->pu8FramebufferVRAM;
+ ulWidth = pFBInfo->w;
+ ulHeight = pFBInfo->h;
+ ulBitsPerPixel = pFBInfo->u16BitsPerPixel;
+ ulBytesPerLine = pFBInfo->u32LineSize;
+ bitmapFormat = BitmapFormat_BGR;
+ m.pu8Allocated = NULL;
+ }
+ else
+ {
+ /* Allocated byffer */
+ LogFunc(("%d allocated\n", aScreenId));
+ pAddress = NULL;
+ ulWidth = pFBInfo->w;
+ ulHeight = pFBInfo->h;
+ ulBitsPerPixel = 32;
+ ulBytesPerLine = ulWidth * 4;
+ bitmapFormat = BitmapFormat_BGR;
+
+ m.pu8Allocated = (uint8_t *)RTMemAlloc(ulBytesPerLine * ulHeight);
+ if (m.pu8Allocated == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pAddress = m.pu8Allocated;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m.pu8Address = pAddress;
+ m.ulWidth = ulWidth;
+ m.ulHeight = ulHeight;
+ m.ulBitsPerPixel = ulBitsPerPixel;
+ m.ulBytesPerLine = ulBytesPerLine;
+ m.bitmapFormat = bitmapFormat;
+ if (pFBInfo->fDisabled)
+ {
+ RT_BZERO(pAddress, ulBytesPerLine * ulHeight);
+ }
+ }
+
+ return vrc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/DrvAudioRec.cpp b/src/VBox/Main/src-client/DrvAudioRec.cpp
new file mode 100644
index 00000000..59ed4245
--- /dev/null
+++ b/src/VBox/Main/src-client/DrvAudioRec.cpp
@@ -0,0 +1,971 @@
+/* $Id: DrvAudioRec.cpp $ */
+/** @file
+ * Video recording audio backend for Main.
+ *
+ * This driver is part of Main and is responsible for providing audio
+ * data to Main's video capturing feature.
+ *
+ * The driver itself implements a PDM host audio backend, which in turn
+ * provides the driver with the required audio data and audio events.
+ *
+ * For now there is support for the following destinations (called "sinks"):
+ *
+ * - Direct writing of .webm files to the host.
+ * - Communicating with Main via the Console object to send the encoded audio data to.
+ * The Console object in turn then will route the data to the Display / video capturing interface then.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include "DrvAudioRec.h"
+#include "ConsoleImpl.h"
+
+#include "WebMWriter.h"
+
+#include <iprt/mem.h>
+#include <iprt/cdefs.h>
+
+#include "VBox/com/VirtualBox.h"
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include "VBox/settings.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Enumeration for specifying the recording container type.
+ */
+typedef enum AVRECCONTAINERTYPE
+{
+ /** Unknown / invalid container type. */
+ AVRECCONTAINERTYPE_UNKNOWN = 0,
+ /** Recorded data goes to Main / Console. */
+ AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
+ /** Recorded data will be written to a .webm file. */
+ AVRECCONTAINERTYPE_WEBM = 2
+} AVRECCONTAINERTYPE;
+
+/**
+ * Structure for keeping generic container parameters.
+ */
+typedef struct AVRECCONTAINERPARMS
+{
+ /** Stream index (hint). */
+ uint32_t idxStream;
+ /** The container's type. */
+ AVRECCONTAINERTYPE enmType;
+ union
+ {
+ /** WebM file specifics. */
+ struct
+ {
+ /** Allocated file name to write .webm file to. Must be free'd. */
+ char *pszFile;
+ } WebM;
+ };
+
+} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
+
+/**
+ * Structure for keeping container-specific data.
+ */
+typedef struct AVRECCONTAINER
+{
+ /** Generic container parameters. */
+ AVRECCONTAINERPARMS Parms;
+
+ union
+ {
+ struct
+ {
+ /** Pointer to Console. */
+ Console *pConsole;
+ } Main;
+
+ struct
+ {
+ /** Pointer to WebM container to write recorded audio data to.
+ * See the AVRECMODE enumeration for more information. */
+ WebMWriter *pWebM;
+ /** Assigned track number from WebM container. */
+ uint8_t uTrack;
+ } WebM;
+ };
+} AVRECCONTAINER, *PAVRECCONTAINER;
+
+/**
+ * Audio video recording sink.
+ */
+typedef struct AVRECSINK
+{
+ /** Pointer (weak) to recording stream to bind to. */
+ RecordingStream *pRecStream;
+ /** Container data to use for data processing. */
+ AVRECCONTAINER Con;
+ /** Timestamp (in ms) of when the sink was created. */
+ uint64_t tsStartMs;
+} AVRECSINK, *PAVRECSINK;
+
+/**
+ * Audio video recording (output) stream.
+ */
+typedef struct AVRECSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** (Audio) frame buffer. */
+ PRTCIRCBUF pCircBuf;
+ /** Pointer to sink to use for writing. */
+ PAVRECSINK pSink;
+ /** Last encoded PTS (in ms). */
+ uint64_t uLastPTSMs;
+ /** Temporary buffer for the input (source) data to encode. */
+ void *pvSrcBuf;
+ /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
+ size_t cbSrcBuf;
+} AVRECSTREAM, *PAVRECSTREAM;
+
+/**
+ * Video recording audio driver instance data.
+ */
+typedef struct DRVAUDIORECORDING
+{
+ /** Pointer to audio video recording object. */
+ AudioVideoRec *pAudioVideoRec;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Pointer to the console object. */
+ ComPtr<Console> pConsole;
+ /** Pointer to the DrvAudio port interface that is above us. */
+ AVRECCONTAINERPARMS ContainerParms;
+ /** Weak pointer to recording context to use. */
+ RecordingContext *pRecCtx;
+ /** The driver's sink for writing output to. */
+ AVRECSINK Sink;
+} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
+
+
+AudioVideoRec::AudioVideoRec(Console *pConsole)
+ : AudioDriver(pConsole)
+ , mpDrv(NULL)
+{
+}
+
+
+AudioVideoRec::~AudioVideoRec(void)
+{
+ if (mpDrv)
+ {
+ mpDrv->pAudioVideoRec = NULL;
+ mpDrv = NULL;
+ }
+}
+
+
+/**
+ * Applies recording settings to this driver instance.
+ *
+ * @returns VBox status code.
+ * @param Settings Recording settings to apply.
+ */
+int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
+{
+ /** @todo Do some validation here. */
+ mSettings = Settings; /* Note: Does have an own copy operator. */
+ return VINF_SUCCESS;
+}
+
+
+int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
+{
+ /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
+ unsigned const idxScreen = 0;
+
+ AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER);
+ const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen];
+
+ int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest);
+ AssertRCReturn(vrc, vrc);
+ if (screenSettings.enmDest == RecordingDestination_File)
+ {
+ vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
+ AssertRCReturn(vrc, vrc);
+ }
+
+ vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
+ AssertRCReturn(vrc, vrc);
+
+ return AudioDriver::configureDriver(pLunCfg, pVMM);
+}
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
+ pBackendCfg->cbStream = sizeof(AVRECSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = 0;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Creates an audio output stream and associates it with the specified recording sink.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pStreamAV Audio output stream to create.
+ * @param pSink Recording sink to associate audio output stream to.
+ * @param pCfgReq Requested configuration by the audio backend.
+ * @param pCfgAcq Acquired configuration by the audio output stream.
+ */
+static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
+ PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
+ {
+ LogRel(("Recording: Support for surround audio not implemented yet\n"));
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+
+ PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec();
+
+ /* Stuff which has to be set by now. */
+ Assert(pCodec->Parms.cbFrame);
+ Assert(pCodec->Parms.msFrame);
+
+ int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbScratchBuf = pCodec->Parms.cbFrame;
+ pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
+ if (pStreamAV->pvSrcBuf)
+ {
+ pStreamAV->cbSrcBuf = cbScratchBuf;
+
+ pStreamAV->pSink = pSink; /* Assign sink to stream. */
+ pStreamAV->uLastPTSMs = 0;
+
+ /* Make sure to let the driver backend know that we need the audio data in
+ * a specific sampling rate the codec is optimized for. */
+ pCfgAcq->Props = pCodec->Parms.Audio.PCMProps;
+
+ /* Every codec frame marks a period for now. Optimize this later. */
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
+ pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2;
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ return VERR_NOT_SUPPORTED;
+
+ /* For now we only have one sink, namely the driver's one.
+ * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
+ PAVRECSINK pSink = &pThis->Sink;
+
+ int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
+ PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
+
+ return vrc;
+}
+
+
+/**
+ * Destroys (closes) an audio output stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pStreamAV Audio output stream to destroy.
+ */
+static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
+{
+ RT_NOREF(pThis);
+
+ if (pStreamAV->pCircBuf)
+ {
+ RTCircBufDestroy(pStreamAV->pCircBuf);
+ pStreamAV->pCircBuf = NULL;
+ }
+
+ if (pStreamAV->pvSrcBuf)
+ {
+ Assert(pStreamAV->cbSrcBuf);
+ RTMemFree(pStreamAV->pvSrcBuf);
+ pStreamAV->pvSrcBuf = NULL;
+ pStreamAV->cbSrcBuf = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ int vrc = VINF_SUCCESS;
+ if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
+ vrc = avRecDestroyStreamOut(pThis, pStreamAV);
+
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+
+ RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
+ PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
+
+ return pCodec->Parms.cbFrame;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface);
+ PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+ AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+
+ PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ uint32_t cbToWrite = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf));
+ AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Write as much as we can into our internal ring buffer.
+ */
+ while (cbToWrite)
+ {
+ void *pvCircBuf = NULL;
+ size_t cbCircBuf = 0;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
+
+ Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
+
+ memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
+ cbWrittenTotal += (uint32_t)cbCircBuf;
+ Assert(cbWrittenTotal <= cbBuf);
+ Assert(cbToWrite >= cbCircBuf);
+ cbToWrite -= (uint32_t)cbCircBuf;
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
+ }
+
+ RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
+ PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
+
+ /*
+ * Process our internal ring buffer and send the obtained audio data to our encoding thread.
+ */
+ cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
+
+ /** @todo Can we encode more than a frame at a time? Optimize this! */
+ uint32_t const cbFrame = pCodec->Parms.cbFrame;
+
+ /* Only encode data if we have data for at least one full codec frame. */
+ while (cbToWrite >= cbFrame)
+ {
+ uint32_t cbSrc = 0;
+ do
+ {
+ void *pvCircBuf = NULL;
+ size_t cbCircBuf = 0;
+ RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
+
+ Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
+
+ memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
+
+ cbSrc += (uint32_t)cbCircBuf;
+ Assert(cbSrc <= pStreamAV->cbSrcBuf);
+ Assert(cbSrc <= cbFrame);
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
+
+ if (cbSrc == cbFrame) /* Only send full codec frames. */
+ {
+ vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS());
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ } while (cbSrc < cbFrame);
+
+ Assert(cbToWrite >= cbFrame);
+ cbToWrite -= cbFrame;
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ } /* while */
+
+ *pcbWritten = cbWrittenTotal;
+
+ LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
+ return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return 0; /* Video capturing does not provide any input. */
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Shuts down (closes) a recording sink,
+ *
+ * @param pSink Recording sink to shut down.
+ */
+static void avRecSinkShutdown(PAVRECSINK pSink)
+{
+ AssertPtrReturnVoid(pSink);
+
+ pSink->pRecStream = NULL;
+
+ switch (pSink->Con.Parms.enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ if (pSink->Con.WebM.pWebM)
+ {
+ LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
+ pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
+
+ int vrc2 = pSink->Con.WebM.pWebM->Close();
+ AssertRC(vrc2);
+
+ delete pSink->Con.WebM.pWebM;
+ pSink->Con.WebM.pWebM = NULL;
+ }
+ break;
+ }
+
+ case AVRECCONTAINERTYPE_MAIN_CONSOLE:
+ RT_FALL_THROUGH();
+ default:
+ break;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+ LogFlowFuncEnter();
+ avRecSinkShutdown(&pThis->Sink);
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+
+ LogFlowFuncEnter();
+
+ switch (pThis->ContainerParms.enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ avRecSinkShutdown(&pThis->Sink);
+ RTStrFree(pThis->ContainerParms.WebM.pszFile);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ /*
+ * If the AudioVideoRec object is still alive, we must clear it's reference to
+ * us since we'll be invalid when we return from this method.
+ */
+ if (pThis->pAudioVideoRec)
+ {
+ pThis->pAudioVideoRec->mpDrv = NULL;
+ pThis->pAudioVideoRec = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Initializes a recording sink.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance.
+ * @param pSink Sink to initialize.
+ * @param pConParms Container parameters to set.
+ * @param pStream Recording stream to asssign sink to.
+ */
+static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
+{
+ pSink->pRecStream = pStream;
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Container setup.
+ */
+ try
+ {
+ switch (pConParms->enmType)
+ {
+ case AVRECCONTAINERTYPE_MAIN_CONSOLE:
+ {
+ if (pThis->pConsole)
+ {
+ pSink->Con.Main.pConsole = pThis->pConsole;
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ case AVRECCONTAINERTYPE_WEBM:
+ {
+ #if 0
+ /* If we only record audio, create our own WebM writer instance here. */
+ if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
+ {
+ /** @todo Add sink name / number to file name. */
+ const char *pszFile = pSink->Con.Parms.WebM.pszFile;
+ AssertPtr(pszFile);
+
+ pSink->Con.WebM.pWebM = new WebMWriter();
+ vrc = pSink->Con.WebM.pWebM->Open(pszFile,
+ /** @todo Add option to add some suffix if file exists instead of overwriting? */
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
+ pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
+ if (RT_SUCCESS(vrc))
+ {
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
+ PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
+ PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
+ }
+ else
+ LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
+ }
+ else
+ LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
+ }
+ break;
+ #endif
+ }
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pSink->Con.Parms.enmType = pConParms->enmType;
+ pSink->tsStartMs = RTTimeMilliTS();
+
+ return VINF_SUCCESS;
+ }
+
+ LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Construct a audio video recording driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
+ RT_NOREF(fFlags);
+
+ LogRel(("Audio: Initializing video recording audio driver\n"));
+ LogFlowFunc(("fFlags=0x%x\n", fFlags));
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
+
+ /*
+ * Read configuration.
+ */
+ PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
+ /** @todo validate it. */
+
+ /*
+ * Get the Console object pointer.
+ */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ pThis->pConsole = pConsole;
+ AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
+ pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
+ AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
+
+ pThis->pAudioVideoRec->mpDrv = pThis;
+
+ /*
+ * Get the recording container parameters from the audio driver instance.
+ */
+ RT_ZERO(pThis->ContainerParms);
+ PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
+
+ int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
+ AssertRCReturn(vrc, vrc);
+
+ switch (pConParams->enmType)
+ {
+ case AVRECCONTAINERTYPE_WEBM:
+ vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
+ AssertRCReturn(vrc, vrc);
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Obtain the recording context.
+ */
+ pThis->pRecCtx = pConsole->i_recordingGetContext();
+ AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Get the codec configuration.
+ */
+ RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ /*
+ * Init the recording sink.
+ */
+ vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
+ if (RT_SUCCESS(vrc))
+ LogRel2(("Recording: Audio recording driver initialized\n"));
+ else
+ LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
+
+ return vrc;
+}
+
+
+/**
+ * Video recording audio driver registration record.
+ */
+const PDMDRVREG AudioVideoRec::DrvReg =
+{
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AudioVideoRec",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio driver for video recording",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVAUDIORECORDING),
+ /* pfnConstruct */
+ AudioVideoRec::drvConstruct,
+ /* pfnDestruct */
+ AudioVideoRec::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ AudioVideoRec::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Main/src-client/DrvAudioVRDE.cpp b/src/VBox/Main/src-client/DrvAudioVRDE.cpp
new file mode 100644
index 00000000..c3a4be53
--- /dev/null
+++ b/src/VBox/Main/src-client/DrvAudioVRDE.cpp
@@ -0,0 +1,823 @@
+/* $Id: DrvAudioVRDE.cpp $ */
+/** @file
+ * VRDE audio backend for Main.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include "LoggingNew.h"
+
+#include <VBox/log.h>
+#include "DrvAudioVRDE.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+
+#include <iprt/mem.h>
+#include <iprt/cdefs.h>
+#include <iprt/circbuf.h>
+
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/err.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * VRDE stream.
+ */
+typedef struct VRDESTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ union
+ {
+ struct
+ {
+ /** Circular buffer for holding the recorded audio frames from the host. */
+ PRTCIRCBUF pCircBuf;
+ } In;
+ };
+} VRDESTREAM;
+/** Pointer to a VRDE stream. */
+typedef VRDESTREAM *PVRDESTREAM;
+
+/**
+ * VRDE (host) audio driver instance data.
+ */
+typedef struct DRVAUDIOVRDE
+{
+ /** Pointer to audio VRDE object. */
+ AudioVRDE *pAudioVRDE;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the VRDP's console object. */
+ ConsoleVRDPServer *pConsoleVRDPServer;
+ /** Number of connected clients to this VRDE instance. */
+ uint32_t cClients;
+ /** Interface to the driver above us (DrvAudio). */
+ PDMIHOSTAUDIOPORT *pIHostAudioPort;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVAUDIOVRDE;
+/** Pointer to the instance data for an VRDE audio driver. */
+typedef DRVAUDIOVRDE *PDRVAUDIOVRDE;
+
+
+/*********************************************************************************************************************************
+* Class AudioVRDE *
+*********************************************************************************************************************************/
+
+AudioVRDE::AudioVRDE(Console *pConsole)
+ : AudioDriver(pConsole)
+ , mpDrv(NULL)
+{
+ RTCritSectInit(&mCritSect);
+}
+
+
+AudioVRDE::~AudioVRDE(void)
+{
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ mpDrv->pAudioVRDE = NULL;
+ mpDrv = NULL;
+ }
+ RTCritSectLeave(&mCritSect);
+ RTCritSectDelete(&mCritSect);
+}
+
+
+int AudioVRDE::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
+{
+ return AudioDriver::configureDriver(pLunCfg, pVMM);
+}
+
+
+void AudioVRDE::onVRDEClientConnect(uint32_t uClientID)
+{
+ RT_NOREF(uClientID);
+
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ mpDrv->cClients++;
+ LogRel2(("Audio: VRDE client connected (#%u)\n", mpDrv->cClients));
+
+#if 0 /* later, maybe */
+ /*
+ * The first client triggers a device change event in both directions
+ * so that can start talking to the audio device.
+ *
+ * Note! Should be okay to stay in the critical section here, as it's only
+ * used at construction and destruction time.
+ */
+ if (mpDrv->cClients == 1)
+ {
+ VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
+ if (enmState <= VMSTATE_POWERING_OFF)
+ {
+ PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
+ AssertPtr(pIHostAudioPort);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+#endif
+ }
+ RTCritSectLeave(&mCritSect);
+}
+
+
+void AudioVRDE::onVRDEClientDisconnect(uint32_t uClientID)
+{
+ RT_NOREF(uClientID);
+
+ RTCritSectEnter(&mCritSect);
+ if (mpDrv)
+ {
+ Assert(mpDrv->cClients > 0);
+ mpDrv->cClients--;
+ LogRel2(("Audio: VRDE client disconnected (%u left)\n", mpDrv->cClients));
+#if 0 /* later maybe */
+ /*
+ * The last client leaving triggers a device change event in both
+ * directions so the audio devices can stop wasting time trying to
+ * talk to us. (There is an additional safeguard in
+ * drvAudioVrdeHA_StreamGetStatus.)
+ */
+ if (mpDrv->cClients == 0)
+ {
+ VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
+ if (enmState <= VMSTATE_POWERING_OFF)
+ {
+ PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
+ AssertPtr(pIHostAudioPort);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+#endif
+ }
+ RTCritSectLeave(&mCritSect);
+}
+
+
+int AudioVRDE::onVRDEControl(bool fEnable, uint32_t uFlags)
+{
+ RT_NOREF(fEnable, uFlags);
+ LogFlowThisFunc(("fEnable=%RTbool, uFlags=0x%x\n", fEnable, uFlags));
+
+ if (mpDrv == NULL)
+ return VERR_INVALID_STATE;
+
+ return VINF_SUCCESS; /* Never veto. */
+}
+
+
+/**
+ * Marks the beginning of sending captured audio data from a connected
+ * RDP client.
+ *
+ * @returns VBox status code.
+ * @param pvContext The context; in this case a pointer to a
+ * VRDESTREAMIN structure.
+ * @param pVRDEAudioBegin Pointer to a VRDEAUDIOINBEGIN structure.
+ */
+int AudioVRDE::onVRDEInputBegin(void *pvContext, PVRDEAUDIOINBEGIN pVRDEAudioBegin)
+{
+ AssertPtrReturn(pvContext, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVRDEAudioBegin, VERR_INVALID_POINTER);
+ PVRDESTREAM pVRDEStrmIn = (PVRDESTREAM)pvContext;
+ AssertPtrReturn(pVRDEStrmIn, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ VRDEAUDIOFORMAT const audioFmt = pVRDEAudioBegin->fmt;
+ LogFlowFunc(("cbSample=%RU32, iSampleHz=%d, cChannels=%d, cBits=%d, fUnsigned=%RTbool\n",
+ VRDE_AUDIO_FMT_BYTES_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SAMPLE_FREQ(audioFmt),
+ VRDE_AUDIO_FMT_CHANNELS(audioFmt), VRDE_AUDIO_FMT_BITS_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SIGNED(audioFmt)));
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+int AudioVRDE::onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData)
+{
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pvContext;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ LogFlowFunc(("cbData=%#x\n", cbData));
+
+ void *pvBuf = NULL;
+ size_t cbBuf = 0;
+ RTCircBufAcquireWriteBlock(pStreamVRDE->In.pCircBuf, cbData, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ memcpy(pvBuf, pvData, cbBuf);
+
+ RTCircBufReleaseWriteBlock(pStreamVRDE->In.pCircBuf, cbBuf);
+
+ if (cbBuf < cbData)
+ LogRelMax(999, ("VRDE: Capturing audio data lost %zu bytes\n", cbData - cbBuf)); /** @todo Use an error counter. */
+
+ return VINF_SUCCESS; /** @todo r=andy How to tell the caller if we were not able to handle *all* input data? */
+}
+
+
+int AudioVRDE::onVRDEInputEnd(void *pvContext)
+{
+ RT_NOREF(pvContext);
+ return VINF_SUCCESS;
+}
+
+
+int AudioVRDE::onVRDEInputIntercept(bool fEnabled)
+{
+ RT_NOREF(fEnabled);
+ return VINF_SUCCESS; /* Never veto. */
+}
+
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VRDE");
+ pBackendCfg->cbStream = sizeof(VRDESTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVrdeHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVAUDIOVRDE pThis = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ /*
+ * Only create a stream if we have clients.
+ */
+ int vrc;
+ NOREF(pThis);
+#if 0 /* later maybe */
+ if (pThis->cClients == 0)
+ {
+ LogFunc(("No clients, failing with VERR_AUDIO_STREAM_COULD_NOT_CREATE.\n"));
+ vrc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ else
+#endif
+ {
+ /*
+ * The VRDP server does its own mixing and resampling because it may be
+ * sending the audio to any number of different clients all with different
+ * formats (including clients which hasn't yet connected). So, it desires
+ * the raw data from the mixer (somewhat akind to stereo signed 64-bit,
+ * see st_sample_t and PDMAUDIOFRAME).
+ */
+ PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/,
+ 22050 /*Hz - VRDP_AUDIO_CHUNK_INTERNAL_FREQ_HZ*/,
+ true /*fLittleEndian*/, true /*fRaw*/);
+
+ /* According to the VRDP docs (VRDP_AUDIO_CHUNK_TIME_MS), the VRDP server
+ stores audio in 200ms chunks. */
+ const uint32_t cFramesVrdpServer = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 200 /*ms*/);
+
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ pCfgAcq->Backend.cFramesBufferSize = cFramesVrdpServer;
+ pCfgAcq->Backend.cFramesPeriod = cFramesVrdpServer / 4; /* This is utter non-sense, but whatever. */
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * cFramesVrdpServer
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ vrc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, PDMAudioPropsFramesToBytes(&pCfgAcq->Props, cFramesVrdpServer));
+ }
+ else
+ {
+ /** @todo r=bird: So, if VRDP does 200ms chunks, why do we report 100ms
+ * buffer and 20ms period? How does these parameters at all correlate
+ * with the above comment?!? */
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 20 /*ms*/);
+ pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/);
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
+ vrc = VINF_SUCCESS;
+ }
+
+ PDMAudioStrmCfgCopy(&pStreamVRDE->Cfg, pCfgAcq);
+ }
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ LogFlowFunc(("Calling SendAudioInputEnd\n"));
+ if (pDrv->pConsoleVRDPServer)
+ pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL);
+
+ if (pStreamVRDE->In.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamVRDE->In.pCircBuf);
+ pStreamVRDE->In.pCircBuf = NULL;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ int vrc;
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (enable)\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ vrc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE,
+ PDMAudioPropsMilliToFrames(&pStreamVRDE->Cfg.Props, 200 /*ms*/),
+ PDMAudioPropsHz(&pStreamVRDE->Cfg.Props),
+ PDMAudioPropsChannels(&pStreamVRDE->Cfg.Props),
+ PDMAudioPropsSampleBits(&pStreamVRDE->Cfg.Props));
+ LogFlowFunc(("SendAudioInputBegin returns %Rrc\n", vrc));
+ if (vrc == VERR_NOT_SUPPORTED)
+ {
+ LogRelMax(64, ("Audio: No VRDE client connected, so no input recording available\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+ else
+ vrc = VINF_SUCCESS;
+ LogFlowFunc(("returns %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ int vrc;
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (disable)\n"));
+ vrc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ LogFlowFunc(("Calling SendAudioInputEnd\n"));
+ pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */);
+ vrc = VINF_SUCCESS;
+ }
+ else
+ vrc = VINF_SUCCESS;
+ LogFlowFunc(("returns %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ RT_NOREF(pStream);
+
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (pause)\n"));
+ return VERR_AUDIO_STREAM_NOT_READY;
+ }
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ RT_NOREF(pStream);
+
+ if (!pDrv->pConsoleVRDPServer)
+ {
+ LogRelMax(32, ("Audio: VRDP console not ready (resume)\n"));
+ return VERR_AUDIO_STREAM_NOT_READY;
+ }
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVrdeHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ return pDrv->cClients > 0 ? PDMHOSTAUDIOSTREAMSTATE_OKAY : PDMHOSTAUDIOSTREAMSTATE_INACTIVE;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */
+ if (pDrv->cClients)
+ return PDMAudioPropsFramesToBytes(&pStreamVRDE->Cfg.Props, pStreamVRDE->Cfg.Backend.cFramesBufferSize);
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
+ AssertPtr(pDrv);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+
+ if (!pDrv->pConsoleVRDPServer)
+ return VERR_NOT_AVAILABLE;
+
+ /* Prepate the format. */
+ PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props;
+ VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps),
+ PDMAudioPropsChannels(pProps),
+ PDMAudioPropsSampleBits(pProps),
+ pProps->fSigned);
+ Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true));
+
+ /** @todo r=bird: there was some incoherent mumbling about "using the
+ * internal counter to track if we (still) can write to the VRDP
+ * server or if need to wait another round (time slot)". However it
+ * wasn't accessing any internal counter nor doing anything else
+ * sensible, so I've removed it. */
+
+ uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStream->pStream->Cfg.Props, cbBuf);
+ Assert(cFrames == cbBuf / (sizeof(uint64_t) * 2));
+ pDrv->pConsoleVRDPServer->SendAudioSamples(pvBuf, cFrames, uVrdpFormat);
+
+ Log3Func(("cFramesWritten=%RU32\n", cFrames));
+ *pcbWritten = PDMAudioPropsFramesToBytes(&pStream->pStream->Cfg.Props, cFrames);
+ Assert(*pcbWritten == cbBuf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+
+ AssertReturn(pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN, 0);
+ uint32_t cbRet = (uint32_t)RTCircBufUsed(pStreamVRDE->In.pCircBuf);
+ Log4Func(("returns %#x\n", cbRet));
+ return cbRet;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
+ AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
+
+ *pcbRead = 0;
+ while (cbBuf > 0 && RTCircBufUsed(pStreamVRDE->In.pCircBuf) > 0)
+ {
+ size_t cbData = 0;
+ void *pvData = NULL;
+ RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData);
+
+ memcpy(pvBuf, pvData, cbData);
+
+ RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData);
+
+ *pcbRead += (uint32_t)cbData;
+ cbBuf -= (uint32_t)cbData;
+ pvData = (uint8_t *)pvData + cbData;
+ }
+
+ LogFlowFunc(("returns %#x bytes\n", *pcbRead));
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ LogFlowFuncEnter();
+
+ if (pThis->pConsoleVRDPServer)
+ pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL);
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ LogFlowFuncEnter();
+
+ /** @todo For runtime detach maybe:
+ if (pThis->pConsoleVRDPServer)
+ pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */
+
+ /*
+ * If the AudioVRDE object is still alive, we must clear it's reference to
+ * us since we'll be invalid when we return from this method.
+ */
+ AudioVRDE *pAudioVRDE = pThis->pAudioVRDE;
+ if (pAudioVRDE)
+ {
+ RTCritSectEnter(&pAudioVRDE->mCritSect);
+ pAudioVRDE->mpDrv = NULL;
+ pThis->pAudioVRDE = NULL;
+ RTCritSectLeave(&pAudioVRDE->mCritSect);
+ }
+}
+
+
+/**
+ * Construct a VRDE audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+/* static */
+DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
+ RT_NOREF(fFlags);
+
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogRel(("Audio: Initializing VRDE driver\n"));
+ LogFlowFunc(("fFlags=0x%x\n", fFlags));
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->cClients = 0;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvAudioVrdeHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvAudioVrdeHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvAudioVrdeHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvAudioVrdeHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvAudioVrdeHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvAudioVrdeHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture;
+
+ /*
+ * Resolve the interface to the driver above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /* Get the Console object pointer. */
+ com::Guid ConsoleUuid(COM_IIDOF(IConsole));
+ IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
+ AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
+ Console *pConsole = static_cast<Console *>(pIConsole);
+ AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
+
+ /* Get the console VRDP object pointer. */
+ pThis->pConsoleVRDPServer = pConsole->i_consoleVRDPServer();
+ AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer,
+ ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER);
+
+ /* Get the AudioVRDE object pointer. */
+ pThis->pAudioVRDE = pConsole->i_getAudioVRDE();
+ AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER);
+ RTCritSectEnter(&pThis->pAudioVRDE->mCritSect);
+ pThis->pAudioVRDE->mpDrv = pThis;
+ RTCritSectLeave(&pThis->pAudioVRDE->mCritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VRDE audio driver registration record.
+ */
+const PDMDRVREG AudioVRDE::DrvReg =
+{
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AudioVRDE",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio driver for VRDE backend",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVAUDIOVRDE),
+ /* pfnConstruct */
+ AudioVRDE::drvConstruct,
+ /* pfnDestruct */
+ AudioVRDE::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ AudioVRDE::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Main/src-client/EBMLWriter.cpp b/src/VBox/Main/src-client/EBMLWriter.cpp
new file mode 100644
index 00000000..846fdca5
--- /dev/null
+++ b/src/VBox/Main/src-client/EBMLWriter.cpp
@@ -0,0 +1,275 @@
+/* $Id: EBMLWriter.cpp $ */
+/** @file
+ * EBMLWriter.cpp - EBML writer implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * For more information, see:
+ * - https://w3c.github.io/media-source/webm-byte-stream-format.html
+ * - https://www.webmproject.org/docs/container/#muxer-guidelines
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
+#include "LoggingNew.h"
+
+#include <list>
+#include <map>
+#include <queue>
+#include <stack>
+
+#include <math.h> /* For lround.h. */
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/cdefs.h>
+#include <iprt/critsect.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+#include "EBMLWriter.h"
+#include "EBML_MKV.h"
+
+/** No flags set. */
+#define VBOX_EBMLWRITER_FLAG_NONE 0
+/** The file handle was inherited. */
+#define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0)
+
+/** Creates an EBML output file using an existing, open file handle. */
+int EBMLWriter::createEx(const char *a_pszFile, PRTFILE phFile)
+{
+ AssertPtrReturn(phFile, VERR_INVALID_POINTER);
+
+ m_hFile = *phFile;
+ m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED;
+ m_strFile = a_pszFile;
+
+ return VINF_SUCCESS;
+}
+
+/** Creates an EBML output file using a file name. */
+int EBMLWriter::create(const char *a_pszFile, uint64_t fOpen)
+{
+ int vrc = RTFileOpen(&m_hFile, a_pszFile, fOpen);
+ if (RT_SUCCESS(vrc))
+ m_strFile = a_pszFile;
+
+ return vrc;
+}
+
+/** Returns available space on storage. */
+uint64_t EBMLWriter::getAvailableSpace(void)
+{
+ RTFOFF pcbFree;
+ int vrc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0);
+ return (RT_SUCCESS(vrc)? (uint64_t)pcbFree : UINT64_MAX);
+}
+
+/** Closes the file. */
+void EBMLWriter::close(void)
+{
+ if (!isOpen())
+ return;
+
+ AssertMsg(m_Elements.size() == 0,
+ ("%zu elements are not closed yet (next element to close is 0x%x)\n",
+ m_Elements.size(), m_Elements.top().classId));
+
+ if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED))
+ {
+ RTFileClose(m_hFile);
+ m_hFile = NIL_RTFILE;
+ }
+
+ m_fFlags = VBOX_EBMLWRITER_FLAG_NONE;
+ m_strFile = "";
+}
+
+/** Starts an EBML sub-element. */
+EBMLWriter& EBMLWriter::subStart(EbmlClassId classId)
+{
+ writeClassId(classId);
+ /* store the current file offset. */
+ m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId));
+ /* Indicates that size of the element
+ * is unkown (as according to EBML specs).
+ */
+ writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
+ return *this;
+}
+
+/** Ends an EBML sub-element. */
+EBMLWriter& EBMLWriter::subEnd(EbmlClassId classId)
+{
+#ifdef VBOX_STRICT
+ /* Class ID on the top of the stack should match the class ID passed
+ * to the function. Otherwise it may mean that we have a bug in the code.
+ */
+ AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
+ AssertMsg(m_Elements.top().classId == classId,
+ ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
+#else
+ RT_NOREF(classId);
+#endif
+
+ uint64_t uPos = RTFileTell(m_hFile);
+ uint64_t uSize = uPos - m_Elements.top().offset - 8;
+ RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
+
+ /* Make sure that size will be serialized as uint64_t. */
+ writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
+ RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL);
+ m_Elements.pop();
+ return *this;
+}
+
+/** Serializes a null-terminated string. */
+EBMLWriter& EBMLWriter::serializeString(EbmlClassId classId, const char *str)
+{
+ writeClassId(classId);
+ uint64_t size = strlen(str);
+ writeSize(size);
+ write(str, size);
+ return *this;
+}
+
+/** Serializes an UNSIGNED integer.
+ * If size is zero then it will be detected automatically. */
+EBMLWriter& EBMLWriter::serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size /* = 0 */)
+{
+ writeClassId(classId);
+ if (!size) size = getSizeOfUInt(parm);
+ writeSize(size);
+ writeUnsignedInteger(parm, size);
+ return *this;
+}
+
+/** Serializes a floating point value.
+ *
+ * Only 8-bytes double precision values are supported
+ * by this function.
+ */
+EBMLWriter& EBMLWriter::serializeFloat(EbmlClassId classId, float value)
+{
+ writeClassId(classId);
+ Assert(sizeof(uint32_t) == sizeof(float));
+ writeSize(sizeof(float));
+
+ union
+ {
+ float f;
+ uint8_t u8[4];
+ } u;
+
+ u.f = value;
+
+ for (int i = 3; i >= 0; i--) /* Converts values to big endian. */
+ write(&u.u8[i], 1);
+
+ return *this;
+}
+
+/** Serializes binary data. */
+EBMLWriter& EBMLWriter::serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
+{
+ writeClassId(classId);
+ writeSize(cbData);
+ write(pvData, cbData);
+ return *this;
+}
+
+/** Writes raw data to file. */
+int EBMLWriter::write(const void *data, size_t size)
+{
+ return RTFileWrite(m_hFile, data, size, NULL);
+}
+
+/** Writes an unsigned integer of variable of fixed size. */
+void EBMLWriter::writeUnsignedInteger(uint64_t value, size_t size /* = sizeof(uint64_t) */)
+{
+ /* convert to big-endian */
+ value = RT_H2BE_U64(value);
+ write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
+}
+
+/** Writes EBML class ID to file.
+ *
+ * EBML ID already has a UTF8-like represenation
+ * so getSizeOfUInt is used to determine
+ * the number of its bytes.
+ */
+void EBMLWriter::writeClassId(EbmlClassId parm)
+{
+ writeUnsignedInteger(parm, getSizeOfUInt(parm));
+}
+
+/** Writes data size value. */
+void EBMLWriter::writeSize(uint64_t parm)
+{
+ /* The following expression defines the size of the value that will be serialized
+ * as an EBML UTF-8 like integer (with trailing bits represeting its size):
+ 1xxx xxxx - value 0 to 2^7-2
+ 01xx xxxx xxxx xxxx - value 0 to 2^14-2
+ 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
+ 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
+ 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
+ 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
+ 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
+ 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
+ */
+ size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
+ ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
+ ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
+ ! (parm & (UINT64_MAX << 7));
+ /* One is subtracted in order to avoid loosing significant bit when size = 8. */
+ uint64_t mask = RT_BIT_64(size * 8 - 1);
+ writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
+}
+
+/** Size calculation for variable size UNSIGNED integer.
+ *
+ * The function defines the size of the number by trimming
+ * consequent trailing zero bytes starting from the most significant.
+ * The following statement is always true:
+ * 1 <= getSizeOfUInt(arg) <= 8.
+ *
+ * Every !(arg & (UINT64_MAX << X)) expression gives one
+ * if an only if all the bits from X to 63 are set to zero.
+ */
+size_t EBMLWriter::getSizeOfUInt(uint64_t arg)
+{
+ return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
+ ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
+ ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
+ ! (arg & (UINT64_MAX << 8));
+}
+
diff --git a/src/VBox/Main/src-client/EmulatedUSBImpl.cpp b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp
new file mode 100644
index 00000000..b83cdd51
--- /dev/null
+++ b/src/VBox/Main/src-client/EmulatedUSBImpl.cpp
@@ -0,0 +1,678 @@
+/* $Id: EmulatedUSBImpl.cpp $ */
+/** @file
+ * Emulated USB manager implementation.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_EMULATEDUSB
+#include "LoggingNew.h"
+
+#include "EmulatedUSBImpl.h"
+#include "ConsoleImpl.h"
+
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/vmm/vmmr3vtable.h>
+
+
+/*
+ * Emulated USB webcam device instance.
+ */
+typedef std::map <Utf8Str, Utf8Str> EUSBSettingsMap;
+
+typedef enum EUSBDEVICESTATUS
+{
+ EUSBDEVICE_CREATED,
+ EUSBDEVICE_ATTACHING,
+ EUSBDEVICE_ATTACHED
+} EUSBDEVICESTATUS;
+
+class EUSBWEBCAM /* : public EUSBDEVICE */
+{
+private:
+ int32_t volatile mcRefs;
+
+ EmulatedUSB *mpEmulatedUSB;
+
+ RTUUID mUuid;
+ char mszUuid[RTUUID_STR_LENGTH];
+
+ Utf8Str mPath;
+ Utf8Str mSettings;
+
+ EUSBSettingsMap mDevSettings;
+ EUSBSettingsMap mDrvSettings;
+
+ void *mpvObject;
+
+ static DECLCALLBACK(int) emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver);
+ static DECLCALLBACK(int) emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis);
+
+ HRESULT settingsParse(void);
+
+ ~EUSBWEBCAM()
+ {
+ }
+
+public:
+ EUSBWEBCAM()
+ :
+ mcRefs(1),
+ mpEmulatedUSB(NULL),
+ mpvObject(NULL),
+ enmStatus(EUSBDEVICE_CREATED)
+ {
+ RT_ZERO(mUuid);
+ RT_ZERO(mszUuid);
+ }
+
+ int32_t AddRef(void)
+ {
+ return ASMAtomicIncS32(&mcRefs);
+ }
+
+ void Release(void)
+ {
+ int32_t c = ASMAtomicDecS32(&mcRefs);
+ if (c == 0)
+ {
+ delete this;
+ }
+ }
+
+ HRESULT Initialize(Console *pConsole,
+ EmulatedUSB *pEmulatedUSB,
+ const com::Utf8Str *aPath,
+ const com::Utf8Str *aSettings,
+ void *pvObject);
+ HRESULT Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver);
+ HRESULT Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ bool HasId(const char *pszId) { return RTStrCmp(pszId, mszUuid) == 0;}
+
+ void *getObjectPtr() { return mpvObject; }
+
+ EUSBDEVICESTATUS enmStatus;
+};
+
+
+static int emulatedWebcamInsertSettings(PCFGMNODE pConfig, PCVMMR3VTABLE pVMM, EUSBSettingsMap *pSettings)
+{
+ for (EUSBSettingsMap::const_iterator it = pSettings->begin(); it != pSettings->end(); ++it)
+ {
+ /* Convert some well known settings for backward compatibility. */
+ int vrc;
+ if ( RTStrCmp(it->first.c_str(), "MaxPayloadTransferSize") == 0
+ || RTStrCmp(it->first.c_str(), "MaxFramerate") == 0)
+ {
+ uint32_t u32 = 0;
+ vrc = RTStrToUInt32Full(it->second.c_str(), 10, &u32);
+ if (vrc == VINF_SUCCESS)
+ vrc = pVMM->pfnCFGMR3InsertInteger(pConfig, it->first.c_str(), u32);
+ else if (RT_SUCCESS(vrc)) /* VWRN_* */
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, it->first.c_str(), it->second.c_str());
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/*static*/ DECLCALLBACK(int)
+EUSBWEBCAM::emulatedWebcamAttach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis, const char *pszDriver)
+{
+ PCFGMNODE pInstance = pVMM->pfnCFGMR3CreateTree(pUVM);
+ PCFGMNODE pConfig;
+ int vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "Config", &pConfig);
+ AssertRCReturn(vrc, vrc);
+ vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDevSettings);
+ AssertRCReturn(vrc, vrc);
+
+ PCFGMNODE pEUSB;
+ vrc = pVMM->pfnCFGMR3InsertNode(pConfig, "EmulatedUSB", &pEUSB);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pEUSB, "Id", pThis->mszUuid);
+ AssertRCReturn(vrc, vrc);
+
+ PCFGMNODE pLunL0;
+ vrc = pVMM->pfnCFGMR3InsertNode(pInstance, "LUN#0", &pLunL0);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pLunL0, "Driver", pszDriver);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertNode(pLunL0, "Config", &pConfig);
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, "DevicePath", pThis->mPath.c_str());
+ AssertRCReturn(vrc, vrc);
+ vrc = pVMM->pfnCFGMR3InsertString(pConfig, "Id", pThis->mszUuid);
+ AssertRCReturn(vrc, vrc);
+ vrc = emulatedWebcamInsertSettings(pConfig, pVMM, &pThis->mDrvSettings);
+ AssertRCReturn(vrc, vrc);
+
+ /* pInstance will be used by PDM and deallocated on error. */
+ vrc = pVMM->pfnPDMR3UsbCreateEmulatedDevice(pUVM, "Webcam", pInstance, &pThis->mUuid, NULL);
+ LogRelFlowFunc(("PDMR3UsbCreateEmulatedDevice %Rrc\n", vrc));
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+EUSBWEBCAM::emulatedWebcamDetach(PUVM pUVM, PCVMMR3VTABLE pVMM, EUSBWEBCAM *pThis)
+{
+ return pVMM->pfnPDMR3UsbDetachDevice(pUVM, &pThis->mUuid);
+}
+
+HRESULT EUSBWEBCAM::Initialize(Console *pConsole,
+ EmulatedUSB *pEmulatedUSB,
+ const com::Utf8Str *aPath,
+ const com::Utf8Str *aSettings,
+ void *pvObject)
+{
+ HRESULT hrc = S_OK;
+
+ int vrc = RTUuidCreate(&mUuid);
+ AssertRCReturn(vrc, pConsole->setError(vrc, EmulatedUSB::tr("Init emulated USB webcam (RTUuidCreate -> %Rrc)"), vrc));
+
+ RTStrPrintf(mszUuid, sizeof(mszUuid), "%RTuuid", &mUuid);
+ hrc = mPath.assignEx(*aPath);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = mSettings.assignEx(*aSettings);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = settingsParse();
+ if (SUCCEEDED(hrc))
+ {
+ mpEmulatedUSB = pEmulatedUSB;
+ mpvObject = pvObject;
+ }
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT EUSBWEBCAM::settingsParse(void)
+{
+ HRESULT hr = S_OK;
+
+ /* Parse mSettings string:
+ * "[dev:|drv:]Name1=Value1;[dev:|drv:]Name2=Value2"
+ */
+ char *pszSrc = mSettings.mutableRaw();
+
+ if (pszSrc)
+ {
+ while (*pszSrc)
+ {
+ /* Does the setting belong to device of driver. Default is both. */
+ bool fDev = true;
+ bool fDrv = true;
+ if (RTStrNICmp(pszSrc, RT_STR_TUPLE("drv:")) == 0)
+ {
+ pszSrc += sizeof("drv:")-1;
+ fDev = false;
+ }
+ else if (RTStrNICmp(pszSrc, RT_STR_TUPLE("dev:")) == 0)
+ {
+ pszSrc += sizeof("dev:")-1;
+ fDrv = false;
+ }
+
+ char *pszEq = strchr(pszSrc, '=');
+ if (!pszEq)
+ {
+ hr = E_INVALIDARG;
+ break;
+ }
+
+ char *pszEnd = strchr(pszEq, ';');
+ if (!pszEnd)
+ pszEnd = pszEq + strlen(pszEq);
+
+ *pszEq = 0;
+ char chEnd = *pszEnd;
+ *pszEnd = 0;
+
+ /* Empty strings not allowed. */
+ if (*pszSrc != 0 && pszEq[1] != 0)
+ {
+ if (fDev)
+ mDevSettings[pszSrc] = &pszEq[1];
+ if (fDrv)
+ mDrvSettings[pszSrc] = &pszEq[1];
+ }
+
+ *pszEq = '=';
+ *pszEnd = chEnd;
+
+ pszSrc = pszEnd;
+ if (*pszSrc == ';')
+ pszSrc++;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ EUSBSettingsMap::const_iterator it;
+ for (it = mDevSettings.begin(); it != mDevSettings.end(); ++it)
+ LogRelFlowFunc(("[dev:%s] = [%s]\n", it->first.c_str(), it->second.c_str()));
+ for (it = mDrvSettings.begin(); it != mDrvSettings.end(); ++it)
+ LogRelFlowFunc(("[drv:%s] = [%s]\n", it->first.c_str(), it->second.c_str()));
+ }
+ }
+
+ return hr;
+}
+
+HRESULT EUSBWEBCAM::Attach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszDriver)
+{
+ int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)emulatedWebcamAttach, 4,
+ pUVM, pVMM, this, pszDriver);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Attach emulated USB webcam (%Rrc)"), vrc);
+}
+
+HRESULT EUSBWEBCAM::Detach(Console *pConsole, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ int vrc = pVMM->pfnVMR3ReqCallWaitU(pUVM, 0 /* idDstCpu (saved state, see #6232) */,
+ (PFNRT)emulatedWebcamDetach, 3,
+ pUVM, pVMM, this);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return pConsole->setErrorBoth(VBOX_E_VM_ERROR, vrc, EmulatedUSB::tr("Detach emulated USB webcam (%Rrc)"), vrc);
+}
+
+
+/*
+ * EmulatedUSB implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(EmulatedUSB)
+
+HRESULT EmulatedUSB::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void EmulatedUSB::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+/*
+ * Initializes the instance.
+ *
+ * @param pConsole The owner.
+ */
+HRESULT EmulatedUSB::init(ComObjPtr<Console> pConsole)
+{
+ LogFlowThisFunc(("\n"));
+
+ ComAssertRet(!pConsole.isNull(), E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pConsole = pConsole;
+
+ mEmUsbIf.pvUser = this;
+ mEmUsbIf.pfnQueryEmulatedUsbDataById = EmulatedUSB::i_QueryEmulatedUsbDataById;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/*
+ * Uninitializes the instance.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void EmulatedUSB::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ m.pConsole.setNull();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (WebcamsMap::iterator it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p)
+ {
+ it->second = NULL;
+ p->Release();
+ }
+ }
+ m.webcams.clear();
+ alock.release();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+HRESULT EmulatedUSB::getWebcams(std::vector<com::Utf8Str> &aWebcams)
+{
+ HRESULT hrc = S_OK;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ try
+ {
+ aWebcams.resize(m.webcams.size());
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ size_t i;
+ WebcamsMap::const_iterator it;
+ for (i = 0, it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ aWebcams[i++] = it->first;
+ }
+
+ return hrc;
+}
+
+PEMULATEDUSBIF EmulatedUSB::i_getEmulatedUsbIf()
+{
+ return &mEmUsbIf;
+}
+
+static const Utf8Str s_pathDefault(".0");
+
+HRESULT EmulatedUSB::webcamAttach(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings)
+{
+ return i_webcamAttachInternal(aPath, aSettings, "HostWebcam", NULL);
+}
+
+HRESULT EmulatedUSB::i_webcamAttachInternal(const com::Utf8Str &aPath,
+ const com::Utf8Str &aSettings,
+ const char *pszDriver,
+ void *pvObject)
+{
+ HRESULT hrc = S_OK;
+
+ const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ EUSBWEBCAM *p = new EUSBWEBCAM();
+ if (p)
+ {
+ hrc = p->Initialize(m.pConsole, this, &path, &aSettings, pvObject);
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it = m.webcams.find(path);
+ if (it == m.webcams.end())
+ {
+ p->AddRef();
+ try
+ {
+ m.webcams[path] = p;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ hrc = E_FAIL;
+ }
+ p->enmStatus = EUSBDEVICE_ATTACHING;
+ }
+ else
+ {
+ hrc = E_FAIL;
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ hrc = p->Attach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable(), pszDriver);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (SUCCEEDED(hrc))
+ p->enmStatus = EUSBDEVICE_ATTACHED;
+ else if (p->enmStatus != EUSBDEVICE_CREATED)
+ m.webcams.erase(path);
+ alock.release();
+
+ p->Release();
+ }
+ else
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+HRESULT EmulatedUSB::webcamDetach(const com::Utf8Str &aPath)
+{
+ return i_webcamDetachInternal(aPath);
+}
+
+HRESULT EmulatedUSB::i_webcamDetachInternal(const com::Utf8Str &aPath)
+{
+ HRESULT hrc = S_OK;
+
+ const Utf8Str &path = aPath.isEmpty() || aPath == "."? s_pathDefault: aPath;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ EUSBWEBCAM *p = NULL;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::iterator it = m.webcams.find(path);
+ if (it != m.webcams.end())
+ {
+ if (it->second->enmStatus == EUSBDEVICE_ATTACHED)
+ {
+ p = it->second;
+ m.webcams.erase(it);
+ }
+ }
+ alock.release();
+
+ if (p)
+ {
+ hrc = p->Detach(m.pConsole, ptrVM.rawUVM(), ptrVM.vtable());
+ p->Release();
+ }
+ else
+ {
+ hrc = E_INVALIDARG;
+ }
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(int)
+EmulatedUSB::eusbCallbackEMT(EmulatedUSB *pThis, char *pszId, uint32_t iEvent, void *pvData, uint32_t cbData)
+{
+ LogRelFlowFunc(("id %s event %d, data %p %d\n", pszId, iEvent, pvData, cbData));
+
+ NOREF(cbData);
+
+ int vrc = VINF_SUCCESS;
+ if (iEvent == 0)
+ {
+ com::Utf8Str path;
+ HRESULT hrc = pThis->webcamPathFromId(&path, pszId);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pThis->webcamDetach(path);
+ if (FAILED(hrc))
+ {
+ vrc = VERR_INVALID_STATE;
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ RTMemFree(pszId);
+ RTMemFree(pvData);
+
+ LogRelFlowFunc(("vrc %Rrc\n", vrc));
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(int)
+EmulatedUSB::i_eusbCallback(void *pv, const char *pszId, uint32_t iEvent, const void *pvData, uint32_t cbData)
+{
+ /* Make a copy of parameters, forward to EMT and leave the callback to not hold any lock in the device. */
+ int vrc = VINF_SUCCESS;
+ void *pvDataCopy = NULL;
+ if (cbData > 0)
+ {
+ pvDataCopy = RTMemDup(pvData, cbData);
+ if (!pvDataCopy)
+ vrc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ void *pvIdCopy = RTMemDup(pszId, strlen(pszId) + 1);
+ if (pvIdCopy)
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ EmulatedUSB *pThis = (EmulatedUSB *)pv;
+ Console::SafeVMPtr ptrVM(pThis->m.pConsole);
+ if (ptrVM.isOk())
+ {
+ /* No wait. */
+ vrc = ptrVM.vtable()->pfnVMR3ReqCallNoWaitU(ptrVM.rawUVM(), 0 /* idDstCpu */,
+ (PFNRT)EmulatedUSB::eusbCallbackEMT, 5,
+ pThis, pvIdCopy, iEvent, pvDataCopy, cbData);
+ if (RT_SUCCESS(vrc))
+ return vrc;
+ }
+ else
+ vrc = VERR_INVALID_STATE;
+ }
+ RTMemFree(pvIdCopy);
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ RTMemFree(pvDataCopy);
+ }
+ return vrc;
+}
+
+/*static*/
+DECLCALLBACK(int) EmulatedUSB::i_QueryEmulatedUsbDataById(void *pvUser, const char *pszId, void **ppvEmUsbCb, void **ppvEmUsbCbData, void **ppvObject)
+{
+ EmulatedUSB *pEmUsb = (EmulatedUSB *)pvUser;
+
+ AutoReadLock alock(pEmUsb COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it;
+ for (it = pEmUsb->m.webcams.begin(); it != pEmUsb->m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p->HasId(pszId))
+ {
+ if (ppvEmUsbCb)
+ *ppvEmUsbCb = (void *)EmulatedUSB::i_eusbCallback;
+ if (ppvEmUsbCbData)
+ *ppvEmUsbCbData = pEmUsb;
+ if (ppvObject)
+ *ppvObject = p->getObjectPtr();
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+HRESULT EmulatedUSB::webcamPathFromId(com::Utf8Str *pPath, const char *pszId)
+{
+ HRESULT hrc = S_OK;
+
+ Console::SafeVMPtr ptrVM(m.pConsole);
+ if (ptrVM.isOk())
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ WebcamsMap::const_iterator it;
+ for (it = m.webcams.begin(); it != m.webcams.end(); ++it)
+ {
+ EUSBWEBCAM *p = it->second;
+ if (p->HasId(pszId))
+ {
+ *pPath = it->first;
+ break;
+ }
+ }
+
+ if (it == m.webcams.end())
+ {
+ hrc = E_FAIL;
+ }
+ alock.release();
+ }
+ else
+ {
+ hrc = VBOX_E_INVALID_VM_STATE;
+ }
+
+ return hrc;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/GuestCtrlImpl.cpp b/src/VBox/Main/src-client/GuestCtrlImpl.cpp
new file mode 100644
index 00000000..a82084e8
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestCtrlImpl.cpp
@@ -0,0 +1,725 @@
+/* $Id: GuestCtrlImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: Guest
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include "GuestSessionImpl.h"
+# include "GuestSessionImplTasks.h"
+# include "GuestCtrlImplPrivate.h"
+#endif
+
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#include "VBoxEvents.h"
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/VMMDev.h>
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include <VBox/com/array.h>
+# include <VBox/com/ErrorInfo.h>
+#endif
+#include <iprt/cpp/utils.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/list.h>
+#include <iprt/path.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/AssertGuest.h>
+
+#include <memory>
+
+
+/*
+ * This #ifdef goes almost to the end of the file where there are a couple of
+ * IGuest method implementations.
+ */
+#ifdef VBOX_WITH_GUEST_CONTROL
+
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Static callback function for receiving updates on guest control messages
+ * from the guest. Acts as a dispatcher for the actual class instance.
+ *
+ * @returns VBox status code.
+ * @param pvExtension Pointer to HGCM service extension.
+ * @param idMessage HGCM message ID the callback was called for.
+ * @param pvData Pointer to user-supplied callback data.
+ * @param cbData Size (in bytes) of user-supplied callback data.
+ */
+/* static */
+DECLCALLBACK(int) Guest::i_notifyCtrlDispatcher(void *pvExtension,
+ uint32_t idMessage,
+ void *pvData,
+ uint32_t cbData)
+{
+ using namespace guestControl;
+
+ /*
+ * No locking, as this is purely a notification which does not make any
+ * changes to the object state.
+ */
+ Log2Func(("pvExtension=%p, idMessage=%RU32, pvParms=%p, cbParms=%RU32\n", pvExtension, idMessage, pvData, cbData));
+
+ ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
+ AssertReturn(pGuest.isNotNull(), VERR_WRONG_ORDER);
+
+ /*
+ * The data packet should ever be a problem, but check to be sure.
+ */
+ AssertMsgReturn(cbData == sizeof(VBOXGUESTCTRLHOSTCALLBACK),
+ ("Guest control host callback data has wrong size (expected %zu, got %zu) - buggy host service!\n",
+ sizeof(VBOXGUESTCTRLHOSTCALLBACK), cbData), VERR_INVALID_PARAMETER);
+ PVBOXGUESTCTRLHOSTCALLBACK pSvcCb = (PVBOXGUESTCTRLHOSTCALLBACK)pvData;
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ /*
+ * Deal with GUEST_MSG_REPORT_FEATURES here as it shouldn't be handed
+ * i_dispatchToSession() and has different parameters.
+ */
+ if (idMessage == GUEST_MSG_REPORT_FEATURES)
+ {
+ Assert(pSvcCb->mParms == 2);
+ Assert(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_64BIT);
+ Assert(pSvcCb->mpaParms[1].type == VBOX_HGCM_SVC_PARM_64BIT);
+ Assert(pSvcCb->mpaParms[1].u.uint64 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE);
+ pGuest->mData.mfGuestFeatures0 = pSvcCb->mpaParms[0].u.uint64;
+ pGuest->mData.mfGuestFeatures1 = pSvcCb->mpaParms[1].u.uint64;
+ LogRel(("Guest Control: GUEST_MSG_REPORT_FEATURES: %#RX64, %#RX64\n",
+ pGuest->mData.mfGuestFeatures0, pGuest->mData.mfGuestFeatures1));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * For guest control 2.0 using the legacy messages we need to do the following here:
+ * - Get the callback header to access the context ID
+ * - Get the context ID of the callback
+ * - Extract the session ID out of the context ID
+ * - Dispatch the whole stuff to the appropriate session (if still exists)
+ *
+ * At least context ID parameter must always be present.
+ */
+ ASSERT_GUEST_RETURN(pSvcCb->mParms > 0, VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_RETURN(pSvcCb->mpaParms[0].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%d\n", pSvcCb->mpaParms[0].type), VERR_WRONG_PARAMETER_TYPE);
+ uint32_t const idContext = pSvcCb->mpaParms[0].u.uint32;
+
+ VBOXGUESTCTRLHOSTCBCTX CtxCb = { idMessage, idContext };
+ int vrc = pGuest->i_dispatchToSession(&CtxCb, pSvcCb);
+
+ Log2Func(("CID=%#x, idSession=%RU32, uObject=%RU32, uCount=%RU32, vrc=%Rrc\n",
+ idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext), VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(idContext),
+ VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(idContext), vrc));
+ return vrc;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Dispatches a host service callback to the appropriate guest control session object.
+ *
+ * @returns VBox status code.
+ * @param pCtxCb Pointer to host callback context.
+ * @param pSvcCb Pointer to callback parameters.
+ */
+int Guest::i_dispatchToSession(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ Log2Func(("uMessage=%RU32, uContextID=%RU32, uProtocol=%RU32\n", pCtxCb->uMessage, pCtxCb->uContextID, pCtxCb->uProtocol));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t uSessionID = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtxCb->uContextID);
+
+ Log2Func(("uSessionID=%RU32 (%zu total)\n", uSessionID, mData.mGuestSessions.size()));
+
+ GuestSessions::const_iterator itSession = mData.mGuestSessions.find(uSessionID);
+
+ int vrc;
+ if (itSession != mData.mGuestSessions.end())
+ {
+ ComObjPtr<GuestSession> pSession(itSession->second);
+ Assert(!pSession.isNull());
+
+ alock.release();
+
+#ifdef DEBUG
+ /*
+ * Pre-check: If we got a status message with an error and VERR_TOO_MUCH_DATA
+ * it means that that guest could not handle the entire message
+ * because of its exceeding size. This should not happen on daily
+ * use but testcases might try this. It then makes no sense to dispatch
+ * this further because we don't have a valid context ID.
+ */
+ bool fDispatch = true;
+ vrc = VERR_INVALID_FUNCTION;
+ if ( pCtxCb->uMessage == GUEST_MSG_EXEC_STATUS
+ && pSvcCb->mParms >= 5)
+ {
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCb->mpaParms[1], &dataCb.uPID);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[2], &dataCb.uStatus);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[3], &dataCb.uFlags);
+ HGCMSvcGetPv(&pSvcCb->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+
+ if ( dataCb.uStatus == PROC_STS_ERROR
+ && (int32_t)dataCb.uFlags == VERR_TOO_MUCH_DATA)
+ {
+ LogFlowFunc(("Requested message with too much data, skipping dispatching ...\n"));
+ Assert(dataCb.uPID == 0);
+ fDispatch = false;
+ }
+ }
+ if (fDispatch)
+#endif
+ {
+ switch (pCtxCb->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+
+ /* Process stuff. */
+ case GUEST_MSG_EXEC_STATUS:
+ case GUEST_MSG_EXEC_OUTPUT:
+ case GUEST_MSG_EXEC_INPUT_STATUS:
+ case GUEST_MSG_EXEC_IO_NOTIFY:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+
+ /* File stuff. */
+ case GUEST_MSG_FILE_NOTIFY:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+
+ /* Session stuff. */
+ case GUEST_MSG_SESSION_NOTIFY:
+ vrc = pSession->i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+
+ default:
+ vrc = pSession->i_dispatchToObject(pCtxCb, pSvcCb);
+ break;
+ }
+ }
+ }
+ else
+ vrc = VERR_INVALID_SESSION_ID;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a new guest session.
+ * This will invoke VBoxService running on the guest creating a new (dedicated) guest session
+ * On older Guest Additions this call has no effect on the guest, and only the credentials will be
+ * used for starting/impersonating guest processes.
+ *
+ * @returns VBox status code.
+ * @param ssInfo Guest session startup information.
+ * @param guestCreds Guest OS (user) credentials to use on the guest for creating the session.
+ * The specified user must be able to logon to the guest and able to start new processes.
+ * @param pGuestSession Where to store the created guest session on success.
+ *
+ * @note Takes the write lock.
+ */
+int Guest::i_sessionCreate(const GuestSessionStartupInfo &ssInfo,
+ const GuestCredentials &guestCreds, ComObjPtr<GuestSession> &pGuestSession)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VERR_MAX_PROCS_REACHED;
+ if (mData.mGuestSessions.size() >= VBOX_GUESTCTRL_MAX_SESSIONS)
+ return vrc;
+
+ try
+ {
+ /* Create a new session ID and assign it. */
+ uint32_t uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE;
+ uint32_t uTries = 0;
+
+ for (;;)
+ {
+ /* Is the context ID already used? */
+ if (!i_sessionExists(uNewSessionID))
+ {
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ uNewSessionID++;
+ if (uNewSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS)
+ uNewSessionID = VBOX_GUESTCTRL_SESSION_ID_BASE;
+
+ if (++uTries == VBOX_GUESTCTRL_MAX_SESSIONS)
+ break; /* Don't try too hard. */
+ }
+ if (RT_FAILURE(vrc)) throw vrc;
+
+ /* Create the session object. */
+ HRESULT hrc = pGuestSession.createObject();
+ if (FAILED(hrc)) throw VERR_COM_UNEXPECTED;
+
+ /** @todo Use an overloaded copy operator. Later. */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mID = uNewSessionID; /* Assign new session ID. */
+ startupInfo.mName = ssInfo.mName;
+ startupInfo.mOpenFlags = ssInfo.mOpenFlags;
+ startupInfo.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS;
+
+ GuestCredentials guestCredentials;
+ if (!guestCreds.mUser.isEmpty())
+ {
+ /** @todo Use an overloaded copy operator. Later. */
+ guestCredentials.mUser = guestCreds.mUser;
+ guestCredentials.mPassword = guestCreds.mPassword;
+ guestCredentials.mDomain = guestCreds.mDomain;
+ }
+ else
+ {
+ /* Internal (annonymous) session. */
+ startupInfo.mIsInternal = true;
+ }
+
+ vrc = pGuestSession->init(this, startupInfo, guestCredentials);
+ if (RT_FAILURE(vrc)) throw vrc;
+
+ /*
+ * Add session object to our session map. This is necessary
+ * before calling openSession because the guest calls back
+ * with the creation result of this session.
+ */
+ mData.mGuestSessions[uNewSessionID] = pGuestSession;
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionRegisteredEvent(mEventSource, pGuestSession, true /* Registered */);
+ }
+ catch (int vrc2)
+ {
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Destroys a given guest session and removes it from the internal list.
+ *
+ * @returns VBox status code.
+ * @param uSessionID ID of the guest control session to destroy.
+ *
+ * @note Takes the write lock.
+ */
+int Guest::i_sessionDestroy(uint32_t uSessionID)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VERR_NOT_FOUND;
+
+ LogFlowThisFunc(("Destroying session (ID=%RU32) ...\n", uSessionID));
+
+ GuestSessions::iterator itSessions = mData.mGuestSessions.find(uSessionID);
+ if (itSessions == mData.mGuestSessions.end())
+ return VERR_NOT_FOUND;
+
+ /* Make sure to consume the pointer before the one of the
+ * iterator gets released. */
+ ComObjPtr<GuestSession> pSession = itSessions->second;
+
+ LogFlowThisFunc(("Removing session %RU32 (now total %ld sessions)\n",
+ uSessionID, mData.mGuestSessions.size() ? mData.mGuestSessions.size() - 1 : 0));
+
+ vrc = pSession->i_onRemove();
+ mData.mGuestSessions.erase(itSessions);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionRegisteredEvent(mEventSource, pSession, false /* Unregistered */);
+ pSession.setNull();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns whether a guest control session with a specific ID exists or not.
+ *
+ * @returns Returns \c true if the session exists, \c false if not.
+ * @param uSessionID ID to check for.
+ *
+ * @note No locking done, as inline function!
+ */
+inline bool Guest::i_sessionExists(uint32_t uSessionID)
+{
+ GuestSessions::const_iterator itSessions = mData.mGuestSessions.find(uSessionID);
+ return (itSessions == mData.mGuestSessions.end()) ? false : true;
+}
+
+#endif /* VBOX_WITH_GUEST_CONTROL */
+
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Guest::createSession(const com::Utf8Str &aUser, const com::Utf8Str &aPassword, const com::Utf8Str &aDomain,
+ const com::Utf8Str &aSessionName, ComPtr<IGuestSession> &aGuestSession)
+
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /* 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 hrc = S_OK;
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hrc = 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:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::findSession(const com::Utf8Str &aSessionName, std::vector<ComPtr<IGuestSession> > &aSessions)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ LogFlowFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strName(aSessionName);
+ std::list < ComObjPtr<GuestSession> > listSessions;
+
+ GuestSessions::const_iterator itSessions = mData.mGuestSessions.begin();
+ while (itSessions != mData.mGuestSessions.end())
+ {
+ if (strName.contains(itSessions->second->i_getName())) /** @todo Use a (simple) pattern match (IPRT?). */
+ listSessions.push_back(itSessions->second);
+ ++itSessions;
+ }
+
+ LogFlowFunc(("Sessions with \"%s\" = %RU32\n",
+ aSessionName.c_str(), listSessions.size()));
+
+ aSessions.resize(listSessions.size());
+ if (!listSessions.empty())
+ {
+ size_t i = 0;
+ for (std::list < ComObjPtr<GuestSession> >::const_iterator it = listSessions.begin(); it != listSessions.end(); ++it, ++i)
+ (*it).queryInterfaceTo(aSessions[i].asOutParam());
+
+ return S_OK;
+
+ }
+
+ return setErrorNoLog(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Could not find sessions with name '%s'"),
+ aSessionName.c_str());
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::shutdown(const std::vector<GuestShutdownFlag_T> &aFlags)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ /* Validate flags. */
+ uint32_t fFlags = GuestShutdownFlag_None;
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); ++i)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = GuestShutdownFlag_None
+ | GuestShutdownFlag_PowerOff | GuestShutdownFlag_Reboot | GuestShutdownFlag_Force;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+
+ if ( (fFlags & GuestShutdownFlag_PowerOff)
+ && (fFlags & GuestShutdownFlag_Reboot))
+ return setError(E_INVALIDARG, tr("Invalid combination of flags (%#x)"), fFlags);
+
+ Utf8Str strAction = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting") : tr("Shutting down");
+
+ /*
+ * Create an anonymous session. This is required to run shutting down / rebooting
+ * the guest with administrative rights.
+ */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mName = (fFlags & GuestShutdownFlag_Reboot) ? tr("Rebooting guest") : tr("Shutting down guest");
+
+ GuestCredentials guestCreds;
+
+ HRESULT hrc = S_OK;
+
+ ComObjPtr<GuestSession> pSession;
+ int vrc = i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR;
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pSession->i_shutdown(fFlags, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc,
+ tr("%s not supported by installed Guest Additions"), strAction.c_str());
+ break;
+
+ default:
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Error %s guest: %Rrc"), strAction.c_str(), vrc);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc);
+ }
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"),
+ VBOX_GUESTCTRL_MAX_SESSIONS);
+ break;
+
+ /** @todo Add more errors here. */
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
+HRESULT Guest::updateGuestAdditions(const com::Utf8Str &aSource, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<AdditionsUpdateFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_GUEST_CONTROL */
+
+ /* Validate flags. */
+ uint32_t fFlags = AdditionsUpdateFlag_None;
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); ++i)
+ fFlags |= aFlags[i];
+
+ if (fFlags && !(fFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
+ return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags);
+
+
+ /* Copy arguments into aArgs: */
+ ProcessArguments aArgs;
+ try
+ {
+ aArgs.resize(0);
+ for (size_t i = 0; i < aArguments.size(); ++i)
+ aArgs.push_back(aArguments[i]);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ /*
+ * Create an anonymous session. This is required to run the Guest Additions
+ * update process with administrative rights.
+ */
+ GuestSessionStartupInfo startupInfo;
+ startupInfo.mName = "Updating Guest Additions";
+
+ GuestCredentials guestCreds;
+
+ HRESULT hrc;
+ ComObjPtr<GuestSession> pSession;
+ int vrc = i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR;
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Create the update task.
+ */
+ GuestSessionTaskUpdateAdditions *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskUpdateAdditions(pSession /* GuestSession */, aSource, aArgs, fFlags);
+ hrc = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to create SessionTaskUpdateAdditions object"));
+ }
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Updating Guest Additions")));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->GetProgressObject();
+
+ /*
+ * Kick off the thread. Note! consumes pTask!
+ */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for updating Guest Additions on the guest failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Failed to initialize SessionTaskUpdateAdditions object"));
+ delete pTask;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not open guest session: %Rrc"), vrc);
+ }
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_MAX_PROCS_REACHED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Maximum number of concurrent guest sessions (%d) reached"),
+ VBOX_GUESTCTRL_MAX_SESSIONS);
+ break;
+
+ /** @todo Add more errors here. */
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Could not create guest session: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
diff --git a/src/VBox/Main/src-client/GuestCtrlPrivate.cpp b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
new file mode 100644
index 00000000..d05a5def
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
@@ -0,0 +1,1985 @@
+/* $Id: GuestCtrlPrivate.cpp $ */
+/** @file
+ * Internal helpers/structures for guest control functionality.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestCtrlImplPrivate.h"
+#include "GuestSessionImpl.h"
+#include "VMMDev.h"
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/ctype.h>
+#ifdef DEBUG
+# include <iprt/file.h>
+#endif
+#include <iprt/fs.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/time.h>
+#include <VBox/AssertGuest.h>
+
+
+/**
+ * Extracts the timespec from a given stream block key.
+ *
+ * @return Pointer to handed-in timespec, or NULL if invalid / not found.
+ * @param strmBlk Stream block to extract timespec from.
+ * @param strKey Key to get timespec for.
+ * @param pTimeSpec Where to store the extracted timespec.
+ */
+/* static */
+PRTTIMESPEC GuestFsObjData::TimeSpecFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey, PRTTIMESPEC pTimeSpec)
+{
+ AssertPtrReturn(pTimeSpec, NULL);
+
+ Utf8Str strTime = strmBlk.GetString(strKey.c_str());
+ if (strTime.isEmpty())
+ return NULL;
+
+ if (!RTTimeSpecFromString(pTimeSpec, strTime.c_str()))
+ return NULL;
+
+ return pTimeSpec;
+}
+
+/**
+ * Extracts the nanoseconds relative from Unix epoch for a given stream block key.
+ *
+ * @return Nanoseconds relative from Unix epoch, or 0 if invalid / not found.
+ * @param strmBlk Stream block to extract nanoseconds from.
+ * @param strKey Key to get nanoseconds for.
+ */
+/* static */
+int64_t GuestFsObjData::UnixEpochNsFromKey(const GuestProcessStreamBlock &strmBlk, const Utf8Str &strKey)
+{
+ RTTIMESPEC TimeSpec;
+ if (!GuestFsObjData::TimeSpecFromKey(strmBlk, strKey, &TimeSpec))
+ return 0;
+
+ return TimeSpec.i64NanosecondsRelativeToUnixEpoch;
+}
+
+/**
+ * Initializes this object data with a stream block from VBOXSERVICE_TOOL_LS.
+ *
+ * This is also used by FromStat since the output should be identical given that
+ * they use the same output function on the guest side when fLong is true.
+ *
+ * @return VBox status code.
+ * @param strmBlk Stream block to use for initialization.
+ * @param fLong Whether the stream block contains long (detailed) information or not.
+ */
+int GuestFsObjData::FromLs(const GuestProcessStreamBlock &strmBlk, bool fLong)
+{
+ LogFlowFunc(("\n"));
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+
+ /* Object name. */
+ mName = strmBlk.GetString("name");
+ ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND);
+
+ /* Type & attributes. */
+ bool fHaveAttribs = false;
+ char szAttribs[32];
+ memset(szAttribs, '?', sizeof(szAttribs) - 1);
+ mType = FsObjType_Unknown;
+ const char *psz = strmBlk.GetString("ftype");
+ if (psz)
+ {
+ fHaveAttribs = true;
+ szAttribs[0] = *psz;
+ switch (*psz)
+ {
+ case '-': mType = FsObjType_File; break;
+ case 'd': mType = FsObjType_Directory; break;
+ case 'l': mType = FsObjType_Symlink; break;
+ case 'c': mType = FsObjType_DevChar; break;
+ case 'b': mType = FsObjType_DevBlock; break;
+ case 'f': mType = FsObjType_Fifo; break;
+ case 's': mType = FsObjType_Socket; break;
+ case 'w': mType = FsObjType_WhiteOut; break;
+ default:
+ AssertMsgFailed(("%s\n", psz));
+ szAttribs[0] = '?';
+ fHaveAttribs = false;
+ break;
+ }
+ }
+ psz = strmBlk.GetString("owner_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[1] = psz[0];
+ szAttribs[2] = psz[1];
+ szAttribs[3] = psz[2];
+ fHaveAttribs = true;
+ }
+ psz = strmBlk.GetString("group_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[4] = psz[0];
+ szAttribs[5] = psz[1];
+ szAttribs[6] = psz[2];
+ fHaveAttribs = true;
+ }
+ psz = strmBlk.GetString("other_mask");
+ if ( psz
+ && (psz[0] == '-' || psz[0] == 'r')
+ && (psz[1] == '-' || psz[1] == 'w')
+ && (psz[2] == '-' || psz[2] == 'x'))
+ {
+ szAttribs[7] = psz[0];
+ szAttribs[8] = psz[1];
+ szAttribs[9] = psz[2];
+ fHaveAttribs = true;
+ }
+ szAttribs[10] = ' '; /* Reserve three chars for sticky bits. */
+ szAttribs[11] = ' ';
+ szAttribs[12] = ' ';
+ szAttribs[13] = ' '; /* Separator. */
+ psz = strmBlk.GetString("dos_mask");
+ if ( psz
+ && (psz[ 0] == '-' || psz[ 0] == 'R')
+ && (psz[ 1] == '-' || psz[ 1] == 'H')
+ && (psz[ 2] == '-' || psz[ 2] == 'S')
+ && (psz[ 3] == '-' || psz[ 3] == 'D')
+ && (psz[ 4] == '-' || psz[ 4] == 'A')
+ && (psz[ 5] == '-' || psz[ 5] == 'd')
+ && (psz[ 6] == '-' || psz[ 6] == 'N')
+ && (psz[ 7] == '-' || psz[ 7] == 'T')
+ && (psz[ 8] == '-' || psz[ 8] == 'P')
+ && (psz[ 9] == '-' || psz[ 9] == 'J')
+ && (psz[10] == '-' || psz[10] == 'C')
+ && (psz[11] == '-' || psz[11] == 'O')
+ && (psz[12] == '-' || psz[12] == 'I')
+ && (psz[13] == '-' || psz[13] == 'E'))
+ {
+ memcpy(&szAttribs[14], psz, 14);
+ fHaveAttribs = true;
+ }
+ szAttribs[28] = '\0';
+ if (fHaveAttribs)
+ mFileAttrs = szAttribs;
+
+ /* Object size. */
+ int vrc = strmBlk.GetInt64Ex("st_size", &mObjectSize);
+ ASSERT_GUEST_RC_RETURN(vrc, vrc);
+ strmBlk.GetInt64Ex("alloc", &mAllocatedSize);
+
+ /* INode number and device. */
+ psz = strmBlk.GetString("node_id");
+ if (!psz)
+ psz = strmBlk.GetString("cnode_id"); /* copy & past error fixed in 6.0 RC1 */
+ if (psz)
+ mNodeID = RTStrToInt64(psz);
+ mNodeIDDevice = strmBlk.GetUInt32("inode_dev"); /* (Produced by GAs prior to 6.0 RC1.) */
+
+ if (fLong)
+ {
+ /* Dates. */
+ mAccessTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_atime");
+ mBirthTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_birthtime");
+ mChangeTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_ctime");
+ mModificationTime = GuestFsObjData::UnixEpochNsFromKey(strmBlk, "st_mtime");
+
+ /* Owner & group. */
+ mUID = strmBlk.GetInt32("uid");
+ psz = strmBlk.GetString("username");
+ if (psz)
+ mUserName = psz;
+ mGID = strmBlk.GetInt32("gid");
+ psz = strmBlk.GetString("groupname");
+ if (psz)
+ mGroupName = psz;
+
+ /* Misc attributes: */
+ mNumHardLinks = strmBlk.GetUInt32("hlinks", 1);
+ mDeviceNumber = strmBlk.GetUInt32("st_rdev");
+ mGenerationID = strmBlk.GetUInt32("st_gen");
+ mUserFlags = strmBlk.GetUInt32("st_flags");
+
+ /** @todo ACL */
+ }
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Parses stream block output data which came from the 'rm' (vbox_rm)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromRm(const GuestProcessStreamBlock &strmBlk)
+{
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+ /* Object name. */
+ mName = strmBlk.GetString("fname"); /* Note: RTPathRmCmd() only sets this on failure. */
+
+ /* Return the stream block's vrc. */
+ return strmBlk.GetVrc(true /* fSucceedIfNotFound */);
+}
+
+/**
+ * Parses stream block output data which came from the 'stat' (vbox_stat)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromStat(const GuestProcessStreamBlock &strmBlk)
+{
+ /* Should be identical output. */
+ return GuestFsObjData::FromLs(strmBlk, true /*fLong*/);
+}
+
+/**
+ * Parses stream block output data which came from the 'mktemp' (vbox_mktemp)
+ * VBoxService toolbox command. The result will be stored in this object.
+ *
+ * @returns VBox status code.
+ * @param strmBlk Stream block output data to parse.
+ */
+int GuestFsObjData::FromMkTemp(const GuestProcessStreamBlock &strmBlk)
+{
+ LogFlowFunc(("\n"));
+
+#ifdef DEBUG
+ strmBlk.DumpToLog();
+#endif
+ /* Object name. */
+ mName = strmBlk.GetString("name");
+ ASSERT_GUEST_RETURN(mName.isNotEmpty(), VERR_NOT_FOUND);
+
+ /* Assign the stream block's vrc. */
+ int const vrc = strmBlk.GetVrc();
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * 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)
+ : m_fComplete(false) { }
+
+GuestProcessStreamBlock::~GuestProcessStreamBlock()
+{
+ Clear();
+}
+
+/**
+ * Clears (destroys) the currently stored stream pairs.
+ */
+void GuestProcessStreamBlock::Clear(void)
+{
+ m_fComplete = false;
+ m_mapPairs.clear();
+}
+
+#ifdef DEBUG
+/**
+ * Dumps the currently stored stream pairs to the (debug) log.
+ */
+void GuestProcessStreamBlock::DumpToLog(void) const
+{
+ LogFlowFunc(("Dumping contents of stream block=0x%p (%ld items, fComplete=%RTbool):\n",
+ this, m_mapPairs.size(), m_fComplete));
+
+ for (GuestCtrlStreamPairMapIterConst it = m_mapPairs.begin();
+ it != m_mapPairs.end(); ++it)
+ {
+ LogFlowFunc(("\t%s=%s\n", it->first.c_str(), it->second.mValue.c_str()));
+ }
+}
+#endif
+
+/**
+ * Returns a 64-bit signed integer of a specified key.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if key was not found.
+ * @param pszKey Name of key to get the value for.
+ * @param piVal Pointer to value to return.
+ */
+int GuestProcessStreamBlock::GetInt64Ex(const char *pszKey, int64_t *piVal) const
+{
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrReturn(piVal, VERR_INVALID_POINTER);
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ *piVal = RTStrToInt64(pszValue);
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a 64-bit integer of a specified key.
+ *
+ * @return int64_t Value to return, 0 if not found / on failure.
+ * @param pszKey Name of key to get the value for.
+ */
+int64_t GuestProcessStreamBlock::GetInt64(const char *pszKey) const
+{
+ int64_t iVal;
+ if (RT_SUCCESS(GetInt64Ex(pszKey, &iVal)))
+ return iVal;
+ return 0;
+}
+
+/**
+ * Returns the current number of stream pairs.
+ *
+ * @return uint32_t Current number of stream pairs.
+ */
+size_t GuestProcessStreamBlock::GetCount(void) const
+{
+ return m_mapPairs.size();
+}
+
+/**
+ * Gets the return code (name = "rc") of this stream block.
+ *
+ * @return VBox status code.
+ * @retval VERR_NOT_FOUND if the return code string ("rc") was not found.
+ * @param fSucceedIfNotFound When set to @c true, this reports back VINF_SUCCESS when the key ("rc") is not found.
+ * This can happen with some (older) IPRT-provided tools such as RTPathRmCmd(), which only outputs
+ * rc on failure but not on success. Defaults to @c false.
+ */
+int GuestProcessStreamBlock::GetVrc(bool fSucceedIfNotFound /* = false */) const
+{
+ const char *pszValue = GetString("rc");
+ if (pszValue)
+ return RTStrToInt16(pszValue);
+ if (fSucceedIfNotFound)
+ return VINF_SUCCESS;
+ /** @todo We probably should have a dedicated error for that, VERR_GSTCTL_GUEST_TOOLBOX_whatever. */
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a string value of a specified key.
+ *
+ * @return uint32_t Pointer to string to return, NULL if not found / on failure.
+ * @param pszKey Name of key to get the value for.
+ */
+const char *GuestProcessStreamBlock::GetString(const char *pszKey) const
+{
+ AssertPtrReturn(pszKey, NULL);
+
+ try
+ {
+ GuestCtrlStreamPairMapIterConst itPairs = m_mapPairs.find(pszKey);
+ if (itPairs != m_mapPairs.end())
+ return itPairs->second.mValue.c_str();
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ }
+ return NULL;
+}
+
+/**
+ * Returns a 32-bit unsigned integer of a specified key.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if key was not found.
+ * @param pszKey Name of key to get the value for.
+ * @param puVal Pointer to value to return.
+ */
+int GuestProcessStreamBlock::GetUInt32Ex(const char *pszKey, uint32_t *puVal) const
+{
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ *puVal = RTStrToUInt32(pszValue);
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Returns a 32-bit signed integer of a specified key.
+ *
+ * @returns 32-bit signed value
+ * @param pszKey Name of key to get the value for.
+ * @param iDefault The default to return on error if not found.
+ */
+int32_t GuestProcessStreamBlock::GetInt32(const char *pszKey, int32_t iDefault) const
+{
+ const char *pszValue = GetString(pszKey);
+ if (pszValue)
+ {
+ int32_t iRet;
+ int vrc = RTStrToInt32Full(pszValue, 0, &iRet);
+ if (RT_SUCCESS(vrc))
+ 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. Extended version.
+ *
+ * @return VBox status code.
+ * @param pszKey Key name to process.
+ * @param cwcKey Maximum characters of \a pszKey to process.
+ * @param pszValue Value to set. Set NULL for deleting the key.
+ * @param cwcValue Maximum characters of \a pszValue to process.
+ * @param fOverwrite Whether a key can be overwritten with a new value if it already exists. Will assert otherwise.
+ */
+int GuestProcessStreamBlock::SetValueEx(const char *pszKey, size_t cwcKey, const char *pszValue, size_t cwcValue,
+ bool fOverwrite /* = false */)
+{
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertReturn(cwcKey, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+ try
+ {
+ Utf8Str const strKey(pszKey, cwcKey);
+
+ /* Take a shortcut and prevent crashes on some funny versions
+ * of STL if map is empty initially. */
+ if (!m_mapPairs.empty())
+ {
+ GuestCtrlStreamPairMapIter it = m_mapPairs.find(strKey);
+ if (it != m_mapPairs.end())
+ {
+ if (pszValue == NULL)
+ m_mapPairs.erase(it);
+ else if (!fOverwrite)
+ AssertMsgFailedReturn(("Key '%*s' already exists! Value is '%s'\n", cwcKey, pszKey, m_mapPairs[strKey].mValue.c_str()),
+ VERR_ALREADY_EXISTS);
+ }
+ }
+
+ if (pszValue)
+ {
+ GuestProcessStreamValue val(pszValue, cwcValue);
+ Log3Func(("strKey='%s', strValue='%s'\n", strKey.c_str(), val.mValue.c_str()));
+ m_mapPairs[strKey] = val;
+ }
+ }
+ catch (const std::exception &)
+ {
+ /** @todo set vrc? */
+ }
+ return vrc;
+}
+
+/**
+ * Sets a value to a key or deletes a key by setting a NULL value.
+ *
+ * @return VBox status code.
+ * @param pszKey Key name to process.
+ * @param pszValue Value to set. Set NULL for deleting the key.
+ */
+int GuestProcessStreamBlock::SetValue(const char *pszKey, const char *pszValue)
+{
+ return SetValueEx(pszKey, RTSTR_MAX, pszValue, RTSTR_MAX);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GuestProcessStream::GuestProcessStream(void)
+ : m_cbMax(_32M)
+ , m_cbAllocated(0)
+ , m_cbUsed(0)
+ , m_offBuf(0)
+ , m_pbBuffer(NULL)
+ , m_cBlocks(0) { }
+
+GuestProcessStream::~GuestProcessStream(void)
+{
+ Destroy();
+}
+
+/**
+ * Adds data to the internal parser buffer. Useful if there
+ * are multiple rounds of adding data needed.
+ *
+ * @return VBox status code. Will return VERR_TOO_MUCH_DATA if the buffer's maximum (limit) has been reached.
+ * @param pbData Pointer to data to add.
+ * @param cbData Size (in bytes) of data to add.
+ */
+int GuestProcessStream::AddData(const BYTE *pbData, size_t cbData)
+{
+ AssertPtrReturn(pbData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ /* Rewind the buffer if it's empty. */
+ size_t cbInBuf = m_cbUsed - m_offBuf;
+ bool const fAddToSet = cbInBuf == 0;
+ if (fAddToSet)
+ m_cbUsed = m_offBuf = 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_offBuf;
+ if (cbInBuf == 0)
+ m_cbUsed = m_offBuf = 0;
+ else if (m_offBuf) /* Do we have something to move? */
+ {
+ memmove(m_pbBuffer, &m_pbBuffer[m_offBuf], cbInBuf);
+ m_cbUsed = cbInBuf;
+ m_offBuf = 0;
+ }
+
+ /* Do we need to grow the buffer? */
+ if (cbData + m_cbUsed > m_cbAllocated)
+ {
+ size_t cbAlloc = m_cbUsed + cbData;
+ if (cbAlloc <= m_cbMax)
+ {
+ cbAlloc = RT_ALIGN_Z(cbAlloc, _64K);
+ void *pvNew = RTMemRealloc(m_pbBuffer, cbAlloc);
+ if (pvNew)
+ {
+ m_pbBuffer = (uint8_t *)pvNew;
+ m_cbAllocated = cbAlloc;
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ vrc = VERR_TOO_MUCH_DATA;
+ }
+
+ /* Finally, copy the data. */
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbData + m_cbUsed <= m_cbAllocated)
+ {
+ memcpy(&m_pbBuffer[m_cbUsed], pbData, cbData);
+ m_cbUsed += cbData;
+ }
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * 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_offBuf = 0;
+ m_cBlocks = 0;
+}
+
+#ifdef DEBUG
+/**
+ * Dumps the raw guest process output to a file on the host.
+ * If the file on the host already exists, it will be overwritten.
+ *
+ * @param pszFile Absolute path to host file to dump the output to.
+ */
+void GuestProcessStream::Dump(const char *pszFile)
+{
+ LogFlowFunc(("Dumping contents of stream=0x%p (cbAlloc=%u, cbSize=%u, cbOff=%u) to %s\n",
+ m_pbBuffer, m_cbAllocated, m_cbUsed, m_offBuf, pszFile));
+
+ RTFILE hFile;
+ int vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, m_pbBuffer, m_cbUsed, NULL /* pcbWritten */);
+ RTFileClose(hFile);
+ }
+}
+#endif /* DEBUG */
+
+/**
+ * Tries to parse the next upcoming pair block within the internal buffer.
+ *
+ * Parsing behavior:
+ * - A stream can contain one or multiple blocks and is terminated by four (4) "\0".
+ * - A block (or "object") contains one or multiple key=value pairs and is terminated with two (2) "\0".
+ * - Each key=value pair is terminated by a single (1) "\0".
+ *
+ * As new data can arrive at a later time eventually completing a pair / block / stream,
+ * the algorithm needs to be careful not intepreting its current data too early. So only skip termination
+ * sequences if we really know that the termination sequence is complete. See comments down below.
+ *
+ * No locking done.
+ *
+ * @return VBox status code.
+ * @retval VINF_EOF if the stream reached its end.
+ * @param streamBlock Reference to guest stream block to fill
+ */
+int GuestProcessStream::ParseBlock(GuestProcessStreamBlock &streamBlock)
+{
+ AssertMsgReturn(streamBlock.m_fComplete == false, ("Block object already marked as being completed\n"), VERR_WRONG_ORDER);
+
+ if ( !m_pbBuffer
+ || !m_cbUsed)
+ return VINF_EOF;
+
+ AssertReturn(m_offBuf <= m_cbUsed, VERR_INVALID_PARAMETER);
+ if (m_offBuf == m_cbUsed)
+ return VINF_EOF;
+
+ char * const pszStart = (char *)&m_pbBuffer[m_offBuf];
+
+ size_t cbLeftParsed = m_offBuf < m_cbUsed ? m_cbUsed - m_offBuf : 0;
+ size_t cbLeftLookAhead = cbLeftParsed;
+
+ char *pszLookAhead = pszStart; /* Look ahead pointer to count terminators. */
+ char *pszParsed = pszStart; /* Points to data considered as being parsed already. */
+
+ Log4Func(("Current @ %zu/%zu:\n%.*Rhxd\n", m_offBuf, m_cbUsed, RT_MIN(cbLeftParsed, _1K), pszStart));
+
+ size_t cTerm = 0;
+
+ /*
+ * We have to be careful when handling single terminators ('\0') here, as we might not know yet
+ * if it's part of a multi-terminator seqeuence.
+ *
+ * So handle and skip those *only* when we hit a non-terminator char again.
+ */
+ int vrc = VINF_SUCCESS;
+ while (cbLeftLookAhead)
+ {
+ /* Count consequtive terminators. */
+ if (*pszLookAhead == GUESTTOOLBOX_STRM_TERM)
+ {
+ cTerm++;
+ pszLookAhead++;
+ cbLeftLookAhead--;
+ continue;
+ }
+
+ pszParsed = pszLookAhead;
+ cbLeftParsed = cbLeftLookAhead;
+
+ /* We hit a non-terminator (again); now interpret where we are, and
+ * bail out if we need to. */
+ if (cTerm >= 2)
+ {
+ Log2Func(("Hit end of termination sequence (%zu)\n", cTerm));
+ break;
+ }
+
+ cTerm = 0; /* Reset consequtive counter. */
+
+ char * const pszPairEnd = RTStrEnd(pszParsed, cbLeftParsed);
+ if (!pszPairEnd) /* No zero terminator found (yet), try next time. */
+ break;
+
+ Log3Func(("Pair '%s' (%u)\n", pszParsed, strlen(pszParsed)));
+
+ Assert(pszPairEnd != pszParsed);
+ size_t const cbPair = (size_t)(pszPairEnd - pszParsed);
+ Assert(cbPair);
+ const char *pszSep = (const char *)memchr(pszParsed, '=', cbPair);
+ if (!pszSep) /* No separator found (yet), try next time. */
+ break;
+
+ /* Skip the separator so that pszSep points to the actual value. */
+ pszSep++;
+
+ char const * const pszKey = pszParsed;
+ char const * const pszVal = pszSep;
+
+ vrc = streamBlock.SetValueEx(pszKey, pszSep - pszKey - 1, pszVal, pszPairEnd - pszVal);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (cbPair >= cbLeftParsed)
+ break;
+
+ /* Accounting for next iteration. */
+ pszParsed = pszPairEnd;
+ Assert(cbLeftParsed >= cbPair);
+ cbLeftParsed -= cbPair;
+
+ pszLookAhead = pszPairEnd;
+ cbLeftLookAhead = cbLeftParsed;
+
+ if (cbLeftParsed)
+ Log4Func(("Next iteration @ %zu:\n%.*Rhxd\n", pszParsed - pszStart, cbLeftParsed, pszParsed));
+ }
+
+ if (cbLeftParsed)
+ Log4Func(("Done @ %zu:\n%.*Rhxd\n", pszParsed - pszStart, cbLeftParsed, pszParsed));
+
+ m_offBuf += pszParsed - pszStart; /* Only account really parsed content. */
+ Assert(m_offBuf <= m_cbUsed);
+
+ /* Did we hit a block or stream termination sequence? */
+ if (cTerm >= GUESTTOOLBOX_STRM_BLK_TERM_CNT)
+ {
+ if (!streamBlock.IsEmpty()) /* Only account and complete blocks which have values in it. */
+ {
+ m_cBlocks++;
+ streamBlock.m_fComplete = true;
+#ifdef DEBUG
+ streamBlock.DumpToLog();
+#endif
+ }
+
+ if (cTerm >= GUESTTOOLBOX_STRM_TERM_CNT)
+ {
+ m_offBuf = m_cbUsed;
+ vrc = VINF_EOF;
+ }
+ }
+
+ LogFlowThisFunc(("cbLeft=%zu, offBuffer=%zu / cbUsed=%zu, cBlocks=%zu, cTerm=%zu -> current block has %RU64 pairs (complete = %RTbool), rc=%Rrc\n",
+ cbLeftParsed, m_offBuf, m_cbUsed, m_cBlocks, cTerm, streamBlock.GetCount(), streamBlock.IsComplete(), vrc));
+
+ return vrc;
+}
+
+GuestBase::GuestBase(void)
+ : mConsole(NULL)
+ , mNextContextID(RTRandU32() % VBOX_GUESTCTRL_MAX_CONTEXTS)
+{
+}
+
+GuestBase::~GuestBase(void)
+{
+}
+
+/**
+ * Separate initialization function for the base class.
+ *
+ * @returns VBox status code.
+ */
+int GuestBase::baseInit(void)
+{
+ int const vrc = RTCritSectInit(&mWaitEventCritSect);
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Separate uninitialization function for the base class.
+ */
+void GuestBase::baseUninit(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Make sure to cancel any outstanding wait events. */
+ int vrc2 = cancelWaitEvents();
+ AssertRC(vrc2);
+
+ vrc2 = RTCritSectDelete(&mWaitEventCritSect);
+ AssertRC(vrc2);
+
+ LogFlowFuncLeaveRC(vrc2);
+ /* No return value. */
+}
+
+/**
+ * Cancels all outstanding wait events.
+ *
+ * @returns VBox status code.
+ */
+int GuestBase::cancelWaitEvents(void)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+ 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 vrc2 = pEvent->Cancel();
+ AssertRC(vrc2);
+
+ ++itEvents;
+ }
+
+ ++itEventGroups;
+ }
+
+ int vrc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Handles generic messages not bound to a specific object type.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if no handler has been found or VERR_NOT_SUPPORTED
+ * if this class does not support the specified callback.
+ * @param pCtxCb Host callback context.
+ * @param pSvcCb Service callback data.
+ */
+int GuestBase::dispatchGeneric(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ int vrc;
+
+ try
+ {
+ Log2Func(("uFunc=%RU32, cParms=%RU32\n", pCtxCb->uMessage, pSvcCb->mParms));
+
+ switch (pCtxCb->uMessage)
+ {
+ case GUEST_MSG_PROGRESS_UPDATE:
+ vrc = VINF_SUCCESS;
+ break;
+
+ case GUEST_MSG_REPLY:
+ {
+ if (pSvcCb->mParms >= 4)
+ {
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_MSG_REPLY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCb->mpaParms[idx++], &dataCb.pvPayload, &dataCb.cbPayload);
+ AssertRCReturn(vrc, vrc);
+
+ try
+ {
+ GuestWaitEventPayload evPayload(dataCb.uType, dataCb.pvPayload, dataCb.cbPayload);
+ vrc = signalWaitEventInternal(pCtxCb, dataCb.rc, &evPayload);
+ }
+ catch (int vrcEx) /* Thrown by GuestWaitEventPayload constructor. */
+ {
+ vrc = vrcEx;
+ }
+ }
+ else
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ catch (int vrcCatch)
+ {
+ vrc = vrcCatch;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Generates a context ID (CID) by incrementing the object's count.
+ * A CID consists of a session ID, an object ID and a count.
+ *
+ * Note: This function does not guarantee that the returned CID is unique;
+ * the caller has to take care of that and eventually retry.
+ *
+ * @returns VBox status code.
+ * @param uSessionID Session ID to use for CID generation.
+ * @param uObjectID Object ID to use for CID generation.
+ * @param puContextID Where to store the generated CID on success.
+ */
+int GuestBase::generateContextID(uint32_t uSessionID, uint32_t uObjectID, uint32_t *puContextID)
+{
+ AssertPtrReturn(puContextID, VERR_INVALID_POINTER);
+
+ if ( uSessionID >= VBOX_GUESTCTRL_MAX_SESSIONS
+ || uObjectID >= VBOX_GUESTCTRL_MAX_OBJECTS)
+ return VERR_INVALID_PARAMETER;
+
+ uint32_t uCount = ASMAtomicIncU32(&mNextContextID);
+ uCount %= VBOX_GUESTCTRL_MAX_CONTEXTS;
+
+ uint32_t uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID, uObjectID, uCount);
+
+ *puContextID = uNewContextID;
+
+#if 0
+ LogFlowThisFunc(("mNextContextID=%RU32, uSessionID=%RU32, uObjectID=%RU32, uCount=%RU32, uNewContextID=%RU32\n",
+ mNextContextID, uSessionID, uObjectID, uCount, uNewContextID));
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers (creates) a new wait event based on a given session and object ID.
+ *
+ * From those IDs an unique context ID (CID) will be built, which only can be
+ * around once at a time.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)).
+ * @param uSessionID Session ID to register wait event for.
+ * @param uObjectID Object ID to register wait event for.
+ * @param ppEvent Pointer to registered (created) wait event on success.
+ * Must be destroyed with unregisterWaitEvent().
+ */
+int GuestBase::registerWaitEvent(uint32_t uSessionID, uint32_t uObjectID, GuestWaitEvent **ppEvent)
+{
+ GuestEventTypes eventTypesEmpty;
+ return registerWaitEventEx(uSessionID, uObjectID, eventTypesEmpty, ppEvent);
+}
+
+/**
+ * Creates and registers a new wait event object that waits on a set of events
+ * related to a given object within the session.
+ *
+ * From the session ID and object ID a one-time unique context ID (CID) is built
+ * for this wait object. Normally the CID is then passed to the guest along
+ * with a request, and the guest passed the CID back with the reply. The
+ * handler for the reply then emits a signal on the event type associated with
+ * the reply, which includes signalling the object returned by this method and
+ * the waking up the thread waiting on it.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_MAX_CID_COUNT_REACHED if unable to generate a free context ID (CID, the count part (bits 15:0)).
+ * @param uSessionID Session ID to register wait event for.
+ * @param uObjectID Object ID to register wait event for.
+ * @param lstEvents List of events to register the wait event for.
+ * @param ppEvent Pointer to registered (created) wait event on success.
+ * Must be destroyed with unregisterWaitEvent().
+ */
+int GuestBase::registerWaitEventEx(uint32_t uSessionID, uint32_t uObjectID, const GuestEventTypes &lstEvents,
+ GuestWaitEvent **ppEvent)
+{
+ AssertPtrReturn(ppEvent, VERR_INVALID_POINTER);
+
+ uint32_t idContext;
+ int vrc = generateContextID(uSessionID, uObjectID, &idContext);
+ AssertRCReturn(vrc, vrc);
+
+ GuestWaitEvent *pEvent = new GuestWaitEvent();
+ AssertPtrReturn(pEvent, VERR_NO_MEMORY);
+
+ vrc = pEvent->Init(idContext, lstEvents);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("New event=%p, CID=%RU32\n", pEvent, idContext));
+
+ vrc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * 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
+ {
+ vrc = generateContextID(uSessionID, uObjectID, &idContext);
+ AssertRCBreak(vrc);
+ LogFunc(("Found context ID duplicate; trying a different context ID: %#x\n", idContext));
+ if (mWaitEvents.find(idContext) != mWaitEvents.end())
+ vrc = VERR_GSTCTL_MAX_CID_COUNT_REACHED;
+ } while (RT_FAILURE_NP(vrc) && cTries++ < 10);
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * 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);
+ }
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ else
+ Assert(cInserts > 0); /* else: lstEvents has duplicate entries. */
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(cInserts > 0 || lstEvents.size() == 0);
+ RT_NOREF(cInserts);
+
+ /*
+ * Register event in the regular event list.
+ */
+ try
+ {
+ mWaitEvents[idContext] = pEvent;
+ }
+ catch (std::bad_alloc &)
+ {
+ for (GuestEventTypes::const_iterator ItType = lstEvents.begin(); ItType != lstEvents.end(); ++ItType)
+ mWaitEventGroups[*ItType].erase(idContext);
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ RTCritSectLeave(&mWaitEventCritSect);
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ *ppEvent = pEvent;
+ return vrc;
+ }
+
+ if (pEvent)
+ delete pEvent;
+
+ return vrc;
+}
+
+/**
+ * Signals all wait events of a specific type (if found)
+ * and notifies external events accordingly.
+ *
+ * @returns VBox status code.
+ * @param aType Event type to signal.
+ * @param aEvent Which external event to notify.
+ */
+int GuestBase::signalWaitEvent(VBoxEventType_T aType, IEvent *aEvent)
+{
+ int vrc = RTCritSectEnter(&mWaitEventCritSect);
+#ifdef DEBUG
+ uint32_t cEvents = 0;
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ GuestEventGroup::iterator itGroup = mWaitEventGroups.find(aType);
+ if (itGroup != mWaitEventGroups.end())
+ {
+ /* Signal all events in the group, leaving the group empty afterwards. */
+ GuestWaitEvents::iterator ItWaitEvt;
+ while ((ItWaitEvt = itGroup->second.begin()) != itGroup->second.end())
+ {
+ LogFlowThisFunc(("Signalling event=%p, type=%ld (CID %#x: Session=%RU32, Object=%RU32, Count=%RU32) ...\n",
+ ItWaitEvt->second, aType, ItWaitEvt->first, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(ItWaitEvt->first),
+ VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(ItWaitEvt->first), VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(ItWaitEvt->first)));
+
+ int vrc2 = ItWaitEvt->second->SignalExternal(aEvent);
+ AssertRC(vrc2);
+
+ /* Take down the wait event object details before we erase it from this list and invalid ItGrpEvt. */
+ const GuestEventTypes &EvtTypes = ItWaitEvt->second->Types();
+ uint32_t idContext = ItWaitEvt->first;
+ itGroup->second.erase(ItWaitEvt);
+
+ for (GuestEventTypes::const_iterator ItType = EvtTypes.begin(); ItType != EvtTypes.end(); ++ItType)
+ {
+ GuestEventGroup::iterator EvtTypeGrp = mWaitEventGroups.find(*ItType);
+ if (EvtTypeGrp != mWaitEventGroups.end())
+ {
+ ItWaitEvt = EvtTypeGrp->second.find(idContext);
+ if (ItWaitEvt != EvtTypeGrp->second.end())
+ {
+ LogFlowThisFunc(("Removing event %p (CID %#x) from type %d group\n", ItWaitEvt->second, idContext, *ItType));
+ EvtTypeGrp->second.erase(ItWaitEvt);
+ LogFlowThisFunc(("%zu events left for type %d\n", EvtTypeGrp->second.size(), *ItType));
+ Assert(EvtTypeGrp->second.find(idContext) == EvtTypeGrp->second.end()); /* no duplicates */
+ }
+ }
+ }
+ }
+ }
+
+ int vrc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+#ifdef DEBUG
+ LogFlowThisFunc(("Signalled %RU32 events, vrc=%Rrc\n", cEvents, vrc));
+#endif
+ return vrc;
+}
+
+/**
+ * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Pointer to host service callback context.
+ * @param vrcGuest Guest return VBox status code to set.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestBase::signalWaitEventInternal(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int vrcGuest, const GuestWaitEventPayload *pPayload)
+{
+ if (RT_SUCCESS(vrcGuest))
+ return signalWaitEventInternalEx(pCbCtx, VINF_SUCCESS, VINF_SUCCESS /* vrcGuest */, pPayload);
+
+ return signalWaitEventInternalEx(pCbCtx, VERR_GSTCTL_GUEST_ERROR, vrcGuest, pPayload);
+}
+
+/**
+ * Signals a wait event which is registered to a specific callback (bound to a context ID (CID)).
+ * Extended version.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Pointer to host service callback context.
+ * @param vrc Return VBox status code to set as wait result.
+ * @param vrcGuest Guest return VBox status code to set additionally, if
+ * vrc is set to VERR_GSTCTL_GUEST_ERROR.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestBase::signalWaitEventInternalEx(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, int vrc, int vrcGuest,
+ const GuestWaitEventPayload *pPayload)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ /* pPayload is optional. */
+
+ int vrc2 = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc2))
+ {
+ GuestWaitEvents::iterator itEvent = mWaitEvents.find(pCbCtx->uContextID);
+ if (itEvent != mWaitEvents.end())
+ {
+ LogFlowThisFunc(("Signalling event=%p (CID %RU32, vrc=%Rrc, vrcGuest=%Rrc, pPayload=%p) ...\n",
+ itEvent->second, itEvent->first, vrc, vrcGuest, pPayload));
+ GuestWaitEvent *pEvent = itEvent->second;
+ AssertPtr(pEvent);
+ vrc2 = pEvent->SignalInternal(vrc, vrcGuest, pPayload);
+ }
+ else
+ vrc2 = VERR_NOT_FOUND;
+
+ int vrc3 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc2))
+ vrc2 = vrc3;
+ }
+
+ return vrc2;
+}
+
+/**
+ * Unregisters (deletes) a wait event.
+ *
+ * After successful unregistration the event will not be valid anymore.
+ *
+ * @returns VBox status code.
+ * @param pWaitEvt Wait event to unregister (delete).
+ */
+int GuestBase::unregisterWaitEvent(GuestWaitEvent *pWaitEvt)
+{
+ if (!pWaitEvt) /* Nothing to unregister. */
+ return VINF_SUCCESS;
+
+ int vrc = RTCritSectEnter(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("pWaitEvt=%p\n", pWaitEvt));
+
+/** @todo r=bird: One way of optimizing this would be to use the pointer
+ * instead of the context ID as index into the groups, i.e. revert the value
+ * pair for the GuestWaitEvents type.
+ *
+ * An even more efficent way, would be to not use sexy std::xxx containers for
+ * the types, but iprt/list.h, as that would just be a RTListNodeRemove call for
+ * each type w/o needing to iterate much at all. I.e. add a struct {
+ * RTLISTNODE, GuestWaitEvent *pSelf} array to GuestWaitEvent, and change
+ * GuestEventGroup to std::map<VBoxEventType_T, RTListAnchorClass>
+ * (RTListAnchorClass == RTLISTANCHOR wrapper with a constructor)).
+ *
+ * P.S. the try/catch is now longer needed after I changed pWaitEvt->Types() to
+ * return a const reference rather than a copy of the type list (and it think it
+ * is safe to assume iterators are not hitting the heap). Copy vs reference is
+ * an easy mistake to make in C++.
+ *
+ * P.P.S. The mWaitEventGroups optimization is probably just a lot of extra work
+ * with little payoff.
+ */
+ try
+ {
+ /* Remove the event from all event type groups. */
+ const GuestEventTypes &lstTypes = pWaitEvt->Types();
+ for (GuestEventTypes::const_iterator itType = lstTypes.begin();
+ itType != lstTypes.end(); ++itType)
+ {
+ /** @todo Slow O(n) lookup. Optimize this. */
+ GuestWaitEvents::iterator itCurEvent = mWaitEventGroups[(*itType)].begin();
+ while (itCurEvent != mWaitEventGroups[(*itType)].end())
+ {
+ if (itCurEvent->second == pWaitEvt)
+ {
+ mWaitEventGroups[(*itType)].erase(itCurEvent);
+ break;
+ }
+ ++itCurEvent;
+ }
+ }
+
+ /* Remove the event from the general event list as well. */
+ GuestWaitEvents::iterator itEvent = mWaitEvents.find(pWaitEvt->ContextID());
+
+ Assert(itEvent != mWaitEvents.end());
+ Assert(itEvent->second == pWaitEvt);
+
+ mWaitEvents.erase(itEvent);
+
+ delete pWaitEvt;
+ pWaitEvt = NULL;
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ AssertFailedStmt(vrc = VERR_NOT_FOUND);
+ }
+
+ int vrc2 = RTCritSectLeave(&mWaitEventCritSect);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ return vrc;
+}
+
+/**
+ * Waits for an already registered guest wait event.
+ *
+ * @return VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get
+ * the actual result.
+ *
+ * @param pWaitEvt Pointer to event to wait for.
+ * @param msTimeout Timeout (in ms) for waiting.
+ * @param pType Event type of following IEvent. Optional.
+ * @param ppEvent Pointer to IEvent which got triggered for this event. Optional.
+ */
+int GuestBase::waitForEvent(GuestWaitEvent *pWaitEvt, uint32_t msTimeout, VBoxEventType_T *pType, IEvent **ppEvent)
+{
+ AssertPtrReturn(pWaitEvt, VERR_INVALID_POINTER);
+ /* pType is optional. */
+ /* ppEvent is optional. */
+
+ int vrc = pWaitEvt->Wait(msTimeout);
+ if (RT_SUCCESS(vrc))
+ {
+ const ComPtr<IEvent> pThisEvent = pWaitEvt->Event();
+ if (pThisEvent.isNotNull()) /* Make sure that we actually have an event associated. */
+ {
+ if (pType)
+ {
+ HRESULT hrc = pThisEvent->COMGETTER(Type)(pType);
+ if (FAILED(hrc))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ if ( RT_SUCCESS(vrc)
+ && ppEvent)
+ pThisEvent.queryInterfaceTo(ppEvent);
+
+ unconst(pThisEvent).setNull();
+ }
+ }
+
+ return vrc;
+}
+
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+/**
+ * Convenience function to return a pre-formatted string using an action description and a guest error information.
+ *
+ * @returns Pre-formatted string with a user-friendly error string.
+ * @param strAction Action of when the error occurred.
+ * @param guestErrorInfo Related guest error information to use.
+ */
+/* static */ Utf8Str GuestBase::getErrorAsString(const Utf8Str& strAction, const GuestErrorInfo& guestErrorInfo)
+{
+ Assert(strAction.isNotEmpty());
+ return Utf8StrFmt("%s: %s", strAction.c_str(), getErrorAsString(guestErrorInfo).c_str());
+}
+
+/**
+ * Returns a user-friendly error message from a given GuestErrorInfo object.
+ *
+ * @returns Error message string.
+ * @param guestErrorInfo Guest error info to return error message for.
+ */
+/* static */ Utf8Str GuestBase::getErrorAsString(const GuestErrorInfo& guestErrorInfo)
+{
+ AssertMsg(RT_FAILURE(guestErrorInfo.getVrc()), ("Guest vrc does not indicate a failure\n"));
+
+ Utf8Str strErr;
+
+#define CASE_TOOL_ERROR(a_eType, a_strTool) \
+ case a_eType: \
+ { \
+ strErr = GuestProcessTool::guestErrorToString(a_strTool, guestErrorInfo); \
+ break; \
+ }
+
+ switch (guestErrorInfo.getType())
+ {
+ case GuestErrorInfo::Type_Session:
+ strErr = GuestSession::i_guestErrorToString(guestErrorInfo.getVrc());
+ break;
+
+ case GuestErrorInfo::Type_Process:
+ strErr = GuestProcess::i_guestErrorToString(guestErrorInfo.getVrc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case GuestErrorInfo::Type_File:
+ strErr = GuestFile::i_guestErrorToString(guestErrorInfo.getVrc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case GuestErrorInfo::Type_Directory:
+ strErr = GuestDirectory::i_guestErrorToString(guestErrorInfo.getVrc(), guestErrorInfo.getWhat().c_str());
+ break;
+
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolCat, VBOXSERVICE_TOOL_CAT);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolLs, VBOXSERVICE_TOOL_LS);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkDir, VBOXSERVICE_TOOL_MKDIR);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolMkTemp, VBOXSERVICE_TOOL_MKTEMP);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolRm, VBOXSERVICE_TOOL_RM);
+ CASE_TOOL_ERROR(GuestErrorInfo::Type_ToolStat, VBOXSERVICE_TOOL_STAT);
+
+ default:
+ AssertMsgFailed(("Type not implemented (type=%RU32, vrc=%Rrc)\n", guestErrorInfo.getType(), guestErrorInfo.getVrc()));
+ strErr = Utf8StrFmt("Unknown / Not implemented -- Please file a bug report (type=%RU32, vrc=%Rrc)\n",
+ guestErrorInfo.getType(), guestErrorInfo.getVrc());
+ break;
+ }
+
+ return strErr;
+}
+
+#endif /* VBOX_GUESTCTRL_TEST_CASE */
+
+/**
+ * Converts RTFMODE to FsObjType_T.
+ *
+ * @return Converted FsObjType_T type.
+ * @param fMode RTFMODE to convert.
+ */
+/* static */
+FsObjType_T GuestBase::fileModeToFsObjType(RTFMODE fMode)
+{
+ if (RTFS_IS_FILE(fMode)) return FsObjType_File;
+ else if (RTFS_IS_DIRECTORY(fMode)) return FsObjType_Directory;
+ else if (RTFS_IS_SYMLINK(fMode)) return FsObjType_Symlink;
+
+ return FsObjType_Unknown;
+}
+
+/**
+ * Converts a FsObjType_T to a human-readable string.
+ *
+ * @returns Human-readable string of FsObjType_T.
+ * @param enmType FsObjType_T to convert.
+ */
+/* static */
+const char *GuestBase::fsObjTypeToStr(FsObjType_T enmType)
+{
+ switch (enmType)
+ {
+ case FsObjType_Directory: return "directory";
+ case FsObjType_Symlink: return "symbolic link";
+ case FsObjType_File: return "file";
+ default: break;
+ }
+
+ return "unknown";
+}
+
+/**
+ * Converts a PathStyle_T to a human-readable string.
+ *
+ * @returns Human-readable string of PathStyle_T.
+ * @param enmPathStyle PathStyle_T to convert.
+ */
+/* static */
+const char *GuestBase::pathStyleToStr(PathStyle_T enmPathStyle)
+{
+ switch (enmPathStyle)
+ {
+ case PathStyle_DOS: return "DOS";
+ case PathStyle_UNIX: return "UNIX";
+ case PathStyle_Unknown: return "Unknown";
+ default: break;
+ }
+
+ return "<invalid>";
+}
+
+GuestObject::GuestObject(void)
+ : mSession(NULL),
+ mObjectID(0)
+{
+}
+
+GuestObject::~GuestObject(void)
+{
+}
+
+/**
+ * Binds this guest (control) object to a specific guest (control) session.
+ *
+ * @returns VBox status code.
+ * @param pConsole Pointer to console object to use.
+ * @param pSession Pointer to session to bind this object to.
+ * @param uObjectID Object ID for this object to use within that specific session.
+ * Each object ID must be unique per session.
+ */
+int GuestObject::bindToSession(Console *pConsole, GuestSession *pSession, uint32_t uObjectID)
+{
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ mConsole = pConsole;
+ mSession = pSession;
+ mObjectID = uObjectID;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers (creates) a new wait event.
+ *
+ * @returns VBox status code.
+ * @param lstEvents List of events which the new wait event gets triggered at.
+ * @param ppEvent Returns the new wait event on success.
+ */
+int GuestObject::registerWaitEvent(const GuestEventTypes &lstEvents,
+ GuestWaitEvent **ppEvent)
+{
+ AssertPtr(mSession);
+ return GuestBase::registerWaitEventEx(mSession->i_getId(), mObjectID, lstEvents, ppEvent);
+}
+
+/**
+ * Sends a HGCM message to the guest (via the guest control host service).
+ *
+ * @returns VBox status code.
+ * @param uMessage Message ID of message to send.
+ * @param cParms Number of HGCM message parameters to send.
+ * @param paParms Array of HGCM message parameters to send.
+ */
+int GuestObject::sendMessage(uint32_t uMessage, uint32_t cParms, PVBOXHGCMSVCPARM paParms)
+{
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+ ComObjPtr<Console> pConsole = mConsole;
+ Assert(!pConsole.isNull());
+
+ int vrc = VERR_HGCM_SERVICE_NOT_FOUND;
+
+ /* Forward the information to the VMM device. */
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ if (pVMMDev)
+ {
+ /* HACK ALERT! We extend the first parameter to 64-bit and use the
+ two topmost bits for call destination information. */
+ Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT);
+ paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT;
+ paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | VBOX_GUESTCTRL_DST_SESSION;
+
+ /* Make the call. */
+ LogFlowThisFunc(("uMessage=%RU32, cParms=%RU32\n", uMessage, cParms));
+ vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, cParms, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo What to do here? */
+ }
+ }
+#else
+ LogFlowThisFuncEnter();
+
+ /* Not needed within testcases. */
+ RT_NOREF(uMessage, cParms, paParms);
+ int vrc = VINF_SUCCESS;
+#endif
+ return vrc;
+}
+
+GuestWaitEventBase::GuestWaitEventBase(void)
+ : mfAborted(false),
+ mCID(0),
+ mEventSem(NIL_RTSEMEVENT),
+ mVrc(VINF_SUCCESS),
+ mGuestRc(VINF_SUCCESS)
+{
+}
+
+GuestWaitEventBase::~GuestWaitEventBase(void)
+{
+ if (mEventSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(mEventSem);
+ mEventSem = NIL_RTSEMEVENT;
+ }
+}
+
+/**
+ * Initializes a wait event with a specific context ID (CID).
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID (CID) to initialize wait event with.
+ */
+int GuestWaitEventBase::Init(uint32_t uCID)
+{
+ mCID = uCID;
+
+ return RTSemEventCreate(&mEventSem);
+}
+
+/**
+ * Signals a wait event.
+ *
+ * @returns VBox status code.
+ * @param vrc Return VBox status code to set as wait result.
+ * @param vrcGuest Guest return VBox status code to set additionally, if
+ * @a vrc is set to VERR_GSTCTL_GUEST_ERROR.
+ * @param pPayload Additional wait event payload data set set on return. Optional.
+ */
+int GuestWaitEventBase::SignalInternal(int vrc, int vrcGuest, const GuestWaitEventPayload *pPayload)
+{
+ if (mfAborted)
+ return VERR_CANCELLED;
+
+#ifdef VBOX_STRICT
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ AssertMsg(RT_FAILURE(vrcGuest), ("Guest error indicated but no actual guest error set (%Rrc)\n", vrcGuest));
+ else
+ AssertMsg(RT_SUCCESS(vrcGuest), ("No guest error indicated but actual guest error set (%Rrc)\n", vrcGuest));
+#endif
+
+ int vrc2;
+ if (pPayload)
+ vrc2 = mPayload.CopyFromDeep(*pPayload);
+ else
+ vrc2 = VINF_SUCCESS;
+ if (RT_SUCCESS(vrc2))
+ {
+ mVrc = vrc;
+ mGuestRc = vrcGuest;
+
+ vrc2 = RTSemEventSignal(mEventSem);
+ }
+
+ return vrc2;
+}
+
+/**
+ * Waits for the event to get triggered. Will return success if the
+ * wait was successufl (e.g. was being triggered), otherwise an error will be returned.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR may be returned, call GuestResult() to get
+ * the actual result.
+ *
+ * @param msTimeout Timeout (in ms) to wait.
+ * Specifiy 0 to wait indefinitely.
+ */
+int GuestWaitEventBase::Wait(RTMSINTERVAL msTimeout)
+{
+ int vrc;
+ if (!mfAborted)
+ {
+ AssertReturn(mEventSem != NIL_RTSEMEVENT, VERR_CANCELLED);
+
+ vrc = RTSemEventWait(mEventSem, msTimeout ? msTimeout : RT_INDEFINITE_WAIT);
+ if ( RT_SUCCESS(vrc)
+ && mfAborted)
+ vrc = VERR_CANCELLED;
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* If waiting succeeded, return the overall
+ * result code. */
+ vrc = mVrc;
+ }
+ }
+ else
+ vrc = VERR_CANCELLED;
+ return vrc;
+}
+
+GuestWaitEvent::GuestWaitEvent(void)
+{
+}
+
+GuestWaitEvent::~GuestWaitEvent(void)
+{
+
+}
+
+/**
+ * Cancels the event.
+ */
+int GuestWaitEvent::Cancel(void)
+{
+ if (mfAborted) /* Already aborted? */
+ return VINF_SUCCESS;
+
+ mfAborted = true;
+
+#ifdef DEBUG_andy
+ LogFlowThisFunc(("Cancelling %p ...\n"));
+#endif
+ return RTSemEventSignal(mEventSem);
+}
+
+/**
+ * Initializes a wait event with a given context ID (CID).
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID to initialize wait event with.
+ */
+int GuestWaitEvent::Init(uint32_t uCID)
+{
+ return GuestWaitEventBase::Init(uCID);
+}
+
+/**
+ * Initializes a wait event with a given context ID (CID) and a list of event types to wait for.
+ *
+ * @returns VBox status code.
+ * @param uCID Context ID to initialize wait event with.
+ * @param lstEvents List of event types to wait for this wait event to get signalled.
+ */
+int GuestWaitEvent::Init(uint32_t uCID, const GuestEventTypes &lstEvents)
+{
+ int vrc = GuestWaitEventBase::Init(uCID);
+ if (RT_SUCCESS(vrc))
+ mEventTypes = lstEvents;
+
+ return vrc;
+}
+
+/**
+ * Signals the event.
+ *
+ * @return VBox status code.
+ * @param pEvent Public IEvent to associate.
+ * Optional.
+ */
+int GuestWaitEvent::SignalExternal(IEvent *pEvent)
+{
+ if (pEvent)
+ mEvent = pEvent;
+
+ return RTSemEventSignal(mEventSem);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// GuestPath
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Builds a (final) destination path from a given source + destination path.
+ *
+ * This does not utilize any file system access whatsoever. Used for guest and host paths.
+ *
+ * @returns VBox status code.
+ * @param strSrcPath Source path to build destination path for.
+ * @param enmSrcPathStyle Path style the source path is in.
+ * @param strDstPath Destination path to use for building the (final) destination path.
+ * @param enmDstPathStyle Path style the destination path is in.
+ *
+ * @note See rules within the function.
+ */
+/* static */
+int GuestPath::BuildDestinationPath(const Utf8Str &strSrcPath, PathStyle_T enmSrcPathStyle,
+ Utf8Str &strDstPath, PathStyle_T enmDstPathStyle)
+{
+ /*
+ * Rules:
+ *
+ * # source dest final dest remarks
+ *
+ * 1 /src/path1/ /dst/path2/ /dst/path2/<contents of path1> Just copies contents of <contents of path1>, not the path1 itself.
+ * 2 /src/path1 /dst/path2/ /dst/path2/path1 Copies path1 into path2.
+ * 3 /src/path1 /dst/path2 /dst/path2 Overwrites stuff from path2 with stuff from path1.
+ * 4 Dotdot ("..") directories are forbidden for security reasons.
+ */
+ const char *pszSrcName = RTPathFilenameEx(strSrcPath.c_str(),
+ enmSrcPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+
+ const char *pszDstName = RTPathFilenameEx(strDstPath.c_str(),
+ enmDstPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+
+ if ( (!pszSrcName && !pszDstName) /* #1 */
+ || ( pszSrcName && pszDstName)) /* #3 */
+ {
+ /* Note: Must have DirectoryFlag_CopyIntoExisting + FileFlag_NoReplace *not* set. */
+ }
+ else if (pszSrcName && !pszDstName) /* #2 */
+ {
+ if (!strDstPath.endsWith(PATH_STYLE_SEP_STR(enmDstPathStyle)))
+ strDstPath += PATH_STYLE_SEP_STR(enmDstPathStyle);
+ strDstPath += pszSrcName;
+ }
+
+ /* Translate the built destination path to a path compatible with the destination. */
+ int vrc = GuestPath::Translate(strDstPath, enmSrcPathStyle, enmDstPathStyle);
+ if (RT_SUCCESS(vrc))
+ {
+ union
+ {
+ RTPATHPARSED Parsed;
+ RTPATHSPLIT Split;
+ uint8_t ab[4096];
+ } u;
+ vrc = RTPathParse(strDstPath.c_str(), &u.Parsed, sizeof(u), enmDstPathStyle == PathStyle_DOS
+ ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+ if (RT_SUCCESS(vrc))
+ {
+ if (u.Parsed.fProps & RTPATH_PROP_DOTDOT_REFS) /* #4 */
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ }
+
+ LogRel2(("Guest Control: Building destination path for '%s' (%s) -> '%s' (%s): %Rrc\n",
+ strSrcPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strDstPath.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+
+ return vrc;
+}
+
+/**
+ * Translates a path from a specific path style into another.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if a conversion is not supported.
+ * @retval VERR_NOT_IMPLEMENTED if path style conversion is not implemented yet.
+ * @param strPath Path to translate. Will contain the translated path on success. UTF-8 only.
+ * @param enmSrcPathStyle Source path style \a strPath is expected in.
+ * @param enmDstPathStyle Destination path style to convert to.
+ * @param fForce Whether to force the translation to the destination path style or not.
+ *
+ * @note This does NOT remove any trailing slashes and/or perform file system lookups!
+ */
+/* static */
+int GuestPath::Translate(Utf8Str &strPath, PathStyle_T enmSrcPathStyle, PathStyle_T enmDstPathStyle, bool fForce /* = false */)
+{
+ if (strPath.isEmpty())
+ return VINF_SUCCESS;
+
+ AssertReturn(RTStrIsValidEncoding(strPath.c_str()), VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ Utf8Str strTranslated;
+
+ if ( ( enmSrcPathStyle == PathStyle_DOS
+ && enmDstPathStyle == PathStyle_UNIX)
+ || (fForce && enmDstPathStyle == PathStyle_UNIX))
+ {
+ strTranslated = strPath;
+ RTPathChangeToUnixSlashes(strTranslated.mutableRaw(), true /* fForce */);
+ }
+ else if ( ( enmSrcPathStyle == PathStyle_UNIX
+ && enmDstPathStyle == PathStyle_DOS)
+ || (fForce && enmDstPathStyle == PathStyle_DOS))
+
+ {
+ strTranslated = strPath;
+ RTPathChangeToDosSlashes(strTranslated.mutableRaw(), true /* fForce */);
+ }
+
+ if ( strTranslated.isEmpty() /* Not forced. */
+ && enmSrcPathStyle == enmDstPathStyle)
+ {
+ strTranslated = strPath;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Guest Control: Translating path '%s' (%s) -> '%s' (%s) failed, vrc=%Rrc\n",
+ strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+ return vrc;
+ }
+
+ /* Cleanup. */
+ const char *psz = strTranslated.mutableRaw();
+ size_t const cch = strTranslated.length();
+ size_t off = 0;
+ while (off < cch)
+ {
+ if (off + 1 > cch)
+ break;
+ /* Remove double back slashes (DOS only). */
+ if ( enmDstPathStyle == PathStyle_DOS
+ && psz[off] == '\\'
+ && psz[off + 1] == '\\')
+ {
+ strTranslated.erase(off + 1, 1);
+ off++;
+ }
+ /* Remove double forward slashes (UNIX only). */
+ if ( enmDstPathStyle == PathStyle_UNIX
+ && psz[off] == '/'
+ && psz[off + 1] == '/')
+ {
+ strTranslated.erase(off + 1, 1);
+ off++;
+ }
+ off++;
+ }
+
+ /* Note: Do not trim() paths here, as technically it's possible to create paths with trailing spaces. */
+
+ strTranslated.jolt();
+
+ LogRel2(("Guest Control: Translating '%s' (%s) -> '%s' (%s): %Rrc\n",
+ strPath.c_str(), GuestBase::pathStyleToStr(enmSrcPathStyle),
+ strTranslated.c_str(), GuestBase::pathStyleToStr(enmDstPathStyle), vrc));
+
+ if (RT_SUCCESS(vrc))
+ strPath = strTranslated;
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestDirectoryImpl.cpp b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp
new file mode 100644
index 00000000..8659c998
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDirectoryImpl.cpp
@@ -0,0 +1,501 @@
+/* $Id: GuestDirectoryImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest directory handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTDIRECTORY
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestDirectoryImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+
+#include <VBox/com/array.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestDirectory)
+
+HRESULT GuestDirectory::FinalConstruct(void)
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDirectory::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+int GuestDirectory::init(Console *pConsole, GuestSession *pSession, ULONG aObjectID, const GuestDirectoryOpenInfo &openInfo)
+{
+ LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s, strFilter=%s, uFlags=%x\n",
+ pConsole, pSession, aObjectID, openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags));
+
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ int vrc = bindToSession(pConsole, pSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ mSession = pSession;
+ mObjectID = aObjectID;
+
+ mData.mOpenInfo = openInfo;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Start the directory process on the guest. */
+ GuestProcessStartupInfo procInfo;
+ procInfo.mName.printf(tr("Opening directory \"%s\""), openInfo.mPath.c_str());
+ procInfo.mTimeoutMS = 5 * 60 * 1000; /* 5 minutes timeout. */
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ procInfo.mExecutable= Utf8Str(VBOXSERVICE_TOOL_LS);
+
+ procInfo.mArguments.push_back(procInfo.mExecutable);
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ /* We want the long output format which contains all the object details. */
+ procInfo.mArguments.push_back(Utf8Str("-l"));
+#if 0 /* Flags are not supported yet. */
+ if (uFlags & DirectoryOpenFlag_NoSymlinks)
+ procInfo.mArguments.push_back(Utf8Str("--nosymlinks")); /** @todo What does GNU here? */
+#endif
+ /** @todo Recursion support? */
+ procInfo.mArguments.push_back(openInfo.mPath); /* The directory we want to open. */
+
+ /*
+ * Start the process synchronously and keep it around so that we can use
+ * it later in subsequent read() calls.
+ */
+ vrc = mData.mProcessTool.init(mSession, procInfo, false /*fAsync*/, NULL /*pvrcGuest*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /* As we need to know if the directory we were about to open exists and and is accessible,
+ * do the first read here in order to return a meaningful status here. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = i_readInternal(mData.mObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ /*
+ * We need to actively terminate our process tool in case of an error here,
+ * as this otherwise would be done on (directory) object destruction implicitly.
+ * This in turn then will run into a timeout, as the directory object won't be
+ * around anymore at that time. Ugly, but that's how it is for the moment.
+ */
+ int vrcTerm = mData.mProcessTool.terminate(30 * RT_MS_1SEC, NULL /* prcGuest */);
+ AssertRC(vrcTerm);
+
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrc = vrcGuest;
+ }
+ }
+ }
+
+ /* Confirm a successful initialization when it's the case. */
+ if (RT_SUCCESS(vrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDirectory::uninit(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncLeave();
+}
+
+// implementation of private wrapped getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDirectory::getDirectoryName(com::Utf8Str &aDirectoryName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDirectoryName = mData.mOpenInfo.mPath;
+
+ return S_OK;
+}
+
+HRESULT GuestDirectory::getFilter(com::Utf8Str &aFilter)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFilter = mData.mOpenInfo.mFilter;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side directory callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestDirectory::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
+ mData.mOpenInfo.mPath.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DIR_NOTIFY:
+ {
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_DIR_NOTIFY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.uType);
+ HGCMSvcGetU32(&pSvcCb->mpaParms[idx++], &dataCb.rc);
+
+ LogFlowFunc(("uType=%RU32, vrcGguest=%Rrc\n", dataCb.uType, (int)dataCb.rc));
+
+ switch (dataCb.uType)
+ {
+ /* Nothing here yet, nothing to dispatch further. */
+
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ break;
+ }
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a given guest directory error to a string.
+ *
+ * @returns Error string.
+ * @param vrcGuest Guest file error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestDirectory::i_guestErrorToString(int vrcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (vrcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+ CASE_MSG(VERR_CANT_CREATE , tr("Access to guest directory \"%s\" is denied"), pcszWhat);
+ CASE_MSG(VERR_DIR_NOT_EMPTY, tr("Guest directory \"%s\" is not empty"), pcszWhat);
+ default:
+ strErr.printf(tr("Error %Rrc for guest directory \"%s\" occurred\n"), vrcGuest, pcszWhat);
+ break;
+ }
+
+#undef CASE_MSG
+
+ return strErr;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestDirectory::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestDirectory::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ RT_NOREF(enmSessionStatus);
+
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Closes this guest directory and removes it from the
+ * guest session's directory list.
+ *
+ * @return VBox status code.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_closeInternal(int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ int vrc = mData.mProcessTool.terminate(30 * 1000 /* 30s timeout */, prcGuest);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_directoryUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Reads the next directory entry, internal version.
+ *
+ * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available.
+ * @param objData Where to store the read directory entry as internal object data.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_readInternal(GuestFsObjData &objData, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ GuestProcessStreamBlock curBlock;
+ int vrc = mData.mProcessTool.waitEx(GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK, &curBlock, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Note: The guest process can still be around to serve the next
+ * upcoming stream block next time.
+ */
+ if (!mData.mProcessTool.isRunning())
+ vrc = mData.mProcessTool.getTerminationStatus(); /* Tool process is not running (anymore). Check termination status. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (curBlock.GetCount()) /* Did we get content? */
+ {
+ if (curBlock.GetString("name"))
+ {
+ vrc = objData.FromLs(curBlock, true /* fLong */);
+ }
+ else
+ vrc = VERR_PATH_NOT_FOUND;
+ }
+ else
+ {
+ /* Nothing to read anymore. Tell the caller. */
+ vrc = VERR_NO_MORE_FILES;
+ }
+ }
+ }
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Reads the next directory entry.
+ *
+ * @return VBox status code. Will return VERR_NO_MORE_FILES if no more entries are available.
+ * @param fsObjInfo Where to store the read directory entry.
+ * @param prcGuest Where to store the guest result code in case VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+int GuestDirectory::i_read(ComObjPtr<GuestFsObjInfo> &fsObjInfo, int *prcGuest)
+{
+ AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
+
+ /* Create the FS info object. */
+ HRESULT hr = fsObjInfo.createObject();
+ if (FAILED(hr))
+ return VERR_COM_UNEXPECTED;
+
+ int vrc;
+
+ /* If we have a valid object data cache, read from it. */
+ if (mData.mObjData.mName.isNotEmpty())
+ {
+ vrc = fsObjInfo->init(mData.mObjData);
+ if (RT_SUCCESS(vrc))
+ {
+ mData.mObjData.mName = ""; /* Mark the object data as being empty (beacon). */
+ }
+ }
+ else /* Otherwise ask the guest for the next object data (block). */
+ {
+
+ GuestFsObjData objData;
+ vrc = i_readInternal(objData, prcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = fsObjInfo->init(objData);
+ }
+
+ LogFlowThisFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestDirectory::close()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_closeInternal(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, mData.mOpenInfo.mPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_NOT_SUPPORTED:
+ /* Silently skip old Guest Additions which do not support killing the
+ * the guest directory handling process. */
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Closing guest directory \"%s\" failed: %Rrc"), mData.mOpenInfo.mPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestDirectory::read(ComPtr<IFsObjInfo> &aObjInfo)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ ComObjPtr<GuestFsObjInfo> fsObjInfo;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_read(fsObjInfo, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Return info object to the caller. */
+ hrc = fsObjInfo.queryInterfaceTo(aObjInfo.asOutParam());
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolLs, vrcGuest, mData.mOpenInfo.mPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Reading guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_GSTCTL_PROCESS_EXIT_CODE:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mPath.c_str(), mData.mProcessTool.getRc());
+ break;
+
+ case VERR_PATH_NOT_FOUND:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" failed: Path not found"),
+ mData.mOpenInfo.mPath.c_str());
+ break;
+
+ case VERR_NO_MORE_FILES:
+ /* See SDK reference. */
+ hrc = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("Reading guest directory \"%s\" failed: No more entries"),
+ mData.mOpenInfo.mPath.c_str());
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading guest directory \"%s\" returned error: %Rrc\n"),
+ mData.mOpenInfo.mPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning hrc=%Rhrc / vrc=%Rrc\n", hrc, vrc));
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestDnDPrivate.cpp b/src/VBox/Main/src-client/GuestDnDPrivate.cpp
new file mode 100644
index 00000000..3007e181
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDPrivate.cpp
@@ -0,0 +1,1625 @@
+/* $Id: GuestDnDPrivate.cpp $ */
+/** @file
+ * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "AutoCaller.h"
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "ConsoleImpl.h"
+# include "ProgressImpl.h"
+# include "GuestDnDPrivate.h"
+
+# include <algorithm>
+
+# include <iprt/dir.h>
+# include <iprt/path.h>
+# include <iprt/stream.h>
+# include <iprt/semaphore.h>
+# include <iprt/cpp/utils.h>
+
+# include <VMMDev.h>
+
+# include <VBox/GuestHost/DragAndDrop.h>
+# include <VBox/HostServices/DragAndDropSvc.h>
+# include <VBox/version.h>
+
+/** @page pg_main_dnd Dungeons & Dragons - Overview
+ * Overview:
+ *
+ * Drag and Drop is handled over the internal HGCM service for the host <->
+ * guest communication. Beside that we need to map the Drag and Drop protocols
+ * of the various OS's we support to our internal channels, this is also highly
+ * communicative in both directions. Unfortunately HGCM isn't really designed
+ * for that. Next we have to foul some of the components. This includes to
+ * trick X11 on the guest side, but also Qt needs to be tricked on the host
+ * side a little bit.
+ *
+ * The following components are involved:
+ *
+ * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
+ * of it to the Main IGuest / IGuestDnDSource / IGuestDnDTarget interfaces.
+ * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
+ * interfaces for blocking the caller by showing a progress dialog (see
+ * this file).
+ * 3. HGCM service: Handle all messages from the host to the guest at once and
+ * encapsulate the internal communication details (see dndmanager.cpp and
+ * friends).
+ * 4. Guest Additions: Split into the platform neutral part (see
+ * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
+ * Receive/send message from/to the HGCM service and does all guest specific
+ * operations. For Windows guests VBoxTray is in charge, whereas on UNIX-y guests
+ * VBoxClient will be used.
+ *
+ * Terminology:
+ *
+ * All transfers contain a MIME format and according meta data. This meta data then can
+ * be interpreted either as raw meta data or something else. When raw meta data is
+ * being handled, this gets passed through to the destination (guest / host) without
+ * modification. Other meta data (like URI lists) can and will be modified by the
+ * receiving side before passing to OS. How and when modifications will be applied
+ * depends on the MIME format.
+ *
+ * Host -> Guest:
+ * 1. There are DnD Enter, Move, Leave events which are send exactly like this
+ * to the guest. The info includes the position, MIME types and allowed actions.
+ * The guest has to respond with an action it would accept, so the GUI could
+ * change the cursor accordingly.
+ * 2. On drop, first a drop event is sent. If this is accepted a drop data
+ * event follows. This blocks the GUI and shows some progress indicator.
+ *
+ * Guest -> Host:
+ * 1. The GUI is asking the guest if a DnD event is pending when the user moves
+ * the cursor out of the view window. If so, this returns the mimetypes and
+ * allowed actions.
+ * (2. On every mouse move this is asked again, to make sure the DnD event is
+ * still valid.)
+ * 3. On drop the host request the data from the guest. This blocks the GUI and
+ * shows some progress indicator.
+ *
+ * Implementation hints:
+ * m_strSupportedFormats here in this file defines the allowed mime-types.
+ * This is necessary because we need special handling for some of the
+ * mime-types. E.g. for URI lists we need to transfer the actual dirs and
+ * files. Text EOL may to be changed. Also unknown mime-types may need special
+ * handling as well, which may lead to undefined behavior in the host/guest, if
+ * not done.
+ *
+ * Dropping of a directory, means recursively transferring _all_ the content.
+ *
+ * Directories and files are placed into the user's temporary directory on the
+ * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
+ * DnD operation, because we didn't know what the DnD target does with it. E.g.
+ * it could just be opened in place. This could lead ofc to filling up the disk
+ * within the guest. To inform the user about this, a small app could be
+ * developed which scans this directory regularly and inform the user with a
+ * tray icon hint (and maybe the possibility to clean this up instantly). The
+ * same has to be done in the G->H direction when it is implemented.
+ *
+ * Only regular files are supported; symlinks are not allowed.
+ *
+ * Transfers currently are an all-succeed or all-fail operation (see todos).
+ *
+ * On MacOS hosts we had to implement own DnD "promises" support for file transfers,
+ * as Qt does not support this out-of-the-box.
+ *
+ * The code tries to preserve the file modes of the transfered directories / files.
+ * This is useful (and maybe necessary) for two things:
+ * 1. If a file is executable, it should be also after the transfer, so the
+ * user can just execute it, without manually tweaking the modes first.
+ * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
+ * be in the guest.
+ * In any case, the user mode is always set to rwx (so that we can access it
+ * ourself, in e.g. for a cleanup case after cancel).
+ *
+ * ACEs / ACLs currently are not supported.
+ *
+ * Cancelling ongoing transfers is supported in both directions by the guest
+ * and/or host side and cleans up all previous steps. This also involves
+ * removing partially transferred directories / files in the temporary directory.
+ *
+ ** @todo
+ * - ESC doesn't really work (on Windows guests it's already implemented)
+ * ... in any case it seems a little bit difficult to handle from the Qt side.
+ * - Transfers currently do not have any interactive (UI) callbacks / hooks which
+ * e.g. would allow to skip / replace / rename and entry, or abort the operation on failure.
+ * - Add support for more MIME types (especially images, csv)
+ * - Test unusual behavior:
+ * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
+ * - Not expected order of the events between HGCM and the guest
+ * - Security considerations: We transfer a lot of memory between the guest and
+ * the host and even allow the creation of dirs/files. Maybe there should be
+ * limits introduced to preventing DoS attacks or filling up all the memory
+ * (both in the host and the guest).
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Tries locking the GuestDnD object and returns on failure. */
+#define GUESTDND_LOCK() do { \
+ int const vrcLock = RTCritSectEnter(&m_CritSect); \
+ if (RT_SUCCESS(vrcLock)) { /* likely */ } else return vrcLock; \
+ } while (0)
+
+/** Tries locking the GuestDnD object and returns a_Ret failure. */
+#define GUESTDND_LOCK_RET(a_Ret) do { \
+ int const vrcLock = RTCritSectEnter(&m_CritSect); \
+ if (RT_SUCCESS(vrcLock)) { /* likely */ } else return vrcLock; \
+ } while (0)
+
+/** Unlocks a formerly locked GuestDnD object. */
+#define GUESTDND_UNLOCK() do { \
+ int const vrcUnlock = RTCritSectLeave(&m_CritSect); \
+ AssertRC(vrcUnlock); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* GuestDnDSendCtx implementation. *
+*********************************************************************************************************************************/
+
+GuestDnDSendCtx::GuestDnDSendCtx(void)
+ : pTarget(NULL)
+ , pState(NULL)
+{
+ reset();
+}
+
+/**
+ * Resets a GuestDnDSendCtx object.
+ */
+void GuestDnDSendCtx::reset(void)
+{
+ uScreenID = 0;
+
+ Transfer.reset();
+
+ int vrc2 = EventCallback.Reset();
+ AssertRC(vrc2);
+
+ GuestDnDData::reset();
+}
+
+
+/*********************************************************************************************************************************
+* GuestDnDRecvCtx implementation. *
+*********************************************************************************************************************************/
+
+GuestDnDRecvCtx::GuestDnDRecvCtx(void)
+ : pSource(NULL)
+ , pState(NULL)
+{
+ reset();
+}
+
+/**
+ * Resets a GuestDnDRecvCtx object.
+ */
+void GuestDnDRecvCtx::reset(void)
+{
+ lstFmtOffered.clear();
+ strFmtReq = "";
+ strFmtRecv = "";
+ enmAction = 0;
+
+ Transfer.reset();
+
+ int vrc2 = EventCallback.Reset();
+ AssertRC(vrc2);
+
+ GuestDnDData::reset();
+}
+
+
+/*********************************************************************************************************************************
+* GuestDnDCallbackEvent implementation. *
+*********************************************************************************************************************************/
+
+GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
+{
+ if (NIL_RTSEMEVENT != m_SemEvent)
+ RTSemEventDestroy(m_SemEvent);
+}
+
+/**
+ * Resets a GuestDnDCallbackEvent object.
+ */
+int GuestDnDCallbackEvent::Reset(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ if (m_SemEvent == NIL_RTSEMEVENT)
+ vrc = RTSemEventCreate(&m_SemEvent);
+
+ m_vrc = VINF_SUCCESS;
+ return vrc;
+}
+
+/**
+ * Completes a callback event by notifying the waiting side.
+ *
+ * @returns VBox status code.
+ * @param vrc Result code to use for the event completion.
+ */
+int GuestDnDCallbackEvent::Notify(int vrc /* = VINF_SUCCESS */)
+{
+ m_vrc = vrc;
+ return RTSemEventSignal(m_SemEvent);
+}
+
+/**
+ * Waits on a callback event for being notified.
+ *
+ * @returns VBox status code.
+ * @param msTimeout Timeout (in ms) to wait for callback event.
+ */
+int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
+{
+ return RTSemEventWait(m_SemEvent, msTimeout);
+}
+
+
+/*********************************************************************************************************************************
+* GuestDnDState implementation *
+*********************************************************************************************************************************/
+
+GuestDnDState::GuestDnDState(const ComObjPtr<Guest>& pGuest)
+ : m_uProtocolVersion(0)
+ , m_fGuestFeatures0(VBOX_DND_GF_NONE)
+ , m_EventSem(NIL_RTSEMEVENT)
+ , m_pParent(pGuest)
+{
+ reset();
+
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+ vrc = RTSemEventCreate(&m_EventSem);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+GuestDnDState::~GuestDnDState(void)
+{
+ int vrc = RTSemEventDestroy(m_EventSem);
+ AssertRC(vrc);
+ vrc = RTCritSectDelete(&m_CritSect);
+ AssertRC(vrc);
+}
+
+/**
+ * Notifies the waiting side about a guest notification response.
+ *
+ * @returns VBox status code.
+ * @param vrcGuest Guest VBox status code to set for the response.
+ * Defaults to VINF_SUCCESS (for success).
+ */
+int GuestDnDState::notifyAboutGuestResponse(int vrcGuest /* = VINF_SUCCESS */)
+{
+ m_vrcGuest = vrcGuest;
+ return RTSemEventSignal(m_EventSem);
+}
+
+/**
+ * Resets a guest drag'n drop state.
+ */
+void GuestDnDState::reset(void)
+{
+ LogRel2(("DnD: Reset\n"));
+
+ m_enmState = VBOXDNDSTATE_UNKNOWN;
+
+ m_dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ m_dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
+
+ m_lstFormats.clear();
+ m_mapCallbacks.clear();
+
+ m_vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+}
+
+/**
+ * Default callback handler for guest callbacks.
+ *
+ * This handler acts as a fallback in case important callback messages are not being handled
+ * by the specific callers.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @retval VERR_NO_DATA if no new messages from the host side are available at the moment.
+ * @retval VERR_CANCELLED for indicating that the current operation was cancelled.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDState::i_defaultCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDState *pThis = (GuestDnDState *)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("uMsg=%RU32 (%#x)\n", uMsg, uMsg));
+
+ int vrc = VERR_IPE_UNINITIALIZED_STATUS;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+
+ vrc = pThis->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ Utf8StrFmt("Received error from guest: %Rrc", pCBData->rc));
+ AssertRCBreak(vrc);
+ vrc = pThis->notifyAboutGuestResponse(pCBData->rc);
+ AssertRCBreak(vrc);
+ break;
+ }
+
+ case GUEST_DND_FN_GET_NEXT_HOST_MSG:
+ vrc = VERR_NO_DATA; /* Indicate back to the host service that there are no new messages. */
+ break;
+
+ default:
+ AssertMsgBreakStmt(pThis->isProgressRunning() == false,
+ ("Progress object not completed / canceld yet! State is '%s' (%#x)\n",
+ DnDStateToStr(pThis->m_enmState), pThis->m_enmState),
+ vrc = VERR_INVALID_STATE); /* Please report this! */
+ vrc = VERR_CANCELLED;
+ break;
+ }
+
+ LogFlowFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Resets the progress object.
+ *
+ * @returns HRESULT
+ * @param pParent Parent to set for the progress object.
+ * @param strDesc Description of the progress.
+ */
+HRESULT GuestDnDState::resetProgress(const ComObjPtr<Guest>& pParent, const Utf8Str &strDesc)
+{
+ AssertReturn(strDesc.isNotEmpty(), E_INVALIDARG);
+
+ m_pProgress.setNull();
+
+ HRESULT hrc = m_pProgress.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = m_pProgress->init(static_cast<IGuest *>(pParent), Bstr(strDesc).raw(), TRUE /* aCancelable */);
+
+ return hrc;
+}
+
+/**
+ * Returns whether the progress object has been canceled or not.
+ *
+ * @returns \c true if canceled or progress does not exist, \c false if not.
+ */
+bool GuestDnDState::isProgressCanceled(void) const
+{
+ if (m_pProgress.isNull())
+ return true;
+
+ BOOL fCanceled;
+ HRESULT hrc = m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ AssertComRCReturn(hrc, false);
+ return RT_BOOL(fCanceled);
+}
+
+/**
+ * Returns whether the progress object still is in a running state or not.
+ *
+ * @returns \c true if running, \c false if not.
+ */
+bool GuestDnDState::isProgressRunning(void) const
+{
+ if (m_pProgress.isNull())
+ return false;
+
+ BOOL fCompleted;
+ HRESULT const hrc = m_pProgress->COMGETTER(Completed)(&fCompleted);
+ AssertComRCReturn(hrc, false);
+ return !RT_BOOL(fCompleted);
+}
+
+/**
+ * Sets (registers or unregisters) a callback for a specific HGCM message.
+ *
+ * @returns VBox status code.
+ * @param uMsg HGCM message ID to set callback for.
+ * @param pfnCallback Callback function pointer to use. Pass NULL to unregister.
+ * @param pvUser User-provided arguments for the callback function. Optional and can be NULL.
+ */
+int GuestDnDState::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
+{
+ GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
+
+ /* Register. */
+ if (pfnCallback)
+ {
+ try
+ {
+ m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+ }
+
+ /* Unregister. */
+ if (it != m_mapCallbacks.end())
+ m_mapCallbacks.erase(it);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the progress object to a new state.
+ *
+ * @returns VBox status code.
+ * @param uPercentage Percentage (0-100) to set.
+ * @param uStatus Status (of type DND_PROGRESS_XXX) to set.
+ * @param vrcOp VBox status code to set. Optional.
+ * @param strMsg Message to set. Optional.
+ */
+int GuestDnDState::setProgress(unsigned uPercentage, uint32_t uStatus,
+ int vrcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = Utf8Str::Empty */)
+{
+ LogFlowFunc(("uPercentage=%u, uStatus=%RU32, , vrcOp=%Rrc, strMsg=%s\n",
+ uPercentage, uStatus, vrcOp, strMsg.c_str()));
+
+ if (m_pProgress.isNull())
+ return VINF_SUCCESS;
+
+ BOOL fCompleted = FALSE;
+ HRESULT hrc = m_pProgress->COMGETTER(Completed)(&fCompleted);
+ AssertComRCReturn(hrc, VERR_COM_UNEXPECTED);
+
+ BOOL fCanceled = FALSE;
+ hrc = m_pProgress->COMGETTER(Canceled)(&fCanceled);
+ AssertComRCReturn(hrc, VERR_COM_UNEXPECTED);
+
+ LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
+
+ int vrc = VINF_SUCCESS;
+
+ switch (uStatus)
+ {
+ case DragAndDropSvc::DND_PROGRESS_ERROR:
+ {
+ LogRel(("DnD: Guest reported error %Rrc\n", vrcOp));
+
+ if (!fCompleted)
+ hrc = m_pProgress->i_notifyComplete(VBOX_E_DND_ERROR, COM_IIDOF(IGuest),
+ m_pParent->getComponentName(), strMsg.c_str());
+ break;
+ }
+
+ case DragAndDropSvc::DND_PROGRESS_CANCELLED:
+ {
+ LogRel2(("DnD: Guest cancelled operation\n"));
+
+ if (!fCanceled)
+ {
+ hrc = m_pProgress->Cancel();
+ AssertComRC(hrc);
+ }
+
+ if (!fCompleted)
+ {
+ hrc = m_pProgress->i_notifyComplete(S_OK);
+ AssertComRC(hrc);
+ }
+ break;
+ }
+
+ case DragAndDropSvc::DND_PROGRESS_RUNNING:
+ RT_FALL_THROUGH();
+ case DragAndDropSvc::DND_PROGRESS_COMPLETE:
+ {
+ LogRel2(("DnD: Guest reporting running/completion status with %u%%\n", uPercentage));
+
+ if ( !fCompleted
+ && !fCanceled)
+ {
+ hrc = m_pProgress->SetCurrentOperationProgress(uPercentage);
+ AssertComRCReturn(hrc, VERR_COM_UNEXPECTED);
+ if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
+ || uPercentage >= 100)
+ {
+ hrc = m_pProgress->i_notifyComplete(S_OK);
+ AssertComRCReturn(hrc, VERR_COM_UNEXPECTED);
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Dispatching function for handling the host service service callback.
+ *
+ * @returns VBox status code.
+ * @param u32Function HGCM message ID to handle.
+ * @param pvParms Pointer to optional data provided for a particular message. Optional.
+ * @param cbParms Size (in bytes) of \a pvParms.
+ */
+int GuestDnDState::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
+{
+ LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
+
+ int vrc = VERR_WRONG_ORDER; /* Play safe. */
+
+ /* Whether or not to try calling host-installed callbacks after successfully processing the message. */
+ bool fTryCallbacks = false;
+
+ switch (u32Function)
+ {
+ case DragAndDropSvc::GUEST_DND_FN_CONNECT:
+ {
+ DragAndDropSvc::PVBOXDNDCBCONNECTDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBCONNECTDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBCONNECTDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_CONNECT == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ m_uProtocolVersion = pCBData->uProtocolVersion;
+ /** @todo Handle flags. */
+
+ LogThisFunc(("Client connected, using protocol v%RU32\n", m_uProtocolVersion));
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_REPORT_FEATURES:
+ {
+ DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBREPORTFEATURESDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBREPORTFEATURESDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_REPORT_FEATURES == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ m_fGuestFeatures0 = pCBData->fGuestFeatures0;
+
+ LogThisFunc(("Client reported features: %#RX64\n", m_fGuestFeatures0));
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Note: GUEST_DND_FN_EVT_ERROR is handled in either the state's default callback or in specific
+ * (overriden) callbacks (e.g. GuestDnDSendCtx / GuestDnDRecvCtx). */
+
+ case DragAndDropSvc::GUEST_DND_FN_DISCONNECT:
+ {
+ LogThisFunc(("Client disconnected\n"));
+ vrc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_ACK_OP:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Guest responded with action '%s' for host->guest drag event\n", DnDActionToStr(pCBData->uAction)));
+
+ setActionDefault(pCBData->uAction);
+ vrc = notifyAboutGuestResponse();
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_REQ_DATA:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if ( pCBData->cbFormat == 0
+ || pCBData->cbFormat > _64K /** @todo Make this configurable? */
+ || pCBData->pszFormat == NULL)
+ vrc = VERR_INVALID_PARAMETER;
+ else if (!RTStrIsValidEncoding(pCBData->pszFormat))
+ vrc = VERR_INVALID_PARAMETER;
+ else
+ {
+ setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
+ vrc = VINF_SUCCESS;
+ }
+
+ int vrc2 = notifyAboutGuestResponse();
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ break;
+ }
+
+ case DragAndDropSvc::GUEST_DND_FN_HG_EVT_PROGRESS:
+ {
+ DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
+ reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
+ if (RT_SUCCESS(vrc))
+ vrc = notifyAboutGuestResponse(pCBData->rc);
+ break;
+ }
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case DragAndDropSvc::GUEST_DND_FN_GH_ACK_PENDING:
+ {
+ DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
+ reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Guest responded with pending action '%s' (%RU32 bytes format data) to guest->host drag event\n",
+ DnDActionToStr(pCBData->uDefAction), pCBData->cbFormat));
+
+ if ( pCBData->cbFormat == 0
+ || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
+ || pCBData->pszFormat == NULL)
+ vrc = VERR_INVALID_PARAMETER;
+ else if (!RTStrIsValidEncoding(pCBData->pszFormat))
+ vrc = VERR_INVALID_PARAMETER;
+ else
+ {
+ setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
+ setActionDefault (pCBData->uDefAction);
+ setActionsAllowed(pCBData->uAllActions);
+
+ vrc = VINF_SUCCESS;
+ }
+
+ int vrc2 = notifyAboutGuestResponse();
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ 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);
+ vrc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
+ }
+ else
+ {
+ /* Invoke the default callback handler in case we don't have any registered callback above. */
+ vrc = i_defaultCallback(u32Function, pvParms, cbParms, this /* pvUser */);
+ }
+ }
+
+ LogFlowFunc(("Returning vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Helper function to query the internal progress object to an IProgress interface.
+ *
+ * @returns HRESULT
+ * @param ppProgress Where to query the progress object to.
+ */
+HRESULT GuestDnDState::queryProgressTo(IProgress **ppProgress)
+{
+ return m_pProgress.queryInterfaceTo(ppProgress);
+}
+
+/**
+ * Waits for a guest response to happen, extended version.
+ *
+ * @returns VBox status code.
+ * @retval VERR_TIMEOUT when waiting has timed out.
+ * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest.
+ * @param msTimeout Timeout (in ms) for waiting. Optional, waits 3000 ms if not specified.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_DND_GUEST_ERROR is returned. Optional.
+ */
+int GuestDnDState::waitForGuestResponseEx(RTMSINTERVAL msTimeout /* = 3000 */, int *pvrcGuest /* = NULL */)
+{
+ int vrc = RTSemEventWait(m_EventSem, msTimeout);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_FAILURE(m_vrcGuest))
+ vrc = VERR_DND_GUEST_ERROR;
+ if (pvrcGuest)
+ *pvrcGuest = m_vrcGuest;
+ }
+ return vrc;
+}
+
+/**
+ * Waits for a guest response to happen.
+ *
+ * @returns VBox status code.
+ * @retval VERR_TIMEOUT when waiting has timed out.
+ * @retval VERR_DND_GUEST_ERROR on an error reported back from the guest.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_DND_GUEST_ERROR is returned. Optional.
+ *
+ * @note Uses the default timeout of 3000 ms.
+ */
+int GuestDnDState::waitForGuestResponse(int *pvrcGuest /* = NULL */)
+{
+ return waitForGuestResponseEx(3000 /* ms */, pvrcGuest);
+}
+
+/*********************************************************************************************************************************
+ * GuestDnD implementation. *
+ ********************************************************************************************************************************/
+
+/** Static (Singleton) instance of the GuestDnD object. */
+GuestDnD* GuestDnD::s_pInstance = NULL;
+
+GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
+ : m_pGuest(pGuest)
+ , m_cTransfersPending(0)
+{
+ LogFlowFuncEnter();
+
+ try
+ {
+ m_pState = new GuestDnDState(pGuest);
+ }
+ catch (std::bad_alloc &)
+ {
+ throw VERR_NO_MEMORY;
+ }
+
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ /* List of supported default MIME types. */
+ LogRel2(("DnD: Supported default host formats:\n"));
+ const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
+ for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
+ {
+ m_strDefaultFormats.push_back(arrEntries[i]);
+ LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
+ }
+}
+
+GuestDnD::~GuestDnD(void)
+{
+ LogFlowFuncEnter();
+
+ Assert(m_cTransfersPending == 0); /* Sanity. */
+
+ RTCritSectDelete(&m_CritSect);
+
+ if (m_pState)
+ delete m_pState;
+}
+
+/**
+ * Adjusts coordinations to a given screen.
+ *
+ * @returns HRESULT
+ * @param uScreenId ID of screen to adjust coordinates to.
+ * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success.
+ * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success.
+ */
+HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
+{
+ /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
+ * Only query for new offsets when the screen ID or the screen's resolution has changed. */
+
+ /* For multi-monitor support we need to add shift values to the coordinates
+ * (depending on the screen number). */
+ ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
+ ComPtr<IDisplay> pDisplay;
+ HRESULT hrc = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ ULONG dummy;
+ LONG xShift, yShift;
+ GuestMonitorStatus_T monitorStatus;
+ hrc = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy, &xShift, &yShift, &monitorStatus);
+ if (FAILED(hrc))
+ return hrc;
+
+ if (puX)
+ *puX += xShift;
+ if (puY)
+ *puY += yShift;
+
+ LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
+ return S_OK;
+}
+
+/**
+ * Returns a DnD guest state.
+ *
+ * @returns Pointer to DnD guest state, or NULL if not found / invalid.
+ * @param uID ID of DnD guest state to return.
+ */
+GuestDnDState *GuestDnD::getState(uint32_t uID /* = 0 */) const
+{
+ AssertMsgReturn(uID == 0, ("Only one state (0) is supported at the moment\n"), NULL);
+
+ return m_pState;
+}
+
+/**
+ * Sends a (blocking) message to the host side of the host service.
+ *
+ * @returns VBox status code.
+ * @param u32Function HGCM message ID to send.
+ * @param cParms Number of parameters to send.
+ * @param paParms Array of parameters to send. Must match \c cParms.
+ */
+int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
+{
+ Assert(!m_pGuest.isNull());
+ ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
+
+ /* Forward the information to the VMM device. */
+ Assert(!pConsole.isNull());
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ if (!pVMMDev)
+ return VERR_COM_OBJECT_NOT_FOUND;
+
+ return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
+}
+
+/**
+ * Registers a GuestDnDSource object with the GuestDnD manager.
+ *
+ * Currently only one source is supported at a time.
+ *
+ * @returns VBox status code.
+ * @param Source Source to register.
+ */
+int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source)
+{
+ GUESTDND_LOCK();
+
+ Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */
+ m_lstSrc.push_back(Source);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters a GuestDnDSource object from the GuestDnD manager.
+ *
+ * @returns VBox status code.
+ * @param Source Source to unregister.
+ */
+int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source)
+{
+ GUESTDND_LOCK();
+
+ GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source);
+ if (itSrc != m_lstSrc.end())
+ m_lstSrc.erase(itSrc);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the current number of registered sources.
+ *
+ * @returns Current number of registered sources.
+ */
+size_t GuestDnD::getSourceCount(void)
+{
+ GUESTDND_LOCK_RET(0);
+
+ size_t cSources = m_lstSrc.size();
+
+ GUESTDND_UNLOCK();
+ return cSources;
+}
+
+/**
+ * Registers a GuestDnDTarget object with the GuestDnD manager.
+ *
+ * Currently only one target is supported at a time.
+ *
+ * @returns VBox status code.
+ * @param Target Target to register.
+ */
+int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target)
+{
+ GUESTDND_LOCK();
+
+ Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */
+ m_lstTgt.push_back(Target);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters a GuestDnDTarget object from the GuestDnD manager.
+ *
+ * @returns VBox status code.
+ * @param Target Target to unregister.
+ */
+int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target)
+{
+ GUESTDND_LOCK();
+
+ GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target);
+ if (itTgt != m_lstTgt.end())
+ m_lstTgt.erase(itTgt);
+
+ GUESTDND_UNLOCK();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the current number of registered targets.
+ *
+ * @returns Current number of registered targets.
+ */
+size_t GuestDnD::getTargetCount(void)
+{
+ GUESTDND_LOCK_RET(0);
+
+ size_t cTargets = m_lstTgt.size();
+
+ GUESTDND_UNLOCK();
+ return cTargets;
+}
+
+/**
+ * Static main dispatcher function to handle callbacks from the DnD host service.
+ *
+ * @returns VBox status code.
+ * @param pvExtension Pointer to service extension.
+ * @param u32Function Callback HGCM message ID.
+ * @param pvParms Pointer to optional data provided for a particular message. Optional.
+ * @param cbParms Size (in bytes) of \a pvParms.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
+ void *pvParms, uint32_t cbParms)
+{
+ LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
+ pvExtension, u32Function, pvParms, cbParms));
+
+ GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
+ AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
+
+ /** @todo In case we need to handle multiple guest DnD responses at a time this
+ * would be the place to lookup and dispatch to those. For the moment we
+ * only have one response -- simple. */
+ if (pGuestDnD->m_pState)
+ return pGuestDnD->m_pState->onDispatch(u32Function, pvParms, cbParms);
+
+ return VERR_NOT_SUPPORTED;
+}
+
+/**
+ * Static helper function to determine whether a format is part of a given MIME list.
+ *
+ * @returns \c true if found, \c false if not.
+ * @param strFormat Format to search for.
+ * @param lstFormats MIME list to search in.
+ */
+/* static */
+bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
+{
+ return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
+}
+
+/**
+ * Static helper function to create a GuestDnDMIMEList out of a format list string.
+ *
+ * @returns MIME list object.
+ * @param strFormats List of formats to convert.
+ * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR_STR will be used.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
+{
+ GuestDnDMIMEList lstFormats;
+ RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
+
+ for (size_t i = 0; i < lstFormatsTmp.size(); i++)
+ lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
+
+ return lstFormats;
+}
+
+/**
+ * Static helper function to create a format list string from a given GuestDnDMIMEList object.
+ *
+ * @returns Format list string.
+ * @param lstFormats GuestDnDMIMEList to convert.
+ * @param strSep Separator to use between formats.
+ * Uses DND_FORMATS_SEPARATOR_STR as default.
+ */
+/* static */
+com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
+{
+ com::Utf8Str strFormat;
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ {
+ const com::Utf8Str &f = lstFormats.at(i);
+ strFormat += f + strSep;
+ }
+
+ return strFormat;
+}
+
+/**
+ * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
+ *
+ * @returns Filtered MIME list object.
+ * @param lstFormatsSupported MIME list of supported formats.
+ * @param lstFormatsWanted MIME list of wanted formats in returned object.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
+{
+ GuestDnDMIMEList lstFormats;
+
+ for (size_t i = 0; i < lstFormatsWanted.size(); i++)
+ {
+ /* Only keep supported format types. */
+ if (std::find(lstFormatsSupported.begin(),
+ lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
+ {
+ lstFormats.push_back(lstFormatsWanted[i]);
+ }
+ }
+
+ return lstFormats;
+}
+
+/**
+ * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
+ *
+ * @returns Filtered MIME list object.
+ * @param lstFormatsSupported MIME list of supported formats.
+ * @param strFormatsWanted Format list string of wanted formats in returned object.
+ */
+/* static */
+GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
+{
+ GuestDnDMIMEList lstFmt;
+
+ RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR_STR);
+ size_t i = 0;
+ while (i < lstFormats.size())
+ {
+ /* Only keep allowed format types. */
+ if (std::find(lstFormatsSupported.begin(),
+ lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
+ {
+ lstFmt.push_back(lstFormats[i]);
+ }
+ i++;
+ }
+
+ return lstFmt;
+}
+
+/**
+ * Static helper function to convert a Main DnD action an internal DnD action.
+ *
+ * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported.
+ * @param enmAction Main DnD action to convert.
+ */
+/* static */
+VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
+{
+ VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
+ switch (enmAction)
+ {
+ case DnDAction_Copy:
+ dndAction = VBOX_DND_ACTION_COPY;
+ break;
+ case DnDAction_Move:
+ dndAction = VBOX_DND_ACTION_MOVE;
+ break;
+ case DnDAction_Link:
+ /* For now it doesn't seems useful to allow a link
+ action between host & guest. Later? */
+ case DnDAction_Ignore:
+ /* Ignored. */
+ break;
+ default:
+ AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
+ break;
+ }
+
+ return dndAction;
+}
+
+/**
+ * Static helper function to convert a Main DnD default action and allowed Main actions to their
+ * corresponding internal representations.
+ *
+ * @param enmDnDActionDefault Default Main action to convert.
+ * @param pDnDActionDefault Where to store the converted default action.
+ * @param vecDnDActionsAllowed Allowed Main actions to convert.
+ * @param pDnDLstActionsAllowed Where to store the converted allowed actions.
+ */
+/* static */
+void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
+ VBOXDNDACTION *pDnDActionDefault,
+ const std::vector<DnDAction_T> vecDnDActionsAllowed,
+ VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
+{
+ VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
+
+ if (!vecDnDActionsAllowed.empty())
+ {
+ /* First convert the allowed actions to a bit array. */
+ for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
+ dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
+
+ /*
+ * If no default action is set (ignoring), try one of the
+ * set allowed actions, preferring copy, move (in that order).
+ */
+ if (isDnDIgnoreAction(dndActionDefault))
+ {
+ if (hasDnDCopyAction(dndLstActionsAllowed))
+ dndActionDefault = VBOX_DND_ACTION_COPY;
+ else if (hasDnDMoveAction(dndLstActionsAllowed))
+ dndActionDefault = VBOX_DND_ACTION_MOVE;
+ }
+ }
+
+ if (pDnDActionDefault)
+ *pDnDActionDefault = dndActionDefault;
+ if (pDnDLstActionsAllowed)
+ *pDnDLstActionsAllowed = dndLstActionsAllowed;
+}
+
+/**
+ * Static helper function to convert an internal DnD action to its Main representation.
+ *
+ * @returns Converted Main DnD action.
+ * @param dndAction DnD action to convert.
+ */
+/* static */
+DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
+{
+ /* For now it doesn't seems useful to allow a
+ * link action between host & guest. Maybe later! */
+ return isDnDCopyAction(dndAction) ? DnDAction_Copy
+ : isDnDMoveAction(dndAction) ? DnDAction_Move
+ : DnDAction_Ignore;
+}
+
+/**
+ * Static helper function to convert an internal DnD action list to its Main representation.
+ *
+ * @returns Converted Main DnD action list.
+ * @param dndActionList DnD action list to convert.
+ */
+/* static */
+std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
+{
+ std::vector<DnDAction_T> vecActions;
+
+ /* For now it doesn't seems useful to allow a
+ * link action between host & guest. Maybe later! */
+ RTCList<DnDAction_T> lstActions;
+ if (hasDnDCopyAction(dndActionList))
+ lstActions.append(DnDAction_Copy);
+ if (hasDnDMoveAction(dndActionList))
+ lstActions.append(DnDAction_Move);
+
+ for (size_t i = 0; i < lstActions.size(); ++i)
+ vecActions.push_back(lstActions.at(i));
+
+ return vecActions;
+}
+
+/*********************************************************************************************************************************
+ * GuestDnDBase implementation. *
+ ********************************************************************************************************************************/
+
+GuestDnDBase::GuestDnDBase(VirtualBoxBase *pBase)
+ : m_pBase(pBase)
+ , m_fIsPending(false)
+{
+ /* Initialize public attributes. */
+ m_lstFmtSupported = GuestDnDInst()->defaultFormats();
+}
+
+GuestDnDBase::~GuestDnDBase(void)
+{
+}
+
+/**
+ * Checks whether a given DnD format is supported or not.
+ *
+ * @returns \c true if supported, \c false if not.
+ * @param aFormat DnD format to check.
+ */
+bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
+{
+ return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
+}
+
+/**
+ * Returns the currently supported DnD formats.
+ *
+ * @returns List of the supported DnD formats.
+ */
+const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
+{
+ return m_lstFmtSupported;
+}
+
+/**
+ * Adds DnD formats to the supported formats list.
+ *
+ * @returns HRESULT
+ * @param aFormats List of DnD formats to add.
+ */
+HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
+{
+ for (size_t i = 0; i < aFormats.size(); ++i)
+ {
+ Utf8Str strFormat = aFormats.at(i);
+ if (std::find(m_lstFmtSupported.begin(),
+ m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
+ {
+ m_lstFmtSupported.push_back(strFormat);
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Removes DnD formats from tehh supported formats list.
+ *
+ * @returns HRESULT
+ * @param aFormats List of DnD formats to remove.
+ */
+HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
+{
+ for (size_t i = 0; i < aFormats.size(); ++i)
+ {
+ Utf8Str strFormat = aFormats.at(i);
+ GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
+ m_lstFmtSupported.end(), strFormat);
+ if (itFormat != m_lstFmtSupported.end())
+ m_lstFmtSupported.erase(itFormat);
+ }
+
+ return S_OK;
+}
+
+/**
+ * Prints an error in the release log and sets the COM error info.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param va Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorV(int vrc, const char *pcszMsgFmt, va_list va)
+{
+ char *psz = NULL;
+ if (RTStrAPrintfV(&psz, pcszMsgFmt, va) < 0)
+ return E_OUTOFMEMORY;
+ AssertPtrReturn(psz, E_OUTOFMEMORY);
+
+ HRESULT hrc;
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Error: %s (%Rrc)\n", psz, vrc));
+ hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s (%Rrc)", psz, vrc);
+ }
+ else
+ {
+ LogRel(("DnD: Error: %s\n", psz));
+ hrc = m_pBase->setErrorBoth(VBOX_E_DND_ERROR, vrc, "DnD: Error: %s", psz);
+ }
+
+ RTStrFree(psz);
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log and sets the COM error info.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param ... Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setError(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va);
+ va_end(va);
+
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log, sets the COM error info and calls the object's reset function.
+ *
+ * @returns HRESULT
+ * @param pcszMsgFmt Format string.
+ * @param va Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorAndReset(const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(VINF_SUCCESS, pcszMsgFmt, va);
+ va_end(va);
+
+ i_reset();
+
+ return hrc;
+}
+
+/**
+ * Prints an error in the release log, sets the COM error info and calls the object's reset function.
+ *
+ * @returns HRESULT
+ * @param vrc IPRT-style error code to print in addition.
+ * Will not be printed if set to a non-error (e.g. VINF _ / VWRN_) code.
+ * @param pcszMsgFmt Format string.
+ * @param ... Format arguments.
+ * @note
+ */
+HRESULT GuestDnDBase::i_setErrorAndReset(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT const hrc = i_setErrorV(vrc, pcszMsgFmt, va);
+ va_end(va);
+
+ i_reset();
+
+ return hrc;
+}
+
+/**
+ * Adds a new guest DnD message to the internal message queue.
+ *
+ * @returns VBox status code.
+ * @param pMsg Pointer to message to add.
+ */
+int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
+{
+ m_DataBase.lstMsgOut.push_back(pMsg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns the next guest DnD message in the internal message queue (FIFO).
+ *
+ * @returns Pointer to guest DnD message, or NULL if none found.
+ */
+GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
+{
+ if (m_DataBase.lstMsgOut.empty())
+ return NULL;
+ return m_DataBase.lstMsgOut.front();
+}
+
+/**
+ * Removes the next guest DnD message from the internal message queue.
+ */
+void GuestDnDBase::msgQueueRemoveNext(void)
+{
+ if (!m_DataBase.lstMsgOut.empty())
+ {
+ GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
+ if (pMsg)
+ delete pMsg;
+ m_DataBase.lstMsgOut.pop_front();
+ }
+}
+
+/**
+ * Clears the internal message queue.
+ */
+void GuestDnDBase::msgQueueClear(void)
+{
+ LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
+
+ GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
+ while (itMsg != m_DataBase.lstMsgOut.end())
+ {
+ GuestDnDMsg *pMsg = *itMsg;
+ if (pMsg)
+ delete pMsg;
+
+ itMsg++;
+ }
+
+ m_DataBase.lstMsgOut.clear();
+}
+
+/**
+ * Sends a request to the guest side to cancel the current DnD operation.
+ *
+ * @returns VBox status code.
+ */
+int GuestDnDBase::sendCancel(void)
+{
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_CANCEL);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+
+ LogRel2(("DnD: Cancelling operation on guest ...\n"));
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Helper function to update the progress based on given a GuestDnDData object.
+ *
+ * @returns VBox status code.
+ * @param pData GuestDnDData object to use for accounting.
+ * @param pState Guest state to update its progress object for.
+ * @param cbDataAdd By how much data (in bytes) to update the progress.
+ */
+int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDState *pState,
+ size_t cbDataAdd /* = 0 */)
+{
+ AssertPtrReturn(pData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+ /* cbDataAdd is optional. */
+
+ LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
+ pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
+
+ if ( !pState
+ || !cbDataAdd) /* Only update if something really changes. */
+ return VINF_SUCCESS;
+
+ if (cbDataAdd)
+ pData->addProcessed(cbDataAdd);
+
+ const uint8_t uPercent = pData->getPercentComplete();
+
+ LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
+
+ int vrc = pState->setProgress(uPercent, pData->isComplete() ? DND_PROGRESS_COMPLETE : DND_PROGRESS_RUNNING);
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a specific guest callback event to get signalled.
+ *
+ * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object.
+ * @param pEvent Callback event to wait for.
+ * @param pState Guest state to update.
+ * @param msTimeout Timeout (in ms) to wait.
+ */
+int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+
+ int vrc;
+
+ 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.
+ */
+ vrc = pEvent->Wait(500 /* ms */);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Result();
+ LogFlowFunc(("Callback done, result is %Rrc\n", vrc));
+ break;
+ }
+ if (vrc == VERR_TIMEOUT) /* Continue waiting. */
+ vrc = VINF_SUCCESS;
+
+ if ( msTimeout != RT_INDEFINITE_WAIT
+ && RTTimeMilliTS() - tsStart > msTimeout)
+ {
+ vrc = VERR_TIMEOUT;
+ LogRel2(("DnD: Error: Guest did not respond within time\n"));
+ }
+ else if (pState->isProgressCanceled())
+ {
+ LogRel2(("DnD: Operation was canceled by user\n"));
+ vrc = VERR_CANCELLED;
+ }
+
+ } while (RT_SUCCESS(vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#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..be5ec31a
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp
@@ -0,0 +1,1715 @@
+/* $Id: GuestDnDSourceImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag and drop source.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDSOURCE
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "GuestDnDSourceImpl.h"
+#include "GuestDnDPrivate.h"
+#include "ConsoleImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+#include <iprt/uri.h>
+
+#include <iprt/cpp/utils.h> /* For unconst(). */
+
+#include <VBox/com/array.h>
+
+
+/**
+ * Base class for a source task.
+ */
+class GuestDnDSourceTask : public ThreadTask
+{
+public:
+
+ GuestDnDSourceTask(GuestDnDSource *pSource)
+ : ThreadTask("GenericGuestDnDSourceTask")
+ , mSource(pSource)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestDnDSourceTask(void) { }
+
+ /** Returns the overall result of the task. */
+ int getRC(void) const { return mRC; }
+ /** Returns if the overall result of the task is ok (succeeded) or not. */
+ bool isOk(void) const { return RT_SUCCESS(mRC); }
+
+protected:
+
+ /** COM object pointer to the parent (source). */
+ const ComObjPtr<GuestDnDSource> mSource;
+ /** Overall result of the task. */
+ int mRC;
+};
+
+/**
+ * Task structure for receiving data from a source using
+ * a worker thread.
+ */
+class GuestDnDRecvDataTask : public GuestDnDSourceTask
+{
+public:
+
+ GuestDnDRecvDataTask(GuestDnDSource *pSource, GuestDnDRecvCtx *pCtx)
+ : GuestDnDSourceTask(pSource)
+ , mpCtx(pCtx)
+ {
+ m_strTaskName = "dndSrcRcvData";
+ }
+
+ void handler()
+ {
+ LogFlowThisFunc(("\n"));
+
+ const ComObjPtr<GuestDnDSource> pThis(mSource);
+ Assert(!pThis.isNull());
+
+ AutoCaller autoCaller(pThis);
+ if (FAILED(autoCaller.hrc()))
+ return;
+
+ int vrc = pThis->i_receiveData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */);
+ if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_receiveData(). */
+ {
+ if (vrc != VERR_CANCELLED)
+ LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
+
+ /* Make sure to fire a cancel request to the guest side in case something went wrong. */
+ pThis->sendCancel();
+ }
+ }
+
+ virtual ~GuestDnDRecvDataTask(void) { }
+
+protected:
+
+ /** Pointer to receive data context. */
+ GuestDnDRecvCtx *mpCtx;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GuestDnDSource::GuestDnDSource(void)
+ : GuestDnDBase(this) { }
+
+GuestDnDSource::~GuestDnDSource(void) { }
+
+HRESULT GuestDnDSource::FinalConstruct(void)
+{
+ /*
+ * Set the maximum block size this source can handle to 64K. This always has
+ * been hardcoded until now.
+ *
+ * Note: Never ever rely on information from the guest; the host dictates what and
+ * how to do something, so try to negogiate a sensible value here later.
+ */
+ mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
+
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDnDSource::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m_pGuest) = pGuest;
+
+ /* Set the response we're going to use for this object.
+ *
+ * At the moment we only have one response total, as we
+ * don't allow
+ * 1) parallel transfers (multiple G->H at the same time)
+ * nor 2) mixed transfers (G->H + H->G at the same time).
+ */
+ m_pState = GuestDnDInst()->getState();
+ AssertPtrReturn(m_pState, E_POINTER);
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDnDSource::uninit(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+// implementation of wrapped IDnDBase methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::getFormats(GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFormats = GuestDnDBase::i_getFormats();
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::addFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ 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.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_removeFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of wrapped IDnDSource methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, GuestDnDMIMEList &aFormats,
+ std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* aDefaultAction is optional. */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /* Default is ignoring the action. */
+ if (aDefaultAction)
+ *aDefaultAction = DnDAction_Ignore;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+
+ /* Check if any operation is active, and if so, bail out, returning an ignore action (see above). */
+ if (pState->get() != VBOXDNDSTATE_UNKNOWN)
+ return S_OK;
+
+ pState->set(VBOXDNDSTATE_QUERY_FORMATS);
+
+ HRESULT hrc = S_OK;
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_REQ_PENDING);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(uScreenId);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ vrc = pState->waitForGuestResponseEx(100 /* Timeout in ms */, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!isDnDIgnoreAction(pState->getActionDefault()))
+ {
+ /*
+ * In the GuestDnDSource case the source formats are from the guest,
+ * as GuestDnDSource acts as a target for the guest. The host always
+ * dictates what's supported and what's not, so filter out all formats
+ * which are not supported by the host.
+ */
+ GuestDnDMIMEList const &lstGuest = pState->formats();
+ GuestDnDMIMEList const lstFiltered = GuestDnD::toFilteredFormatList(m_lstFmtSupported, lstGuest);
+ if (lstFiltered.size())
+ {
+ LogRel2(("DnD: Host offered the following formats:\n"));
+ for (size_t i = 0; i < lstFiltered.size(); i++)
+ LogRel2(("DnD:\tFormat #%zu: %s\n", i, lstFiltered.at(i).c_str()));
+
+ aFormats = lstFiltered;
+ aAllowedActions = GuestDnD::toMainActions(pState->getActionsAllowed());
+ if (aDefaultAction)
+ *aDefaultAction = GuestDnD::toMainAction(pState->getActionDefault());
+
+ /* Apply the (filtered) formats list. */
+ m_lstFmtOffered = lstFiltered;
+ }
+ else
+ {
+ bool fSetError = true; /* Whether to set an error and reset or not. */
+
+ /*
+ * HACK ALERT: As we now expose an error (via i_setErrorAndReset(), see below) back to the API client, we
+ * have to add a kludge here. Older X11-based Guest Additions report "TARGETS, MULTIPLE" back
+ * to us, even if they don't offer any other *supported* formats of the host. This then in turn
+ * would lead to exposing an error, whereas we just should ignore those specific X11-based
+ * formats. For anything other we really want to be notified by setting an error though.
+ */
+ if ( lstGuest.size() == 2
+ && GuestDnD::isFormatInFormatList("TARGETS", lstGuest)
+ && GuestDnD::isFormatInFormatList("MULTIPLE", lstGuest))
+ {
+ fSetError = false;
+ }
+ /* HACK ALERT END */
+
+ if (fSetError)
+ hrc = i_setErrorAndReset(tr("Negotiation of formats between guest and host failed!\n\nHost offers: %s\n\nGuest offers: %s"),
+ GuestDnD::toFormatString(m_lstFmtSupported , ",").c_str(),
+ GuestDnD::toFormatString(pState->formats() , ",").c_str());
+ else /* Just silently reset. */
+ i_reset();
+ }
+ }
+ /* Note: Don't report an error here when the action is "ignore" -- that only means that the current window on the guest
+ simply doesn't support the format or drag and drop at all. */
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Requesting pending data from guest failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Dragging from guest to host not allowed -- make sure that the correct drag'n drop mode is set"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Dragging from guest to host not supported by guest -- make sure that the Guest Additions are properly installed and running"));
+ break;
+ }
+
+ default:
+ {
+ hrc = i_setErrorAndReset(vrc, tr("Sending drag pending event to guest failed"));
+ break;
+ }
+ }
+ }
+
+ pState->set(VBOXDNDSTATE_UNKNOWN);
+
+ LogFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFunc(("aFormat=%s, aAction=%RU32\n", aFormat.c_str(), aAction));
+
+ /* Input validation. */
+ if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No drop format specified"));
+
+ /* Is the specified format in our list of (left over) offered formats? */
+ if (!GuestDnD::isFormatInFormatList(aFormat, m_lstFmtOffered))
+ return setError(E_INVALIDARG, tr("Specified format '%s' is not supported"), aFormat.c_str());
+
+ /* Check that the given action is supported by us. */
+ VBOXDNDACTION dndAction = GuestDnD::toHGCMAction(aAction);
+ if (isDnDIgnoreAction(dndAction)) /* If there is no usable action, ignore this request. */
+ return S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if this object still is in a pending state and bail out if so. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to host still in progress"));
+
+ /* Reset our internal state. */
+ i_reset();
+
+ /* At the moment we only support one transfer at a time. */
+ if (GuestDnDInst()->getSourceCount())
+ return setError(E_INVALIDARG, tr("Another drag and drop operation to the host already is in progress"));
+
+ /* Reset progress object. */
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+ HRESULT hrc = pState->resetProgress(m_pGuest, tr("Dropping data to host"));
+ if (FAILED(hrc))
+ return hrc;
+
+ GuestDnDRecvDataTask *pTask = NULL;
+
+ try
+ {
+ mData.mRecvCtx.pSource = this;
+ mData.mRecvCtx.pState = pState;
+ mData.mRecvCtx.enmAction = dndAction;
+ mData.mRecvCtx.strFmtReq = aFormat;
+ mData.mRecvCtx.lstFmtOffered = m_lstFmtOffered;
+
+ LogRel2(("DnD: Requesting data from guest in format '%s'\n", aFormat.c_str()));
+
+ pTask = new GuestDnDRecvDataTask(this, &mData.mRecvCtx);
+ if (!pTask->isOk())
+ {
+ delete pTask;
+ LogRel2(("DnD: Receive data task failed to initialize\n"));
+ throw hrc = E_FAIL;
+ }
+
+ /* Drop write lock before creating thread. */
+ alock.release();
+
+ /* This function delete pTask in case of exceptions,
+ * so there is no need in the call of delete operator. */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
+ pTask = NULL; /* Note: pTask is now owned by the worker thread. */
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ LogRel2(("DnD: Could not create thread for data receiving task\n"));
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ /* Register ourselves at the DnD manager. */
+ GuestDnDInst()->registerSource(this);
+
+ hrc = pState->queryProgressTo(aProgress.asOutParam());
+ ComAssertComRC(hrc);
+ }
+ else
+ hrc = i_setErrorAndReset(tr("Starting thread for GuestDnDSource failed (%Rhrc)"), hrc);
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#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.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Don't allow receiving the actual data until our current transfer is complete. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to host still in progress"));
+
+ HRESULT hrc = S_OK;
+
+ try
+ {
+ GuestDnDRecvCtx *pCtx = &mData.mRecvCtx;
+ if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
+ {
+ PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ const char *pcszDropDirAbs = DnDDroppedFilesGetDirAbs(pDF);
+ AssertPtr(pcszDropDirAbs);
+
+ LogRel2(("DnD: Using drop directory '%s', got %RU64 root entries\n",
+ pcszDropDirAbs, DnDTransferListGetRootCount(&pCtx->Transfer.List)));
+
+ /* We return the data as "text/uri-list" MIME data here. */
+ char *pszBuf = NULL;
+ size_t cbBuf = 0;
+ int vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ pcszDropDirAbs, DND_PATH_SEPARATOR_STR, &pszBuf, &cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(cbBuf);
+ AssertPtr(pszBuf);
+
+ aData.resize(cbBuf);
+ memcpy(&aData.front(), pszBuf, cbBuf);
+ RTStrFree(pszBuf);
+ }
+ else
+ LogRel(("DnD: Unable to build source root list, vrc=%Rrc\n", vrc));
+ }
+ else /* Raw data. */
+ {
+ if (pCtx->Meta.cbData)
+ {
+ /* Copy the data into a safe array of bytes. */
+ aData.resize(pCtx->Meta.cbData);
+ memcpy(&aData.front(), pCtx->Meta.pvData, pCtx->Meta.cbData);
+ }
+ else
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ LogFlowFunc(("Returning hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of internal methods.
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns an error string from a guest DnD error.
+ *
+ * @returns Error string.
+ * @param guestRc Guest error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDSource::i_guestErrorToString(int guestRc)
+{
+ Utf8Str strError;
+
+ switch (guestRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your guest user has the appropriate rights"));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the guest, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
+ "found on the guest anymore. This can be the case if the guest files were moved and/or"
+ "altered while the drag and drop operation was in progress"));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
+ "Please make sure that all selected elements can be accessed and that your guest user has "
+ "the appropriate rights"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError += Utf8StrFmt(tr("The guest was not able to retrieve the drag and drop data within time"));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns an error string from a host DnD error.
+ *
+ * @returns Error string.
+ * @param hostRc Host error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDSource::i_hostErrorToString(int hostRc)
+{
+ Utf8Str strError;
+
+ switch (hostRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your host user has the appropriate rights."));
+ break;
+
+ case VERR_DISK_FULL:
+ strError += Utf8StrFmt(tr("Host disk ran out of space (disk is full)."));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the host, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
+ "found on the host anymore. This can be the case if the host files were moved and/or"
+ "altered while the drag and drop operation was in progress."));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
+ "Please make sure that all selected elements can be accessed and that your host user has "
+ "the appropriate rights."));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Resets all internal data and state.
+ */
+void GuestDnDSource::i_reset(void)
+{
+ LogRel2(("DnD: Source reset\n"));
+
+ mData.mRecvCtx.reset();
+
+ m_fIsPending = false;
+
+ /* Unregister ourselves from the DnD manager. */
+ GuestDnDInst()->unregisterSource(this);
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+
+/**
+ * Handles receiving a send data header from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pDataHdr Pointer to send data header from the guest.
+ */
+int GuestDnDSource::i_onReceiveDataHdr(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+
+ LogRel2(("DnD: Receiving %RU64 bytes total data (%RU32 bytes meta data, %RU64 objects) from guest ...\n",
+ pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects));
+
+ AssertReturn(pDataHdr->cbTotal >= pDataHdr->cbMeta, VERR_INVALID_PARAMETER);
+
+ pCtx->Meta.cbAnnounced = pDataHdr->cbMeta;
+ pCtx->cbExtra = pDataHdr->cbTotal - pDataHdr->cbMeta;
+
+ Assert(pCtx->Transfer.cObjToProcess == 0); /* Sanity. */
+ Assert(pCtx->Transfer.cObjProcessed == 0);
+
+ pCtx->Transfer.reset();
+
+ pCtx->Transfer.cObjToProcess = pDataHdr->cObjects;
+
+ /** @todo Handle compression type. */
+ /** @todo Handle checksum type. */
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Main function for receiving data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pSndData Pointer to send data block from the guest.
+ */
+int GuestDnDSource::i_onReceiveData(GuestDnDRecvCtx *pCtx, PVBOXDNDSNDDATA pSndData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSndData, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+
+ try
+ {
+ GuestDnDTransferRecvData *pTransfer = &pCtx->Transfer;
+
+ size_t cbData;
+ void *pvData;
+ size_t cbTotalAnnounced;
+ size_t cbMetaAnnounced;
+
+ if (m_pState->m_uProtocolVersion < 3)
+ {
+ cbData = pSndData->u.v1.cbData;
+ pvData = pSndData->u.v1.pvData;
+
+ /* Sends the total data size to receive for every data chunk. */
+ cbTotalAnnounced = pSndData->u.v1.cbTotalSize;
+
+ /* Meta data size always is cbData, meaning there cannot be an
+ * extended data chunk transfer by sending further data. */
+ cbMetaAnnounced = cbData;
+ }
+ else
+ {
+ cbData = pSndData->u.v3.cbData;
+ pvData = pSndData->u.v3.pvData;
+
+ /* Note: Data sizes get initialized in i_onReceiveDataHdr().
+ * So just use the set values here. */
+ cbTotalAnnounced = pCtx->getTotalAnnounced();
+ cbMetaAnnounced = pCtx->Meta.cbAnnounced;
+ }
+
+ if (cbData > cbTotalAnnounced)
+ {
+ AssertMsgFailed(("Incoming data size invalid: cbData=%zu, cbTotal=%zu\n", cbData, cbTotalAnnounced));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else if ( cbTotalAnnounced == 0
+ || cbTotalAnnounced < cbMetaAnnounced)
+ {
+ AssertMsgFailed(("cbTotal (%zu) is smaller than cbMeta (%zu)\n", cbTotalAnnounced, cbMetaAnnounced));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ AssertReturn(cbData <= mData.mcbBlockSize, VERR_BUFFER_OVERFLOW);
+
+ const size_t cbMetaRecv = pCtx->Meta.add(pvData, cbData);
+ AssertReturn(cbMetaRecv <= pCtx->Meta.cbData, VERR_BUFFER_OVERFLOW);
+
+ LogFlowThisFunc(("cbData=%zu, cbMetaRecv=%zu, cbMetaAnnounced=%zu, cbTotalAnnounced=%zu\n",
+ cbData, cbMetaRecv, cbMetaAnnounced, cbTotalAnnounced));
+
+ LogRel2(("DnD: %RU8%% of meta data complete (%zu/%zu bytes)\n",
+ (uint8_t)(cbMetaRecv * 100 / RT_MAX(cbMetaAnnounced, 1)), cbMetaRecv, cbMetaAnnounced));
+
+ /*
+ * (Meta) Data transfer complete?
+ */
+ if (cbMetaAnnounced == cbMetaRecv)
+ {
+ LogRel2(("DnD: Receiving meta data complete\n"));
+
+ if (DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length()))
+ {
+ vrc = DnDTransferListInitEx(&pTransfer->List,
+ DnDDroppedFilesGetDirAbs(&pTransfer->DroppedFiles), DNDTRANSFERLISTFMT_NATIVE);
+ if (RT_SUCCESS(vrc))
+ vrc = DnDTransferListAppendRootsFromBuffer(&pTransfer->List, DNDTRANSFERLISTFMT_URI,
+ (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData,
+ DND_PATH_SEPARATOR_STR, DNDTRANSFERLIST_FLAGS_NONE);
+ /* Validation. */
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t cRoots = DnDTransferListGetRootCount(&pTransfer->List);
+
+ LogRel2(("DnD: Received %RU64 root entries from guest\n", cRoots));
+
+ if ( cRoots == 0
+ || cRoots > pTransfer->cObjToProcess)
+ {
+ LogRel(("DnD: Number of root entries invalid / mismatch: Got %RU64, expected %RU64\n",
+ cRoots, pTransfer->cObjToProcess));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Update our process with the data we already received. */
+ vrc = updateProgress(pCtx, pCtx->pState, cbMetaAnnounced);
+ AssertRC(vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Error building root entry list, vrc=%Rrc\n", vrc));
+ }
+ else /* Raw data. */
+ {
+ vrc = updateProgress(pCtx, pCtx->pState, cbData);
+ AssertRC(vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Error receiving meta data, vrc=%Rrc\n", vrc));
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+
+int GuestDnDSource::i_onReceiveDir(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
+
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ int vrc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_DIRECTORY, DnDDroppedFilesGetDirAbs(pDF), pszPath);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pcszPathAbs = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtr(pcszPathAbs);
+
+ vrc = RTDirCreateFullPath(pcszPathAbs, fMode);
+ if (RT_SUCCESS(vrc))
+ {
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
+ vrc = DnDDroppedFilesAddDir(pDF, pcszPathAbs);
+ else
+ vrc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+
+ if (RT_FAILURE(vrc))
+ LogRel2(("DnD: Created guest directory '%s' on host\n", pcszPathAbs));
+ }
+ else
+ LogRel(("DnD: Error creating guest directory '%s' on host, vrc=%Rrc\n", pcszPathAbs, vrc));
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Receiving guest directory '%s' failed with vrc=%Rrc\n", pszPath, vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Receives a file header from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pszPath File path of file to use.
+ * @param cbPath Size (in bytes, including terminator) of file path.
+ * @param cbSize File size (in bytes) to receive.
+ * @param fMode File mode to use.
+ * @param fFlags Additional receive flags; not used yet.
+ */
+int GuestDnDSource::i_onReceiveFileHdr(GuestDnDRecvCtx *pCtx, const char *pszPath, uint32_t cbPath,
+ uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath, VERR_INVALID_PARAMETER);
+ AssertReturn(fMode, VERR_INVALID_PARAMETER);
+ /* fFlags are optional. */
+
+ RT_NOREF(fFlags);
+
+ LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
+
+ AssertMsgReturn(cbSize <= pCtx->cbExtra,
+ ("File size (%RU64) exceeds extra size to transfer (%RU64)\n", cbSize, pCtx->cbExtra), VERR_INVALID_PARAMETER);
+ AssertMsgReturn( pCtx->isComplete() == false
+ && pCtx->Transfer.cObjToProcess,
+ ("Data transfer already complete, bailing out\n"), VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ do
+ {
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+
+ if ( DnDTransferObjectIsOpen(pObj)
+ && !DnDTransferObjectIsComplete(pObj))
+ {
+ AssertMsgFailed(("Object '%s' not complete yet\n", DnDTransferObjectGetSourcePath(pObj)));
+ vrc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ vrc = DnDTransferObjectInitEx(pObj, DNDTRANSFEROBJTYPE_FILE, DnDDroppedFilesGetDirAbs(pDF), pszPath);
+ AssertRCBreak(vrc);
+
+ const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
+
+ /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
+ vrc = DnDTransferObjectOpen(pObj, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
+ (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR, DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Error opening/creating guest file '%s' on host, vrc=%Rrc\n", pcszSource, vrc));
+ break;
+ }
+
+ /* Note: Protocol v1 does not send any file sizes, so always 0. */
+ if (m_pState->m_uProtocolVersion >= 2)
+ vrc = DnDTransferObjectSetSize(pObj, cbSize);
+
+ /** @todo Unescape path before printing. */
+ LogRel2(("DnD: Transferring guest file '%s' to host (%RU64 bytes, mode %#x)\n",
+ pcszSource, DnDTransferObjectGetSize(pObj), DnDTransferObjectGetMode(pObj)));
+
+ /** @todo Set progress object title to current file being transferred? */
+
+ if (DnDTransferObjectIsComplete(pObj)) /* 0-byte file? We're done already. */
+ {
+ LogRel2(("DnD: Transferring guest file '%s' (0 bytes) to host complete\n", pcszSource));
+
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed <= pCtx->Transfer.cObjToProcess)
+ {
+ /* Add for having a proper rollback. */
+ vrc = DnDDroppedFilesAddFile(pDF, pcszSource);
+ }
+ else
+ vrc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Error receiving guest file header, vrc=%Rrc\n", vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Receives file data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param pvData Pointer to file data received from the guest.
+ * @param pCtx Size (in bytes) of file data received from the guest.
+ */
+int GuestDnDSource::i_onReceiveFileData(GuestDnDRecvCtx *pCtx, const void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ LogFlowFunc(("pvData=%p, cbData=%RU32, cbBlockSize=%RU32\n", pvData, cbData, mData.mcbBlockSize));
+
+ /*
+ * Sanity checking.
+ */
+ if (cbData > mData.mcbBlockSize)
+ return VERR_INVALID_PARAMETER;
+
+ do
+ {
+ const PDNDTRANSFEROBJECT pObj = &pCtx->Transfer.ObjCur;
+
+ const char *pcszSource = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrBreakStmt(pcszSource, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(DnDTransferObjectIsOpen(pObj),
+ ("Object '%s' not open (anymore)\n", pcszSource), VERR_WRONG_ORDER);
+ AssertMsgReturn(DnDTransferObjectIsComplete(pObj) == false,
+ ("Object '%s' already marked as complete\n", pcszSource), VERR_WRONG_ORDER);
+
+ uint32_t cbWritten;
+ vrc = DnDTransferObjectWrite(pObj, pvData, cbData, &cbWritten);
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Error writing guest file data for '%s', vrc=%Rrc\n", pcszSource, vrc));
+
+ Assert(cbWritten <= cbData);
+ if (cbWritten < cbData)
+ {
+ LogRel(("DnD: Only written %RU32 of %RU32 bytes of guest file '%s' -- disk full?\n",
+ cbWritten, cbData, pcszSource));
+ vrc = VERR_IO_GEN_FAILURE; /** @todo Find a better vrc. */
+ break;
+ }
+
+ vrc = updateProgress(pCtx, pCtx->pState, cbWritten);
+ AssertRCBreak(vrc);
+
+ if (DnDTransferObjectIsComplete(pObj))
+ {
+ LogRel2(("DnD: Transferring guest file '%s' to host complete\n", pcszSource));
+
+ pCtx->Transfer.cObjProcessed++;
+ if (pCtx->Transfer.cObjProcessed > pCtx->Transfer.cObjToProcess)
+ vrc = VERR_TOO_MUCH_DATA;
+
+ DnDTransferObjectDestroy(pObj);
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Error receiving guest file data, vrc=%Rrc\n", vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/**
+ * Main function to receive DnD data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /* Sanity. */
+ AssertMsgReturn(pCtx->enmAction,
+ ("Action to perform is none when it shouldn't\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pCtx->strFmtReq.isNotEmpty(),
+ ("Requested format from host is empty when it shouldn't\n"), VERR_INVALID_PARAMETER);
+
+ /*
+ * Do we need to receive a different format than initially requested?
+ *
+ * For example, receiving a file link as "text/plain" requires still to receive
+ * the file from the guest as "text/uri-list" first, then pointing to
+ * the file path on the host in the "text/plain" data returned.
+ */
+
+ bool fFoundFormat = true; /* Whether we've found a common format between host + guest. */
+
+ LogFlowFunc(("strFmtReq=%s, strFmtRecv=%s, enmAction=0x%x\n",
+ pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str(), pCtx->enmAction));
+
+ /* Plain text wanted? */
+ if ( pCtx->strFmtReq.equalsIgnoreCase("text/plain")
+ || pCtx->strFmtReq.equalsIgnoreCase("text/plain;charset=utf-8"))
+ {
+ /* Did the guest offer a file? Receive a file instead. */
+ if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
+ pCtx->strFmtRecv = "text/uri-list";
+ /* Guest only offers (plain) text. */
+ else
+ pCtx->strFmtRecv = "text/plain;charset=utf-8";
+
+ /** @todo Add more conversions here. */
+ }
+ /* File(s) wanted? */
+ else if (pCtx->strFmtReq.equalsIgnoreCase("text/uri-list"))
+ {
+ /* Does the guest support sending files? */
+ if (GuestDnD::isFormatInFormatList("text/uri-list", pCtx->lstFmtOffered))
+ pCtx->strFmtRecv = "text/uri-list";
+ else /* Bail out. */
+ fFoundFormat = false;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ if (fFoundFormat)
+ {
+ if (!pCtx->strFmtRecv.equals(pCtx->strFmtReq))
+ LogRel2(("DnD: Requested data in format '%s', receiving in intermediate format '%s' now\n",
+ pCtx->strFmtReq.c_str(), pCtx->strFmtRecv.c_str()));
+
+ /*
+ * Call the appropriate receive handler based on the data format to handle.
+ */
+ bool fURIData = DnDMIMENeedsDropDir(pCtx->strFmtRecv.c_str(), pCtx->strFmtRecv.length());
+ if (fURIData)
+ vrc = i_receiveTransferData(pCtx, msTimeout);
+ else
+ vrc = i_receiveRawData(pCtx, msTimeout);
+ }
+ else /* Just inform the user (if verbose release logging is enabled). */
+ {
+ LogRel(("DnD: The guest does not support format '%s':\n", pCtx->strFmtReq.c_str()));
+ LogRel(("DnD: Guest offered the following formats:\n"));
+ for (size_t i = 0; i < pCtx->lstFmtOffered.size(); i++)
+ LogRel(("DnD:\tFormat #%zu: %s\n", i, pCtx->lstFmtOffered.at(i).c_str()));
+
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Receiving data from guest failed with %Rrc\n", vrc));
+
+ /* Let the guest side know first. */
+ sendCancel();
+
+ /* Reset state. */
+ i_reset();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Receives raw (meta) data from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveRawData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ int vrc;
+
+ LogFlowFuncEnter();
+
+ GuestDnDState *pState = pCtx->pState;
+ AssertPtr(pCtx->pState);
+
+ GuestDnD *pInst = GuestDnDInst();
+ if (!pInst)
+ return VERR_INVALID_POINTER;
+
+#define REGISTER_CALLBACK(x) do { \
+ vrc = pState->setCallback(x, i_receiveRawDataCallback, pCtx); \
+ if (RT_FAILURE(vrc)) \
+ return vrc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) do { \
+ int vrc2 = pState->setCallback(x, NULL); \
+ AssertRC(vrc2); \
+ } while (0)
+
+ /*
+ * Register callbacks.
+ */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+
+ do
+ {
+ /*
+ * Receive the raw data.
+ */
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32(pCtx->enmAction);
+
+ /* Make the initial call to the guest by telling that we initiated the "dropped" event on
+ * the host and therefore now waiting for the actual raw data. */
+ vrc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(vrc))
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
+ {
+ /*
+ * Now that we've cleaned up tell the guest side to cancel.
+ * This does not imply we're waiting for the guest to react, as the
+ * host side never must depend on anything from the guest.
+ */
+ int vrc2 = sendCancel();
+ AssertRC(vrc2);
+
+ vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ AssertRC(vrc2);
+ }
+ else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ int vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR,
+ vrc, GuestDnDSource::i_hostErrorToString(vrc));
+ AssertRC(vrc2);
+ }
+
+ vrc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Receives transfer data (files / directories / ...) from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Receive context to use.
+ * @param msTimeout Timeout (in ms) to wait for receiving data.
+ */
+int GuestDnDSource::i_receiveTransferData(GuestDnDRecvCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ int vrc;
+
+ LogFlowFuncEnter();
+
+ GuestDnDState *pState = pCtx->pState;
+ AssertPtr(pCtx->pState);
+
+ GuestDnD *pInst = GuestDnDInst();
+ if (!pInst)
+ return VERR_INVALID_POINTER;
+
+#define REGISTER_CALLBACK(x) do { \
+ vrc = pState->setCallback(x, i_receiveTransferDataCallback, pCtx); \
+ if (RT_FAILURE(vrc)) \
+ return vrc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) do { \
+ int vrc2 = pState->setCallback(x, NULL); \
+ AssertRC(vrc2); \
+ } while (0)
+
+ /*
+ * Register callbacks.
+ */
+ /* Guest callbacks. */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR);
+ REGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA);
+
+ const PDNDDROPPEDFILES pDF = &pCtx->Transfer.DroppedFiles;
+
+ do
+ {
+ vrc = DnDDroppedFilesOpenTemp(pDF, 0 /* fFlags */);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Opening dropped files directory '%s' on the host failed with vrc=%Rrc\n",
+ DnDDroppedFilesGetDirAbs(pDF), vrc));
+ break;
+ }
+
+ /*
+ * Receive the transfer list.
+ */
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_GH_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer((void*)pCtx->strFmtRecv.c_str(), (uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32((uint32_t)pCtx->strFmtRecv.length() + 1);
+ Msg.appendUInt32(pCtx->enmAction);
+
+ /* Make the initial call to the guest by telling that we initiated the "dropped" event on
+ * the host and therefore now waiting for the actual URI data. */
+ vrc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Waiting ...\n"));
+
+ vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(vrc))
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+
+ LogFlowFunc(("Waiting ended with vrc=%Rrc\n", vrc));
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DATA);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_DIR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_HDR);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GH_SND_FILE_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = DnDDroppedFilesRollback(pDF);
+ if (RT_FAILURE(vrc2))
+ LogRel(("DnD: Deleting left over temporary files failed (%Rrc), please remove directory '%s' manually\n",
+ vrc2, DnDDroppedFilesGetDirAbs(pDF)));
+
+ if (vrc == 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.
+ */
+ vrc2 = sendCancel();
+ AssertRC(vrc2);
+
+ vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ AssertRC(vrc2);
+
+ /* Cancelling is not an error, just set success here. */
+ vrc = VINF_SUCCESS;
+ }
+ else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, vrc, GuestDnDSource::i_hostErrorToString(vrc2));
+ AssertRC(vrc2);
+ }
+ }
+
+ DnDDroppedFilesClose(pDF);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Static HGCM service callback which handles receiving raw data.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDSource *pThis = pCtx->pSource;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
+
+ int vrc = VINF_SUCCESS;
+
+ int vrcCallback = VINF_SUCCESS; /* vrc for the callback. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ vrc = VERR_CANCELLED;
+ break;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case GUEST_DND_FN_GH_SND_DATA_HDR:
+ {
+ PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DATA:
+ {
+ PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveData(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Received guest error with no error code set\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+ else if (pCBData->rc == VERR_WRONG_ORDER)
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ else
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDSource::i_guestErrorToString(pCBData->rc));
+
+ LogRel3(("DnD: Guest reported data transfer error: %Rrc\n", pCBData->rc));
+
+ if (RT_SUCCESS(vrc))
+ vrcCallback = VERR_DND_GUEST_ERROR;
+ break;
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if ( RT_FAILURE(vrc)
+ || RT_FAILURE(vrcCallback))
+ {
+ fNotify = true;
+ if (RT_SUCCESS(vrcCallback))
+ vrcCallback = vrc;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ 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", vrc));
+ break;
+ }
+
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(vrc2);
+ }
+
+ /* All data processed? */
+ if (pCtx->isComplete())
+ fNotify = true;
+
+ LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, vrcCallback=%Rrc, vrc=%Rrc\n",
+ pCtx->cbProcessed, pCtx->cbExtra, fNotify, vrcCallback, vrc));
+
+ if (fNotify)
+ {
+ int vrc2 = pCtx->EventCallback.Notify(vrcCallback);
+ AssertRC(vrc2);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc; /* Tell the guest. */
+}
+
+/**
+ * Static HGCM service callback which handles receiving transfer data from the guest.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDSource::i_receiveTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDRecvCtx *pCtx = (GuestDnDRecvCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDSource *pThis = pCtx->pSource;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
+
+ int vrc = VINF_SUCCESS;
+
+ int vrcCallback = VINF_SUCCESS; /* vrc for the callback. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ vrc = VERR_CANCELLED;
+ break;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case GUEST_DND_FN_GH_SND_DATA_HDR:
+ {
+ PVBOXDNDCBSNDDATAHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATAHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATAHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveDataHdr(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DATA:
+ {
+ PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveData(pCtx, &pCBData->data);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_DIR:
+ {
+ PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDDIRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_FILE_HDR:
+ {
+ PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ vrc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
+ pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
+ break;
+ }
+ case GUEST_DND_FN_GH_SND_FILE_DATA:
+ {
+ PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<PVBOXDNDCBSNDFILEDATADATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ if (pThis->m_pState->m_uProtocolVersion <= 1)
+ {
+ /*
+ * Notes for protocol v1 (< VBox 5.0):
+ * - Every time this command is being sent it includes the file header,
+ * so just process both calls here.
+ * - There was no information whatsoever about the total file size; the old code only
+ * appended data to the desired file. So just pass 0 as cbSize.
+ */
+ vrc = pThis->i_onReceiveFileHdr(pCtx, pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
+ 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ vrc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
+ }
+ else /* Protocol v2 and up. */
+ vrc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Received guest error with no error code set\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+ else if (pCBData->rc == VERR_WRONG_ORDER)
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED);
+ else
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDSource::i_guestErrorToString(pCBData->rc));
+
+ LogRel3(("DnD: Guest reported file transfer error: %Rrc\n", pCBData->rc));
+
+ if (RT_SUCCESS(vrc))
+ vrcCallback = VERR_DND_GUEST_ERROR;
+ break;
+ }
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if ( RT_FAILURE(vrc)
+ || RT_FAILURE(vrcCallback))
+ {
+ fNotify = true;
+ if (RT_SUCCESS(vrcCallback))
+ vrcCallback = vrc;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ 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", vrc));
+ break;
+ }
+
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(vrc2);
+ }
+
+ /* All data processed? */
+ if ( pCtx->Transfer.isComplete()
+ && pCtx->isComplete())
+ fNotify = true;
+
+ LogFlowFunc(("cbProcessed=%RU64, cbExtra=%RU64, fNotify=%RTbool, vrcCallback=%Rrc, vrc=%Rrc\n",
+ pCtx->cbProcessed, pCtx->cbExtra, fNotify, vrcCallback, vrc));
+
+ if (fNotify)
+ {
+ int vrc2 = pCtx->EventCallback.Notify(
+ vrcCallback);
+ AssertRC(vrc2);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc; /* 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..fa982753
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp
@@ -0,0 +1,1789 @@
+/* $Id: GuestDnDTargetImpl.cpp $ */
+/** @file
+ * VBox Console COM Class implementation - Guest drag'n drop target.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDTARGET
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#include "GuestDnDTargetImpl.h"
+#include "ConsoleImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ThreadTask.h"
+
+#include <algorithm> /* For std::find(). */
+
+#include <iprt/asm.h>
+#include <iprt/file.h>
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/uri.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+
+#include <VBox/com/array.h>
+
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/Service.h>
+
+
+/**
+ * Base class for a target task.
+ */
+class GuestDnDTargetTask : public ThreadTask
+{
+public:
+
+ GuestDnDTargetTask(GuestDnDTarget *pTarget)
+ : ThreadTask("GenericGuestDnDTargetTask")
+ , mTarget(pTarget)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestDnDTargetTask(void) { }
+
+ /** Returns the overall result of the task. */
+ int getRC(void) const { return mRC; }
+ /** Returns if the overall result of the task is ok (succeeded) or not. */
+ bool isOk(void) const { return RT_SUCCESS(mRC); }
+
+protected:
+
+ /** COM object pointer to the parent (source). */
+ const ComObjPtr<GuestDnDTarget> mTarget;
+ /** Overall result of the task. */
+ int mRC;
+};
+
+/**
+ * Task structure for sending data to a target using
+ * a worker thread.
+ */
+class GuestDnDSendDataTask : public GuestDnDTargetTask
+{
+public:
+
+ GuestDnDSendDataTask(GuestDnDTarget *pTarget, GuestDnDSendCtx *pCtx)
+ : GuestDnDTargetTask(pTarget),
+ mpCtx(pCtx)
+ {
+ m_strTaskName = "dndTgtSndData";
+ }
+
+ void handler()
+ {
+ const ComObjPtr<GuestDnDTarget> pThis(mTarget);
+ Assert(!pThis.isNull());
+
+ AutoCaller autoCaller(pThis);
+ if (autoCaller.isNotOk())
+ return;
+
+ pThis->i_sendData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */); /* ignore return code */
+ }
+
+ virtual ~GuestDnDSendDataTask(void) { }
+
+protected:
+
+ /** Pointer to send data context. */
+ GuestDnDSendCtx *mpCtx;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+GuestDnDTarget::GuestDnDTarget(void)
+ : GuestDnDBase(this) { }
+
+GuestDnDTarget::~GuestDnDTarget(void) { }
+
+HRESULT GuestDnDTarget::FinalConstruct(void)
+{
+ /* Set the maximum block size our guests can handle to 64K. This always has
+ * been hardcoded until now. */
+ /* Note: Never ever rely on information from the guest; the host dictates what and
+ * how to do something, so try to negogiate a sensible value here later. */
+ mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
+
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void GuestDnDTarget::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(m_pGuest) = pGuest;
+
+ /* Set the response we're going to use for this object.
+ *
+ * At the moment we only have one response total, as we
+ * don't allow
+ * 1) parallel transfers (multiple G->H at the same time)
+ * nor 2) mixed transfers (G->H + H->G at the same time).
+ */
+ m_pState = GuestDnDInst()->getState();
+ AssertPtrReturn(m_pState, E_POINTER);
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestDnDTarget::uninit(void)
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+}
+
+// implementation of wrapped IDnDBase methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFormats = GuestDnDBase::i_getFormats();
+
+ return S_OK;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_addFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return GuestDnDBase::i_removeFormats(aFormats);
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+// implementation of wrapped IDnDTarget methods.
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* Input validation. */
+ if (aDefaultAction == DnDAction_Ignore)
+ return setError(E_INVALIDARG, tr("No default action specified"));
+ if (!aAllowedActions.size())
+ return setError(E_INVALIDARG, tr("Number of allowed actions is empty"));
+ if (!aFormats.size())
+ return setError(E_INVALIDARG, tr("Number of supported formats is empty"));
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAction = DnDAction_Ignore;
+
+ /* Check & convert the drag & drop actions. */
+ VBOXDNDACTION dndActionDefault = 0;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ return S_OK;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ LogRel2(("DnD: Offered formats to guest:\n"));
+ RTCList<RTCString> lstFormats = strFormats.split(DND_PATH_SEPARATOR_STR);
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ LogRel2(("DnD: \t%s\n", lstFormats[i].c_str()));
+
+ /* Save the formats offered to the guest. This is needed to later
+ * decide what to do with the data when sending stuff to the guest. */
+ m_lstFmtOffered = aFormats;
+ Assert(m_lstFmtOffered.size());
+
+ /* Adjust the coordinates in a multi-monitor setup. */
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_ENTER);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void *)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAction = GuestDnD::toMainAction(m_pState->getActionDefault());
+
+ LogRel2(("DnD: Host enters the VM window at %RU32,%RU32 (screen %u, default action is '%s') -> guest reported back action '%s'\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction)));
+
+ pState->set(VBOXDNDSTATE_ENTERED);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Entering VM window failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Drag and drop to guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Drag and drop to guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Entering VM window failed"));
+ break;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ if (aResultAction)
+ *aResultAction = resAction;
+ }
+
+ LogFlowFunc(("hrc=%Rhrc, resAction=%ld\n", hrc, resAction));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ /* Input validation. */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAction = DnDAction_Ignore;
+
+ /* Check & convert the drag & drop actions. */
+ VBOXDNDACTION dndActionDefault = 0;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ return S_OK;
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_MOVE);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void *)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAction = GuestDnD::toMainAction(pState->getActionDefault());
+
+ LogRel2(("DnD: Host moved to %RU32,%RU32 in VM window (screen %u, default action is '%s') -> guest reported back action '%s'\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault), DnDActionToStr(resAction)));
+
+ pState->set(VBOXDNDSTATE_DRAGGING);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc,
+ tr("Moving to %RU32,%RU32 (screen %u) failed"), aX, aY, aScreenId);
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Moving in guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Moving in guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Moving in VM window failed"));
+ break;
+ }
+ }
+ }
+ else
+ hrc = i_setErrorAndReset(tr("Retrieving move coordinates failed"));
+
+ if (SUCCEEDED(hrc))
+ {
+ if (aResultAction)
+ *aResultAction = resAction;
+ }
+
+ LogFlowFunc(("hrc=%Rhrc, *pResultAction=%ld\n", hrc, resAction));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::leave(ULONG uScreenId)
+{
+ RT_NOREF(uScreenId);
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (autoCaller.isNotOk()) return autoCaller.hrc();
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ if (pState->get() == VBOXDNDSTATE_DROP_STARTED)
+ return S_OK;
+
+ HRESULT hrc = S_OK;
+
+ LogRel2(("DnD: Host left the VM window (screen %u)\n", uScreenId));
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_LEAVE);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ pState->set(VBOXDNDSTATE_LEFT);
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Leaving VM window failed"));
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ hrc = i_setErrorAndReset(tr("Leaving guest not allowed. Select the right mode first"));
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = i_setErrorAndReset(tr("Leaving guest not possible -- either the guest OS does not support this, "
+ "or the Guest Additions are not installed"));
+ break;
+ }
+
+ default:
+ hrc = i_setErrorAndReset(vrc, tr("Leaving VM window failed"));
+ break;
+ }
+ }
+
+ LogFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
+ DnDAction_T aDefaultAction,
+ const std::vector<DnDAction_T> &aAllowedActions,
+ const GuestDnDMIMEList &aFormats,
+ com::Utf8Str &aFormat,
+ DnDAction_T *aResultAction)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ if (aDefaultAction == DnDAction_Ignore)
+ return setError(E_INVALIDARG, tr("Invalid default action specified"));
+ if (!aAllowedActions.size())
+ return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
+ if (!aFormats.size())
+ return setError(E_INVALIDARG, tr("No drop format(s) specified"));
+ /* aResultAction is optional. */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /* Default action is ignoring. */
+ DnDAction_T resAct = DnDAction_Ignore;
+ Utf8Str resFmt;
+
+ /* Check & convert the drag & drop actions to HGCM codes. */
+ VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTIONLIST dndActionListAllowed = 0;
+ GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
+ aAllowedActions, &dndActionListAllowed);
+
+ /* If there is no usable action, ignore this request. */
+ if (isDnDIgnoreAction(dndActionDefault))
+ {
+ aFormat = "";
+ if (aResultAction)
+ *aResultAction = DnDAction_Ignore;
+ return S_OK;
+ }
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ /*
+ * Make a flat data string out of the supported format list.
+ * In the GuestDnDTarget case the source formats are from the host,
+ * as GuestDnDTarget acts as a source for the guest.
+ */
+ Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
+ if (strFormats.isEmpty())
+ return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
+ const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
+
+ /* Adjust the coordinates in a multi-monitor setup. */
+ HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
+ if (SUCCEEDED(hrc))
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_EVT_DROPPED);
+ if (m_pState->m_uProtocolVersion >= 3)
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendUInt32(aScreenId);
+ Msg.appendUInt32(aX);
+ Msg.appendUInt32(aY);
+ Msg.appendUInt32(dndActionDefault);
+ Msg.appendUInt32(dndActionListAllowed);
+ Msg.appendPointer((void*)strFormats.c_str(), cbFormats);
+ Msg.appendUInt32(cbFormats);
+
+ LogRel2(("DnD: Host drops at %RU32,%RU32 in VM window (screen %u, default action is '%s')\n",
+ aX, aY, aScreenId, DnDActionToStr(dndActionDefault)));
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest;
+ if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
+ {
+ resAct = GuestDnD::toMainAction(pState->getActionDefault());
+ if (resAct != DnDAction_Ignore) /* Does the guest accept a drop at the current position? */
+ {
+ GuestDnDMIMEList lstFormats = pState->formats();
+ if (lstFormats.size() == 1) /* Exactly one format to use specified? */
+ {
+ resFmt = lstFormats.at(0);
+
+ LogRel2(("DnD: Guest accepted drop in format '%s' (action %#x, %zu format(s))\n",
+ resFmt.c_str(), resAct, lstFormats.size()));
+
+ pState->set(VBOXDNDSTATE_DROP_STARTED);
+ }
+ else
+ {
+ if (lstFormats.size() == 0)
+ hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but did not specify the format"));
+ else
+ hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but returned more than one drop format (%zu formats)"),
+ lstFormats.size());
+ }
+ }
+ }
+ else
+ hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Dropping into VM failed"));
+ }
+ else
+ hrc = i_setErrorAndReset(vrc, tr("Sending dropped event to guest failed"));
+ }
+ else
+ hrc = i_setErrorAndReset(hrc, tr("Retrieving drop coordinates failed"));
+
+ if (SUCCEEDED(hrc))
+ {
+ aFormat = resFmt;
+ if (aResultAction)
+ *aResultAction = resAct;
+ }
+
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+/**
+ * Initiates a data transfer from the host to the guest.
+ *
+ * The source is the host, whereas the target is the guest.
+ *
+ * @return HRESULT
+ * @param aScreenId Screen ID where this data transfer was initiated from.
+ * @param aFormat Format of data to send. MIME-style.
+ * @param aData Actual data to send.
+ * @param aProgress Where to return the progress object on success.
+ */
+HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
+ ComPtr<IProgress> &aProgress)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ /* Input validation. */
+ if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No data format specified"));
+ if (RT_UNLIKELY(!aData.size()))
+ return setError(E_INVALIDARG, tr("No data to send specified"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if this object still is in a pending state and bail out if so. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to guest still in progress"));
+
+ /* At the moment we only support one transfer at a time. */
+ if (GuestDnDInst()->getTargetCount())
+ return setError(E_INVALIDARG, tr("Another drag and drop operation to the guest already is in progress"));
+
+ /* Reset progress object. */
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtr(pState);
+ HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to guest"));
+ if (FAILED(hr))
+ return hr;
+
+ GuestDnDSendDataTask *pTask = NULL;
+
+ try
+ {
+ mData.mSendCtx.reset();
+
+ mData.mSendCtx.pTarget = this;
+ mData.mSendCtx.pState = pState;
+ mData.mSendCtx.uScreenID = aScreenId;
+
+ mData.mSendCtx.Meta.strFmt = aFormat;
+ mData.mSendCtx.Meta.add(aData);
+
+ LogRel2(("DnD: Host sends data in format '%s'\n", aFormat.c_str()));
+
+ pTask = new GuestDnDSendDataTask(this, &mData.mSendCtx);
+ if (!pTask->isOk())
+ {
+ delete pTask;
+ LogRel(("DnD: Could not create SendDataTask object\n"));
+ throw hr = E_FAIL;
+ }
+
+ /* This function delete pTask in case of exceptions,
+ * so there is no need in the call of delete operator. */
+ hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
+ pTask = NULL; /* Note: pTask is now owned by the worker thread. */
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ catch (...)
+ {
+ LogRel(("DnD: Could not create thread for data sending task\n"));
+ hr = E_FAIL;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ /* Register ourselves at the DnD manager. */
+ GuestDnDInst()->registerTarget(this);
+
+ /* Return progress to caller. */
+ hr = pState->queryProgressTo(aProgress.asOutParam());
+ ComAssertComRC(hr);
+ }
+ else
+ hr = i_setErrorAndReset(tr("Starting thread for GuestDnDTarget failed (%Rhrc)"), hr);
+
+ LogFlowFunc(("Returning hr=%Rhrc\n", hr));
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+/**
+ * Returns an error string from a guest DnD error.
+ *
+ * @returns Error string.
+ * @param guestRc Guest error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
+{
+ Utf8Str strError;
+
+ switch (guestRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your guest user has the appropriate rights"));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the guest, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
+ "found on the guest anymore. This can be the case if the guest files were moved and/or"
+ "altered while the drag and drop operation was in progress"));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
+ "Please make sure that all selected elements can be accessed and that your guest user has "
+ "the appropriate rights"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns an error string from a host DnD error.
+ *
+ * @returns Error string.
+ * @param hostRc Host error to return error string for.
+ */
+/* static */
+Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
+{
+ Utf8Str strError;
+
+ switch (hostRc)
+ {
+ case VERR_ACCESS_DENIED:
+ strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
+ "user does not have the appropriate access rights for. Please make sure that all selected "
+ "elements can be accessed and that your host user has the appropriate rights."));
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Should not happen due to file locking on the host, but anyway ... */
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
+ "found on the host anymore. This can be the case if the host files were moved and/or"
+ "altered while the drag and drop operation was in progress."));
+ break;
+
+ case VERR_SHARING_VIOLATION:
+ strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
+ "Please make sure that all selected elements can be accessed and that your host user has "
+ "the appropriate rights."));
+ break;
+
+ default:
+ strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Resets all internal data and state.
+ */
+void GuestDnDTarget::i_reset(void)
+{
+ LogRel2(("DnD: Target reset\n"));
+
+ mData.mSendCtx.reset();
+
+ m_fIsPending = false;
+
+ /* Unregister ourselves from the DnD manager. */
+ GuestDnDInst()->unregisterTarget(this);
+}
+
+/**
+ * Main function for sending DnD host data to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to wait for getting the data sent.
+ */
+int GuestDnDTarget::i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /* Don't allow receiving the actual data until our current transfer is complete. */
+ if (m_fIsPending)
+ return setError(E_FAIL, tr("Current drop operation to guest still in progress"));
+
+ /* Clear all remaining outgoing messages. */
+ m_DataBase.lstMsgOut.clear();
+
+ /**
+ * Do we need to build up a file tree?
+ * Note: The decision whether we need to build up a file tree and sending
+ * actual file data only depends on the actual formats offered by this target.
+ * If the guest does not want a transfer list ("text/uri-list") but text ("TEXT" and
+ * friends) instead, still send the data over to the guest -- the file as such still
+ * is needed on the guest in this case, as the guest then just wants a simple path
+ * instead of a transfer list (pointing to a file on the guest itself).
+ *
+ ** @todo Support more than one format; add a format<->function handler concept. Later. */
+ int vrc;
+ const bool fHasURIList = std::find(m_lstFmtOffered.begin(),
+ m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
+ if (fHasURIList)
+ {
+ vrc = i_sendTransferData(pCtx, msTimeout);
+ }
+ else
+ {
+ vrc = i_sendRawData(pCtx, msTimeout);
+ }
+
+ GuestDnDState *pState = GuestDnDInst()->getState();
+ AssertPtrReturn(pState, E_POINTER);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pState->set(VBOXDNDSTATE_DROP_ENDED);
+ }
+ else
+ {
+ if (vrc == VERR_CANCELLED)
+ {
+ LogRel(("DnD: Sending data to guest cancelled by the user\n"));
+ pState->set(VBOXDNDSTATE_CANCELLED);
+ }
+ else
+ {
+ LogRel(("DnD: Sending data to guest failed with %Rrc\n", vrc));
+ pState->set(VBOXDNDSTATE_ERROR);
+ }
+
+ /* Make sure to fire a cancel request to the guest side in any case to prevent any guest side hangs. */
+ sendCancel();
+ }
+
+ /* Reset state. */
+ i_reset();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the common meta data body to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ */
+int GuestDnDTarget::i_sendMetaDataBody(GuestDnDSendCtx *pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ uint8_t *pvData = (uint8_t *)pCtx->Meta.pvData;
+ size_t cbData = pCtx->Meta.cbData;
+
+ int vrc = VINF_SUCCESS;
+
+ const size_t cbFmt = pCtx->Meta.strFmt.length() + 1; /* Include terminator. */
+ const char *pcszFmt = pCtx->Meta.strFmt.c_str();
+
+ LogFlowFunc(("uProtoVer=%RU32, szFmt=%s, cbFmt=%RU32, cbData=%zu\n", m_pState->m_uProtocolVersion, pcszFmt, cbFmt, cbData));
+
+ LogRel2(("DnD: Sending meta data to guest as '%s' (%zu bytes)\n", pcszFmt, cbData));
+
+#ifdef DEBUG
+ RTCList<RTCString> lstFilesURI = RTCString((char *)pvData, cbData).split(DND_PATH_SEPARATOR_STR);
+ LogFlowFunc(("lstFilesURI=%zu\n", lstFilesURI.size()));
+ for (size_t i = 0; i < lstFilesURI.size(); i++)
+ LogFlowFunc(("\t%s\n", lstFilesURI.at(i).c_str()));
+#endif
+
+ uint8_t *pvChunk = pvData;
+ size_t cbChunk = RT_MIN(mData.mcbBlockSize, cbData);
+ while (cbData)
+ {
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_SND_DATA);
+
+ if (m_pState->m_uProtocolVersion < 3)
+ {
+ Msg.appendUInt32(pCtx->uScreenID); /* uScreenId */
+ Msg.appendPointer(unconst(pcszFmt), (uint32_t)cbFmt); /* pvFormat */
+ Msg.appendUInt32((uint32_t)cbFmt); /* cbFormat */
+ Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */
+ /* Fill in the current data block size to send.
+ * Note: Only supports uint32_t. */
+ Msg.appendUInt32((uint32_t)cbChunk); /* cbData */
+ }
+ else
+ {
+ Msg.appendUInt32(0); /** @todo ContextID not used yet. */
+ Msg.appendPointer(pvChunk, (uint32_t)cbChunk); /* pvData */
+ Msg.appendUInt32((uint32_t)cbChunk); /* cbData */
+ Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
+ Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */
+ }
+
+ vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+ if (RT_FAILURE(vrc))
+ break;
+
+ pvChunk += cbChunk;
+ AssertBreakStmt(cbData >= cbChunk, VERR_BUFFER_UNDERFLOW);
+ cbData -= cbChunk;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = updateProgress(pCtx, pCtx->pState, (uint32_t)pCtx->Meta.cbData);
+ AssertRC(vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the common meta data header to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ */
+int GuestDnDTarget::i_sendMetaDataHeader(GuestDnDSendCtx *pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ if (m_pState->m_uProtocolVersion < 3) /* Protocol < v3 did not support this, skip. */
+ return VINF_SUCCESS;
+
+ GuestDnDMsg Msg;
+ Msg.setType(HOST_DND_FN_HG_SND_DATA_HDR);
+
+ LogRel2(("DnD: Sending meta data header to guest (%RU64 bytes total data, %RU32 bytes meta data, %RU64 objects)\n",
+ pCtx->getTotalAnnounced(), pCtx->Meta.cbData, pCtx->Transfer.cObjToProcess));
+
+ Msg.appendUInt32(0); /** @todo uContext; not used yet. */
+ Msg.appendUInt32(0); /** @todo uFlags; not used yet. */
+ Msg.appendUInt32(pCtx->uScreenID); /* uScreen */
+ Msg.appendUInt64(pCtx->getTotalAnnounced()); /* cbTotal */
+ Msg.appendUInt32((uint32_t)pCtx->Meta.cbData); /* cbMeta*/
+ Msg.appendPointer(unconst(pCtx->Meta.strFmt.c_str()), (uint32_t)pCtx->Meta.strFmt.length() + 1); /* pvMetaFmt */
+ Msg.appendUInt32((uint32_t)pCtx->Meta.strFmt.length() + 1); /* cbMetaFmt */
+ Msg.appendUInt64(pCtx->Transfer.cObjToProcess); /* cObjects */
+ Msg.appendUInt32(0); /** @todo enmCompression; not used yet. */
+ Msg.appendUInt32(0); /** @todo enmChecksumType; not used yet. */
+ Msg.appendPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
+ Msg.appendUInt32(0); /** @todo cbChecksum; not used yet. */
+
+ int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends a directory entry to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a directory.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+ AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
+ const size_t cchPath = RTStrNLen(pcszDstPath, RTPATH_MAX); /* Note: Maximum is RTPATH_MAX on guest side. */
+ AssertReturn(cchPath, VERR_INVALID_PARAMETER);
+
+ LogRel2(("DnD: Transferring host directory '%s' to guest\n", DnDTransferObjectGetSourcePath(pObj)));
+
+ pMsg->setType(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 3)
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ pMsg->appendString(pcszDstPath); /* path */
+ pMsg->appendUInt32((uint32_t)(cchPath + 1)); /* path length, including terminator. */
+ pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* mode */
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends a file to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a file.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendFile(GuestDnDSendCtx *pCtx,
+ PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
+ AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER);
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+ AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
+
+ int vrc = VINF_SUCCESS;
+
+ if (!DnDTransferObjectIsOpen(pObj))
+ {
+ LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", pcszSrcPath));
+
+ vrc = DnDTransferObjectOpen(pObj, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fMode */,
+ DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Opening host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc));
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ bool fSendData = false;
+ if (RT_SUCCESS(vrc)) /** @todo r=aeichner Could save an identation level here as there is a error check above already... */
+ {
+ if (m_pState->m_uProtocolVersion >= 2)
+ {
+ if (!(pCtx->Transfer.fObjState & DND_OBJ_STATE_HAS_HDR))
+ {
+ const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
+ const size_t cbSize = DnDTransferObjectGetSize(pObj);
+ const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
+
+ /*
+ * Since protocol v2 the file header and the actual file contents are
+ * separate messages, so send the file header first.
+ * The just registered callback will be called by the guest afterwards.
+ */
+ pMsg->setType(HOST_DND_FN_HG_SND_FILE_HDR);
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ pMsg->appendString(pcszDstPath); /* pvName */
+ pMsg->appendUInt32((uint32_t)(cchDstPath + 1)); /* cbName */
+ pMsg->appendUInt32(0); /* uFlags */
+ pMsg->appendUInt32(fMode); /* fMode */
+ pMsg->appendUInt64(cbSize); /* uSize */
+
+ LogRel2(("DnD: Transferring host file '%s' to guest (as '%s', %zu bytes, mode %#x)\n",
+ pcszSrcPath, pcszDstPath, cbSize, fMode));
+
+ /** @todo Set progress object title to current file being transferred? */
+
+ /* Update object state to reflect that we have sent the file header. */
+ pCtx->Transfer.fObjState |= DND_OBJ_STATE_HAS_HDR;
+ }
+ else
+ {
+ /* File header was sent, so only send the actual file data. */
+ fSendData = true;
+ }
+ }
+ else /* Protocol v1. */
+ {
+ /* Always send the file data, every time. */
+ fSendData = true;
+ }
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && fSendData)
+ {
+ vrc = i_sendFileData(pCtx, pObj, pMsg);
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("DnD: Sending host file '%s' to guest failed, vrc=%Rrc\n", pcszSrcPath, vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Helper function to send actual file data to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param pObj Transfer object to send. Must be a file.
+ * @param pMsg Where to store the message to send.
+ */
+int GuestDnDTarget::i_sendFileData(GuestDnDSendCtx *pCtx,
+ PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pCtx->pState, VERR_WRONG_ORDER);
+
+ /** @todo Don't allow concurrent reads per context! */
+
+ /* Set the message type. */
+ pMsg->setType(HOST_DND_FN_HG_SND_FILE_DATA);
+
+ const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
+ const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
+
+ /* Protocol version 1 sends the file path *every* time with a new file chunk.
+ * In protocol version 2 we only do this once with HOST_DND_FN_HG_SND_FILE_HDR. */
+ if (m_pState->m_uProtocolVersion <= 1)
+ {
+ const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
+
+ pMsg->appendString(pcszDstPath); /* pvName */
+ pMsg->appendUInt32((uint32_t)cchDstPath + 1); /* cbName */
+ }
+ else if (m_pState->m_uProtocolVersion >= 2)
+ {
+ pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
+ }
+
+ void *pvBuf = pCtx->Transfer.pvScratchBuf;
+ AssertPtr(pvBuf);
+ size_t cbBuf = pCtx->Transfer.cbScratchBuf;
+ Assert(cbBuf);
+
+ uint32_t cbRead;
+
+ int vrc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead));
+
+ if (m_pState->m_uProtocolVersion <= 1)
+ {
+ pMsg->appendPointer(pvBuf, cbRead); /* pvData */
+ pMsg->appendUInt32(cbRead); /* cbData */
+ pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */
+ }
+ else /* Protocol v2 and up. */
+ {
+ pMsg->appendPointer(pvBuf, cbRead); /* pvData */
+ pMsg->appendUInt32(cbRead); /* cbData */
+
+ if (m_pState->m_uProtocolVersion >= 3)
+ {
+ /** @todo Calculate checksum. */
+ pMsg->appendPointer(NULL, 0); /* pvChecksum */
+ pMsg->appendUInt32(0); /* cbChecksum */
+ }
+ }
+
+ int vrc2 = updateProgress(pCtx, pCtx->pState, (uint32_t)cbRead);
+ AssertRC(vrc2);
+
+ /* DnDTransferObjectRead() will return VINF_EOF if reading is complete. */
+ if (vrc == VINF_EOF)
+ vrc = VINF_SUCCESS;
+
+ if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */
+ LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath));
+ }
+ else
+ LogRel(("DnD: Reading from host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Static HGCM service callback which handles sending transfer data to the guest.
+ *
+ * @returns VBox status code. Will get sent back to the host service.
+ * @param uMsg HGCM message ID (function number).
+ * @param pvParms Pointer to additional message data. Optional and can be NULL.
+ * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
+ * @param pvUser User-supplied pointer on callback registration.
+ */
+/* static */
+DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
+{
+ GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser;
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ GuestDnDTarget *pThis = pCtx->pTarget;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /* At the moment we only have one transfer list per transfer. */
+ PDNDTRANSFERLIST pList = &pCtx->Transfer.List;
+
+ LogFlowFunc(("pThis=%p, pList=%p, uMsg=%RU32\n", pThis, pList, uMsg));
+
+ int vrc = VINF_SUCCESS;
+ int vrcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_DND_GUEST_ERROR. */
+ bool fNotify = false;
+
+ switch (uMsg)
+ {
+ case GUEST_DND_FN_CONNECT:
+ /* Nothing to do here (yet). */
+ break;
+
+ case GUEST_DND_FN_DISCONNECT:
+ vrc = VERR_CANCELLED;
+ break;
+
+ case GUEST_DND_FN_GET_NEXT_HOST_MSG:
+ {
+ PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ try
+ {
+ GuestDnDMsg *pMsg = new GuestDnDMsg();
+
+ vrc = pThis->i_sendTransferListObject(pCtx, pList, pMsg);
+ if (vrc == VINF_EOF) /* Transfer complete? */
+ {
+ LogFlowFunc(("Last transfer item processed, bailing out\n"));
+ }
+ else if (RT_SUCCESS(vrc))
+ {
+ vrc = pThis->msgQueueAdd(pMsg);
+ if (RT_SUCCESS(vrc)) /* Return message type & required parameter count to the guest. */
+ {
+ LogFlowFunc(("GUEST_DND_FN_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
+ pCBData->uMsg = pMsg->getType();
+ pCBData->cParms = pMsg->getCount();
+ }
+ }
+
+ if ( RT_FAILURE(vrc)
+ || vrc == VINF_EOF) /* Transfer complete? */
+ {
+ delete pMsg;
+ pMsg = NULL;
+ }
+ }
+ catch(std::bad_alloc & /*e*/)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+ case GUEST_DND_FN_EVT_ERROR:
+ {
+ PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
+ AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
+
+ pCtx->pState->reset();
+
+ if (RT_SUCCESS(pCBData->rc))
+ {
+ AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
+ pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
+ }
+
+ vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
+ GuestDnDTarget::i_guestErrorToString(pCBData->rc));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = VERR_DND_GUEST_ERROR;
+ vrcGuest = pCBData->rc;
+ }
+ break;
+ }
+ case HOST_DND_FN_HG_SND_DIR:
+ case HOST_DND_FN_HG_SND_FILE_HDR:
+ case HOST_DND_FN_HG_SND_FILE_DATA:
+ {
+ PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
+ = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
+ AssertPtr(pCBData);
+ AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
+
+ GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
+ if (pMsg)
+ {
+ /*
+ * Sanity checks.
+ */
+ if ( pCBData->uMsg != uMsg
+ || pCBData->paParms == NULL
+ || pCBData->cParms != pMsg->getCount())
+ {
+ LogFlowFunc(("Current message does not match:\n"));
+ LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
+ pCBData->uMsg, pCBData->cParms, pCBData->paParms));
+ LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
+
+ /* Start over. */
+ pThis->msgQueueClear();
+
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
+ vrc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
+ false /* fDeepCopy */);
+ if (RT_SUCCESS(vrc))
+ {
+ pCBData->cParms = pMsg->getCount();
+ pThis->msgQueueRemoveNext();
+ }
+ else
+ LogFlowFunc(("Copying parameters failed with vrc=%Rrc\n", vrc));
+ }
+ }
+ else
+ vrc = VERR_NO_DATA;
+
+ LogFlowFunc(("Processing next message ended with vrc=%Rrc\n", vrc));
+ break;
+ }
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ int vrcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
+
+ /*
+ * Resolve errors.
+ */
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ break;
+
+ case VINF_EOF:
+ {
+ LogRel2(("DnD: Transfer to guest complete\n"));
+
+ /* Complete operation on host side. */
+ fNotify = true;
+
+ /* The guest expects VERR_NO_DATA if the transfer is complete. */
+ vrcToGuest = VERR_NO_DATA;
+ break;
+ }
+
+ case VERR_DND_GUEST_ERROR:
+ {
+ LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", vrcGuest));
+ break;
+ }
+
+ case VERR_CANCELLED:
+ {
+ LogRel2(("DnD: Transfer to guest canceled\n"));
+ vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
+ break;
+ }
+
+ default:
+ {
+ LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", vrc));
+ vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ /* Unregister this callback. */
+ AssertPtr(pCtx->pState);
+ int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
+ AssertRC(vrc2);
+
+ /* Let the waiter(s) know. */
+ fNotify = true;
+ }
+
+ LogFlowFunc(("fNotify=%RTbool, vrc=%Rrc, vrcToGuest=%Rrc\n", fNotify, vrc, vrcToGuest));
+
+ if (fNotify)
+ {
+ int vrc2 = pCtx->EventCallback.Notify(vrc); /** @todo Also pass guest error back? */
+ AssertRC(vrc2);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrcToGuest; /* Tell the guest. */
+}
+
+/**
+ * Main function for sending the actual transfer data (i.e. files + directories) to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to use for getting the data sent.
+ */
+int GuestDnDTarget::i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtr(pCtx->pState);
+
+#define REGISTER_CALLBACK(x) \
+ do { \
+ vrc = pCtx->pState->setCallback(x, i_sendTransferDataCallback, pCtx); \
+ if (RT_FAILURE(vrc)) \
+ return vrc; \
+ } while (0)
+
+#define UNREGISTER_CALLBACK(x) \
+ do { \
+ int vrc2 = pCtx->pState->setCallback(x, NULL); \
+ AssertRC(vrc2); \
+ } while (0)
+
+ int vrc = pCtx->Transfer.init(mData.mcbBlockSize);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = pCtx->EventCallback.Reset();
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Register callbacks.
+ */
+ /* Guest callbacks. */
+ REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ REGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
+ REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ /* Host callbacks. */
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
+ REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
+
+ do
+ {
+ /*
+ * Extract transfer list from current meta data.
+ */
+ vrc = DnDTransferListAppendPathsFromBuffer(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR,
+ DNDTRANSFERLIST_FLAGS_RECURSIVE);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /*
+ * Update internal state to reflect everything we need to work with it.
+ */
+ pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List);
+ /* cbExtra can be 0, if all files are of 0 bytes size. */
+ pCtx->Transfer.cObjToProcess = DnDTransferListObjCount(&pCtx->Transfer.List);
+ AssertBreakStmt(pCtx->Transfer.cObjToProcess, vrc = VERR_INVALID_PARAMETER);
+
+ /* Update the meta data to have the current root transfer entries in the right shape. */
+ if (DnDMIMEHasFileURLs(pCtx->Meta.strFmt.c_str(), RTSTR_MAX))
+ {
+ /* Save original format we're still going to use after updating the actual meta data. */
+ Utf8Str strFmt = pCtx->Meta.strFmt;
+
+ /* Reset stale data. */
+ pCtx->Meta.reset();
+
+ void *pvData;
+ size_t cbData;
+#ifdef DEBUG
+ vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, "" /* pcszPathBase */,
+ "\n" /* pcszSeparator */, (char **)&pvData, &cbData);
+ AssertRCReturn(vrc, vrc);
+ LogFlowFunc(("URI data:\n%s", (char *)pvData));
+ RTMemFree(pvData);
+ cbData = 0;
+#endif
+ vrc = DnDTransferListGetRoots(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
+ (char **)&pvData, &cbData);
+ AssertRCReturn(vrc, vrc);
+
+ /* pCtx->Meta now owns the allocated data. */
+ pCtx->Meta.strFmt = strFmt;
+ pCtx->Meta.pvData = pvData;
+ pCtx->Meta.cbData = cbData;
+ pCtx->Meta.cbAllocated = cbData;
+ pCtx->Meta.cbAnnounced = cbData;
+ }
+
+ /*
+ * The first message always is the data header. The meta data itself then follows
+ * and *only* contains the root elements of a transfer list.
+ *
+ * After the meta data we generate the messages required to send the
+ * file/directory data itself.
+ *
+ * Note: Protocol < v3 use the first data message to tell what's being sent.
+ */
+
+ /*
+ * Send the data header first.
+ */
+ if (m_pState->m_uProtocolVersion >= 3)
+ vrc = i_sendMetaDataHeader(pCtx);
+
+ /*
+ * Send the (meta) data body.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = i_sendMetaDataBody(pCtx);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
+ if (RT_SUCCESS(vrc))
+ pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
+ }
+
+ } while (0);
+
+ /*
+ * Unregister callbacks.
+ */
+ /* Guest callbacks. */
+ UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
+ UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
+ /* Host callbacks. */
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
+ if (m_pState->m_uProtocolVersion >= 2)
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
+ UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
+
+#undef REGISTER_CALLBACK
+#undef UNREGISTER_CALLBACK
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
+ {
+ /*
+ * Now that we've cleaned up tell the guest side to cancel.
+ * This does not imply we're waiting for the guest to react, as the
+ * host side never must depend on anything from the guest.
+ */
+ int vrc2 = sendCancel();
+ AssertRC(vrc2);
+
+ LogRel2(("DnD: Sending transfer data to guest cancelled by user\n"));
+
+ vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
+ AssertRC(vrc2);
+
+ /* Cancelling is not an error, just set success here. */
+ vrc = VINF_SUCCESS;
+ }
+ else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
+ {
+ LogRel(("DnD: Sending transfer data to guest failed with vrc=%Rrc\n", vrc));
+ int vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, vrc,
+ GuestDnDTarget::i_hostErrorToString(vrc));
+ AssertRC(vrc2);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends the next object of a transfer list to the guest.
+ *
+ * @returns VBox status code. VINF_EOF if the transfer list is complete.
+ * @param pCtx Send context to use.
+ * @param pList Transfer list to use.
+ * @param pMsg Message to store send data into.
+ */
+int GuestDnDTarget::i_sendTransferListObject(GuestDnDSendCtx *pCtx, PDNDTRANSFERLIST pList, GuestDnDMsg *pMsg)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
+
+ int vrc = updateProgress(pCtx, pCtx->pState);
+ AssertRCReturn(vrc, vrc);
+
+ PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pList);
+ if (!pObj) /* Transfer complete? */
+ return VINF_EOF;
+
+ switch (DnDTransferObjectGetType(pObj))
+ {
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ vrc = i_sendDirectory(pCtx, pObj, pMsg);
+ break;
+
+ case DNDTRANSFEROBJTYPE_FILE:
+ vrc = i_sendFile(pCtx, pObj, pMsg);
+ break;
+
+ default:
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+ break;
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && DnDTransferObjectIsComplete(pObj))
+ {
+ DnDTransferListObjRemove(pList, pObj);
+ pObj = NULL;
+
+ AssertReturn(pCtx->Transfer.cObjProcessed + 1 <= pCtx->Transfer.cObjToProcess, VERR_WRONG_ORDER);
+ pCtx->Transfer.cObjProcessed++;
+
+ pCtx->Transfer.fObjState = DND_OBJ_STATE_NONE;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for sending raw data (e.g. text, RTF, ...) to the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Send context to use.
+ * @param msTimeout Timeout (in ms) to use for getting the data sent.
+ */
+int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ NOREF(msTimeout);
+
+ /** @todo At the moment we only allow sending up to 64K raw data.
+ * For protocol v1+v2: Fix this by using HOST_DND_FN_HG_SND_MORE_DATA.
+ * For protocol v3 : Send another HOST_DND_FN_HG_SND_DATA message. */
+ if (!pCtx->Meta.cbData)
+ return VINF_SUCCESS;
+
+ int vrc = i_sendMetaDataHeader(pCtx);
+ if (RT_SUCCESS(vrc))
+ vrc = i_sendMetaDataBody(pCtx);
+
+ int vrc2;
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("DnD: Sending raw data to guest failed with vrc=%Rrc\n", vrc));
+ vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_ERROR, vrc,
+ GuestDnDTarget::i_hostErrorToString(vrc));
+ }
+ else
+ vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, vrc);
+ AssertRC(vrc2);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Cancels sending DnD data.
+ *
+ * @returns VBox status code.
+ * @param aVeto Whether cancelling was vetoed or not.
+ * Not implemented yet.
+ */
+HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
+{
+#if !defined(VBOX_WITH_DRAG_AND_DROP)
+ ReturnComNotImplemented();
+#else /* VBOX_WITH_DRAG_AND_DROP */
+
+ LogRel2(("DnD: Sending cancelling request to the guest ...\n"));
+
+ int vrc = GuestDnDBase::sendCancel();
+
+ if (aVeto)
+ *aVeto = FALSE; /** @todo Implement vetoing. */
+
+ HRESULT hrc = RT_SUCCESS(vrc) ? S_OK : VBOX_E_DND_ERROR;
+
+ LogFlowFunc(("hrc=%Rhrc\n", hrc));
+ return hrc;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
diff --git a/src/VBox/Main/src-client/GuestFileImpl.cpp b/src/VBox/Main/src-client/GuestFileImpl.cpp
new file mode 100644
index 00000000..4f081dac
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestFileImpl.cpp
@@ -0,0 +1,1890 @@
+/* $Id: GuestFileImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest file handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTFILE
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestFileImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/file.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/listeners.h>
+#include <VBox/AssertGuest.h>
+
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestFileListener
+{
+public:
+
+ GuestFileListener(void)
+ {
+ }
+
+ virtual ~GuestFileListener()
+ {
+ }
+
+ HRESULT init(GuestFile *pFile)
+ {
+ AssertPtrReturn(pFile, E_POINTER);
+ mFile = pFile;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mFile = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestFileStateChanged:
+ case VBoxEventType_OnGuestFileOffsetChanged:
+ case VBoxEventType_OnGuestFileRead:
+ case VBoxEventType_OnGuestFileWrite:
+ {
+ AssertPtrReturn(mFile, E_POINTER);
+ int vrc2 = mFile->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef DEBUG_andy
+ LogFlowFunc(("Signalling events of type=%RU32, file=%p resulted in vrc=%Rrc\n",
+ aType, mFile, vrc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ /** Weak pointer to the guest file object to listen for. */
+ GuestFile *mFile;
+};
+typedef ListenerImpl<GuestFileListener, GuestFile*> GuestFileListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestFileListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestFile)
+
+HRESULT GuestFile::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestFile::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes a file object but does *not* open the file on the guest
+ * yet. This is done in the dedidcated openFile call.
+ *
+ * @return IPRT status code.
+ * @param pConsole Pointer to console object.
+ * @param pSession Pointer to session object.
+ * @param aObjectID The object's ID.
+ * @param openInfo File opening information.
+ */
+int GuestFile::init(Console *pConsole, GuestSession *pSession,
+ ULONG aObjectID, const GuestFileOpenInfo &openInfo)
+{
+ LogFlowThisFunc(("pConsole=%p, pSession=%p, aObjectID=%RU32, strPath=%s\n",
+ pConsole, pSession, aObjectID, openInfo.mFilename.c_str()));
+
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ int vrc = bindToSession(pConsole, pSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ mSession = pSession;
+
+ mData.mOpenInfo = openInfo;
+ mData.mInitialSize = 0;
+ mData.mStatus = FileStatus_Undefined;
+ mData.mLastError = VINF_SUCCESS;
+ mData.mOffCurrent = 0;
+
+ unconst(mEventSource).createObject();
+ HRESULT hr = mEventSource->init();
+ if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ GuestFileListener *pListener = new GuestFileListener();
+ ComObjPtr<GuestFileListenerImpl> thisListener;
+ HRESULT hr = thisListener.createObject();
+ if (SUCCEEDED(hr))
+ hr = thisListener->init(pListener, this);
+
+ if (SUCCEEDED(hr))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+ hr = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hr))
+ {
+ vrc = baseInit();
+ if (RT_SUCCESS(vrc))
+ {
+ mLocalListener = thisListener;
+ }
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ catch(std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+ }
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestFile::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncEnter();
+
+ baseUninit();
+ LogFlowThisFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestFile::getCreationMode(ULONG *aCreationMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCreationMode = mData.mOpenInfo.mCreationMode;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getOpenAction(FileOpenAction_T *aOpenAction)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOpenAction = mData.mOpenInfo.mOpenAction;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* No need to lock - lifetime constant. */
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getFilename(com::Utf8Str &aFilename)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFilename = mData.mOpenInfo.mFilename;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getId(ULONG *aId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aId = mObjectID;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getInitialSize(LONG64 *aInitialSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aInitialSize = mData.mInitialSize;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getOffset(LONG64 *aOffset)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * This is updated by GuestFile::i_onFileNotify() when read, write and seek
+ * confirmation messages are recevied.
+ *
+ * Note! This will not be accurate with older (< 5.2.32, 6.0.0 - 6.0.9)
+ * Guest Additions when using writeAt, readAt or writing to a file
+ * opened in append mode.
+ */
+ *aOffset = mData.mOffCurrent;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getAccessMode(FileAccessMode_T *aAccessMode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAccessMode = mData.mOpenInfo.mAccessMode;
+
+ return S_OK;
+}
+
+HRESULT GuestFile::getStatus(FileStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatus = mData.mStatus;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side file callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestFile::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strName=%s, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
+ mData.mOpenInfo.mFilename.c_str(), pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
+ break;
+
+ case GUEST_MSG_FILE_NOTIFY:
+ vrc = i_onFileNotify(pCbCtx, pSvcCb);
+ break;
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+#ifdef DEBUG
+ LogFlowFuncLeaveRC(vrc);
+#endif
+ return vrc;
+}
+
+/**
+ * Closes the file on the guest side and unregisters it.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestFile::i_closeFile(int *prcGuest)
+{
+ LogFlowThisFunc(("strFile=%s\n", mData.mOpenInfo.mFilename.c_str()));
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* Guest file ID */);
+
+ vrc = sendMessage(HOST_MSG_FILE_CLOSE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, 30 * 1000 /* Timeout in ms */,
+ NULL /* FileStatus */, prcGuest);
+ unregisterWaitEvent(pEvent);
+
+ /* Unregister the file object from the guest session. */
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_fileUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a given guest file error to a string.
+ *
+ * @returns Error string.
+ * @param rcGuest Guest file error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestFile::i_guestErrorToString(int rcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (rcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+ CASE_MSG(VERR_ACCESS_DENIED , tr("Access to guest file \"%s\" denied"), pcszWhat);
+ CASE_MSG(VERR_ALREADY_EXISTS , tr("Guest file \"%s\" already exists"), pcszWhat);
+ CASE_MSG(VERR_FILE_NOT_FOUND , tr("Guest file \"%s\" not found"), pcszWhat);
+ CASE_MSG(VERR_NET_HOST_NOT_FOUND, tr("Host name \"%s\", not found"), pcszWhat);
+ CASE_MSG(VERR_SHARING_VIOLATION , tr("Sharing violation for guest file \"%s\""), pcszWhat);
+ default:
+ strErr.printf(tr("Error %Rrc for guest file \"%s\" occurred\n"), rcGuest, pcszWhat);
+ break;
+#undef CASE_MSG
+ }
+
+ return strErr;
+}
+
+/**
+ * Called when the guest side notifies the host of a file event.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestFile::i_onFileNotify(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ LogFlowThisFuncEnter();
+
+ if (pSvcCbData->mParms < 3)
+ return VERR_INVALID_PARAMETER;
+
+ int idx = 1; /* Current parameter index. */
+ CALLBACKDATA_FILE_NOTIFY dataCb;
+ RT_ZERO(dataCb);
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.uType);
+ HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.rc);
+
+ int vrcGuest = (int)dataCb.rc; /* uint32_t vs. int. */
+
+ LogFlowThisFunc(("uType=%RU32, vrcGuest=%Rrc\n", dataCb.uType, vrcGuest));
+
+ if (RT_FAILURE(vrcGuest))
+ {
+ int vrc2 = i_setFileStatus(FileStatus_Error, vrcGuest);
+ AssertRC(vrc2);
+
+ /* Ignore return code, as the event to signal might not be there (anymore). */
+ signalWaitEventInternal(pCbCtx, vrcGuest, NULL /* pPayload */);
+ return VINF_SUCCESS; /* Report to the guest. */
+ }
+
+ AssertMsg(mObjectID == VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID),
+ ("File ID %RU32 does not match object ID %RU32\n", mObjectID,
+ VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCbCtx->uContextID)));
+
+ int vrc = VERR_NOT_SUPPORTED; /* Play safe by default. */
+
+ switch (dataCb.uType)
+ {
+ case GUEST_FILE_NOTIFYTYPE_ERROR:
+ {
+ vrc = i_setFileStatus(FileStatus_Error, vrcGuest);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_OPEN:
+ {
+ if (pSvcCbData->mParms == 4)
+ {
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[idx++], &dataCb.u.open.uHandle);
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Set the process status. */
+ vrc = i_setFileStatus(FileStatus_Open, vrcGuest);
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_CLOSE:
+ {
+ vrc = i_setFileStatus(FileStatus_Closed, vrcGuest);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_READ:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[idx++], &dataCb.u.read.pvData, &dataCb.u.read.cbData);
+ if (RT_FAILURE(vrc))
+ break;
+
+ const uint32_t cbRead = dataCb.u.read.cbData;
+ Log3ThisFunc(("cbRead=%RU32\n", cbRead));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent += cbRead; /* Bogus for readAt, which is why we've got GUEST_FILE_NOTIFYTYPE_READ_OFFSET. */
+ alock.release();
+
+ try
+ {
+ com::SafeArray<BYTE> data((size_t)cbRead);
+ AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY);
+ data.initFrom((BYTE *)dataCb.u.read.pvData, cbRead);
+ ::FireGuestFileReadEvent(mEventSource, mSession, this, mData.mOffCurrent, cbRead, ComSafeArrayAsInParam(data));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_READ_OFFSET:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_PTR,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx + 1].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ BYTE const * const pbData = (BYTE const *)pSvcCbData->mpaParms[idx].u.pointer.addr;
+ uint32_t const cbRead = pSvcCbData->mpaParms[idx].u.pointer.size;
+ int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64;
+ Log3ThisFunc(("cbRead=%RU32 offNew=%RI64 (%#RX64)\n", cbRead, offNew, offNew));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (offNew < 0) /* non-seekable */
+ offNew = mData.mOffCurrent + cbRead;
+ mData.mOffCurrent = offNew;
+ alock.release();
+
+ try
+ {
+ com::SafeArray<BYTE> data((size_t)cbRead);
+ AssertBreakStmt(data.size() == cbRead, vrc = VERR_NO_MEMORY);
+ data.initFrom(pbData, cbRead);
+ ::FireGuestFileReadEvent(mEventSource, mSession, this, offNew, cbRead, ComSafeArrayAsInParam(data));
+ vrc = VINF_SUCCESS;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_WRITE:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+
+ uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32;
+
+ Log3ThisFunc(("cbWritten=%RU32\n", cbWritten));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent += cbWritten; /* Bogus for writeAt and append mode, thus GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET. */
+ alock.release();
+
+ ::FireGuestFileWriteEvent(mEventSource, mSession, this, mData.mOffCurrent, cbWritten);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 5, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_32BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx + 1].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ uint32_t const cbWritten = pSvcCbData->mpaParms[idx].u.uint32;
+ int64_t offNew = (int64_t)pSvcCbData->mpaParms[idx + 1].u.uint64;
+ Log3ThisFunc(("cbWritten=%RU32 offNew=%RI64 (%#RX64)\n", cbWritten, offNew, offNew));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (offNew < 0) /* non-seekable */
+ offNew = mData.mOffCurrent + cbWritten;
+ mData.mOffCurrent = offNew;
+ alock.release();
+
+ HRESULT hrc2 = ::FireGuestFileWriteEvent(mEventSource, mSession, this, offNew, cbWritten);
+ vrc = SUCCEEDED(hrc2) ? VINF_SUCCESS : Global::vboxStatusCodeFromCOM(hrc2);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_SEEK:
+ {
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+
+ vrc = HGCMSvcGetU64(&pSvcCbData->mpaParms[idx++], &dataCb.u.seek.uOffActual);
+ if (RT_FAILURE(vrc))
+ break;
+
+ Log3ThisFunc(("uOffActual=%RU64\n", dataCb.u.seek.uOffActual));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mOffCurrent = dataCb.u.seek.uOffActual;
+ alock.release();
+
+ ::FireGuestFileOffsetChangedEvent(mEventSource, mSession, this, dataCb.u.seek.uOffActual, 0 /* Processed */);
+ break;
+ }
+
+ case GUEST_FILE_NOTIFYTYPE_TELL:
+ /* We don't issue any HOST_MSG_FILE_TELL, so we shouldn't get these notifications! */
+ AssertFailed();
+ break;
+
+ case GUEST_FILE_NOTIFYTYPE_SET_SIZE:
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mParms == 4, ("mParms=%u\n", pSvcCbData->mParms),
+ vrc = VERR_WRONG_PARAMETER_COUNT);
+ ASSERT_GUEST_MSG_STMT_BREAK(pSvcCbData->mpaParms[idx].type == VBOX_HGCM_SVC_PARM_64BIT,
+ ("type=%u\n", pSvcCbData->mpaParms[idx].type),
+ vrc = VERR_WRONG_PARAMETER_TYPE);
+ dataCb.u.SetSize.cbSize = pSvcCbData->mpaParms[idx].u.uint64;
+ Log3ThisFunc(("cbSize=%RU64\n", dataCb.u.SetSize.cbSize));
+
+ ::FireGuestFileSizeChangedEvent(mEventSource, mSession, this, dataCb.u.SetSize.cbSize);
+ vrc = VINF_SUCCESS;
+ break;
+
+ default:
+ break;
+ }
+
+ try
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEventPayload payload(dataCb.uType, &dataCb, sizeof(dataCb));
+
+ /* Ignore return code, as the event to signal might not be there (anymore). */
+ signalWaitEventInternal(pCbCtx, vrcGuest, &payload);
+ }
+ else /* OOM situation, wrong HGCM parameters or smth. not expected. */
+ {
+ /* Ignore return code, as the event to signal might not be there (anymore). */
+ signalWaitEventInternalEx(pCbCtx, vrc, 0 /* guestRc */, NULL /* pPayload */);
+ }
+ }
+ catch (int vrcEx) /* Thrown by GuestWaitEventPayload constructor. */
+ {
+ /* Also try to signal the waiter, to let it know of the OOM situation.
+ * Ignore return code, as the event to signal might not be there (anymore). */
+ signalWaitEventInternalEx(pCbCtx, vrcEx, 0 /* guestRc */, NULL /* pPayload */);
+ vrc = vrcEx;
+ }
+
+ LogFlowThisFunc(("uType=%RU32, rcGuest=%Rrc, vrc=%Rrc\n", dataCb.uType, vrcGuest, vrc));
+ return vrc;
+}
+
+/**
+ * Called when the guest side of the file has been disconnected (closed, terminated, +++).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestFile::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ int vrc = i_setFileStatus(FileStatus_Down, VINF_SUCCESS);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestFile::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestFile::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ /* If the session now is in a terminated state, set the file status
+ * to "down", as there is not much else we can do now. */
+ if (GuestSession::i_isTerminated(enmSessionStatus))
+ vrc = i_setFileStatus(FileStatus_Down, 0 /* fileRc, ignored */);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Opens the file on the guest.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestFile::i_openFile(uint32_t uTimeoutMS, int *prcGuest)
+{
+ AssertReturn(mData.mOpenInfo.mFilename.isNotEmpty(), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("strFile=%s, enmAccessMode=%d, enmOpenAction=%d, uCreationMode=%o, mfOpenEx=%#x\n",
+ mData.mOpenInfo.mFilename.c_str(), mData.mOpenInfo.mAccessMode, mData.mOpenInfo.mOpenAction,
+ mData.mOpenInfo.mCreationMode, mData.mOpenInfo.mfOpenEx));
+
+ /* Validate and translate open action. */
+ const char *pszOpenAction = NULL;
+ switch (mData.mOpenInfo.mOpenAction)
+ {
+ case FileOpenAction_OpenExisting: pszOpenAction = "oe"; break;
+ case FileOpenAction_OpenOrCreate: pszOpenAction = "oc"; break;
+ case FileOpenAction_CreateNew: pszOpenAction = "ce"; break;
+ case FileOpenAction_CreateOrReplace: pszOpenAction = "ca"; break;
+ case FileOpenAction_OpenExistingTruncated: pszOpenAction = "ot"; break;
+ case FileOpenAction_AppendOrCreate:
+ pszOpenAction = "oa"; /** @todo get rid of this one and implement AppendOnly/AppendRead. */
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Validate and translate access mode. */
+ const char *pszAccessMode = NULL;
+ switch (mData.mOpenInfo.mAccessMode)
+ {
+ case FileAccessMode_ReadOnly: pszAccessMode = "r"; break;
+ case FileAccessMode_WriteOnly: pszAccessMode = "w"; break;
+ case FileAccessMode_ReadWrite: pszAccessMode = "r+"; break;
+ case FileAccessMode_AppendOnly: pszAccessMode = "a"; break;
+ case FileAccessMode_AppendRead: pszAccessMode = "a+"; break;
+ default: return VERR_INVALID_PARAMETER;
+ }
+
+ /* Validate and translate sharing mode. */
+ const char *pszSharingMode = NULL;
+ switch (mData.mOpenInfo.mSharingMode)
+ {
+ case FileSharingMode_All: pszSharingMode = ""; break;
+ case FileSharingMode_Read: RT_FALL_THRU();
+ case FileSharingMode_Write: RT_FALL_THRU();
+ case FileSharingMode_ReadWrite: RT_FALL_THRU();
+ case FileSharingMode_Delete: RT_FALL_THRU();
+ case FileSharingMode_ReadDelete: RT_FALL_THRU();
+ case FileSharingMode_WriteDelete: return VERR_NOT_IMPLEMENTED;
+ default: return VERR_INVALID_PARAMETER;
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mOpenInfo.mFilename.c_str(),
+ (ULONG)mData.mOpenInfo.mFilename.length() + 1);
+ HGCMSvcSetStr(&paParms[i++], pszAccessMode);
+ HGCMSvcSetStr(&paParms[i++], pszOpenAction);
+ HGCMSvcSetStr(&paParms[i++], pszSharingMode);
+ HGCMSvcSetU32(&paParms[i++], mData.mOpenInfo.mCreationMode);
+ HGCMSvcSetU64(&paParms[i++], 0 /*unused offset*/);
+ /** @todo Next protocol version: add flags, replace strings, remove initial offset. */
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_OPEN, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, uTimeoutMS, NULL /* FileStatus */, prcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Queries file system information from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param objData Where to store the file system object data on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestFile::i_queryInfo(GuestFsObjData &objData, int *prcGuest)
+{
+ AssertPtr(mSession);
+ return mSession->i_fsQueryInfo(mData.mOpenInfo.mFilename, FALSE /* fFollowSymlinks */, objData, prcGuest);
+}
+
+/**
+ * Reads data from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ */
+int GuestFile::i_readData(uint32_t uSize, uint32_t uTimeoutMS,
+ void* pvData, uint32_t cbData, uint32_t* pcbRead)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uSize, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_READ, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbRead = 0;
+ vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbRead=%RU32\n", cbRead));
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pEvent->GetGuestError();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Reads data from a specific position from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uOffset Offset (in bytes) to start reading from.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ */
+int GuestFile::i_readDataAt(uint64_t uOffset, uint32_t uSize, uint32_t uTimeoutMS,
+ void* pvData, size_t cbData, size_t* pcbRead)
+{
+ LogFlowThisFunc(("uOffset=%RU64, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uOffset, uSize, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileRead);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU64(&paParms[i++], uOffset /* Offset (in bytes) to start reading */);
+ HGCMSvcSetU32(&paParms[i++], uSize /* Size (in bytes) to read */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_READ_AT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbRead = 0;
+ vrc = i_waitForRead(pEvent, uTimeoutMS, pvData, cbData, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbRead=%RU32\n", cbRead));
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pEvent->GetGuestError();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Seeks a guest file to a specific position.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param iOffset Offset (in bytes) to seek.
+ * @param eSeekType Seek type to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puOffset Where to return the new current file position (in bytes) on success.
+ */
+int GuestFile::i_seekAt(int64_t iOffset, GUEST_FILE_SEEKTYPE eSeekType,
+ uint32_t uTimeoutMS, uint64_t *puOffset)
+{
+ LogFlowThisFunc(("iOffset=%RI64, uTimeoutMS=%RU32\n",
+ iOffset, uTimeoutMS));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileOffsetChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], eSeekType /* Seek method */);
+ /** @todo uint64_t vs. int64_t! */
+ HGCMSvcSetU64(&paParms[i++], (uint64_t)iOffset /* Offset (in bytes) to start reading */);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_SEEK, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t uOffset;
+ vrc = i_waitForOffsetChange(pEvent, uTimeoutMS, &uOffset);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("uOffset=%RU64\n", uOffset));
+
+ if (puOffset)
+ *puOffset = uOffset;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pEvent->GetGuestError();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets the current internal file object status.
+ *
+ * @returns VBox status code.
+ * @param fileStatus New file status to set.
+ * @param vrcFile New result code to set.
+ *
+ * @note Takes the write lock.
+ */
+int GuestFile::i_setFileStatus(FileStatus_T fileStatus, int vrcFile)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, vrcFile=%Rrc\n", mData.mStatus, fileStatus, vrcFile));
+
+#ifdef VBOX_STRICT
+ if (fileStatus == FileStatus_Error)
+ AssertMsg(RT_FAILURE(vrcFile), ("Guest vrc must be an error (%Rrc)\n", vrcFile));
+ else
+ AssertMsg(RT_SUCCESS(vrcFile), ("Guest vrc must not be an error (%Rrc)\n", vrcFile));
+#endif
+
+ if (mData.mStatus != fileStatus)
+ {
+ mData.mStatus = fileStatus;
+ mData.mLastError = vrcFile;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ ComAssertComRCRet(hrc, VERR_COM_UNEXPECTED);
+ if (RT_FAILURE(vrcFile))
+ {
+ hrc = errorInfo->initEx(VBOX_E_GSTCTL_GUEST_ERROR, vrcFile,
+ COM_IIDOF(IGuestFile), getComponentName(),
+ i_guestErrorToString(vrcFile, mData.mOpenInfo.mFilename.c_str()));
+ ComAssertComRCRet(hrc, VERR_COM_UNEXPECTED);
+ }
+ /* Note: On vrcFile success, errorInfo is set to S_OK and also sent via the event below. */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestFileStateChangedEvent(mEventSource, mSession, this, fileStatus, errorInfo);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for a guest file offset change.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puOffset Where to return the new offset (in bytes) on success.
+ * Optional and can be NULL.
+ */
+int GuestFile::i_waitForOffsetChange(GuestWaitEvent *pEvent,
+ uint32_t uTimeoutMS, uint64_t *puOffset)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileOffsetChanged)
+ {
+ if (puOffset)
+ {
+ ComPtr<IGuestFileOffsetChangedEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr = pFileEvent->COMGETTER(Offset)((LONG64*)puOffset);
+ ComAssertComRC(hr);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Waits for reading from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store read file data on success.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param pcbRead Where to return the actual bytes read on success.
+ * Optional and can be NULL.
+ */
+int GuestFile::i_waitForRead(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileRead)
+ {
+ vrc = VINF_SUCCESS;
+
+ ComPtr<IGuestFileReadEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ if (pvData)
+ {
+ com::SafeArray <BYTE> data;
+ HRESULT hrc1 = pFileEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
+ ComAssertComRC(hrc1);
+ const size_t cbRead = data.size();
+ if (cbRead)
+ {
+ if (cbRead <= cbData)
+ memcpy(pvData, data.raw(), cbRead);
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+ }
+ /* else: used to be VERR_NO_DATA, but that messes stuff up. */
+
+ if (pcbRead)
+ {
+ *pcbRead = (uint32_t)cbRead;
+ Assert(*pcbRead == cbRead);
+ }
+ }
+ else if (pcbRead)
+ {
+ *pcbRead = 0;
+ HRESULT hrc2 = pFileEvent->COMGETTER(Processed)((ULONG *)pcbRead);
+ ComAssertComRC(hrc2); NOREF(hrc2);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Waits for a guest file status change.
+ *
+ * @note Similar code in GuestProcess::i_waitForStatusChange() and
+ * GuestSession::i_waitForStatusChange().
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pFileStatus Where to return the file status on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestFile::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ FileStatus_T *pFileStatus, int *prcGuest)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pFileStatus is optional. */
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(evtType == VBoxEventType_OnGuestFileStateChanged);
+ ComPtr<IGuestFileStateChangedEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr;
+ if (pFileStatus)
+ {
+ hr = pFileEvent->COMGETTER(Status)(pFileStatus);
+ ComAssertComRC(hr);
+ }
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ hr = pFileEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hr);
+
+ LONG lGuestRc;
+ hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hr);
+
+ LogFlowThisFunc(("resultDetail=%RI32 (%Rrc)\n",
+ lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (prcGuest)
+ *prcGuest = (int)lGuestRc;
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */
+ /** @todo r=bird: Andy, you seem to have forgotten this scenario. Showed up occasionally when
+ * using the wrong password with a copyto command in a debug build on windows, error info
+ * contained "Unknown Status -858993460 (0xcccccccc)". As you know windows fills the stack frames
+ * with 0xcccccccc in debug builds to highlight use of uninitialized data, so that's what happened
+ * here. It's actually good you didn't initialize lGuest, as it would be heck to find otherwise.
+ *
+ * I'm still not very impressed with the error managment or the usuefullness of the documentation
+ * in this code, though the latter is getting better! */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc);
+
+ return vrc;
+}
+
+int GuestFile::i_waitForWrite(GuestWaitEvent *pEvent,
+ uint32_t uTimeoutMS, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestFileWrite)
+ {
+ if (pcbWritten)
+ {
+ ComPtr<IGuestFileWriteEvent> pFileEvent = pIEvent;
+ Assert(!pFileEvent.isNull());
+
+ HRESULT hr = pFileEvent->COMGETTER(Processed)((ULONG*)pcbWritten);
+ ComAssertComRC(hr);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes data to a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Data to write.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param pcbWritten Where to return to size (in bytes) written on success.
+ * Optional.
+ */
+int GuestFile::i_writeData(uint32_t uTimeoutMS, const void *pvData, uint32_t cbData,
+ uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */);
+ HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_WRITE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbWritten = 0;
+ vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten));
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pEvent->GetGuestError();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+
+/**
+ * Writes data to a specific position to a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uOffset Offset (in bytes) to start writing at.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Data to write.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param pcbWritten Where to return to size (in bytes) written on success.
+ * Optional.
+ */
+int GuestFile::i_writeDataAt(uint64_t uOffset, uint32_t uTimeoutMS,
+ const void *pvData, uint32_t cbData, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("uOffset=%RU64, uTimeoutMS=%RU32, pvData=%p, cbData=%zu\n",
+ uOffset, uTimeoutMS, pvData, cbData));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestFileWrite);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mObjectID /* File handle */);
+ HGCMSvcSetU64(&paParms[i++], uOffset /* Offset where to starting writing */);
+ HGCMSvcSetU32(&paParms[i++], cbData /* Size (in bytes) to write */);
+ HGCMSvcSetPv (&paParms[i++], unconst(pvData), cbData);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_WRITE_AT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cbWritten = 0;
+ vrc = i_waitForWrite(pEvent, uTimeoutMS, &cbWritten);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten));
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ else if (pEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pEvent->GetGuestError();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+// Wrapped IGuestFile methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestFile::close()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ /* Close file on guest. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_closeFile(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_File, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Closing guest file \"%s\" failed with %Rrc\n"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowThisFunc(("Returning S_OK / vrc=%Rrc\n", vrc));
+ return S_OK;
+}
+
+HRESULT GuestFile::queryInfo(ComPtr<IFsObjInfo> &aObjInfo)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ GuestFsObjData fsObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_queryInfo(fsObjData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> ptrFsObjInfo;
+ hrc = ptrFsObjInfo.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrFsObjInfo->init(fsObjData);
+ if (RT_SUCCESS(vrc))
+ hrc = ptrFsObjInfo.queryInterfaceTo(aObjInfo.asOutParam());
+ else
+ hrc = setErrorVrc(vrc,
+ tr("Initialization of guest file object for \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc,
+ tr("Querying guest file information for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::querySize(LONG64 *aSize)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ GuestFsObjData fsObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_queryInfo(fsObjData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aSize = fsObjData.mObjectSize;
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, mData.mOpenInfo.mFilename.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file size failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file size for \"%s\" failed: %Rrc"), mData.mOpenInfo.mFilename.c_str(), vrc);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read is zero"));
+
+ LogFlowThisFuncEnter();
+
+ /* Cap the read at 1MiB because that's all the guest will return anyway. */
+ if (aToRead > _1M)
+ aToRead = _1M;
+
+ HRESULT hrc = S_OK;
+
+ int vrc;
+ try
+ {
+ aData.resize(aToRead);
+
+ uint32_t cbRead;
+ vrc = i_readData(aToRead, aTimeoutMS,
+ &aData.front(), aToRead, &cbRead);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::readAt(LONG64 aOffset, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read for guest file \"%s\" is zero"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ /* Cap the read at 1MiB because that's all the guest will return anyway. */
+ if (aToRead > _1M)
+ aToRead = _1M;
+
+ HRESULT hrc = S_OK;
+
+ int vrc;
+ try
+ {
+ aData.resize(aToRead);
+
+ size_t cbRead;
+ vrc = i_readDataAt(aOffset, aToRead, aTimeoutMS,
+ &aData.front(), aToRead, &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from file \"%s\" (at offset %RU64) failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LONG64 *aNewOffset)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ HRESULT hrc = S_OK;
+
+ GUEST_FILE_SEEKTYPE eSeekType;
+ switch (aWhence)
+ {
+ case FileSeekOrigin_Begin:
+ eSeekType = GUEST_FILE_SEEKTYPE_BEGIN;
+ break;
+
+ case FileSeekOrigin_Current:
+ eSeekType = GUEST_FILE_SEEKTYPE_CURRENT;
+ break;
+
+ case FileSeekOrigin_End:
+ eSeekType = GUEST_FILE_SEEKTYPE_END;
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Invalid seek type for guest file \"%s\" specified"),
+ mData.mOpenInfo.mFilename.c_str());
+ }
+
+ LogFlowThisFuncEnter();
+
+ uint64_t uNewOffset;
+ int vrc = i_seekAt(aOffset, eSeekType,
+ 30 * 1000 /* 30s timeout */, &uNewOffset);
+ if (RT_SUCCESS(vrc))
+ *aNewOffset = RT_MIN(uNewOffset, (uint64_t)INT64_MAX);
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Seeking file \"%s\" (to offset %RI64) failed: %Rrc"),
+ mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::setACL(const com::Utf8Str &aAcl, ULONG aMode)
+{
+ RT_NOREF(aAcl, aMode);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestFile::setSize(LONG64 aSize)
+{
+ LogFlowThisFuncEnter();
+
+ /*
+ * Validate.
+ */
+ if (aSize < 0)
+ return setError(E_INVALIDARG, tr("The size (%RI64) for guest file \"%s\" cannot be a negative value"),
+ aSize, mData.mOpenInfo.mFilename.c_str());
+
+ /*
+ * Register event callbacks.
+ */
+ int vrc;
+ GuestWaitEvent *pWaitEvent = NULL;
+ GuestEventTypes lstEventTypes;
+ try
+ {
+ lstEventTypes.push_back(VBoxEventType_OnGuestFileStateChanged);
+ lstEventTypes.push_back(VBoxEventType_OnGuestFileSizeChanged);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ vrc = registerWaitEvent(lstEventTypes, &pWaitEvent);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Send of the HGCM message.
+ */
+ VBOXHGCMSVCPARM aParms[3];
+ HGCMSvcSetU32(&aParms[0], pWaitEvent->ContextID());
+ HGCMSvcSetU32(&aParms[1], mObjectID /* File handle */);
+ HGCMSvcSetU64(&aParms[2], aSize);
+
+ alock.release(); /* Drop write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_FILE_SET_SIZE, RT_ELEMENTS(aParms), aParms);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Wait for the event.
+ */
+ VBoxEventType_T enmEvtType;
+ ComPtr<IEvent> pIEvent;
+ vrc = waitForEvent(pWaitEvent, RT_MS_1MIN / 2, &enmEvtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (enmEvtType == VBoxEventType_OnGuestFileSizeChanged)
+ vrc = VINF_SUCCESS;
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+ if (RT_FAILURE(vrc) && pWaitEvent->HasGuestError()) /* Return guest vrc if available. */
+ vrc = pWaitEvent->GetGuestError();
+ }
+
+ /*
+ * Unregister the wait event and deal with error reporting if needed.
+ */
+ unregisterWaitEvent(pWaitEvent);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Setting the guest file size of \"%s\" to %RU64 (%#RX64) bytes failed: %Rrc", "", aSize),
+ mData.mOpenInfo.mFilename.c_str(), aSize, aSize, vrc);
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::write(const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ if (aData.size() == 0)
+ return setError(E_INVALIDARG, tr("No data to write specified"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ const uint32_t cbData = (uint32_t)aData.size();
+ const void *pvData = (void *)&aData.front();
+ int vrc = i_writeData(aTimeoutMS, pvData, cbData, (uint32_t*)aWritten);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc", "", aData.size()),
+ aData.size(), mData.mOpenInfo.mFilename.c_str(), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestFile::writeAt(LONG64 aOffset, const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ if (aData.size() == 0)
+ return setError(E_INVALIDARG, tr("No data to write at for guest file \"%s\" specified"), mData.mOpenInfo.mFilename.c_str());
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ const uint32_t cbData = (uint32_t)aData.size();
+ const void *pvData = (void *)&aData.front();
+ int vrc = i_writeDataAt(aOffset, aTimeoutMS, pvData, cbData, (uint32_t*)aWritten);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Writing %zu bytes to file \"%s\" (at offset %RU64) failed: %Rrc", "", aData.size()),
+ aData.size(), mData.mOpenInfo.mFilename.c_str(), aOffset, vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
diff --git a/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp
new file mode 100644
index 00000000..3afb3cfa
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestFsObjInfoImpl.cpp
@@ -0,0 +1,237 @@
+/* $Id: GuestFsObjInfoImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest file system object information handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#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..8f6a758f
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestImpl.cpp
@@ -0,0 +1,1200 @@
+/* $Id: GuestImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation: Guest features.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_GUEST
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifdef VBOX_WITH_GUEST_CONTROL
+# include "GuestSessionImpl.h"
+#endif
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+#ifdef VBOX_WITH_DRAG_AND_DROP
+# include "GuestDnDPrivate.h"
+#endif
+#include "VMMDev.h"
+
+#include "AutoCaller.h"
+#include "Performance.h"
+#include "VBoxEvents.h"
+
+#include <VBox/VMMDev.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/ctype.h>
+#include <iprt/stream.h>
+#include <iprt/timer.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/version.h>
+
+// defines
+/////////////////////////////////////////////////////////////////////////////
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(Guest)
+
+HRESULT Guest::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void Guest::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the guest object.
+ */
+HRESULT Guest::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ ULONG aMemoryBalloonSize = 0;
+ HRESULT hr = mParent->i_machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
+ if (SUCCEEDED(hr))
+ mMemoryBalloonSize = aMemoryBalloonSize;
+ else
+ mMemoryBalloonSize = 0; /* Default is no ballooning */
+
+ BOOL fPageFusionEnabled = FALSE;
+ hr = mParent->i_machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled);
+ if (SUCCEEDED(hr))
+ mfPageFusionEnabled = fPageFusionEnabled;
+ else
+ mfPageFusionEnabled = false; /* Default is no page fusion*/
+
+ mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
+ mCollectVMMStats = false;
+
+ /* Clear statistics. */
+ mNetStatRx = mNetStatTx = 0;
+ mNetStatLastTs = RTTimeNanoTS();
+ for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
+ mCurrentGuestStat[i] = 0;
+ mVmValidStats = pm::VMSTATMASK_NONE;
+ RT_ZERO(mCurrentGuestCpuUserStat);
+ RT_ZERO(mCurrentGuestCpuKernelStat);
+ RT_ZERO(mCurrentGuestCpuIdleStat);
+
+ mMagic = GUEST_MAGIC;
+ mStatTimer = NIL_RTTIMERLR;
+
+ hr = unconst(mEventSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = mEventSource->init();
+
+ mCpus = 1;
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ if (SUCCEEDED(hr))
+ {
+ try
+ {
+ GuestDnD::createInstance(this /* pGuest */);
+ hr = unconst(mDnDSource).createObject();
+ if (SUCCEEDED(hr))
+ hr = mDnDSource->init(this /* pGuest */);
+ if (SUCCEEDED(hr))
+ {
+ hr = unconst(mDnDTarget).createObject();
+ if (SUCCEEDED(hr))
+ hr = mDnDTarget->init(this /* pGuest */);
+ }
+
+ LogFlowFunc(("Drag and drop initializied with hr=%Rhrc\n", hr));
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+#endif
+
+ /* Confirm a successful initialization when it's the case: */
+ if (SUCCEEDED(hr))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed();
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Guest::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* Destroy stat update timer */
+ int vrc = RTTimerLRDestroy(mStatTimer);
+ AssertMsgRC(vrc, ("Failed to create guest statistics update timer(%Rra)\n", vrc));
+ mStatTimer = NIL_RTTIMERLR;
+ mMagic = 0;
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ LogFlowThisFunc(("Closing sessions (%RU64 total)\n",
+ mData.mGuestSessions.size()));
+ GuestSessions::iterator itSessions = mData.mGuestSessions.begin();
+ while (itSessions != mData.mGuestSessions.end())
+ {
+# ifdef DEBUG
+/** @todo r=bird: hit a use-after-free situation here while debugging the
+ * 0xcccccccc status code issue in copyto. My bet is that this happens
+ * because of an uninit race, where GuestSession::close(), or someone, does
+ * not ensure that the parent object (Guest) is okay to use (in the AutoCaller
+ * sense), only their own object. */
+ ULONG cRefs = itSessions->second->AddRef();
+ LogFlowThisFunc(("sessionID=%RU32, cRefs=%RU32\n", itSessions->first, cRefs > 1 ? cRefs - 1 : 0));
+ itSessions->second->Release();
+# endif
+ itSessions->second->uninit();
+ ++itSessions;
+ }
+ mData.mGuestSessions.clear();
+#endif
+
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ GuestDnD::destroyInstance();
+ unconst(mDnDSource).setNull();
+ unconst(mDnDTarget).setNull();
+#endif
+
+ unconst(mEventSource).setNull();
+ unconst(mParent) = NULL;
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+DECLCALLBACK(void) Guest::i_staticUpdateStats(RTTIMERLR hTimerLR, void *pvUser, uint64_t iTick)
+{
+ AssertReturnVoid(pvUser != NULL);
+ Guest *guest = static_cast<Guest *>(pvUser);
+ Assert(guest->mMagic == GUEST_MAGIC);
+ if (guest->mMagic == GUEST_MAGIC)
+ guest->i_updateStats(iTick);
+
+ NOREF(hTimerLR);
+}
+
+/* static */
+DECLCALLBACK(int) Guest::i_staticEnumStatsCallback(const char *pszName, STAMTYPE enmType, void *pvSample,
+ STAMUNIT enmUnit, const char *pszUnit, STAMVISIBILITY enmVisiblity,
+ const char *pszDesc, void *pvUser)
+{
+ RT_NOREF(enmVisiblity, pszDesc, pszUnit);
+ AssertLogRelMsgReturn(enmType == STAMTYPE_COUNTER, ("Unexpected sample type %d ('%s')\n", enmType, pszName), VINF_SUCCESS);
+ AssertLogRelMsgReturn(enmUnit == STAMUNIT_BYTES, ("Unexpected sample unit %d ('%s')\n", enmUnit, pszName), VINF_SUCCESS);
+
+ /* Get the base name w/ slash. */
+ const char *pszLastSlash = strrchr(pszName, '/');
+ AssertLogRelMsgReturn(pszLastSlash, ("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
+
+ /* Receive or transmit? */
+ bool fRx;
+ if (!strcmp(pszLastSlash, "/BytesReceived"))
+ fRx = true;
+ else if (!strcmp(pszLastSlash, "/BytesTransmitted"))
+ fRx = false;
+ else
+ AssertLogRelMsgFailedReturn(("Unexpected sample '%s'\n", pszName), VINF_SUCCESS);
+
+#if 0 /* not used for anything, so don't bother parsing it. */
+ /* Find start of instance number. ASSUMES '/Public/Net/Name<Instance digits>/Bytes...' */
+ do
+ --pszLastSlash;
+ while (pszLastSlash > pszName && RT_C_IS_DIGIT(*pszLastSlash));
+ pszLastSlash++;
+
+ uint8_t uInstance;
+ int vrc = RTStrToUInt8Ex(pszLastSlash, NULL, 10, &uInstance);
+ AssertLogRelMsgReturn(RT_SUCCESS(vrc) && vrc != VWRN_NUMBER_TOO_BIG && vrc != VWRN_NEGATIVE_UNSIGNED,
+ ("%Rrc '%s'\n", vrc, pszName), VINF_SUCCESS)
+#endif
+
+ /* Add the bytes to our counters. */
+ PSTAMCOUNTER pCnt = (PSTAMCOUNTER)pvSample;
+ Guest *pGuest = (Guest *)pvUser;
+ uint64_t cb = pCnt->c;
+#if 0
+ LogFlowFunc(("%s i=%u d=%s %llu bytes\n", pszName, uInstance, fRx ? "RX" : "TX", cb));
+#else
+ LogFlowFunc(("%s d=%s %llu bytes\n", pszName, fRx ? "RX" : "TX", cb));
+#endif
+ if (fRx)
+ pGuest->mNetStatRx += cb;
+ else
+ pGuest->mNetStatTx += cb;
+
+ return VINF_SUCCESS;
+}
+
+void Guest::i_updateStats(uint64_t iTick)
+{
+ RT_NOREF(iTick);
+
+ uint64_t cbFreeTotal = 0;
+ uint64_t cbAllocTotal = 0;
+ uint64_t cbBalloonedTotal = 0;
+ uint64_t cbSharedTotal = 0;
+ uint64_t cbSharedMem = 0;
+ ULONG uNetStatRx = 0;
+ ULONG uNetStatTx = 0;
+ ULONG aGuestStats[GUESTSTATTYPE_MAX];
+ RT_ZERO(aGuestStats);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ULONG validStats = mVmValidStats;
+ /* Check if we have anything to report */
+ if (validStats)
+ {
+ mVmValidStats = pm::VMSTATMASK_NONE;
+ memcpy(aGuestStats, mCurrentGuestStat, sizeof(aGuestStats));
+ }
+ alock.release();
+
+ /*
+ * Calling SessionMachine may take time as the object resides in VBoxSVC
+ * process. This is why we took a snapshot of currently collected stats
+ * and released the lock.
+ */
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ int vrc;
+
+ /*
+ * There is no point in collecting VM shared memory if other memory
+ * statistics are not available yet. Or is there?
+ */
+ if (validStats)
+ {
+ /* Query the missing per-VM memory statistics. */
+ uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbZeroMemIgn;
+ vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn,
+ &cbSharedMem, &cbZeroMemIgn);
+ if (vrc == VINF_SUCCESS)
+ validStats |= pm::VMSTATMASK_GUEST_MEMSHARED;
+ }
+
+ if (mCollectVMMStats)
+ {
+ vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal,
+ &cbBalloonedTotal, &cbSharedTotal);
+ AssertRC(vrc);
+ if (vrc == VINF_SUCCESS)
+ validStats |= pm::VMSTATMASK_VMM_ALLOC | pm::VMSTATMASK_VMM_FREE
+ | pm::VMSTATMASK_VMM_BALOON | pm::VMSTATMASK_VMM_SHARED;
+ }
+
+ uint64_t uRxPrev = mNetStatRx;
+ uint64_t uTxPrev = mNetStatTx;
+ mNetStatRx = mNetStatTx = 0;
+ vrc = ptrVM.vtable()->pfnSTAMR3Enum(ptrVM.rawUVM(), "/Public/Net/*/Bytes*", i_staticEnumStatsCallback, this);
+ AssertRC(vrc);
+
+ uint64_t uTsNow = RTTimeNanoTS();
+ uint64_t cNsPassed = uTsNow - mNetStatLastTs;
+ if (cNsPassed >= 1000)
+ {
+ mNetStatLastTs = uTsNow;
+
+ uNetStatRx = (ULONG)((mNetStatRx - uRxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
+ uNetStatTx = (ULONG)((mNetStatTx - uTxPrev) * 1000000 / (cNsPassed / 1000)); /* in bytes per second */
+ validStats |= pm::VMSTATMASK_NET_RX | pm::VMSTATMASK_NET_TX;
+ LogFlowThisFunc(("Net Rx=%llu Tx=%llu Ts=%llu Delta=%llu\n", mNetStatRx, mNetStatTx, uTsNow, cNsPassed));
+ }
+ else
+ {
+ /* Can happen on resume or if we're using a non-monotonic clock
+ source for the timer and the time is adjusted. */
+ mNetStatRx = uRxPrev;
+ mNetStatTx = uTxPrev;
+ LogThisFunc(("Net Ts=%llu cNsPassed=%llu - too small interval\n", uTsNow, cNsPassed));
+ }
+ }
+
+ mParent->i_reportVmStatistics(validStats,
+ aGuestStats[GUESTSTATTYPE_CPUUSER],
+ aGuestStats[GUESTSTATTYPE_CPUKERNEL],
+ aGuestStats[GUESTSTATTYPE_CPUIDLE],
+ /* Convert the units for RAM usage stats: page (4K) -> 1KB units */
+ mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K),
+ (ULONG)(cbSharedMem / _1K), /* bytes -> KB */
+ mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K),
+ mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K),
+ (ULONG)(cbAllocTotal / _1K), /* bytes -> KB */
+ (ULONG)(cbFreeTotal / _1K),
+ (ULONG)(cbBalloonedTotal / _1K),
+ (ULONG)(cbSharedTotal / _1K),
+ uNetStatRx,
+ uNetStatTx);
+}
+
+// IGuest properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Guest::getOSTypeId(com::Utf8Str &aOSTypeId)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!mData.mInterfaceVersion.isEmpty())
+ aOSTypeId = mData.mOSTypeId;
+ else
+ {
+ /* Redirect the call to IMachine if no additions are installed. */
+ ComPtr<IMachine> ptrMachine(mParent->i_machine());
+ alock.release();
+ Bstr bstr;
+ hrc = ptrMachine->COMGETTER(OSTypeId)(bstr.asOutParam());
+ aOSTypeId = bstr;
+ }
+ return hrc;
+}
+
+HRESULT Guest::getAdditionsRunLevel(AdditionsRunLevelType_T *aAdditionsRunLevel)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aAdditionsRunLevel = mData.mAdditionsRunLevel;
+
+ return S_OK;
+}
+
+HRESULT Guest::getAdditionsVersion(com::Utf8Str &aAdditionsVersion)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = S_OK;
+
+ /*
+ * Return the ReportGuestInfo2 version info if available.
+ */
+ if ( !mData.mAdditionsVersionNew.isEmpty()
+ || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
+ aAdditionsVersion = mData.mAdditionsVersionNew;
+ else
+ {
+ /*
+ * If we're running older Guest Additions (< 3.2.0) try get it from
+ * the guest properties. Detected switched around Version and
+ * Revision in early 3.1.x releases (see r57115).
+ */
+ ComPtr<IMachine> ptrMachine = mParent->i_machine();
+ alock.release(); /* No need to hold this during the IPC fun. */
+
+ Bstr bstr;
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
+ if ( SUCCEEDED(hrc)
+ && !bstr.isEmpty())
+ {
+ Utf8Str str(bstr);
+ if (str.count('.') == 0)
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
+ str = bstr;
+ if (str.count('.') != 2)
+ hrc = E_FAIL;
+ }
+
+ if (SUCCEEDED(hrc))
+ aAdditionsVersion = bstr;
+ else
+ {
+ /* Returning 1.4 is better than nothing. */
+ alock.acquire();
+ aAdditionsVersion = mData.mInterfaceVersion;
+ hrc = S_OK;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Guest::getAdditionsRevision(ULONG *aAdditionsRevision)
+{
+ HRESULT hrc = S_OK;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Return the ReportGuestInfo2 version info if available.
+ */
+ if ( !mData.mAdditionsVersionNew.isEmpty()
+ || mData.mAdditionsRunLevel <= AdditionsRunLevelType_None)
+ *aAdditionsRevision = mData.mAdditionsRevision;
+ else
+ {
+ /*
+ * If we're running older Guest Additions (< 3.2.0) try get it from
+ * the guest properties. Detected switched around Version and
+ * Revision in early 3.1.x releases (see r57115).
+ */
+ ComPtr<IMachine> ptrMachine = mParent->i_machine();
+ alock.release(); /* No need to hold this during the IPC fun. */
+
+ Bstr bstr;
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Revision").raw(), bstr.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ Utf8Str str(bstr);
+ uint32_t uRevision;
+ int vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
+ if (vrc != VINF_SUCCESS && str.count('.') == 2)
+ {
+ hrc = ptrMachine->GetGuestPropertyValue(Bstr("/VirtualBox/GuestAdd/Version").raw(), bstr.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ str = bstr;
+ vrc = RTStrToUInt32Full(str.c_str(), 0, &uRevision);
+ }
+ }
+ if (vrc == VINF_SUCCESS)
+ *aAdditionsRevision = uRevision;
+ else
+ hrc = VBOX_E_IPRT_ERROR;
+ }
+ if (FAILED(hrc))
+ {
+ /* Return 0 if we don't know. */
+ *aAdditionsRevision = 0;
+ hrc = S_OK;
+ }
+ }
+ return hrc;
+}
+
+HRESULT Guest::getDnDSource(ComPtr<IGuestDnDSource> &aDnDSource)
+{
+#ifndef VBOX_WITH_DRAG_AND_DROP
+ RT_NOREF(aDnDSource);
+ ReturnComNotImplemented();
+#else
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ HRESULT hr = mDnDSource.queryInterfaceTo(aDnDSource.asOutParam());
+
+ LogFlowFuncLeaveRC(hr);
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT Guest::getDnDTarget(ComPtr<IGuestDnDTarget> &aDnDTarget)
+{
+#ifndef VBOX_WITH_DRAG_AND_DROP
+ RT_NOREF(aDnDTarget);
+ ReturnComNotImplemented();
+#else
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ HRESULT hr = mDnDTarget.queryInterfaceTo(aDnDTarget.asOutParam());
+
+ LogFlowFuncLeaveRC(hr);
+ return hr;
+#endif /* VBOX_WITH_DRAG_AND_DROP */
+}
+
+HRESULT Guest::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ /* No need to lock - lifetime constant. */
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowFuncLeaveRC(S_OK);
+ return S_OK;
+}
+
+HRESULT Guest::getFacilities(std::vector<ComPtr<IAdditionsFacility> > &aFacilities)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFacilities.resize(mData.mFacilityMap.size());
+ size_t i = 0;
+ for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aFacilities[i].asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Guest::getSessions(std::vector<ComPtr<IGuestSession> > &aSessions)
+{
+#ifdef VBOX_WITH_GUEST_CONTROL
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aSessions.resize(mData.mGuestSessions.size());
+ size_t i = 0;
+ for (GuestSessions::iterator it = mData.mGuestSessions.begin(); it != mData.mGuestSessions.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aSessions[i].asOutParam());
+
+ return S_OK;
+#else
+ ReturnComNotImplemented();
+#endif
+}
+
+BOOL Guest::i_isPageFusionEnabled()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mfPageFusionEnabled;
+}
+
+HRESULT Guest::getMemoryBalloonSize(ULONG *aMemoryBalloonSize)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aMemoryBalloonSize = mMemoryBalloonSize;
+
+ return S_OK;
+}
+
+HRESULT Guest::setMemoryBalloonSize(ULONG aMemoryBalloonSize)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
+ * does not call us back in any way! */
+ HRESULT ret = mParent->i_machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
+ if (ret == S_OK)
+ {
+ mMemoryBalloonSize = aMemoryBalloonSize;
+ /* forward the information to the VMM device */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ /* MUST release all locks before calling VMM device as its critsect
+ * has higher lock order than anything in Main. */
+ alock.release();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize);
+ }
+ }
+
+ return ret;
+}
+
+HRESULT Guest::getStatisticsUpdateInterval(ULONG *aStatisticsUpdateInterval)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatisticsUpdateInterval = mStatUpdateInterval;
+ return S_OK;
+}
+
+HRESULT Guest::setStatisticsUpdateInterval(ULONG aStatisticsUpdateInterval)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Update the timer, creating it the first time we're called with a non-zero value. */
+ int vrc;
+ HRESULT hrc = S_OK;
+ if (aStatisticsUpdateInterval > 0)
+ {
+ if (mStatTimer == NIL_RTTIMERLR)
+ {
+ vrc = RTTimerLRCreate(&mStatTimer, aStatisticsUpdateInterval * RT_MS_1SEC, &Guest::i_staticUpdateStats, this);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to create guest statistics update timer (%Rrc)"), vrc));
+ }
+ else if (aStatisticsUpdateInterval != mStatUpdateInterval)
+ {
+ vrc = RTTimerLRChangeInterval(mStatTimer, aStatisticsUpdateInterval * RT_NS_1SEC_64);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to change guest statistics update timer interval from %u to %u failed (%Rrc)"),
+ mStatUpdateInterval, aStatisticsUpdateInterval, vrc));
+ if (mStatUpdateInterval == 0)
+ {
+ vrc = RTTimerLRStart(mStatTimer, 0);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to start the guest statistics update timer (%Rrc)"), vrc));
+ }
+ }
+ }
+ /* Setting interval to zero - stop the update timer if needed: */
+ else if (mStatUpdateInterval > 0 && mStatTimer != NIL_RTTIMERLR)
+ {
+ vrc = RTTimerLRStop(mStatTimer);
+ AssertRCStmt(vrc, hrc = setErrorVrc(vrc, tr("Failed to stop the guest statistics update timer (%Rrc)"), vrc));
+ }
+
+ /* Update the interval now that the timer is in sync. */
+ mStatUpdateInterval = aStatisticsUpdateInterval;
+
+ /* Forward the information to the VMM device.
+ MUST release all locks before calling VMM device as its critsect
+ has higher lock order than anything in Main. */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ alock.release();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aStatisticsUpdateInterval);
+ }
+
+ return hrc;
+}
+
+
+HRESULT Guest::internalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
+ ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
+ ULONG *aMemShared, ULONG *aMemCache, ULONG *aPageTotal,
+ ULONG *aMemAllocTotal, ULONG *aMemFreeTotal,
+ ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
+ *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
+ *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
+ *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */
+ *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
+
+ /* Play safe or smth? */
+ *aMemAllocTotal = 0;
+ *aMemFreeTotal = 0;
+ *aMemBalloonTotal = 0;
+ *aMemSharedTotal = 0;
+ *aMemShared = 0;
+
+ /* MUST release all locks before calling any PGM statistics queries,
+ * as they are executed by EMT and that might deadlock us by VMM device
+ * activity which waits for the Guest object lock. */
+ alock.release();
+ Console::SafeVMPtr ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return E_FAIL;
+
+ uint64_t cbFreeTotal, cbAllocTotal, cbBalloonedTotal, cbSharedTotal;
+ int vrc = ptrVM.vtable()->pfnPGMR3QueryGlobalMemoryStats(ptrVM.rawUVM(), &cbAllocTotal, &cbFreeTotal,
+ &cbBalloonedTotal, &cbSharedTotal);
+ AssertRCReturn(vrc, E_FAIL);
+
+ *aMemAllocTotal = (ULONG)(cbAllocTotal / _1K); /* bytes -> KB */
+ *aMemFreeTotal = (ULONG)(cbFreeTotal / _1K);
+ *aMemBalloonTotal = (ULONG)(cbBalloonedTotal / _1K);
+ *aMemSharedTotal = (ULONG)(cbSharedTotal / _1K);
+
+ /* Query the missing per-VM memory statistics. */
+ uint64_t cbTotalMemIgn, cbPrivateMemIgn, cbSharedMem, cbZeroMemIgn;
+ vrc = ptrVM.vtable()->pfnPGMR3QueryMemoryStats(ptrVM.rawUVM(), &cbTotalMemIgn, &cbPrivateMemIgn, &cbSharedMem, &cbZeroMemIgn);
+ AssertRCReturn(vrc, E_FAIL);
+ *aMemShared = (ULONG)(cbSharedMem / _1K);
+
+ return S_OK;
+}
+
+HRESULT Guest::i_setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
+{
+ static ULONG indexToPerfMask[] =
+ {
+ pm::VMSTATMASK_GUEST_CPUUSER,
+ pm::VMSTATMASK_GUEST_CPUKERNEL,
+ pm::VMSTATMASK_GUEST_CPUIDLE,
+ pm::VMSTATMASK_GUEST_MEMTOTAL,
+ pm::VMSTATMASK_GUEST_MEMFREE,
+ pm::VMSTATMASK_GUEST_MEMBALLOON,
+ pm::VMSTATMASK_GUEST_MEMCACHE,
+ pm::VMSTATMASK_GUEST_PAGETOTAL,
+ pm::VMSTATMASK_NONE
+ };
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (enmType >= GUESTSTATTYPE_MAX)
+ return E_INVALIDARG;
+
+ if (aCpuId < VMM_MAX_CPU_COUNT)
+ {
+ ULONG *paCpuStats;
+ switch (enmType)
+ {
+ case GUESTSTATTYPE_CPUUSER: paCpuStats = mCurrentGuestCpuUserStat; break;
+ case GUESTSTATTYPE_CPUKERNEL: paCpuStats = mCurrentGuestCpuKernelStat; break;
+ case GUESTSTATTYPE_CPUIDLE: paCpuStats = mCurrentGuestCpuIdleStat; break;
+ default: paCpuStats = NULL; break;
+ }
+ if (paCpuStats)
+ {
+ paCpuStats[aCpuId] = aVal;
+ aVal = 0;
+ for (uint32_t i = 0; i < mCpus && i < VMM_MAX_CPU_COUNT; i++)
+ aVal += paCpuStats[i];
+ aVal /= mCpus;
+ }
+ }
+
+ mCurrentGuestStat[enmType] = aVal;
+ mVmValidStats |= indexToPerfMask[enmType];
+ return S_OK;
+}
+
+/**
+ * Returns the status of a specified Guest Additions facility.
+ *
+ * @return COM status code
+ * @param aFacility Facility to get the status from.
+ * @param aTimestamp Timestamp of last facility status update in ms (optional).
+ * @param aStatus Current status of the specified facility.
+ */
+HRESULT Guest::getFacilityStatus(AdditionsFacilityType_T aFacility, LONG64 *aTimestamp, AdditionsFacilityStatus_T *aStatus)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Not checking for aTimestamp is intentional; it's optional. */
+ FacilityMapIterConst it = mData.mFacilityMap.find(aFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFacility = it->second;
+ ComAssert(pFacility);
+ *aStatus = pFacility->i_getStatus();
+ if (aTimestamp)
+ *aTimestamp = pFacility->i_getLastUpdated();
+ }
+ else
+ {
+ /*
+ * Do not fail here -- could be that the facility never has been brought up (yet) but
+ * the host wants to have its status anyway. So just tell we don't know at this point.
+ */
+ *aStatus = AdditionsFacilityStatus_Unknown;
+ if (aTimestamp)
+ *aTimestamp = RTTimeMilliTS();
+ }
+ return S_OK;
+}
+
+HRESULT Guest::getAdditionsStatus(AdditionsRunLevelType_T aLevel, BOOL *aActive)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ switch (aLevel)
+ {
+ case AdditionsRunLevelType_System:
+ *aActive = (mData.mAdditionsRunLevel > AdditionsRunLevelType_None);
+ break;
+
+ case AdditionsRunLevelType_Userland:
+ *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Userland);
+ break;
+
+ case AdditionsRunLevelType_Desktop:
+ *aActive = (mData.mAdditionsRunLevel >= AdditionsRunLevelType_Desktop);
+ break;
+
+ default:
+ hrc = setError(VBOX_E_NOT_SUPPORTED,
+ tr("Invalid status level defined: %u"), aLevel);
+ break;
+ }
+
+ return hrc;
+}
+
+HRESULT Guest::setCredentials(const com::Utf8Str &aUserName, const com::Utf8Str &aPassword,
+ const com::Utf8Str &aDomain, BOOL aAllowInteractiveLogon)
+{
+ /* Check for magic domain names which are used to pass encryption keys to the disk. */
+ if (Utf8Str(aDomain) == "@@disk")
+ return mParent->i_setDiskEncryptionKeys(aPassword);
+ if (Utf8Str(aDomain) == "@@mem")
+ {
+ /** @todo */
+ return E_NOTIMPL;
+ }
+
+ /* forward the information to the VMM device */
+ VMMDev *pVMMDev = mParent->i_getVMMDev();
+ if (pVMMDev)
+ {
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (pVMMDevPort)
+ {
+ uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
+ if (!aAllowInteractiveLogon)
+ u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
+
+ pVMMDevPort->pfnSetCredentials(pVMMDevPort,
+ aUserName.c_str(),
+ aPassword.c_str(),
+ aDomain.c_str(),
+ u32Flags);
+ return S_OK;
+ }
+ }
+
+ return setError(VBOX_E_VM_ERROR, tr("VMM device is not available (is the VM running?)"));
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Sets the general Guest Additions information like
+ * API (interface) version and OS type. Gets called by
+ * vmmdevUpdateGuestInfo.
+ *
+ * @param aInterfaceVersion
+ * @param aOsType
+ */
+void Guest::i_setAdditionsInfo(const com::Utf8Str &aInterfaceVersion, VBOXOSTYPE aOsType)
+{
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Note: The Guest Additions API (interface) version is deprecated
+ * and will not be used anymore! We might need it to at least report
+ * something as version number if *really* ancient Guest Additions are
+ * installed (without the guest version + revision properties having set).
+ */
+ mData.mInterfaceVersion = aInterfaceVersion;
+
+ /*
+ * Older Additions rely on the Additions API version whether they
+ * are assumed to be active or not. Since newer Additions do report
+ * the Additions version *before* calling this function (by calling
+ * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
+ * in that order) we can tell apart old and new Additions here. Old
+ * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
+ * so they just rely on the aInterfaceVersion string (which gets set by
+ * VMMDevReportGuestInfo).
+ *
+ * So only mark the Additions as being active (run level = system) when we
+ * don't have the Additions version set.
+ */
+ if (mData.mAdditionsVersionNew.isEmpty())
+ {
+ if (aInterfaceVersion.isEmpty())
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
+ else
+ {
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
+
+ /*
+ * To keep it compatible with the old Guest Additions behavior we need to set the
+ * "graphics" (feature) facility to active as soon as we got the Guest Additions
+ * interface version.
+ */
+ i_facilityUpdate(VBoxGuestFacilityType_Graphics, VBoxGuestFacilityStatus_Active, 0 /*fFlags*/, &TimeSpecTS);
+ }
+ }
+
+ /*
+ * Older Additions didn't have this finer grained capability bit,
+ * so enable it by default. Newer Additions will not enable this here
+ * and use the setSupportedFeatures function instead.
+ */
+ /** @todo r=bird: I don't get the above comment nor the code below...
+ * One talks about capability bits, the one always does something to a facility.
+ * Then there is the comment below it all, which is placed like it addresses the
+ * mOSTypeId, but talks about something which doesn't remotely like mOSTypeId...
+ *
+ * Andy, could you please try clarify and make the comments shorter and more
+ * coherent! Also, explain why this is important and what depends on it.
+ *
+ * PS. There is the VMMDEV_GUEST_SUPPORTS_GRAPHICS capability* report... It
+ * should come in pretty quickly after this update, normally.
+ */
+ i_facilityUpdate(VBoxGuestFacilityType_Graphics,
+ i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver)
+ ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS); /** @todo the timestamp isn't gonna be right here on saved state restore. */
+
+ /*
+ * Note! There is a race going on between setting mAdditionsRunLevel and
+ * mSupportsGraphics here and disabling/enabling it later according to
+ * its real status when using new(er) Guest Additions.
+ */
+ mData.mOSType = aOsType;
+ mData.mOSTypeId = Global::OSTypeId(aOsType);
+
+ /*
+ * Always fire an event here.
+ */
+ AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_None, AdditionsFacilityStatus_Active,
+ enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS));
+}
+
+/**
+ * Sets the Guest Additions version information details.
+ *
+ * Gets called by vmmdevUpdateGuestInfo2 and vmmdevUpdateGuestInfo (to clear the
+ * state).
+ *
+ * @param a_uFullVersion VBoxGuestInfo2::additionsMajor,
+ * VBoxGuestInfo2::additionsMinor and
+ * VBoxGuestInfo2::additionsBuild combined into
+ * one value by VBOX_FULL_VERSION_MAKE.
+ *
+ * When this is 0, it's vmmdevUpdateGuestInfo
+ * calling to reset the state.
+ *
+ * @param a_pszName Build type tag and/or publisher tag, empty
+ * string if neiter of those are present.
+ * @param a_uRevision See VBoxGuestInfo2::additionsRevision.
+ * @param a_fFeatures See VBoxGuestInfo2::additionsFeatures.
+ */
+void Guest::i_setAdditionsInfo2(uint32_t a_uFullVersion, const char *a_pszName, uint32_t a_uRevision, uint32_t a_fFeatures)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (a_uFullVersion)
+ {
+ mData.mAdditionsVersionNew = Utf8StrFmt(*a_pszName ? "%u.%u.%u_%s" : "%u.%u.%u",
+ VBOX_FULL_VERSION_GET_MAJOR(a_uFullVersion),
+ VBOX_FULL_VERSION_GET_MINOR(a_uFullVersion),
+ VBOX_FULL_VERSION_GET_BUILD(a_uFullVersion),
+ a_pszName);
+ mData.mAdditionsVersionFull = a_uFullVersion;
+ mData.mAdditionsRevision = a_uRevision;
+ mData.mAdditionsFeatures = a_fFeatures;
+ }
+ else
+ {
+ Assert(!a_fFeatures && !a_uRevision && !*a_pszName);
+ mData.mAdditionsVersionNew.setNull();
+ mData.mAdditionsVersionFull = 0;
+ mData.mAdditionsRevision = 0;
+ mData.mAdditionsFeatures = 0;
+ }
+}
+
+bool Guest::i_facilityIsActive(VBoxGuestFacilityType enmFacility)
+{
+ Assert(enmFacility < INT32_MAX);
+ FacilityMapIterConst it = mData.mFacilityMap.find((AdditionsFacilityType_T)enmFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFac = it->second;
+ return (pFac->i_getStatus() == AdditionsFacilityStatus_Active);
+ }
+ return false;
+}
+
+bool Guest::i_facilityUpdate(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ AssertReturn( a_enmFacility < VBoxGuestFacilityType_All
+ && a_enmFacility > VBoxGuestFacilityType_Unknown, false);
+
+ bool fChanged;
+ FacilityMapIter it = mData.mFacilityMap.find((AdditionsFacilityType_T)a_enmFacility);
+ if (it != mData.mFacilityMap.end())
+ {
+ AdditionsFacility *pFac = it->second;
+ fChanged = pFac->i_update((AdditionsFacilityStatus_T)a_enmStatus, a_fFlags, a_pTimeSpecTS);
+ }
+ else
+ {
+ if (mData.mFacilityMap.size() > 64)
+ {
+ /* The easy way out for now. We could automatically destroy
+ inactive facilities like VMMDev does if we like... */
+ AssertFailedReturn(false);
+ }
+
+ ComObjPtr<AdditionsFacility> ptrFac;
+ HRESULT hrc = ptrFac.createObject();
+ AssertComRCReturn(hrc, false);
+ Assert(ptrFac);
+
+ hrc = ptrFac->init(this, (AdditionsFacilityType_T)a_enmFacility, (AdditionsFacilityStatus_T)a_enmStatus,
+ a_fFlags, a_pTimeSpecTS);
+ AssertComRCReturn(hrc, false);
+ try
+ {
+ mData.mFacilityMap.insert(std::make_pair((AdditionsFacilityType_T)a_enmFacility, ptrFac));
+ fChanged = true;
+ }
+ catch (std::bad_alloc &)
+ {
+ fChanged = false;
+ }
+ }
+ return fChanged;
+}
+
+/**
+ * Issued by the guest when a guest user changed its state.
+ *
+ * @param aUser Guest user name.
+ * @param aDomain Domain of guest user account. Optional.
+ * @param enmState New state to indicate.
+ * @param pbDetails Pointer to state details. Optional.
+ * @param cbDetails Size (in bytes) of state details. Pass 0 if not used.
+ */
+void Guest::i_onUserStateChanged(const Utf8Str &aUser, const Utf8Str &aDomain, VBoxGuestUserState enmState,
+ const uint8_t *pbDetails, uint32_t cbDetails)
+{
+ RT_NOREF(pbDetails, cbDetails);
+ LogFlowThisFunc(("\n"));
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ Utf8Str strDetails; /** @todo Implement state details here. */
+
+ ::FireGuestUserStateChangedEvent(mEventSource, aUser, aDomain, (GuestUserState_T)enmState, strDetails);
+ LogFlowFuncLeave();
+}
+
+/**
+ * Sets the status of a certain Guest Additions facility.
+ *
+ * Gets called by vmmdevUpdateGuestStatus, which just passes the report along.
+ *
+ * @param a_enmFacility The facility.
+ * @param a_enmStatus The status.
+ * @param a_fFlags Flags assoicated with the update. Currently
+ * reserved and should be ignored.
+ * @param a_pTimeSpecTS Pointer to the timestamp of this report.
+ * @sa PDMIVMMDEVCONNECTOR::pfnUpdateGuestStatus, vmmdevUpdateGuestStatus
+ * @thread The emulation thread.
+ */
+void Guest::i_setAdditionsStatus(VBoxGuestFacilityType a_enmFacility, VBoxGuestFacilityStatus a_enmStatus,
+ uint32_t a_fFlags, PCRTTIMESPEC a_pTimeSpecTS)
+{
+ Assert( a_enmFacility > VBoxGuestFacilityType_Unknown
+ && a_enmFacility <= VBoxGuestFacilityType_All); /* Paranoia, VMMDev checks for this. */
+
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Set a specific facility status.
+ */
+ bool fFireEvent = false;
+ if (a_enmFacility == VBoxGuestFacilityType_All)
+ for (FacilityMapIter it = mData.mFacilityMap.begin(); it != mData.mFacilityMap.end(); ++it)
+ fFireEvent |= i_facilityUpdate((VBoxGuestFacilityType)it->first, a_enmStatus, a_fFlags, a_pTimeSpecTS);
+ else /* Update one facility only. */
+ fFireEvent = i_facilityUpdate(a_enmFacility, a_enmStatus, a_fFlags, a_pTimeSpecTS);
+
+ /*
+ * Recalc the runlevel.
+ */
+ AdditionsRunLevelType_T const enmOldRunLevel = mData.mAdditionsRunLevel;
+ if (i_facilityIsActive(VBoxGuestFacilityType_VBoxTrayClient))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_Desktop;
+ else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxService))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_Userland;
+ else if (i_facilityIsActive(VBoxGuestFacilityType_VBoxGuestDriver))
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_System;
+ else
+ mData.mAdditionsRunLevel = AdditionsRunLevelType_None;
+
+ /*
+ * Fire event if something actually changed.
+ */
+ AdditionsRunLevelType_T const enmNewRunLevel = mData.mAdditionsRunLevel;
+ if (fFireEvent || enmNewRunLevel != enmOldRunLevel)
+ {
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, (AdditionsFacilityType_T)a_enmFacility,
+ (AdditionsFacilityStatus_T)a_enmStatus, enmNewRunLevel,
+ RTTimeSpecGetMilli(a_pTimeSpecTS));
+ }
+}
+
+/**
+ * Sets the supported features (and whether they are active or not).
+ *
+ * @param aCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
+ */
+void Guest::i_setSupportedFeatures(uint32_t aCaps)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo A nit: The timestamp is wrong on saved state restore. Would be better
+ * to move the graphics and seamless capability -> facility translation to
+ * VMMDev so this could be saved. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+
+ bool fFireEvent = i_facilityUpdate(VBoxGuestFacilityType_Seamless,
+ aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
+ ? VBoxGuestFacilityStatus_Active : VBoxGuestFacilityStatus_Inactive,
+ 0 /*fFlags*/, &TimeSpecTS);
+ /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */
+
+ /*
+ * Fire event if the state actually changed.
+ */
+ if (fFireEvent)
+ {
+ AdditionsRunLevelType_T const enmRunLevel = mData.mAdditionsRunLevel;
+ alock.release();
+ ::FireGuestAdditionsStatusChangedEvent(mEventSource, AdditionsFacilityType_Seamless,
+ aCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS
+ ? AdditionsFacilityStatus_Active : AdditionsFacilityStatus_Inactive,
+ enmRunLevel, RTTimeSpecGetMilli(&TimeSpecTS));
+ }
+}
diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp
new file mode 100644
index 00000000..36ff3887
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp
@@ -0,0 +1,3010 @@
+/* $Id: GuestProcessImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest process handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * Locking rules:
+ * - When the main dispatcher (callbackDispatcher) is called it takes the
+ * WriteLock while dispatching to the various on* methods.
+ * - All other outer functions (accessible by Main) must not own a lock
+ * while waiting for a callback or for an event.
+ * - Only keep Read/WriteLocks as short as possible and only when necessary.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTPROCESS
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestImpl.h"
+#include "GuestProcessImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+#include "ThreadTask.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/getopt.h>
+
+#include <VBox/com/listeners.h>
+
+#include <VBox/com/array.h>
+
+
+/**
+ * Base class for all guest process tasks.
+ */
+class GuestProcessTask : public ThreadTask
+{
+public:
+
+ GuestProcessTask(GuestProcess *pProcess)
+ : ThreadTask("GenericGuestProcessTask")
+ , mProcess(pProcess)
+ , mVrc(VINF_SUCCESS) { }
+
+ virtual ~GuestProcessTask(void) { }
+
+ /** Returns the last set result code. */
+ int i_vrc(void) const { return mVrc; }
+ /** Returns whether the last set result is okay (successful) or not. */
+ bool i_isOk(void) const { return RT_SUCCESS(mVrc); }
+ /** Returns the reference of the belonging progress object. */
+ const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; }
+
+protected:
+
+ /** Progress object this process belongs to. */
+ const ComObjPtr<GuestProcess> mProcess;
+ /** Last set result code. */
+ int mVrc;
+};
+
+/**
+ * Task to start a process on the guest.
+ */
+class GuestProcessStartTask : public GuestProcessTask
+{
+public:
+
+ GuestProcessStartTask(GuestProcess *pProcess)
+ : GuestProcessTask(pProcess)
+ {
+ m_strTaskName = "gctlPrcStart";
+ }
+
+ void handler()
+ {
+ GuestProcess::i_startProcessThreadTask(this);
+ }
+};
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestProcessListener
+{
+public:
+
+ GuestProcessListener(void)
+ {
+ }
+
+ virtual ~GuestProcessListener(void)
+ {
+ }
+
+ HRESULT init(GuestProcess *pProcess)
+ {
+ AssertPtrReturn(pProcess, E_POINTER);
+ mProcess = pProcess;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mProcess = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestProcessStateChanged:
+ case VBoxEventType_OnGuestProcessInputNotify:
+ case VBoxEventType_OnGuestProcessOutput:
+ {
+ AssertPtrReturn(mProcess, E_POINTER);
+ int vrc2 = mProcess->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef LOG_ENABLED
+ LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in vrc=%Rrc\n",
+ aType, &mProcess, vrc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ GuestProcess *mProcess;
+};
+typedef ListenerImpl<GuestProcessListener, GuestProcess*> GuestProcessListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestProcessListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
+
+HRESULT GuestProcess::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestProcess::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initialies a guest process object.
+ *
+ * @returns VBox status code.
+ * @param aConsole Console this process is bound to.
+ * @param aSession Guest session this process is bound to.
+ * @param aObjectID Object ID to use for this process object.
+ * @param aProcInfo Process startup information to use.
+ * @param pBaseEnv Guest environment to apply when starting the process on the guest.
+ */
+int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aObjectID,
+ const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv)
+{
+ LogFlowThisFunc(("aConsole=%p, aSession=%p, aObjectID=%RU32, pBaseEnv=%p\n",
+ aConsole, aSession, aObjectID, pBaseEnv));
+
+ AssertPtrReturn(aConsole, VERR_INVALID_POINTER);
+ AssertPtrReturn(aSession, VERR_INVALID_POINTER);
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ HRESULT hrc;
+
+ int vrc = bindToSession(aConsole, aSession, aObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = unconst(mEventSource).createObject();
+ if (FAILED(hrc))
+ vrc = VERR_NO_MEMORY;
+ else
+ {
+ hrc = mEventSource->init();
+ if (FAILED(hrc))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ GuestProcessListener *pListener = new GuestProcessListener();
+ ComObjPtr<GuestProcessListenerImpl> thisListener;
+ hrc = thisListener.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = thisListener->init(pListener, this);
+
+ if (SUCCEEDED(hrc))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+ hrc = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = baseInit();
+ if (RT_SUCCESS(vrc))
+ {
+ mLocalListener = thisListener;
+ }
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ catch(std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ mData.mProcess = aProcInfo;
+ mData.mpSessionBaseEnv = pBaseEnv;
+ if (pBaseEnv)
+ pBaseEnv->retainConst();
+ mData.mExitCode = 0;
+ mData.mPID = 0;
+ mData.mLastError = VINF_SUCCESS;
+ mData.mStatus = ProcessStatus_Undefined;
+ /* Everything else will be set by the actual starting routine. */
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return vrc;
+ }
+
+ autoInitSpan.setFailed();
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease() or IGuestSession::uninit().
+ */
+void GuestProcess::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("mExe=%s, PID=%RU32\n", mData.mProcess.mExecutable.c_str(), mData.mPID));
+
+ if (mData.mpSessionBaseEnv)
+ {
+ mData.mpSessionBaseEnv->releaseConst();
+ mData.mpSessionBaseEnv = NULL;
+ }
+
+ baseUninit();
+
+ LogFlowFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestProcess::getArguments(std::vector<com::Utf8Str> &aArguments)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aArguments = mData.mProcess.mArguments;
+ return S_OK;
+}
+
+HRESULT GuestProcess::getEnvironment(std::vector<com::Utf8Str> &aEnvironment)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* (Paranoia since both environment objects are immutable.) */
+ HRESULT hrc;
+ if (mData.mpSessionBaseEnv)
+ {
+ int vrc;
+ if (mData.mProcess.mEnvironmentChanges.count() == 0)
+ vrc = mData.mpSessionBaseEnv->queryPutEnvArray(&aEnvironment);
+ else
+ {
+ GuestEnvironment TmpEnv;
+ vrc = TmpEnv.copy(*mData.mpSessionBaseEnv);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = TmpEnv.applyChanges(mData.mProcess.mEnvironmentChanges);
+ if (RT_SUCCESS(vrc))
+ vrc = TmpEnv.queryPutEnvArray(&aEnvironment);
+ }
+ }
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by installed Guest Additions"));
+ LogFlowThisFuncLeave();
+ return hrc;
+#endif
+}
+
+HRESULT GuestProcess::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExecutablePath(com::Utf8Str &aExecutablePath)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aExecutablePath = mData.mProcess.mExecutable;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExitCode(LONG *aExitCode)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aExitCode = mData.mExitCode;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.mProcess.mName;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getPID(ULONG *aPID)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPID = mData.mPID;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getStatus(ProcessStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ *aStatus = i_getStatus();
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side process callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestProcess::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+#ifdef DEBUG
+ LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
+ mData.mPID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+#endif
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ {
+ vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_STATUS:
+ {
+ vrc = i_onProcessStatusChange(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_OUTPUT:
+ {
+ vrc = i_onProcessOutput(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_INPUT_STATUS:
+ {
+ vrc = i_onProcessInputStatus(pCbCtx, pSvcCb);
+ break;
+ }
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+#ifdef DEBUG
+ LogFlowFuncLeaveRC(vrc);
+#endif
+ return vrc;
+}
+
+/**
+ * Checks if the current assigned PID matches another PID (from a callback).
+ *
+ * In protocol v1 we don't have the possibility to terminate/kill
+ * processes so it can happen that a formerly started process A
+ * (which has the context ID 0 (session=0, process=0, count=0) will
+ * send a delayed message to the host if this process has already
+ * been discarded there and the same context ID was reused by
+ * a process B. Process B in turn then has a different guest PID.
+ *
+ * Note: This also can happen when restoring from a saved state which
+ * had a guest process running.
+ *
+ * @return IPRT status code.
+ * @param uPID PID to check.
+ */
+inline int GuestProcess::i_checkPID(uint32_t uPID)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Was there a PID assigned yet? */
+ if (mData.mPID)
+ {
+ if (RT_UNLIKELY(mData.mPID != uPID))
+ {
+ LogFlowFunc(("Stale guest process (PID=%RU32) sent data to a newly started process (pProcesS=%p, PID=%RU32, status=%RU32)\n",
+ uPID, this, mData.mPID, mData.mStatus));
+ vrc = VERR_NOT_FOUND;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * Returns the current process status.
+ *
+ * @returns Current process status.
+ *
+ * @note Takes the read lock.
+ */
+ProcessStatus_T GuestProcess::i_getStatus(void)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mData.mStatus;
+}
+
+/**
+ * Converts a given guest process error to a string.
+ *
+ * @returns Error as a string.
+ * @param vrcGuest Guest process error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestProcess::i_guestErrorToString(int vrcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (vrcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+
+ CASE_MSG(VERR_FILE_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat); /* This is the most likely error. */
+ CASE_MSG(VERR_PATH_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_VM_HANDLE, tr("VMM device is not available (is the VM running?)"));
+ CASE_MSG(VERR_HGCM_SERVICE_NOT_FOUND, tr("The guest execution service is not available"));
+ CASE_MSG(VERR_BAD_EXE_FORMAT, tr("The file \"%s\" is not an executable format on guest"), pcszWhat);
+ CASE_MSG(VERR_AUTHENTICATION_FAILURE, tr("The user \"%s\" was not able to logon on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_NAME, tr("The file \"%s\" is an invalid name"), pcszWhat);
+ CASE_MSG(VERR_TIMEOUT, tr("The guest did not respond within time"));
+ CASE_MSG(VERR_CANCELLED, tr("The execution operation for \"%s\" was canceled"), pcszWhat);
+ CASE_MSG(VERR_GSTCTL_MAX_CID_OBJECTS_REACHED, tr("Maximum number of concurrent guest processes has been reached"));
+ CASE_MSG(VERR_NOT_FOUND, tr("The guest execution service is not ready (yet)"));
+ default:
+ strErr.printf(tr("Error %Rrc for guest process \"%s\" occurred\n"), vrcGuest, pcszWhat);
+ break;
+#undef CASE_MSG
+ }
+
+ return strErr;
+}
+
+/**
+ * Translates a process status to a human readable string.
+ *
+ * @returns Process status as a string.
+ * @param enmStatus Guest process status to return string for.
+ */
+/* static */
+Utf8Str GuestProcess::i_statusToString(ProcessStatus_T enmStatus)
+{
+ switch (enmStatus)
+ {
+ case ProcessStatus_Starting:
+ return "starting";
+ case ProcessStatus_Started:
+ return "started";
+ case ProcessStatus_Paused:
+ return "paused";
+ case ProcessStatus_Terminating:
+ return "terminating";
+ case ProcessStatus_TerminatedNormally:
+ return "successfully terminated";
+ case ProcessStatus_TerminatedSignal:
+ return "terminated by signal";
+ case ProcessStatus_TerminatedAbnormally:
+ return "abnormally aborted";
+ case ProcessStatus_TimedOutKilled:
+ return "timed out";
+ case ProcessStatus_TimedOutAbnormally:
+ return "timed out, hanging";
+ case ProcessStatus_Down:
+ return "killed";
+ case ProcessStatus_Error:
+ return "error";
+ default:
+ break;
+ }
+
+ AssertFailed(); /* Should never happen! */
+ return "unknown";
+}
+
+/**
+ * Returns @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ *
+ * @return bool @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ * @param vrc Error code to check.
+ */
+/* static */
+bool GuestProcess::i_isGuestError(int vrc)
+{
+ return vrc == VERR_GSTCTL_GUEST_ERROR
+ || vrc == VERR_GSTCTL_PROCESS_EXIT_CODE;
+}
+
+/**
+ * Returns whether the guest process is alive (i.e. running) or not.
+ *
+ * @returns \c true if alive and running, or \c false if not.
+ */
+inline bool GuestProcess::i_isAlive(void)
+{
+ return mData.mStatus == ProcessStatus_Started
+ || mData.mStatus == ProcessStatus_Paused
+ || mData.mStatus == ProcessStatus_Terminating;
+}
+
+/**
+ * Returns whether the guest process has ended (i.e. terminated) or not.
+ *
+ * @returns \c true if ended, or \c false if not.
+ */
+inline bool GuestProcess::i_hasEnded(void)
+{
+ return mData.mStatus == ProcessStatus_TerminatedNormally
+ || mData.mStatus == ProcessStatus_TerminatedSignal
+ || mData.mStatus == ProcessStatus_TerminatedAbnormally
+ || mData.mStatus == ProcessStatus_TimedOutKilled
+ || mData.mStatus == ProcessStatus_TimedOutAbnormally
+ || mData.mStatus == ProcessStatus_Down
+ || mData.mStatus == ProcessStatus_Error;
+}
+
+/**
+ * Called when the guest side of the process has been disconnected (closed, terminated, +++).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ int vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current input status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+ /* pCallback is optional. */
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_INPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[4], &dataCb.uProcessed);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessInputStatus_T inputStatus = ProcessInputStatus_Undefined;
+ switch (dataCb.uStatus)
+ {
+ case INPUT_STS_WRITTEN:
+ inputStatus = ProcessInputStatus_Written;
+ break;
+ case INPUT_STS_ERROR:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_TERMINATED:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_OVERFLOW:
+ inputStatus = ProcessInputStatus_Overflow;
+ break;
+ case INPUT_STS_UNDEFINED:
+ /* Fall through is intentional. */
+ default:
+ AssertMsg(!dataCb.uProcessed, ("Processed data is not 0 in undefined input state\n"));
+ break;
+ }
+
+ if (inputStatus != ProcessInputStatus_Undefined)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessInputNotifyEvent(mEventSource, mSession, this, uPID, 0 /* StdIn */, dataCb.uProcessed, inputStatus);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Notifies of an I/O operation of the guest process.
+ *
+ * @returns VERR_NOT_IMPLEMENTED -- not implemented yet.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Sets (reports) the current running status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ int vrcProc = 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:
+ {
+ vrcProc = 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 vrc=%Rrc, procSts=%RU32, vrcProc=%Rrc\n", vrc, procStatus, vrcProc));
+
+ /* Set the process status. */
+ int vrc2 = i_setProcessStatus(procStatus, vrcProc);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current output status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ RT_NOREF(pCbCtx);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_OUTPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uHandle);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n",
+ dataCb.uPID, dataCb.uHandle, dataCb.uFlags, dataCb.pvData, dataCb.cbData));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ com::SafeArray<BYTE> data((size_t)dataCb.cbData);
+ if (dataCb.cbData)
+ data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData);
+
+ ::FireGuestProcessOutputEvent(mEventSource, mSession, this,
+ mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data));
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestProcess::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestProcess::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ /* If the session now is in a terminated state, set the process status
+ * to "down", as there is not much else we can do now. */
+ if (GuestSession::i_isTerminated(enmSessionStatus))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS /* vrcProc, ignored */);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Reads data from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Internal file handle to use for reading.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead, int *pvrcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, pvrcGuest=%p\n",
+ mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, pvrcGuest));
+ AssertReturn(uSize, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mData.mStatus != ProcessStatus_Started
+ /* Skip reading if the process wasn't started with the appropriate
+ * flags. */
+ || ( ( uHandle == GUEST_PROC_OUT_H_STDOUT
+ || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED)
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut))
+ || ( uHandle == GUEST_PROC_OUT_H_STDERR
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr))
+ )
+ {
+ if (pcbRead)
+ *pcbRead = 0;
+ if (pvrcGuest)
+ *pvrcGuest = VINF_SUCCESS;
+ return VINF_SUCCESS; /* Nothing to read anymore. */
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that the process status
+ * change arrives *after* the output event, e.g. if this was the last output
+ * block being read and the process will report status "terminate".
+ * So just skip checking for process status change and only wait for the
+ * output event.
+ */
+ if (mSession->i_getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (RT_SUCCESS(vrc))
+ {
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+ HGCMSvcSetU32(&paParms[i++], uHandle);
+ HGCMSvcSetU32(&paParms[i++], 0 /* Flags, none set yet. */);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_GET_OUTPUT, i, paParms);
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForOutput(pEvent, uHandle, uTimeoutMS,
+ pvData, cbData, pcbRead);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current (overall) status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param procStatus Guest process status to set.
+ * @param vrcProc Guest process result code to set.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_setProcessStatus(ProcessStatus_T procStatus, int vrcProc)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, vrcProc=%Rrc\n", mData.mStatus, procStatus, vrcProc));
+
+ if (procStatus == ProcessStatus_Error)
+ {
+ AssertMsg(RT_FAILURE(vrcProc), ("Guest vrcProc must be an error (%Rrc)\n", vrcProc));
+ /* 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 vrcProc already set (to %Rrc)\n", mData.mLastError));
+ }
+ else
+ AssertMsg(RT_SUCCESS(vrcProc), ("Guest vrcProc must not be an error (%Rrc)\n", vrcProc));
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != procStatus) /* Was there a process status change? */
+ {
+ mData.mStatus = procStatus;
+ mData.mLastError = vrcProc;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ ComAssertComRC(hrc);
+ if (RT_FAILURE(mData.mLastError))
+ {
+ hrc = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError,
+ COM_IIDOF(IGuestProcess), getComponentName(),
+ i_guestErrorToString(mData.mLastError, mData.mProcess.mExecutable.c_str()));
+ ComAssertComRC(hrc);
+ }
+
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessStateChangedEvent(mEventSource, mSession, this, uPID, procStatus, errorInfo);
+#if 0
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that outstanding
+ * requests will be delivered to the host after the process has ended,
+ * so just cancel all waiting events here to not let clients run
+ * into timeouts.
+ */
+ if ( mSession->getProtocolVersion() < 2
+ && hasEnded())
+ {
+ LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n"));
+ vrc = cancelWaitEvents();
+ }
+#endif
+ }
+
+ return vrc;
+}
+
+/**
+ * Starts the process on the guest.
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_startProcess(uint32_t cMsTimeout, int *pvrcGuest)
+{
+ 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, pvrcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Helper function to start a process on the guest. Do not call directly!
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param rLock Write lock to use for serialization.
+ * @param pEvent Event to use for notifying waiters.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestProcess::i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *pvrcGuest)
+{
+ GuestSession *pSession = mSession;
+ AssertPtr(pSession);
+ uint32_t const uProtocol = pSession->i_getProtocolVersion();
+
+ const GuestCredentials &sessionCreds = pSession->i_getCredentials();
+
+ /* Prepare arguments. */
+ size_t cArgs = mData.mProcess.mArguments.size();
+ if (cArgs >= 128*1024)
+ return VERR_BUFFER_OVERFLOW;
+
+ size_t cbArgs = 0;
+ char *pszArgs = NULL;
+ int vrc = VINF_SUCCESS;
+ if (cArgs)
+ {
+ char const **papszArgv = (char const **)RTMemAlloc((cArgs + 1) * sizeof(papszArgv[0]));
+ AssertReturn(papszArgv, VERR_NO_MEMORY);
+
+ for (size_t i = 0; i < cArgs; i++)
+ {
+ papszArgv[i] = mData.mProcess.mArguments[i].c_str();
+ AssertPtr(papszArgv[i]);
+ }
+ papszArgv[cArgs] = NULL;
+
+ Guest *pGuest = mSession->i_getParent();
+ AssertPtr(pGuest);
+
+ const uint64_t fGuestControlFeatures0 = pGuest->i_getGuestControlFeatures0();
+
+ /* If the Guest Additions don't support using argv[0] correctly (< 6.1.x), don't supply it. */
+ if (!(fGuestControlFeatures0 & VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0))
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ else /* ... else send the whole argv, including argv[0]. */
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+
+ RTMemFree(papszArgv);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Note! No direct returns after this. */
+ }
+
+ /* Calculate arguments size (in bytes). */
+ AssertPtr(pszArgs);
+ cbArgs = strlen(pszArgs) + 1; /* Include terminating zero. */
+
+ /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */
+ size_t cbEnvBlock = 0; /* Shut up MSVC. */
+ char *pszzEnvBlock = NULL; /* Ditto. */
+ vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(cbEnvBlock > 0);
+ cbEnvBlock--;
+ AssertPtr(pszzEnvBlock);
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[16];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetRTCStr(&paParms[i++], mData.mProcess.mExecutable);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mFlags);
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)mData.mProcess.mArguments.size());
+ HGCMSvcSetPv(&paParms[i++], pszArgs, (uint32_t)cbArgs);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mEnvironmentChanges.count());
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)cbEnvBlock);
+ HGCMSvcSetPv(&paParms[i++], pszzEnvBlock, (uint32_t)cbEnvBlock);
+ if (uProtocol < 2)
+ {
+ /* In protocol v1 (VBox < 4.3) the credentials were part of the execution
+ * call. In newer protocols these credentials are part of the opened guest
+ * session, so not needed anymore here. */
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mUser);
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mPassword);
+ }
+ /*
+ * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout
+ * until the process was started - the process itself then gets an infinite timeout for execution.
+ * This is handy when we want to start a process inside a worker thread within a certain timeout
+ * but let the started process perform lengthly operations then.
+ */
+ if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ HGCMSvcSetU32(&paParms[i++], UINT32_MAX /* Infinite timeout */);
+ else
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mTimeoutMS);
+ if (uProtocol >= 2)
+ {
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mPriority);
+ /* CPU affinity: We only support one CPU affinity block at the moment,
+ * so that makes up to 64 CPUs total. This can be more in the future. */
+ HGCMSvcSetU32(&paParms[i++], 1);
+ /* The actual CPU affinity blocks. */
+ HGCMSvcSetPv(&paParms[i++], (void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));
+ }
+
+ rLock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_CMD, i, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = i_setProcessStatus(ProcessStatus_Error, vrc);
+ AssertRC(vrc2);
+ }
+
+ mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock);
+ }
+
+ RTStrFree(pszArgs);
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, cMsTimeout, NULL /* Process status */, pvrcGuest);
+ return vrc;
+}
+
+/**
+ * Starts the process asynchronously (via worker thread) on the guest.
+ *
+ * @returns VBox status code.
+ */
+int GuestProcess::i_startProcessAsync(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Create the task: */
+ GuestProcessStartTask *pTask = NULL;
+ try
+ {
+ pTask = new GuestProcessStartTask(this);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogFlowThisFunc(("out of memory\n"));
+ return VERR_NO_MEMORY;
+ }
+ AssertReturnStmt(pTask->i_isOk(), delete pTask, E_FAIL); /* cannot fail for GuestProcessStartTask. */
+ LogFlowThisFunc(("Successfully created GuestProcessStartTask object\n"));
+
+ /* Start the thread (always consumes the task): */
+ HRESULT hrc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+ LogFlowThisFunc(("Failed to create thread for GuestProcessStartTask\n"));
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Thread task which does the asynchronous starting of a guest process.
+ *
+ * @returns VBox status code.
+ * @param pTask Process start task (context) to process.
+ */
+/* static */
+int GuestProcess::i_startProcessThreadTask(GuestProcessStartTask *pTask)
+{
+ LogFlowFunc(("pTask=%p\n", pTask));
+
+ const ComObjPtr<GuestProcess> pProcess(pTask->i_process());
+ Assert(!pProcess.isNull());
+
+ AutoCaller autoCaller(pProcess);
+ if (FAILED(autoCaller.hrc()))
+ return VERR_COM_UNEXPECTED;
+
+ int vrc = pProcess->i_startProcess(30 * 1000 /* 30s timeout */, NULL /* pvrcGuest, ignored */);
+ /* Nothing to do here anymore. */
+
+ LogFlowFunc(("pProcess=%p, vrc=%Rrc\n", (GuestProcess *)pProcess, vrc));
+ return vrc;
+}
+
+/**
+ * Terminates a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VWRN_INVALID_STATE if process not in running state (anymore).
+ * @retval VERR_NOT_SUPPORTED if process termination is not supported on the guest.
+ * @param uTimeoutMS Timeout (in ms) to wait for process termination.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_terminateProcess(uint32_t uTimeoutMS, int *pvrcGuest)
+{
+ LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n",
+ mData.mStatus));
+ vrc = VWRN_INVALID_STATE;
+ }
+ else
+ {
+ AssertPtr(mSession);
+ /* Note: VBox < 4.3 (aka protocol version 1) does not
+ * support this, so just skip. */
+ if (mSession->i_getProtocolVersion() < 2)
+ vrc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_TERMINATE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, uTimeoutMS,
+ NULL /* ProcessStatus */, pvrcGuest);
+ unregisterWaitEvent(pEvent);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts given process status / flags and wait flag combination
+ * to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ * @param oldStatus Old process status to use for conversion.
+ * @param newStatus New process status to use for conversion.
+ * @param uProcFlags Process flags to use for conversion.
+ * @param uProtocol Guest Control protocol version to use for conversion.
+ */
+/* static */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResultEx(uint32_t fWaitFlags,
+ ProcessStatus_T oldStatus, ProcessStatus_T newStatus,
+ uint32_t uProcFlags, uint32_t uProtocol)
+{
+ ProcessWaitResult_T waitResult = ProcessWaitResult_None;
+
+ switch (newStatus)
+ {
+ case ProcessStatus_TerminatedNormally:
+ case ProcessStatus_TerminatedSignal:
+ case ProcessStatus_TerminatedAbnormally:
+ case ProcessStatus_Down:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Terminate;
+ break;
+
+ case ProcessStatus_TimedOutKilled:
+ case ProcessStatus_TimedOutAbnormally:
+ /* Dito. */
+ waitResult = ProcessWaitResult_Timeout;
+ break;
+
+ case ProcessStatus_Started:
+ switch (oldStatus)
+ {
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ /* Also wait for process start. */
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ else
+ {
+ /*
+ * If ProcessCreateFlag_WaitForProcessStartOnly was specified on process creation the
+ * caller is not interested in getting further process statuses -- so just don't notify
+ * anything here anymore and return.
+ */
+ if (uProcFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ waitResult = ProcessWaitResult_Start;
+ }
+ break;
+
+ case ProcessStatus_Started:
+ /* Only wait for process start. */
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled old status %RU32 before new status 'started'\n",
+ oldStatus));
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+ }
+ break;
+
+ case ProcessStatus_Error:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Error;
+ break;
+
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ case ProcessStatus_Terminating:
+ case ProcessStatus_Paused:
+ /* No result available yet, leave wait
+ * flags untouched. */
+ break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case ProcessStatus_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+
+ if (newStatus == ProcessStatus_Started)
+ {
+ /*
+ * Filter out waits which are *not* supported using
+ * older guest control Guest Additions.
+ *
+ */
+ /** @todo ProcessWaitForFlag_Std* flags are not implemented yet. */
+ if (uProtocol < 99) /* See @todo above. */
+ {
+ if ( waitResult == ProcessWaitResult_None
+ /* We don't support waiting for stdin, out + err,
+ * just skip waiting then. */
+ && ( (fWaitFlags & ProcessWaitForFlag_StdIn)
+ || (fWaitFlags & ProcessWaitForFlag_StdOut)
+ || (fWaitFlags & ProcessWaitForFlag_StdErr)
+ )
+ )
+ {
+ /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
+ waitResult = ProcessWaitResult_WaitFlagNotSupported;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n",
+ oldStatus, newStatus, fWaitFlags, waitResult));
+#endif
+ return waitResult;
+}
+
+/**
+ * Converts given wait flags to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags)
+{
+ AssertPtr(mSession);
+ return GuestProcess::i_waitFlagsToResultEx(fWaitFlags,
+ mData.mStatus /* oldStatus */, mData.mStatus /* newStatus */,
+ mData.mProcess.mFlags, mSession->i_getProtocolVersion());
+}
+
+/**
+ * Waits for certain events of the guest process.
+ *
+ * @returns VBox status code.
+ * @param fWaitFlags Process wait flags to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param waitResult Where to return the process wait result on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ * @note Takes the read lock.
+ */
+int GuestProcess::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, ProcessWaitResult_T &waitResult, int *pvrcGuest)
+{
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, vrcProc=%Rrc, pvrcGuest=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, pvrcGuest));
+
+ /* 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 vrc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
+ if (pvrcGuest)
+ *pvrcGuest = mData.mLastError; /* Return last set error. */
+ LogFlowThisFunc(("Process is in error state (vrcGuest=%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 (pvrcGuest)
+ *pvrcGuest = mData.mLastError; /* Return last set error (if any). */
+ LogFlowThisFunc(("Nothing to wait for (vrcGuest=%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, pvrcGuest);
+ 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, vrc=%Rrc\n", waitResult, newStatus, vrc));
+ return vrc;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pInputStatus Where to return the process input status on success.
+ * @param pcbProcessed Where to return the processed input (in bytes) on success.
+ */
+int GuestProcess::i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed)
+{
+ RT_NOREF(uHandle);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessInputNotify)
+ {
+ ComPtr<IGuestProcessInputNotifyEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ if (pInputStatus)
+ {
+ HRESULT hrc2 = pProcessEvent->COMGETTER(Status)(pInputStatus);
+ ComAssertComRC(hrc2);
+ }
+ if (pcbProcessed)
+ {
+ HRESULT hrc2 = pProcessEvent->COMGETTER(Processed)((ULONG*)pcbProcessed);
+ ComAssertComRC(hrc2);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, vrc=%Rrc\n", pEvent, uHandle, vrc));
+ return vrc;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @returns VBox status code.
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the guest process output on success.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param pcbRead Where to return the size (in bytes) read.
+ */
+int GuestProcess::i_waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pvData is optional. */
+ /* cbData is optional. */
+ /* pcbRead is optional. */
+
+ LogFlowThisFunc(("cEventTypes=%zu, pEvent=%p, uHandle=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu, pcbRead=%p\n",
+ pEvent->TypeCount(), pEvent, uHandle, uTimeoutMS, pvData, cbData, pcbRead));
+
+ int vrc;
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ do
+ {
+ vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessOutput)
+ {
+ ComPtr<IGuestProcessOutputEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ ULONG uHandleEvent;
+ HRESULT hrc = pProcessEvent->COMGETTER(Handle)(&uHandleEvent);
+ if ( SUCCEEDED(hrc)
+ && uHandleEvent == uHandle)
+ {
+ if (pvData)
+ {
+ com::SafeArray <BYTE> data;
+ hrc = pProcessEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
+ ComAssertComRC(hrc);
+ 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), vrc=%Rrc\n", cbRead, uHandleEvent, vrc));
+ }
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && pcbRead)
+ {
+ ULONG cbRead;
+ hrc = pProcessEvent->COMGETTER(Processed)(&cbRead);
+ ComAssertComRC(hrc);
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ break;
+ }
+ else if (FAILED(hrc))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
+
+ } while (vrc == VINF_SUCCESS);
+
+ if ( vrc != VINF_SUCCESS
+ && pcbRead)
+ {
+ *pcbRead = 0;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process status change.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pProcessStatus Where to return the process status on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned.
+ */
+int GuestProcess::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ ProcessStatus_T *pProcessStatus, int *pvrcGuest)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pProcessStatus is optional. */
+ /* pvrcGuest 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 hrc = pProcessEvent->COMGETTER(Status)(&procStatus);
+ ComAssertComRC(hrc);
+ if (pProcessStatus)
+ *pProcessStatus = procStatus;
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ hrc = pProcessEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hrc);
+
+ LONG lGuestRc;
+ hrc = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hrc);
+
+ LogFlowThisFunc(("Got procStatus=%RU32, vrcGuest=%RI32 (%Rrc)\n", procStatus, lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (pvrcGuest)
+ *pvrcGuest = (int)lGuestRc;
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make pvrcGuest is set. */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !pvrcGuest || *pvrcGuest != (int)0xcccccccc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#if 0 /* Unused */
+/* static */
+bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult, ProcessStatus_T procStatus, uint32_t uProtocol)
+{
+ RT_NOREF(uProtocol);
+
+ bool fImplies;
+
+ switch (waitResult)
+ {
+ case ProcessWaitResult_Start:
+ fImplies = procStatus == ProcessStatus_Started;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fImplies = ( procStatus == ProcessStatus_TerminatedNormally
+ || procStatus == ProcessStatus_TerminatedSignal
+ || procStatus == ProcessStatus_TerminatedAbnormally
+ || procStatus == ProcessStatus_TimedOutKilled
+ || procStatus == ProcessStatus_TimedOutAbnormally
+ || procStatus == ProcessStatus_Down
+ || procStatus == ProcessStatus_Error);
+ break;
+
+ default:
+ fImplies = false;
+ break;
+ }
+
+ return fImplies;
+}
+#endif /* unused */
+
+/**
+ * Writes input data to a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Guest process file handle to write to.
+ * @param uFlags Input flags of type PRocessInputFlag_XXX.
+ * @param pvData Data to write to the guest process.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puWritten Where to return the size (in bytes) written. Optional.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_writeData(uint32_t uHandle, uint32_t uFlags,
+ void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *pvrcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, pvrcGuest=%p\n",
+ mData.mPID, uHandle, uFlags, pvData, cbData, uTimeoutMS, puWritten, pvrcGuest));
+ /* 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 (pvrcGuest)
+ *pvrcGuest = 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 vrcGuest. */
+
+ if (puWritten)
+ *puWritten = cbProcessed;
+ }
+ /** @todo Error handling. */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowThisFunc(("Returning cbProcessed=%RU32, vrc=%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.hrc())) return autoCaller.hrc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read is zero"));
+
+ LogFlowThisFuncEnter();
+
+ aData.resize(aToRead);
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbRead;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Reading %RU32 bytes from guest process handle %RU32 failed: %s", "", aToRead),
+ aToRead, aHandle, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("vrc=%Rrc, cbRead=%RU32\n", vrc, cbRead));
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::terminate()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */, &vrcGuest);
+
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ /* Nothing to do here, all good. */
+ break;
+
+ case VWRN_INVALID_STATE:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, VWRN_INVALID_STATE,
+ tr("Guest process is not in '%s' state anymore (current is in '%s')"),
+ GuestProcess::i_statusToString(ProcessStatus_Started).c_str(),
+ GuestProcess::i_statusToString(i_getStatus()).c_str());
+ break;
+ }
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Terminating guest process failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Terminating guest process \"%s\" (PID %RU32) not supported by installed Guest Additions"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID);
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Terminating guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+
+ /* Note: Also could be VWRN_INVALID_STATE from i_terminateProcess().
+ * In such a case we have to keep the process in our list in order to fullfill any upcoming responses / requests. */
+ if (vrc == VINF_SUCCESS)
+ {
+ /* Remove process from guest session list. Now only API clients
+ * still can hold references to it. */
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_processUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ /* Validate flags: */
+ static ULONG const s_fValidFlags = ProcessWaitForFlag_None | ProcessWaitForFlag_Start | ProcessWaitForFlag_Terminate
+ | ProcessWaitForFlag_StdIn | ProcessWaitForFlag_StdOut | ProcessWaitForFlag_StdErr;
+ if (aWaitFor & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aWaitFor, aWaitFor & ~s_fValidFlags);
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ ProcessWaitResult_T waitResult;
+ int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aReason = waitResult;
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Waiting for guest process (flags %#x) failed: %s"),
+ aWaitFor, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_TIMEOUT:
+ *aReason = ProcessWaitResult_Timeout;
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor,
+ ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ uint32_t fWaitFor = ProcessWaitForFlag_None;
+ for (size_t i = 0; i < aWaitFor.size(); i++)
+ fWaitFor |= aWaitFor[i];
+
+ return WaitFor(fWaitFor, aTimeoutMS, aReason);
+}
+
+HRESULT GuestProcess::write(ULONG aHandle, ULONG aFlags, const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS, ULONG *aWritten)
+{
+ static ULONG const s_fValidFlags = ProcessInputFlag_None | ProcessInputFlag_EndOfFile;
+ if (aFlags & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aFlags, aFlags & ~s_fValidFlags);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbWritten;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ uint32_t cbData = (uint32_t)aData.size();
+ void *pvData = cbData > 0 ? (void *)&aData.front() : NULL;
+ int vrc = i_writeData(aHandle, aFlags, pvData, cbData, aTimeoutMS, &cbWritten, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Writing %RU32 bytes (flags %#x) to guest process failed: %s", "", cbData),
+ cbData, aFlags, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing to guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("vrc=%Rrc, aWritten=%RU32\n", vrc, cbWritten));
+
+ *aWritten = (ULONG)cbWritten;
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::writeArray(ULONG aHandle, const std::vector<ProcessInputFlag_T> &aFlags,
+ const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ LogFlowThisFuncEnter();
+
+ ULONG fWrite = ProcessInputFlag_None;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fWrite |= aFlags[i];
+
+ return write(aHandle, fWrite, aData, aTimeoutMS, aWritten);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GuestProcessTool::GuestProcessTool(void)
+ : pSession(NULL),
+ pProcess(NULL)
+{
+}
+
+GuestProcessTool::~GuestProcessTool(void)
+{
+ uninit();
+}
+
+/**
+ * Initializes and starts a process tool on the guest.
+ *
+ * @returns VBox status code.
+ * @param pGuestSession Guest session the process tools should be started in.
+ * @param startupInfo Guest process startup info to use for starting.
+ * @param fAsync Whether to start asynchronously or not.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestProcessTool::init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
+ bool fAsync, int *pvrcGuest)
+{
+ LogFlowThisFunc(("pGuestSession=%p, exe=%s, fAsync=%RTbool\n",
+ pGuestSession, startupInfo.mExecutable.c_str(), fAsync));
+
+ AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
+ Assert(startupInfo.mArguments[0] == startupInfo.mExecutable);
+
+ pSession = pGuestSession;
+ mStartupInfo = startupInfo;
+
+ /* Make sure the process is hidden. */
+ mStartupInfo.mFlags |= ProcessCreateFlag_Hidden;
+
+ int vrc = pSession->i_processCreateEx(mStartupInfo, pProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest = VINF_SUCCESS;
+ vrc = fAsync
+ ? pProcess->i_startProcessAsync()
+ : pProcess->i_startProcess(30 * 1000 /* 30s timeout */, &vrcGuest);
+
+ if ( RT_SUCCESS(vrc)
+ && !fAsync
+ && RT_FAILURE(vrcGuest)
+ )
+ {
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unitializes a guest process tool by terminating it on the guest.
+ */
+void GuestProcessTool::uninit(void)
+{
+ /* Make sure the process is terminated and unregistered from the guest session. */
+ int vrcGuestIgnored;
+ terminate(30 * 1000 /* 30s timeout */, &vrcGuestIgnored);
+
+ /* Unregister the process from the process (and the session's object) list. */
+ if ( pSession
+ && pProcess)
+ pSession->i_processUnregister(pProcess);
+
+ /* Release references. */
+ pProcess.setNull();
+ pSession.setNull();
+}
+
+/**
+ * Gets the current guest process stream block.
+ *
+ * @returns VBox status code.
+ * @retval VINF_EOF if the stream reached its end.
+ * @param uHandle Guest process file handle to get current block for.
+ * @param strmBlock Where to return the stream block on success.
+ */
+int GuestProcessTool::getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock)
+{
+ GuestProcessStream *pStream = NULL;
+ if (uHandle == GUEST_PROC_OUT_H_STDOUT)
+ pStream = &mStdOut;
+ else if (uHandle == GUEST_PROC_OUT_H_STDERR)
+ pStream = &mStdErr;
+
+ if (!pStream)
+ return VERR_INVALID_PARAMETER;
+
+ int vrc = pStream->ParseBlock(strmBlock);
+
+ LogFlowThisFunc(("vrc=%Rrc, currently %RU64 pairs\n", vrc, strmBlock.GetCount()));
+ return vrc;
+}
+
+/**
+ * Returns the result code from an ended guest process tool.
+ *
+ * @returns Result code from guest process tool.
+ */
+int GuestProcessTool::getRc(void) const
+{
+ LONG exitCode = -1;
+ HRESULT hrc = pProcess->COMGETTER(ExitCode(&exitCode));
+ AssertComRC(hrc);
+
+ return GuestProcessTool::exitCodeToRc(mStartupInfo, exitCode);
+}
+
+/**
+ * Returns whether a guest process tool is still running or not.
+ *
+ * @returns \c true if running, or \c false if not.
+ */
+bool GuestProcessTool::isRunning(void)
+{
+ AssertReturn(!pProcess.isNull(), false);
+
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ HRESULT hrc = pProcess->COMGETTER(Status(&procStatus));
+ AssertComRC(hrc);
+
+ if ( procStatus == ProcessStatus_Started
+ || procStatus == ProcessStatus_Paused
+ || procStatus == ProcessStatus_Terminating)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the tool has been run correctly or not, based on it's internal process
+ * status and reported exit status.
+ *
+ * @return @c true if the tool has been run correctly (exit status 0), or @c false if some error
+ * occurred (exit status <> 0 or wrong process state).
+ */
+bool GuestProcessTool::isTerminatedOk(void)
+{
+ return getTerminationStatus() == VINF_SUCCESS ? true : false;
+}
+
+/**
+ * Static helper function to start and wait for a certain toolbox tool.
+ *
+ * This function most likely is the one you want to use in the first place if you
+ * want to just use a toolbox tool and wait for its result. See runEx() if you also
+ * needs its output.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param pvrcGuest Where to store the toolbox tool's specific error code in case
+ * VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+/* static */
+int GuestProcessTool::run( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = runErrorInfo(pGuestSession, startupInfo, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.vrcGuest))
+ {
+ if (errorInfo.vrcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.vrcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.vrcGuest, 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,
+ GuestProcessStartupInfo const &startupInfo,
+ GuestProcessToolErrorInfo &errorInfo)
+{
+ return runExErrorInfo(pGuestSession, startupInfo, NULL /* paStrmOutObjects */, 0 /* cStrmOutObjects */, errorInfo);
+}
+
+/**
+ * Static helper function to start and wait for output of a certain toolbox tool.
+ *
+ * @return IPRT status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
+ * Optional.
+ * @param cStrmOutObjects Number of stream objects passed in. Optional.
+ * @param pvrcGuest Error code returned from the guest side if VERR_GSTCTL_GUEST_ERROR is returned. Optional.
+ */
+/* static */
+int GuestProcessTool::runEx(GuestSession *pGuestSession,
+ GuestProcessStartupInfo const &startupInfo,
+ GuestCtrlStreamObjects *paStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = GuestProcessTool::runExErrorInfo(pGuestSession, startupInfo, paStrmOutObjects, cStrmOutObjects, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.vrcGuest))
+ {
+ if (errorInfo.vrcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.vrcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.vrcGuest, 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,
+ GuestProcessStartupInfo const &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.vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ while (cStrmOutObjects--)
+ {
+ try
+ {
+ GuestProcessStreamBlock strmBlk;
+ vrc = procTool.waitEx( paStrmOutObjects
+ ? GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK
+ : GUESTPROCESSTOOL_WAIT_FLAG_NONE, &strmBlk, &errorInfo.vrcGuest);
+ if (paStrmOutObjects)
+ paStrmOutObjects->push_back(strmBlk);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure the process runs until completion. */
+ vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &errorInfo.vrcGuest);
+ if (RT_SUCCESS(vrc))
+ errorInfo.vrcGuest = procTool.getTerminationStatus(&errorInfo.iExitCode);
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.vrcGuest, 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 hrc = pProcess->COMGETTER(ExitCode(&iExitCode));
+ AssertComRC(hrc);
+
+ if (piExitCode)
+ *piExitCode = iExitCode;
+
+ vrc = iExitCode != 0 ? VERR_GSTCTL_PROCESS_EXIT_CODE : VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_GSTCTL_PROCESS_WRONG_STATE;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestProcessTool::wait(uint32_t fToolWaitFlags, int *pvrcGuest)
+{
+ return waitEx(fToolWaitFlags, NULL /* pStrmBlkOut */, pvrcGuest);
+}
+
+/**
+ * Waits for a guest process tool, also returning process output.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param pStrmBlkOut Where to store the guest process output.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestProcessTool::waitEx(uint32_t fToolWaitFlags, GuestProcessStreamBlock *pStrmBlkOut, int *pvrcGuest)
+{
+ LogFlowThisFunc(("fToolWaitFlags=0x%x, pStreamBlock=%p, pvrcGuest=%p\n", fToolWaitFlags, pStrmBlkOut, pvrcGuest));
+
+ int vrc;
+
+ /* Is the next block complete without waiting for new data from the guest? */
+ if (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK)
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut);
+ if ( RT_SUCCESS(vrc)
+ && pStrmBlkOut->IsComplete())
+ 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 vrc. */
+ break;
+
+ case ProcessWaitResult_Error:
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fDone = true;
+ break;
+
+ case ProcessWaitResult_Timeout:
+ vrc = VERR_TIMEOUT;
+ break;
+
+ case ProcessWaitResult_Start:
+ case ProcessWaitResult_Status:
+ /* Not used here, just skip. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled process wait result %RU32\n", waitRes));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ if (fHandleStdOut)
+ {
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
+ vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDOUT, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead));
+ vrc = mStdOut.AddData(byBuf, cbRead);
+
+ if ( RT_SUCCESS(vrc)
+ && (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK))
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut);
+
+ /* When successful, break out of the loop because we're done
+ * with reading the first stream block. */
+ if (RT_SUCCESS(vrc))
+ fDone = true;
+ }
+ }
+
+ fHandleStdOut = false;
+ }
+
+ if (fHandleStdErr)
+ {
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
+ vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDERR, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead));
+ vrc = mStdErr.AddData(byBuf, cbRead);
+ }
+
+ fHandleStdErr = false;
+ }
+
+ } while (!fDone && RT_SUCCESS(vrc));
+
+#undef UPDATE_AND_CHECK_ELAPSED_TIME
+#undef GET_REMAINING_TIME
+
+ if (RT_FAILURE(vrcGuest))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ LogFlowThisFunc(("Loop ended with vrc=%Rrc, vrcGuest=%Rrc, waitRes=%RU32\n", vrc, vrcGuest, waitRes));
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Terminates a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestProcessTool::terminate(uint32_t uTimeoutMS, int *pvrcGuest)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ if (!pProcess.isNull())
+ vrc = pProcess->i_terminateProcess(uTimeoutMS, pvrcGuest);
+ else
+ vrc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param startupInfo Startup info of the toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const GuestProcessStartupInfo &startupInfo, int32_t iExitCode)
+{
+ if (startupInfo.mArguments.size() == 0)
+ {
+ AssertFailed();
+ return VERR_GENERAL_FAILURE; /* Should not happen. */
+ }
+
+ return exitCodeToRc(startupInfo.mArguments[0].c_str(), iExitCode);
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param pszTool Name of toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const char *pszTool, int32_t iExitCode)
+{
+ AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("%s: %d\n", pszTool, iExitCode));
+
+ if (iExitCode == 0) /* No error? Bail out early. */
+ return VINF_SUCCESS;
+
+ if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_CAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION: return VERR_SHARING_VIOLATION;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY: return VERR_IS_A_DIRECTORY;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_LS))
+ {
+ switch (iExitCode)
+ {
+ /** @todo Handle access denied? */
+ case RTEXITCODE_FAILURE: return VERR_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_STAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND: return VERR_NET_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKDIR))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKTEMP))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_RM))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_FILE_NOT_FOUND;
+ /** @todo RTPathRmCmd does not yet distinguish between not found and access denied yet. */
+ default: break;
+ }
+ }
+
+ LogFunc(("Warning: Exit code %d not handled for tool '%s', returning VERR_GENERAL_FAILURE\n", iExitCode, pszTool));
+
+ if (iExitCode == RTEXITCODE_SYNTAX)
+ return VERR_INTERNAL_ERROR_5;
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Returns a stringyfied error of a guest process tool error.
+ *
+ * @returns Stringyfied error.
+ * @param pszTool Toolbox tool name to get stringyfied error for.
+ * @param guestErrorInfo Guest error info to get stringyfied error for.
+ */
+/* static */
+Utf8Str GuestProcessTool::guestErrorToString(const char *pszTool, const GuestErrorInfo &guestErrorInfo)
+{
+ Utf8Str strErr;
+
+ /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
+ switch (guestErrorInfo.getVrc())
+ {
+ case VERR_ACCESS_DENIED:
+ strErr.printf(tr("Access to \"%s\" denied"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
+ RT_FALL_THROUGH();
+ case VERR_PATH_NOT_FOUND:
+ strErr.printf(tr("No such file or directory \"%s\""), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_VM_HANDLE:
+ strErr.printf(tr("VMM device is not available (is the VM running?)"));
+ break;
+
+ case VERR_HGCM_SERVICE_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not available"));
+ break;
+
+ case VERR_BAD_EXE_FORMAT:
+ strErr.printf(tr("The file \"%s\" is not an executable format"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_AUTHENTICATION_FAILURE:
+ strErr.printf(tr("The user \"%s\" was not able to logon"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_NAME:
+ strErr.printf(tr("The file \"%s\" is an invalid name"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_TIMEOUT:
+ strErr.printf(tr("The guest did not respond within time"));
+ break;
+
+ case VERR_CANCELLED:
+ strErr.printf(tr("The execution operation was canceled"));
+ break;
+
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
+ strErr.printf(tr("Maximum number of concurrent guest processes has been reached"));
+ break;
+
+ case VERR_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not ready (yet)"));
+ break;
+
+ default:
+ strErr.printf(tr("Unhandled error %Rrc for \"%s\" occurred for tool \"%s\" on guest -- please file a bug report"),
+ guestErrorInfo.getVrc(), guestErrorInfo.getWhat().c_str(), pszTool);
+ break;
+ }
+
+ return strErr;
+}
+
diff --git a/src/VBox/Main/src-client/GuestSessionImpl.cpp b/src/VBox/Main/src-client/GuestSessionImpl.cpp
new file mode 100644
index 00000000..dd758cac
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestSessionImpl.cpp
@@ -0,0 +1,4809 @@
+/* $Id: GuestSessionImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest session handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#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)
+ , mVrc(VINF_SUCCESS) { }
+
+ virtual ~GuestSessionTaskInternal(void) { }
+
+ /** Returns the last set result code. */
+ int vrc(void) const { return mVrc; }
+ /** Returns whether the last set result code indicates success or not. */
+ bool isOk(void) const { return RT_SUCCESS(mVrc); }
+ /** Returns the task's guest session object. */
+ const ComObjPtr<GuestSession> &Session(void) const { return mSession; }
+
+protected:
+
+ /** Guest session the task belongs to. */
+ const ComObjPtr<GuestSession> mSession;
+ /** The last set VBox status code. */
+ int mVrc;
+};
+
+/**
+ * Class for asynchronously starting a guest session.
+ */
+class GuestSessionTaskInternalStart : public GuestSessionTaskInternal
+{
+public:
+
+ GuestSessionTaskInternalStart(GuestSession *pSession)
+ : GuestSessionTaskInternal(pSession)
+ {
+ m_strTaskName = "gctlSesStart";
+ }
+
+ void handler()
+ {
+ /* Ignore return code */
+ 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 vrc2 = mSession->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef DEBUG_andy
+ LogFlowFunc(("Signalling events of type=%RU32, session=%p resulted in vrc2=%Rrc\n", aType, mSession, vrc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ GuestSession *mSession;
+};
+typedef ListenerImpl<GuestSessionListener, GuestSession*> GuestSessionListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestSessionListenerImpl)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestSession)
+
+HRESULT GuestSession::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestSession::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes a guest session but does *not* open in on the guest side
+ * yet. This needs to be done via the openSession() / openSessionAsync calls.
+ *
+ * @returns VBox status code.
+ * @param pGuest Guest object the guest session belongs to.
+ * @param ssInfo Guest session startup info to use.
+ * @param guestCreds Guest credentials to use for starting a guest session
+ * with a specific guest account.
+ */
+int GuestSession::init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo,
+ const GuestCredentials &guestCreds)
+{
+ LogFlowThisFunc(("pGuest=%p, ssInfo=%p, guestCreds=%p\n",
+ pGuest, &ssInfo, &guestCreds));
+
+ /* Enclose the state transition NotReady->InInit->Ready. */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
+
+ AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
+
+ /*
+ * Initialize our data members from the input.
+ */
+ mParent = pGuest;
+
+ /* Copy over startup info. */
+ /** @todo Use an overloaded copy operator. Later. */
+ mData.mSession.mID = ssInfo.mID;
+ mData.mSession.mIsInternal = ssInfo.mIsInternal;
+ mData.mSession.mName = ssInfo.mName;
+ mData.mSession.mOpenFlags = ssInfo.mOpenFlags;
+ mData.mSession.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS;
+
+ /* Copy over session credentials. */
+ /** @todo Use an overloaded copy operator. Later. */
+ mData.mCredentials.mUser = guestCreds.mUser;
+ mData.mCredentials.mPassword = guestCreds.mPassword;
+ mData.mCredentials.mDomain = guestCreds.mDomain;
+
+ /* Initialize the remainder of the data. */
+ mData.mVrc = 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 vrc = i_objectRegister(NULL /* pObject */, SESSIONOBJECTTYPE_SESSION, &mData.mObjectID);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = mData.mEnvironmentChanges.initChangeRecord(pGuest->i_isGuestInWindowsNtFamily()
+ ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCritSectInit(&mWaitEventCritSect);
+ AssertRC(vrc);
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_determineProtocolVersion();
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * <Replace this if you figure out what the code is doing.>
+ */
+ HRESULT hrc = unconst(mEventSource).createObject();
+ if (SUCCEEDED(hrc))
+ hrc = mEventSource->init();
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ GuestSessionListener *pListener = new GuestSessionListener();
+ ComObjPtr<GuestSessionListenerImpl> thisListener;
+ hrc = thisListener.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = thisListener->init(pListener, this); /* thisListener takes ownership of pListener. */
+ if (SUCCEEDED(hrc))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+ hrc = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hrc))
+ {
+ mLocalListener = thisListener;
+
+ /*
+ * Mark this object as operational and return success.
+ */
+ autoInitSpan.setSucceeded();
+ LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool vrc=VINF_SUCCESS\n",
+ mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ vrc = Global::vboxStatusCodeFromCOM(hrc);
+ }
+
+ autoInitSpan.setFailed();
+ LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => vrc=%Rrc\n",
+ mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, vrc));
+ return vrc;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void GuestSession::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFuncEnter();
+
+ /* Call i_onRemove to take care of the object cleanups. */
+ i_onRemove();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Unregister the session's object ID. */
+ i_objectUnregister(mData.mObjectID);
+
+ Assert(mData.mObjects.size () == 0);
+ mData.mObjects.clear();
+
+ mData.mEnvironmentChanges.reset();
+
+ if (mData.mpBaseEnvironment)
+ {
+ mData.mpBaseEnvironment->releaseConst();
+ mData.mpBaseEnvironment = NULL;
+ }
+
+ /* Unitialize our local listener. */
+ mLocalListener.setNull();
+
+ baseUninit();
+
+ LogFlowFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestSession::getUser(com::Utf8Str &aUser)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aUser = mData.mCredentials.mUser;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getDomain(com::Utf8Str &aDomain)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDomain = mData.mCredentials.mDomain;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.mSession.mName;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getId(ULONG *aId)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aId = mData.mSession.mID;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getStatus(GuestSessionStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aStatus = mData.mStatus;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getTimeout(ULONG *aTimeout)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeout = mData.mTimeout;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::setTimeout(ULONG aTimeout)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.mTimeout = aTimeout;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getProtocolVersion(ULONG *aProtocolVersion)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aProtocolVersion = mData.mProtocolVersion;
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestSession::getEnvironmentChanges(std::vector<com::Utf8Str> &aEnvironmentChanges)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return Global::vboxStatusCodeToCOM(vrc);
+}
+
+HRESULT GuestSession::setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ size_t idxError = ~(size_t)0;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mEnvironmentChanges.reset();
+ vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges, &idxError);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+ if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ return setError(E_INVALIDARG, tr("Invalid environment variable name '%s', index %zu"),
+ aEnvironmentChanges[idxError].c_str(), idxError);
+ return setErrorBoth(Global::vboxStatusCodeToCOM(vrc), vrc, tr("Failed to apply '%s', index %zu (%Rrc)"),
+ aEnvironmentChanges[idxError].c_str(), idxError, vrc);
+}
+
+HRESULT GuestSession::getEnvironmentBase(std::vector<com::Utf8Str> &aEnvironmentBase)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ int vrc = mData.mpBaseEnvironment->queryPutEnvArray(&aEnvironmentBase);
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::getProcesses(std::vector<ComPtr<IGuestProcess> > &aProcesses)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aProcesses.resize(mData.mProcesses.size());
+ size_t i = 0;
+ for (SessionProcesses::iterator it = mData.mProcesses.begin();
+ it != mData.mProcesses.end();
+ ++it, ++i)
+ {
+ it->second.queryInterfaceTo(aProcesses[i].asOutParam());
+ }
+
+ LogFlowFunc(("mProcesses=%zu\n", aProcesses.size()));
+ return S_OK;
+}
+
+HRESULT GuestSession::getPathStyle(PathStyle_T *aPathStyle)
+{
+ *aPathStyle = i_getGuestPathStyle();
+ return S_OK;
+}
+
+HRESULT GuestSession::getCurrentDirectory(com::Utf8Str &aCurrentDirectory)
+{
+ RT_NOREF(aCurrentDirectory);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::setCurrentDirectory(const com::Utf8Str &aCurrentDirectory)
+{
+ RT_NOREF(aCurrentDirectory);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::getUserHome(com::Utf8Str &aUserHome)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathUserHome(aUserHome, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Getting the user's home path is not supported by installed Guest Additions"));
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Getting the user's home path failed on the guest: %Rrc"), vrcGuest);
+ break;
+ }
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's home path failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::getUserDocuments(com::Utf8Str &aUserDocuments)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathUserDocuments(aUserDocuments, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Getting the user's documents path is not supported by installed Guest Additions"));
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Getting the user's documents path failed on the guest: %Rrc"), vrcGuest);
+ break;
+ }
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's documents path failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::getDirectories(std::vector<ComPtr<IGuestDirectory> > &aDirectories)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aDirectories.resize(mData.mDirectories.size());
+ size_t i = 0;
+ for (SessionDirectories::iterator it = mData.mDirectories.begin(); it != mData.mDirectories.end(); ++it, ++i)
+ {
+ it->second.queryInterfaceTo(aDirectories[i].asOutParam());
+ }
+
+ LogFlowFunc(("mDirectories=%zu\n", aDirectories.size()));
+ return S_OK;
+}
+
+HRESULT GuestSession::getFiles(std::vector<ComPtr<IGuestFile> > &aFiles)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aFiles.resize(mData.mFiles.size());
+ size_t i = 0;
+ for(SessionFiles::iterator it = mData.mFiles.begin(); it != mData.mFiles.end(); ++it, ++i)
+ it->second.queryInterfaceTo(aFiles[i].asOutParam());
+
+ LogFlowFunc(("mDirectories=%zu\n", aFiles.size()));
+
+ return S_OK;
+}
+
+HRESULT GuestSession::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+// private methods
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Closes a guest session on the guest.
+ *
+ * @returns VBox status code.
+ * @param uFlags Guest session close flags.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *pvrcGuest)
+{
+ AssertPtrReturn(pvrcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("uFlags=%x, uTimeoutMS=%RU32\n", uFlags, uTimeoutMS));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Guest Additions < 4.3 don't support closing dedicated
+ guest sessions, skip. */
+ if (mData.mProtocolVersion < 2)
+ {
+ LogFlowThisFunc(("Installed Guest Additions don't support closing dedicated sessions, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ /** @todo uFlags validation. */
+
+ if (mData.mStatus != GuestSessionStatus_Started)
+ {
+ LogFlowThisFunc(("Session ID=%RU32 not started (anymore), status now is: %RU32\n",
+ mData.mSession.mID, mData.mStatus));
+ return VINF_SUCCESS;
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ LogFlowThisFunc(("Sending closing request to guest session ID=%RU32, uFlags=%x\n",
+ mData.mSession.mID, uFlags));
+
+ alock.release();
+
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+
+ vrc = i_sendMessage(HOST_MSG_SESSION_CLOSE, i, paParms, VBOX_GUESTCTRL_DST_BOTH);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Terminate, uTimeoutMS,
+ NULL /* Session status */, pvrcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Internal worker function for public APIs that handle copying elements from
+ * guest to the host.
+ *
+ * @return HRESULT
+ * @param SourceSet Source set specifying what to copy.
+ * @param strDestination Destination path on the host. Host path style.
+ * @param pProgress Progress object returned to the caller.
+ */
+HRESULT GuestSession::i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet,
+ const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* Validate stuff. */
+ if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */
+ return setError(E_INVALIDARG, tr("No source(s) specified"));
+ if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No destination specified"));
+
+ GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin();
+ while (itSrc != SourceSet.end())
+ {
+ LogRel2(("Guest Control: Copying '%s' from guest to '%s' on the host (type: %s, filter: %s)\n",
+ itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str()));
+ ++itSrc;
+ }
+
+ /* Create a task and return the progress obejct for it. */
+ GuestSessionTaskCopyFrom *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination);
+ }
+ catch (std::bad_alloc &)
+ {
+ return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyFrom object"));
+ }
+
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str()));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject();
+
+ /* Kick off the worker thread. Note! Consumes pTask. */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for copying from guest to the host failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyFrom object failed"));
+ delete pTask;
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Internal worker function for public APIs that handle copying elements from
+ * host to the guest.
+ *
+ * @return HRESULT
+ * @param SourceSet Source set specifying what to copy.
+ * @param strDestination Destination path on the guest. Guest path style.
+ * @param pProgress Progress object returned to the caller.
+ */
+HRESULT GuestSession::i_copyToGuest(const GuestSessionFsSourceSet &SourceSet,
+ const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin();
+ while (itSrc != SourceSet.end())
+ {
+ LogRel2(("Guest Control: Copying '%s' from host to '%s' on the guest (type: %s, filter: %s)\n",
+ itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str()));
+ ++itSrc;
+ }
+
+ /* Create a task and return the progress object for it. */
+ GuestSessionTaskCopyTo *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination);
+ }
+ catch (std::bad_alloc &)
+ {
+ return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyTo object"));
+ }
+
+ try
+ {
+ hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str()));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<Progress> ptrProgressObj = pTask->GetProgressObject();
+
+ /* Kick off the worker thread. Note! Consumes pTask. */
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setError(hrc, tr("Starting thread for copying from host to the guest failed"));
+ }
+ else
+ {
+ hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyTo object failed"));
+ delete pTask;
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hrc));
+ return hrc;
+}
+
+/**
+ * Validates and extracts directory copy flags from a comma-separated string.
+ *
+ * @return COM status, error set on failure
+ * @param strFlags String to extract flags from.
+ * @param fStrict Whether to set an error when an unknown / invalid flag is detected.
+ * @param pfFlags Where to store the extracted (and validated) flags.
+ */
+HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, DirectoryCopyFlag_T *pfFlags)
+{
+ unsigned fFlags = DirectoryCopyFlag_None;
+
+ /* Validate and set flags. */
+ if (strFlags.isNotEmpty())
+ {
+ const char *pszNext = strFlags.c_str();
+ for (;;)
+ {
+ /* Find the next keyword, ignoring all whitespace. */
+ pszNext = RTStrStripL(pszNext);
+
+ const char * const pszComma = strchr(pszNext, ',');
+ size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
+ while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
+ cchKeyword--;
+
+ if (cchKeyword > 0)
+ {
+ /* Convert keyword to flag. */
+#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
+ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
+ if (MATCH_KEYWORD("CopyIntoExisting"))
+ fFlags |= (unsigned)DirectoryCopyFlag_CopyIntoExisting;
+ else if (MATCH_KEYWORD("Recursive"))
+ fFlags |= (unsigned)DirectoryCopyFlag_Recursive;
+ else if (MATCH_KEYWORD("FollowLinks"))
+ fFlags |= (unsigned)DirectoryCopyFlag_FollowLinks;
+ else if (fStrict)
+ return setError(E_INVALIDARG, tr("Invalid directory copy flag: %.*s"), (int)cchKeyword, pszNext);
+#undef MATCH_KEYWORD
+ }
+ if (!pszComma)
+ break;
+ pszNext = pszComma + 1;
+ }
+ }
+
+ if (pfFlags)
+ *pfFlags = (DirectoryCopyFlag_T)fFlags;
+ return S_OK;
+}
+
+/**
+ * Creates a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @param strPath Path on guest to directory to create.
+ * @param uMode Creation mode to use (octal, 0777 max).
+ * @param uFlags Directory creation flags to use.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestSession::i_directoryCreate(const Utf8Str &strPath, uint32_t uMode, uint32_t uFlags, int *pvrcGuest)
+{
+ AssertPtrReturn(pvrcGuest, 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, pvrcGuest);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Checks if a directory on the guest exists.
+ *
+ * @returns \c true if directory exists on the guest, \c false if not.
+ * @param strPath Path of directory to check.
+ */
+bool GuestSession::i_directoryExists(const Utf8Str &strPath)
+{
+ GuestFsObjData objDataIgnored;
+ int vrcGuestIgnored;
+ int const vrc = i_directoryQueryInfo(strPath, true /* fFollowSymlinks */, objDataIgnored, &vrcGuestIgnored);
+ return RT_SUCCESS(vrc);
+}
+
+/**
+ * Checks if a directory object exists and optionally returns its object.
+ *
+ * @returns \c true if directory object exists, or \c false if not.
+ * @param uDirID ID of directory object to check.
+ * @param pDir Where to return the found directory object on success.
+ */
+inline bool GuestSession::i_directoryExists(uint32_t uDirID, ComObjPtr<GuestDirectory> *pDir)
+{
+ SessionDirectories::const_iterator it = mData.mDirectories.find(uDirID);
+ if (it != mData.mDirectories.end())
+ {
+ if (pDir)
+ *pDir = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Queries information about a directory on the guest.
+ *
+ * @returns VBox status code, or VERR_NOT_A_DIRECTORY if the file system object exists but is not a directory.
+ * @param strPath Path to directory to query information for.
+ * @param fFollowSymlinks Whether to follow symlinks or not.
+ * @param objData Where to store the information returned on success.
+ * @param pvrcGuest Guest VBox status code, when returning
+ * VERR_GSTCTL_GUEST_ERROR.
+ */
+int GuestSession::i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *pvrcGuest)
+{
+ AssertPtrReturn(pvrcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
+
+ int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, pvrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = objData.mType == FsObjType_Directory
+ ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unregisters a directory object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore).
+ * @param pDirectory Directory object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_directoryUnregister(GuestDirectory *pDirectory)
+{
+ AssertPtrReturn(pDirectory, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pDirectory=%p\n", pDirectory));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pDirectory->getObjectID();
+
+ LogFlowFunc(("Removing directory (objectID=%RU32) ...\n", idObject));
+
+ int vrc = i_objectUnregister(idObject);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ 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()));
+
+ vrc = pDirConsumed->i_onUnregister();
+ AssertRCReturn(vrc, vrc);
+
+ mData.mDirectories.erase(itDirs);
+
+ alock.release(); /* Release lock before firing off event. */
+
+// ::FireGuestDirectoryRegisteredEvent(mEventSource, this /* Session */, pDirConsumed, false /* Process unregistered */);
+
+ pDirConsumed.setNull();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Removes a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @param strPath Path of directory on guest to remove.
+ * @param fFlags Directory remove flags to use.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t fFlags, int *pvrcGuest)
+{
+ AssertReturn(!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvrcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), fFlags));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)strPath.c_str(),
+ (ULONG)strPath.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], fFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_DIR_REMOVE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a temporary directory / file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strTemplate Name template to use.
+ * \sa RTDirCreateTemp / RTDirCreateTempSecure.
+ * @param strPath Path where to create the temporary directory / file.
+ * @param fDirectory Whether to create a temporary directory or file.
+ * @param strName Where to return the created temporary name on success.
+ * @param fMode File mode to use for creation (octal, umask-style).
+ * Ignored when \a fSecure is specified.
+ * @param fSecure Whether to perform a secure creation or not.
+ * @param pvrcGuest Guest VBox status code, when returning
+ * VERR_GSTCTL_GUEST_ERROR.
+ */
+int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName,
+ uint32_t fMode, bool fSecure, int *pvrcGuest)
+{
+ AssertPtrReturn(pvrcGuest, VERR_INVALID_POINTER);
+ AssertReturn(fSecure || !(fMode & ~07777), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool, fMode=%o, fSecure=%RTbool\n",
+ strTemplate.c_str(), strPath.c_str(), fDirectory, fMode, fSecure));
+
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ try
+ {
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKTEMP);
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ if (fDirectory)
+ procInfo.mArguments.push_back(Utf8Str("-d"));
+ if (strPath.length()) /* Otherwise use /tmp or equivalent. */
+ {
+ procInfo.mArguments.push_back(Utf8Str("-t"));
+ procInfo.mArguments.push_back(strPath);
+ }
+ /* Note: Secure flag and mode cannot be specified at the same time. */
+ if (fSecure)
+ {
+ procInfo.mArguments.push_back(Utf8Str("--secure"));
+ }
+ else
+ {
+ procInfo.mArguments.push_back(Utf8Str("--mode"));
+
+ /* Note: Pass the mode unmodified down to the guest. See @ticketref{21394}. */
+ char szMode[16];
+ int vrc2 = RTStrPrintf2(szMode, sizeof(szMode), "%d", fMode);
+ AssertRCReturn(vrc2, vrc2);
+ procInfo.mArguments.push_back(szMode);
+ }
+ procInfo.mArguments.push_back("--"); /* strTemplate could be '--help'. */
+ procInfo.mArguments.push_back(strTemplate);
+ }
+ catch (std::bad_alloc &)
+ {
+ Log(("Out of memory!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ /** @todo Use an internal HGCM command for this operation, since
+ * we now can run in a user-dedicated session. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ GuestCtrlStreamObjects stdOut;
+ int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest);
+ if (!GuestProcess::i_isGuestError(vrc))
+ {
+ GuestFsObjData objData;
+ if (!stdOut.empty())
+ {
+ vrc = objData.FromMkTemp(stdOut.at(0));
+ if (RT_FAILURE(vrc))
+ {
+ vrcGuest = vrc;
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+
+ if (RT_SUCCESS(vrc))
+ strName = objData.mName;
+ }
+ else if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Open a directory on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param openInfo Open information to use.
+ * @param pDirectory Where to return the guest directory object on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_directoryOpen(const GuestDirectoryOpenInfo &openInfo, ComObjPtr<GuestDirectory> &pDirectory, int *pvrcGuest)
+{
+ AssertPtrReturn(pvrcGuest, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("strPath=%s, strPath=%s, uFlags=%x\n", openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Create the directory object. */
+ HRESULT hrc = pDirectory.createObject();
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int vrc = i_objectRegister(pDirectory, SESSIONOBJECTTYPE_DIRECTORY, &idObject);
+ if (RT_FAILURE(vrc))
+ {
+ pDirectory.setNull();
+ return vrc;
+ }
+
+ /* We need to release the write lock first before initializing the directory object below,
+ * as we're starting a guest process as part of it. This in turn will try to acquire the session's
+ * write lock. */
+ alock.release();
+
+ Console *pConsole = mParent->i_getConsole();
+ AssertPtr(pConsole);
+
+ vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo);
+ if (RT_FAILURE(vrc))
+ {
+ /* Make sure to acquire the write lock again before unregistering the object. */
+ alock.acquire();
+
+ int vrc2 = i_objectUnregister(idObject);
+ AssertRC(vrc2);
+
+ pDirectory.setNull();
+ }
+ else
+ {
+ /* Make sure to acquire the write lock again before continuing. */
+ alock.acquire();
+
+ try
+ {
+ /* Add the created directory to our map. */
+ mData.mDirectories[idObject] = pDirectory;
+
+ LogFlowFunc(("Added new guest directory \"%s\" (Session: %RU32) (now total %zu directories)\n",
+ openInfo.mPath.c_str(), mData.mSession.mID, mData.mDirectories.size()));
+
+ alock.release(); /* Release lock before firing off event. */
+
+ /** @todo Fire off a VBoxEventType_OnGuestDirectoryRegistered event? */
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Nothing further to do here yet. */
+ if (pvrcGuest)
+ *pvrcGuest = VINF_SUCCESS;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Dispatches a host callback to its corresponding object.
+ *
+ * @return VBox status code. VERR_NOT_FOUND if no corresponding object was found.
+ * @param pCtxCb Host callback context.
+ * @param pSvcCb Service callback data.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
+
+ AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Find the object.
+ */
+ int vrc = VERR_NOT_FOUND;
+ const uint32_t idObject = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCtxCb->uContextID);
+ SessionObjects::const_iterator itObj = mData.mObjects.find(idObject);
+ if (itObj != mData.mObjects.end())
+ {
+ /* Set protocol version so that pSvcCb can be interpreted right. */
+ pCtxCb->uProtocol = mData.mProtocolVersion;
+
+ switch (itObj->second.enmType)
+ {
+ /* Note: The session object is special, as it does not inherit from GuestObject we could call
+ * its dispatcher for -- so treat this separately and call it directly. */
+ case SESSIONOBJECTTYPE_SESSION:
+ {
+ alock.release();
+
+ vrc = i_dispatchToThis(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_DIRECTORY:
+ {
+ ComObjPtr<GuestDirectory> pObj((GuestDirectory *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ vrc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_FILE:
+ {
+ ComObjPtr<GuestFile> pObj((GuestFile *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ vrc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ case SESSIONOBJECTTYPE_PROCESS:
+ {
+ ComObjPtr<GuestProcess> pObj((GuestProcess *)itObj->second.pObject);
+ AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER);
+
+ alock.release();
+
+ vrc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb);
+ break;
+ }
+ default:
+ AssertMsgFailed(("%d\n", itObj->second.enmType));
+ vrc = VERR_INTERNAL_ERROR_4;
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main handler for guest session messages from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context from HGCM service.
+ * @param pSvcCbData HGCM service callback data.
+ *
+ * @note No locking!
+ */
+int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
+ mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCbData));
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ /** @todo Handle closing all guest objects. */
+ vrc = VERR_INTERNAL_ERROR;
+ break;
+
+ case GUEST_MSG_SESSION_NOTIFY: /* Guest Additions >= 4.3.0. */
+ vrc = i_onSessionStatusChange(pCbCtx, pSvcCbData);
+ break;
+
+ default:
+ vrc = dispatchGeneric(pCbCtx, pSvcCbData);
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Validates and extracts file copy flags from a comma-separated string.
+ *
+ * @return COM status, error set on failure
+ * @param strFlags String to extract flags from.
+ * @param fStrict Whether to set an error when an unknown / invalid flag is detected.
+ * @param pfFlags Where to store the extracted (and validated) flags.
+ */
+HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, FileCopyFlag_T *pfFlags)
+{
+ unsigned fFlags = (unsigned)FileCopyFlag_None;
+
+ /* Validate and set flags. */
+ if (strFlags.isNotEmpty())
+ {
+ const char *pszNext = strFlags.c_str();
+ for (;;)
+ {
+ /* Find the next keyword, ignoring all whitespace. */
+ pszNext = RTStrStripL(pszNext);
+
+ const char * const pszComma = strchr(pszNext, ',');
+ size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
+ while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
+ cchKeyword--;
+
+ if (cchKeyword > 0)
+ {
+ /* Convert keyword to flag. */
+#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
+ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
+ if (MATCH_KEYWORD("NoReplace"))
+ fFlags |= (unsigned)FileCopyFlag_NoReplace;
+ else if (MATCH_KEYWORD("FollowLinks"))
+ fFlags |= (unsigned)FileCopyFlag_FollowLinks;
+ else if (MATCH_KEYWORD("Update"))
+ fFlags |= (unsigned)FileCopyFlag_Update;
+ else if (fStrict)
+ return setError(E_INVALIDARG, tr("Invalid file copy flag: %.*s"), (int)cchKeyword, pszNext);
+#undef MATCH_KEYWORD
+ }
+ if (!pszComma)
+ break;
+ pszNext = pszComma + 1;
+ }
+ }
+
+ if (pfFlags)
+ *pfFlags = (FileCopyFlag_T)fFlags;
+ return S_OK;
+}
+
+/**
+ * Checks if a file object exists and optionally returns its object.
+ *
+ * @returns \c true if file object exists, or \c false if not.
+ * @param uFileID ID of file object to check.
+ * @param pFile Where to return the found file object on success.
+ */
+inline bool GuestSession::i_fileExists(uint32_t uFileID, ComObjPtr<GuestFile> *pFile)
+{
+ SessionFiles::const_iterator it = mData.mFiles.find(uFileID);
+ if (it != mData.mFiles.end())
+ {
+ if (pFile)
+ *pFile = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Unregisters a file object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the file is not registered (anymore).
+ * @param pFile File object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileUnregister(GuestFile *pFile)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pFile=%p\n", pFile));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pFile->getObjectID();
+
+ LogFlowFunc(("Removing file (objectID=%RU32) ...\n", idObject));
+
+ int vrc = i_objectUnregister(idObject);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ 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()));
+
+ vrc = pFileConsumed->i_onUnregister();
+ AssertRCReturn(vrc, vrc);
+
+ mData.mFiles.erase(itFiles);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestFileRegisteredEvent(mEventSource, this, pFileConsumed, false /* Unregistered */);
+
+ pFileConsumed.setNull();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Removes a file from the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strPath Path of file on guest to remove.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestSession::i_fileRemove(const Utf8Str &strPath, int *pvrcGuest)
+{
+ LogFlowThisFunc(("strPath=%s\n", strPath.c_str()));
+
+ GuestProcessStartupInfo procInfo;
+ GuestProcessStream streamOut;
+
+ procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
+ procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_RM);
+
+ try
+ {
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
+ procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */
+ procInfo.mArguments.push_back(strPath); /* The file we want to remove. */
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ GuestCtrlStreamObjects stdOut;
+ int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest);
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ if (!stdOut.empty())
+ {
+ GuestFsObjData objData;
+ vrc = objData.FromRm(stdOut.at(0));
+ if (RT_FAILURE(vrc))
+ {
+ vrcGuest = vrc;
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+ }
+ else if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Opens a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param aPath File path on guest to open.
+ * @param aAccessMode Access mode to use.
+ * @param aOpenAction Open action to use.
+ * @param aSharingMode Sharing mode to use.
+ * @param aCreationMode Creation mode to use.
+ * @param aFlags Open flags to use.
+ * @param pFile Where to return the file object on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector<FileOpenExFlag_T> &aFlags,
+ ComObjPtr<GuestFile> &pFile, int *pvrcGuest)
+{
+ GuestFileOpenInfo openInfo;
+ openInfo.mFilename = aPath;
+ openInfo.mCreationMode = aCreationMode;
+ openInfo.mAccessMode = aAccessMode;
+ openInfo.mOpenAction = aOpenAction;
+ openInfo.mSharingMode = aSharingMode;
+
+ /* Combine and validate flags. */
+ for (size_t i = 0; i < aFlags.size(); i++)
+ openInfo.mfOpenEx |= aFlags[i];
+ /* Validation is done in i_fileOpen(). */
+
+ return i_fileOpen(openInfo, pFile, pvrcGuest);
+}
+
+/**
+ * Opens a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param openInfo Open information to use for opening the file.
+ * @param pFile Where to return the file object on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr<GuestFile> &pFile, int *pvrcGuest)
+{
+ 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 (pvrcGuest)
+ *pvrcGuest = VERR_NOT_SUPPORTED;
+ return VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ if (!openInfo.IsValid())
+ return VERR_INVALID_PARAMETER;
+
+ /* Create the directory object. */
+ HRESULT hrc = pFile.createObject();
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int vrc = i_objectRegister(pFile, SESSIONOBJECTTYPE_FILE, &idObject);
+ if (RT_FAILURE(vrc))
+ {
+ pFile.setNull();
+ return vrc;
+ }
+
+ Console *pConsole = mParent->i_getConsole();
+ AssertPtr(pConsole);
+
+ vrc = pFile->init(pConsole, this /* GuestSession */, 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 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 &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &vrcGuest);
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Queries information from a file on the guest.
+ *
+ * @returns IPRT status code. VERR_NOT_A_FILE if the queried file system object on the guest is not a file,
+ * or VERR_GSTCTL_GUEST_ERROR if pvrcGuest contains
+ * more error information from the guest.
+ * @param strPath Absolute path of file to query information for.
+ * @param fFollowSymlinks Whether or not to follow symbolic links on the guest.
+ * @param objData Where to store the acquired information.
+ * @param pvrcGuest Where to store the guest VBox status code.
+ * Optional.
+ */
+int GuestSession::i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *pvrcGuest)
+{
+ LogFlowThisFunc(("strPath=%s fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
+
+ int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, pvrcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = objData.mType == FsObjType_File ? VINF_SUCCESS : VERR_NOT_A_FILE;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Queries the size of a file on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_
+ * @param strPath Path of file on guest to query size for.
+ * @param fFollowSymlinks \c true when wanting to follow symbolic links, \c false if not.
+ * @param pllSize Where to return the queried file size on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestSession::i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *pvrcGuest)
+{
+ AssertPtrReturn(pllSize, VERR_INVALID_POINTER);
+
+ GuestFsObjData objData;
+ int vrc = i_fileQueryInfo(strPath, fFollowSymlinks, objData, pvrcGuest);
+ 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 pvrcGuest Guest VBox status code, 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 *pvrcGuest)
+{
+ 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 (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+ else
+ vrc = VERR_BROKEN_PIPE;
+ }
+ else if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+ return vrc;
+}
+
+/**
+ * Returns the guest credentials of a guest session.
+ *
+ * @returns Guest credentials.
+ */
+const GuestCredentials& GuestSession::i_getCredentials(void)
+{
+ return mData.mCredentials;
+}
+
+/**
+ * Returns the guest session (friendly) name.
+ *
+ * @returns Guest session name.
+ */
+Utf8Str GuestSession::i_getName(void)
+{
+ return mData.mSession.mName;
+}
+
+/**
+ * Returns a stringified error description for a given guest result code.
+ *
+ * @returns Stringified error description.
+ */
+/* static */
+Utf8Str GuestSession::i_guestErrorToString(int vrcGuest)
+{
+ Utf8Str strError;
+
+ /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
+ switch (vrcGuest)
+ {
+ case VERR_INVALID_VM_HANDLE:
+ strError.printf(tr("VMM device is not available (is the VM running?)"));
+ break;
+
+ case VERR_HGCM_SERVICE_NOT_FOUND:
+ strError.printf(tr("The guest execution service is not available"));
+ break;
+
+ case VERR_ACCOUNT_RESTRICTED:
+ strError.printf(tr("The specified user account on the guest is restricted and can't be used to logon"));
+ break;
+
+ case VERR_AUTHENTICATION_FAILURE:
+ strError.printf(tr("The specified user was not able to logon on guest"));
+ break;
+
+ case VERR_TIMEOUT:
+ strError.printf(tr("The guest did not respond within time"));
+ break;
+
+ case VERR_CANCELLED:
+ strError.printf(tr("The session operation was canceled"));
+ break;
+
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
+ strError.printf(tr("Maximum number of concurrent guest processes has been reached"));
+ break;
+
+ case VERR_NOT_FOUND:
+ strError.printf(tr("The guest execution service is not ready (yet)"));
+ break;
+
+ default:
+ strError.printf("%Rrc", vrcGuest);
+ break;
+ }
+
+ return strError;
+}
+
+/**
+ * Returns whether the session is in a started state or not.
+ *
+ * @returns \c true if in a started state, or \c false if not.
+ */
+bool GuestSession::i_isStarted(void) const
+{
+ return (mData.mStatus == GuestSessionStatus_Started);
+}
+
+/**
+ * Checks if this session is ready state where it can handle
+ * all session-bound actions (like guest processes, guest files).
+ * Only used by official API methods. Will set an external
+ * error when not ready.
+ */
+HRESULT GuestSession::i_isStartedExternal(void)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Be a bit more informative. */
+ if (!i_isStarted())
+ return setError(E_UNEXPECTED, tr("Session is not in started state"));
+
+ return S_OK;
+}
+
+/**
+ * Returns whether a guest session status implies a terminated state or not.
+ *
+ * @returns \c true if it's a terminated state, or \c false if not.
+ */
+/* static */
+bool GuestSession::i_isTerminated(GuestSessionStatus_T enmStatus)
+{
+ switch (enmStatus)
+ {
+ case GuestSessionStatus_Terminated:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutKilled:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutAbnormally:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Down:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Error:
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the session is in a terminated state or not.
+ *
+ * @returns \c true if in a terminated state, or \c false if not.
+ */
+bool GuestSession::i_isTerminated(void) const
+{
+ return GuestSession::i_isTerminated(mData.mStatus);
+}
+
+/**
+ * Called by IGuest right before this session gets removed from
+ * the public session list.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_onRemove(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = i_objectsUnregister();
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit.
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Handles guest session status changes from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context from HGCM service.
+ * @param pSvcCbData HGCM service callback data.
+ *
+ * @note Takes the read lock (for session ID lookup).
+ */
+int GuestSession::i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ /* pCallback is optional. */
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 3)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_SESSION_NOTIFY dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uType);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uResult);
+ AssertRCReturn(vrc, vrc);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("ID=%RU32, uType=%RU32, vrcGuest=%Rrc\n", mData.mSession.mID, dataCb.uType, dataCb.uResult));
+
+ GuestSessionStatus_T sessionStatus = GuestSessionStatus_Undefined;
+
+ int vrcGuest = dataCb.uResult; /** @todo uint32_t vs. int. */
+ switch (dataCb.uType)
+ {
+ case GUEST_SESSION_NOTIFYTYPE_ERROR:
+ sessionStatus = GuestSessionStatus_Error;
+ LogRel(("Guest Control: Error starting Session '%s' (%Rrc) \n", mData.mSession.mName.c_str(), vrcGuest));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_STARTED:
+ sessionStatus = GuestSessionStatus_Started;
+#if 0 /** @todo If we get some environment stuff along with this kind notification: */
+ const char *pszzEnvBlock = ...;
+ uint32_t cbEnvBlock = ...;
+ if (!mData.mpBaseEnvironment)
+ {
+ GuestEnvironment *pBaseEnv;
+ try { pBaseEnv = new GuestEnvironment(); } catch (std::bad_alloc &) { pBaseEnv = NULL; }
+ if (pBaseEnv)
+ {
+ int vrc = pBaseEnv->initNormal(Guest.i_isGuestInWindowsNtFamily() ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0);
+ if (RT_SUCCESS(vrc))
+ vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock);
+ if (RT_SUCCESS(vrc))
+ mData.mpBaseEnvironment = pBaseEnv;
+ else
+ pBaseEnv->release();
+ }
+ }
+#endif
+ LogRel(("Guest Control: Session '%s' was successfully started\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TEN:
+ LogRel(("Guest Control: Session '%s' was terminated normally with exit code %#x\n",
+ mData.mSession.mName.c_str(), dataCb.uResult));
+ sessionStatus = GuestSessionStatus_Terminated;
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TEA:
+ LogRel(("Guest Control: Session '%s' was terminated abnormally\n", mData.mSession.mName.c_str()));
+ sessionStatus = GuestSessionStatus_Terminated;
+ /* dataCb.uResult is undefined. */
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TES:
+ LogRel(("Guest Control: Session '%s' was terminated via signal %#x\n", mData.mSession.mName.c_str(), dataCb.uResult));
+ sessionStatus = GuestSessionStatus_Terminated;
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TOK:
+ sessionStatus = GuestSessionStatus_TimedOutKilled;
+ LogRel(("Guest Control: Session '%s' timed out and was killed\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_TOA:
+ sessionStatus = GuestSessionStatus_TimedOutAbnormally;
+ LogRel(("Guest Control: Session '%s' timed out and was not killed successfully\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_DWN:
+ sessionStatus = GuestSessionStatus_Down;
+ LogRel(("Guest Control: Session '%s' got killed as guest service/OS is down\n", mData.mSession.mName.c_str()));
+ break;
+
+ case GUEST_SESSION_NOTIFYTYPE_UNDEFINED:
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ /* Leave the lock, as i_setSessionStatus() below will require a write lock for actually
+ * committing the session state. */
+ alock.release();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_FAILURE(vrcGuest))
+ sessionStatus = GuestSessionStatus_Error;
+ }
+
+ /* Set the session status. */
+ if (RT_SUCCESS(vrc))
+ vrc = i_setSessionStatus(sessionStatus, vrcGuest);
+
+ LogFlowThisFunc(("ID=%RU32, vrcGuest=%Rrc\n", mData.mSession.mID, vrcGuest));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the path separation style used on the guest.
+ *
+ * @returns Separation style used on the guest.
+ */
+PathStyle_T GuestSession::i_getGuestPathStyle(void)
+{
+ PathStyle_T enmPathStyle;
+
+ VBOXOSTYPE enmOsType = mParent->i_getGuestOSType();
+ if (enmOsType < VBOXOSTYPE_DOS)
+ {
+ LogFlowFunc(("returns PathStyle_Unknown\n"));
+ enmPathStyle = PathStyle_Unknown;
+ }
+ else if (enmOsType < VBOXOSTYPE_Linux)
+ {
+ LogFlowFunc(("returns PathStyle_DOS\n"));
+ enmPathStyle = PathStyle_DOS;
+ }
+ else
+ {
+ LogFlowFunc(("returns PathStyle_UNIX\n"));
+ enmPathStyle = PathStyle_UNIX;
+ }
+
+ return enmPathStyle;
+}
+
+/**
+ * Returns the path separation style used on the host.
+ *
+ * @returns Separation style used on the host.
+ */
+/* static */
+PathStyle_T GuestSession::i_getHostPathStyle(void)
+{
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ return PathStyle_DOS;
+#else
+ return PathStyle_UNIX;
+#endif
+}
+
+/**
+ * Starts the guest session on the guest.
+ *
+ * @returns VBox status code.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the read and write locks.
+ */
+int GuestSession::i_startSession(int *pvrcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mID=%RU32, mName=%s, uProtocolVersion=%RU32, openFlags=%x, openTimeoutMS=%RU32\n",
+ mData.mSession.mID, mData.mSession.mName.c_str(), mData.mProtocolVersion,
+ mData.mSession.mOpenFlags, mData.mSession.mOpenTimeoutMS));
+
+ /* Guest Additions < 4.3 don't support opening dedicated
+ guest sessions. Simply return success here. */
+ if (mData.mProtocolVersion < 2)
+ {
+ alock.release(); /* Release lock before changing status. */
+
+ i_setSessionStatus(GuestSessionStatus_Started, VINF_SUCCESS); /* ignore return code*/
+ LogFlowThisFunc(("Installed Guest Additions don't support opening dedicated sessions, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ if (mData.mStatus != GuestSessionStatus_Undefined)
+ return VINF_SUCCESS;
+
+ /** @todo mData.mSession.uFlags validation. */
+
+ alock.release(); /* Release lock before changing status. */
+
+ /* Set current session status. */
+ int vrc = i_setSessionStatus(GuestSessionStatus_Starting, VINF_SUCCESS);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ alock.acquire(); /* Re-acquire lock before accessing session attributes below. */
+
+ VBOXHGCMSVCPARM paParms[8];
+
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mProtocolVersion);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mUser.c_str(),
+ (ULONG)mData.mCredentials.mUser.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mPassword.c_str(),
+ (ULONG)mData.mCredentials.mPassword.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mDomain.c_str(),
+ (ULONG)mData.mCredentials.mDomain.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], mData.mSession.mOpenFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_SESSION_CREATE, i, paParms, VBOX_GUESTCTRL_DST_ROOT_SVC);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Start,
+ 30 * 1000 /* 30s timeout */,
+ NULL /* Session status */, pvrcGuest);
+ }
+ 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.
+ */
+ i_setSessionStatus(GuestSessionStatus_Error, vrc); /* ignore return code */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Starts the guest session asynchronously in a separate worker thread.
+ *
+ * @returns IPRT status code.
+ */
+int GuestSession::i_startSessionAsync(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Create task: */
+ GuestSessionTaskInternalStart *pTask = NULL;
+ try
+ {
+ pTask = new GuestSessionTaskInternalStart(this);
+ }
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+ if (pTask->isOk())
+ {
+ /* Kick off the thread: */
+ HRESULT hrc = pTask->createThread();
+ pTask = NULL; /* Not valid anymore, not even on failure! */
+ if (SUCCEEDED(hrc))
+ {
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+ }
+ LogFlow(("GuestSession: Failed to create thread for GuestSessionTaskInternalOpen task.\n"));
+ }
+ else
+ LogFlow(("GuestSession: GuestSessionTaskInternalStart creation failed: %Rhrc.\n", pTask->vrc()));
+ LogFlowFuncLeaveRC(VERR_GENERAL_FAILURE);
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Static function to start a guest session asynchronously.
+ *
+ * @returns IPRT status code.
+ * @param pTask Task object to use for starting the guest session.
+ */
+/* static */
+int GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask)
+{
+ LogFlowFunc(("pTask=%p\n", pTask));
+ AssertPtr(pTask);
+
+ const ComObjPtr<GuestSession> pSession(pTask->Session());
+ Assert(!pSession.isNull());
+
+ AutoCaller autoCaller(pSession);
+ if (FAILED(autoCaller.hrc()))
+ return VERR_COM_INVALID_OBJECT_STATE;
+
+ int vrc = pSession->i_startSession(NULL /*pvrcGuest*/);
+ /* Nothing to do here anymore. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Registers an object with the session, i.e. allocates an object ID.
+ *
+ * @return VBox status code.
+ * @retval VERR_GSTCTL_MAX_OBJECTS_REACHED if the maximum of concurrent objects
+ * is reached.
+ * @param pObject Guest object to register (weak pointer). Optional.
+ * @param enmType Session object type to register.
+ * @param pidObject Where to return the object ID on success. Optional.
+ */
+int GuestSession::i_objectRegister(GuestObject *pObject, SESSIONOBJECTTYPE enmType, uint32_t *pidObject)
+{
+ /* pObject can be NULL. */
+ /* pidObject is optional. */
+
+ /*
+ * Pick a random bit as starting point. If it's in use, search forward
+ * for a free one, wrapping around. We've reserved both the zero'th and
+ * max-1 IDs (see Data constructor).
+ */
+ uint32_t idObject = RTRandU32Ex(1, VBOX_GUESTCTRL_MAX_OBJECTS - 2);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject))
+ { /* likely */ }
+ else if (mData.mObjects.size() < VBOX_GUESTCTRL_MAX_OBJECTS - 2 /* First and last are not used */)
+ {
+ /* Forward search. */
+ int iHit = ASMBitNextClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS, idObject);
+ if (iHit < 0)
+ iHit = ASMBitFirstClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS);
+ AssertLogRelMsgReturn(iHit >= 0, ("object count: %#zu\n", mData.mObjects.size()), VERR_GSTCTL_MAX_CID_OBJECTS_REACHED);
+ idObject = iHit;
+ AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2);
+ }
+ else
+ {
+ LogFunc(("Maximum number of objects reached (enmType=%RU32, %zu objects)\n", enmType, mData.mObjects.size()));
+ return VERR_GSTCTL_MAX_CID_OBJECTS_REACHED;
+ }
+
+ Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size()));
+
+ try
+ {
+ mData.mObjects[idObject].pObject = pObject; /* Can be NULL. */
+ mData.mObjects[idObject].enmType = enmType;
+ mData.mObjects[idObject].msBirth = RTTimeMilliTS();
+ }
+ catch (std::bad_alloc &)
+ {
+ ASMBitClear(&mData.bmObjectIds[0], idObject);
+ return VERR_NO_MEMORY;
+ }
+
+ if (pidObject)
+ *pidObject = idObject;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters an object from the session objects list.
+ *
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_NOT_FOUND if the object ID was not found.
+ * @param idObject Object ID to unregister.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_objectUnregister(uint32_t idObject)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+ AssertMsgStmt(ASMBitTestAndClear(&mData.bmObjectIds, idObject), ("idObject=%#x\n", idObject), vrc = 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 vrc;
+}
+
+/**
+ * Unregisters all objects from the session list.
+ *
+ * @returns VBox status code.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_objectsUnregister(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("Unregistering directories (%zu total)\n", mData.mDirectories.size()));
+
+ SessionDirectories::iterator itDirs;
+ while ((itDirs = mData.mDirectories.begin()) != mData.mDirectories.end())
+ {
+ alock.release();
+ i_directoryUnregister(itDirs->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mDirectories.size() == 0);
+ mData.mDirectories.clear();
+
+ LogFlowThisFunc(("Unregistering files (%zu total)\n", mData.mFiles.size()));
+
+ SessionFiles::iterator itFiles;
+ while ((itFiles = mData.mFiles.begin()) != mData.mFiles.end())
+ {
+ alock.release();
+ i_fileUnregister(itFiles->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mFiles.size() == 0);
+ mData.mFiles.clear();
+
+ LogFlowThisFunc(("Unregistering processes (%zu total)\n", mData.mProcesses.size()));
+
+ SessionProcesses::iterator itProcs;
+ while ((itProcs = mData.mProcesses.begin()) != mData.mProcesses.end())
+ {
+ alock.release();
+ i_processUnregister(itProcs->second);
+ alock.acquire();
+ }
+
+ Assert(mData.mProcesses.size() == 0);
+ mData.mProcesses.clear();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notifies all registered objects about a guest session status change.
+ *
+ * @returns VBox status code.
+ * @param enmSessionStatus Session status to notify objects about.
+ */
+int GuestSession::i_objectsNotifyAboutStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFunc(("enmSessionStatus=%RU32\n", enmSessionStatus));
+
+ int vrc = VINF_SUCCESS;
+
+ SessionObjects::iterator itObjs = mData.mObjects.begin();
+ while (itObjs != mData.mObjects.end())
+ {
+ GuestObject *pObj = itObjs->second.pObject;
+ if (pObj) /* pObject can be NULL (weak pointer). */
+ {
+ int vrc2 = pObj->i_onSessionStatusChange(enmSessionStatus);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ /* If the session got terminated, make sure to cancel all wait events for
+ * the current object. */
+ if (i_isTerminated())
+ pObj->cancelWaitEvents();
+ }
+
+ ++itObjs;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Renames a path on the guest.
+ *
+ * @returns VBox status code.
+ * @returns VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @param strSource Source path on guest to rename.
+ * @param strDest Destination path on guest to rename \a strSource to.
+ * @param uFlags Renaming flags.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *pvrcGuest)
+{
+ AssertReturn(!(uFlags & ~PATHRENAME_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("strSource=%s, strDest=%s, uFlags=0x%x\n",
+ strSource.c_str(), strDest.c_str(), uFlags));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetPv(&paParms[i++], (void*)strSource.c_str(),
+ (ULONG)strSource.length() + 1);
+ HGCMSvcSetPv(&paParms[i++], (void*)strDest.c_str(),
+ (ULONG)strDest.length() + 1);
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_RENAME, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the user's absolute documents path, if any.
+ *
+ * @returns VBox status code.
+ * @param strPath Where to store the user's document path.
+ * @param pvrcGuest Guest VBox status code, when returning
+ * VERR_GSTCTL_GUEST_ERROR. Any other return code indicates
+ * some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *pvrcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Cache the user's document path? */
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_USER_DOCUMENTS, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_SUCCESS(vrc))
+ {
+ strPath = pEvent->Payload().ToString();
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ if (pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ }
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the user's absolute home path, if any.
+ *
+ * @returns VBox status code.
+ * @param strPath Where to store the user's home path.
+ * @param pvrcGuest Guest VBox status code, when returning
+ * VERR_GSTCTL_GUEST_ERROR. Any other return code indicates
+ * some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_pathUserHome(Utf8Str &strPath, int *pvrcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /** @todo Cache the user's home path? */
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+
+ alock.release(); /* Drop lock before sending. */
+
+ vrc = i_sendMessage(HOST_MSG_PATH_USER_HOME, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_SUCCESS(vrc))
+ {
+ strPath = pEvent->Payload().ToString();
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ if (pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ }
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unregisters a process object from a guest session.
+ *
+ * @returns VBox status code. VERR_NOT_FOUND if the process is not registered (anymore).
+ * @param pProcess Process object to unregister from session.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_processUnregister(GuestProcess *pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("pProcess=%p\n", pProcess));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const uint32_t idObject = pProcess->getObjectID();
+
+ LogFlowFunc(("Removing process (objectID=%RU32) ...\n", idObject));
+
+ int vrc = i_objectUnregister(idObject);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ 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 hrc = pProc->COMGETTER(PID)(&uPID);
+ ComAssertComRC(hrc);
+
+ LogFlowFunc(("Removing process ID=%RU32 (session %RU32, guest PID %RU32, now total %zu processes)\n",
+ idObject, mData.mSession.mID, uPID, mData.mProcesses.size()));
+
+ vrc = pProcess->i_onUnregister();
+ AssertRCReturn(vrc, vrc);
+
+ mData.mProcesses.erase(itProcs);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProc, uPID, false /* Process unregistered */);
+
+ pProc.setNull();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates but does *not* start the process yet.
+ *
+ * See GuestProcess::startProcess() or GuestProcess::startProcessAsync() for
+ * starting the process.
+ *
+ * @returns IPRT status code.
+ * @param procInfo Process startup info to use for starting the process.
+ * @param pProcess Where to return the created guest process object on success.
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr<GuestProcess> &pProcess)
+{
+ LogFlowFunc(("mExe=%s, mFlags=%x, mTimeoutMS=%RU32\n",
+ procInfo.mExecutable.c_str(), procInfo.mFlags, procInfo.mTimeoutMS));
+#ifdef DEBUG
+ if (procInfo.mArguments.size())
+ {
+ LogFlowFunc(("Arguments:"));
+ ProcessArguments::const_iterator it = procInfo.mArguments.begin();
+ while (it != procInfo.mArguments.end())
+ {
+ LogFlow((" %s", (*it).c_str()));
+ ++it;
+ }
+ LogFlow(("\n"));
+ }
+#endif
+
+ /* Validate flags. */
+ if (procInfo.mFlags)
+ {
+ if ( !(procInfo.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ && !(procInfo.mFlags & ProcessCreateFlag_Hidden)
+ && !(procInfo.mFlags & ProcessCreateFlag_Profile)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdErr))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ if ( (procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ && ( (procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ || (procInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
+ )
+ )
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (procInfo.mPriority)
+ {
+ if (!(procInfo.mPriority & ProcessPriority_Default))
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Adjust timeout.
+ * If set to 0, we define an infinite timeout (unlimited process run time). */
+ if (procInfo.mTimeoutMS == 0)
+ procInfo.mTimeoutMS = UINT32_MAX;
+
+ /** @todo Implement process priority + affinity. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Create the process object. */
+ HRESULT hrc = pProcess.createObject();
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ /* Register a new object ID. */
+ uint32_t idObject;
+ int vrc = i_objectRegister(pProcess, SESSIONOBJECTTYPE_PROCESS, &idObject);
+ if (RT_FAILURE(vrc))
+ {
+ pProcess.setNull();
+ return vrc;
+ }
+
+ vrc = pProcess->init(mParent->i_getConsole() /* Console */, this /* Session */, idObject, procInfo, mData.mpBaseEnvironment);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* 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 &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ return vrc;
+}
+
+/**
+ * Checks if a process object exists and optionally returns its object.
+ *
+ * @returns \c true if process object exists, or \c false if not.
+ * @param uProcessID ID of process object to check.
+ * @param pProcess Where to return the found process object on success.
+ *
+ * @note No locking done!
+ */
+inline bool GuestSession::i_processExists(uint32_t uProcessID, ComObjPtr<GuestProcess> *pProcess)
+{
+ SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID);
+ if (it != mData.mProcesses.end())
+ {
+ if (pProcess)
+ *pProcess = it->second;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns the process object from a guest PID.
+ *
+ * @returns VBox status code.
+ * @param uPID Guest PID to get process object for.
+ * @param pProcess Where to return the process object on success.
+ *
+ * @note No locking done!
+ */
+inline int GuestSession::i_processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess)
+{
+ AssertReturn(uPID, false);
+ /* pProcess is optional. */
+
+ SessionProcesses::iterator itProcs = mData.mProcesses.begin();
+ for (; itProcs != mData.mProcesses.end(); ++itProcs)
+ {
+ ComObjPtr<GuestProcess> pCurProc = itProcs->second;
+ AutoCaller procCaller(pCurProc);
+ if (!procCaller.isOk())
+ return VERR_COM_INVALID_OBJECT_STATE;
+
+ ULONG uCurPID;
+ HRESULT hrc = pCurProc->COMGETTER(PID)(&uCurPID);
+ ComAssertComRC(hrc);
+
+ if (uCurPID == uPID)
+ {
+ if (pProcess)
+ *pProcess = pCurProc;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Sends a message to the HGCM host service.
+ *
+ * @returns VBox status code.
+ * @param uMessage Message ID to send.
+ * @param uParms Number of parameters in \a paParms to send.
+ * @param paParms Array of HGCM parameters to send.
+ * @param fDst Host message destination flags of type VBOX_GUESTCTRL_DST_XXX.
+ */
+int GuestSession::i_sendMessage(uint32_t uMessage, uint32_t uParms, PVBOXHGCMSVCPARM paParms,
+ uint64_t fDst /*= VBOX_GUESTCTRL_DST_SESSION*/)
+{
+ LogFlowThisFuncEnter();
+
+#ifndef VBOX_GUESTCTRL_TEST_CASE
+ ComObjPtr<Console> pConsole = mParent->i_getConsole();
+ Assert(!pConsole.isNull());
+
+ /* Forward the information to the VMM device. */
+ VMMDev *pVMMDev = pConsole->i_getVMMDev();
+ AssertPtr(pVMMDev);
+
+ LogFlowThisFunc(("uMessage=%RU32 (%s), uParms=%RU32\n", uMessage, GstCtrlHostMsgtoStr((guestControl::eHostMsg)uMessage), uParms));
+
+ /* HACK ALERT! We extend the first parameter to 64-bit and use the
+ two topmost bits for call destination information. */
+ Assert(fDst == VBOX_GUESTCTRL_DST_SESSION || fDst == VBOX_GUESTCTRL_DST_ROOT_SVC || fDst == VBOX_GUESTCTRL_DST_BOTH);
+ Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT);
+ paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT;
+ paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | fDst;
+
+ /* Make the call. */
+ int vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, uParms, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo What to do here? */
+ }
+#else
+ /* Not needed within testcases. */
+ int vrc = VINF_SUCCESS;
+#endif
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets the guest session's current status.
+ *
+ * @returns VBox status code.
+ * @param sessionStatus Session status to set.
+ * @param vrcSession Session result to set (for error handling).
+ *
+ * @note Takes the write lock.
+ */
+int GuestSession::i_setSessionStatus(GuestSessionStatus_T sessionStatus, int vrcSession)
+{
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, vrcSession=%Rrc\n", mData.mStatus, sessionStatus, vrcSession));
+
+ if (sessionStatus == GuestSessionStatus_Error)
+ {
+ AssertMsg(RT_FAILURE(vrcSession), ("Guest vrcSession must be an error (%Rrc)\n", vrcSession));
+ /* Do not allow overwriting an already set error. If this happens
+ * this means we forgot some error checking/locking somewhere. */
+ AssertMsg(RT_SUCCESS(mData.mVrc), ("Guest mVrc already set (to %Rrc)\n", mData.mVrc));
+ }
+ else
+ AssertMsg(RT_SUCCESS(vrcSession), ("Guest vrcSession must not be an error (%Rrc)\n", vrcSession));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != sessionStatus)
+ {
+ mData.mStatus = sessionStatus;
+ mData.mVrc = vrcSession;
+
+ /* Make sure to notify all underlying objects first. */
+ vrc = i_objectsNotifyAboutStatusChange(sessionStatus);
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ ComAssertComRC(hrc);
+ int vrc2 = errorInfo->initEx(VBOX_E_IPRT_ERROR, vrcSession, COM_IIDOF(IGuestSession), getComponentName(),
+ i_guestErrorToString(vrcSession));
+ AssertRC(vrc2);
+
+ alock.release(); /* Release lock before firing off event. */
+
+ ::FireGuestSessionStateChangedEvent(mEventSource, this, mData.mSession.mID, sessionStatus, errorInfo);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/** @todo Unused --remove? */
+int GuestSession::i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int vrc /*= VINF_SUCCESS */)
+{
+ RT_NOREF(enmWaitResult, vrc);
+
+ /*LogFlowThisFunc(("enmWaitResult=%d, vrc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n",
+ enmWaitResult, vrc, mData.mWaitCount, mData.mWaitEvent));*/
+
+ /* Note: No write locking here -- already done in the caller. */
+
+ int vrc2 = VINF_SUCCESS;
+ /*if (mData.mWaitEvent)
+ vrc2 = mData.mWaitEvent->Signal(enmWaitResult, vrc);*/
+ LogFlowFuncLeaveRC(vrc2);
+ return vrc2;
+}
+
+/**
+ * Shuts down (and optionally powers off / reboots) the guest.
+ * Needs supported Guest Additions installed.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if not supported by Guest Additions.
+ * @param fFlags Guest shutdown flags.
+ * @param pvrcGuest Guest VBox status code, when returning
+ * VERR_GSTCTL_GUEST_ERROR. Any other return code indicates
+ * some host side error.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_shutdown(uint32_t fFlags, int *pvrcGuest)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertPtrReturn(mParent, VERR_INVALID_POINTER);
+ if (!(mParent->i_getGuestControlFeatures0() & VBOX_GUESTCTRL_GF_0_SHUTDOWN))
+ return VERR_NOT_SUPPORTED;
+
+ LogRel(("Guest Control: Shutting down guest (flags = %#x) ...\n", fFlags));
+
+ GuestWaitEvent *pEvent = NULL;
+ int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[2];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], fFlags);
+
+ alock.release(); /* Drop lock before sending. */
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ vrc = i_sendMessage(HOST_MSG_SHUTDOWN, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pEvent->Wait(30 * 1000);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ vrcGuest = pEvent->GuestResult();
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Guest Control: Shutting down guest failed, vrc=%Rrc\n", vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc));
+ if ( vrc == VERR_GSTCTL_GUEST_ERROR
+ && pvrcGuest)
+ *pvrcGuest = vrcGuest;
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Determines the protocol version (sets mData.mProtocolVersion).
+ *
+ * This is called from the init method prior to to establishing a guest
+ * session.
+ *
+ * @returns VBox status code.
+ */
+int GuestSession::i_determineProtocolVersion(void)
+{
+ /*
+ * We currently do this based on the reported Guest Additions version,
+ * ASSUMING that VBoxService and VBoxDrv are at the same version.
+ */
+ ComObjPtr<Guest> pGuest = mParent;
+ AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED);
+ uint32_t uGaVersion = pGuest->i_getAdditionsVersion();
+
+ /* Everyone supports version one, if they support anything at all. */
+ mData.mProtocolVersion = 1;
+
+ /* Guest control 2.0 was introduced with 4.3.0. */
+ if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0))
+ mData.mProtocolVersion = 2; /* Guest control 2.0. */
+
+ LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n",
+ VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
+ VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
+
+ /*
+ * Inform the user about outdated Guest Additions (VM release log).
+ */
+ if (mData.mProtocolVersion < 2)
+ LogRelMax(3, ("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n"
+ " Please upgrade GAs to the current version to get full guest control capabilities.\n",
+ VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
+ VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for guest session events.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_TIMEOUT when a timeout has occurred.
+ * @param fWaitFlags Wait flags to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param waitResult Where to return the wait result on success.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ *
+ * @note Takes the read lock.
+ */
+int GuestSession::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *pvrcGuest)
+{
+ LogFlowThisFuncEnter();
+
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+
+ /*LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, pvrcGuest=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, pvrcGuest));*/
+
+ 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.mVrc), ("No error mVrc (%Rrc) set when guest session indicated an error\n", mData.mVrc));
+ if (pvrcGuest)
+ *pvrcGuest = mData.mVrc; /* 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, vrcSession=%Rrc, waitResult=%RU32\n", mData.mStatus, mData.mVrc, waitResult));
+
+ /* No waiting needed? Return immediately using the last set error. */
+ if (waitResult != GuestSessionWaitResult_None)
+ {
+ if (pvrcGuest)
+ *pvrcGuest = mData.mVrc; /* Return last set error (if any). */
+ return RT_SUCCESS(mData.mVrc) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ uint64_t const tsStart = RTTimeMilliTS();
+ uint64_t tsNow = tsStart;
+
+ while (tsNow - tsStart < uTimeoutMS)
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
+
+ vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ alock.release(); /* Release lock before waiting. */
+
+ GuestSessionStatus_T sessionStatus;
+ vrc = i_waitForStatusChange(pEvent, fWaitFlags,
+ uTimeoutMS - (tsNow - tsStart), &sessionStatus, pvrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (sessionStatus)
+ {
+ case GuestSessionStatus_Started:
+ waitResult = GuestSessionWaitResult_Start;
+ break;
+
+ case GuestSessionStatus_Starting:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_Terminating:
+ if (fWaitFlags & GuestSessionWaitForFlag_Status) /* Any status wanted? */
+ waitResult = GuestSessionWaitResult_Status;
+ /* else: Wait another round until we get the event(s) fWaitFlags defines. */
+ break;
+
+ case GuestSessionStatus_Terminated:
+ waitResult = GuestSessionWaitResult_Terminate;
+ break;
+
+ case GuestSessionStatus_TimedOutKilled:
+ RT_FALL_THROUGH();
+ case GuestSessionStatus_TimedOutAbnormally:
+ waitResult = GuestSessionWaitResult_Timeout;
+ break;
+
+ case GuestSessionStatus_Down:
+ waitResult = GuestSessionWaitResult_Terminate;
+ break;
+
+ case GuestSessionStatus_Error:
+ waitResult = GuestSessionWaitResult_Error;
+ break;
+
+ default:
+ waitResult = GuestSessionWaitResult_Status;
+ break;
+ }
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ /* Wait result not None, e.g. some result acquired or a wait error occurred? Bail out. */
+ if ( waitResult != GuestSessionWaitResult_None
+ || RT_FAILURE(vrc))
+ break;
+
+ tsNow = RTTimeMilliTS();
+
+ alock.acquire(); /* Re-acquire lock before waiting for the next event. */
+ }
+
+ if (tsNow - tsStart >= uTimeoutMS)
+ {
+ waitResult = GuestSessionWaitResult_None; /* Paranoia. */
+ vrc = VERR_TIMEOUT;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for guest session status changes.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR on received guest error.
+ * @retval VERR_WRONG_ORDER when an unexpected event type has been received.
+ * @param pEvent Wait event to use for waiting.
+ * @param fWaitFlags Wait flags to use.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pSessionStatus Where to return the guest session status.
+ * @param pvrcGuest Where to return the guest error when
+ * VERR_GSTCTL_GUEST_ERROR was returned. Optional.
+ */
+int GuestSession::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS,
+ GuestSessionStatus_T *pSessionStatus, int *pvrcGuest)
+{
+ RT_NOREF(fWaitFlags);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS, &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestSessionStateChanged)
+ {
+ ComPtr<IGuestSessionStateChangedEvent> pChangedEvent = pIEvent;
+ Assert(!pChangedEvent.isNull());
+
+ GuestSessionStatus_T sessionStatus;
+ pChangedEvent->COMGETTER(Status)(&sessionStatus);
+ if (pSessionStatus)
+ *pSessionStatus = sessionStatus;
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = pChangedEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hrc);
+
+ LONG lGuestRc;
+ hrc = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hrc);
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ if (pvrcGuest)
+ *pvrcGuest = (int)lGuestRc;
+
+ LogFlowThisFunc(("Status changed event for session ID=%RU32, new status is: %RU32 (%Rrc)\n",
+ mData.mSession.mID, sessionStatus,
+ RT_SUCCESS((int)lGuestRc) ? VINF_SUCCESS : (int)lGuestRc));
+ }
+ else /** @todo Re-visit this. Can this happen more frequently? */
+ AssertMsgFailedReturn(("Got unexpected event type %#x\n", evtType), VERR_WRONG_ORDER);
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make pvrcGuest is set. */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && pvrcGuest)
+ *pvrcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !pvrcGuest || *pvrcGuest != (int)0xcccccccc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestSession::close()
+{
+ LogFlowThisFuncEnter();
+
+ /* Note: Don't check if the session is ready via i_isStartedExternal() here;
+ * the session (already) could be in a stopped / aborted state. */
+
+ int vrc = VINF_SUCCESS; /* Shut up MSVC. */
+ int vrcGuest = VINF_SUCCESS;
+
+ uint32_t msTimeout = RT_MS_10SEC; /* 10s timeout by default */
+ for (int i = 0; i < 3; i++)
+ {
+ if (i)
+ {
+ LogRel(("Guest Control: Closing session '%s' timed out (%RU32s timeout, attempt %d/10), retrying ...\n",
+ mData.mSession.mName.c_str(), msTimeout / RT_MS_1SEC, i + 1));
+ msTimeout += RT_MS_5SEC; /* Slightly increase the timeout. */
+ }
+
+ /* Close session on guest. */
+ vrc = i_closeSession(0 /* Flags */, msTimeout, &vrcGuest);
+ if ( RT_SUCCESS(vrc)
+ || vrc != VERR_TIMEOUT) /* If something else happened there is no point in retrying further. */
+ break;
+ }
+
+ /* On failure don't return here, instead do all the cleanup
+ * work first and then return an error. */
+
+ /* Destroy session + remove ourselves from the session list. */
+ AssertPtr(mParent);
+ int vrc2 = mParent->i_sessionDestroy(mData.mSession.mID);
+ if (vrc2 == VERR_NOT_FOUND) /* Not finding the session anymore isn't critical. */
+ vrc2 = VINF_SUCCESS;
+
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Session, vrcGuest, mData.mSession.mName.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Closing guest session failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session \"%s\" failed with %Rrc"),
+ mData.mSession.mName.c_str(), vrc);
+ }
+
+ return S_OK;
+}
+
+HRESULT GuestSession::fileCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fileCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = FileCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_File;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = DirectoryCopyFlag_None;
+ source.fFileCopyFlags = (FileCopyFlag_T)fFlags;
+
+ SourceSet.push_back(source);
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::fileCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = FileCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_File;
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = DirectoryCopyFlag_None;
+ source.fFileCopyFlags = (FileCopyFlag_T)fFlags;
+
+ SourceSet.push_back(source);
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::copyFromGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress)
+{
+ const size_t cSources = aSources.size();
+ if ( (aFilters.size() && aFilters.size() != cSources)
+ || (aFlags.size() && aFlags.size() != cSources))
+ {
+ return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
+ std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
+ std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
+
+ const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
+ const bool fFollowSymlinks = true; /** @todo Ditto. */
+
+ while (itSource != aSources.end())
+ {
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ && !fContinueOnErrors)
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, (*itSource).c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying type for guest source failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ return setError(E_FAIL, tr("Querying type for guest source \"%s\" failed: %Rrc"), (*itSource).c_str(), vrc);
+ }
+
+ Utf8Str strFlags;
+ if (itFlags != aFlags.end())
+ {
+ strFlags = *itFlags;
+ ++itFlags;
+ }
+
+ Utf8Str strFilter;
+ if (itFilter != aFilters.end())
+ {
+ strFilter = *itFilter;
+ ++itFilter;
+ }
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = *itSource;
+ source.strFilter = strFilter;
+ source.enmType = objData.mType;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+
+ /* Check both flag groups here, as copying a directory also could mean to explicitly
+ * *not* replacing any existing files (or just copy files which are newer, for instance). */
+ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags);
+ GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags);
+
+ SourceSet.push_back(source);
+
+ ++itSource;
+ }
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::copyToGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
+ const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
+ ComPtr<IProgress> &aProgress)
+{
+ const size_t cSources = aSources.size();
+ if ( (aFilters.size() && aFilters.size() != cSources)
+ || (aFlags.size() && aFlags.size() != cSources))
+ {
+ return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
+ std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
+ std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
+
+ const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
+
+ while (itSource != aSources.end())
+ {
+ RTFSOBJINFO objInfo;
+ int vrc = RTPathQueryInfo((*itSource).c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
+ if ( RT_FAILURE(vrc)
+ && !fContinueOnErrors)
+ {
+ return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc);
+ }
+
+ Utf8Str strFlags;
+ if (itFlags != aFlags.end())
+ {
+ strFlags = *itFlags;
+ ++itFlags;
+ }
+
+ Utf8Str strFilter;
+ if (itFilter != aFilters.end())
+ {
+ strFilter = *itFilter;
+ ++itFilter;
+ }
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = *itSource;
+ source.strFilter = strFilter;
+ source.enmType = GuestBase::fileModeToFsObjType(objInfo.Attr.fMode);
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+
+ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags);
+ GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags);
+
+ SourceSet.push_back(source);
+
+ ++itSource;
+ }
+
+ /* (Re-)Validate stuff. */
+ if (RT_UNLIKELY(SourceSet.size() == 0)) /* At least one source must be present. */
+ return setError(E_INVALIDARG, tr("No sources specified"));
+ if (RT_UNLIKELY(SourceSet[0].strSource.isEmpty()))
+ return setError(E_INVALIDARG, tr("First source entry is empty"));
+ if (RT_UNLIKELY(aDestination.isEmpty()))
+ return setError(E_INVALIDARG, tr("No destination specified"));
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::directoryCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = DirectoryCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive
+ | DirectoryCopyFlag_FollowLinks;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_Directory;
+ source.enmPathStyle = i_getGuestPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags;
+ source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */
+
+ SourceSet.push_back(source);
+
+ return i_copyFromGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ uint32_t fFlags = DirectoryCopyFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive
+ | DirectoryCopyFlag_FollowLinks;
+ if (fFlags & ~fValidFlags)
+ return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags);
+ }
+
+ GuestSessionFsSourceSet SourceSet;
+
+ GuestSessionFsSourceSpec source;
+ source.strSource = aSource;
+ source.enmType = FsObjType_Directory;
+ source.enmPathStyle = GuestSession::i_getHostPathStyle();
+ source.fDryRun = false; /** @todo Implement support for a dry run. */
+ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags;
+ source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */
+
+ SourceSet.push_back(source);
+
+ return i_copyToGuest(SourceSet, aDestination, aProgress);
+}
+
+HRESULT GuestSession::directoryCreate(const com::Utf8Str &aPath, ULONG aMode,
+ const std::vector<DirectoryCreateFlag_T> &aFlags)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to create specified"));
+
+ uint32_t fFlags = DirectoryCreateFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ if (fFlags & ~DirectoryCreateFlag_Parents)
+ return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags);
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ ComObjPtr <GuestDirectory> pDirectory;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, aPath.c_str());
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Guest directory creation failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Invalid parameters given"));
+ break;
+
+ case VERR_BROKEN_PIPE:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Unexpectedly aborted"));
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: %Rrc"), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath,
+ BOOL aSecure, com::Utf8Str &aDirectory)
+{
+ if (RT_UNLIKELY((aTemplateName.c_str()) == NULL || *(aTemplateName.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No template specified"));
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory name specified"));
+ if (!aSecure) /* Ignore what mode is specified when a secure temp thing needs to be created. */
+ if (RT_UNLIKELY(aMode & ~07777))
+ return setError(E_INVALIDARG, tr("Mode invalid (must be specified in octal mode)"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, aMode, RT_BOOL(aSecure), &vrcGuest);
+ if (!RT_SUCCESS(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolMkTemp, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Temporary guest directory creation failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary guest directory creation \"%s\" with template \"%s\" failed: %Rrc"),
+ aPath.c_str(), aTemplateName.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ if (RT_UNLIKELY(aPath.isEmpty()))
+ return setError(E_INVALIDARG, tr("Empty path"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ *aExists = TRUE;
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ *aExists = FALSE;
+ break;
+ default:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying directory existence failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ }
+ break;
+ }
+
+ case VERR_NOT_A_DIRECTORY:
+ {
+ *aExists = FALSE;
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying directory existence \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryOpen(const com::Utf8Str &aPath, const com::Utf8Str &aFilter,
+ const std::vector<DirectoryOpenFlag_T> &aFlags, ComPtr<IGuestDirectory> &aDirectory)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to open specified"));
+ if (RT_UNLIKELY((aFilter.c_str()) != NULL && *(aFilter.c_str()) != '\0'))
+ return setError(E_INVALIDARG, tr("Directory filters are not implemented yet"));
+
+ uint32_t fFlags = DirectoryOpenFlag_None;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fFlags |= aFlags[i];
+
+ if (fFlags)
+ return setError(E_INVALIDARG, tr("Open flags (%#x) not implemented yet"), fFlags);
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestDirectoryOpenInfo openInfo;
+ openInfo.mPath = aPath;
+ openInfo.mFilter = aFilter;
+ openInfo.mFlags = fFlags;
+
+ ComObjPtr<GuestDirectory> pDirectory;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryOpen(openInfo, pDirectory, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Return directory object to the caller. */
+ hrc = pDirectory.queryInterfaceTo(aDirectory.asOutParam());
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed; invalid parameters given"),
+ aPath.c_str());
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Opening guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath)
+{
+ if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to remove specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* No flags; only remove the directory when empty. */
+ uint32_t fFlags = DIRREMOVEREC_FLAG_NONE;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryRemove(aPath, fFlags, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling removing guest directories not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Removing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::directoryRemoveRecursive(const com::Utf8Str &aPath, const std::vector<DirectoryRemoveRecFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0'))
+ return setError(E_INVALIDARG, tr("No directory to remove recursively specified"));
+
+ /* By defautl remove recursively as the function name implies. */
+ uint32_t fFlags = DIRREMOVEREC_FLAG_RECURSIVE;
+ if (aFlags.size())
+ {
+ for (size_t i = 0; i < aFlags.size(); i++)
+ {
+ switch (aFlags[i])
+ {
+ case DirectoryRemoveRecFlag_None: /* Skip. */
+ continue;
+
+ case DirectoryRemoveRecFlag_ContentAndDir:
+ fFlags |= DIRREMOVEREC_FLAG_CONTENT_AND_DIR;
+ break;
+
+ case DirectoryRemoveRecFlag_ContentOnly:
+ fFlags |= DIRREMOVEREC_FLAG_CONTENT_ONLY;
+ break;
+
+ default:
+ return setError(E_INVALIDARG, tr("Invalid flags specified"));
+ }
+ }
+ }
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<Progress> pProgress;
+ hrc = pProgress.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = pProgress->init(static_cast<IGuestSession *>(this),
+ Bstr(tr("Removing guest directory")).raw(),
+ TRUE /*aCancelable*/);
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Note: At the moment we don't supply progress information while
+ * deleting a guest directory recursively. So just complete
+ * the progress object right now. */
+ /** @todo Implement progress reporting on guest directory deletion! */
+ hrc = pProgress->i_notifyComplete(S_OK);
+ if (FAILED(hrc))
+ return hrc;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_directoryRemove(aPath, fFlags, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling removing guest directories recursively not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Directory, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Recursively removing guest directory failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recursively removing guest directory \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+ }
+ else
+ {
+ pProgress.queryInterfaceTo(aProgress.asOutParam());
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::environmentScheduleSet(const com::Utf8Str &aName, const com::Utf8Str &aValue)
+{
+ LogFlowThisFuncEnter();
+ int vrc;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.setVariable(aName, aValue);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to schedule setting environment variable '%s' to '%s'"), aName.c_str(), aValue.c_str());
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+ int vrc;
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ vrc = mData.mEnvironmentChanges.unsetVariable(aName);
+ }
+ HRESULT hrc;
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to schedule unsetting environment variable '%s'"), aName.c_str());
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue)
+{
+ LogFlowThisFuncEnter();
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_ENV_INVALID_VAR_NAME)
+ hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists)
+{
+ LogFlowThisFuncEnter();
+ *aExists = FALSE;
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc;
+ if (mData.mpBaseEnvironment)
+ {
+ hrc = S_OK;
+ *aExists = mData.mpBaseEnvironment->doesVariableExist(aName);
+ }
+ else if (mData.mProtocolVersion < 99999)
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions"));
+ else
+ hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+HRESULT GuestSession::fileCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure,
+ ComPtr<IGuestFile> &aFile)
+{
+ RT_NOREF(aTemplateName, aMode, aPath, aSecure, aFile);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fileExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ /* By default we return non-existent. */
+ *aExists = FALSE;
+
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return S_OK;
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileQueryInfo(aPath, RT_BOOL(aFollowSymlinks), objData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aExists = TRUE;
+ return S_OK;
+ }
+
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_FILE_NOT_FOUND:
+ break;
+
+ default:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file existence failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case VERR_NOT_A_FILE:
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"),
+ aPath.c_str(), vrc);
+ break;
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fileOpen(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ ULONG aCreationMode, ComPtr<IGuestFile> &aFile)
+{
+ LogFlowThisFuncEnter();
+
+ const std::vector<FileOpenExFlag_T> EmptyFlags;
+ return fileOpenEx(aPath, aAccessMode, aOpenAction, FileSharingMode_All, aCreationMode, EmptyFlags, aFile);
+}
+
+HRESULT GuestSession::fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
+ FileSharingMode_T aSharingMode, ULONG aCreationMode,
+ const std::vector<FileOpenExFlag_T> &aFlags, ComPtr<IGuestFile> &aFile)
+{
+ if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
+ return setError(E_INVALIDARG, tr("No file to open specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFuncEnter();
+
+ /* Validate aAccessMode. */
+ switch (aAccessMode)
+ {
+ case FileAccessMode_ReadOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_WriteOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_ReadWrite:
+ break;
+ case FileAccessMode_AppendOnly:
+ RT_FALL_THRU();
+ case FileAccessMode_AppendRead:
+ return setError(E_NOTIMPL, tr("Append access modes are not yet implemented"));
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileAccessMode value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Validate aOpenAction to the old format. */
+ switch (aOpenAction)
+ {
+ case FileOpenAction_OpenExisting:
+ RT_FALL_THRU();
+ case FileOpenAction_OpenOrCreate:
+ RT_FALL_THRU();
+ case FileOpenAction_CreateNew:
+ RT_FALL_THRU();
+ case FileOpenAction_CreateOrReplace:
+ RT_FALL_THRU();
+ case FileOpenAction_OpenExistingTruncated:
+ RT_FALL_THRU();
+ case FileOpenAction_AppendOrCreate:
+ break;
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Validate aSharingMode. */
+ switch (aSharingMode)
+ {
+ case FileSharingMode_All:
+ break;
+ case FileSharingMode_Read:
+ case FileSharingMode_Write:
+ case FileSharingMode_ReadWrite:
+ case FileSharingMode_Delete:
+ case FileSharingMode_ReadDelete:
+ case FileSharingMode_WriteDelete:
+ return setError(E_NOTIMPL, tr("Only FileSharingMode_All is currently implemented"));
+
+ default:
+ return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
+ }
+
+ /* Combine and validate flags. */
+ uint32_t fOpenEx = 0;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fOpenEx |= aFlags[i];
+ if (fOpenEx)
+ return setError(E_INVALIDARG, tr("Unsupported FileOpenExFlag value(s) in aFlags (%#x)"), fOpenEx);
+
+ ComObjPtr <GuestFile> pFile;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileOpenEx(aPath, aAccessMode, aOpenAction, aSharingMode, aCreationMode, aFlags, pFile, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ /* Return directory object to the caller. */
+ hrc = pFile.queryInterfaceTo(aFile.asOutParam());
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling guest files not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_File, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Opening guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fileQuerySize(const com::Utf8Str &aPath, BOOL aFollowSymlinks, LONG64 *aSize)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ int64_t llSize;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ *aSize = llSize;
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file size failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file size of \"%s\" failed: %Rrc"),
+ vrc, aPath.c_str());
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsQueryFreeSpace(const com::Utf8Str &aPath, LONG64 *aFreeSpace)
+{
+ RT_NOREF(aPath, aFreeSpace);
+
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsQueryInfo(const com::Utf8Str &aPath, ComPtr<IGuestFsInfo> &aInfo)
+{
+ RT_NOREF(aPath, aInfo);
+
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
+
+ *aExists = false;
+
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ *aExists = TRUE;
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ if ( vrcGuest == VERR_NOT_A_FILE
+ || vrcGuest == VERR_PATH_NOT_FOUND
+ || vrcGuest == VERR_FILE_NOT_FOUND
+ || vrcGuest == VERR_INVALID_NAME)
+ hrc = S_OK; /* Ignore these vrc values. */
+ else
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file existence information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file existence information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr<IGuestFsObjInfo> &aInfo)
+{
+ if (aPath.isEmpty())
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
+
+ GuestFsObjData Info;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, Info, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> ptrFsObjInfo;
+ hrc = ptrFsObjInfo.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrFsObjInfo->init(Info);
+ if (RT_SUCCESS(vrc))
+ hrc = ptrFsObjInfo.queryInterfaceTo(aInfo.asOutParam());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ }
+ else
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Querying guest file information failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath)
+{
+ if (RT_UNLIKELY(aPath.isEmpty()))
+ return setError(E_INVALIDARG, tr("No path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ LogFlowThisFunc(("aPath=%s\n", aPath.c_str()));
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_fileRemove(aPath, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (GuestProcess::i_isGuestError(vrc))
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_ToolRm, vrcGuest, aPath.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Removing guest file failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aPaths, aProgress);
+ return E_NOTIMPL;
+}
+
+HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjRenameFlag_T> &aFlags)
+{
+ if (RT_UNLIKELY(aSource.isEmpty()))
+ return setError(E_INVALIDARG, tr("No source path specified"));
+
+ if (RT_UNLIKELY(aDestination.isEmpty()))
+ return setError(E_INVALIDARG, tr("No destination path specified"));
+
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Combine, validate and convert flags. */
+ uint32_t fApiFlags = 0;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fApiFlags |= aFlags[i];
+ if (fApiFlags & ~((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace))
+ return setError(E_INVALIDARG, tr("Unknown rename flag: %#x"), fApiFlags);
+
+ LogFlowThisFunc(("aSource=%s, aDestination=%s\n", aSource.c_str(), aDestination.c_str()));
+
+ AssertCompile(FsObjRenameFlag_NoReplace == 0);
+ AssertCompile(FsObjRenameFlag_Replace != 0);
+ uint32_t fBackend;
+ if ((fApiFlags & ((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) == FsObjRenameFlag_Replace)
+ fBackend = PATHRENAME_FLAG_REPLACE;
+ else
+ fBackend = PATHRENAME_FLAG_NO_REPLACE;
+
+ /* Call worker to do the job. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_pathRename(aSource, aDestination, fBackend, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_NOT_SUPPORTED:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Handling renaming guest paths not supported by installed Guest Additions"));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, aSource.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Renaming guest path failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest path \"%s\" failed: %Rrc"),
+ aSource.c_str(), vrc);
+ break;
+ }
+ }
+
+ return hrc;
+}
+
+HRESULT GuestSession::fsObjMove(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjMoveArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FsObjMoveFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjCopyArray(const std::vector<com::Utf8Str> &aSource,
+ const com::Utf8Str &aDestination,
+ const std::vector<FileCopyFlag_T> &aFlags,
+ ComPtr<IProgress> &aProgress)
+{
+ RT_NOREF(aSource, aDestination, aFlags, aProgress);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::fsObjSetACL(const com::Utf8Str &aPath, BOOL aFollowSymlinks, const com::Utf8Str &aAcl, ULONG aMode)
+{
+ RT_NOREF(aPath, aFollowSymlinks, aAcl, aMode);
+ ReturnComNotImplemented();
+}
+
+
+HRESULT GuestSession::processCreate(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags,
+ ULONG aTimeoutMS, ComPtr<IGuestProcess> &aGuestProcess)
+{
+ LogFlowThisFuncEnter();
+
+ std::vector<LONG> affinityIgnored;
+ return processCreateEx(aExecutable, aArguments, aEnvironment, aFlags, aTimeoutMS, ProcessPriority_Default,
+ affinityIgnored, aGuestProcess);
+}
+
+HRESULT GuestSession::processCreateEx(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
+ const std::vector<com::Utf8Str> &aEnvironment,
+ const std::vector<ProcessCreateFlag_T> &aFlags, ULONG aTimeoutMS,
+ ProcessPriority_T aPriority, const std::vector<LONG> &aAffinity,
+ ComPtr<IGuestProcess> &aGuestProcess)
+{
+ HRESULT hrc = i_isStartedExternal();
+ if (FAILED(hrc))
+ return hrc;
+
+ /*
+ * Must have an executable to execute. If none is given, we try use the
+ * zero'th argument.
+ */
+ const char *pszExecutable = aExecutable.c_str();
+ if (RT_UNLIKELY(pszExecutable == NULL || *pszExecutable == '\0'))
+ {
+ if (aArguments.size() > 0)
+ pszExecutable = aArguments[0].c_str();
+ if (pszExecutable == NULL || *pszExecutable == '\0')
+ return setError(E_INVALIDARG, tr("No command to execute specified"));
+ }
+
+ /* The rest of the input is being validated in i_processCreateEx(). */
+
+ LogFlowThisFuncEnter();
+
+ /*
+ * Build the process startup info.
+ */
+ GuestProcessStartupInfo procInfo;
+
+ /* Executable and arguments. */
+ procInfo.mExecutable = pszExecutable;
+ if (aArguments.size())
+ {
+ for (size_t i = 0; i < aArguments.size(); i++)
+ procInfo.mArguments.push_back(aArguments[i]);
+ }
+ else /* If no arguments were given, add the executable as argv[0] by default. */
+ procInfo.mArguments.push_back(procInfo.mExecutable);
+
+ /* Combine the environment changes associated with the ones passed in by
+ the caller, giving priority to the latter. The changes are putenv style
+ and will be applied to the standard environment for the guest user. */
+ int vrc = procInfo.mEnvironmentChanges.copy(mData.mEnvironmentChanges);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t idxError = ~(size_t)0;
+ vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment, &idxError);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Convert the flag array into a mask. */
+ if (aFlags.size())
+ for (size_t i = 0; i < aFlags.size(); i++)
+ procInfo.mFlags |= aFlags[i];
+
+ procInfo.mTimeoutMS = aTimeoutMS;
+
+ /** @todo use RTCPUSET instead of archaic 64-bit variables! */
+ if (aAffinity.size())
+ for (size_t i = 0; i < aAffinity.size(); i++)
+ if (aAffinity[i])
+ procInfo.mAffinity |= (uint64_t)1 << i;
+
+ procInfo.mPriority = aPriority;
+
+ /*
+ * Create a guest process object.
+ */
+ ComObjPtr<GuestProcess> pProcess;
+ vrc = i_processCreateEx(procInfo, pProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ ComPtr<IGuestProcess> pIProcess;
+ hrc = pProcess.queryInterfaceTo(pIProcess.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Start the process.
+ */
+ vrc = pProcess->i_startProcessAsync();
+ if (RT_SUCCESS(vrc))
+ {
+ aGuestProcess = pIProcess;
+
+ LogFlowFuncLeaveRC(vrc);
+ return S_OK;
+ }
+
+ hrc = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc);
+ }
+ }
+ else if (vrc == VERR_GSTCTL_MAX_CID_OBJECTS_REACHED)
+ hrc = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"),
+ VBOX_GUESTCTRL_MAX_OBJECTS);
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc);
+ }
+ else
+ hrc = setErrorBoth(vrc == VERR_ENV_INVALID_VAR_NAME ? E_INVALIDARG : Global::vboxStatusCodeToCOM(vrc), vrc,
+ tr("Failed to apply environment variable '%s', index %u (%Rrc)'"),
+ aEnvironment[idxError].c_str(), idxError, vrc);
+ }
+ else
+ hrc = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestSession::processGet(ULONG aPid, ComPtr<IGuestProcess> &aGuestProcess)
+
+{
+ if (aPid == 0)
+ return setError(E_INVALIDARG, tr("No valid process ID (PID) specified"));
+
+ LogFlowThisFunc(("PID=%RU32\n", aPid));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+
+ ComObjPtr<GuestProcess> pProcess;
+ int vrc = i_processGetByPID(aPid, &pProcess);
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPid);
+
+ /* This will set (*aProcess) to NULL if pProgress is NULL. */
+ HRESULT hrc2 = pProcess.queryInterfaceTo(aGuestProcess.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = hrc2;
+
+ LogFlowThisFunc(("aProcess=%p, hrc=%Rhrc\n", (IGuestProcess*)aGuestProcess, hrc));
+ return hrc;
+}
+
+HRESULT GuestSession::symlinkCreate(const com::Utf8Str &aSource, const com::Utf8Str &aTarget, SymlinkType_T aType)
+{
+ RT_NOREF(aSource, aTarget, aType);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::symlinkExists(const com::Utf8Str &aSymlink, BOOL *aExists)
+
+{
+ RT_NOREF(aSymlink, aExists);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::symlinkRead(const com::Utf8Str &aSymlink, const std::vector<SymlinkReadFlag_T> &aFlags,
+ com::Utf8Str &aTarget)
+{
+ RT_NOREF(aSymlink, aFlags, aTarget);
+ ReturnComNotImplemented();
+}
+
+HRESULT GuestSession::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason)
+{
+ /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestSessionWaitResult_T waitResult;
+ int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ *aReason = waitResult;
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Session, vrcGuest, mData.mSession.mName.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Waiting for guest process failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_TIMEOUT:
+ *aReason = GuestSessionWaitResult_Timeout;
+ break;
+
+ default:
+ {
+ const char *pszSessionName = mData.mSession.mName.c_str();
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Waiting for guest session \"%s\" failed: %Rrc"),
+ pszSessionName ? pszSessionName : tr("Unnamed"), vrc);
+ break;
+ }
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestSession::waitForArray(const std::vector<GuestSessionWaitForFlag_T> &aWaitFor, ULONG aTimeoutMS,
+ GuestSessionWaitResult_T *aReason)
+{
+ /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */
+
+ LogFlowThisFuncEnter();
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ uint32_t fWaitFor = GuestSessionWaitForFlag_None;
+ for (size_t i = 0; i < aWaitFor.size(); i++)
+ fWaitFor |= aWaitFor[i];
+
+ return WaitFor(fWaitFor, aTimeoutMS, aReason);
+}
diff --git a/src/VBox/Main/src-client/GuestSessionImplTasks.cpp b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
new file mode 100644
index 00000000..59fbf06c
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
@@ -0,0 +1,3363 @@
+/* $Id: GuestSessionImplTasks.cpp $ */
+/** @file
+ * VirtualBox Main - Guest session tasks.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION
+#include "LoggingNew.h"
+
+#include "GuestImpl.h"
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestSessionImpl.h"
+#include "GuestSessionImplTasks.h"
+#include "GuestCtrlImplPrivate.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/env.h>
+#include <iprt/file.h> /* For CopyTo/From. */
+#include <iprt/dir.h>
+#include <iprt/path.h>
+#include <iprt/fsvfs.h>
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+
+/**
+ * (Guest Additions) ISO file flags.
+ * Needed for handling Guest Additions updates.
+ */
+#define ISOFILE_FLAG_NONE 0
+/** Copy over the file from host to the
+ * guest. */
+#define ISOFILE_FLAG_COPY_FROM_ISO RT_BIT(0)
+/** Execute file on the guest after it has
+ * been successfully transferred. */
+#define ISOFILE_FLAG_EXECUTE RT_BIT(7)
+/** File is optional, does not have to be
+ * existent on the .ISO. */
+#define ISOFILE_FLAG_OPTIONAL RT_BIT(8)
+
+
+// session task classes
+/////////////////////////////////////////////////////////////////////////////
+
+GuestSessionTask::GuestSessionTask(GuestSession *pSession)
+ : ThreadTask("GenericGuestSessionTask")
+{
+ mSession = pSession;
+
+ switch (mSession->i_getGuestPathStyle())
+ {
+ case PathStyle_DOS:
+ mstrGuestPathStyle = "\\";
+ break;
+
+ default:
+ mstrGuestPathStyle = "/";
+ break;
+ }
+}
+
+GuestSessionTask::~GuestSessionTask(void)
+{
+}
+
+/**
+ * Creates (and initializes / sets) the progress objects of a guest session task.
+ *
+ * @returns VBox status code.
+ * @param cOperations Number of operation the task wants to perform.
+ */
+int GuestSessionTask::createAndSetProgressObject(ULONG cOperations /* = 1 */)
+{
+ LogFlowThisFunc(("cOperations=%ld\n", cOperations));
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession),
+ Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw());
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ mProgress = pProgress;
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+#if 0 /* unused */
+/** @note The task object is owned by the thread after this returns, regardless of the result. */
+int GuestSessionTask::RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress)
+{
+ LogFlowThisFunc(("strDesc=%s\n", strDesc.c_str()));
+
+ mDesc = strDesc;
+ mProgress = pProgress;
+ HRESULT hrc = createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
+
+ LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc));
+ return Global::vboxStatusCodeToCOM(hrc);
+}
+#endif
+
+/**
+ * Gets a guest property from the VM.
+ *
+ * @returns VBox status code.
+ * @param pGuest Guest object of VM to get guest property from.
+ * @param strPath Guest property to path to get.
+ * @param strValue Where to store the guest property value on success.
+ */
+int GuestSessionTask::getGuestProperty(const ComObjPtr<Guest> &pGuest,
+ const Utf8Str &strPath, Utf8Str &strValue)
+{
+ ComObjPtr<Console> pConsole = pGuest->i_getConsole();
+ const ComPtr<IMachine> pMachine = pConsole->i_machine();
+
+ Assert(!pMachine.isNull());
+ Bstr strTemp, strFlags;
+ LONG64 i64Timestamp;
+ HRESULT hrc = pMachine->GetGuestProperty(Bstr(strPath).raw(), strTemp.asOutParam(), &i64Timestamp, strFlags.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ strValue = strTemp;
+ return VINF_SUCCESS;
+ }
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * Sets the percentage of a guest session task progress.
+ *
+ * @returns VBox status code.
+ * @param uPercent Percentage (0-100) to set.
+ */
+int GuestSessionTask::setProgress(ULONG uPercent)
+{
+ if (mProgress.isNull()) /* Progress is optional. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled;
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ return VERR_CANCELLED;
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && fCompleted)
+ {
+ AssertMsgFailed(("Setting value of an already completed progress\n"));
+ return VINF_SUCCESS;
+ }
+ HRESULT hrc = mProgress->SetCurrentOperationProgress(uPercent);
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the task's progress object to succeeded.
+ *
+ * @returns VBox status code.
+ */
+int GuestSessionTask::setProgressSuccess(void)
+{
+ if (mProgress.isNull()) /* Progress is optional. */
+ return VINF_SUCCESS;
+
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && !fCompleted)
+ {
+#ifdef VBOX_STRICT
+ ULONG uCurOp; mProgress->COMGETTER(Operation(&uCurOp));
+ ULONG cOps; mProgress->COMGETTER(OperationCount(&cOps));
+ AssertMsg(uCurOp + 1 /* Zero-based */ == cOps, ("Not all operations done yet (%u/%u)\n", uCurOp + 1, cOps));
+#endif
+ HRESULT hrc = mProgress->i_notifyComplete(S_OK);
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED; /** @todo Find a better vrc. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the task's progress object to an error using a string message.
+ *
+ * @returns Returns \a hrc for convenience.
+ * @param hrc Progress operation result to set.
+ * @param strMsg Message to set.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hrc, const Utf8Str &strMsg)
+{
+ LogFlowFunc(("hrc=%Rhrc, strMsg=%s\n", hrc, strMsg.c_str()));
+
+ if (mProgress.isNull()) /* Progress is optional. */
+ return hrc; /* Return original status. */
+
+ BOOL fCanceled;
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && !fCanceled
+ && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && !fCompleted)
+ {
+ HRESULT hrc2 = mProgress->i_notifyComplete(hrc,
+ COM_IIDOF(IGuestSession),
+ GuestSession::getStaticComponentName(),
+ /* Make sure to hand-in the message via format string to avoid problems
+ * with (file) paths which e.g. contain "%s" and friends. Can happen with
+ * randomly generated Validation Kit stuff. */
+ "%s", strMsg.c_str());
+ if (FAILED(hrc2))
+ return hrc2;
+ }
+ return hrc; /* Return original status. */
+}
+
+/**
+ * Sets the task's progress object to an error using a string message and a guest error info object.
+ *
+ * @returns Returns \a hrc for convenience.
+ * @param hrc Progress operation result to set.
+ * @param strMsg Message to set.
+ * @param guestErrorInfo Guest error info to use.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hrc, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo)
+{
+ return setProgressErrorMsg(hrc, strMsg + Utf8Str(": ") + GuestBase::getErrorAsString(guestErrorInfo));
+}
+
+/**
+ * Creates a directory on the guest.
+ *
+ * @return VBox status code.
+ * VINF_ALREADY_EXISTS if directory on the guest already exists (\a fCanExist is \c true).
+ * VWRN_ALREADY_EXISTS if directory on the guest already exists but must not exist (\a fCanExist is \c false).
+ * @param strPath Absolute path to directory on the guest (guest style path) to create.
+ * @param fMode Directory mode to use for creation.
+ * @param enmDirectoryCreateFlags Directory creation flags.
+ * @param fFollowSymlinks Whether to follow symlinks on the guest or not.
+ * @param fCanExist Whether the directory to create is allowed to exist already.
+ */
+int GuestSessionTask::directoryCreateOnGuest(const com::Utf8Str &strPath,
+ uint32_t fMode, DirectoryCreateFlag_T enmDirectoryCreateFlags,
+ bool fFollowSymlinks, bool fCanExist)
+{
+ LogFlowFunc(("strPath=%s, enmDirectoryCreateFlags=0x%x, fMode=%RU32, fFollowSymlinks=%RTbool, fCanExist=%RTbool\n",
+ strPath.c_str(), enmDirectoryCreateFlags, fMode, fFollowSymlinks, fCanExist));
+
+ GuestFsObjData objData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!fCanExist)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest directory \"%s\" already exists"), strPath.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ else
+ vrc = VWRN_ALREADY_EXISTS;
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ switch (vrcGuest)
+ {
+ case VERR_FILE_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_PATH_NOT_FOUND:
+ vrc = mSession->i_directoryCreate(strPath.c_str(), fMode, enmDirectoryCreateFlags, &vrcGuest);
+ break;
+ default:
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest error creating directory \"%s\" on the guest: %Rrc"),
+ strPath.c_str(), vrcGuest));
+ break;
+ }
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host error creating directory \"%s\" on the guest: %Rrc"),
+ strPath.c_str(), vrc));
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Creates a directory on the host.
+ *
+ * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists.
+ * @param strPath Absolute path to directory on the host (host style path) to create.
+ * @param fMode Directory mode to use for creation.
+ * @param fCreate Directory creation flags.
+ * @param fCanExist Whether the directory to create is allowed to exist already.
+ */
+int GuestSessionTask::directoryCreateOnHost(const com::Utf8Str &strPath, uint32_t fMode, uint32_t fCreate, bool fCanExist)
+{
+ LogFlowFunc(("strPath=%s, fMode=%RU32, fCreate=0x%x, fCanExist=%RTbool\n", strPath.c_str(), fMode, fCreate, fCanExist));
+
+ LogRel2(("Guest Control: Creating host directory \"%s\" ...\n", strPath.c_str()));
+
+ int vrc = RTDirCreate(strPath.c_str(), fMode, fCreate);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_ALREADY_EXISTS)
+ {
+ if (!fCanExist)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host directory \"%s\" already exists"), strPath.c_str()));
+ }
+ else
+ vrc = VINF_SUCCESS;
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Could not create host directory \"%s\": %Rrc"),
+ strPath.c_str(), vrc));
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for copying a file from guest to the host.
+ *
+ * @return VBox status code.
+ * @param strSrcFile Full path of source file on the host to copy.
+ * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already.
+ * @param strDstFile Full destination path and file name (guest style) to copy file to.
+ * @param phDstFile Pointer to host file handle (destination) to copy to. Must be in opened and ready state already.
+ * @param fFileCopyFlags File copy flags.
+ * @param offCopy Offset (in bytes) where to start copying the source file.
+ * @param cbSize Size (in bytes) to copy from the source file.
+ */
+int GuestSessionTask::fileCopyFromGuestInner(const Utf8Str &strSrcFile, ComObjPtr<GuestFile> &srcFile,
+ const Utf8Str &strDstFile, PRTFILE phDstFile,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize)
+{
+ RT_NOREF(fFileCopyFlags);
+
+ if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled = FALSE;
+ uint64_t cbWrittenTotal = 0;
+ uint64_t cbToRead = cbSize;
+
+ uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
+
+ int vrc = VINF_SUCCESS;
+
+ if (offCopy)
+ {
+ uint64_t offActual;
+ vrc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Seeking to offset %RU64 of guest file \"%s\" failed: %Rrc"),
+ offCopy, strSrcFile.c_str(), vrc));
+ return vrc;
+ }
+ }
+
+ BYTE byBuf[_64K]; /** @todo Can we do better here? */
+ while (cbToRead)
+ {
+ uint32_t cbRead;
+ const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
+ vrc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from guest \"%s\" failed: %Rrc", "", cbChunk),
+ cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc));
+ break;
+ }
+
+ vrc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing %RU32 bytes to host file \"%s\" failed: %Rrc", "", cbRead),
+ cbRead, strDstFile.c_str(), vrc));
+ break;
+ }
+
+ AssertBreak(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+
+ /* Update total bytes written to the guest. */
+ cbWrittenTotal += cbRead;
+ AssertBreak(cbWrittenTotal <= cbSize);
+
+ /* Did the user cancel the operation above? */
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ break;
+
+ AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR);
+ vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ return VINF_SUCCESS;
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Even if we succeeded until here make sure to check whether we really transferred
+ * everything.
+ */
+ if (cbWrittenTotal == 0)
+ {
+ /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write
+ * to the destination -> access denied. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing guest file \"%s\" to host file \"%s\" failed: Access denied"),
+ strSrcFile.c_str(), strDstFile.c_str()));
+ vrc = VERR_ACCESS_DENIED;
+ }
+ else if (cbWrittenTotal < cbSize)
+ {
+ /* If we did not copy all let the user know. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Copying guest file \"%s\" to host file \"%s\" failed (%RU64/%RU64 bytes transferred)"),
+ strSrcFile.c_str(), strDstFile.c_str(), cbWrittenTotal, cbSize));
+ vrc = VERR_INTERRUPTED;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Closes a formerly opened guest file.
+ *
+ * @returns VBox status code.
+ * @param file Guest file to close.
+ *
+ * @note Set a progress error message on error.
+ */
+int GuestSessionTask::fileClose(const ComObjPtr<GuestFile> &file)
+{
+ int vrcGuest;
+ int vrc = file->i_closeFile(&vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ Utf8Str strFilename;
+ HRESULT const hrc = file->getFilename(strFilename);
+ AssertComRCReturn(hrc, VERR_OBJECT_DESTROYED);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Error closing guest file \"%s\": %Rrc"),
+ strFilename.c_str(), vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc));
+ if (RT_SUCCESS(vrc))
+ vrc = vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc;
+ }
+
+ return vrc;
+}
+
+/**
+ * Copies a file from the guest to the host.
+ *
+ * @return VBox status code.
+ * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified,
+ * *or * the file at the destination has the same (or newer) modification time
+ * and FileCopyFlag_Update is specified.
+ * @param strSrc Full path of source file on the guest to copy.
+ * @param strDst Full destination path and file name (host style) to copy file to.
+ * @param fFileCopyFlags File copy flags.
+ */
+int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags)
+{
+ LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags));
+
+ GuestFileOpenInfo srcOpenInfo;
+ srcOpenInfo.mFilename = strSrc;
+ srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting;
+ srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly;
+ srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> srcFile;
+
+ GuestFsObjData srcObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = mSession->i_fsQueryInfo(strSrc, TRUE /* fFollowSymlinks */, srcObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file lookup failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file lookup for \"%s\" failed: %Rrc"), strSrc.c_str(), vrc));
+ }
+ else
+ {
+ switch (srcObjData.mType)
+ {
+ case FsObjType_File:
+ break;
+
+ case FsObjType_Symlink:
+ if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" is a symbolic link"),
+ strSrc.c_str()));
+ vrc = VERR_IS_A_SYMLINK;
+ }
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest object \"%s\" is not a file (is type %#x)"),
+ strSrc.c_str(), srcObjData.mType));
+ vrc = VERR_NOT_A_FILE;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = mSession->i_fileOpen(srcOpenInfo, srcFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file could not be opened"),
+ GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strSrc.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), strSrc.c_str(), vrc));
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ RTFSOBJINFO dstObjInfo;
+ RT_ZERO(dstObjInfo);
+
+ bool fSkip = false; /* Whether to skip handling the file. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfo(strDst.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ LogRel2(("Guest Control: Host file \"%s\" already exists, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+
+ if ( !fSkip
+ && fFileCopyFlags & FileCopyFlag_Update)
+ {
+ RTTIMESPEC srcModificationTimeTS;
+ RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime);
+ if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0)
+ {
+ LogRel2(("Guest Control: Host file \"%s\" has same or newer modification date, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_PATH_NOT_FOUND) /* Destination file does not exist (yet)? */
+ vrc = VERR_FILE_NOT_FOUND; /* Needed in next block further down. */
+ else if (vrc != VERR_FILE_NOT_FOUND) /* Ditto. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host file lookup for \"%s\" failed: %Rrc"), strDst.c_str(), vrc));
+ }
+ }
+
+ if (fSkip)
+ {
+ int vrc2 = fileClose(srcFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_FILE(dstObjInfo.Attr.fMode))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host file \"%s\" already exists"), strDst.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a directory"), strDst.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ }
+ else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode))
+ {
+ if (!(fFileCopyFlags & FileCopyFlag_FollowLinks))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a symbolic link"), strDst.c_str()));
+ vrc = VERR_IS_A_SYMLINK;
+ }
+ }
+ else
+ {
+ LogFlowThisFunc(("Host file system type %#x not supported\n", dstObjInfo.Attr.fMode & RTFS_TYPE_MASK));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlowFunc(("vrc=%Rrc, dstFsType=%#x, pszDstFile=%s\n", vrc, dstObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDst.c_str()));
+
+ if ( RT_SUCCESS(vrc)
+ || vrc == VERR_FILE_NOT_FOUND)
+ {
+ LogRel2(("Guest Control: Copying file \"%s\" from guest to \"%s\" on host ...\n", strSrc.c_str(), strDst.c_str()));
+
+ RTFILE hDstFile;
+ vrc = RTFileOpen(&hDstFile, strDst.c_str(),
+ RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n",
+ strSrc.c_str(), strDst.c_str(), srcObjData.mObjectSize));
+
+ vrc = fileCopyFromGuestInner(strSrc, srcFile, strDst, &hDstFile, fFileCopyFlags,
+ 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize);
+
+ int vrc2 = RTFileClose(hDstFile);
+ AssertRC(vrc2);
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Opening/creating host file \"%s\" failed: %Rrc"), strDst.c_str(), vrc));
+ }
+
+ int vrc2 = fileClose(srcFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Main function for copying a file from host to the guest.
+ *
+ * @return VBox status code.
+ * @param strSrcFile Full path of source file on the host to copy.
+ * @param hVfsFile The VFS file handle to read from.
+ * @param strDstFile Full destination path and file name (guest style) to copy file to.
+ * @param fileDst Guest file (destination) to copy to the guest. Must be in opened and ready state already.
+ * @param fFileCopyFlags File copy flags.
+ * @param offCopy Offset (in bytes) where to start copying the source file.
+ * @param cbSize Size (in bytes) to copy from the source file.
+ */
+int GuestSessionTask::fileCopyToGuestInner(const Utf8Str &strSrcFile, RTVFSFILE hVfsFile,
+ const Utf8Str &strDstFile, ComObjPtr<GuestFile> &fileDst,
+ FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize)
+{
+ RT_NOREF(fFileCopyFlags);
+
+ if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */
+ return VINF_SUCCESS;
+
+ BOOL fCanceled = FALSE;
+ uint64_t cbWrittenTotal = 0;
+ uint64_t cbToRead = cbSize;
+
+ uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */
+
+ int vrc = VINF_SUCCESS;
+
+ if (offCopy)
+ {
+ uint64_t offActual;
+ vrc = RTVfsFileSeek(hVfsFile, offCopy, RTFILE_SEEK_END, &offActual);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Seeking to offset %RU64 of host file \"%s\" failed: %Rrc"),
+ offCopy, strSrcFile.c_str(), vrc));
+ return vrc;
+ }
+ }
+
+ BYTE byBuf[_64K];
+ while (cbToRead)
+ {
+ size_t cbRead;
+ const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf));
+ vrc = RTVfsFileRead(hVfsFile, byBuf, cbChunk, &cbRead);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from host file \"%s\" failed: %Rrc"),
+ cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc));
+ break;
+ }
+
+ vrc = fileDst->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc"),
+ cbRead, strDstFile.c_str(), vrc));
+ break;
+ }
+
+ Assert(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+
+ /* Update total bytes written to the guest. */
+ cbWrittenTotal += cbRead;
+ Assert(cbWrittenTotal <= cbSize);
+
+ /* Did the user cancel the operation above? */
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && fCanceled)
+ break;
+
+ AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR);
+ vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /*
+ * Even if we succeeded until here make sure to check whether we really transferred
+ * everything.
+ */
+ if (cbWrittenTotal == 0)
+ {
+ /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write
+ * to the destination -> access denied. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Writing to guest file \"%s\" failed: Access denied"),
+ strDstFile.c_str()));
+ vrc = VERR_ACCESS_DENIED;
+ }
+ else if (cbWrittenTotal < cbSize)
+ {
+ /* If we did not copy all let the user know. */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Copying to guest file \"%s\" failed (%RU64/%RU64 bytes transferred)"),
+ strDstFile.c_str(), cbWrittenTotal, cbSize));
+ vrc = VERR_INTERRUPTED;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Copies a file from the host to the guest.
+ *
+ * @return VBox status code.
+ * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified,
+ * *or * the file at the destination has the same (or newer) modification time
+ * and FileCopyFlag_Update is specified.
+ * @param strSrc Full path of source file on the host.
+ * @param strDst Full destination path and file name (guest style) to copy file to. Guest-path style.
+ * @param fFileCopyFlags File copy flags.
+ */
+int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags)
+{
+ LogFlowThisFunc(("strSource=%s, strDst=%s, fFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags));
+
+ GuestFileOpenInfo dstOpenInfo;
+ dstOpenInfo.mFilename = strDst;
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateNew;
+ else
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
+ dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
+ dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> dstFile;
+ int vrcGuest;
+ int vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced"), strDst.c_str()),
+ GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strDst.c_str()));
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced: %Rrc"), strDst.c_str(), vrc));
+ return vrc;
+ }
+
+ char szSrcReal[RTPATH_MAX];
+
+ RTFSOBJINFO srcObjInfo;
+ RT_ZERO(srcObjInfo);
+
+ bool fSkip = false; /* Whether to skip handling the file. */
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathReal(strSrc.c_str(), szSrcReal, sizeof(szSrcReal));
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host path lookup for file \"%s\" failed: %Rrc"),
+ strSrc.c_str(), vrc));
+ }
+ else
+ {
+ vrc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Only perform a remote file query when needed. */
+ if ( (fFileCopyFlags & FileCopyFlag_Update)
+ || (fFileCopyFlags & FileCopyFlag_NoReplace))
+ {
+ GuestFsObjData dstObjData;
+ vrc = mSession->i_fileQueryInfo(strDst, RT_BOOL(fFileCopyFlags & FileCopyFlag_FollowLinks), dstObjData,
+ &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (fFileCopyFlags & FileCopyFlag_NoReplace)
+ {
+ LogRel2(("Guest Control: Guest file \"%s\" already exists, skipping\n", strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+
+ if ( !fSkip
+ && fFileCopyFlags & FileCopyFlag_Update)
+ {
+ RTTIMESPEC dstModificationTimeTS;
+ RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime);
+ if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0)
+ {
+ LogRel2(("Guest Control: Guest file \"%s\" has same or newer modification date, skipping\n",
+ strDst.c_str()));
+ vrc = VWRN_ALREADY_EXISTS;
+ fSkip = true;
+ }
+ }
+ }
+ else
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ switch (vrcGuest)
+ {
+ case VERR_FILE_NOT_FOUND:
+ vrc = VINF_SUCCESS;
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest error while determining object data for guest file \"%s\": %Rrc"),
+ strDst.c_str(), vrcGuest));
+ break;
+ }
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host error while determining object data for guest file \"%s\": %Rrc"),
+ strDst.c_str(), vrc));
+ }
+ }
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host source file lookup for \"%s\" failed: %Rrc"),
+ szSrcReal, vrc));
+ }
+ }
+ }
+
+ if (fSkip)
+ {
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Copying file \"%s\" from host to \"%s\" on guest ...\n", strSrc.c_str(), strDst.c_str()));
+
+ RTVFSFILE hSrcFile;
+ vrc = RTVfsFileOpenNormal(szSrcReal, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hSrcFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n",
+ szSrcReal, strDst.c_str(), srcObjInfo.cbObject));
+
+ vrc = fileCopyToGuestInner(szSrcReal, hSrcFile, strDst, dstFile,
+ fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject);
+
+ int vrc2 = RTVfsFileRelease(hSrcFile);
+ AssertRC(vrc2);
+ }
+ else
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Opening host file \"%s\" failed: %Rrc"),
+ szSrcReal, vrc));
+ }
+
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Adds a guest file system entry to a given list.
+ *
+ * @return VBox status code.
+ * @param strFile Path to file system entry to add.
+ * @param fsObjData Guest file system information of entry to add.
+ */
+int FsList::AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData)
+{
+ LogFlowFunc(("Adding \"%s\"\n", strFile.c_str()));
+
+ FsEntry *pEntry = NULL;
+ try
+ {
+ pEntry = new FsEntry();
+ pEntry->fMode = fsObjData.GetFileMode();
+ pEntry->strPath = strFile;
+
+ mVecEntries.push_back(pEntry);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pEntry)
+ delete pEntry;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds a host file system entry to a given list.
+ *
+ * @return VBox status code.
+ * @param strFile Path to file system entry to add.
+ * @param pcObjInfo File system information of entry to add.
+ */
+int FsList::AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo)
+{
+ LogFlowFunc(("Adding \"%s\"\n", strFile.c_str()));
+
+ FsEntry *pEntry = NULL;
+ try
+ {
+ pEntry = new FsEntry();
+ pEntry->fMode = pcObjInfo->Attr.fMode;
+ pEntry->strPath = strFile;
+
+ mVecEntries.push_back(pEntry);
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pEntry)
+ delete pEntry;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+FsList::FsList(const GuestSessionTask &Task)
+ : mTask(Task)
+{
+}
+
+FsList::~FsList()
+{
+ Destroy();
+}
+
+/**
+ * Initializes a file list.
+ *
+ * @return VBox status code.
+ * @param strSrcRootAbs Source root path (absolute) for this file list.
+ * @param strDstRootAbs Destination root path (absolute) for this file list.
+ * @param SourceSpec Source specification to use.
+ */
+int FsList::Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs,
+ const GuestSessionFsSourceSpec &SourceSpec)
+{
+ mSrcRootAbs = strSrcRootAbs;
+ mDstRootAbs = strDstRootAbs;
+ mSourceSpec = SourceSpec;
+
+ /* Note: Leave the source and dest roots unmodified -- how paths will be treated
+ * will be done directly when working on those. See @bugref{10139}. */
+
+ LogFlowFunc(("mSrcRootAbs=%s, mDstRootAbs=%s, fDirCopyFlags=%#x, fFileCopyFlags=%#x\n",
+ mSrcRootAbs.c_str(), mDstRootAbs.c_str(), mSourceSpec.fDirCopyFlags, mSourceSpec.fFileCopyFlags));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a file list.
+ */
+void FsList::Destroy(void)
+{
+ LogFlowFuncEnter();
+
+ FsEntries::iterator itEntry = mVecEntries.begin();
+ while (itEntry != mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ delete pEntry;
+ mVecEntries.erase(itEntry);
+ itEntry = mVecEntries.begin();
+ }
+
+ Assert(mVecEntries.empty());
+
+ LogFlowFuncLeave();
+}
+
+#ifdef DEBUG
+/**
+ * Dumps a FsList to the debug log.
+ */
+void FsList::DumpToLog(void)
+{
+ LogFlowFunc(("strSrcRootAbs=%s, strDstRootAbs=%s\n", mSrcRootAbs.c_str(), mDstRootAbs.c_str()));
+
+ FsEntries::iterator itEntry = mVecEntries.begin();
+ while (itEntry != mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ LogFlowFunc(("\tstrPath=%s (fMode %#x)\n", pEntry->strPath.c_str(), pEntry->fMode));
+ ++itEntry;
+ }
+
+ LogFlowFuncLeave();
+}
+#endif /* DEBUG */
+
+/**
+ * Builds a guest file list from a given path (and optional filter).
+ *
+ * @return VBox status code.
+ * @param strPath Directory on the guest to build list from.
+ * @param strSubDir Current sub directory path; needed for recursion.
+ * Set to an empty path.
+ */
+int FsList::AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir /* = "" */)
+{
+ Utf8Str strPathAbs = strPath;
+ if (!strPathAbs.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle)))
+ strPathAbs += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle);
+
+ Utf8Str strPathSub = strSubDir;
+ if ( strPathSub.isNotEmpty()
+ && !strPathSub.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle)))
+ strPathSub += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle);
+
+ strPathAbs += strPathSub;
+
+ LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str()));
+
+ LogRel2(("Guest Control: Handling directory \"%s\" on guest ...\n", strPathAbs.c_str()));
+
+ GuestDirectoryOpenInfo dirOpenInfo;
+ dirOpenInfo.mFilter = "";
+ dirOpenInfo.mPath = strPathAbs;
+ dirOpenInfo.mFlags = 0; /** @todo Handle flags? */
+
+ const ComObjPtr<GuestSession> &pSession = mTask.GetSession();
+
+ ComObjPtr <GuestDirectory> pDir;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = pSession->i_directoryOpen(dirOpenInfo, pDir, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_INVALID_PARAMETER:
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ break;
+
+ default:
+ break;
+ }
+
+ return vrc;
+ }
+
+ if (strPathSub.isNotEmpty())
+ {
+ GuestFsObjData fsObjData;
+ fsObjData.mType = FsObjType_Directory;
+
+ vrc = AddEntryFromGuest(strPathSub, fsObjData);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ ComObjPtr<GuestFsObjInfo> fsObjInfo;
+ while (RT_SUCCESS(vrc = pDir->i_read(fsObjInfo, &vrcGuest)))
+ {
+ FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */
+ HRESULT hrc2 = fsObjInfo->COMGETTER(Type)(&enmObjType);
+ AssertComRC(hrc2);
+
+ com::Bstr bstrName;
+ hrc2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam());
+ AssertComRC(hrc2);
+
+ Utf8Str strEntry = strPathSub + Utf8Str(bstrName);
+
+ LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str()));
+
+ switch (enmObjType)
+ {
+ case FsObjType_Directory:
+ {
+ if ( bstrName.equals(".")
+ || bstrName.equals(".."))
+ {
+ break;
+ }
+
+ LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str()));
+
+ if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive))
+ break;
+
+ vrc = AddDirFromGuest(strPath, strEntry);
+ break;
+ }
+
+ case FsObjType_Symlink:
+ {
+ if ( mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks
+ || mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks)
+ {
+ /** @todo Symlink handling from guest is not implemented yet.
+ * See IGuestSession::symlinkRead(). */
+ LogRel2(("Guest Control: Warning: Symlink support on guest side not available, skipping \"%s\"\n",
+ strEntry.c_str()));
+ }
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str()));
+
+ vrc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData());
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (vrc == VERR_NO_MORE_FILES) /* End of listing reached? */
+ vrc = VINF_SUCCESS;
+ }
+
+ int vrc2 = pDir->i_closeInternal(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ return vrc;
+}
+
+/**
+ * Builds a host file list from a given path.
+ *
+ * @return VBox status code.
+ * @param strPath Directory on the host to build list from.
+ * @param strSubDir Current sub directory path; needed for recursion.
+ * Set to an empty path.
+ * @param pszPathReal Scratch buffer for holding the resolved real path.
+ * Needed for recursion.
+ * @param cbPathReal Size (in bytes) of \a pszPathReal.
+ * @param pDirEntry Where to store looked up directory information for handled paths.
+ * Needed for recursion.
+ */
+int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir,
+ char *pszPathReal, size_t cbPathReal, PRTDIRENTRYEX pDirEntry)
+{
+ Utf8Str strPathAbs = strPath;
+ if (!strPathAbs.endsWith(RTPATH_SLASH_STR))
+ strPathAbs += RTPATH_SLASH_STR;
+
+ Utf8Str strPathSub = strSubDir;
+ if ( strPathSub.isNotEmpty()
+ && !strPathSub.endsWith(RTPATH_SLASH_STR))
+ strPathSub += RTPATH_SLASH_STR;
+
+ strPathAbs += strPathSub;
+
+ LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str()));
+
+ LogRel2(("Guest Control: Handling directory \"%s\" on host ...\n", strPathAbs.c_str()));
+
+ RTFSOBJINFO objInfo;
+ int vrc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (strPathSub.isNotEmpty())
+ vrc = AddEntryFromHost(strPathSub, &objInfo);
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, strPathAbs.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ do
+ {
+ /* Retrieve the next directory entry. */
+ vrc = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_NO_MORE_FILES)
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ Utf8Str strEntry = strPathSub + Utf8Str(pDirEntry->szName);
+
+ LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str()));
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ break;
+
+ LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str()));
+
+ if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive))
+ break;
+
+ vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str()));
+
+ vrc = AddEntryFromHost(strEntry, &pDirEntry->Info);
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ Utf8Str strEntryAbs = strPathAbs + (const char *)pDirEntry->szName;
+
+ vrc = RTPathReal(strEntryAbs.c_str(), pszPathReal, cbPathReal);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfo(pszPathReal, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (directory)\n",
+ strEntryAbs.c_str(), pszPathReal));
+ if (mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks)
+ vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (file)\n",
+ strEntryAbs.c_str(), pszPathReal));
+ if (mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks)
+ vrc = AddEntryFromHost(strEntry, &objInfo);
+ }
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel2(("Guest Control: Unable to query symbolic link info for \"%s\", vrc=%Rrc\n",
+ pszPathReal, vrc));
+ }
+ else
+ {
+ LogRel2(("Guest Control: Unable to resolve symlink for \"%s\", vrc=%Rrc\n",
+ strPathAbs.c_str(), vrc));
+ if (vrc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */
+ vrc = VINF_SUCCESS;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ } while (RT_SUCCESS(vrc));
+
+ RTDirClose(hDir);
+ }
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ vrc = VERR_IS_A_FILE;
+ else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ vrc = VERR_IS_A_SYMLINK;
+ else
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ LogFlowFunc(("Unable to query \"%s\", vrc=%Rrc\n", strPathAbs.c_str(), vrc));
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS)
+ : GuestSessionTask(pSession)
+ , mFlags(uFlags)
+ , mTimeoutMS(uTimeoutMS)
+{
+ m_strTaskName = "gctlSesOpen";
+}
+
+GuestSessionTaskOpen::~GuestSessionTaskOpen(void)
+{
+
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskOpen::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ int vrc = mSession->i_startSession(NULL /*pvrcGuest*/);
+ /* Nothing to do here anymore. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionCopyTask::GuestSessionCopyTask(GuestSession *pSession)
+ : GuestSessionTask(pSession)
+{
+}
+
+GuestSessionCopyTask::~GuestSessionCopyTask()
+{
+ FsLists::iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pFsList = (*itList);
+ pFsList->Destroy();
+ delete pFsList;
+ mVecLists.erase(itList);
+ itList = mVecLists.begin();
+ }
+
+ Assert(mVecLists.empty());
+}
+
+GuestSessionTaskCopyFrom::GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc,
+ const Utf8Str &strDest)
+ : GuestSessionCopyTask(pSession)
+{
+ m_strTaskName = "gctlCpyFrm";
+
+ mSources = vecSrc;
+ mDest = strDest;
+}
+
+GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void)
+{
+}
+
+/**
+ * Initializes a copy-from-guest task.
+ *
+ * @returns HRESULT
+ * @param strTaskDesc Friendly task description.
+ */
+HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc)
+{
+ setTaskDesc(strTaskDesc);
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ mProgress = pProgress;
+
+ int vrc = VINF_SUCCESS;
+
+ ULONG cOperations = 0;
+ Utf8Str strErrorInfo;
+
+ /**
+ * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyFrom::Run
+ * because the caller expects a ready-for-operation progress object on return.
+ * The progress object will have a variable operation count, based on the elements to
+ * be processed.
+ */
+
+ if (mSources.empty())
+ {
+ strErrorInfo.printf(tr("No guest sources specified"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else if (mDest.isEmpty())
+ {
+ strErrorInfo.printf(tr("Host destination must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
+ while (itSrc != mSources.end())
+ {
+ Utf8Str strSrc = itSrc->strSource;
+ Utf8Str strDst = mDest;
+
+ bool fFollowSymlinks;
+
+ if (strSrc.isEmpty())
+ {
+ strErrorInfo.printf(tr("Guest source entry must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks;
+ }
+ else
+ {
+ fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+
+ LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s, fFollowSymlinks=%RTbool\n",
+ strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str(), fFollowSymlinks));
+
+ GuestFsObjData srcObjData;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = mSession->i_fsQueryInfo(strSrc, fFollowSymlinks, srcObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ strErrorInfo = GuestBase::getErrorAsString(tr("Guest source lookup failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str()));
+ else
+ strErrorInfo.printf(tr("Guest source lookup for \"%s\" failed: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+
+ if (srcObjData.mType == FsObjType_Directory)
+ {
+ if (itSrc->enmType != FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Guest source is not a file: %s"), strSrc.c_str());
+ vrc = VERR_NOT_A_FILE;
+ break;
+ }
+ }
+ else
+ {
+ if (itSrc->enmType != FsObjType_File)
+ {
+ strErrorInfo.printf(tr("Guest source is not a directory: %s"), strSrc.c_str());
+ vrc = VERR_NOT_A_DIRECTORY;
+ break;
+ }
+ }
+
+ FsList *pFsList = NULL;
+ try
+ {
+ pFsList = new FsList(*this);
+ vrc = pFsList->Init(strSrc, strDst, *itSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (itSrc->enmType)
+ {
+ case FsObjType_Directory:
+ {
+ vrc = pFsList->AddDirFromGuest(strSrc);
+ break;
+ }
+
+ case FsObjType_File:
+ /* The file name is already part of the actual list's source root (strSrc). */
+ break;
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown guest file system type %#x for source \"%s\", skipping\n",
+ itSrc->enmType, strSrc.c_str()));
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ delete pFsList;
+ strErrorInfo.printf(tr("Error adding guest source \"%s\" to list: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+#ifdef DEBUG
+ pFsList->DumpToLog();
+#endif
+ mVecLists.push_back(pFsList);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ AssertPtr(pFsList);
+ cOperations += (ULONG)pFsList->mVecEntries.size();
+
+ itSrc++;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* When there are no entries in the first source list, this means the source only contains a single file
+ * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */
+ Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0
+ ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs;
+
+ /* Now that we know how many objects we're handling, tweak the progress description so that it
+ * reflects more accurately what the progress is actually doing. */
+ if (cOperations > 1)
+ {
+ mDesc.printf(tr("Copying \"%s\" [and %zu %s] from guest to \"%s\" on the host ..."),
+ strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str());
+ }
+ else
+ mDesc.printf(tr("Copying \"%s\" from guest to \"%s\" on the host ..."), strFirstOp.c_str(), mDest.c_str());
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, Bstr(strFirstOp).raw());
+ }
+ else /* On error we go with an "empty" progress object when will be used for error handling. */
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
+
+ if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */
+ return hrc;
+
+ if (RT_FAILURE(vrc))
+ {
+ if (strErrorInfo.isEmpty())
+ strErrorInfo.printf(tr("Failed with %Rrc"), vrc);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
+ }
+
+ LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc));
+ return hrc;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskCopyFrom::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ int vrc = VINF_SUCCESS;
+
+ FsLists::const_iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pList = *itList;
+ AssertPtr(pList);
+
+ LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
+
+ Utf8Str strSrcRootAbs = pList->mSrcRootAbs;
+ Utf8Str strDstRootAbs = pList->mDstRootAbs;
+
+ vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, mSession->i_getGuestPathStyle() /* Source */,
+ strDstRootAbs, PATH_STYLE_NATIVE /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Building host destination root path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ bool fCopyIntoExisting;
+ bool fFollowSymlinks;
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks);
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+ else
+ AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED);
+
+ uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
+ uint32_t fDirCreate = 0;
+
+ bool fDstExists = true;
+
+ RTFSOBJINFO dstFsObjInfo;
+ RT_ZERO(dstFsObjInfo);
+ vrc = RTPathQueryInfoEx(strDstRootAbs.c_str(), &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ char szPathReal[RTPATH_MAX];
+ vrc = RTPathReal(strDstRootAbs.c_str(), szPathReal, sizeof(szPathReal));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfoEx(szPathReal, &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK /* fFlags */);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Host destination is a symbolic link \"%s\" -> \"%s\" (%s)\n",
+ strDstRootAbs.c_str(), szPathReal,
+ GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+ }
+
+ strDstRootAbs = szPathReal;
+ }
+ }
+ else
+ {
+ if ( vrc == VERR_FILE_NOT_FOUND
+ || vrc == VERR_PATH_NOT_FOUND)
+ {
+ fDstExists = false;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host path lookup for \"%s\" failed: %Rrc"), strDstRootAbs.c_str(), vrc));
+ break;
+ }
+ }
+
+ /* Create the root directory. */
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+
+ if (fDstExists)
+ {
+ switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host root directory \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ break;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a file"), strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_FILE;
+ break;
+ }
+
+ default:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""),
+ dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Make sure the destination root directory exists. */
+ if (pList->mSourceSpec.fDryRun == false)
+ {
+ vrc = directoryCreateOnHost(strDstRootAbs, fDirMode, 0 /* fCreate */, true /* fCanExist */);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ AssertBreakStmt(pList->mSourceSpec.enmType == FsObjType_Directory, vrc = VERR_NOT_SUPPORTED);
+
+ /* Walk the entries. */
+ FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
+ while (itEntry != pList->mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ AssertPtr(pEntry);
+
+ Utf8Str strSrcAbs = strSrcRootAbs;
+ Utf8Str strDstAbs = strDstRootAbs;
+
+ strSrcAbs += PATH_STYLE_SEP_STR(pList->mSourceSpec.enmPathStyle);
+ strSrcAbs += pEntry->strPath;
+
+ strDstAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE);
+ strDstAbs += pEntry->strPath;
+
+ /* Clean up the final guest source path. */
+ vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */,
+ pList->mSourceSpec.enmPathStyle /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest source path \"%s\" failed: %Rrc"),
+ strSrcAbs.c_str(), vrc));
+ break;
+ }
+
+ /* Translate the final host desitnation path. */
+ vrc = GuestPath::Translate(strDstAbs, mSession->i_getGuestPathStyle() /* Source */, PATH_STYLE_NATIVE /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"),
+ strDstAbs.c_str(), vrc));
+ break;
+ }
+
+ mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
+
+ switch (pEntry->fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = directoryCreateOnHost(strDstAbs, fDirMode, fDirCreate, fCopyIntoExisting);
+ break;
+
+ case RTFS_TYPE_FILE:
+ RT_FALL_THROUGH();
+ case RTFS_TYPE_SYMLINK:
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyFromGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags);
+ break;
+
+ default:
+ AssertFailed(); /* Should never happen (we already have a filtered list). */
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itEntry;
+ }
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode))));
+
+ if (fDstExists)
+ {
+ switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a directory"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Host file \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ default:
+ {
+ /** @todo Resolve symlinks? */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""),
+ dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Translate the final host destination file path. */
+ vrc = GuestPath::Translate(strDstRootAbs,
+ mSession->i_getGuestPathStyle() /* Dest */, PATH_STYLE_NATIVE /* Source */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyFromGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags);
+ }
+ }
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itList;
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgressSuccess();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc,
+ const Utf8Str &strDest)
+ : GuestSessionCopyTask(pSession)
+{
+ m_strTaskName = "gctlCpyTo";
+
+ mSources = vecSrc;
+ mDest = strDest;
+}
+
+GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void)
+{
+}
+
+/**
+ * Initializes a copy-to-guest task.
+ *
+ * @returns HRESULT
+ * @param strTaskDesc Friendly task description.
+ */
+HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc)
+{
+ LogFlowFuncEnter();
+
+ setTaskDesc(strTaskDesc);
+
+ /* Create the progress object. */
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ mProgress = pProgress;
+
+ int vrc = VINF_SUCCESS;
+
+ ULONG cOperations = 0;
+ Utf8Str strErrorInfo;
+
+ /*
+ * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyTo::Run
+ * because the caller expects a ready-for-operation progress object on return.
+ * The progress object will have a variable operation count, based on the elements to
+ * be processed.
+ */
+
+ if (mSources.empty())
+ {
+ strErrorInfo.printf(tr("No host sources specified"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else if (mDest.isEmpty())
+ {
+ strErrorInfo.printf(tr("Guest destination must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ GuestSessionFsSourceSet::iterator itSrc = mSources.begin();
+ while (itSrc != mSources.end())
+ {
+ Utf8Str strSrc = itSrc->strSource;
+ Utf8Str strDst = mDest;
+
+ bool fFollowSymlinks;
+
+ if (strSrc.isEmpty())
+ {
+ strErrorInfo.printf(tr("Host source entry must not be empty"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks;
+ }
+ else
+ {
+ fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+
+ LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s\n",
+ strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str()));
+
+ RTFSOBJINFO srcFsObjInfo;
+ vrc = RTPathQueryInfoEx(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */);
+ if (RT_FAILURE(vrc))
+ {
+ strErrorInfo.printf(tr("No such host file/directory: %s"), strSrc.c_str());
+ break;
+ }
+
+ switch (srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (itSrc->enmType != FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is not a file (is a directory)"), strSrc.c_str());
+ vrc = VERR_NOT_A_FILE;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (itSrc->enmType == FsObjType_Directory)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is not a directory (is a file)"), strSrc.c_str());
+ vrc = VERR_NOT_A_DIRECTORY;
+ }
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (!fFollowSymlinks)
+ {
+ strErrorInfo.printf(tr("Host source \"%s\" is a symbolic link"), strSrc.c_str());
+ vrc = VERR_IS_A_SYMLINK;
+ break;
+ }
+
+ char szPathReal[RTPATH_MAX];
+ vrc = RTPathReal(strSrc.c_str(), szPathReal, sizeof(szPathReal));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathQueryInfoEx(szPathReal, &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Guest Control: Host source is a symbolic link \"%s\" -> \"%s\" (%s)\n",
+ strSrc.c_str(), szPathReal,
+ GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode))));
+
+ /* We want to keep the symbolic link name of the source instead of the target pointing to,
+ * so don't touch the source's name here. */
+ itSrc->enmType = GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode);
+ }
+ else
+ {
+ strErrorInfo.printf(tr("Querying symbolic link info for host source \"%s\" failed"), strSrc.c_str());
+ break;
+ }
+ }
+ else
+ {
+ strErrorInfo.printf(tr("Resolving symbolic link for host source \"%s\" failed"), strSrc.c_str());
+ break;
+ }
+ break;
+ }
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown host file system type %#x for source \"%s\", skipping\n",
+ srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strSrc.c_str()));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ FsList *pFsList = NULL;
+ try
+ {
+ pFsList = new FsList(*this);
+ vrc = pFsList->Init(strSrc, strDst, *itSrc);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (itSrc->enmType)
+ {
+ case FsObjType_Directory:
+ {
+ char szPathReal[RTPATH_MAX];
+ RTDIRENTRYEX DirEntry;
+ vrc = pFsList->AddDirFromHost(strSrc /* strPath */, "" /* strSubDir */,
+ szPathReal, sizeof(szPathReal), &DirEntry);
+ break;
+ }
+
+ case FsObjType_File:
+ /* The file name is already part of the actual list's source root (strSrc). */
+ break;
+
+ case FsObjType_Symlink:
+ AssertFailed(); /* Should never get here, as we do the resolving above. */
+ break;
+
+ default:
+ LogRel2(("Guest Control: Warning: Unknown source type %#x for host source \"%s\", skipping\n",
+ itSrc->enmType, strSrc.c_str()));
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ delete pFsList;
+ strErrorInfo.printf(tr("Error adding host source \"%s\" to list: %Rrc"),
+ strSrc.c_str(), vrc);
+ break;
+ }
+#ifdef DEBUG
+ pFsList->DumpToLog();
+#endif
+ mVecLists.push_back(pFsList);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ AssertPtr(pFsList);
+ cOperations += (ULONG)pFsList->mVecEntries.size();
+
+ itSrc++;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* When there are no entries in the first source list, this means the source only contains a single file
+ * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */
+ Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0
+ ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs;
+
+ /* Now that we know how many objects we're handling, tweak the progress description so that it
+ * reflects more accurately what the progress is actually doing. */
+ if (cOperations > 1)
+ {
+ mDesc.printf(tr("Copying \"%s\" [and %zu %s] from host to \"%s\" on the guest ..."),
+ strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str());
+ }
+ else
+ mDesc.printf(tr("Copying \"%s\" from host to \"%s\" on the guest ..."), strFirstOp.c_str(), mDest.c_str());
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations + 1/* Number of operations */,
+ Bstr(strFirstOp).raw());
+ }
+ else /* On error we go with an "empty" progress object when will be used for error handling. */
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw());
+
+ if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */
+ return hrc;
+
+ if (RT_FAILURE(vrc))
+ {
+ if (strErrorInfo.isEmpty())
+ strErrorInfo.printf(tr("Failed with %Rrc"), vrc);
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo);
+ }
+
+ LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc));
+ return hrc;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskCopyTo::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(mSession);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ int vrc = VINF_SUCCESS;
+
+ FsLists::const_iterator itList = mVecLists.begin();
+ while (itList != mVecLists.end())
+ {
+ FsList *pList = *itList;
+ AssertPtr(pList);
+
+ LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str()));
+
+ Utf8Str strSrcRootAbs = pList->mSrcRootAbs;
+ Utf8Str strDstRootAbs = pList->mDstRootAbs;
+
+ vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, PATH_STYLE_NATIVE /* Source */,
+ strDstRootAbs, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Building guest destination root path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ bool fCopyIntoExisting;
+ bool fFollowSymlinks;
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks);
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace);
+ fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks);
+ }
+ else
+ AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED);
+
+ uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */
+
+ bool fDstExists = true;
+
+ GuestFsObjData dstObjData;
+ int vrcGuest;
+ vrc = mSession->i_fsQueryInfo(strDstRootAbs, fFollowSymlinks, dstObjData, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_GSTCTL_GUEST_ERROR)
+ {
+ switch (vrcGuest)
+ {
+ case VERR_PATH_NOT_FOUND:
+ RT_FALL_THROUGH();
+ case VERR_FILE_NOT_FOUND:
+ {
+ fDstExists = false;
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrcGuest));
+ break;
+ }
+ }
+ else
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+ }
+
+ if (pList->mSourceSpec.enmType == FsObjType_Directory)
+ {
+ LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType)));
+
+ if (fDstExists)
+ {
+ switch (dstObjData.mType)
+ {
+ case FsObjType_Directory:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest root directory \"%s\" already exists"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on guest already exists and is a file"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_FILE;
+ }
+
+ case FsObjType_Symlink:
+ /** @todo Resolve symlinks? */
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unknown object type (%#x) on guest for \"%s\""),
+ dstObjData.mType, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ /* Make sure the destination root directory exists. */
+ if (pList->mSourceSpec.fDryRun == false)
+ {
+ vrc = directoryCreateOnGuest(strDstRootAbs, fDirMode, DirectoryCreateFlag_None,
+ fFollowSymlinks, fCopyIntoExisting);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ /* Walk the entries. */
+ FsEntries::const_iterator itEntry = pList->mVecEntries.begin();
+ while ( RT_SUCCESS(vrc)
+ && itEntry != pList->mVecEntries.end())
+ {
+ FsEntry *pEntry = *itEntry;
+ AssertPtr(pEntry);
+
+ Utf8Str strSrcAbs = strSrcRootAbs;
+ Utf8Str strDstAbs = strDstRootAbs;
+
+ strSrcAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE);
+ strSrcAbs += pEntry->strPath;
+
+ strDstAbs += PATH_STYLE_SEP_STR(mSession->i_getGuestPathStyle());
+ strDstAbs += pEntry->strPath;
+
+ /* Clean up the final host source path. */
+ vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */,
+ pList->mSourceSpec.enmPathStyle /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating host source path\"%s\" failed: %Rrc"),
+ strSrcAbs.c_str(), vrc));
+ break;
+ }
+
+ /* Translate final guest destination path. */
+ vrc = GuestPath::Translate(strDstAbs,
+ PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"),
+ strDstAbs.c_str(), vrc));
+ break;
+ }
+
+ mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1);
+
+ switch (pEntry->fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ LogRel2(("Guest Control: Copying directory \"%s\" from host to \"%s\" on guest ...\n",
+ strSrcAbs.c_str(), strDstAbs.c_str()));
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = directoryCreateOnGuest(strDstAbs, fDirMode, DirectoryCreateFlag_None,
+ fFollowSymlinks, fCopyIntoExisting);
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyToGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags);
+ break;
+ }
+
+ default:
+ LogRel2(("Guest Control: Warning: Host file system type 0x%x for \"%s\" is not supported, skipping\n",
+ pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str()));
+ break;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itEntry;
+ }
+ }
+ else if (pList->mSourceSpec.enmType == FsObjType_File)
+ {
+ LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n",
+ pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks,
+ fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType)));
+
+ if (fDstExists)
+ {
+ switch (dstObjData.mType)
+ {
+ case FsObjType_Directory:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Destination \"%s\" on the guest already exists and is a directory"),
+ strDstRootAbs.c_str()));
+ vrc = VERR_IS_A_DIRECTORY;
+ break;
+ }
+
+ case FsObjType_File:
+ {
+ if (!fCopyIntoExisting)
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" already exists"), strDstRootAbs.c_str()));
+ vrc = VERR_ALREADY_EXISTS;
+ }
+ break;
+ }
+
+ default:
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unsupported guest file system type (%#x) for \"%s\""),
+ dstObjData.mType, strDstRootAbs.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Translate the final guest destination file path. */
+ vrc = GuestPath::Translate(strDstRootAbs,
+ PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */);
+ if (RT_FAILURE(vrc))
+ {
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"),
+ strDstRootAbs.c_str(), vrc));
+ break;
+ }
+
+ if (!pList->mSourceSpec.fDryRun)
+ vrc = fileCopyToGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags);
+ }
+ }
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ if (RT_FAILURE(vrc))
+ break;
+
+ ++itList;
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgressSuccess();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+GuestSessionTaskUpdateAdditions::GuestSessionTaskUpdateAdditions(GuestSession *pSession,
+ const Utf8Str &strSource,
+ const ProcessArguments &aArguments,
+ uint32_t fFlags)
+ : GuestSessionTask(pSession)
+{
+ m_strTaskName = "gctlUpGA";
+
+ mSource = strSource;
+ mArguments = aArguments;
+ mFlags = fFlags;
+}
+
+GuestSessionTaskUpdateAdditions::~GuestSessionTaskUpdateAdditions(void)
+{
+
+}
+
+/**
+ * Adds arguments to existing process arguments.
+ * Identical / already existing arguments will be filtered out.
+ *
+ * @returns VBox status code.
+ * @param aArgumentsDest Destination to add arguments to.
+ * @param aArgumentsSource Arguments to add.
+ */
+int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource)
+{
+ try
+ {
+ /* Filter out arguments which already are in the destination to
+ * not end up having them specified twice. Not the fastest method on the
+ * planet but does the job. */
+ ProcessArguments::const_iterator itSource = aArgumentsSource.begin();
+ while (itSource != aArgumentsSource.end())
+ {
+ bool fFound = false;
+ ProcessArguments::iterator itDest = aArgumentsDest.begin();
+ while (itDest != aArgumentsDest.end())
+ {
+ if ((*itDest).equalsIgnoreCase((*itSource)))
+ {
+ fFound = true;
+ break;
+ }
+ ++itDest;
+ }
+
+ if (!fFound)
+ aArgumentsDest.push_back((*itSource));
+
+ ++itSource;
+ }
+ }
+ catch(std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper function to copy a file from a VISO to the guest.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session to use.
+ * @param hVfsIso VISO handle to use.
+ * @param strFileSrc Source file path on VISO to copy.
+ * @param strFileDst Destination file path on guest.
+ * @param fOptional When set to \c true, the file is optional, i.e. can be skipped
+ * when not found, \c false if not.
+ */
+int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso,
+ Utf8Str const &strFileSrc, const Utf8Str &strFileDst, bool fOptional)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertReturn(hVfsIso != NIL_RTVFS, VERR_INVALID_POINTER);
+
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int vrc = RTVfsFileOpen(hVfsIso, strFileSrc.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ uint64_t cbSrcSize = 0;
+ vrc = RTVfsFileQuerySize(hVfsFile, &cbSrcSize);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n",
+ strFileSrc.c_str(), strFileDst.c_str()));
+
+ GuestFileOpenInfo dstOpenInfo;
+ dstOpenInfo.mFilename = strFileDst;
+ dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace;
+ dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly;
+ dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */
+
+ ComObjPtr<GuestFile> dstFile;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(vrcGuest, strFileDst.c_str()));
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"),
+ strFileDst.c_str(), vrc));
+ break;
+ }
+ }
+ else
+ {
+ vrc = fileCopyToGuestInner(strFileSrc, hVfsFile, strFileDst, dstFile, FileCopyFlag_None, 0 /*offCopy*/, cbSrcSize);
+
+ int vrc2 = fileClose(dstFile);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ else if (fOptional)
+ vrc = VINF_SUCCESS;
+
+ return vrc;
+}
+
+/**
+ * Helper function to run (start) a file on the guest.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session to use.
+ * @param procInfo Guest process startup info to use.
+ * @param fSilent Whether to set progress into failure state in case of error.
+ */
+int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo, bool fSilent)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ LogRel(("Running %s ...\n", procInfo.mName.c_str()));
+
+ GuestProcessTool procTool;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = procTool.init(pSession, procInfo, false /* Async */, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_SUCCESS(vrcGuest))
+ vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ vrc = procTool.getTerminationStatus();
+ }
+
+ if ( RT_FAILURE(vrc)
+ && !fSilent)
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_PROCESS_EXIT_CODE:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Running update file \"%s\" on guest failed: %Rrc"),
+ procInfo.mExecutable.c_str(), procTool.getRc()));
+ break;
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Running update file on guest failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_Process, vrcGuest, procInfo.mExecutable.c_str()));
+ break;
+
+ case VERR_INVALID_STATE: /** @todo Special guest control vrc needed! */
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Update file \"%s\" reported invalid running state"),
+ procInfo.mExecutable.c_str()));
+ break;
+
+ default:
+ setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Error while running update file \"%s\" on guest: %Rrc"),
+ procInfo.mExecutable.c_str(), vrc));
+ break;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * Helper function which checks Guest Additions installation status.
+ *
+ * @returns IPRT status code.
+ * @param pSession Guest session to use.
+ * @param osType Guest type.
+ */
+int GuestSessionTaskUpdateAdditions::checkGuestAdditionsStatus(GuestSession *pSession, eOSType osType)
+{
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+
+ if (osType == eOSType_Linux)
+ {
+ const Utf8Str ksStatusScript = Utf8Str("/sbin/rcvboxadd");
+
+ /* Check if Guest Additions kernel modules were loaded. */
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_None;
+ procInfo.mExecutable = Utf8Str("/bin/sh");;
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(ksStatusScript);
+ procInfo.mArguments.push_back("status-kernel");
+
+ vrc = runFileOnGuest(pSession, procInfo, true /* fSilent */);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Replace the last argument with corresponding value and check
+ * if Guest Additions user services were started. */
+ procInfo.mArguments.pop_back();
+ procInfo.mArguments.push_back("status-user");
+
+ vrc = runFileOnGuest(pSession, procInfo, true /* fSilent */);
+ if (RT_FAILURE(vrc))
+ hrc = setProgressErrorMsg(VBOX_E_GSTCTL_GUEST_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "files were installed, but user services were not reloaded automatically. "
+ "Please consider rebooting the guest")));
+ }
+ else
+ hrc = setProgressErrorMsg(VBOX_E_GSTCTL_GUEST_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "files were installed, but kernel modules were not reloaded automatically. "
+ "Please consider rebooting the guest")));
+ }
+
+ return vrc;
+}
+
+/**
+ * Helper function which waits until Guest Additions services started.
+ *
+ * @returns 0 on success or VERR_TIMEOUT if guest services were not
+ * started on time.
+ * @param pGuest Guest interface to use.
+ * @param osType Guest type.
+ */
+int GuestSessionTaskUpdateAdditions::waitForGuestSession(ComObjPtr<Guest> pGuest, eOSType osType)
+{
+ int vrc = VERR_GSTCTL_GUEST_ERROR;
+ int vrcRet = VERR_TIMEOUT;
+
+ uint64_t tsStart = RTTimeSystemMilliTS();
+ const uint64_t cMsTimeout = 10 * RT_MS_1MIN;
+
+ AssertReturn(!pGuest.isNull(), VERR_TIMEOUT);
+
+ do
+ {
+ ComObjPtr<GuestSession> pSession;
+ GuestCredentials guestCreds;
+ GuestSessionStartupInfo startupInfo;
+
+ startupInfo.mName = "Guest Additions connection checker";
+ startupInfo.mOpenTimeoutMS = 100;
+
+ vrc = pGuest->i_sessionCreate(startupInfo, guestCreds, pSession);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR; /* unused. */
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Wait for VBoxService to start. */
+ GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None;
+ int vrcGuest2 = VINF_SUCCESS; /* unused. */
+ vrc = pSession->i_waitFor(GuestSessionWaitForFlag_Start, 100 /* timeout, ms */, enmWaitResult, &vrcGuest2);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure Guest Additions were reloaded on the guest side. */
+ vrc = checkGuestAdditionsStatus(pSession, osType);
+ if (RT_SUCCESS(vrc))
+ LogRel(("Guest Additions were successfully reloaded after installation\n"));
+ else
+ LogRel(("Guest Additions were failed to reload after installation, please consider rebooting the guest\n"));
+
+ vrc = pSession->Close();
+ vrcRet = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ vrc = pSession->Close();
+ }
+
+ RTThreadSleep(100);
+
+ } while ((RTTimeSystemMilliTS() - tsStart) < cMsTimeout);
+
+ return vrcRet;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskUpdateAdditions::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<GuestSession> pSession = mSession;
+ Assert(!pSession.isNull());
+
+ AutoCaller autoCaller(pSession);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ int vrc = setProgress(10);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ HRESULT hrc = S_OK;
+
+ LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", mSource.c_str()));
+
+ ComObjPtr<Guest> pGuest(mSession->i_getParent());
+#if 0
+ /*
+ * Wait for the guest being ready within 30 seconds.
+ */
+ AdditionsRunLevelType_T addsRunLevel;
+ uint64_t tsStart = RTTimeSystemMilliTS();
+ while ( SUCCEEDED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
+ && ( addsRunLevel != AdditionsRunLevelType_Userland
+ && addsRunLevel != AdditionsRunLevelType_Desktop))
+ {
+ if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000)
+ {
+ vrc = VERR_TIMEOUT;
+ break;
+ }
+
+ RTThreadSleep(100); /* Wait a bit. */
+ }
+
+ if (FAILED(hrc)) vrc = VERR_TIMEOUT;
+ if (vrc == VERR_TIMEOUT)
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions were not ready within time, giving up")));
+#else
+ /*
+ * For use with the GUI we don't want to wait, just return so that the manual .ISO mounting
+ * can continue.
+ */
+ AdditionsRunLevelType_T addsRunLevel;
+ if ( FAILED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel))
+ || ( addsRunLevel != AdditionsRunLevelType_Userland
+ && addsRunLevel != AdditionsRunLevelType_Desktop))
+ {
+ if (addsRunLevel == AdditionsRunLevelType_System)
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions are installed but not fully loaded yet, aborting automatic update")));
+ else
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest Additions not installed or ready, aborting automatic update")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+#endif
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Determine if we are able to update automatically. This only works
+ * if there are recent Guest Additions installed already.
+ */
+ Utf8Str strAddsVer;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0)
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"),
+ strAddsVer.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ Utf8Str strOSVer;
+ eOSType osType = eOSType_Unknown;
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Determine guest OS type and the required installer image.
+ */
+ Utf8Str strOSType;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType);
+ if (RT_SUCCESS(vrc))
+ {
+ if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive)
+ || strOSType.contains("Windows", Utf8Str::CaseInsensitive))
+ {
+ osType = eOSType_Windows;
+
+ /*
+ * Determine guest OS version.
+ */
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Unable to detected guest OS version, please update manually")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ /* Because Windows 2000 + XP and is bitching with WHQL popups even if we have signed drivers we
+ * can't do automated updates here. */
+ /* Windows XP 64-bit (5.2) is a Windows 2003 Server actually, so skip this here. */
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
+ {
+ if ( strOSVer.startsWith("5.0") /* Exclude the build number. */
+ || strOSVer.startsWith("5.1") /* Exclude the build number. */)
+ {
+ /* If we don't have AdditionsUpdateFlag_WaitForUpdateStartOnly set we can't continue
+ * because the Windows Guest Additions installer will fail because of WHQL popups. If the
+ * flag is set this update routine ends successfully as soon as the installer was started
+ * (and the user has to deal with it in the guest). */
+ if (!(mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually")));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+ else
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("%s (%s) not supported for automatic updating, please update manually"),
+ strOSType.c_str(), strOSVer.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive))
+ {
+ osType = eOSType_Solaris;
+ }
+ else /* Everything else hopefully means Linux :-). */
+ osType = eOSType_Linux;
+
+ if ( RT_SUCCESS(vrc)
+ && ( osType != eOSType_Windows
+ && osType != eOSType_Linux))
+ /** @todo Support Solaris. */
+ {
+ hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED,
+ Utf8StrFmt(tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
+ strOSType.c_str()));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Try to open the .ISO file to extract all needed files.
+ */
+ RTVFSFILE hVfsFileIso;
+ vrc = RTVfsFileOpenNormal(mSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFileIso);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"),
+ mSource.c_str(), vrc));
+ }
+ else
+ {
+ RTVFS hVfsIso;
+ vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Unable to open file as ISO 9660 file system volume: %Rrc"), vrc));
+ }
+ else
+ {
+ Utf8Str strUpdateDir;
+
+ vrc = setProgress(5);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Try getting the installed Guest Additions version to know whether we
+ * can install our temporary Guest Addition data into the original installation
+ * directory.
+ *
+ * Because versions prior to 4.2 had bugs wrt spaces in paths we have to choose
+ * a different location then.
+ */
+ bool fUseInstallDir = false;
+
+ Utf8Str strAddsVer;
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer);
+ if ( RT_SUCCESS(vrc)
+ && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0)
+ {
+ fUseInstallDir = true;
+ }
+
+ if (fUseInstallDir)
+ {
+ vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir);
+ if (RT_SUCCESS(vrc))
+ {
+ if (strUpdateDir.isNotEmpty())
+ {
+ if (osType == eOSType_Windows)
+ {
+ strUpdateDir.findReplace('/', '\\');
+ strUpdateDir.append("\\Update\\");
+ }
+ else
+ strUpdateDir.append("/update/");
+ }
+ /* else Older Guest Additions might not handle this property correctly. */
+ }
+ /* Ditto. */
+ }
+
+ /** @todo Set fallback installation directory. Make this a *lot* smarter. Later. */
+ if (strUpdateDir.isEmpty())
+ {
+ if (osType == eOSType_Windows)
+ strUpdateDir = "C:\\Temp\\";
+ else
+ strUpdateDir = "/tmp/";
+ }
+ }
+
+ /* Create the installation directory. */
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Guest Additions update directory is: %s\n", strUpdateDir.c_str()));
+
+ vrc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Creating installation directory on guest failed"),
+ GuestErrorInfo(GuestErrorInfo::Type_Directory, vrcGuest, strUpdateDir.c_str()));
+ break;
+
+ default:
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Creating installation directory \"%s\" on guest failed: %Rrc"),
+ strUpdateDir.c_str(), vrc));
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = setProgress(10);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Prepare the file(s) we want to copy over to the guest and
+ * (maybe) want to run. */
+ switch (osType)
+ {
+ case eOSType_Windows:
+ {
+ /* Do we need to install our certificates? We do this for W2K and up. */
+ bool fInstallCert = false;
+
+ /* Only Windows 2000 and up need certificates to be installed. */
+ if (RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0)
+ {
+ fInstallCert = true;
+ LogRel(("Certificates for auto updating WHQL drivers will be installed\n"));
+ }
+ else
+ LogRel(("Skipping installation of certificates for WHQL drivers\n"));
+
+ if (fInstallCert)
+ {
+ static struct { const char *pszDst, *pszIso; } const s_aCertFiles[] =
+ {
+ { "vbox.cer", "/CERT/VBOX.CER" },
+ { "vbox-sha1.cer", "/CERT/VBOX-SHA1.CER" },
+ { "vbox-sha256.cer", "/CERT/VBOX-SHA256.CER" },
+ { "vbox-sha256-r3.cer", "/CERT/VBOX-SHA256-R3.CER" },
+ { "oracle-vbox.cer", "/CERT/ORACLE-VBOX.CER" },
+ };
+ uint32_t fCopyCertUtil = ISOFILE_FLAG_COPY_FROM_ISO;
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aCertFiles); i++)
+ {
+ /* Skip if not present on the ISO. */
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsQueryPathInfo(hVfsIso, s_aCertFiles[i].pszIso, &ObjInfo, RTFSOBJATTRADD_NOTHING,
+ RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ continue;
+
+ /* Copy the certificate certificate. */
+ Utf8Str const strDstCert(strUpdateDir + s_aCertFiles[i].pszDst);
+ mFiles.push_back(ISOFile(s_aCertFiles[i].pszIso,
+ strDstCert,
+ ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_OPTIONAL));
+
+ /* Out certificate installation utility. */
+ /* First pass: Copy over the file (first time only) + execute it to remove any
+ * existing VBox certificates. */
+ GuestProcessStartupInfo siCertUtilRem;
+ siCertUtilRem.mName = "VirtualBox Certificate Utility, removing old VirtualBox certificates";
+ /* The argv[0] should contain full path to the executable module */
+ siCertUtilRem.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe");
+ siCertUtilRem.mArguments.push_back(Utf8Str("remove-trusted-publisher"));
+ siCertUtilRem.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
+ siCertUtilRem.mArguments.push_back(strDstCert);
+ siCertUtilRem.mArguments.push_back(strDstCert);
+ mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
+ strUpdateDir + "VBoxCertUtil.exe",
+ fCopyCertUtil | ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
+ siCertUtilRem));
+ fCopyCertUtil = 0;
+ /* Second pass: Only execute (but don't copy) again, this time installng the
+ * recent certificates just copied over. */
+ GuestProcessStartupInfo siCertUtilAdd;
+ siCertUtilAdd.mName = "VirtualBox Certificate Utility, installing VirtualBox certificates";
+ /* The argv[0] should contain full path to the executable module */
+ siCertUtilAdd.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe");
+ siCertUtilAdd.mArguments.push_back(Utf8Str("add-trusted-publisher"));
+ siCertUtilAdd.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */
+ siCertUtilAdd.mArguments.push_back(strDstCert);
+ siCertUtilAdd.mArguments.push_back(strDstCert);
+ mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE",
+ strUpdateDir + "VBoxCertUtil.exe",
+ ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL,
+ siCertUtilAdd));
+ }
+ }
+ /* The installers in different flavors, as we don't know (and can't assume)
+ * the guest's bitness. */
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-X86.EXE",
+ strUpdateDir + "VBoxWindowsAdditions-x86.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO));
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-AMD64.EXE",
+ strUpdateDir + "VBoxWindowsAdditions-amd64.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO));
+ /* The stub loader which decides which flavor to run. */
+ GuestProcessStartupInfo siInstaller;
+ siInstaller.mName = "VirtualBox Windows Guest Additions Installer";
+ /* Set a running timeout of 5 minutes -- the Windows Guest Additions
+ * setup can take quite a while, so be on the safe side. */
+ siInstaller.mTimeoutMS = 5 * 60 * 1000;
+
+ /* The argv[0] should contain full path to the executable module */
+ siInstaller.mArguments.push_back(strUpdateDir + "VBoxWindowsAdditions.exe");
+ siInstaller.mArguments.push_back(Utf8Str("/S")); /* We want to install in silent mode. */
+ siInstaller.mArguments.push_back(Utf8Str("/l")); /* ... and logging enabled. */
+ /* Don't quit VBoxService during upgrade because it still is used for this
+ * piece of code we're in right now (that is, here!) ... */
+ siInstaller.mArguments.push_back(Utf8Str("/no_vboxservice_exit"));
+ /* Tell the installer to report its current installation status
+ * using a running VBoxTray instance via balloon messages in the
+ * Windows taskbar. */
+ siInstaller.mArguments.push_back(Utf8Str("/post_installstatus"));
+ /* Add optional installer command line arguments from the API to the
+ * installer's startup info. */
+ vrc = addProcessArguments(siInstaller.mArguments, mArguments);
+ AssertRC(vrc);
+ /* If the caller does not want to wait for out guest update process to end,
+ * complete the progress object now so that the caller can do other work. */
+ if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
+ siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly;
+ mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS.EXE",
+ strUpdateDir + "VBoxWindowsAdditions.exe",
+ ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_EXECUTE, siInstaller));
+ break;
+ }
+ case eOSType_Linux:
+ {
+ /* Copy over the installer to the guest but don't execute it.
+ * Execution will be done by the shell instead. */
+ mFiles.push_back(ISOFile("VBOXLINUXADDITIONS.RUN",
+ strUpdateDir + "VBoxLinuxAdditions.run", ISOFILE_FLAG_COPY_FROM_ISO));
+
+ GuestProcessStartupInfo siInstaller;
+ siInstaller.mName = "VirtualBox Linux Guest Additions Installer";
+ /* Set a running timeout of 5 minutes -- compiling modules and stuff for the Linux Guest Additions
+ * setup can take quite a while, so be on the safe side. */
+ siInstaller.mTimeoutMS = 5 * 60 * 1000;
+ /* The argv[0] should contain full path to the shell we're using to execute the installer. */
+ siInstaller.mArguments.push_back("/bin/sh");
+ /* Now add the stuff we need in order to execute the installer. */
+ siInstaller.mArguments.push_back(strUpdateDir + "VBoxLinuxAdditions.run");
+ /* Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking xterm
+ * window spawned when doing any unattended Linux GA installations. */
+ siInstaller.mArguments.push_back("--nox11");
+ siInstaller.mArguments.push_back("--");
+ /* Force the upgrade. Needed in order to skip the confirmation dialog about warning to upgrade. */
+ siInstaller.mArguments.push_back("--force"); /** @todo We might want a dedicated "--silent" switch here. */
+ /* If the caller does not want to wait for out guest update process to end,
+ * complete the progress object now so that the caller can do other work. */
+ if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
+ siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly;
+ mFiles.push_back(ISOFile("/bin/sh" /* Source */, "/bin/sh" /* Dest */,
+ ISOFILE_FLAG_EXECUTE, siInstaller));
+ break;
+ }
+ case eOSType_Solaris:
+ /** @todo Add Solaris support. */
+ break;
+ default:
+ AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType));
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* We want to spend 40% total for all copying operations. So roughly
+ * calculate the specific percentage step of each copied file. */
+ uint8_t uOffset = 20; /* Start at 20%. */
+ uint8_t uStep = 40 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
+
+ LogRel(("Copying over Guest Additions update files to the guest ...\n"));
+
+ std::vector<ISOFile>::const_iterator itFiles = mFiles.begin();
+ while (itFiles != mFiles.end())
+ {
+ if (itFiles->fFlags & ISOFILE_FLAG_COPY_FROM_ISO)
+ {
+ bool fOptional = false;
+ if (itFiles->fFlags & ISOFILE_FLAG_OPTIONAL)
+ fOptional = true;
+ vrc = copyFileToGuest(pSession, hVfsIso, itFiles->strSource, itFiles->strDest, fOptional);
+ if (RT_FAILURE(vrc))
+ {
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"),
+ itFiles->strSource.c_str(), itFiles->strDest.c_str(), vrc));
+ break;
+ }
+ }
+
+ vrc = setProgress(uOffset);
+ if (RT_FAILURE(vrc))
+ break;
+ uOffset += uStep;
+
+ ++itFiles;
+ }
+ }
+
+ /* Done copying, close .ISO file. */
+ RTVfsRelease(hVfsIso);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* We want to spend 35% total for all copying operations. So roughly
+ * calculate the specific percentage step of each copied file. */
+ uint8_t uOffset = 60; /* Start at 60%. */
+ uint8_t uStep = 35 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10);
+
+ LogRel(("Executing Guest Additions update files ...\n"));
+
+ std::vector<ISOFile>::iterator itFiles = mFiles.begin();
+ while (itFiles != mFiles.end())
+ {
+ if (itFiles->fFlags & ISOFILE_FLAG_EXECUTE)
+ {
+ vrc = runFileOnGuest(pSession, itFiles->mProcInfo);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ vrc = setProgress(uOffset);
+ if (RT_FAILURE(vrc))
+ break;
+ uOffset += uStep;
+
+ ++itFiles;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Linux Guest Additions will restart VBoxService during installation process.
+ * In this case, connection to the guest will be temporary lost until new
+ * kernel modules will be rebuilt, loaded and new VBoxService restarted.
+ * Handle this case here: check if old connection was terminated and
+ * new one has started. */
+ if (osType == eOSType_Linux)
+ {
+ if (pSession->i_isTerminated())
+ {
+ LogRel(("Old guest session has terminated, waiting updated guest services to start\n"));
+
+ /* Wait for VBoxService to restart. */
+ vrc = waitForGuestSession(pSession->i_getParent(), osType);
+ if (RT_FAILURE(vrc))
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "guest services were not restarted, please reinstall Guest Additions manually")));
+ }
+ else
+ {
+ vrc = VERR_TRY_AGAIN;
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Old guest session is still active, guest services were not restarted "
+ "after installation, please reinstall Guest Additions manually")));
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Automatic update of Guest Additions succeeded\n"));
+ hrc = setProgressSuccess();
+ }
+ }
+ }
+
+ RTVfsFileRelease(hVfsFileIso);
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_CANCELLED)
+ {
+ LogRel(("Automatic update of Guest Additions was canceled\n"));
+
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Installation was canceled")));
+ }
+ else if (vrc == VERR_TIMEOUT)
+ {
+ LogRel(("Automatic update of Guest Additions has timed out\n"));
+
+ hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR,
+ Utf8StrFmt(tr("Installation has timed out")));
+ }
+ else
+ {
+ Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", vrc);
+ if (!mProgress.isNull()) /* Progress object is optional. */
+ {
+#ifdef VBOX_STRICT
+ /* If we forgot to set the progress object accordingly, let us know. */
+ LONG rcProgress;
+ AssertMsg( SUCCEEDED(mProgress->COMGETTER(ResultCode(&rcProgress)))
+ && FAILED(rcProgress), ("Task indicated an error (%Rrc), but progress did not indicate this (%Rhrc)\n",
+ vrc, rcProgress));
+#endif
+ com::ProgressErrorInfo errorInfo(mProgress);
+ if ( errorInfo.isFullAvailable()
+ || errorInfo.isBasicAvailable())
+ {
+ strError = errorInfo.getText();
+ }
+ }
+
+ LogRel(("Automatic update of Guest Additions failed: %s (%Rhrc)\n",
+ strError.c_str(), hrc));
+ }
+
+ LogRel(("Please install Guest Additions manually\n"));
+ }
+
+ /** @todo Clean up copied / left over installation files. */
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
diff --git a/src/VBox/Main/src-client/HGCM.cpp b/src/VBox/Main/src-client/HGCM.cpp
new file mode 100644
index 00000000..4650ca35
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCM.cpp
@@ -0,0 +1,3038 @@
+/* $Id: HGCM.cpp $ */
+/** @file
+ * HGCM (Host-Guest Communication Manager)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCM.h"
+#include "HGCMThread.h"
+
+#include <VBox/err.h>
+#include <VBox/hgcmsvc.h>
+#include <VBox/vmm/ssm.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/sup.h>
+#include <VBox/AssertGuest.h>
+
+#include <iprt/alloc.h>
+#include <iprt/avl.h>
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <iprt/ldr.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include <VBox/VMMDev.h>
+#include <new>
+
+/**
+ * A service gets one thread, which synchronously delivers messages to
+ * the service. This is good for serialization.
+ *
+ * Some services may want to process messages asynchronously, and will want
+ * a next message to be delivered, while a previous message is still being
+ * processed.
+ *
+ * The dedicated service thread delivers a next message when service
+ * returns after fetching a previous one. The service will call a message
+ * completion callback when message is actually processed. So returning
+ * from the service call means only that the service is processing message.
+ *
+ * 'Message processed' condition is indicated by service, which call the
+ * callback, even if the callback is called synchronously in the dedicated
+ * thread.
+ *
+ * This message completion callback is only valid for Call requests.
+ * Connect and Disconnect are processed synchronously by the service.
+ */
+
+
+/* The maximum allowed size of a service name in bytes. */
+#define VBOX_HGCM_SVC_NAME_MAX_BYTES 1024
+
+struct _HGCMSVCEXTHANDLEDATA
+{
+ char *pszServiceName;
+ /* The service name follows. */
+};
+
+class HGCMClient;
+
+/** Internal helper service object. HGCM code would use it to
+ * hold information about services and communicate with services.
+ * The HGCMService is an (in future) abstract class that implements
+ * common functionality. There will be derived classes for specific
+ * service types.
+ */
+
+class HGCMService
+{
+ private:
+ VBOXHGCMSVCHELPERS m_svcHelpers;
+
+ static HGCMService *sm_pSvcListHead;
+ static HGCMService *sm_pSvcListTail;
+
+ static int sm_cServices;
+
+ HGCMThread *m_pThread;
+ friend DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser);
+
+ uint32_t volatile m_u32RefCnt;
+
+ HGCMService *m_pSvcNext;
+ HGCMService *m_pSvcPrev;
+
+ char *m_pszSvcName;
+ char *m_pszSvcLibrary;
+
+ RTLDRMOD m_hLdrMod;
+ PFNVBOXHGCMSVCLOAD m_pfnLoad;
+
+ VBOXHGCMSVCFNTABLE m_fntable;
+
+ /** Set if servicing SVC_MSG_CONNECT or SVC_MSG_DISCONNECT.
+ * Used for context checking pfnDisconnectClient calls, as it can only
+ * safely be made when the main HGCM thread is waiting on the service to
+ * process those messages. */
+ bool m_fInConnectOrDisconnect;
+
+ uint32_t m_acClients[HGCM_CLIENT_CATEGORY_MAX]; /**< Clients per category. */
+ uint32_t m_cClients;
+ uint32_t m_cClientsAllocated;
+
+ uint32_t *m_paClientIds;
+
+ HGCMSVCEXTHANDLE m_hExtension;
+
+ PUVM m_pUVM;
+ PCVMMR3VTABLE m_pVMM;
+ PPDMIHGCMPORT m_pHgcmPort;
+
+ /** @name Statistics
+ * @{ */
+ STAMPROFILE m_StatHandleMsg;
+ STAMCOUNTER m_StatTooManyClients;
+ STAMCOUNTER m_StatTooManyCalls;
+ /** @} */
+
+ int loadServiceDLL(void);
+ void unloadServiceDLL(void);
+
+ /*
+ * Main HGCM thread methods.
+ */
+ int instanceCreate(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort);
+ void registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM);
+ void instanceDestroy(void);
+
+ int saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM);
+ int loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion);
+
+ HGCMService();
+ ~HGCMService() {};
+
+ static DECLCALLBACK(int) svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t vrc);
+ static DECLCALLBACK(int) svcHlpDisconnectClient(void *pvInstance, uint32_t idClient);
+ static DECLCALLBACK(bool) svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle);
+ static DECLCALLBACK(bool) svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle);
+ static DECLCALLBACK(int) svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType,
+ STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
+ const char *pszName, va_list va);
+ static DECLCALLBACK(int) svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va);
+ static DECLCALLBACK(int) svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc,
+ PFNDBGFHANDLEREXT pfnHandler, void *pvUser);
+ static DECLCALLBACK(int) svcHlpInfoDeregister(void *pvInstance, const char *pszName);
+ static DECLCALLBACK(uint32_t) svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall);
+ static DECLCALLBACK(uint64_t) svcHlpGetVMMDevSessionId(void *pvInstance);
+
+ public:
+
+ /*
+ * Main HGCM thread methods.
+ */
+ static int LoadService(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort);
+ void UnloadService(bool fUvmIsInvalid);
+
+ static void UnloadAll(bool fUvmIsInvalid);
+
+ static int ResolveService(HGCMService **ppsvc, const char *pszServiceName);
+ void ReferenceService(void);
+ void ReleaseService(void);
+
+ static void Reset(void);
+
+ static int SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM);
+ static int LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion);
+
+ int CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring);
+ int DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient);
+
+ int HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms);
+ static void BroadcastNotify(HGCMNOTIFYEVENT enmEvent);
+ void Notify(HGCMNOTIFYEVENT enmEvent);
+
+ uint32_t SizeOfClient(void) { return m_fntable.cbClient; };
+
+ int RegisterExtension(HGCMSVCEXTHANDLE handle, PFNHGCMSVCEXT pfnExtension, void *pvExtension);
+ void UnregisterExtension(HGCMSVCEXTHANDLE handle);
+
+ /*
+ * The service thread methods.
+ */
+
+ int GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient,
+ uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], uint64_t tsArrival);
+ void GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient);
+};
+
+
+class HGCMClient: public HGCMObject
+{
+ public:
+ HGCMClient(uint32_t a_fRequestor, uint32_t a_idxCategory)
+ : HGCMObject(HGCMOBJ_CLIENT)
+ , pService(NULL)
+ , pvData(NULL)
+ , fRequestor(a_fRequestor)
+ , idxCategory(a_idxCategory)
+ , cPendingCalls(0)
+ , m_fGuestAccessible(false)
+ {
+ Assert(idxCategory < HGCM_CLIENT_CATEGORY_MAX);
+ }
+ ~HGCMClient();
+
+ int Init(HGCMService *pSvc);
+
+ /** Lookups a client object by its handle. */
+ static HGCMClient *ReferenceByHandle(uint32_t idClient)
+ {
+ return (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT);
+ }
+
+ /** Lookups a client object by its handle and makes sure that it's accessible to the guest. */
+ static HGCMClient *ReferenceByHandleForGuest(uint32_t idClient)
+ {
+ HGCMClient *pClient = (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT);
+ if (pClient)
+ {
+ if (RT_LIKELY(pClient->m_fGuestAccessible))
+ return pClient;
+ pClient->Dereference();
+ }
+ return NULL;
+ }
+
+ /** Make the client object accessible to the guest. */
+ void makeAccessibleToGuest()
+ {
+ ASMAtomicWriteBool(&m_fGuestAccessible, true);
+ }
+
+ /** Service that the client is connected to. */
+ HGCMService *pService;
+
+ /** Client specific data. */
+ void *pvData;
+
+ /** The requestor flags this client was created with.
+ * @sa VMMDevRequestHeader::fRequestor */
+ uint32_t fRequestor;
+
+ /** The client category (HGCM_CLIENT_CATEGORY_XXX). */
+ uint32_t idxCategory;
+
+ /** Number of pending calls. */
+ uint32_t volatile cPendingCalls;
+
+ protected:
+ /** Set if the client is accessible to the guest, clear if not. */
+ bool volatile m_fGuestAccessible;
+
+ private: /* none of this: */
+ HGCMClient();
+ HGCMClient(HGCMClient const &);
+ HGCMClient &operator=(HGCMClient const &);
+};
+
+HGCMClient::~HGCMClient()
+{
+ if (pService->SizeOfClient() > 0)
+ {
+ RTMemFree(pvData);
+ pvData = NULL;
+ }
+}
+
+
+int HGCMClient::Init(HGCMService *pSvc)
+{
+ pService = pSvc;
+
+ if (pService->SizeOfClient() > 0)
+ {
+ pvData = RTMemAllocZ(pService->SizeOfClient());
+
+ if (!pvData)
+ {
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#define HGCM_CLIENT_DATA(pService, pClient)(pClient->pvData)
+
+
+
+HGCMService *HGCMService::sm_pSvcListHead = NULL;
+HGCMService *HGCMService::sm_pSvcListTail = NULL;
+int HGCMService::sm_cServices = 0;
+
+HGCMService::HGCMService()
+ :
+ m_pThread (NULL),
+ m_u32RefCnt (0),
+ m_pSvcNext (NULL),
+ m_pSvcPrev (NULL),
+ m_pszSvcName (NULL),
+ m_pszSvcLibrary (NULL),
+ m_hLdrMod (NIL_RTLDRMOD),
+ m_pfnLoad (NULL),
+ m_fInConnectOrDisconnect(false),
+ m_cClients (0),
+ m_cClientsAllocated (0),
+ m_paClientIds (NULL),
+ m_hExtension (NULL),
+ m_pUVM (NULL),
+ m_pVMM (NULL),
+ m_pHgcmPort (NULL)
+{
+ RT_ZERO(m_acClients);
+ RT_ZERO(m_fntable);
+}
+
+
+static bool g_fResetting = false;
+static bool g_fSaveState = false;
+
+
+/** Helper function to load a local service DLL.
+ *
+ * @return VBox code
+ */
+int HGCMService::loadServiceDLL(void)
+{
+ LogFlowFunc(("m_pszSvcLibrary = %s\n", m_pszSvcLibrary));
+
+ if (m_pszSvcLibrary == NULL)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+
+ int vrc;
+
+ if (RTPathHasPath(m_pszSvcLibrary))
+ vrc = SUPR3HardenedLdrLoadPlugIn(m_pszSvcLibrary, &m_hLdrMod, &ErrInfo.Core);
+ else
+ vrc = SUPR3HardenedLdrLoadAppPriv(m_pszSvcLibrary, &m_hLdrMod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogFlowFunc(("successfully loaded the library.\n"));
+
+ m_pfnLoad = NULL;
+
+ vrc = RTLdrGetSymbol(m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad);
+
+ if (RT_FAILURE(vrc) || !m_pfnLoad)
+ {
+ Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, vrc = %Rrc, m_pfnLoad = %p\n",
+ VBOX_HGCM_SVCLOAD_NAME, vrc, m_pfnLoad));
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* m_pfnLoad was NULL */
+ vrc = VERR_SYMBOL_NOT_FOUND;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ RT_ZERO(m_fntable);
+
+ m_fntable.cbSize = sizeof(m_fntable);
+ m_fntable.u32Version = VBOX_HGCM_SVC_VERSION;
+ m_fntable.pHelpers = &m_svcHelpers;
+
+ /* Total max calls: (2048 + 1024 + 1024) * 8192 = 33 554 432 */
+ m_fntable.idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_KERNEL;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = _2K;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT] = _1K;
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER] = _1K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL] = _8K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT] = _4K;
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER] = _2K;
+ /** @todo provide way to configure different values via extra data. */
+
+ vrc = m_pfnLoad(&m_fntable);
+
+ LogFlowFunc(("m_pfnLoad vrc = %Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ if ( m_fntable.pfnUnload != NULL
+ && m_fntable.pfnConnect != NULL
+ && m_fntable.pfnDisconnect != NULL
+ && m_fntable.pfnCall != NULL
+ )
+ {
+ Assert(m_fntable.idxLegacyClientCategory < RT_ELEMENTS(m_fntable.acMaxClients));
+ LogRel2(("HGCMService::loadServiceDLL: acMaxClients={%u,%u,%u} acMaxCallsPerClient={%u,%u,%u} => %RU64 calls; idxLegacyClientCategory=%d; %s\n",
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL],
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT],
+ m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT],
+ m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER],
+ (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL]
+ + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT]
+ + (uint64_t)m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER]
+ * m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER],
+ m_fntable.idxLegacyClientCategory, m_pszSvcName));
+ }
+ else
+ {
+ Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n"));
+
+ vrc = VERR_INVALID_PARAMETER;
+
+ if (m_fntable.pfnUnload)
+ {
+ m_fntable.pfnUnload(m_fntable.pvService);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("HGCM: Failed to load the service library: [%s], vrc = %Rrc - %s. The service will be not available.\n",
+ m_pszSvcLibrary, vrc, ErrInfo.Core.pszMsg));
+ m_hLdrMod = NIL_RTLDRMOD;
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ unloadServiceDLL();
+ }
+
+ return vrc;
+}
+
+/**
+ * Helper function to free a local service DLL.
+ */
+void HGCMService::unloadServiceDLL(void)
+{
+ if (m_hLdrMod)
+ {
+ RTLdrClose(m_hLdrMod);
+ }
+
+ RT_ZERO(m_fntable);
+ m_pfnLoad = NULL;
+ m_hLdrMod = NIL_RTLDRMOD;
+}
+
+/*
+ * Messages processed by service threads. These threads only call the service entry points.
+ */
+
+#define SVC_MSG_LOAD (0) /**< Load the service library and call VBOX_HGCM_SVCLOAD_NAME entry point. */
+#define SVC_MSG_UNLOAD (1) /**< call pfnUnload and unload the service library. */
+#define SVC_MSG_CONNECT (2) /**< pfnConnect */
+#define SVC_MSG_DISCONNECT (3) /**< pfnDisconnect */
+#define SVC_MSG_GUESTCALL (4) /**< pfnGuestCall */
+#define SVC_MSG_HOSTCALL (5) /**< pfnHostCall */
+#define SVC_MSG_LOADSTATE (6) /**< pfnLoadState. */
+#define SVC_MSG_SAVESTATE (7) /**< pfnSaveState. */
+#define SVC_MSG_QUIT (8) /**< Terminate the thread. */
+#define SVC_MSG_REGEXT (9) /**< pfnRegisterExtension */
+#define SVC_MSG_UNREGEXT (10) /**< pfnRegisterExtension */
+#define SVC_MSG_NOTIFY (11) /**< pfnNotify */
+#define SVC_MSG_GUESTCANCELLED (12) /**< pfnCancelled */
+
+class HGCMMsgSvcLoad: public HGCMMsgCore
+{
+ public:
+ HGCMMsgSvcLoad() : HGCMMsgCore(), pUVM() {}
+
+ /** The user mode VM handle (for statistics and such). */
+ PUVM pUVM;
+};
+
+class HGCMMsgSvcUnload: public HGCMMsgCore
+{
+};
+
+class HGCMMsgSvcConnect: public HGCMMsgCore
+{
+ public:
+ /** client identifier */
+ uint32_t u32ClientId;
+ /** Requestor flags. */
+ uint32_t fRequestor;
+ /** Set if restoring. */
+ bool fRestoring;
+};
+
+class HGCMMsgSvcDisconnect: public HGCMMsgCore
+{
+ public:
+ /** client identifier */
+ uint32_t u32ClientId;
+ /** The client instance. */
+ HGCMClient *pClient;
+};
+
+class HGCMMsgHeader: public HGCMMsgCore
+{
+ public:
+ HGCMMsgHeader() : pCmd(NULL), pHGCMPort(NULL) {};
+
+ /* Command pointer/identifier. */
+ PVBOXHGCMCMD pCmd;
+
+ /* Port to be informed on message completion. */
+ PPDMIHGCMPORT pHGCMPort;
+};
+
+class HGCMMsgCall: public HGCMMsgHeader
+{
+ public:
+ HGCMMsgCall() : pcCounter(NULL)
+ { }
+
+ HGCMMsgCall(HGCMThread *pThread)
+ : pcCounter(NULL)
+ {
+ InitializeCore(SVC_MSG_GUESTCALL, pThread);
+ Initialize();
+ }
+ ~HGCMMsgCall()
+ {
+ Log(("~HGCMMsgCall %p\n", this));
+ Assert(!pcCounter);
+ }
+
+ /** Points to HGCMClient::cPendingCalls if it needs to be decremented. */
+ uint32_t volatile *pcCounter;
+
+ /* client identifier */
+ uint32_t u32ClientId;
+
+ /* function number */
+ uint32_t u32Function;
+
+ /* number of parameters */
+ uint32_t cParms;
+
+ VBOXHGCMSVCPARM *paParms;
+
+ /** The STAM_GET_TS() value when the request arrived. */
+ uint64_t tsArrival;
+};
+
+class HGCMMsgCancelled: public HGCMMsgHeader
+{
+ public:
+ HGCMMsgCancelled() {}
+
+ HGCMMsgCancelled(HGCMThread *pThread)
+ {
+ InitializeCore(SVC_MSG_GUESTCANCELLED, pThread);
+ Initialize();
+ }
+ ~HGCMMsgCancelled() { Log(("~HGCMMsgCancelled %p\n", this)); }
+
+ /** The client identifier. */
+ uint32_t idClient;
+};
+
+class HGCMMsgLoadSaveStateClient: public HGCMMsgCore
+{
+ public:
+ PSSMHANDLE pSSM;
+ PCVMMR3VTABLE pVMM;
+ uint32_t uVersion;
+ uint32_t u32ClientId;
+};
+
+class HGCMMsgHostCallSvc: public HGCMMsgCore
+{
+ public:
+ /* function number */
+ uint32_t u32Function;
+
+ /* number of parameters */
+ uint32_t cParms;
+
+ VBOXHGCMSVCPARM *paParms;
+};
+
+class HGCMMsgSvcRegisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the extension to be registered. */
+ HGCMSVCEXTHANDLE handle;
+ /* The extension entry point. */
+ PFNHGCMSVCEXT pfnExtension;
+ /* The extension pointer. */
+ void *pvExtension;
+};
+
+class HGCMMsgSvcUnregisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the registered extension. */
+ HGCMSVCEXTHANDLE handle;
+};
+
+class HGCMMsgNotify: public HGCMMsgCore
+{
+ public:
+ /** The event. */
+ HGCMNOTIFYEVENT enmEvent;
+};
+
+static HGCMMsgCore *hgcmMessageAllocSvc(uint32_t u32MsgId)
+{
+ switch (u32MsgId)
+ {
+ case SVC_MSG_LOAD: return new HGCMMsgSvcLoad();
+ case SVC_MSG_UNLOAD: return new HGCMMsgSvcUnload();
+ case SVC_MSG_CONNECT: return new HGCMMsgSvcConnect();
+ case SVC_MSG_DISCONNECT: return new HGCMMsgSvcDisconnect();
+ case SVC_MSG_HOSTCALL: return new HGCMMsgHostCallSvc();
+ case SVC_MSG_GUESTCALL: return new HGCMMsgCall();
+ case SVC_MSG_LOADSTATE:
+ case SVC_MSG_SAVESTATE: return new HGCMMsgLoadSaveStateClient();
+ case SVC_MSG_REGEXT: return new HGCMMsgSvcRegisterExtension();
+ case SVC_MSG_UNREGEXT: return new HGCMMsgSvcUnregisterExtension();
+ case SVC_MSG_NOTIFY: return new HGCMMsgNotify();
+ case SVC_MSG_GUESTCANCELLED: return new HGCMMsgCancelled();
+ default:
+ AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId));
+ }
+
+ return NULL;
+}
+
+/*
+ * The service thread. Loads the service library and calls the service entry points.
+ */
+DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser)
+{
+ HGCMService *pSvc = (HGCMService *)pvUser;
+ AssertRelease(pSvc != NULL);
+
+ bool fQuit = false;
+
+ while (!fQuit)
+ {
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgGet(pThread, &pMsgCore);
+
+ if (RT_FAILURE(vrc))
+ {
+ /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */
+ AssertMsgFailed(("%Rrc\n", vrc));
+ break;
+ }
+
+ STAM_REL_PROFILE_START(&pSvc->m_StatHandleMsg, a);
+
+ /* Cache required information to avoid unnecessary pMsgCore access. */
+ uint32_t u32MsgId = pMsgCore->MsgId();
+
+ switch (u32MsgId)
+ {
+ case SVC_MSG_LOAD:
+ {
+ LogFlowFunc(("SVC_MSG_LOAD\n"));
+ vrc = pSvc->loadServiceDLL();
+ } break;
+
+ case SVC_MSG_UNLOAD:
+ {
+ LogFlowFunc(("SVC_MSG_UNLOAD\n"));
+ if (pSvc->m_fntable.pfnUnload)
+ {
+ pSvc->m_fntable.pfnUnload(pSvc->m_fntable.pvService);
+ }
+
+ pSvc->unloadServiceDLL();
+ fQuit = true;
+ } break;
+
+ case SVC_MSG_CONNECT:
+ {
+ HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_CONNECT u32ClientId = %d\n", pMsg->u32ClientId));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ pSvc->m_fInConnectOrDisconnect = true;
+ vrc = pSvc->m_fntable.pfnConnect(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient),
+ pMsg->fRequestor, pMsg->fRestoring);
+ pSvc->m_fInConnectOrDisconnect = false;
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_DISCONNECT:
+ {
+ HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_DISCONNECT u32ClientId = %d, pClient = %p\n", pMsg->u32ClientId, pMsg->pClient));
+
+ if (pMsg->pClient)
+ {
+ pSvc->m_fInConnectOrDisconnect = true;
+ vrc = pSvc->m_fntable.pfnDisconnect(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pMsg->pClient));
+ pSvc->m_fInConnectOrDisconnect = false;
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_GUESTCALL:
+ {
+ HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_GUESTCALL u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n",
+ pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ pSvc->m_fntable.pfnCall(pSvc->m_fntable.pvService, (VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function,
+ pMsg->cParms, pMsg->paParms, pMsg->tsArrival);
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_GUESTCANCELLED:
+ {
+ HGCMMsgCancelled *pMsg = (HGCMMsgCancelled *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_GUESTCANCELLED idClient = %d\n", pMsg->idClient));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(pMsg->idClient);
+
+ if (pClient)
+ {
+ pSvc->m_fntable.pfnCancelled(pSvc->m_fntable.pvService, pMsg->idClient, HGCM_CLIENT_DATA(pSvc, pClient));
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_HOSTCALL:
+ {
+ HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_HOSTCALL u32Function = %d, cParms = %d, paParms = %p\n",
+ pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ vrc = pSvc->m_fntable.pfnHostCall(pSvc->m_fntable.pvService, pMsg->u32Function, pMsg->cParms, pMsg->paParms);
+ } break;
+
+ case SVC_MSG_LOADSTATE:
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_LOADSTATE\n"));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (pClient)
+ {
+ /* fRequestor: Restored by the message sender already. */
+ bool fHaveClientState = pSvc->m_fntable.pfnLoadState != NULL;
+ if (pMsg->uVersion > HGCM_SAVED_STATE_VERSION_V2)
+ vrc = pMsg->pVMM->pfnSSMR3GetBool(pMsg->pSSM, &fHaveClientState);
+ else
+ vrc = VINF_SUCCESS;
+ if (RT_SUCCESS(vrc) )
+ {
+ if (pSvc->m_fntable.pfnLoadState)
+ vrc = pSvc->m_fntable.pfnLoadState(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM,
+ fHaveClientState ? pMsg->uVersion : 0);
+ else
+ AssertLogRelStmt(!fHaveClientState, vrc = VERR_INTERNAL_ERROR_5);
+ }
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_SAVESTATE:
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_SAVESTATE\n"));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ vrc = VINF_SUCCESS;
+
+ if (pClient)
+ {
+ pMsg->pVMM->pfnSSMR3PutU32(pMsg->pSSM, pClient->fRequestor); /* Quicker to save this here than in the message sender. */
+ vrc = pMsg->pVMM->pfnSSMR3PutBool(pMsg->pSSM, pSvc->m_fntable.pfnSaveState != NULL);
+ if (RT_SUCCESS(vrc) && pSvc->m_fntable.pfnSaveState)
+ {
+ g_fSaveState = true;
+ vrc = pSvc->m_fntable.pfnSaveState(pSvc->m_fntable.pvService, pMsg->u32ClientId,
+ HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, pMsg->pVMM);
+ g_fSaveState = false;
+ }
+
+ hgcmObjDereference(pClient);
+ }
+ else
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ }
+ } break;
+
+ case SVC_MSG_REGEXT:
+ {
+ HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_REGEXT handle = %p, pfn = %p\n", pMsg->handle, pMsg->pfnExtension));
+
+ if (pSvc->m_hExtension)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ if (pSvc->m_fntable.pfnRegisterExtension)
+ {
+ vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, pMsg->pfnExtension,
+ pMsg->pvExtension);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pSvc->m_hExtension = pMsg->handle;
+ }
+ }
+ } break;
+
+ case SVC_MSG_UNREGEXT:
+ {
+ HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_UNREGEXT handle = %p\n", pMsg->handle));
+
+ if (pSvc->m_hExtension != pMsg->handle)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ else
+ {
+ if (pSvc->m_fntable.pfnRegisterExtension)
+ {
+ vrc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, NULL, NULL);
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ pSvc->m_hExtension = NULL;
+ }
+ } break;
+
+ case SVC_MSG_NOTIFY:
+ {
+ HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pMsgCore;
+
+ LogFlowFunc(("SVC_MSG_NOTIFY enmEvent = %d\n", pMsg->enmEvent));
+
+ pSvc->m_fntable.pfnNotify(pSvc->m_fntable.pvService, pMsg->enmEvent);
+ } break;
+
+ default:
+ {
+ AssertMsgFailed(("hgcmServiceThread::Unsupported message number %08X\n", u32MsgId));
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ if (u32MsgId != SVC_MSG_GUESTCALL)
+ {
+ /* For SVC_MSG_GUESTCALL the service calls the completion helper.
+ * Other messages have to be completed here.
+ */
+ hgcmMsgComplete (pMsgCore, vrc);
+ }
+ STAM_REL_PROFILE_STOP(&pSvc->m_StatHandleMsg, a);
+ }
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnCallComplete}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t vrc)
+{
+ 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, vrc);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnDisconnectClient}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpDisconnectClient(void *pvInstance, uint32_t idClient)
+{
+ HGCMService *pService = static_cast <HGCMService *> (pvInstance);
+ AssertReturn(pService, VERR_INVALID_HANDLE);
+
+ /* Only safe to call when the main HGCM thread is waiting on the service
+ to handle a SVC_MSG_CONNECT or SVC_MSG_DISCONNECT message. Otherwise
+ we'd risk racing it and corrupt data structures. */
+ AssertReturn(pService->m_fInConnectOrDisconnect, VERR_INVALID_CONTEXT);
+
+ /* Resolve the client ID and verify that it belongs to this service before
+ trying to disconnect it. */
+ int vrc = VERR_NOT_FOUND;
+ HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient);
+ if (pClient)
+ {
+ if (pClient->pService == pService)
+ vrc = pService->DisconnectClient(idClient, true, pClient);
+ hgcmObjDereference(pClient);
+ }
+ return vrc;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallRestored}
+ */
+/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle;
+ AssertPtrReturn(pMsgHdr, false);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, false);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, false);
+
+ return pHgcmPort->pfnIsCmdRestored(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallCancelled}
+ */
+/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle;
+ AssertPtrReturn(pMsgHdr, false);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, false);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, false);
+
+ return pHgcmPort->pfnIsCmdCancelled(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamRegisterV}
+ */
+/* static */ DECLCALLBACK(int)
+HGCMService::svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility,
+ STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnSTAMR3RegisterVU(pService->m_pUVM, pvSample, enmType, enmVisibility,
+ enmUnit, pszDesc, pszName, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamDeregisterV}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnSTAMR3DeregisterV(pService->m_pUVM, pszPatFmt, va);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoRegister}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc,
+ PFNDBGFHANDLEREXT pfnHandler, void *pvUser)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnDBGFR3InfoRegisterExternal(pService->m_pUVM, pszName, pszDesc, pfnHandler, pvUser);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoDeregister}
+ */
+/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoDeregister(void *pvInstance, const char *pszName)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, VERR_INVALID_PARAMETER);
+ if (pService->m_pUVM)
+ return pService->m_pVMM->pfnDBGFR3InfoDeregisterExternal(pService->m_pUVM, pszName);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetRequestor}
+ */
+/* static */ DECLCALLBACK(uint32_t) HGCMService::svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall)
+{
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)(hCall);
+ AssertPtrReturn(pMsgHdr, VMMDEV_REQUESTOR_LOWEST);
+
+ PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
+ AssertPtrReturn(pCmd, VMMDEV_REQUESTOR_LOWEST);
+
+ PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;
+ AssertPtrReturn(pHgcmPort, VMMDEV_REQUESTOR_LOWEST);
+
+ return pHgcmPort->pfnGetRequestor(pHgcmPort, pCmd);
+}
+
+/**
+ * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetVMMDevSessionId}
+ */
+/* static */ DECLCALLBACK(uint64_t) HGCMService::svcHlpGetVMMDevSessionId(void *pvInstance)
+{
+ HGCMService *pService = static_cast <HGCMService *>(pvInstance);
+ AssertPtrReturn(pService, UINT64_MAX);
+
+ PPDMIHGCMPORT pHgcmPort = pService->m_pHgcmPort;
+ AssertPtrReturn(pHgcmPort, UINT64_MAX);
+
+ return pHgcmPort->pfnGetVMMDevSessionId(pHgcmPort);
+}
+
+
+static DECLCALLBACK(int) hgcmMsgCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore)
+{
+ /* Call the VMMDev port interface to issue IRQ notification. */
+ HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)pMsgCore;
+
+ LogFlow(("MAIN::hgcmMsgCompletionCallback: message %p\n", pMsgCore));
+
+ if (pMsgHdr->pHGCMPort)
+ {
+ if (!g_fResetting)
+ return pMsgHdr->pHGCMPort->pfnCompleted(pMsgHdr->pHGCMPort,
+ g_fSaveState ? VINF_HGCM_SAVE_STATE : result, pMsgHdr->pCmd);
+ return VERR_ALREADY_RESET; /* best I could find. */
+ }
+ return VERR_NOT_AVAILABLE;
+}
+
+/*
+ * The main HGCM methods of the service.
+ */
+
+int HGCMService::instanceCreate(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("name %s, lib %s\n", pszServiceName, pszServiceLibrary));
+
+ /* The maximum length of the thread name, allowed by the RT is 15. */
+ char szThreadName[16];
+ if (!strncmp(pszServiceName, RT_STR_TUPLE("VBoxShared")))
+ RTStrPrintf(szThreadName, sizeof(szThreadName), "Sh%s", pszServiceName + 10);
+ else if (!strncmp(pszServiceName, RT_STR_TUPLE("VBox")))
+ RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName + 4);
+ else
+ RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName);
+
+ int vrc = hgcmThreadCreate(&m_pThread, szThreadName, hgcmServiceThread, this, pszServiceName, pUVM, pVMM);
+ if (RT_SUCCESS(vrc))
+ {
+ m_pszSvcName = RTStrDup(pszServiceName);
+ m_pszSvcLibrary = RTStrDup(pszServiceLibrary);
+
+ if (!m_pszSvcName || !m_pszSvcLibrary)
+ {
+ RTStrFree(m_pszSvcLibrary);
+ m_pszSvcLibrary = NULL;
+
+ RTStrFree(m_pszSvcName);
+ m_pszSvcName = NULL;
+
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ m_pUVM = pUVM;
+ m_pVMM = pVMM;
+ m_pHgcmPort = pHgcmPort;
+
+ registerStatistics(pszServiceName, pUVM, pVMM);
+
+ /* Initialize service helpers table. */
+ m_svcHelpers.pfnCallComplete = svcHlpCallComplete;
+ m_svcHelpers.pvInstance = this;
+ m_svcHelpers.pfnDisconnectClient = svcHlpDisconnectClient;
+ m_svcHelpers.pfnIsCallRestored = svcHlpIsCallRestored;
+ m_svcHelpers.pfnIsCallCancelled = svcHlpIsCallCancelled;
+ m_svcHelpers.pfnStamRegisterV = svcHlpStamRegisterV;
+ m_svcHelpers.pfnStamDeregisterV = svcHlpStamDeregisterV;
+ m_svcHelpers.pfnInfoRegister = svcHlpInfoRegister;
+ m_svcHelpers.pfnInfoDeregister = svcHlpInfoDeregister;
+ m_svcHelpers.pfnGetRequestor = svcHlpGetRequestor;
+ m_svcHelpers.pfnGetVMMDevSessionId = svcHlpGetVMMDevSessionId;
+
+ /* Execute the load request on the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOAD, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcLoad *pMsg = (HGCMMsgSvcLoad *)pCoreMsg;
+
+ pMsg->pUVM = pUVM;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ instanceDestroy();
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Called by HGCMService::instanceCreate to register statistics. */
+void HGCMService::registerStatistics(const char *pszServiceName, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatHandleMsg, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_OCCURENCE,
+ "Message handling", "/HGCM/%s/Msg", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyCalls, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Too many calls (per client)", "/HGCM/%s/TooManyCalls", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatTooManyClients, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Too many clients", "/HGCM/%s/TooManyClients", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_cClients, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of clients", "/HGCM/%s/Clients", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of kernel clients", "/HGCM/%s/Clients/Kernel", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of root/admin clients", "/HGCM/%s/Clients/Root", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_acClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Number of regular user clients", "/HGCM/%s/Clients/User", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of kernel clients", "/HGCM/%s/Clients/KernelMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of root clients", "/HGCM/%s/Clients/RootMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxClients[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of user clients", "/HGCM/%s/Clients/UserMax", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.idxLegacyClientCategory, STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Legacy client mapping", "/HGCM/%s/Clients/LegacyClientMapping", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_KERNEL], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per kernel client", "/HGCM/%s/MaxCallsKernelClient", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_ROOT], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per root client", "/HGCM/%s/MaxCallsRootClient", pszServiceName);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_fntable.acMaxCallsPerClient[HGCM_CLIENT_CATEGORY_USER], STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_OCCURENCES, "Max number of call per user client", "/HGCM/%s/MaxCallsUserClient", pszServiceName);
+}
+
+void HGCMService::instanceDestroy(void)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pMsg, SVC_MSG_UNLOAD, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ hgcmThreadWait(m_pThread);
+ }
+
+ if (m_pszSvcName && m_pUVM)
+ m_pVMM->pfnSTAMR3DeregisterF(m_pUVM, "/HGCM/%s/*", m_pszSvcName);
+ m_pUVM = NULL;
+ m_pHgcmPort = NULL;
+
+ RTStrFree(m_pszSvcLibrary);
+ m_pszSvcLibrary = NULL;
+
+ RTStrFree(m_pszSvcName);
+ m_pszSvcName = NULL;
+
+ if (m_paClientIds)
+ {
+ RTMemFree(m_paClientIds);
+ m_paClientIds = NULL;
+ }
+}
+
+int HGCMService::saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_SAVESTATE, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg;
+
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMService::loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ LogFlowFunc(("%s\n", m_pszSvcName));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOADSTATE, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg;
+
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+ pMsg->uVersion = uVersion;
+ pMsg->u32ClientId = u32ClientId;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/** The method creates a service and references it.
+ *
+ * @param pszServiceLibrary The library to be loaded.
+ * @param pszServiceName The name of the service.
+ * @param pUVM The user mode VM handle (for statistics and such).
+ * @param pVMM The VMM vtable (for statistics and such).
+ * @param pHgcmPort The VMMDev HGCM port interface.
+ *
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::LoadService(const char *pszServiceLibrary, const char *pszServiceName,
+ PUVM pUVM, PCVMMR3VTABLE pVMM, PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("lib %s, name = %s, pUVM = %p\n", pszServiceLibrary, pszServiceName, pUVM));
+
+ /* Look at already loaded services to avoid double loading. */
+
+ HGCMService *pSvc;
+ int vrc = HGCMService::ResolveService(&pSvc, pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* The service is already loaded. */
+ pSvc->ReleaseService();
+ vrc = VERR_HGCM_SERVICE_EXISTS;
+ }
+ else
+ {
+ /* Create the new service. */
+ pSvc = new (std::nothrow) HGCMService();
+
+ if (!pSvc)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Load the library and call the initialization entry point. */
+ vrc = pSvc->instanceCreate(pszServiceLibrary, pszServiceName, pUVM, pVMM, pHgcmPort);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Insert the just created service to list for future references. */
+ pSvc->m_pSvcNext = sm_pSvcListHead;
+ pSvc->m_pSvcPrev = NULL;
+
+ if (sm_pSvcListHead)
+ sm_pSvcListHead->m_pSvcPrev = pSvc;
+ else
+ sm_pSvcListTail = pSvc;
+
+ sm_pSvcListHead = pSvc;
+
+ sm_cServices++;
+
+ /* Reference the service (for first time) until it is unloaded on HGCM termination. */
+ AssertRelease(pSvc->m_u32RefCnt == 0);
+ pSvc->ReferenceService();
+
+ LogFlowFunc(("service %p\n", pSvc));
+ }
+ }
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** The method unloads a service.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::UnloadService(bool fUvmIsInvalid)
+{
+ LogFlowFunc(("name = %s\n", m_pszSvcName));
+
+ if (fUvmIsInvalid)
+ {
+ m_pUVM = NULL;
+ m_pHgcmPort = NULL;
+ }
+
+ /* Remove the service from the list. */
+ if (m_pSvcNext)
+ {
+ m_pSvcNext->m_pSvcPrev = m_pSvcPrev;
+ }
+ else
+ {
+ sm_pSvcListTail = m_pSvcPrev;
+ }
+
+ if (m_pSvcPrev)
+ {
+ m_pSvcPrev->m_pSvcNext = m_pSvcNext;
+ }
+ else
+ {
+ sm_pSvcListHead = m_pSvcNext;
+ }
+
+ sm_cServices--;
+
+ /* The service must be unloaded only if all clients were disconnected. */
+ LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt));
+ AssertRelease(m_u32RefCnt == 1);
+
+ /* Now the service can be released. */
+ ReleaseService();
+}
+
+/** The method unloads all services.
+ *
+ * @thread main HGCM
+ */
+/* static */ void HGCMService::UnloadAll(bool fUvmIsInvalid)
+{
+ while (sm_pSvcListHead)
+ {
+ sm_pSvcListHead->UnloadService(fUvmIsInvalid);
+ }
+}
+
+/** The method obtains a referenced pointer to the service with
+ * specified name. The caller must call ReleaseService when
+ * the pointer is no longer needed.
+ *
+ * @param ppSvc Where to store the pointer to the service.
+ * @param pszServiceName The name of the service.
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::ResolveService(HGCMService **ppSvc, const char *pszServiceName)
+{
+ LogFlowFunc(("ppSvc = %p name = %s\n",
+ ppSvc, pszServiceName));
+
+ if (!ppSvc || !pszServiceName)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ if (strcmp(pSvc->m_pszSvcName, pszServiceName) == 0)
+ {
+ break;
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ LogFlowFunc(("lookup in the list is %p\n", pSvc));
+
+ if (pSvc == NULL)
+ {
+ *ppSvc = NULL;
+ return VERR_HGCM_SERVICE_NOT_FOUND;
+ }
+
+ pSvc->ReferenceService();
+
+ *ppSvc = pSvc;
+
+ return VINF_SUCCESS;
+}
+
+/** The method increases reference counter.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::ReferenceService(void)
+{
+ ASMAtomicIncU32(&m_u32RefCnt);
+ LogFlowFunc(("[%s] m_u32RefCnt = %d\n", m_pszSvcName, m_u32RefCnt));
+}
+
+/** The method dereferences a service and deletes it when no more refs.
+ *
+ * @thread main HGCM
+ */
+void HGCMService::ReleaseService(void)
+{
+ LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt));
+ uint32_t u32RefCnt = ASMAtomicDecU32(&m_u32RefCnt);
+ AssertRelease(u32RefCnt != ~0U);
+
+ LogFlowFunc(("u32RefCnt = %d, name %s\n", u32RefCnt, m_pszSvcName));
+
+ if (u32RefCnt == 0)
+ {
+ instanceDestroy();
+ delete this;
+ }
+}
+
+/** The method is called when the VM is being reset or terminated
+ * and disconnects all clients from all services.
+ *
+ * @thread main HGCM
+ */
+/* static */ void HGCMService::Reset(void)
+{
+ g_fResetting = true;
+
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ while (pSvc->m_cClients && pSvc->m_paClientIds)
+ {
+ uint32_t const idClient = pSvc->m_paClientIds[0];
+ HGCMClient * const pClient = HGCMClient::ReferenceByHandle(idClient);
+ Assert(pClient);
+ LogFlowFunc(("handle %d/%p\n", pSvc->m_paClientIds[0], pClient));
+
+ pSvc->DisconnectClient(pSvc->m_paClientIds[0], false, pClient);
+
+ hgcmObjDereference(pClient);
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ g_fResetting = false;
+}
+
+/** The method saves the HGCM state.
+ *
+ * @param pSSM The saved state context.
+ * @param pVMM The VMM vtable.
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::SaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ /* Save the current handle count and restore afterwards to avoid client id conflicts. */
+ int vrc = pVMM->pfnSSMR3PutU32(pSSM, hgcmObjQueryHandleCount());
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowFunc(("%d services to be saved:\n", sm_cServices));
+
+ /* Save number of services. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, sm_cServices);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save every service. */
+ HGCMService *pSvc = sm_pSvcListHead;
+
+ while (pSvc)
+ {
+ LogFlowFunc(("Saving service [%s]\n", pSvc->m_pszSvcName));
+
+ /* Save the length of the service name. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, (uint32_t) strlen(pSvc->m_pszSvcName) + 1);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save the name of the service. */
+ vrc = pVMM->pfnSSMR3PutStrZ(pSSM, pSvc->m_pszSvcName);
+ AssertRCReturn(vrc, vrc);
+
+ /* Save the number of clients. */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, pSvc->m_cClients);
+ AssertRCReturn(vrc, vrc);
+
+ /* Call the service for every client. Normally a service must not have
+ * a global state to be saved: only per client info is relevant.
+ * The global state of a service is configured during VM startup.
+ */
+ uint32_t i;
+
+ for (i = 0; i < pSvc->m_cClients; i++)
+ {
+ uint32_t u32ClientId = pSvc->m_paClientIds[i];
+
+ Log(("client id 0x%08X\n", u32ClientId));
+
+ /* Save the client id. (fRequestor is saved via SVC_MSG_SAVESTATE for convenience.) */
+ vrc = pVMM->pfnSSMR3PutU32(pSSM, u32ClientId);
+ AssertRCReturn(vrc, vrc);
+
+ /* Call the service, so the operation is executed by the service thread. */
+ vrc = pSvc->saveClientState(u32ClientId, pSSM, pVMM);
+ AssertRCReturn(vrc, vrc);
+ }
+
+ pSvc = pSvc->m_pSvcNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** The method loads saved HGCM state.
+ *
+ * @param pSSM The saved state handle.
+ * @param pVMM The VMM vtable.
+ * @param uVersion The state version being loaded.
+ * @return VBox status code.
+ * @thread main HGCM
+ */
+/* static */ int HGCMService::LoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ /* Restore handle count to avoid client id conflicts. */
+ uint32_t u32;
+
+ int vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+
+ hgcmObjSetHandleCount(u32);
+
+ /* Get the number of services. */
+ uint32_t cServices;
+
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cServices);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowFunc(("%d services to be restored:\n", cServices));
+
+ while (cServices--)
+ {
+ /* Get the length of the service name. */
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(u32 <= VBOX_HGCM_SVC_NAME_MAX_BYTES, VERR_SSM_UNEXPECTED_DATA);
+
+ /* Get the service name. */
+ char szServiceName[VBOX_HGCM_SVC_NAME_MAX_BYTES];
+ vrc = pVMM->pfnSSMR3GetStrZ(pSSM, szServiceName, u32);
+ AssertRCReturn(vrc, vrc);
+
+ LogRel(("HGCM: Restoring [%s]\n", szServiceName));
+
+ /* Resolve the service instance. */
+ HGCMService *pSvc;
+ vrc = ResolveService(&pSvc, szServiceName);
+ AssertLogRelMsgReturn(pSvc, ("vrc=%Rrc, %s\n", vrc, szServiceName), VERR_SSM_UNEXPECTED_DATA);
+
+ /* Get the number of clients. */
+ uint32_t cClients;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &cClients);
+ if (RT_FAILURE(vrc))
+ {
+ pSvc->ReleaseService();
+ AssertFailed();
+ return vrc;
+ }
+
+ while (cClients--)
+ {
+ /* Get the client ID and fRequest (convieniently save via SVC_MSG_SAVESTATE
+ but restored here in time for calling CreateAndConnectClient). */
+ uint32_t u32ClientId;
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &u32ClientId);
+ uint32_t fRequestor = VMMDEV_REQUESTOR_LEGACY;
+ if (RT_SUCCESS(vrc) && uVersion > HGCM_SAVED_STATE_VERSION_V2)
+ vrc = pVMM->pfnSSMR3GetU32(pSSM, &fRequestor);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+
+ /* Connect the client. */
+ vrc = pSvc->CreateAndConnectClient(NULL, u32ClientId, fRequestor, true /*fRestoring*/);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+
+ /* Call the service, so the operation is executed by the service thread. */
+ vrc = pSvc->loadClientState(u32ClientId, pSSM, pVMM, uVersion);
+ AssertLogRelMsgRCReturnStmt(vrc, ("vrc=%Rrc, %s\n", vrc, szServiceName), pSvc->ReleaseService(), vrc);
+ }
+
+ pSvc->ReleaseService();
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* Create a new client instance and connect it to the service.
+ *
+ * @param pu32ClientIdOut If not NULL, then the method must generate a new handle for the client.
+ * If NULL, use the given 'u32ClientIdIn' handle.
+ * @param u32ClientIdIn The handle for the client, when 'pu32ClientIdOut' is NULL.
+ * @param fRequestor The requestor flags, VMMDEV_REQUESTOR_LEGACY if not available.
+ * @param fRestoring Set if we're restoring a saved state.
+ * @return VBox status code.
+ */
+int HGCMService::CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring)
+{
+ LogFlowFunc(("pu32ClientIdOut = %p, u32ClientIdIn = %d, fRequestor = %#x, fRestoring = %d\n",
+ pu32ClientIdOut, u32ClientIdIn, fRequestor, fRestoring));
+
+ /*
+ * Categorize the client (compress VMMDEV_REQUESTOR_USR_MASK)
+ * and check the respective client limit.
+ */
+ uint32_t idxClientCategory;
+ if (fRequestor == VMMDEV_REQUESTOR_LEGACY)
+ {
+ idxClientCategory = m_fntable.idxLegacyClientCategory;
+ AssertStmt(idxClientCategory < RT_ELEMENTS(m_acClients), idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL);
+ }
+ else
+ switch (fRequestor & VMMDEV_REQUESTOR_USR_MASK)
+ {
+ case VMMDEV_REQUESTOR_USR_DRV:
+ case VMMDEV_REQUESTOR_USR_DRV_OTHER:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_KERNEL;
+ break;
+ case VMMDEV_REQUESTOR_USR_ROOT:
+ case VMMDEV_REQUESTOR_USR_SYSTEM:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_ROOT;
+ break;
+ default:
+ idxClientCategory = HGCM_CLIENT_CATEGORY_USER;
+ break;
+ }
+
+ if ( m_acClients[idxClientCategory] < m_fntable.acMaxClients[idxClientCategory]
+ || fRestoring)
+ { }
+ else
+ {
+ LogRel2(("Too many concurrenct clients for HGCM service '%s': %u, max %u; category %u\n",
+ m_pszSvcName, m_cClients, m_fntable.acMaxClients[idxClientCategory], idxClientCategory));
+ STAM_REL_COUNTER_INC(&m_StatTooManyClients);
+ return VERR_HGCM_TOO_MANY_CLIENTS;
+ }
+
+ /* Allocate a client information structure. */
+ HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor, idxClientCategory);
+
+ if (!pClient)
+ {
+ Log1WarningFunc(("Could not allocate HGCMClient!!!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ uint32_t handle;
+
+ if (pu32ClientIdOut != NULL)
+ {
+ handle = hgcmObjGenerateHandle(pClient);
+ }
+ else
+ {
+ handle = hgcmObjAssignHandle(pClient, u32ClientIdIn);
+ }
+
+ LogFlowFunc(("client id = %d\n", handle));
+
+ AssertRelease(handle);
+
+ /* Initialize the HGCM part of the client. */
+ int vrc = pClient->Init(this);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Call the service. */
+ HGCMMsgCore *pCoreMsg;
+
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg;
+
+ pMsg->u32ClientId = handle;
+ pMsg->fRequestor = fRequestor;
+ pMsg->fRestoring = fRestoring;
+
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Add the client Id to the array. */
+ if (m_cClients == m_cClientsAllocated)
+ {
+ const uint32_t cDelta = 64;
+
+ /* Guards against integer overflow on 32bit arch and also limits size of m_paClientIds array to 4GB*/
+ if (m_cClientsAllocated < UINT32_MAX / sizeof(m_paClientIds[0]) - cDelta)
+ {
+ uint32_t *paClientIdsNew;
+
+ paClientIdsNew = (uint32_t *)RTMemRealloc(m_paClientIds,
+ (m_cClientsAllocated + cDelta) * sizeof(m_paClientIds[0]));
+ Assert(paClientIdsNew);
+
+ if (paClientIdsNew)
+ {
+ m_paClientIds = paClientIdsNew;
+ m_cClientsAllocated += cDelta;
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_paClientIds[m_cClients] = handle;
+ m_cClients++;
+ m_acClients[idxClientCategory]++;
+ LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s\n",
+ handle, m_cClients, idxClientCategory, m_acClients[idxClientCategory], m_pszSvcName));
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pu32ClientIdOut != NULL)
+ {
+ *pu32ClientIdOut = handle;
+ }
+
+ ReferenceService();
+
+ /* The guest may now use this client object. */
+ pClient->makeAccessibleToGuest();
+ }
+ else
+ {
+ hgcmObjDeleteHandle(handle);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Disconnect the client from the service and delete the client handle.
+ *
+ * @param u32ClientId The handle of the client.
+ * @param fFromService Set if called by the service via
+ * svcHlpDisconnectClient().
+ * @param pClient The client disconnecting.
+ * @return VBox status code.
+ */
+int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService, HGCMClient *pClient)
+{
+ AssertPtr(pClient);
+ LogFlowFunc(("client id = %d, fFromService = %d, pClient = %p\n", u32ClientId, fFromService, pClient));
+
+ /*
+ * Destroy the client handle prior to the disconnecting to avoid creating
+ * a race with other messages from the same client. See @bugref{10038}
+ * for further details.
+ */
+ Assert(pClient->idxCategory < HGCM_CLIENT_CATEGORY_MAX);
+ Assert(m_acClients[pClient->idxCategory] > 0);
+
+ bool fReleaseService = false;
+ int vrc = VERR_NOT_FOUND;
+ for (uint32_t i = 0; i < m_cClients; i++)
+ {
+ if (m_paClientIds[i] == u32ClientId)
+ {
+ if (m_acClients[pClient->idxCategory] > 0)
+ m_acClients[pClient->idxCategory]--;
+
+ m_cClients--;
+
+ if (m_cClients > i)
+ memmove(&m_paClientIds[i], &m_paClientIds[i + 1], sizeof(m_paClientIds[0]) * (m_cClients - i));
+
+ /* Delete the client handle. */
+ hgcmObjDeleteHandle(u32ClientId);
+ fReleaseService = true;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ /* Some paranoia wrt to not trusting the client ID array. */
+ Assert(vrc == VINF_SUCCESS || fFromService);
+ if (vrc == VERR_NOT_FOUND && !fFromService)
+ {
+ if (m_acClients[pClient->idxCategory] > 0)
+ m_acClients[pClient->idxCategory]--;
+
+ hgcmObjDeleteHandle(u32ClientId);
+ fReleaseService = true;
+ }
+
+ LogFunc(("idClient=%u m_cClients=%u m_acClients[%u]=%u %s (cPendingCalls=%u) vrc=%Rrc\n", u32ClientId, m_cClients,
+ pClient->idxCategory, m_acClients[pClient->idxCategory], m_pszSvcName, pClient->cPendingCalls, vrc));
+
+ /*
+ * Call the service.
+ */
+ if (!fFromService)
+ {
+ /* Call the service. */
+ HGCMMsgCore *pCoreMsg;
+
+ vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_DISCONNECT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pCoreMsg;
+
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->pClient = pClient;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+ else
+ {
+ LogRel(("(%d, %d) [%s] hgcmMsgAlloc(%p, SVC_MSG_DISCONNECT) failed %Rrc\n",
+ u32ClientId, fFromService, RT_VALID_PTR(m_pszSvcName)? m_pszSvcName: "", m_pThread, vrc));
+ }
+ }
+
+
+ /*
+ * Release the pClient->pService reference.
+ */
+ if (fReleaseService)
+ ReleaseService();
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMService::RegisterExtension(HGCMSVCEXTHANDLE handle,
+ PFNHGCMSVCEXT pfnExtension,
+ void *pvExtension)
+{
+ LogFlowFunc(("%s\n", handle->pszServiceName));
+
+ /* Forward the message to the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_REGEXT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+ pMsg->pfnExtension = pfnExtension;
+ pMsg->pvExtension = pvExtension;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+void HGCMService::UnregisterExtension(HGCMSVCEXTHANDLE handle)
+{
+ /* Forward the message to the service thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_UNREGEXT, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+}
+
+/** @callback_method_impl{FNHGCMMSGCALLBACK} */
+static DECLCALLBACK(int) hgcmMsgCallCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore)
+{
+ /*
+ * Do common message completion then decrement the call counter
+ * for the client if necessary.
+ */
+ int vrc = hgcmMsgCompletionCallback(result, pMsgCore);
+
+ HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;
+ if (pMsg->pcCounter)
+ {
+ uint32_t cCalls = ASMAtomicDecU32(pMsg->pcCounter);
+ AssertStmt(cCalls < UINT32_MAX / 2, ASMAtomicWriteU32(pMsg->pcCounter, 0));
+ pMsg->pcCounter = NULL;
+ Log3Func(("pMsg=%p cPendingCalls=%u / %u (fun %u, %u parms)\n",
+ pMsg, cCalls, pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms));
+ }
+
+ return vrc;
+}
+
+/** Perform a guest call to the service.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle to be disconnected and deleted.
+ * @param pClient The client data.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @param tsArrival The STAM_GET_TS() value when the request arrived.
+ * @return VBox status code.
+ * @retval VINF_HGCM_ASYNC_EXECUTE on success.
+ */
+int HGCMService::GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, HGCMClient *pClient,
+ uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival)
+{
+ LogFlow(("MAIN::HGCMService::GuestCall\n"));
+
+ int vrc;
+ HGCMMsgCall *pMsg = new(std::nothrow) HGCMMsgCall(m_pThread);
+ if (pMsg)
+ {
+ pMsg->Reference(); /** @todo starts out with zero references. */
+
+ uint32_t cCalls = ASMAtomicIncU32(&pClient->cPendingCalls);
+ Assert(pClient->idxCategory < RT_ELEMENTS(m_fntable.acMaxCallsPerClient));
+ if (cCalls < m_fntable.acMaxCallsPerClient[pClient->idxCategory])
+ {
+ pMsg->pcCounter = &pClient->cPendingCalls;
+ Log3(("MAIN::HGCMService::GuestCall: pMsg=%p cPendingCalls=%u / %u / %s (fun %u, %u parms)\n",
+ pMsg, cCalls, u32ClientId, m_pszSvcName, u32Function, cParms));
+
+ pMsg->pCmd = pCmd;
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->u32ClientId = u32ClientId;
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+ pMsg->tsArrival = tsArrival;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCallCompletionCallback);
+
+ if (RT_SUCCESS(vrc))
+ { /* Reference donated on success. */ }
+ else
+ {
+ ASMAtomicDecU32(&pClient->cPendingCalls);
+ pMsg->pcCounter = NULL;
+ Log(("MAIN::HGCMService::GuestCall: hgcmMsgPost failed: %Rrc\n", vrc));
+ pMsg->Dereference();
+ }
+ }
+ else
+ {
+ ASMAtomicDecU32(&pClient->cPendingCalls);
+ LogRel2(("HGCM: Too many calls to '%s' from client %u: %u, max %u; category %u\n", m_pszSvcName, u32ClientId,
+ cCalls, m_fntable.acMaxCallsPerClient[pClient->idxCategory], pClient->idxCategory));
+ pMsg->Dereference();
+ STAM_REL_COUNTER_INC(&m_StatTooManyCalls);
+ vrc = VERR_HGCM_TOO_MANY_CLIENT_CALLS;
+ }
+ }
+ else
+ {
+ Log(("MAIN::HGCMService::GuestCall: Message allocation failed\n"));
+ vrc = VERR_NO_MEMORY;
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Guest cancelled a request (call, connection attempt, disconnect attempt).
+ *
+ * @param pHGCMPort The port to be used for completion confirmation
+ * @param pCmd The VBox HGCM context.
+ * @param idClient The client handle to be disconnected and deleted.
+ */
+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 status code.
+ */
+int HGCMService::HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms)
+{
+ LogFlowFunc(("%s u32Function = %d, cParms = %d, paParms = %p\n",
+ m_pszSvcName, u32Function, cParms, paParms));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTCALL, hgcmMessageAllocSvc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pCoreMsg;
+
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Posts a broadcast notification event to all interested services.
+ *
+ * @param enmEvent The notification event.
+ */
+/*static*/ void HGCMService::BroadcastNotify(HGCMNOTIFYEVENT enmEvent)
+{
+ for (HGCMService *pService = sm_pSvcListHead; pService != NULL; pService = pService->m_pSvcNext)
+ {
+ pService->Notify(enmEvent);
+ }
+}
+
+/** Posts a broadcast notification event to the service.
+ *
+ * @param enmEvent The notification event.
+ */
+void HGCMService::Notify(HGCMNOTIFYEVENT enmEvent)
+{
+ LogFlowFunc(("%s enmEvent=%d pfnNotify=%p\n", m_pszSvcName, enmEvent, m_fntable.pfnNotify));
+ if (m_fntable.pfnNotify)
+ {
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_NOTIFY, hgcmMessageAllocSvc);
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pCoreMsg;
+ pMsg->enmEvent = enmEvent;
+
+ vrc = hgcmMsgPost(pMsg, NULL);
+ AssertRC(vrc);
+ }
+ }
+}
+
+/*
+ * Main HGCM thread that manages services.
+ */
+
+/* Messages processed by the main HGCM thread. */
+#define HGCM_MSG_CONNECT (10) /**< Connect a client to a service. */
+#define HGCM_MSG_DISCONNECT (11) /**< Disconnect the specified client id. */
+#define HGCM_MSG_LOAD (12) /**< Load the service. */
+#define HGCM_MSG_HOSTCALL (13) /**< Call the service. */
+#define HGCM_MSG_LOADSTATE (14) /**< Load saved state for the specified service. */
+#define HGCM_MSG_SAVESTATE (15) /**< Save state for the specified service. */
+#define HGCM_MSG_RESET (16) /**< Disconnect all clients from the specified service. */
+#define HGCM_MSG_QUIT (17) /**< Unload all services and terminate the thread. */
+#define HGCM_MSG_REGEXT (18) /**< Register a service extension. */
+#define HGCM_MSG_UNREGEXT (19) /**< Unregister a service extension. */
+#define HGCM_MSG_BRD_NOTIFY (20) /**< Broadcast notification event (VM state change). */
+
+class HGCMMsgMainConnect: public HGCMMsgHeader
+{
+ public:
+ /* Service name. */
+ const char *pszServiceName;
+ /* Where to store the client handle. */
+ uint32_t *pu32ClientId;
+};
+
+class HGCMMsgMainDisconnect: public HGCMMsgHeader
+{
+ public:
+ /* Handle of the client to be disconnected. */
+ uint32_t u32ClientId;
+};
+
+class HGCMMsgMainLoad: public HGCMMsgCore
+{
+ public:
+ /* Name of the library to be loaded. */
+ const char *pszServiceLibrary;
+ /* Name to be assigned to the service. */
+ const char *pszServiceName;
+ /** The user mode VM handle (for statistics and such). */
+ PUVM pUVM;
+ /** The VMM vtable (for statistics and such). */
+ PCVMMR3VTABLE pVMM;
+ /** The HGCM port on the VMMDev device (for session ID and such). */
+ PPDMIHGCMPORT pHgcmPort;
+};
+
+class HGCMMsgMainHostCall: public HGCMMsgCore
+{
+ public:
+ /* Which service to call. */
+ const char *pszServiceName;
+ /* Function number. */
+ uint32_t u32Function;
+ /* Number of the function parameters. */
+ uint32_t cParms;
+ /* Pointer to array of the function parameters. */
+ VBOXHGCMSVCPARM *paParms;
+};
+
+class HGCMMsgMainLoadSaveState: public HGCMMsgCore
+{
+ public:
+ /** Saved state handle. */
+ PSSMHANDLE pSSM;
+ /** The VMM vtable. */
+ PCVMMR3VTABLE pVMM;
+ /** The HGCM saved state version being loaded (ignore for save). */
+ uint32_t uVersion;
+};
+
+class HGCMMsgMainReset: public HGCMMsgCore
+{
+ public:
+ /** Set if this is actually a shutdown and not a VM reset. */
+ bool fForShutdown;
+};
+
+class HGCMMsgMainQuit: public HGCMMsgCore
+{
+ public:
+ /** Whether UVM has gone invalid already or not. */
+ bool fUvmIsInvalid;
+};
+
+class HGCMMsgMainRegisterExtension: public HGCMMsgCore
+{
+ public:
+ /** Returned handle to be used in HGCMMsgMainUnregisterExtension. */
+ HGCMSVCEXTHANDLE *pHandle;
+ /** Name of the service. */
+ const char *pszServiceName;
+ /** The extension entry point. */
+ PFNHGCMSVCEXT pfnExtension;
+ /** The extension pointer. */
+ void *pvExtension;
+};
+
+class HGCMMsgMainUnregisterExtension: public HGCMMsgCore
+{
+ public:
+ /* Handle of the registered extension. */
+ HGCMSVCEXTHANDLE handle;
+};
+
+class HGCMMsgMainBroadcastNotify: public HGCMMsgCore
+{
+ public:
+ /** The notification event. */
+ HGCMNOTIFYEVENT enmEvent;
+};
+
+
+static HGCMMsgCore *hgcmMainMessageAlloc (uint32_t u32MsgId)
+{
+ switch (u32MsgId)
+ {
+ case HGCM_MSG_CONNECT: return new HGCMMsgMainConnect();
+ case HGCM_MSG_DISCONNECT: return new HGCMMsgMainDisconnect();
+ case HGCM_MSG_LOAD: return new HGCMMsgMainLoad();
+ case HGCM_MSG_HOSTCALL: return new HGCMMsgMainHostCall();
+ case HGCM_MSG_LOADSTATE:
+ case HGCM_MSG_SAVESTATE: return new HGCMMsgMainLoadSaveState();
+ case HGCM_MSG_RESET: return new HGCMMsgMainReset();
+ case HGCM_MSG_QUIT: return new HGCMMsgMainQuit();
+ case HGCM_MSG_REGEXT: return new HGCMMsgMainRegisterExtension();
+ case HGCM_MSG_UNREGEXT: return new HGCMMsgMainUnregisterExtension();
+ case HGCM_MSG_BRD_NOTIFY: return new HGCMMsgMainBroadcastNotify();
+
+ default:
+ AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId));
+ }
+
+ return NULL;
+}
+
+
+/* The main HGCM thread handler. */
+static DECLCALLBACK(void) hgcmThread(HGCMThread *pThread, void *pvUser)
+{
+ LogFlowFunc(("pThread = %p, pvUser = %p\n", pThread, pvUser));
+
+ NOREF(pvUser);
+
+ bool fQuit = false;
+
+ while (!fQuit)
+ {
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgGet(pThread, &pMsgCore);
+
+ if (RT_FAILURE(vrc))
+ {
+ /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */
+ AssertMsgFailed(("%Rrc\n", vrc));
+ break;
+ }
+
+ uint32_t u32MsgId = pMsgCore->MsgId();
+
+ switch (u32MsgId)
+ {
+ case HGCM_MSG_CONNECT:
+ {
+ HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_CONNECT pszServiceName %s, pu32ClientId %p\n",
+ pMsg->pszServiceName, pMsg->pu32ClientId));
+
+ /* Resolve the service name to the pointer to service instance.
+ */
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Call the service instance method. */
+ vrc = pService->CreateAndConnectClient(pMsg->pu32ClientId,
+ 0,
+ pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd),
+ pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd));
+
+ /* Release the service after resolve. */
+ pService->ReleaseService();
+ }
+ } break;
+
+ case HGCM_MSG_DISCONNECT:
+ {
+ HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_DISCONNECT u32ClientId = %d\n",
+ pMsg->u32ClientId));
+
+ HGCMClient *pClient = HGCMClient::ReferenceByHandle(pMsg->u32ClientId);
+
+ if (!pClient)
+ {
+ vrc = VERR_HGCM_INVALID_CLIENT_ID;
+ break;
+ }
+
+ /* The service the client belongs to. */
+ HGCMService *pService = pClient->pService;
+
+ /* Call the service instance to disconnect the client. */
+ vrc = pService->DisconnectClient(pMsg->u32ClientId, false, pClient);
+
+ hgcmObjDereference(pClient);
+ } break;
+
+ case HGCM_MSG_LOAD:
+ {
+ HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_LOAD pszServiceName = %s, pMsg->pszServiceLibrary = %s, pMsg->pUVM = %p\n",
+ pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pUVM));
+
+ vrc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName,
+ pMsg->pUVM, pMsg->pVMM, pMsg->pHgcmPort);
+ } break;
+
+ case HGCM_MSG_HOSTCALL:
+ {
+ HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_HOSTCALL pszServiceName %s, u32Function %d, cParms %d, paParms %p\n",
+ pMsg->pszServiceName, pMsg->u32Function, pMsg->cParms, pMsg->paParms));
+
+ /* Resolve the service name to the pointer to service instance. */
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pService->HostCall(pMsg->u32Function, pMsg->cParms, pMsg->paParms);
+
+ pService->ReleaseService();
+ }
+ } break;
+
+ case HGCM_MSG_BRD_NOTIFY:
+ {
+ HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_BRD_NOTIFY enmEvent=%d\n", pMsg->enmEvent));
+
+ HGCMService::BroadcastNotify(pMsg->enmEvent);
+ } break;
+
+ case HGCM_MSG_RESET:
+ {
+ LogFlowFunc(("HGCM_MSG_RESET\n"));
+
+ HGCMService::Reset();
+
+ HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore;
+ if (!pMsg->fForShutdown)
+ HGCMService::BroadcastNotify(HGCMNOTIFYEVENT_RESET);
+ } break;
+
+ case HGCM_MSG_LOADSTATE:
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_LOADSTATE\n"));
+
+ vrc = HGCMService::LoadState(pMsg->pSSM, pMsg->pVMM, pMsg->uVersion);
+ } break;
+
+ case HGCM_MSG_SAVESTATE:
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_SAVESTATE\n"));
+
+ vrc = HGCMService::SaveState(pMsg->pSSM, pMsg->pVMM);
+ } break;
+
+ case HGCM_MSG_QUIT:
+ {
+ HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore;
+ LogFlowFunc(("HGCM_MSG_QUIT\n"));
+
+ HGCMService::UnloadAll(pMsg->fUvmIsInvalid);
+
+ fQuit = true;
+ } break;
+
+ case HGCM_MSG_REGEXT:
+ {
+ HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_REGEXT\n"));
+
+ /* Allocate the handle data. */
+ HGCMSVCEXTHANDLE handle = (HGCMSVCEXTHANDLE)RTMemAllocZ(sizeof(struct _HGCMSVCEXTHANDLEDATA)
+ + strlen(pMsg->pszServiceName)
+ + sizeof(char));
+
+ if (handle == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ handle->pszServiceName = (char *)((uint8_t *)handle + sizeof(struct _HGCMSVCEXTHANDLEDATA));
+ strcpy(handle->pszServiceName, pMsg->pszServiceName);
+
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, handle->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pService->RegisterExtension(handle, pMsg->pfnExtension, pMsg->pvExtension);
+
+ pService->ReleaseService();
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(handle);
+ }
+ else
+ {
+ *pMsg->pHandle = handle;
+ }
+ }
+ } break;
+
+ case HGCM_MSG_UNREGEXT:
+ {
+ HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pMsgCore;
+
+ LogFlowFunc(("HGCM_MSG_UNREGEXT\n"));
+
+ HGCMService *pService;
+ vrc = HGCMService::ResolveService(&pService, pMsg->handle->pszServiceName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pService->UnregisterExtension(pMsg->handle);
+
+ pService->ReleaseService();
+ }
+
+ RTMemFree(pMsg->handle);
+ } break;
+
+ default:
+ {
+ AssertMsgFailed(("hgcmThread: Unsupported message number %08X!!!\n", u32MsgId));
+ vrc = VERR_NOT_SUPPORTED;
+ } break;
+ }
+
+ /* Complete the message processing. */
+ hgcmMsgComplete(pMsgCore, vrc);
+
+ LogFlowFunc(("message processed %Rrc\n", vrc));
+ }
+}
+
+
+/*
+ * The HGCM API.
+ */
+
+/** The main hgcm thread. */
+static HGCMThread *g_pHgcmThread = 0;
+
+/*
+ * Public HGCM functions.
+ *
+ * hgcmGuest* - called as a result of the guest HGCM requests.
+ * hgcmHost* - called by the host.
+ */
+
+/* Load a HGCM service from the specified library.
+ * Assign the specified name to the service.
+ *
+ * @param pszServiceLibrary The library to be loaded.
+ * @param pszServiceName The name to be assigned to the service.
+ * @param pUVM The user mode VM handle (for statistics and such).
+ * @param pVMM The VMM vtable (for statistics and such).
+ * @param pHgcmPort The HGCM port on the VMMDev device (for session ID and such).
+ * @return VBox status code.
+ */
+int HGCMHostLoad(const char *pszServiceLibrary,
+ const char *pszServiceName,
+ PUVM pUVM,
+ PCVMMR3VTABLE pVMM,
+ PPDMIHGCMPORT pHgcmPort)
+{
+ LogFlowFunc(("lib = %s, name = %s\n", pszServiceLibrary, pszServiceName));
+
+ if (!pszServiceLibrary || !pszServiceName)
+ return VERR_INVALID_PARAMETER;
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_LOAD, hgcmMainMessageAlloc);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since the message is synchronous, use the supplied pointers. */
+ HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pCoreMsg;
+
+ pMsg->pszServiceLibrary = pszServiceLibrary;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pUVM = pUVM;
+ pMsg->pVMM = pVMM;
+ pMsg->pHgcmPort = pHgcmPort;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/* Register a HGCM service extension.
+ *
+ * @param pHandle Returned handle for the registered extension.
+ * @param pszServiceName The name of the service.
+ * @param pfnExtension The extension entry point (callback).
+ * @param pvExtension The extension pointer.
+ * @return VBox status code.
+ */
+int HGCMHostRegisterServiceExtension(HGCMSVCEXTHANDLE *pHandle,
+ const char *pszServiceName,
+ PFNHGCMSVCEXT pfnExtension,
+ void *pvExtension)
+{
+ LogFlowFunc(("pHandle = %p, name = %s, pfn = %p, rv = %p\n", pHandle, pszServiceName, pfnExtension, pvExtension));
+
+ if (!pHandle || !pszServiceName || !pfnExtension)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_REGEXT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since the message is synchronous, use the supplied pointers. */
+ HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pCoreMsg;
+
+ pMsg->pHandle = pHandle;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pfnExtension = pfnExtension;
+ pMsg->pvExtension = pvExtension;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("*pHandle = %p, vrc = %Rrc\n", *pHandle, vrc));
+ return vrc;
+}
+
+void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle)
+{
+ LogFlowFunc(("handle = %p\n", handle));
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_UNREGEXT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. */
+ HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pCoreMsg;
+
+ pMsg->handle = handle;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return;
+}
+
+/* Find a service and inform it about a client connection, create a client handle.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param pszServiceName The name of the service to be connected to.
+ * @param pu32ClientId Where the store the created client handle.
+ * @return VBox status code.
+ */
+int HGCMGuestConnect(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ const char *pszServiceName,
+ uint32_t *pu32ClientId)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, name = %s, pu32ClientId = %p\n",
+ pHGCMPort, pCmd, pszServiceName, pu32ClientId));
+
+ if (pHGCMPort == NULL || pCmd == NULL || pszServiceName == NULL || pu32ClientId == NULL)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_CONNECT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. Since 'pszServiceName' and 'pu32ClientId'
+ * will not be deallocated by the caller until the message is completed,
+ * use the supplied pointers.
+ */
+ HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pCoreMsg;
+
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->pCmd = pCmd;
+ pMsg->pszServiceName = pszServiceName;
+ pMsg->pu32ClientId = pu32ClientId;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/* Tell a service that the client is disconnecting, destroy the client handle.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle to be disconnected and deleted.
+ * @return VBox status code.
+ */
+int HGCMGuestDisconnect(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ uint32_t u32ClientId)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d\n",
+ pHGCMPort, pCmd, u32ClientId));
+
+ if (!pHGCMPort || !pCmd || !u32ClientId)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Forward the request to the main hgcm thread. */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_DISCONNECT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Initialize the message. */
+ HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pCoreMsg;
+
+ pMsg->pCmd = pCmd;
+ pMsg->pHGCMPort = pHGCMPort;
+ pMsg->u32ClientId = u32ClientId;
+
+ vrc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Helper to send either HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE messages to the main HGCM thread.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param idMsg The message to be sent: HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE.
+ * @param uVersion The state version being loaded.
+ * @return VBox status code.
+ */
+static int hgcmHostLoadSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t idMsg, uint32_t uVersion)
+{
+ LogFlowFunc(("pSSM = %p, pVMM = %p, idMsg = %d, uVersion = %#x\n", pSSM, pVMM, idMsg, uVersion));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, idMsg, hgcmMainMessageAlloc);
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pCoreMsg;
+ AssertRelease(pMsg);
+
+ pMsg->pSSM = pSSM;
+ pMsg->pVMM = pVMM;
+ pMsg->uVersion = uVersion;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Save the state of services.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @return VBox status code.
+ */
+int HGCMHostSaveState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM)
+{
+ return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_SAVESTATE, HGCM_SAVED_STATE_VERSION);
+}
+
+/** Load the state of services.
+ *
+ * @param pSSM The SSM handle.
+ * @param pVMM The VMM vtable.
+ * @param uVersion The state version being loaded.
+ * @return VBox status code.
+ */
+int HGCMHostLoadState(PSSMHANDLE pSSM, PCVMMR3VTABLE pVMM, uint32_t uVersion)
+{
+ return hgcmHostLoadSaveState(pSSM, pVMM, HGCM_MSG_LOADSTATE, uVersion);
+}
+
+/** The guest calls the service.
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param u32ClientId The client handle.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @param tsArrival The STAM_GET_TS() value when the request arrived.
+ * @return VBox status code.
+ */
+int HGCMGuestCall(PPDMIHGCMPORT pHGCMPort,
+ PVBOXHGCMCMD pCmd,
+ uint32_t u32ClientId,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM *paParms,
+ uint64_t tsArrival)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n",
+ pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms));
+
+ if (!pHGCMPort || !pCmd || u32ClientId == 0)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ int vrc = VERR_HGCM_INVALID_CLIENT_ID;
+
+ /* Resolve the client handle to the client instance pointer. */
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(u32ClientId);
+
+ if (pClient)
+ {
+ AssertRelease(pClient->pService);
+
+ /* Forward the message to the service thread. */
+ vrc = pClient->pService->GuestCall(pHGCMPort, pCmd, u32ClientId, pClient, u32Function, cParms, paParms, tsArrival);
+
+ hgcmObjDereference(pClient);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** The guest cancelled a request (call, connect, disconnect)
+ *
+ * @param pHGCMPort The port to be used for completion confirmation.
+ * @param pCmd The VBox HGCM context.
+ * @param idClient The client handle.
+ */
+void HGCMGuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient)
+{
+ LogFlowFunc(("pHGCMPort = %p, pCmd = %p, idClient = %d\n", pHGCMPort, pCmd, idClient));
+ AssertReturnVoid(pHGCMPort);
+ AssertReturnVoid(pCmd);
+ AssertReturnVoid(idClient != 0);
+
+ /* Resolve the client handle to the client instance pointer. */
+ HGCMClient *pClient = HGCMClient::ReferenceByHandleForGuest(idClient);
+
+ if (pClient)
+ {
+ AssertRelease(pClient->pService);
+
+ /* Forward the message to the service thread. */
+ pClient->pService->GuestCancelled(pHGCMPort, pCmd, idClient);
+
+ hgcmObjDereference(pClient);
+ }
+
+ LogFlowFunc(("returns\n"));
+}
+
+/** The host calls the service.
+ *
+ * @param pszServiceName The service name to be called.
+ * @param u32Function The function number.
+ * @param cParms Number of parameters.
+ * @param paParms Pointer to array of parameters.
+ * @return VBox status code.
+ */
+int HGCMHostCall(const char *pszServiceName,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM *paParms)
+{
+ LogFlowFunc(("name = %s, u32Function = %d, cParms = %d, paParms = %p\n",
+ pszServiceName, u32Function, cParms, paParms));
+
+ if (!pszServiceName)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Host calls go to main HGCM thread that resolves the service name to the
+ * service instance pointer and then, using the service pointer, forwards
+ * the message to the service thread.
+ * So it is slow but host calls are intended mostly for configuration and
+ * other non-time-critical functions.
+ */
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_HOSTCALL, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pCoreMsg;
+
+ pMsg->pszServiceName = (char *)pszServiceName;
+ pMsg->u32Function = u32Function;
+ pMsg->cParms = cParms;
+ pMsg->paParms = paParms;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+/** Posts a notification event to all services.
+ *
+ * @param enmEvent The notification event.
+ * @return VBox status code.
+ */
+int HGCMBroadcastEvent(HGCMNOTIFYEVENT enmEvent)
+{
+ LogFlowFunc(("enmEvent=%d\n", enmEvent));
+
+ HGCMMsgCore *pCoreMsg;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_BRD_NOTIFY, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pCoreMsg;
+
+ pMsg->enmEvent = enmEvent;
+
+ vrc = hgcmMsgPost(pMsg, NULL);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+int HGCMHostReset(bool fForShutdown)
+{
+ LogFlowFunc(("\n"));
+
+ /* Disconnect all clients.
+ */
+
+ HGCMMsgCore *pMsgCore;
+ int vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_RESET, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore;
+
+ pMsg->fForShutdown = fForShutdown;
+
+ vrc = hgcmMsgSend(pMsg);
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMHostInit(void)
+{
+ LogFlowFunc(("\n"));
+
+ int vrc = hgcmThreadInit();
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Start main HGCM thread.
+ */
+
+ vrc = hgcmThreadCreate(&g_pHgcmThread, "MainHGCMthread", hgcmThread, NULL /*pvUser*/,
+ NULL /*pszStatsSubDir*/, NULL /*pUVM*/, NULL /*pVMM*/);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Failed to start HGCM thread. HGCM services will be unavailable!!! vrc = %Rrc\n", vrc));
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMHostShutdown(bool fUvmIsInvalid /*= false*/)
+{
+ LogFlowFunc(("\n"));
+
+ /*
+ * Do HGCMReset and then unload all services.
+ */
+
+ int vrc = HGCMHostReset(true /*fForShutdown*/);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Send the quit message to the main hgcmThread. */
+ HGCMMsgCore *pMsgCore;
+ vrc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_QUIT, hgcmMainMessageAlloc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore;
+ pMsg->fUvmIsInvalid = fUvmIsInvalid;
+
+ vrc = hgcmMsgSend(pMsg);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Wait for the thread termination. */
+ hgcmThreadWait(g_pHgcmThread);
+ g_pHgcmThread = NULL;
+
+ hgcmThreadUninit();
+ }
+ }
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/HGCMObjects.cpp b/src/VBox/Main/src-client/HGCMObjects.cpp
new file mode 100644
index 00000000..3f0bd14e
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCMObjects.cpp
@@ -0,0 +1,286 @@
+/* $Id: HGCMObjects.cpp $ */
+/** @file
+ * HGCMObjects - Host-Guest Communication Manager objects
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCMObjects.h"
+
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+
+static RTCRITSECT g_critsect;
+
+/* There are internal handles, which are not saved,
+ * and client handles, which are saved.
+ * They use different range of values:
+ * 1..7FFFFFFF for clients,
+ * 0x80000001..0xFFFFFFFF for other handles.
+ */
+static uint32_t volatile g_u32InternalHandleCount;
+static uint32_t volatile g_u32ClientHandleCount;
+
+static PAVLU32NODECORE g_pTree;
+
+
+DECLINLINE(int) hgcmObjEnter(void)
+{
+ return RTCritSectEnter(&g_critsect);
+}
+
+DECLINLINE(void) hgcmObjLeave(void)
+{
+ RTCritSectLeave(&g_critsect);
+}
+
+int hgcmObjInit(void)
+{
+ LogFlow(("MAIN::hgcmObjInit\n"));
+
+ g_u32InternalHandleCount = 0x80000000;
+ g_u32ClientHandleCount = 0;
+ g_pTree = NULL;
+
+ int vrc = RTCritSectInit(&g_critsect);
+
+ LogFlow(("MAIN::hgcmObjInit: vrc = %Rrc\n", vrc));
+
+ return vrc;
+}
+
+void hgcmObjUninit(void)
+{
+ if (RTCritSectIsInitialized(&g_critsect))
+ RTCritSectDelete(&g_critsect);
+}
+
+uint32_t hgcmObjMake(HGCMObject *pObject, uint32_t u32HandleIn)
+{
+ int handle = 0;
+
+ LogFlow(("MAIN::hgcmObjGenerateHandle: pObject %p\n", pObject));
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = &pObject->m_core;
+
+ /* Generate a new handle value. */
+
+ uint32_t volatile *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT?
+ &g_u32ClientHandleCount:
+ &g_u32InternalHandleCount;
+
+ uint32_t u32Start = *pu32HandleCountSource;
+
+ for (;;)
+ {
+ uint32_t Key;
+
+ if (u32HandleIn == 0)
+ {
+ Key = ASMAtomicIncU32(pu32HandleCountSource);
+
+ if (Key == u32Start)
+ {
+ /* Rollover. Something is wrong. */
+ AssertReleaseFailed();
+ break;
+ }
+
+ /* 0 and 0x80000000 are not valid handles. */
+ if ((Key & 0x7FFFFFFF) == 0)
+ {
+ /* Over the invalid value, reinitialize the source. */
+ *pu32HandleCountSource = pObject->Type () == HGCMOBJ_CLIENT?
+ 0:
+ UINT32_C(0x80000000);
+ continue;
+ }
+ }
+ else
+ {
+ Key = u32HandleIn;
+ }
+
+ /* Insert object to AVL tree. */
+ pCore->AvlCore.Key = Key;
+
+ bool fRC = RTAvlU32Insert(&g_pTree, &pCore->AvlCore);
+
+ /* Could not insert a handle. */
+ if (!fRC)
+ {
+ if (u32HandleIn == 0)
+ {
+ /* Try another generated handle. */
+ continue;
+ }
+ /* Could not use the specified handle. */
+ break;
+ }
+
+ /* Initialize backlink. */
+ pCore->pSelf = pObject;
+
+ /* Reference the object for time while it resides in the tree. */
+ pObject->Reference();
+
+ /* Store returned handle. */
+ handle = Key;
+
+ Log(("Object key inserted 0x%08X\n", Key));
+
+ break;
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("MAIN::hgcmObjGenerateHandle: Failed to acquire object pool semaphore"));
+ }
+
+ LogFlow(("MAIN::hgcmObjGenerateHandle: handle = 0x%08X, vrc = %Rrc, return void\n", handle, vrc));
+
+ return handle;
+}
+
+uint32_t hgcmObjGenerateHandle(HGCMObject *pObject)
+{
+ return hgcmObjMake(pObject, 0);
+}
+
+uint32_t hgcmObjAssignHandle(HGCMObject *pObject, uint32_t u32Handle)
+{
+ return hgcmObjMake(pObject, u32Handle);
+}
+
+void hgcmObjDeleteHandle(uint32_t handle)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("MAIN::hgcmObjDeleteHandle: handle 0x%08X\n", handle));
+
+ if (handle)
+ {
+ vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Remove(&g_pTree, handle);
+
+ if (pCore)
+ {
+ AssertRelease(pCore->pSelf);
+
+ pCore->pSelf->Dereference();
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc));
+ }
+ }
+
+ LogFlow(("MAIN::hgcmObjDeleteHandle: vrc = %Rrc, return void\n", vrc));
+}
+
+HGCMObject *hgcmObjReference (uint32_t handle, HGCMOBJ_TYPE enmObjType)
+{
+ LogFlow(("MAIN::hgcmObjReference: handle 0x%08X\n", handle));
+
+ HGCMObject *pObject = NULL;
+
+ if ((handle & UINT32_C(0x7FFFFFFF)) == 0)
+ {
+ return pObject;
+ }
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ ObjectAVLCore *pCore = (ObjectAVLCore *)RTAvlU32Get(&g_pTree, handle);
+
+ Assert(!pCore || (pCore->pSelf && pCore->pSelf->Type() == enmObjType));
+ if ( pCore
+ && pCore->pSelf
+ && pCore->pSelf->Type() == enmObjType)
+ {
+ pObject = pCore->pSelf;
+
+ AssertRelease(pObject);
+
+ pObject->Reference();
+ }
+
+ hgcmObjLeave();
+ }
+ else
+ {
+ AssertReleaseMsgFailed(("Failed to acquire object pool semaphore, vrc = %Rrc", vrc));
+ }
+
+ LogFlow(("MAIN::hgcmObjReference: return pObject %p\n", pObject));
+
+ return pObject;
+}
+
+void hgcmObjDereference(HGCMObject *pObject)
+{
+ LogFlow(("MAIN::hgcmObjDereference: pObject %p\n", pObject));
+
+ AssertRelease(pObject);
+
+ pObject->Dereference();
+
+ LogFlow(("MAIN::hgcmObjDereference: return\n"));
+}
+
+uint32_t hgcmObjQueryHandleCount()
+{
+ return g_u32ClientHandleCount;
+}
+
+void hgcmObjSetHandleCount(uint32_t u32ClientHandleCount)
+{
+ Assert(g_u32ClientHandleCount <= u32ClientHandleCount);
+
+ int vrc = hgcmObjEnter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (g_u32ClientHandleCount <= u32ClientHandleCount)
+ g_u32ClientHandleCount = u32ClientHandleCount;
+ hgcmObjLeave();
+ }
+}
diff --git a/src/VBox/Main/src-client/HGCMThread.cpp b/src/VBox/Main/src-client/HGCMThread.cpp
new file mode 100644
index 00000000..e783f279
--- /dev/null
+++ b/src/VBox/Main/src-client/HGCMThread.cpp
@@ -0,0 +1,790 @@
+/* $Id: HGCMThread.cpp $ */
+/** @file
+ * HGCMThread - Host-Guest Communication Manager Threads
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_HGCM
+#include "LoggingNew.h"
+
+#include "HGCMThread.h"
+
+#include <VBox/err.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+
+#include <new> /* for std:nothrow */
+
+
+/* HGCM uses worker threads, which process messages from other threads.
+ * A message consists of the message header and message specific data.
+ * Message header is opaque for callers, but message data is defined
+ * and used by them.
+ *
+ * Messages are distinguished by message identifier and worker thread
+ * they are allocated for.
+ *
+ * Messages are allocated for a worker thread and belong to
+ * the thread. A worker thread holds the queue of messages.
+ *
+ * The calling thread creates a message, specifying which worker thread
+ * the message is created for, then, optionally, initializes message
+ * specific data and, also optionally, references the message.
+ *
+ * Message then is posted or sent to worker thread by inserting
+ * it to the worker thread message queue and referencing the message.
+ * Worker thread then again may fetch next message.
+ *
+ * Upon processing the message the worker thread dereferences it.
+ * Dereferencing also automatically deletes message from the thread
+ * queue and frees memory allocated for the message, if no more
+ * references left. If there are references, the message remains
+ * in the queue.
+ *
+ */
+
+/* Version of HGCM message header */
+#define HGCMMSG_VERSION (1)
+
+/* Thread is initializing. */
+#define HGCMMSG_TF_INITIALIZING (0x00000001)
+/* Thread must be terminated. */
+#define HGCMMSG_TF_TERMINATE (0x00000002)
+/* Thread has been terminated. */
+#define HGCMMSG_TF_TERMINATED (0x00000004)
+
+/** @todo consider use of RTReq */
+
+static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser);
+
+class HGCMThread : public HGCMReferencedObject
+{
+ private:
+ friend DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD ThreadSelf, void *pvUser);
+
+ /* Worker thread function. */
+ PFNHGCMTHREAD m_pfnThread;
+
+ /* A user supplied thread parameter. */
+ void *m_pvUser;
+
+ /* The thread runtime handle. */
+ RTTHREAD m_hThread;
+
+ /** Event the thread waits for, signalled when a message to process is posted to
+ * the thread, automatically reset. */
+ RTSEMEVENT m_eventThread;
+
+ /* A caller thread waits for completion of a SENT message on this event. */
+ RTSEMEVENTMULTI m_eventSend;
+ int32_t volatile m_i32MessagesProcessed;
+
+ /* Critical section for accessing the thread data, mostly for message queues. */
+ RTCRITSECT m_critsect;
+
+ /* thread state/operation flags */
+ uint32_t m_fu32ThreadFlags;
+
+ /* Message queue variables. Messages are inserted at tail of message
+ * queue. They are consumed by worker thread sequentially. If a message was
+ * consumed, it is removed from message queue.
+ */
+
+ /* Head of message queue. */
+ HGCMMsgCore *m_pMsgInputQueueHead;
+ /* Message which another message will be inserted after. */
+ HGCMMsgCore *m_pMsgInputQueueTail;
+
+ /* Head of messages being processed queue. */
+ HGCMMsgCore *m_pMsgInProcessHead;
+ /* Message which another message will be inserted after. */
+ HGCMMsgCore *m_pMsgInProcessTail;
+
+ /* Head of free message structures list. */
+ HGCMMsgCore *m_pFreeHead;
+ /* Tail of free message structures list. */
+ HGCMMsgCore *m_pFreeTail;
+
+ /** @name Statistics
+ * @{ */
+ STAMCOUNTER m_StatPostMsgNoPending;
+ STAMCOUNTER m_StatPostMsgOnePending;
+ STAMCOUNTER m_StatPostMsgTwoPending;
+ STAMCOUNTER m_StatPostMsgThreePending;
+ STAMCOUNTER m_StatPostMsgManyPending;
+ /** @} */
+
+ inline int Enter(void);
+ inline void Leave(void);
+
+ HGCMMsgCore *FetchFreeListHead(void);
+
+ protected:
+ virtual ~HGCMThread(void);
+
+ public:
+
+ HGCMThread ();
+
+ int WaitForTermination (void);
+
+ int Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ int MsgAlloc(HGCMMsgCore **pMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage);
+ int MsgGet(HGCMMsgCore **ppMsg);
+ int MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool bWait);
+ int MsgComplete(HGCMMsgCore *pMsg, int32_t result);
+};
+
+
+/*
+ * HGCMMsgCore implementation.
+ */
+
+#define HGCM_MSG_F_PROCESSED (0x00000001)
+#define HGCM_MSG_F_WAIT (0x00000002)
+#define HGCM_MSG_F_IN_PROCESS (0x00000004)
+
+void HGCMMsgCore::InitializeCore(uint32_t u32MsgId, HGCMThread *pThread)
+{
+ m_u32Version = HGCMMSG_VERSION;
+ m_u32Msg = u32MsgId;
+ m_pfnCallback = NULL;
+ m_pNext = NULL;
+ m_pPrev = NULL;
+ m_fu32Flags = 0;
+ m_vrcSend = VINF_SUCCESS;
+ m_pThread = pThread;
+ pThread->Reference();
+}
+
+/* virtual */ HGCMMsgCore::~HGCMMsgCore()
+{
+ if (m_pThread)
+ {
+ m_pThread->Dereference();
+ m_pThread = NULL;
+ }
+}
+
+/*
+ * HGCMThread implementation.
+ */
+
+static DECLCALLBACK(int) hgcmWorkerThreadFunc(RTTHREAD hThreadSelf, void *pvUser)
+{
+ HGCMThread *pThread = (HGCMThread *)pvUser;
+
+ LogFlow(("MAIN::hgcmWorkerThreadFunc: starting HGCM thread %p\n", pThread));
+
+ AssertRelease(pThread);
+
+ pThread->m_hThread = hThreadSelf;
+ pThread->m_fu32ThreadFlags &= ~HGCMMSG_TF_INITIALIZING;
+ int vrc = RTThreadUserSignal(hThreadSelf);
+ AssertRC(vrc);
+
+ pThread->m_pfnThread(pThread, pThread->m_pvUser);
+
+ pThread->m_fu32ThreadFlags |= HGCMMSG_TF_TERMINATED;
+
+ LogFlow(("MAIN::hgcmWorkerThreadFunc: completed HGCM thread %p\n", pThread));
+
+ return vrc;
+}
+
+HGCMThread::HGCMThread()
+ :
+ HGCMReferencedObject(HGCMOBJ_THREAD),
+ m_pfnThread(NULL),
+ m_pvUser(NULL),
+ m_hThread(NIL_RTTHREAD),
+ m_eventThread(NIL_RTSEMEVENT),
+ m_eventSend(NIL_RTSEMEVENTMULTI),
+ m_i32MessagesProcessed(0),
+ m_fu32ThreadFlags(0),
+ m_pMsgInputQueueHead(NULL),
+ m_pMsgInputQueueTail(NULL),
+ m_pMsgInProcessHead(NULL),
+ m_pMsgInProcessTail(NULL),
+ m_pFreeHead(NULL),
+ m_pFreeTail(NULL)
+{
+ RT_ZERO(m_critsect);
+}
+
+HGCMThread::~HGCMThread()
+{
+ /*
+ * Free resources allocated for the thread.
+ */
+
+ Assert(m_fu32ThreadFlags & HGCMMSG_TF_TERMINATED);
+
+ if (RTCritSectIsInitialized(&m_critsect))
+ RTCritSectDelete(&m_critsect);
+
+ if (m_eventSend != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(m_eventSend);
+ m_eventSend = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (m_eventThread != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(m_eventThread);
+ m_eventThread = NIL_RTSEMEVENT;
+ }
+}
+
+int HGCMThread::WaitForTermination(void)
+{
+ int vrc = VINF_SUCCESS;
+ LogFlowFunc(("\n"));
+
+ if (m_hThread != NIL_RTTHREAD)
+ {
+ vrc = RTThreadWait(m_hThread, 5000, NULL);
+ m_hThread = NIL_RTTHREAD;
+ }
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int HGCMThread::Initialize(const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ int vrc = RTSemEventCreate(&m_eventThread);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTSemEventMultiCreate(&m_eventSend);
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTCritSectInit(&m_critsect);
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_pfnThread = pfnThread;
+ m_pvUser = pvUser;
+
+ m_fu32ThreadFlags = HGCMMSG_TF_INITIALIZING;
+
+ RTTHREAD hThread;
+ vrc = RTThreadCreate(&hThread, hgcmWorkerThreadFunc, this, 0, /* default stack size; some services
+ may need quite a bit */
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE,
+ pszThreadName);
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Register statistics while the thread starts. */
+ if (pUVM)
+ {
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgNoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT, "Times a message was appended to an empty input queue.",
+ "/HGCM/%s/PostMsg0Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgOnePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg1Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgTwoPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg2Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgThreePending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsg3Pending", pszStatsSubDir);
+ pVMM->pfnSTAMR3RegisterFU(pUVM, &m_StatPostMsgManyPending, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
+ STAMUNIT_COUNT,
+ "Times a message was appended to input queue with only one pending message.",
+ "/HGCM/%s/PostMsgManyPending", pszStatsSubDir);
+ }
+
+
+ /* Wait until the thread is ready. */
+ vrc = RTThreadUserWait(hThread, 30000);
+ AssertRC(vrc);
+ Assert(!(m_fu32ThreadFlags & HGCMMSG_TF_INITIALIZING) || RT_FAILURE(vrc));
+ }
+ else
+ {
+ m_hThread = NIL_RTTHREAD;
+ Log(("hgcmThreadCreate: FAILURE: Can't start worker thread.\n"));
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't init a critical section for a hgcm worker thread.\n"));
+ RT_ZERO(m_critsect);
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a sent messages.\n"));
+ m_eventSend = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't create an event semaphore for a hgcm worker thread.\n"));
+ m_eventThread = NIL_RTSEMEVENT;
+ }
+
+ return vrc;
+}
+
+inline int HGCMThread::Enter(void)
+{
+ int vrc = RTCritSectEnter(&m_critsect);
+
+#ifdef LOG_ENABLED
+ if (RT_FAILURE(vrc))
+ Log(("HGCMThread::MsgPost: FAILURE: could not obtain worker thread mutex, vrc = %Rrc!!!\n", vrc));
+#endif
+
+ return vrc;
+}
+
+inline void HGCMThread::Leave(void)
+{
+ RTCritSectLeave(&m_critsect);
+}
+
+
+int HGCMThread::MsgAlloc(HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage)
+{
+ /** @todo Implement this free list / cache thingy. */
+ HGCMMsgCore *pmsg = NULL;
+
+ bool fFromFreeList = false;
+
+ if (!pmsg)
+ {
+ /* We have to allocate a new memory block. */
+ pmsg = pfnNewMessage(u32MsgId);
+ if (pmsg != NULL)
+ pmsg->Reference(); /* (it's created with zero references) */
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ /* Initialize just allocated message core */
+ pmsg->InitializeCore(u32MsgId, this);
+
+ /* and the message specific data. */
+ pmsg->Initialize();
+
+ LogFlow(("MAIN::hgcmMsgAlloc: allocated message %p\n", pmsg));
+
+ *ppMsg = pmsg;
+
+ if (fFromFreeList)
+ {
+ /* Message was referenced in the free list, now dereference it. */
+ pmsg->Dereference();
+ }
+
+ return VINF_SUCCESS;
+}
+
+int HGCMThread::MsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait)
+{
+ LogFlow(("HGCMThread::MsgPost: thread = %p, pMsg = %p, pfnCallback = %p\n", this, pMsg, pfnCallback));
+
+ int vrc = Enter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ pMsg->m_pfnCallback = pfnCallback;
+
+ if (fWait)
+ pMsg->m_fu32Flags |= HGCM_MSG_F_WAIT;
+
+ /* Insert the message to the queue tail. */
+ pMsg->m_pNext = NULL;
+ HGCMMsgCore * const pPrev = m_pMsgInputQueueTail;
+ pMsg->m_pPrev = pPrev;
+
+ if (pPrev)
+ {
+ pPrev->m_pNext = pMsg;
+ if (!pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgOnePending);
+ else if (!pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgTwoPending);
+ else if (!pPrev->m_pPrev->m_pPrev)
+ STAM_REL_COUNTER_INC(&m_StatPostMsgThreePending);
+ else
+ STAM_REL_COUNTER_INC(&m_StatPostMsgManyPending);
+ }
+ else
+ {
+ m_pMsgInputQueueHead = pMsg;
+ STAM_REL_COUNTER_INC(&m_StatPostMsgNoPending);
+ }
+
+ m_pMsgInputQueueTail = pMsg;
+
+ Leave();
+
+ LogFlow(("HGCMThread::MsgPost: going to inform the thread %p about message, fWait = %d\n", this, fWait));
+
+ /* Inform the worker thread that there is a message. */
+ RTSemEventSignal(m_eventThread);
+
+ LogFlow(("HGCMThread::MsgPost: event signalled\n"));
+
+ if (fWait)
+ {
+ /* Immediately check if the message has been processed. */
+ while ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0)
+ {
+ /* Poll infrequently to make sure no completed message has been missed. */
+ RTSemEventMultiWait(m_eventSend, 1000);
+
+ LogFlow(("HGCMThread::MsgPost: wait completed flags = %08X\n", pMsg->m_fu32Flags));
+
+ if ((pMsg->m_fu32Flags & HGCM_MSG_F_PROCESSED) == 0)
+ RTThreadYield();
+ }
+
+ /* 'Our' message has been processed, so should reset the semaphore.
+ * There is still possible that another message has been processed
+ * and the semaphore has been signalled again.
+ * Reset only if there are no other messages completed.
+ */
+ int32_t c = ASMAtomicDecS32(&m_i32MessagesProcessed);
+ Assert(c >= 0);
+ if (c == 0)
+ RTSemEventMultiReset(m_eventSend);
+
+ vrc = pMsg->m_vrcSend;
+ }
+ }
+
+ LogFlow(("HGCMThread::MsgPost: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+
+int HGCMThread::MsgGet(HGCMMsgCore **ppMsg)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("HGCMThread::MsgGet: thread = %p, ppMsg = %p\n", this, ppMsg));
+
+ for (;;)
+ {
+ if (m_fu32ThreadFlags & HGCMMSG_TF_TERMINATE)
+ {
+ vrc = VERR_INTERRUPTED;
+ break;
+ }
+
+ LogFlow(("MAIN::hgcmMsgGet: m_pMsgInputQueueHead = %p\n", m_pMsgInputQueueHead));
+
+ if (m_pMsgInputQueueHead)
+ {
+ /* Move the message to the m_pMsgInProcessHead list */
+ vrc = Enter();
+
+ if (RT_FAILURE(vrc))
+ {
+ break;
+ }
+
+ HGCMMsgCore *pMsg = m_pMsgInputQueueHead;
+
+ /* Remove the message from the head of Queue list. */
+ Assert(m_pMsgInputQueueHead->m_pPrev == NULL);
+
+ if (m_pMsgInputQueueHead->m_pNext)
+ {
+ m_pMsgInputQueueHead = m_pMsgInputQueueHead->m_pNext;
+ m_pMsgInputQueueHead->m_pPrev = NULL;
+ }
+ else
+ {
+ Assert(m_pMsgInputQueueHead == m_pMsgInputQueueTail);
+
+ m_pMsgInputQueueHead = NULL;
+ m_pMsgInputQueueTail = NULL;
+ }
+
+ /* Insert the message to the tail of the m_pMsgInProcessHead list. */
+ pMsg->m_pNext = NULL;
+ pMsg->m_pPrev = m_pMsgInProcessTail;
+
+ if (m_pMsgInProcessTail)
+ m_pMsgInProcessTail->m_pNext = pMsg;
+ else
+ m_pMsgInProcessHead = pMsg;
+
+ m_pMsgInProcessTail = pMsg;
+
+ pMsg->m_fu32Flags |= HGCM_MSG_F_IN_PROCESS;
+
+ Leave();
+
+ /* Return the message to the caller. */
+ *ppMsg = pMsg;
+
+ LogFlow(("MAIN::hgcmMsgGet: got message %p\n", *ppMsg));
+
+ break;
+ }
+
+ /* Wait for an event. */
+ RTSemEventWait(m_eventThread, RT_INDEFINITE_WAIT);
+ }
+
+ LogFlow(("HGCMThread::MsgGet: *ppMsg = %p, return vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+int HGCMThread::MsgComplete(HGCMMsgCore *pMsg, int32_t vrcResult)
+{
+ LogFlow(("HGCMThread::MsgComplete: thread = %p, pMsg = %p, vrcResult = %Rrc (%d)\n", this, pMsg, vrcResult, vrcResult));
+
+ AssertRelease(pMsg->m_pThread == this);
+ AssertReleaseMsg((pMsg->m_fu32Flags & HGCM_MSG_F_IN_PROCESS) != 0, ("%p %x\n", pMsg, pMsg->m_fu32Flags));
+
+ int vrcRet = VINF_SUCCESS;
+ if (pMsg->m_pfnCallback)
+ {
+ /** @todo call callback with error code in MsgPost in case of errors */
+
+ vrcRet = pMsg->m_pfnCallback(vrcResult, pMsg);
+
+ LogFlow(("HGCMThread::MsgComplete: callback executed. pMsg = %p, thread = %p, rcRet = %Rrc\n", pMsg, this, vrcRet));
+ }
+
+ /* Message processing has been completed. */
+
+ int vrc = Enter();
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Remove the message from the InProcess queue. */
+
+ if (pMsg->m_pNext)
+ pMsg->m_pNext->m_pPrev = pMsg->m_pPrev;
+ else
+ m_pMsgInProcessTail = pMsg->m_pPrev;
+
+ if (pMsg->m_pPrev)
+ pMsg->m_pPrev->m_pNext = pMsg->m_pNext;
+ else
+ m_pMsgInProcessHead = pMsg->m_pNext;
+
+ pMsg->m_pNext = NULL;
+ pMsg->m_pPrev = NULL;
+
+ bool fWaited = ((pMsg->m_fu32Flags & HGCM_MSG_F_WAIT) != 0);
+
+ if (fWaited)
+ {
+ ASMAtomicIncS32(&m_i32MessagesProcessed);
+
+ /* This should be done before setting the HGCM_MSG_F_PROCESSED flag. */
+ pMsg->m_vrcSend = vrcResult;
+ }
+
+ /* The message is now completed. */
+ pMsg->m_fu32Flags &= ~HGCM_MSG_F_IN_PROCESS;
+ pMsg->m_fu32Flags &= ~HGCM_MSG_F_WAIT;
+ pMsg->m_fu32Flags |= HGCM_MSG_F_PROCESSED;
+
+ pMsg->Dereference();
+
+ Leave();
+
+ if (fWaited)
+ {
+ /* Wake up all waiters. so they can decide if their message has been processed. */
+ RTSemEventMultiSignal(m_eventSend);
+ }
+ }
+
+ return vrcRet;
+}
+
+/*
+ * Thread API. Public interface.
+ */
+
+int hgcmThreadCreate(HGCMThread **ppThread, const char *pszThreadName, PFNHGCMTHREAD pfnThread, void *pvUser,
+ const char *pszStatsSubDir, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ LogFlow(("MAIN::hgcmThreadCreate\n"));
+ int vrc;
+
+ /* Allocate memory for a new thread object. */
+ HGCMThread *pThread = new (std::nothrow) HGCMThread();
+
+ if (pThread)
+ {
+ pThread->Reference(); /* (it's created with zero references) */
+
+ /* Initialize the object. */
+ vrc = pThread->Initialize(pszThreadName, pfnThread, pvUser, pszStatsSubDir, pUVM, pVMM);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppThread = pThread;
+ LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc));
+ return vrc;
+ }
+
+ Log(("hgcmThreadCreate: FAILURE: Initialize failed: vrc = %Rrc\n", vrc));
+
+ pThread->Dereference();
+ }
+ else
+ {
+ Log(("hgcmThreadCreate: FAILURE: Can't allocate memory for a hgcm worker thread.\n"));
+ vrc = VERR_NO_MEMORY;
+ }
+ *ppThread = NULL;
+
+ LogFlow(("MAIN::hgcmThreadCreate: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int hgcmThreadWait(HGCMThread *pThread)
+{
+ LogFlowFunc(("%p\n", pThread));
+
+ int vrc;
+ if (pThread)
+ {
+ vrc = pThread->WaitForTermination();
+
+ pThread->Dereference();
+ }
+ else
+ vrc = VERR_INVALID_HANDLE;
+
+ LogFlowFunc(("vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+int hgcmMsgAlloc(HGCMThread *pThread, HGCMMsgCore **ppMsg, uint32_t u32MsgId, PFNHGCMNEWMSGALLOC pfnNewMessage)
+{
+ LogFlow(("hgcmMsgAlloc: pThread = %p, ppMsg = %p, sizeof (HGCMMsgCore) = %d\n", pThread, ppMsg, sizeof(HGCMMsgCore)));
+
+ AssertReturn(pThread, VERR_INVALID_HANDLE);
+ AssertReturn(ppMsg, VERR_INVALID_PARAMETER);
+
+ int vrc = pThread->MsgAlloc(ppMsg, u32MsgId, pfnNewMessage);
+
+ LogFlow(("MAIN::hgcmMsgAlloc: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+DECLINLINE(int) hgcmMsgPostInternal(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback, bool fWait)
+{
+ LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait));
+ Assert(pMsg);
+
+ pMsg->Reference(); /* paranoia? */
+
+ int vrc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait);
+
+ pMsg->Dereference();
+
+ LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, vrc = %Rrc\n", pMsg, vrc));
+ return vrc;
+}
+
+int hgcmMsgPost(HGCMMsgCore *pMsg, PFNHGCMMSGCALLBACK pfnCallback)
+{
+ int vrc = hgcmMsgPostInternal(pMsg, pfnCallback, false);
+
+ if (RT_SUCCESS(vrc))
+ vrc = VINF_HGCM_ASYNC_EXECUTE;
+
+ return vrc;
+}
+
+int hgcmMsgSend(HGCMMsgCore *pMsg)
+{
+ return hgcmMsgPostInternal(pMsg, NULL, true);
+}
+
+int hgcmMsgGet(HGCMThread *pThread, HGCMMsgCore **ppMsg)
+{
+ LogFlow(("MAIN::hgcmMsgGet: pThread = %p, ppMsg = %p\n", pThread, ppMsg));
+
+ AssertReturn(pThread, VERR_INVALID_HANDLE);
+ AssertReturn(ppMsg, VERR_INVALID_PARAMETER);
+
+ pThread->Reference(); /* paranoia */
+
+ int vrc = pThread->MsgGet(ppMsg);
+
+ pThread->Dereference();
+
+ LogFlow(("MAIN::hgcmMsgGet: *ppMsg = %p, vrc = %Rrc\n", *ppMsg, vrc));
+ return vrc;
+}
+
+int hgcmMsgComplete(HGCMMsgCore *pMsg, int32_t vrcMsg)
+{
+ LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, vrcMsg = %Rrc (%d)\n", pMsg, vrcMsg, vrcMsg));
+
+ int vrc;
+ if (pMsg)
+ vrc = pMsg->Thread()->MsgComplete(pMsg, vrcMsg);
+ else
+ vrc = VINF_SUCCESS;
+
+ LogFlow(("MAIN::hgcmMsgComplete: pMsg = %p, vrcMsg =%Rrc (%d), returns vrc = %Rrc\n", pMsg, vrcMsg, vrcMsg, vrc));
+ return vrc;
+}
+
+int hgcmThreadInit(void)
+{
+ LogFlow(("MAIN::hgcmThreadInit\n"));
+
+ /** @todo error processing. */
+
+ int vrc = hgcmObjInit();
+
+ LogFlow(("MAIN::hgcmThreadInit: vrc = %Rrc\n", vrc));
+ return vrc;
+}
+
+void hgcmThreadUninit(void)
+{
+ hgcmObjUninit();
+}
+
diff --git a/src/VBox/Main/src-client/KeyboardImpl.cpp b/src/VBox/Main/src-client/KeyboardImpl.cpp
new file mode 100644
index 00000000..a170afae
--- /dev/null
+++ b/src/VBox/Main/src-client/KeyboardImpl.cpp
@@ -0,0 +1,547 @@
+/* $Id: KeyboardImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_KEYBOARD
+#include "LoggingNew.h"
+
+#include "KeyboardImpl.h"
+#include "ConsoleImpl.h"
+
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+
+#include <VBox/com/array.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/err.h>
+
+#include <iprt/cpp/utils.h>
+
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+// globals
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Keyboard device capabilities bitfield.
+ */
+enum
+{
+ /** The keyboard device does not wish to receive keystrokes. */
+ KEYBOARD_DEVCAP_DISABLED = 0,
+ /** The keyboard device does wishes to receive keystrokes. */
+ KEYBOARD_DEVCAP_ENABLED = 1
+};
+
+/**
+ * Keyboard driver instance data.
+ */
+typedef struct DRVMAINKEYBOARD
+{
+ /** Pointer to the keyboard object. */
+ Keyboard *pKeyboard;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the keyboard port interface of the driver/device above us. */
+ PPDMIKEYBOARDPORT pUpPort;
+ /** Our keyboard connector interface. */
+ PDMIKEYBOARDCONNECTOR IConnector;
+ /** The capabilities of this device. */
+ uint32_t u32DevCaps;
+} DRVMAINKEYBOARD, *PDRVMAINKEYBOARD;
+
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+Keyboard::Keyboard()
+ : mParent(NULL)
+{
+}
+
+Keyboard::~Keyboard()
+{
+}
+
+HRESULT Keyboard::FinalConstruct()
+{
+ RT_ZERO(mpDrv);
+ menmLeds = PDMKEYBLEDS_NONE;
+ return BaseFinalConstruct();
+}
+
+void Keyboard::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the keyboard object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT Keyboard::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ unconst(mEventSource).createObject();
+ HRESULT hrc = mEventSource->init();
+ AssertComRCReturnRC(hrc);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Keyboard::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ for (unsigned i = 0; i < KEYBOARD_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i])
+ mpDrv[i]->pKeyboard = NULL;
+ mpDrv[i] = NULL;
+ }
+
+ menmLeds = PDMKEYBLEDS_NONE;
+
+ unconst(mParent) = NULL;
+ unconst(mEventSource).setNull();
+}
+
+/**
+ * Sends a scancode to the keyboard.
+ *
+ * @returns COM status code
+ * @param aScancode The scancode to send
+ */
+HRESULT Keyboard::putScancode(LONG aScancode)
+{
+ std::vector<LONG> scancodes;
+ scancodes.resize(1);
+ scancodes[0] = aScancode;
+ return putScancodes(scancodes, NULL);
+}
+
+/**
+ * Sends a list of scancodes to the keyboard.
+ *
+ * @returns COM status code
+ * @param aScancodes Pointer to the first scancode
+ * @param aCodesStored Address of variable to store the number
+ * of scancodes that were sent to the keyboard.
+ This value can be NULL.
+ */
+HRESULT Keyboard::putScancodes(const std::vector<LONG> &aScancodes,
+ ULONG *aCodesStored)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv[0]);
+
+ /* Send input to the last enabled device. Relies on the fact that
+ * the USB keyboard is always initialized after the PS/2 keyboard.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ /* No enabled keyboard - throw the input away. */
+ if (!pUpPort)
+ {
+ if (aCodesStored)
+ *aCodesStored = (uint32_t)aScancodes.size();
+ return S_OK;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ uint32_t sent;
+ for (sent = 0; (sent < aScancodes.size()) && RT_SUCCESS(vrc); ++sent)
+ vrc = pUpPort->pfnPutEventScan(pUpPort, (uint8_t)aScancodes[sent]);
+
+ if (aCodesStored)
+ *aCodesStored = sent;
+
+ com::SafeArray<LONG> keys(aScancodes.size());
+ for (size_t i = 0; i < aScancodes.size(); ++i)
+ keys[i] = aScancodes[i];
+
+ ::FireGuestKeyboardEvent(mEventSource, ComSafeArrayAsInParam(keys));
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send all scan codes to the virtual keyboard (%Rrc)"),
+ vrc);
+
+ return S_OK;
+}
+
+/**
+ * Sends a HID usage code and page to the keyboard.
+ *
+ * @returns COM status code
+ * @param aUsageCode The HID usage code to send
+ * @param aUsagePage The HID usage page corresponding to the code
+ * @param fKeyRelease The key release flag
+ */
+HRESULT Keyboard::putUsageCode(LONG aUsageCode, LONG aUsagePage, BOOL fKeyRelease)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_CONSOLE_DRV(mpDrv[0]);
+
+ /* Send input to the last enabled device. Relies on the fact that
+ * the USB keyboard is always initialized after the PS/2 keyboard.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ /* No enabled keyboard - throw the input away. */
+ if (!pUpPort)
+ return S_OK;
+
+ uint32_t idUsage = (uint16_t)aUsageCode | ((uint32_t)(uint8_t)aUsagePage << 16) | (fKeyRelease ? UINT32_C(0x80000000) : 0);
+ int vrc = pUpPort->pfnPutEventHid(pUpPort, idUsage);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send usage code to the virtual keyboard (%Rrc)"),
+ vrc);
+
+ return S_OK;
+}
+
+/**
+ * Sends Control-Alt-Delete to the keyboard. This could be done otherwise
+ * but it's so common that we'll be nice and supply a convenience API.
+ *
+ * @returns COM status code
+ *
+ */
+HRESULT Keyboard::putCAD()
+{
+ std::vector<LONG> cadSequence;
+ cadSequence.resize(8);
+
+ cadSequence[0] = 0x1d; // Ctrl down
+ cadSequence[1] = 0x38; // Alt down
+ cadSequence[2] = 0xe0; // Del down 1
+ cadSequence[3] = 0x53; // Del down 2
+ cadSequence[4] = 0xe0; // Del up 1
+ cadSequence[5] = 0xd3; // Del up 2
+ cadSequence[6] = 0xb8; // Alt up
+ cadSequence[7] = 0x9d; // Ctrl up
+
+ return putScancodes(cadSequence, NULL);
+}
+
+/**
+ * Releases all currently held keys in the virtual keyboard.
+ *
+ * @returns COM status code
+ *
+ */
+HRESULT Keyboard::releaseKeys()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Release all keys on the active keyboard in order to start with a clean slate.
+ * Note that this should mirror the logic in Keyboard::putScancodes() when choosing
+ * which keyboard to send the release event to.
+ */
+ PPDMIKEYBOARDPORT pUpPort = NULL;
+ for (int i = KEYBOARD_MAX_DEVICES - 1; i >= 0 ; --i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & KEYBOARD_DEVCAP_ENABLED))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+
+ if (pUpPort)
+ {
+ int vrc = pUpPort->pfnReleaseKeys(pUpPort);
+ if (RT_FAILURE(vrc))
+ AssertMsgFailed(("Failed to release keys on all keyboards! vrc=%Rrc\n", vrc));
+ }
+
+ return S_OK;
+}
+
+HRESULT Keyboard::getKeyboardLEDs(std::vector<KeyboardLED_T> &aKeyboardLEDs)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aKeyboardLEDs.resize(0);
+
+ if (menmLeds & PDMKEYBLEDS_NUMLOCK) aKeyboardLEDs.push_back(KeyboardLED_NumLock);
+ if (menmLeds & PDMKEYBLEDS_CAPSLOCK) aKeyboardLEDs.push_back(KeyboardLED_CapsLock);
+ if (menmLeds & PDMKEYBLEDS_SCROLLLOCK) aKeyboardLEDs.push_back(KeyboardLED_ScrollLock);
+
+ return S_OK;
+}
+
+HRESULT Keyboard::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // No need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ return S_OK;
+}
+
+//
+// private methods
+//
+void Keyboard::onKeyboardLedsChange(PDMKEYBLEDS enmLeds)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Save the current status. */
+ menmLeds = enmLeds;
+
+ alock.release();
+
+ i_getParent()->i_onKeyboardLedsChange(RT_BOOL(enmLeds & PDMKEYBLEDS_NUMLOCK),
+ RT_BOOL(enmLeds & PDMKEYBLEDS_CAPSLOCK),
+ RT_BOOL(enmLeds & PDMKEYBLEDS_SCROLLLOCK));
+}
+
+DECLCALLBACK(void) Keyboard::i_keyboardLedStatusChange(PPDMIKEYBOARDCONNECTOR pInterface, PDMKEYBLEDS enmLeds)
+{
+ PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector);
+ pDrv->pKeyboard->onKeyboardLedsChange(enmLeds);
+}
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDCONNECTOR,pfnSetActive}
+ */
+DECLCALLBACK(void) Keyboard::i_keyboardSetActive(PPDMIKEYBOARDCONNECTOR pInterface, bool fActive)
+{
+ PDRVMAINKEYBOARD pDrv = RT_FROM_MEMBER(pInterface, DRVMAINKEYBOARD, IConnector);
+
+ // Before activating a different keyboard, release all keys on the currently active one.
+ if (fActive)
+ pDrv->pKeyboard->releaseKeys();
+
+ if (fActive)
+ pDrv->u32DevCaps |= KEYBOARD_DEVCAP_ENABLED;
+ else
+ pDrv->u32DevCaps &= ~KEYBOARD_DEVCAP_ENABLED;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Keyboard::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINKEYBOARD pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDCONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * Destruct a keyboard driver instance.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Keyboard::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+ LogFlow(("Keyboard::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pKeyboard)
+ {
+ AutoWriteLock kbdLock(pThis->pKeyboard COMMA_LOCKVAL_SRC_POS);
+ for (unsigned cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev)
+ if (pThis->pKeyboard->mpDrv[cDev] == pThis)
+ {
+ pThis->pKeyboard->mpDrv[cDev] = NULL;
+ break;
+ }
+ }
+}
+
+/**
+ * Construct a keyboard driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Keyboard::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINKEYBOARD pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINKEYBOARD);
+ LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Keyboard::i_drvQueryInterface;
+
+ pThis->IConnector.pfnLedStatusChange = i_keyboardLedStatusChange;
+ pThis->IConnector.pfnSetActive = Keyboard::i_keyboardSetActive;
+
+ /*
+ * Get the IKeyboardPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIKEYBOARDPORT);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No keyboard port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Get the Keyboard object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IKeyboard));
+ IKeyboard *pIKeyboard = (IKeyboard *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIKeyboard)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Keyboard object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pKeyboard = static_cast<Keyboard *>(pIKeyboard);
+
+ unsigned cDev;
+ for (cDev = 0; cDev < KEYBOARD_MAX_DEVICES; ++cDev)
+ if (!pThis->pKeyboard->mpDrv[cDev])
+ {
+ pThis->pKeyboard->mpDrv[cDev] = pThis;
+ break;
+ }
+ if (cDev == KEYBOARD_MAX_DEVICES)
+ return VERR_NO_MORE_HANDLES;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Keyboard driver registration record.
+ */
+const PDMDRVREG Keyboard::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainKeyboard",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main keyboard driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_KEYBOARD,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINKEYBOARD),
+ /* pfnConstruct */
+ Keyboard::i_drvConstruct,
+ /* pfnDestruct */
+ Keyboard::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/MachineDebuggerImpl.cpp b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp
new file mode 100644
index 00000000..e9cc901d
--- /dev/null
+++ b/src/VBox/Main/src-client/MachineDebuggerImpl.cpp
@@ -0,0 +1,1560 @@
+/* $Id: MachineDebuggerImpl.cpp $ */
+/** @file
+ * VBox IMachineDebugger COM class implementation (VBoxC).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_MACHINEDEBUGGER
+#include "LoggingNew.h"
+
+#include "MachineDebuggerImpl.h"
+
+#include "Global.h"
+#include "ConsoleImpl.h"
+#include "ProgressImpl.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/vmm/em.h>
+#include <VBox/vmm/uvm.h>
+#include <VBox/vmm/tm.h>
+#include <VBox/vmm/hm.h>
+#include <VBox/err.h>
+#include <iprt/cpp/utils.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+MachineDebugger::MachineDebugger()
+ : mParent(NULL)
+{
+}
+
+MachineDebugger::~MachineDebugger()
+{
+}
+
+HRESULT MachineDebugger::FinalConstruct()
+{
+ unconst(mParent) = NULL;
+ return BaseFinalConstruct();
+}
+
+void MachineDebugger::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the machine debugger object.
+ *
+ * @returns COM result indicator
+ * @param aParent handle of our parent object
+ */
+HRESULT MachineDebugger::init(Console *aParent)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = aParent;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(maiQueuedEmExecPolicyParams); i++)
+ maiQueuedEmExecPolicyParams[i] = UINT8_MAX;
+ mSingleStepQueued = -1;
+ mLogEnabledQueued = -1;
+ mVirtualTimeRateQueued = UINT32_MAX;
+ mFlushMode = false;
+
+ m_hSampleReport = NULL;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void MachineDebugger::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+ mFlushMode = false;
+}
+
+/**
+ * @callback_method_impl{FNDBGFPROGRESS}
+ */
+/*static*/ DECLCALLBACK(int) MachineDebugger::i_dbgfProgressCallback(void *pvUser, unsigned uPercentage)
+{
+ MachineDebugger *pThis = (MachineDebugger *)pvUser;
+
+ int vrc = pThis->m_Progress->i_iprtProgressCallback(uPercentage, static_cast<Progress *>(pThis->m_Progress));
+ if ( RT_SUCCESS(vrc)
+ && uPercentage == 100)
+ {
+ PCVMMR3VTABLE const pVMM = pThis->mParent->i_getVMMVTable();
+ AssertPtrReturn(pVMM, VERR_INTERNAL_ERROR_3);
+
+ vrc = pVMM->pfnDBGFR3SampleReportDumpToFile(pThis->m_hSampleReport, pThis->m_strFilename.c_str());
+ pVMM->pfnDBGFR3SampleReportRelease(pThis->m_hSampleReport);
+ pThis->m_hSampleReport = NULL;
+ if (RT_SUCCESS(vrc))
+ pThis->m_Progress->i_notifyComplete(S_OK);
+ else
+ {
+ HRESULT hrc = pThis->setError(VBOX_E_IPRT_ERROR,
+ tr("Writing the sample report to '%s' failed with %Rrc"),
+ pThis->m_strFilename.c_str(), vrc);
+ pThis->m_Progress->i_notifyComplete(hrc);
+ }
+ pThis->m_Progress.setNull();
+ }
+ else if (vrc == VERR_CANCELLED)
+ vrc = VERR_DBGF_CANCELLED;
+
+ return vrc;
+}
+
+// IMachineDebugger properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the current singlestepping flag.
+ *
+ * @returns COM status code
+ * @param aSingleStep Where to store the result.
+ */
+HRESULT MachineDebugger::getSingleStep(BOOL *aSingleStep)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ 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.hrc();
+ 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.hrc();
+ 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.hrc();
+ if (SUCCEEDED(hrc))
+ ptrVM.vtable()->pfnEMR3QueryExecutionPolicy(ptrVM.rawUVM(), enmPolicy, &fEnforced);
+ *pfEnforced = fEnforced;
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Internal worker for setting an EM executable policy.
+ *
+ * @returns COM status code.
+ * @param enmPolicy Which policy to change.
+ * @param fEnforce Whether to enforce the policy or not.
+ */
+HRESULT MachineDebugger::i_setEmExecPolicyProperty(EMEXECPOLICY enmPolicy, BOOL fEnforce)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ 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.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnEMR3SetExecutionPolicy(ptrVM.rawUVM(), enmPolicy, fEnforce != FALSE);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("EMR3SetExecutionPolicy failed with %Rrc"), vrc);
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Returns the current execute-all-in-IEM setting.
+ *
+ * @returns COM status code
+ * @param aExecuteAllInIEM Address of result variable.
+ */
+HRESULT MachineDebugger::getExecuteAllInIEM(BOOL *aExecuteAllInIEM)
+{
+ return i_getEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM);
+}
+
+/**
+ * Changes the execute-all-in-IEM setting.
+ *
+ * @returns COM status code
+ * @param aExecuteAllInIEM New setting.
+ */
+HRESULT MachineDebugger::setExecuteAllInIEM(BOOL aExecuteAllInIEM)
+{
+ LogFlowThisFunc(("enable=%d\n", aExecuteAllInIEM));
+ return i_setEmExecPolicyProperty(EMEXECPOLICY_IEM_ALL, aExecuteAllInIEM);
+}
+
+/**
+ * Returns the log enabled / disabled status.
+ *
+ * @returns COM status code
+ * @param aLogEnabled address of result variable
+ */
+HRESULT MachineDebugger::getLogEnabled(BOOL *aLogEnabled)
+{
+#ifdef LOG_ENABLED
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ const PRTLOGGER pLogInstance = RTLogDefaultInstance();
+ *aLogEnabled = pLogInstance && !(RTLogGetFlags(pLogInstance) & RTLOGFLAGS_DISABLED);
+#else
+ *aLogEnabled = false;
+#endif
+
+ return S_OK;
+}
+
+/**
+ * Enables or disables logging.
+ *
+ * @returns COM status code
+ * @param aLogEnabled The new code log state.
+ */
+HRESULT MachineDebugger::setLogEnabled(BOOL aLogEnabled)
+{
+ LogFlowThisFunc(("aLogEnabled=%d\n", aLogEnabled));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (i_queueSettings())
+ {
+ // queue the request
+ mLogEnabledQueued = aLogEnabled;
+ return S_OK;
+ }
+
+ Console::SafeVMPtr ptrVM(mParent);
+ if (FAILED(ptrVM.hrc())) return ptrVM.hrc();
+
+#ifdef LOG_ENABLED
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aLogEnabled ? "enabled" : "disabled");
+ if (RT_FAILURE(vrc))
+ {
+ /** @todo handle error code. */
+ }
+#endif
+
+ return S_OK;
+}
+
+HRESULT MachineDebugger::i_logStringProps(PRTLOGGER pLogger, PFNLOGGETSTR pfnLogGetStr,
+ const char *pszLogGetStr, Utf8Str *pstrSettings)
+{
+ /* Make sure the VM is powered up. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* Make sure we've got a logger. */
+ if (!pLogger)
+ {
+ *pstrSettings = "";
+ return S_OK;
+ }
+
+ /* Do the job. */
+ size_t cbBuf = _1K;
+ for (;;)
+ {
+ char *pszBuf = (char *)RTMemTmpAlloc(cbBuf);
+ AssertReturn(pszBuf, E_OUTOFMEMORY);
+ int vrc = pstrSettings->reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = pfnLogGetStr(pLogger, pstrSettings->mutableRaw(), cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ pstrSettings->jolt();
+ return S_OK;
+ }
+ *pstrSettings = "";
+ AssertReturn(vrc == VERR_BUFFER_OVERFLOW,
+ setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("%s returned %Rrc"), pszLogGetStr, vrc));
+ }
+ else
+ return E_OUTOFMEMORY;
+
+ /* try again with a bigger buffer. */
+ cbBuf *= 2;
+ AssertReturn(cbBuf <= _256K, setError(E_FAIL, tr("%s returns too much data"), pszLogGetStr));
+ }
+}
+
+HRESULT MachineDebugger::getLogDbgFlags(com::Utf8Str &aLogDbgFlags)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogDbgFlags);
+}
+
+HRESULT MachineDebugger::getLogDbgGroups(com::Utf8Str &aLogDbgGroups)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogDbgGroups);
+}
+
+HRESULT MachineDebugger::getLogDbgDestinations(com::Utf8Str &aLogDbgDestinations)
+{
+ return i_logStringProps(RTLogGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogDbgDestinations);
+}
+
+HRESULT MachineDebugger::getLogRelFlags(com::Utf8Str &aLogRelFlags)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryFlags, "RTLogQueryFlags", &aLogRelFlags);
+}
+
+HRESULT MachineDebugger::getLogRelGroups(com::Utf8Str &aLogRelGroups)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryGroupSettings, "RTLogQueryGroupSettings", &aLogRelGroups);
+}
+
+HRESULT MachineDebugger::getLogRelDestinations(com::Utf8Str &aLogRelDestinations)
+{
+ return i_logStringProps(RTLogRelGetDefaultInstance(), RTLogQueryDestinations, "RTLogQueryDestinations", &aLogRelDestinations);
+}
+
+/**
+ * Return the main execution engine of the VM.
+ *
+ * @returns COM status code
+ * @param apenmEngine Address of the result variable.
+ */
+HRESULT MachineDebugger::getExecutionEngine(VMExecutionEngine_T *apenmEngine)
+{
+ *apenmEngine = VMExecutionEngine_NotSet;
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint8_t bEngine = UINT8_MAX;
+ int vrc = ptrVM.vtable()->pfnEMR3QueryMainExecutionEngine(ptrVM.rawUVM(), &bEngine);
+ if (RT_SUCCESS(vrc))
+ switch (bEngine)
+ {
+ case VM_EXEC_ENGINE_NOT_SET: *apenmEngine = VMExecutionEngine_NotSet; break;
+ case VM_EXEC_ENGINE_IEM: *apenmEngine = VMExecutionEngine_Emulated; break;
+ case VM_EXEC_ENGINE_HW_VIRT: *apenmEngine = VMExecutionEngine_HwVirt; break;
+ case VM_EXEC_ENGINE_NATIVE_API: *apenmEngine = VMExecutionEngine_NativeApi; break;
+ default: AssertMsgFailed(("bEngine=%d\n", bEngine));
+ }
+ }
+
+ return S_OK;
+}
+
+/**
+ * Returns the current nested paging flag.
+ *
+ * @returns COM status code
+ * @param aHWVirtExNestedPagingEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExNestedPagingEnabled(BOOL *aHWVirtExNestedPagingEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExNestedPagingEnabled = ptrVM.vtable()->pfnHMR3IsNestedPagingActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExNestedPagingEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current VPID flag.
+ *
+ * @returns COM status code
+ * @param aHWVirtExVPIDEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExVPIDEnabled(BOOL *aHWVirtExVPIDEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExVPIDEnabled = ptrVM.vtable()->pfnHMR3IsVpidActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExVPIDEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current unrestricted execution setting.
+ *
+ * @returns COM status code
+ * @param aHWVirtExUXEnabled address of result variable
+ */
+HRESULT MachineDebugger::getHWVirtExUXEnabled(BOOL *aHWVirtExUXEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ *aHWVirtExUXEnabled = ptrVM.vtable()->pfnHMR3IsUXActive(ptrVM.rawUVM());
+ else
+ *aHWVirtExUXEnabled = false;
+
+ return S_OK;
+}
+
+HRESULT MachineDebugger::getOSName(com::Utf8Str &aOSName)
+{
+ LogFlowThisFunc(("\n"));
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ char szName[64];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), szName, sizeof(szName), NULL, 0);
+ if (RT_SUCCESS(vrc))
+ hrc = aOSName.assignEx(szName);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::getOSVersion(com::Utf8Str &aOSVersion)
+{
+ LogFlowThisFunc(("\n"));
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ char szVersion[256];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSQueryNameAndVersion(ptrVM.rawUVM(), NULL, 0, szVersion, sizeof(szVersion));
+ if (RT_SUCCESS(vrc))
+ hrc = aOSVersion.assignEx(szVersion);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSQueryNameAndVersion failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+/**
+ * Returns the current PAE flag.
+ *
+ * @returns COM status code
+ * @param aPAEEnabled address of result variable.
+ */
+HRESULT MachineDebugger::getPAEEnabled(BOOL *aPAEEnabled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint32_t cr4;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegCpuQueryU32(ptrVM.rawUVM(), 0 /*idCpu*/, DBGFREG_CR4, &cr4); AssertRC(vrc);
+ *aPAEEnabled = RT_BOOL(cr4 & X86_CR4_PAE);
+ }
+ else
+ *aPAEEnabled = false;
+
+ return S_OK;
+}
+
+/**
+ * Returns the current virtual time rate.
+ *
+ * @returns COM status code.
+ * @param aVirtualTimeRate Where to store the rate.
+ */
+HRESULT MachineDebugger::getVirtualTimeRate(ULONG *aVirtualTimeRate)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ *aVirtualTimeRate = ptrVM.vtable()->pfnTMR3GetWarpDrive(ptrVM.rawUVM());
+
+ return hrc;
+}
+
+/**
+ * Set the virtual time rate.
+ *
+ * @returns COM status code.
+ * @param aVirtualTimeRate The new rate.
+ */
+HRESULT MachineDebugger::setVirtualTimeRate(ULONG aVirtualTimeRate)
+{
+ HRESULT hrc = S_OK;
+
+ if (aVirtualTimeRate < 2 || aVirtualTimeRate > 20000)
+ return setError(E_INVALIDARG, tr("%u is out of range [2..20000]"), aVirtualTimeRate);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (i_queueSettings())
+ mVirtualTimeRateQueued = aVirtualTimeRate;
+ else
+ {
+ Console::SafeVMPtr ptrVM(mParent);
+ hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnTMR3SetWarpDrive(ptrVM.rawUVM(), aVirtualTimeRate);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("TMR3SetWarpDrive(, %u) failed with vrc=%Rrc"), aVirtualTimeRate, vrc);
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Get the VM uptime in milliseconds.
+ *
+ * @returns COM status code
+ * @param aUptime Where to store the uptime.
+ */
+HRESULT MachineDebugger::getUptime(LONG64 *aUptime)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ *aUptime = (int64_t)ptrVM.vtable()->pfnTMR3TimeVirtGetMilli(ptrVM.rawUVM());
+
+ return hrc;
+}
+
+// IMachineDebugger methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT MachineDebugger::dumpGuestCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression)
+{
+ if (aCompression.length())
+ return setError(E_INVALIDARG, tr("The compression parameter must be empty"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3CoreWrite(ptrVM.rawUVM(), aFilename.c_str(), false /*fReplaceFile*/);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3CoreWrite failed with %Rrc"), vrc);
+ }
+
+ return hrc;
+}
+
+HRESULT MachineDebugger::dumpHostProcessCore(const com::Utf8Str &aFilename, const com::Utf8Str &aCompression)
+{
+ RT_NOREF(aFilename, aCompression);
+ ReturnComNotImplemented();
+}
+
+/**
+ * Debug info string buffer formatter.
+ */
+typedef struct MACHINEDEBUGGERINOFHLP
+{
+ /** The core info helper structure. */
+ DBGFINFOHLP Core;
+ /** Pointer to the buffer. */
+ char *pszBuf;
+ /** The size of the buffer. */
+ size_t cbBuf;
+ /** The offset into the buffer */
+ size_t offBuf;
+ /** Indicates an out-of-memory condition. */
+ bool fOutOfMemory;
+} MACHINEDEBUGGERINOFHLP;
+/** Pointer to a Debug info string buffer formatter. */
+typedef MACHINEDEBUGGERINOFHLP *PMACHINEDEBUGGERINOFHLP;
+
+
+/**
+ * @callback_method_impl{FNRTSTROUTPUT}
+ */
+static DECLCALLBACK(size_t) MachineDebuggerInfoOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PMACHINEDEBUGGERINOFHLP pHlp = (PMACHINEDEBUGGERINOFHLP)pvArg;
+
+ /*
+ * Grow the buffer if required.
+ */
+ size_t const cbRequired = cbChars + pHlp->offBuf + 1;
+ if (cbRequired > pHlp->cbBuf)
+ {
+ if (RT_UNLIKELY(pHlp->fOutOfMemory))
+ return 0;
+
+ size_t cbBufNew = pHlp->cbBuf * 2;
+ if (cbRequired > cbBufNew)
+ cbBufNew = RT_ALIGN_Z(cbRequired, 256);
+ void *pvBufNew = RTMemRealloc(pHlp->pszBuf, cbBufNew);
+ if (RT_UNLIKELY(!pvBufNew))
+ {
+ pHlp->fOutOfMemory = true;
+ RTMemFree(pHlp->pszBuf);
+ pHlp->pszBuf = NULL;
+ pHlp->cbBuf = 0;
+ pHlp->offBuf = 0;
+ return 0;
+ }
+
+ pHlp->pszBuf = (char *)pvBufNew;
+ pHlp->cbBuf = cbBufNew;
+ }
+
+ /*
+ * Copy the bytes into the buffer and terminate it.
+ */
+ if (cbChars)
+ {
+ memcpy(&pHlp->pszBuf[pHlp->offBuf], pachChars, cbChars);
+ pHlp->offBuf += cbChars;
+ }
+ pHlp->pszBuf[pHlp->offBuf] = '\0';
+ Assert(pHlp->offBuf < pHlp->cbBuf);
+ return cbChars;
+}
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintfV}
+ */
+static DECLCALLBACK(void) MachineDebuggerInfoPrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list args)
+{
+ RTStrFormatV(MachineDebuggerInfoOutput, (void *)pHlp, NULL, NULL, pszFormat, args);
+}
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintf}
+ */
+static DECLCALLBACK(void) MachineDebuggerInfoPrintf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ MachineDebuggerInfoPrintfV(pHlp, pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Initializes the debug info string buffer formatter
+ *
+ * @param pHlp The help structure to init.
+ * @param pVMM The VMM vtable.
+ */
+static void MachineDebuggerInfoInit(PMACHINEDEBUGGERINOFHLP pHlp, PCVMMR3VTABLE pVMM)
+{
+ pHlp->Core.pfnPrintf = MachineDebuggerInfoPrintf;
+ pHlp->Core.pfnPrintfV = MachineDebuggerInfoPrintfV;
+ pHlp->Core.pfnGetOptError = pVMM->pfnDBGFR3InfoGenericGetOptError;
+ pHlp->pszBuf = NULL;
+ pHlp->cbBuf = 0;
+ pHlp->offBuf = 0;
+ pHlp->fOutOfMemory = false;
+}
+
+/**
+ * Deletes the debug info string buffer formatter.
+ * @param pHlp The helper structure to delete.
+ */
+static void MachineDebuggerInfoDelete(PMACHINEDEBUGGERINOFHLP pHlp)
+{
+ RTMemFree(pHlp->pszBuf);
+ pHlp->pszBuf = NULL;
+}
+
+HRESULT MachineDebugger::info(const com::Utf8Str &aName, const com::Utf8Str &aArgs, com::Utf8Str &aInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Do the autocaller and lock bits.
+ */
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Create a helper and call DBGFR3Info.
+ */
+ MACHINEDEBUGGERINOFHLP Hlp;
+ MachineDebuggerInfoInit(&Hlp, ptrVM.vtable());
+ int vrc = ptrVM.vtable()->pfnDBGFR3Info(ptrVM.rawUVM(), aName.c_str(), aArgs.c_str(), &Hlp.Core);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!Hlp.fOutOfMemory)
+ hrc = aInfo.assignEx(Hlp.pszBuf);
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3Info failed with %Rrc"), vrc);
+ MachineDebuggerInfoDelete(&Hlp);
+ }
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::injectNMI()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3InjectNMI(ptrVM.rawUVM(), 0);
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3InjectNMI failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogFlags(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyFlags(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyFlags failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogGroups(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyGroups(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyGroups failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::modifyLogDestinations(const com::Utf8Str &aSettings)
+{
+ LogFlowThisFunc(("aSettings=%s\n", aSettings.c_str()));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3LogModifyDestinations(ptrVM.rawUVM(), aSettings.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3LogModifyDestinations failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::readPhysicalMemory(LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::writePhysicalMemory(LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::readVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aCpuId, aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::writeVirtualMemory(ULONG aCpuId, LONG64 aAddress, ULONG aSize, const std::vector<BYTE> &aBytes)
+{
+ RT_NOREF(aCpuId, aAddress, aSize, aBytes);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::loadPlugIn(const com::Utf8Str &aName, com::Utf8Str &aPlugInName)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ if (aName.equals("all"))
+ {
+ ptrVM.vtable()->pfnDBGFR3PlugInLoadAll(ptrVM.rawUVM());
+ hrc = aPlugInName.assignEx("all");
+ }
+ else
+ {
+ RTERRINFOSTATIC ErrInfo;
+ char szName[80];
+ int vrc = ptrVM.vtable()->pfnDBGFR3PlugInLoad(ptrVM.rawUVM(), aName.c_str(), szName, sizeof(szName), RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ hrc = aPlugInName.assignEx(szName);
+ else
+ hrc = setErrorVrc(vrc, "%s", ErrInfo.szMsg);
+ }
+ }
+ return hrc;
+
+}
+
+HRESULT MachineDebugger::unloadPlugIn(const com::Utf8Str &aName)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job and try convert the name.
+ */
+ if (aName.equals("all"))
+ {
+ ptrVM.vtable()->pfnDBGFR3PlugInUnloadAll(ptrVM.rawUVM());
+ hrc = S_OK;
+ }
+ else
+ {
+ int vrc = ptrVM.vtable()->pfnDBGFR3PlugInUnload(ptrVM.rawUVM(), aName.c_str());
+ if (RT_SUCCESS(vrc))
+ hrc = S_OK;
+ else if (vrc == VERR_NOT_FOUND)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Plug-in '%s' was not found"), aName.c_str());
+ else
+ hrc = setErrorVrc(vrc, tr("Error unloading '%s': %Rrc"), aName.c_str(), vrc);
+ }
+ }
+ return hrc;
+
+}
+
+HRESULT MachineDebugger::detectOS(com::Utf8Str &aOs)
+{
+ LogFlowThisFunc(("\n"));
+
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Do the job.
+ */
+ char szName[64];
+ int vrc = ptrVM.vtable()->pfnDBGFR3OSDetect(ptrVM.rawUVM(), szName, sizeof(szName));
+ if (RT_SUCCESS(vrc) && vrc != VINF_DBGF_OS_NOT_DETCTED)
+ hrc = aOs.assignEx(szName);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc, tr("DBGFR3OSDetect failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::queryOSKernelLog(ULONG aMaxMessages, com::Utf8Str &aDmesg)
+{
+ /*
+ * Lock the debugger and get the VM pointer
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ PDBGFOSIDMESG pDmesg = (PDBGFOSIDMESG)ptrVM.vtable()->pfnDBGFR3OSQueryInterface(ptrVM.rawUVM(), DBGFOSINTERFACE_DMESG);
+ if (pDmesg)
+ {
+ size_t cbActual;
+ size_t cbBuf = _512K;
+ int vrc = aDmesg.reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t cMessages = aMaxMessages == 0 ? UINT32_MAX : aMaxMessages;
+ vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages,
+ aDmesg.mutableRaw(), cbBuf, &cbActual);
+
+ uint32_t cTries = 10;
+ while (vrc == VERR_BUFFER_OVERFLOW && cbBuf < 16*_1M && cTries-- > 0)
+ {
+ cbBuf = RT_ALIGN_Z(cbActual + _4K, _4K);
+ vrc = aDmesg.reserveNoThrow(cbBuf);
+ if (RT_SUCCESS(vrc))
+ vrc = pDmesg->pfnQueryKernelLog(pDmesg, ptrVM.rawUVM(), ptrVM.vtable(), 0 /*fFlags*/, cMessages,
+ aDmesg.mutableRaw(), cbBuf, &cbActual);
+ }
+ if (RT_SUCCESS(vrc))
+ aDmesg.jolt();
+ else if (vrc == VERR_BUFFER_OVERFLOW)
+ hrc = setError(E_FAIL, tr("Too much log available, must use the maxMessages parameter to restrict."));
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setErrorBoth(E_OUTOFMEMORY, vrc);
+ }
+ else
+ hrc = setError(E_FAIL, tr("The dmesg interface isn't implemented by guest OS digger, or detectOS() has not been called."));
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::getRegister(ULONG aCpuId, const com::Utf8Str &aName, com::Utf8Str &aValue)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Real work.
+ */
+ DBGFREGVAL Value;
+ DBGFREGVALTYPE enmType;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQuery(ptrVM.rawUVM(), aCpuId, aName.c_str(), &Value, &enmType);
+ if (RT_SUCCESS(vrc))
+ {
+ char szHex[160];
+ ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &Value, enmType, true /*fSpecial*/);
+ if (cch > 0)
+ hrc = aValue.assignEx(szHex);
+ else
+ hrc = E_UNEXPECTED;
+ }
+ else if (vrc == VERR_DBGF_REGISTER_NOT_FOUND)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Register '%s' was not found"), aName.c_str());
+ else if (vrc == VERR_INVALID_CPU_ID)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Invalid CPU ID: %u"), aCpuId);
+ else
+ hrc = setErrorBoth(VBOX_E_VM_ERROR, vrc,
+ tr("DBGFR3RegNmQuery failed with vrc=%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.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Real work.
+ */
+ size_t cRegs;
+ int vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAllCount(ptrVM.rawUVM(), &cRegs);
+ if (RT_SUCCESS(vrc))
+ {
+ PDBGFREGENTRYNM paRegs = (PDBGFREGENTRYNM)RTMemAllocZ(sizeof(paRegs[0]) * cRegs);
+ if (paRegs)
+ {
+ vrc = ptrVM.vtable()->pfnDBGFR3RegNmQueryAll(ptrVM.rawUVM(), paRegs, cRegs);
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ aValues.resize(cRegs);
+ aNames.resize(cRegs);
+ for (uint32_t iReg = 0; iReg < cRegs; iReg++)
+ {
+ char szHex[160];
+ szHex[159] = szHex[0] = '\0';
+ ssize_t cch = ptrVM.vtable()->pfnDBGFR3RegFormatValue(szHex, sizeof(szHex), &paRegs[iReg].Val,
+ paRegs[iReg].enmType, true /*fSpecial*/);
+ Assert(cch > 0); NOREF(cch);
+ aNames[iReg] = paRegs[iReg].pszName;
+ aValues[iReg] = szHex;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAll failed with %Rrc"), vrc);
+
+ RTMemFree(paRegs);
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3RegNmQueryAllCount failed with %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT MachineDebugger::setRegister(ULONG aCpuId, const com::Utf8Str &aName, const com::Utf8Str &aValue)
+{
+ RT_NOREF(aCpuId, aName, aValue);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::setRegisters(ULONG aCpuId, const std::vector<com::Utf8Str> &aNames,
+ const std::vector<com::Utf8Str> &aValues)
+{
+ RT_NOREF(aCpuId, aNames, aValues);
+ ReturnComNotImplemented();
+}
+
+HRESULT MachineDebugger::dumpGuestStack(ULONG aCpuId, com::Utf8Str &aStack)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * There is currently a problem with the windows diggers and SMP, where
+ * guest driver memory is being read from CPU zero in order to ensure that
+ * we've got a consisten virtual memory view. If one of the other CPUs
+ * initiates a rendezvous while we're unwinding the stack and trying to
+ * read guest driver memory, we will deadlock.
+ *
+ * So, check the VM state and maybe suspend the VM before we continue.
+ */
+ int vrc = VINF_SUCCESS;
+ bool fPaused = false;
+ if (aCpuId != 0)
+ {
+ VMSTATE enmVmState = ptrVM.vtable()->pfnVMR3GetStateU(ptrVM.rawUVM());
+ if ( enmVmState == VMSTATE_RUNNING
+ || enmVmState == VMSTATE_RUNNING_LS)
+ {
+ alock.release();
+ vrc = ptrVM.vtable()->pfnVMR3Suspend(ptrVM.rawUVM(), VMSUSPENDREASON_USER);
+ alock.acquire();
+ fPaused = RT_SUCCESS(vrc);
+ }
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ PCDBGFSTACKFRAME pFirstFrame;
+ vrc = ptrVM.vtable()->pfnDBGFR3StackWalkBegin(ptrVM.rawUVM(), aCpuId, DBGFCODETYPE_GUEST, &pFirstFrame);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Print header.
+ */
+ try
+ {
+ uint32_t fBitFlags = 0;
+ for (PCDBGFSTACKFRAME pFrame = pFirstFrame;
+ pFrame;
+ pFrame = ptrVM.vtable()->pfnDBGFR3StackWalkNext(pFrame))
+ {
+ uint32_t const fCurBitFlags = pFrame->fFlags & (DBGFSTACKFRAME_FLAGS_16BIT | DBGFSTACKFRAME_FLAGS_32BIT | DBGFSTACKFRAME_FLAGS_64BIT);
+ if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_16BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("SS:BP Ret SS:BP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ aStack.appendPrintf("%04RX16:%04RX16 %04RX16:%04RX16 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ pFrame->AddrFrame.Sel,
+ (uint16_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint16_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("EBP Ret EBP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ aStack.appendPrintf("%08RX32 %08RX32 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ (uint32_t)pFrame->AddrFrame.off,
+ (uint32_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ aStack.append("RBP Ret SS:RBP Ret RIP CS:RIP / Symbol [line]\n");
+ aStack.appendPrintf("%016RX64 %04RX16:%016RX64 %016RX64",
+ (uint64_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint64_t)pFrame->AddrReturnFrame.off,
+ (uint64_t)pFrame->AddrReturnPC.off);
+ }
+
+ if (!pFrame->pSymPC)
+ aStack.appendPrintf(fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT
+ ? " %RTsel:%016RGv"
+ : fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT
+ ? " %RTsel:%08RGv"
+ : " %RTsel:%04RGv"
+ , pFrame->AddrPC.Sel, pFrame->AddrPC.off);
+ else
+ {
+ RTGCINTPTR offDisp = pFrame->AddrPC.FlatPtr - pFrame->pSymPC->Value; /** @todo this isn't 100% correct for segmented stuff. */
+ if (offDisp > 0)
+ aStack.appendPrintf(" %s+%llx", pFrame->pSymPC->szName, (int64_t)offDisp);
+ else if (offDisp < 0)
+ aStack.appendPrintf(" %s-%llx", pFrame->pSymPC->szName, -(int64_t)offDisp);
+ else
+ aStack.appendPrintf(" %s", pFrame->pSymPC->szName);
+ }
+ if (pFrame->pLinePC)
+ aStack.appendPrintf(" [%s @ 0i%d]", pFrame->pLinePC->szFilename, pFrame->pLinePC->uLineNo);
+ aStack.append("\n");
+
+ fBitFlags = fCurBitFlags;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ ptrVM.vtable()->pfnDBGFR3StackWalkEnd(pFirstFrame);
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("DBGFR3StackWalkBegin failed with %Rrc"), vrc);
+
+ /*
+ * Resume the VM if we suspended it.
+ */
+ if (fPaused)
+ {
+ alock.release();
+ ptrVM.vtable()->pfnVMR3Resume(ptrVM.rawUVM(), VMRESUMEREASON_USER);
+ }
+ }
+ else
+ hrc = setErrorBoth(E_FAIL, vrc, tr("Suspending the VM failed with %Rrc\n"), vrc);
+ }
+
+ return hrc;
+}
+
+/**
+ * Resets VM statistics.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ */
+HRESULT MachineDebugger::resetStats(const com::Utf8Str &aPattern)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ ptrVM.vtable()->pfnSTAMR3Reset(ptrVM.rawUVM(), aPattern.c_str());
+
+ return S_OK;
+}
+
+/**
+ * Dumps VM statistics to the log.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ */
+HRESULT MachineDebugger::dumpStats(const com::Utf8Str &aPattern)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ ptrVM.vtable()->pfnSTAMR3Dump(ptrVM.rawUVM(), aPattern.c_str());
+
+ return S_OK;
+}
+
+/**
+ * Get the VM statistics in an XML format.
+ *
+ * @returns COM status code.
+ * @param aPattern The selection pattern. A bit similar to filename globbing.
+ * @param aWithDescriptions Whether to include the descriptions.
+ * @param aStats The XML document containing the statistics.
+ */
+HRESULT MachineDebugger::getStats(const com::Utf8Str &aPattern, BOOL aWithDescriptions, com::Utf8Str &aStats)
+{
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (!ptrVM.isOk())
+ return setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+
+ char *pszSnapshot;
+ int vrc = ptrVM.vtable()->pfnSTAMR3Snapshot(ptrVM.rawUVM(), aPattern.c_str(), &pszSnapshot, NULL, !!aWithDescriptions);
+ if (RT_FAILURE(vrc))
+ return vrc == VERR_NO_MEMORY ? E_OUTOFMEMORY : E_FAIL;
+
+ /** @todo this is horribly inefficient! And it's kinda difficult to tell whether it failed...
+ * Must use UTF-8 or ASCII here and completely avoid these two extra copy operations.
+ * Until that's done, this method is kind of useless for debugger statistics GUI because
+ * of the amount statistics in a debug build. */
+ HRESULT hrc = aStats.assignEx(pszSnapshot);
+ ptrVM.vtable()->pfnSTAMR3SnapshotFree(ptrVM.rawUVM(), pszSnapshot);
+
+ return hrc;
+}
+
+
+/** Wrapper around TMR3GetCpuLoadPercents. */
+HRESULT MachineDebugger::getCPULoad(ULONG aCpuId, ULONG *aPctExecuting, ULONG *aPctHalted, ULONG *aPctOther, LONG64 *aMsInterval)
+{
+ HRESULT hrc;
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ if (ptrVM.isOk())
+ {
+ uint8_t uPctExecuting = 0;
+ uint8_t uPctHalted = 0;
+ uint8_t uPctOther = 0;
+ uint64_t msInterval = 0;
+ int vrc = ptrVM.vtable()->pfnTMR3GetCpuLoadPercents(ptrVM.rawUVM(), aCpuId >= UINT32_MAX / 2 ? VMCPUID_ALL : aCpuId,
+ &msInterval, &uPctExecuting, &uPctHalted, &uPctOther);
+ if (RT_SUCCESS(vrc))
+ {
+ *aPctExecuting = uPctExecuting;
+ *aPctHalted = uPctHalted;
+ *aPctOther = uPctOther;
+ *aMsInterval = msInterval;
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_INVALID_VM_STATE, tr("Machine is not running"));
+ return hrc;
+}
+
+
+HRESULT MachineDebugger::takeGuestSample(const com::Utf8Str &aFilename, ULONG aUsInterval, LONG64 aUsSampleTime, ComPtr<IProgress> &pProgress)
+{
+ /*
+ * The prologue.
+ */
+ LogFlowThisFunc(("\n"));
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ if (!m_hSampleReport)
+ {
+ m_strFilename = aFilename;
+
+ int vrc = ptrVM.vtable()->pfnDBGFR3SampleReportCreate(ptrVM.rawUVM(), aUsInterval,
+ DBGF_SAMPLE_REPORT_F_STACK_REVERSE, &m_hSampleReport);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = m_Progress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = m_Progress->init(static_cast<IMachineDebugger*>(this),
+ tr("Creating guest sample report..."),
+ TRUE /* aCancelable */);
+ if (SUCCEEDED(hrc))
+ {
+ vrc = ptrVM.vtable()->pfnDBGFR3SampleReportStart(m_hSampleReport, aUsSampleTime, i_dbgfProgressCallback,
+ static_cast<MachineDebugger*>(this));
+ if (RT_SUCCESS(vrc))
+ hrc = m_Progress.queryInterfaceTo(pProgress.asOutParam());
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ }
+
+ if (FAILED(hrc))
+ {
+ ptrVM.vtable()->pfnDBGFR3SampleReportRelease(m_hSampleReport);
+ m_hSampleReport = NULL;
+ }
+ }
+ else
+ hrc = setErrorVrc(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_INVALID_VM_STATE, tr("A sample report is already in progress"));
+ }
+
+ return hrc;
+}
+
+/**
+ * Hack for getting the user mode VM handle (UVM) and VMM function table.
+ *
+ * @returns COM status code
+ * @param aMagicVersion The VMMR3VTABLE_MAGIC_VERSION value of the
+ * caller so we can check that the function table
+ * is compatible. (Otherwise, the caller can't
+ * safely release the UVM reference.)
+ * @param aUVM Where to store the vm handle. Since there is no
+ * uintptr_t in COM, we're using the max integer. (No,
+ * ULONG is not pointer sized!)
+ * @param aVMMFunctionTable Where to store the vm handle.
+ *
+ * @remarks The returned handle must be passed to VMR3ReleaseUVM()!
+ */
+HRESULT MachineDebugger::getUVMAndVMMFunctionTable(LONG64 aMagicVersion, LONG64 *aVMMFunctionTable, LONG64 *aUVM)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Make sure it is a local call.
+ */
+ RTTHREAD hThread = RTThreadSelf();
+ if (hThread != NIL_RTTHREAD)
+ {
+ const char *pszName = RTThreadGetName(hThread);
+ if ( !RTStrStartsWith(pszName, "ALIEN-") /* COM worker threads are aliens */
+ && !RTStrStartsWith(pszName, "nspr-") /* XPCOM worker threads are nspr-X */ )
+ {
+ /*
+ * Use safe VM pointer to get both the UVM and VMM function table.
+ */
+ Console::SafeVMPtr ptrVM(mParent);
+ HRESULT hrc = ptrVM.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ if (VMMR3VTABLE_IS_COMPATIBLE_EX(ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion))
+ {
+ ptrVM.vtable()->pfnVMR3RetainUVM(ptrVM.rawUVM());
+ *aUVM = (intptr_t)ptrVM.rawUVM();
+ *aVMMFunctionTable = (intptr_t)ptrVM.vtable();
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(E_FAIL, tr("Incompatible VMM function table: %RX64 vs %RX64 (caller)"),
+ ptrVM.vtable()->uMagicVersion, (uint64_t)aMagicVersion);
+ }
+ return hrc;
+ }
+ }
+
+ return setError(E_ACCESSDENIED, tr("The method getUVMAndVMMFunctionTable is only for local calls"));
+}
+
+
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+void MachineDebugger::i_flushQueuedSettings()
+{
+ mFlushMode = true;
+ if (mSingleStepQueued != -1)
+ {
+ COMSETTER(SingleStep)(mSingleStepQueued);
+ mSingleStepQueued = -1;
+ }
+ for (unsigned i = 0; i < EMEXECPOLICY_END; i++)
+ if (maiQueuedEmExecPolicyParams[i] != UINT8_MAX)
+ {
+ i_setEmExecPolicyProperty((EMEXECPOLICY)i, RT_BOOL(maiQueuedEmExecPolicyParams[i]));
+ maiQueuedEmExecPolicyParams[i] = UINT8_MAX;
+ }
+ if (mLogEnabledQueued != -1)
+ {
+ COMSETTER(LogEnabled)(mLogEnabledQueued);
+ mLogEnabledQueued = -1;
+ }
+ if (mVirtualTimeRateQueued != UINT32_MAX)
+ {
+ COMSETTER(VirtualTimeRate)(mVirtualTimeRateQueued);
+ mVirtualTimeRateQueued = UINT32_MAX;
+ }
+ mFlushMode = false;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+bool MachineDebugger::i_queueSettings() const
+{
+ if (!mFlushMode)
+ {
+ // check if the machine is running
+ MachineState_T machineState;
+ mParent->COMGETTER(State)(&machineState);
+ switch (machineState)
+ {
+ // queue the request
+ default:
+ return true;
+
+ case MachineState_Running:
+ case MachineState_Paused:
+ case MachineState_Stuck:
+ case MachineState_LiveSnapshotting:
+ case MachineState_Teleporting:
+ break;
+ }
+ }
+ return false;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/Makefile.kup b/src/VBox/Main/src-client/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-client/Makefile.kup
diff --git a/src/VBox/Main/src-client/MouseImpl.cpp b/src/VBox/Main/src-client/MouseImpl.cpp
new file mode 100644
index 00000000..50ec5819
--- /dev/null
+++ b/src/VBox/Main/src-client/MouseImpl.cpp
@@ -0,0 +1,1403 @@
+/* $Id: MouseImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_MOUSE
+#include "LoggingNew.h"
+
+#include <iprt/cpp/utils.h>
+
+#include "MouseImpl.h"
+#include "DisplayImpl.h"
+#include "VMMDev.h"
+#include "MousePointerShapeWrap.h"
+#include "VBoxEvents.h"
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/VMMDev.h>
+#include <VBox/err.h>
+
+
+class ATL_NO_VTABLE MousePointerShape:
+ public MousePointerShapeWrap
+{
+public:
+
+ DECLARE_COMMON_CLASS_METHODS(MousePointerShape)
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ /* Public initializer/uninitializer for internal purposes only. */
+ HRESULT init(ComObjPtr<Mouse> pMouse,
+ bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape);
+ void uninit();
+
+private:
+ // wrapped IMousePointerShape properties
+ virtual HRESULT getVisible(BOOL *aVisible);
+ virtual HRESULT getAlpha(BOOL *aAlpha);
+ virtual HRESULT getHotX(ULONG *aHotX);
+ virtual HRESULT getHotY(ULONG *aHotY);
+ virtual HRESULT getWidth(ULONG *aWidth);
+ virtual HRESULT getHeight(ULONG *aHeight);
+ virtual HRESULT getShape(std::vector<BYTE> &aShape);
+
+ struct Data
+ {
+ ComObjPtr<Mouse> pMouse;
+ bool fVisible;
+ bool fAlpha;
+ uint32_t hotX;
+ uint32_t hotY;
+ uint32_t width;
+ uint32_t height;
+ std::vector<BYTE> shape;
+ };
+
+ Data m;
+};
+
+/*
+ * MousePointerShape implementation.
+ */
+DEFINE_EMPTY_CTOR_DTOR(MousePointerShape)
+
+HRESULT MousePointerShape::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void MousePointerShape::FinalRelease()
+{
+ uninit();
+
+ BaseFinalRelease();
+}
+
+HRESULT MousePointerShape::init(ComObjPtr<Mouse> pMouse,
+ bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape)
+{
+ LogFlowThisFunc(("v %d, a %d, h %d,%d, %dx%d, cb %d\n",
+ fVisible, fAlpha, hotX, hotY, width, height, cbShape));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m.pMouse = pMouse;
+ m.fVisible = fVisible;
+ m.fAlpha = fAlpha;
+ m.hotX = hotX;
+ m.hotY = hotY;
+ m.width = width;
+ m.height = height;
+ m.shape.resize(cbShape);
+ if (cbShape)
+ {
+ memcpy(&m.shape.front(), pu8Shape, cbShape);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void MousePointerShape::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m.pMouse.setNull();
+}
+
+HRESULT MousePointerShape::getVisible(BOOL *aVisible)
+{
+ *aVisible = m.fVisible;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getAlpha(BOOL *aAlpha)
+{
+ *aAlpha = m.fAlpha;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHotX(ULONG *aHotX)
+{
+ *aHotX = m.hotX;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHotY(ULONG *aHotY)
+{
+ *aHotY = m.hotY;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getWidth(ULONG *aWidth)
+{
+ *aWidth = m.width;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getHeight(ULONG *aHeight)
+{
+ *aHeight = m.height;
+ return S_OK;
+}
+
+HRESULT MousePointerShape::getShape(std::vector<BYTE> &aShape)
+{
+ aShape.resize(m.shape.size());
+ if (m.shape.size())
+ memcpy(&aShape.front(), &m.shape.front(), aShape.size());
+ return S_OK;
+}
+
+
+/** @name Mouse device capabilities bitfield
+ * @{ */
+enum
+{
+ /** The mouse device can do relative reporting */
+ MOUSE_DEVCAP_RELATIVE = 1,
+ /** The mouse device can do absolute reporting */
+ MOUSE_DEVCAP_ABSOLUTE = 2,
+ /** The mouse device can do absolute multi-touch reporting */
+ MOUSE_DEVCAP_MT_ABSOLUTE = 4,
+ /** The mouse device can do relative multi-touch reporting */
+ MOUSE_DEVCAP_MT_RELATIVE = 8,
+};
+/** @} */
+
+
+/**
+ * Mouse driver instance data.
+ */
+struct DRVMAINMOUSE
+{
+ /** Pointer to the mouse object. */
+ Mouse *pMouse;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the mouse port interface of the driver/device above us. */
+ PPDMIMOUSEPORT pUpPort;
+ /** Our mouse connector interface. */
+ PDMIMOUSECONNECTOR IConnector;
+ /** The capabilities of this device. */
+ uint32_t u32DevCaps;
+};
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Mouse::Mouse()
+ : mParent(NULL)
+{
+}
+
+Mouse::~Mouse()
+{
+}
+
+
+HRESULT Mouse::FinalConstruct()
+{
+ RT_ZERO(mpDrv);
+ RT_ZERO(mPointerData);
+ mcLastX = 0x8000;
+ mcLastY = 0x8000;
+ mfLastButtons = 0;
+ mfVMMDevGuestCaps = 0;
+ return BaseFinalConstruct();
+}
+
+void Mouse::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public methods only for internal purposes
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the mouse object.
+ *
+ * @returns COM result indicator
+ * @param parent handle of our parent object
+ */
+HRESULT Mouse::init (ConsoleMouseInterface *parent)
+{
+ LogFlowThisFunc(("\n"));
+
+ ComAssertRet(parent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mParent) = parent;
+
+ unconst(mEventSource).createObject();
+ HRESULT hrc = mEventSource->init();
+ AssertComRCReturnRC(hrc);
+
+ ComPtr<IEvent> ptrEvent;
+ hrc = ::CreateGuestMouseEvent(ptrEvent.asOutParam(), mEventSource,
+ (GuestMouseEventMode_T)0, 0 /*x*/, 0 /*y*/, 0 /*z*/, 0 /*w*/, 0 /*buttons*/);
+ AssertComRCReturnRC(hrc);
+ mMouseEvent.init(ptrEvent, mEventSource);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void Mouse::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i])
+ mpDrv[i]->pMouse = NULL;
+ mpDrv[i] = NULL;
+ }
+
+ mPointerShape.setNull();
+
+ RTMemFree(mPointerData.pu8Shape);
+ mPointerData.pu8Shape = NULL;
+ mPointerData.cbShape = 0;
+
+ mMouseEvent.uninit();
+ unconst(mEventSource).setNull();
+ unconst(mParent) = NULL;
+}
+
+void Mouse::updateMousePointerShape(bool fVisible, bool fAlpha,
+ uint32_t hotX, uint32_t hotY,
+ uint32_t width, uint32_t height,
+ const uint8_t *pu8Shape, uint32_t cbShape)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTMemFree(mPointerData.pu8Shape);
+ mPointerData.pu8Shape = NULL;
+ mPointerData.cbShape = 0;
+
+ mPointerData.fVisible = fVisible;
+ mPointerData.fAlpha = fAlpha;
+ mPointerData.hotX = hotX;
+ mPointerData.hotY = hotY;
+ mPointerData.width = width;
+ mPointerData.height = height;
+ if (cbShape)
+ {
+ mPointerData.pu8Shape = (uint8_t *)RTMemDup(pu8Shape, cbShape);
+ if (mPointerData.pu8Shape)
+ {
+ mPointerData.cbShape = cbShape;
+ }
+ }
+
+ mPointerShape.setNull();
+}
+
+// IMouse properties
+/////////////////////////////////////////////////////////////////////////////
+
+/** Report the front-end's mouse handling capabilities to the VMM device and
+ * thus to the guest.
+ * @note all calls out of this object are made with no locks held! */
+HRESULT Mouse::i_updateVMMDevMouseCaps(uint32_t fCapsAdded,
+ uint32_t fCapsRemoved)
+{
+ VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface();
+ if (!pVMMDev)
+ return E_FAIL; /* No assertion, as the front-ends can send events
+ * at all sorts of inconvenient times. */
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ if (pDisplay == NULL)
+ return E_FAIL;
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ if (!pVMMDevPort)
+ return E_FAIL; /* same here */
+
+ int vrc = pVMMDevPort->pfnUpdateMouseCapabilities(pVMMDevPort, fCapsAdded,
+ fCapsRemoved);
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+ return pDisplay->i_reportHostCursorCapabilities(fCapsAdded, fCapsRemoved);
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept absolute
+ * mouse events.
+ *
+ * @returns COM status code
+ * @param aAbsoluteSupported address of result variable
+ */
+HRESULT Mouse::getAbsoluteSupported(BOOL *aAbsoluteSupported)
+{
+ *aAbsoluteSupported = i_supportsAbs();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept relative
+ * mouse events.
+ *
+ * @returns COM status code
+ * @param aRelativeSupported address of result variable
+ */
+HRESULT Mouse::getRelativeSupported(BOOL *aRelativeSupported)
+{
+ *aRelativeSupported = i_supportsRel();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept multi-touch
+ * touchscreen events.
+ *
+ * @returns COM status code
+ * @param aTouchScreenSupported address of result variable
+ */
+HRESULT Mouse::getTouchScreenSupported(BOOL *aTouchScreenSupported)
+{
+ *aTouchScreenSupported = i_supportsTS();
+ return S_OK;
+}
+
+/**
+ * Returns whether the currently active device portfolio can accept multi-touch
+ * touchpad events.
+ *
+ * @returns COM status code
+ * @param aTouchPadSupported address of result variable
+ */
+HRESULT Mouse::getTouchPadSupported(BOOL *aTouchPadSupported)
+{
+ *aTouchPadSupported = i_supportsTP();
+ return S_OK;
+}
+
+/**
+ * Returns whether the guest can currently switch to drawing the mouse cursor
+ * itself if it is asked to by the front-end.
+ *
+ * @returns COM status code
+ * @param aNeedsHostCursor address of result variable
+ */
+HRESULT Mouse::getNeedsHostCursor(BOOL *aNeedsHostCursor)
+{
+ *aNeedsHostCursor = i_guestNeedsHostCursor();
+ return S_OK;
+}
+
+HRESULT Mouse::getPointerShape(ComPtr<IMousePointerShape> &aPointerShape)
+{
+ HRESULT hr = S_OK;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mPointerShape.isNull())
+ {
+ ComObjPtr<MousePointerShape> obj;
+ hr = obj.createObject();
+ if (SUCCEEDED(hr))
+ {
+ hr = obj->init(this, mPointerData.fVisible, mPointerData.fAlpha,
+ mPointerData.hotX, mPointerData.hotY,
+ mPointerData.width, mPointerData.height,
+ mPointerData.pu8Shape, mPointerData.cbShape);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ mPointerShape = obj;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ aPointerShape = mPointerShape;
+ }
+
+ return hr;
+}
+
+// IMouse methods
+/////////////////////////////////////////////////////////////////////////////
+
+/** Converts a bitfield containing information about mouse buttons currently
+ * held down from the format used by the front-end to the format used by PDM
+ * and the emulated pointing devices. */
+static uint32_t i_mouseButtonsToPDM(LONG buttonState)
+{
+ uint32_t fButtons = 0;
+ if (buttonState & MouseButtonState_LeftButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_LEFT;
+ if (buttonState & MouseButtonState_RightButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_RIGHT;
+ if (buttonState & MouseButtonState_MiddleButton)
+ fButtons |= PDMIMOUSEPORT_BUTTON_MIDDLE;
+ if (buttonState & MouseButtonState_XButton1)
+ fButtons |= PDMIMOUSEPORT_BUTTON_X1;
+ if (buttonState & MouseButtonState_XButton2)
+ fButtons |= PDMIMOUSEPORT_BUTTON_X2;
+ return fButtons;
+}
+
+HRESULT Mouse::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+/**
+ * Send a relative pointer event to the relative device we deem most
+ * appropriate.
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportRelEventToMouseDev(int32_t dx, int32_t dy, int32_t dz,
+ int32_t dw, uint32_t fButtons)
+{
+ if (dx || dy || dz || dw || fButtons != mfLastButtons)
+ {
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_RELATIVE))
+ pUpPort = mpDrv[i]->pUpPort;
+ }
+ }
+ if (!pUpPort)
+ return S_OK;
+
+ int vrc = pUpPort->pfnPutEvent(pUpPort, dx, dy, dz, dw, fButtons);
+
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ mfLastButtons = fButtons;
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute pointer event to the emulated absolute device we deem most
+ * appropriate.
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToMouseDev(int32_t x, int32_t y,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ if ( x < VMMDEV_MOUSE_RANGE_MIN
+ || x > VMMDEV_MOUSE_RANGE_MAX)
+ return S_OK;
+ if ( y < VMMDEV_MOUSE_RANGE_MIN
+ || y > VMMDEV_MOUSE_RANGE_MAX)
+ return S_OK;
+ if ( x != mcLastX || y != mcLastY
+ || dz || dw || fButtons != mfLastButtons)
+ {
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (unsigned i = 0; !pUpPort && i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if (mpDrv[i] && (mpDrv[i]->u32DevCaps & MOUSE_DEVCAP_ABSOLUTE))
+ pUpPort = mpDrv[i]->pUpPort;
+ }
+ }
+ if (!pUpPort)
+ return S_OK;
+
+ int vrc = pUpPort->pfnPutEventAbs(pUpPort, x, y, dz,
+ dw, fButtons);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ mfLastButtons = fButtons;
+
+ }
+ return S_OK;
+}
+
+HRESULT Mouse::i_reportMultiTouchEventToDevice(uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ bool fTouchScreen,
+ uint32_t u32ScanTime)
+{
+ HRESULT hrc = S_OK;
+
+ int match = fTouchScreen ? MOUSE_DEVCAP_MT_ABSOLUTE : MOUSE_DEVCAP_MT_RELATIVE;
+ PPDMIMOUSEPORT pUpPort = NULL;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ unsigned i;
+ for (i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ {
+ if ( mpDrv[i]
+ && (mpDrv[i]->u32DevCaps & match))
+ {
+ pUpPort = mpDrv[i]->pUpPort;
+ break;
+ }
+ }
+ }
+
+ if (pUpPort)
+ {
+ int vrc = pUpPort->pfnPutEventTouchScreen(pUpPort, cContacts, pau64Contacts, u32ScanTime);
+ if (RT_FAILURE(vrc))
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the multi-touch event to the virtual device (%Rrc)"),
+ vrc);
+ }
+ else
+ {
+ hrc = E_UNEXPECTED;
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Send an absolute position event to the VMM device.
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToVMMDev(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ VMMDevMouseInterface *pVMMDev = mParent->i_getVMMDevMouseInterface();
+ ComAssertRet(pVMMDev, E_FAIL);
+ PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
+ ComAssertRet(pVMMDevPort, E_FAIL);
+
+ if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons)
+ {
+ int vrc = pVMMDevPort->pfnSetAbsoluteMouse(pVMMDevPort, x, y, dz, dw, fButtons);
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Could not send the mouse event to the virtual mouse (%Rrc)"),
+ vrc);
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute pointer event to a pointing device (the VMM device if
+ * possible or whatever emulated absolute device seems best to us if not).
+ *
+ * @returns COM status code
+ */
+HRESULT Mouse::i_reportAbsEventToInputDevices(int32_t x, int32_t y, int32_t dz, int32_t dw, uint32_t fButtons,
+ bool fUsesVMMDevEvent)
+{
+ HRESULT hrc = S_OK;
+ /** If we are using the VMMDev to report absolute position but without
+ * VMMDev IRQ support then we need to send a small "jiggle" to the emulated
+ * relative mouse device to alert the guest to changes. */
+ LONG cJiggle = 0;
+
+ if (i_vmmdevCanAbs())
+ {
+ /*
+ * Send the absolute mouse position to the VMM device.
+ */
+ if (x != mcLastX || y != mcLastY || dz || dw || fButtons != mfLastButtons)
+ {
+ hrc = i_reportAbsEventToVMMDev(x, y, dz, dw, fButtons);
+ cJiggle = !fUsesVMMDevEvent;
+ }
+
+ /* If guest cannot yet read full mouse state from DevVMM (i.e.,
+ * only 'x' and 'y' coordinates will be read) we need to pass buttons
+ * state as well as horizontal and vertical wheel movement over ever-present PS/2
+ * emulated mouse device. */
+ if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL))
+ hrc = i_reportRelEventToMouseDev(cJiggle, 0, dz, dw, fButtons);
+ }
+ else
+ hrc = i_reportAbsEventToMouseDev(x, y, dz, dw, fButtons);
+
+ mcLastX = x;
+ mcLastY = y;
+ mfLastButtons = fButtons;
+ return hrc;
+}
+
+
+/**
+ * Send an absolute position event to the display device.
+ * @note all calls out of this object are made with no locks held!
+ * @param x Cursor X position in pixels relative to the first screen, where
+ * (1, 1) is the upper left corner.
+ * @param y Cursor Y position in pixels relative to the first screen, where
+ * (1, 1) is the upper left corner.
+ */
+HRESULT Mouse::i_reportAbsEventToDisplayDevice(int32_t x, int32_t y)
+{
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+
+ if (x != mcLastX || y != mcLastY)
+ {
+ pDisplay->i_reportHostCursorPosition(x - 1, y - 1, false);
+ }
+ return S_OK;
+}
+
+
+void Mouse::i_fireMouseEvent(bool fAbsolute, LONG x, LONG y, LONG dz, LONG dw,
+ LONG fButtons)
+{
+ /* If mouse button is pressed, we generate new event, to avoid reusable events coalescing and thus
+ dropping key press events */
+ GuestMouseEventMode_T mode;
+ if (fAbsolute)
+ mode = GuestMouseEventMode_Absolute;
+ else
+ mode = GuestMouseEventMode_Relative;
+
+ if (fButtons != 0)
+ ::FireGuestMouseEvent(mEventSource, mode, x, y, dz, dw, fButtons);
+ else
+ {
+ ComPtr<IEvent> ptrEvent;
+ mMouseEvent.getEvent(ptrEvent.asOutParam());
+ ReinitGuestMouseEvent(ptrEvent, mode, x, y, dz, dw, fButtons);
+ mMouseEvent.fire(0);
+ }
+}
+
+void Mouse::i_fireMultiTouchEvent(uint8_t cContacts,
+ const LONG64 *paContacts,
+ bool fTouchScreen,
+ uint32_t u32ScanTime)
+{
+ com::SafeArray<SHORT> xPositions(cContacts);
+ com::SafeArray<SHORT> yPositions(cContacts);
+ com::SafeArray<USHORT> contactIds(cContacts);
+ com::SafeArray<USHORT> contactFlags(cContacts);
+
+ uint8_t i;
+ for (i = 0; i < cContacts; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ xPositions[i] = (int16_t)u32Lo;
+ yPositions[i] = (int16_t)(u32Lo >> 16);
+ contactIds[i] = RT_BYTE1(u32Hi);
+ contactFlags[i] = RT_BYTE2(u32Hi);
+ }
+
+ ::FireGuestMultiTouchEvent(mEventSource, cContacts, ComSafeArrayAsInParam(xPositions), ComSafeArrayAsInParam(yPositions),
+ ComSafeArrayAsInParam(contactIds), ComSafeArrayAsInParam(contactFlags), fTouchScreen, u32ScanTime);
+}
+
+/**
+ * Send a relative mouse event to the guest.
+ * @note the VMMDev capability change is so that the guest knows we are sending
+ * real events over the PS/2 device and not dummy events to signal the
+ * arrival of new absolute pointer data
+ *
+ * @returns COM status code
+ * @param dx X movement.
+ * @param dy Y movement.
+ * @param dz Z movement.
+ * @param dw Mouse wheel movement.
+ * @param aButtonState The mouse button state.
+ */
+HRESULT Mouse::putMouseEvent(LONG dx, LONG dy, LONG dz, LONG dw,
+ LONG aButtonState)
+{
+ LogRel3(("%s: dx=%d, dy=%d, dz=%d, dw=%d\n", __PRETTY_FUNCTION__,
+ dx, dy, dz, dw));
+
+ uint32_t fButtonsAdj = i_mouseButtonsToPDM(aButtonState);
+ /* Make sure that the guest knows that we are sending real movement
+ * events to the PS/2 device and not just dummy wake-up ones. */
+ i_updateVMMDevMouseCaps(0, VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE);
+ HRESULT hrc = i_reportRelEventToMouseDev(dx, dy, dz, dw, fButtonsAdj);
+
+ i_fireMouseEvent(false, dx, dy, dz, dw, aButtonState);
+
+ return hrc;
+}
+
+/**
+ * Convert an (X, Y) value pair in screen co-ordinates (starting from 1) to a
+ * value from VMMDEV_MOUSE_RANGE_MIN to VMMDEV_MOUSE_RANGE_MAX. Sets the
+ * optional validity value to false if the pair is not on an active screen and
+ * to true otherwise.
+ * @note since guests with recent versions of X.Org use a different method
+ * to everyone else to map the valuator value to a screen pixel (they
+ * multiply by the screen dimension, do a floating point divide by
+ * the valuator maximum and round the result, while everyone else
+ * does truncating integer operations) we adjust the value we send
+ * so that it maps to the right pixel both when the result is rounded
+ * and when it is truncated.
+ *
+ * @returns COM status value
+ */
+HRESULT Mouse::i_convertDisplayRes(LONG x, LONG y, int32_t *pxAdj, int32_t *pyAdj,
+ bool *pfValid)
+{
+ AssertPtrReturn(pxAdj, E_POINTER);
+ AssertPtrReturn(pyAdj, E_POINTER);
+ AssertPtrNullReturn(pfValid, E_POINTER);
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ /** The amount to add to the result (multiplied by the screen width/height)
+ * to compensate for differences in guest methods for mapping back to
+ * pixels */
+ enum { ADJUST_RANGE = - 3 * VMMDEV_MOUSE_RANGE / 4 };
+
+ if (pfValid)
+ *pfValid = true;
+ if (!(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL) && !pDisplay->i_isInputMappingSet())
+ {
+ ULONG displayWidth, displayHeight;
+ ULONG ulDummy;
+ LONG lDummy;
+ /* Takes the display lock */
+ HRESULT hrc = pDisplay->i_getScreenResolution(0, &displayWidth,
+ &displayHeight, &ulDummy, &lDummy, &lDummy);
+ if (FAILED(hrc))
+ return hrc;
+
+ *pxAdj = displayWidth ? (x * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (LONG) displayWidth: 0;
+ *pyAdj = displayHeight ? (y * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (LONG) displayHeight: 0;
+ }
+ else
+ {
+ int32_t x1, y1, x2, y2;
+ /* Takes the display lock */
+ pDisplay->i_getFramebufferDimensions(&x1, &y1, &x2, &y2);
+ *pxAdj = x1 < x2 ? ((x - x1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (x2 - x1) : 0;
+ *pyAdj = y1 < y2 ? ((y - y1) * VMMDEV_MOUSE_RANGE + ADJUST_RANGE)
+ / (y2 - y1) : 0;
+ if ( *pxAdj < VMMDEV_MOUSE_RANGE_MIN
+ || *pxAdj > VMMDEV_MOUSE_RANGE_MAX
+ || *pyAdj < VMMDEV_MOUSE_RANGE_MIN
+ || *pyAdj > VMMDEV_MOUSE_RANGE_MAX)
+ if (pfValid)
+ *pfValid = false;
+ }
+ return S_OK;
+}
+
+
+/**
+ * Send an absolute mouse event to the VM. This requires either VirtualBox-
+ * specific drivers installed in the guest or absolute pointing device
+ * emulation.
+ * @note the VMMDev capability change is so that the guest knows we are sending
+ * dummy events over the PS/2 device to signal the arrival of new
+ * absolute pointer data, and not pointer real movement data
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code
+ * @param x X position (pixel), starting from 1
+ * @param y Y position (pixel), starting from 1
+ * @param dz Z movement
+ * @param dw mouse wheel movement
+ * @param aButtonState The mouse button state
+ */
+HRESULT Mouse::putMouseEventAbsolute(LONG x, LONG y, LONG dz, LONG dw,
+ LONG aButtonState)
+{
+ LogRel3(("%s: x=%d, y=%d, dz=%d, dw=%d, fButtons=0x%x\n",
+ __PRETTY_FUNCTION__, x, y, dz, dw, aButtonState));
+
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ int32_t xAdj, yAdj;
+ uint32_t fButtonsAdj;
+ bool fValid;
+
+ /* If we are doing old-style (IRQ-less) absolute reporting to the VMM
+ * device then make sure the guest is aware of it, so that it knows to
+ * ignore relative movement on the PS/2 device. */
+ i_updateVMMDevMouseCaps(VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE, 0);
+ /* Detect out-of-range. */
+ if (x == 0x7FFFFFFF && y == 0x7FFFFFFF)
+ {
+ pDisplay->i_reportHostCursorPosition(0, 0, true);
+ return S_OK;
+ }
+ /* Detect "report-only" (-1, -1). This is not ideal, as in theory the
+ * front-end could be sending negative values relative to the primary
+ * screen. */
+ if (x == -1 && y == -1)
+ return S_OK;
+ /** @todo the front end should do this conversion to avoid races */
+ /** @note Or maybe not... races are pretty inherent in everything done in
+ * this object and not really bad as far as I can see. */
+ HRESULT hrc = i_convertDisplayRes(x, y, &xAdj, &yAdj, &fValid);
+ if (FAILED(hrc)) return hrc;
+
+ fButtonsAdj = i_mouseButtonsToPDM(aButtonState);
+ if (fValid)
+ {
+ hrc = i_reportAbsEventToInputDevices(xAdj, yAdj, dz, dw, fButtonsAdj,
+ RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_NEW_PROTOCOL));
+ if (FAILED(hrc)) return hrc;
+
+ i_fireMouseEvent(true, x, y, dz, dw, aButtonState);
+ }
+ hrc = i_reportAbsEventToDisplayDevice(x, y);
+
+ return hrc;
+}
+
+/**
+ * Send a multi-touch event. This requires multi-touch pointing device emulation.
+ * @note all calls out of this object are made with no locks held!
+ *
+ * @returns COM status code.
+ * @param aCount Number of contacts.
+ * @param aContacts Information about each contact.
+ * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events.
+ * @param aScanTime Timestamp.
+ */
+HRESULT Mouse::putEventMultiTouch(LONG aCount,
+ const std::vector<LONG64> &aContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ LogRel3(("%s: aCount %d(actual %d), aScanTime %u\n",
+ __FUNCTION__, aCount, aContacts.size(), aScanTime));
+
+ HRESULT hrc = S_OK;
+
+ if ((LONG)aContacts.size() >= aCount)
+ {
+ const LONG64 *paContacts = aCount > 0? &aContacts.front(): NULL;
+
+ hrc = i_putEventMultiTouch(aCount, paContacts, aIsTouchscreen, aScanTime);
+ }
+ else
+ {
+ hrc = E_INVALIDARG;
+ }
+
+ return hrc;
+}
+
+/**
+ * Send a multi-touch event. Version for scripting languages.
+ *
+ * @returns COM status code.
+ * @param aCount Number of contacts.
+ * @param aContacts Information about each contact.
+ * @param aIsTouchscreen Distinguishes between touchscreen and touchpad events.
+ * @param aScanTime Timestamp.
+ */
+HRESULT Mouse::putEventMultiTouchString(LONG aCount,
+ const com::Utf8Str &aContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ /** @todo implement: convert the string to LONG64 array and call putEventMultiTouch. */
+ NOREF(aCount);
+ NOREF(aContacts);
+ NOREF(aIsTouchscreen);
+ NOREF(aScanTime);
+ return E_NOTIMPL;
+}
+
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/* Used by PutEventMultiTouch and PutEventMultiTouchString. */
+HRESULT Mouse::i_putEventMultiTouch(LONG aCount,
+ const LONG64 *paContacts,
+ BOOL aIsTouchscreen,
+ ULONG aScanTime)
+{
+ if (aCount >= 256)
+ return E_INVALIDARG;
+
+ HRESULT hrc = S_OK;
+
+ /* Touch events in the touchscreen variant are currently mapped to the
+ * primary monitor, because the emulated USB touchscreen device is
+ * associated with one (normally the primary) screen in the guest.
+ * In the future this could/should be extended to multi-screen support. */
+ ULONG uScreenId = 0;
+
+ ULONG cWidth = 0;
+ ULONG cHeight = 0;
+ LONG xOrigin = 0;
+ LONG yOrigin = 0;
+
+ if (aIsTouchscreen)
+ {
+ DisplayMouseInterface *pDisplay = mParent->i_getDisplayMouseInterface();
+ ComAssertRet(pDisplay, E_FAIL);
+ ULONG cBPP = 0;
+ hrc = pDisplay->i_getScreenResolution(uScreenId, &cWidth, &cHeight, &cBPP, &xOrigin, &yOrigin);
+ NOREF(cBPP);
+ ComAssertComRCRetRC(hrc);
+ }
+
+ uint64_t* pau64Contacts = NULL;
+ uint8_t cContacts = 0;
+
+ /* Deliver 0 contacts too, touch device may use this to reset the state. */
+ if (aCount > 0)
+ {
+ /* Create a copy with converted coords. */
+ pau64Contacts = (uint64_t *)RTMemTmpAlloc(aCount * sizeof(uint64_t));
+ if (pau64Contacts)
+ {
+ if (aIsTouchscreen)
+ {
+ int32_t x1 = xOrigin;
+ int32_t y1 = yOrigin;
+ int32_t x2 = x1 + cWidth;
+ int32_t y2 = y1 + cHeight;
+
+ LogRel3(("%s: screen [%d] %d,%d %d,%d\n",
+ __FUNCTION__, uScreenId, x1, y1, x2, y2));
+
+ LONG i;
+ for (i = 0; i < aCount; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ int32_t x = (int16_t)u32Lo;
+ int32_t y = (int16_t)(u32Lo >> 16);
+ uint8_t contactId = RT_BYTE1(u32Hi);
+ bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0;
+ bool fInRange = (RT_BYTE2(u32Hi) & 0x2) != 0;
+
+ LogRel3(("%s: touchscreen [%d] %d,%d id %d, inContact %d, inRange %d\n",
+ __FUNCTION__, i, x, y, contactId, fInContact, fInRange));
+
+ /* x1,y1 are inclusive and x2,y2 are exclusive,
+ * while x,y start from 1 and are inclusive.
+ */
+ if (x <= x1 || x > x2 || y <= y1 || y > y2)
+ {
+ /* Out of range. Skip the contact. */
+ continue;
+ }
+
+ int32_t xAdj = x1 < x2? ((x - 1 - x1) * VMMDEV_MOUSE_RANGE) / (x2 - x1) : 0;
+ int32_t yAdj = y1 < y2? ((y - 1 - y1) * VMMDEV_MOUSE_RANGE) / (y2 - y1) : 0;
+
+ bool fValid = ( xAdj >= VMMDEV_MOUSE_RANGE_MIN
+ && xAdj <= VMMDEV_MOUSE_RANGE_MAX
+ && yAdj >= VMMDEV_MOUSE_RANGE_MIN
+ && yAdj <= VMMDEV_MOUSE_RANGE_MAX);
+
+ if (fValid)
+ {
+ uint8_t fu8 = (uint8_t)( (fInContact? 0x01: 0x00)
+ | (fInRange? 0x02: 0x00));
+ pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16((uint16_t)xAdj,
+ (uint16_t)yAdj,
+ RT_MAKE_U16(contactId, fu8),
+ 0);
+ cContacts++;
+ }
+ }
+ }
+ else
+ {
+ LONG i;
+ for (i = 0; i < aCount; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(paContacts[i]);
+ uint32_t u32Hi = RT_HI_U32(paContacts[i]);
+ uint16_t x = (uint16_t)u32Lo;
+ uint16_t y = (uint16_t)(u32Lo >> 16);
+ uint8_t contactId = RT_BYTE1(u32Hi);
+ bool fInContact = (RT_BYTE2(u32Hi) & 0x1) != 0;
+
+ LogRel3(("%s: touchpad [%d] %#04x,%#04x id %d, inContact %d\n",
+ __FUNCTION__, i, x, y, contactId, fInContact));
+
+ uint8_t fu8 = (uint8_t)(fInContact? 0x01: 0x00);
+
+ pau64Contacts[cContacts] = RT_MAKE_U64_FROM_U16(x, y,
+ RT_MAKE_U16(contactId, fu8),
+ 0);
+ cContacts++;
+ }
+ }
+ }
+ else
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_reportMultiTouchEventToDevice(cContacts, cContacts? pau64Contacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime);
+
+ /* Send the original contact information. */
+ i_fireMultiTouchEvent(cContacts, cContacts? paContacts: NULL, !!aIsTouchscreen, (uint32_t)aScanTime);
+ }
+
+ RTMemTmpFree(pau64Contacts);
+
+ return hrc;
+}
+
+
+/** Does the guest currently rely on the host to draw the mouse cursor or
+ * can it switch to doing it itself in software? */
+bool Mouse::i_guestNeedsHostCursor(void)
+{
+ return RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR);
+}
+
+
+/**
+ * Gets the combined capabilities of all currently enabled devices.
+ *
+ * @returns Combination of MOUSE_DEVCAP_XXX
+ */
+uint32_t Mouse::i_getDeviceCaps(void)
+{
+ uint32_t fCaps = 0;
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+ for (unsigned i = 0; i < MOUSE_MAX_DEVICES; ++i)
+ if (mpDrv[i])
+ fCaps |= mpDrv[i]->u32DevCaps;
+ return fCaps;
+}
+
+
+/** Does the VMM device currently support absolute reporting? */
+bool Mouse::i_vmmdevCanAbs(void)
+{
+ /* This requires the VMMDev cap and a relative device, which supposedly
+ consumes these. As seen in @bugref{10285} this isn't quite as clear cut. */
+ return (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE)
+ && (i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE);
+}
+
+
+/** Does any device currently support absolute reporting w/o help from VMMDev? */
+bool Mouse::i_deviceCanAbs(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_ABSOLUTE);
+}
+
+
+/** Can we currently send relative events to the guest? */
+bool Mouse::i_supportsRel(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_RELATIVE);
+}
+
+
+/** Can we currently send absolute events to the guest (including via VMMDev)? */
+bool Mouse::i_supportsAbs(uint32_t fCaps) const
+{
+ return (fCaps & MOUSE_DEVCAP_ABSOLUTE)
+ || /* inlined i_vmmdevCanAbs() to avoid unnecessary i_getDeviceCaps call: */
+ ( (mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE)
+ && (fCaps & MOUSE_DEVCAP_RELATIVE));
+}
+
+
+/** Can we currently send absolute events to the guest? */
+bool Mouse::i_supportsAbs(void)
+{
+ return Mouse::i_supportsAbs(i_getDeviceCaps());
+}
+
+
+/** Can we currently send multi-touch events (touchscreen variant) to the guest? */
+bool Mouse::i_supportsTS(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_ABSOLUTE);
+}
+
+
+/** Can we currently send multi-touch events (touchpad variant) to the guest? */
+bool Mouse::i_supportsTP(void)
+{
+ return RT_BOOL(i_getDeviceCaps() & MOUSE_DEVCAP_MT_RELATIVE);
+}
+
+
+/** Check what sort of reporting can be done using the devices currently
+ * enabled (including the VMM device) and notify the guest and the front-end.
+ */
+void Mouse::i_sendMouseCapsNotifications(void)
+{
+ bool fRelDev, fTSDev, fTPDev, fCanAbs, fNeedsHostCursor;
+ {
+ AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+ uint32_t const fCaps = i_getDeviceCaps();
+ fRelDev = RT_BOOL(fCaps & MOUSE_DEVCAP_RELATIVE);
+ fTSDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_ABSOLUTE);
+ fTPDev = RT_BOOL(fCaps & MOUSE_DEVCAP_MT_RELATIVE);
+ fCanAbs = i_supportsAbs(fCaps);
+ fNeedsHostCursor = i_guestNeedsHostCursor();
+ }
+ mParent->i_onMouseCapabilityChange(fCanAbs, fRelDev, fTSDev, fTPDev, fNeedsHostCursor);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUSECONNECTOR,pfnReportModes}
+ */
+DECLCALLBACK(void) Mouse::i_mouseReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRelative,
+ bool fAbsolute, bool fMTAbsolute, bool fMTRelative)
+{
+ PDRVMAINMOUSE pDrv = RT_FROM_MEMBER(pInterface, DRVMAINMOUSE, IConnector);
+ if (fRelative)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_RELATIVE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_RELATIVE;
+ if (fAbsolute)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_ABSOLUTE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_ABSOLUTE;
+ if (fMTAbsolute)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_ABSOLUTE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_ABSOLUTE;
+ if (fMTRelative)
+ pDrv->u32DevCaps |= MOUSE_DEVCAP_MT_RELATIVE;
+ else
+ pDrv->u32DevCaps &= ~MOUSE_DEVCAP_MT_RELATIVE;
+
+ pDrv->pMouse->i_sendMouseCapsNotifications();
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) Mouse::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINMOUSE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pDrv->IConnector);
+ return NULL;
+}
+
+
+/**
+ * Destruct a mouse driver instance.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) Mouse::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+ LogFlow(("Mouse::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pMouse)
+ {
+ AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS);
+ for (unsigned cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev)
+ if (pThis->pMouse->mpDrv[cDev] == pThis)
+ {
+ pThis->pMouse->mpDrv[cDev] = NULL;
+ break;
+ }
+ }
+}
+
+
+/**
+ * Construct a mouse driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) Mouse::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINMOUSE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINMOUSE);
+ LogFlow(("drvMainMouse_Construct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = Mouse::i_drvQueryInterface;
+
+ pThis->IConnector.pfnReportModes = Mouse::i_mouseReportModes;
+
+ /*
+ * Get the IMousePort interface of the above driver/device.
+ */
+ pThis->pUpPort = (PPDMIMOUSEPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMIMOUSEPORT_IID);
+ if (!pThis->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No mouse port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Get the Mouse object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(COM_IIDOF(IMouse));
+ IMouse *pIMouse = (IMouse *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pIMouse)
+ {
+ AssertMsgFailed(("Configuration error: No/bad Mouse object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pMouse = static_cast<Mouse *>(pIMouse);
+
+ unsigned cDev;
+ {
+ AutoWriteLock mouseLock(pThis->pMouse COMMA_LOCKVAL_SRC_POS);
+
+ for (cDev = 0; cDev < MOUSE_MAX_DEVICES; ++cDev)
+ if (!pThis->pMouse->mpDrv[cDev])
+ {
+ pThis->pMouse->mpDrv[cDev] = pThis;
+ break;
+ }
+ }
+ if (cDev == MOUSE_MAX_DEVICES)
+ return VERR_NO_MORE_HANDLES;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Main mouse driver registration record.
+ */
+const PDMDRVREG Mouse::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MainMouse",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main mouse driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MOUSE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINMOUSE),
+ /* pfnConstruct */
+ Mouse::i_drvConstruct,
+ /* pfnDestruct */
+ Mouse::i_drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/PCIRawDevImpl.cpp b/src/VBox/Main/src-client/PCIRawDevImpl.cpp
new file mode 100644
index 00000000..a81160ca
--- /dev/null
+++ b/src/VBox/Main/src-client/PCIRawDevImpl.cpp
@@ -0,0 +1,230 @@
+/* $Id: PCIRawDevImpl.cpp $ */
+/** @file
+ * VirtualBox Driver Interface to raw PCI device.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW
+#include "LoggingNew.h"
+
+#include "PCIRawDevImpl.h"
+#include "PCIDeviceAttachmentImpl.h"
+#include "ConsoleImpl.h"
+
+// generated header for events
+#include "VBoxEvents.h"
+
+#include <VBox/err.h>
+
+
+/**
+ * PCI raw driver instance data.
+ */
+typedef struct DRVMAINPCIRAWDEV
+{
+ /** Pointer to the real PCI raw object. */
+ PCIRawDev *pPCIRawDev;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Our PCI device connector interface. */
+ PDMIPCIRAWCONNECTOR IConnector;
+
+} DRVMAINPCIRAWDEV, *PDRVMAINPCIRAWDEV;
+
+//
+// constructor / destructor
+//
+PCIRawDev::PCIRawDev(Console *console)
+ : mParent(console),
+ mpDrv(NULL)
+{
+}
+
+PCIRawDev::~PCIRawDev()
+{
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) PCIRawDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINPCIRAWDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINPCIRAWDEV);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIPCIRAWCONNECTOR, &pThis->IConnector);
+
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMIPCIRAWCONNECTOR,pfnDeviceConstructComplete}
+ */
+DECLCALLBACK(int) PCIRawDev::drvDeviceConstructComplete(PPDMIPCIRAWCONNECTOR pInterface, const char *pcszName,
+ uint32_t uHostPCIAddress, uint32_t uGuestPCIAddress,
+ int vrc)
+{
+ 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(vrc))
+ msg.printf("runtime error %Rrc", vrc);
+
+ ::FireHostPCIDevicePlugEvent(es, bstrId.raw(), true /* plugged */, RT_SUCCESS_NP(vrc) /* 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 vrc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv);
+ if (RT_FAILURE(vrc))
+ {
+ AssertMsgFailed(("Configuration error: No \"Object\" value! vrc=%Rrc\n", vrc));
+ return vrc;
+ }
+
+ 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..ce475213
--- /dev/null
+++ b/src/VBox/Main/src-client/Recording.cpp
@@ -0,0 +1,945 @@
+/* $Id: Recording.cpp $ */
+/** @file
+ * Recording context code.
+ *
+ * This code employs a separate encoding thread per recording context
+ * to keep time spent in EMT as short as possible. Each configured VM display
+ * is represented by an own recording stream, which in turn has its own rendering
+ * queue. Common recording data across all recording streams is kept in a
+ * separate queue in the recording context to minimize data duplication and
+ * multiplexing overhead in EMT.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <stdexcept>
+#include <vector>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/err.h>
+#include <VBox/com/VirtualBox.h>
+
+#include "ConsoleImpl.h"
+#include "Recording.h"
+#include "RecordingInternals.h"
+#include "RecordingStream.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+using namespace com;
+
+#ifdef DEBUG_andy
+/** Enables dumping audio / video data for debugging reasons. */
+//# define VBOX_RECORDING_DUMP
+#endif
+
+
+/**
+ * Recording context constructor.
+ *
+ * @note Will throw vrc when unable to create.
+ */
+RecordingContext::RecordingContext(void)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+/**
+ * Recording context constructor.
+ *
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ *
+ * @note Will throw vrc when unable to create.
+ */
+RecordingContext::RecordingContext(Console *ptrConsole, const settings::RecordingSettings &Settings)
+ : m_pConsole(NULL)
+ , m_enmState(RECORDINGSTS_UNINITIALIZED)
+ , m_cStreamsEnabled(0)
+{
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+
+ vrc = RecordingContext::createInternal(ptrConsole, Settings);
+ if (RT_FAILURE(vrc))
+ throw vrc;
+}
+
+RecordingContext::~RecordingContext(void)
+{
+ destroyInternal();
+
+ if (RTCritSectIsInitialized(&m_CritSect))
+ RTCritSectDelete(&m_CritSect);
+}
+
+/**
+ * Worker thread for all streams of a recording context.
+ *
+ * For video frames, this also does the RGB/YUV conversion and encoding.
+ */
+DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+
+ /* Signal that we're up and rockin'. */
+ RTThreadUserSignal(hThreadSelf);
+
+ LogRel2(("Recording: Thread started\n"));
+
+ for (;;)
+ {
+ int vrc = RTSemEventWait(pThis->m_WaitEvent, RT_INDEFINITE_WAIT);
+ AssertRCBreak(vrc);
+
+ Log2Func(("Processing %zu streams\n", pThis->m_vecStreams.size()));
+
+ /* Process common raw blocks (data which not has been encoded yet). */
+ vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
+
+ /** @todo r=andy This is inefficient -- as we already wake up this thread
+ * for every screen from Main, we here go again (on every wake up) through
+ * all screens. */
+ RecordingStreams::iterator itStream = pThis->m_vecStreams.begin();
+ while (itStream != pThis->m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*itStream);
+
+ /* Hand-in common encoded blocks. */
+ vrc = pStream->Process(pThis->m_mapBlocksEncoded);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), vrc));
+ break;
+ }
+
+ ++itStream;
+ }
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding thread failed (%Rrc)\n", vrc));
+
+ /* Keep going in case of errors. */
+
+ if (ASMAtomicReadBool(&pThis->m_fShutdown))
+ {
+ LogFunc(("Thread is shutting down ...\n"));
+ break;
+ }
+
+ } /* for */
+
+ LogRel2(("Recording: Thread ended\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notifies a recording context's encoding thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::threadNotify(void)
+{
+ return RTSemEventSignal(m_WaitEvent);
+}
+
+/**
+ * Worker function for processing common block data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to handle.
+ * @param msTimeout Timeout to use for maximum time spending to process data.
+ * Use RT_INDEFINITE_WAIT for processing all data.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingContext::processCommonData(RecordingBlockMap &mapCommon, RTMSINTERVAL msTimeout)
+{
+ Log2Func(("Processing %zu common blocks (%RU32ms timeout)\n", mapCommon.size(), msTimeout));
+
+ int vrc = VINF_SUCCESS;
+
+ uint64_t const msStart = RTTimeMilliTS();
+ RecordingBlockMap::iterator itCommonBlocks = mapCommon.begin();
+ while (itCommonBlocks != mapCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+
+ RECORDINGFRAME Frame;
+ Frame.msTimestamp = pBlockCommon->msTimestamp;
+ Frame.Audio.pvBuf = pAudioFrame->pvBuf;
+ Frame.Audio.cbBuf = pAudioFrame->cbBuf;
+
+ vrc = recordingCodecEncode(&m_CodecAudio, &Frame, NULL, NULL);
+ break;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+ default:
+ /* Skip unknown stuff. */
+ break;
+ }
+
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+
+ if (RT_FAILURE(vrc) || RTTimeMilliTS() > msStart + msTimeout)
+ break;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes common block data (i.e. shared / the same) in all streams.
+ *
+ * The multiplexing is needed to supply all recorded (enabled) screens with the same
+ * data at the same given point in time.
+ *
+ * Currently this only is being used for audio data.
+ *
+ * @returns VBox status code.
+ * @param mapCommon Common block map to write data to.
+ * @param pCodec Pointer to codec instance which has written the data.
+ * @param pvData Pointer to written data (encoded).
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Absolute PTS (in ms) of the written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingContext::writeCommonData(RecordingBlockMap &mapCommon, PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msTimestamp, uint32_t uFlags)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pCodec=%p, cbData=%zu, msTimestamp=%zu, uFlags=%#x\n",
+ pCodec, cbData, msTimestamp, uFlags));
+
+ /** @todo Optimize this! Three allocations in here! */
+
+ RECORDINGBLOCKTYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? RECORDINGBLOCKTYPE_AUDIO : RECORDINGBLOCKTYPE_UNKNOWN;
+
+ AssertReturn(enmType != RECORDINGBLOCKTYPE_UNKNOWN, VERR_NOT_SUPPORTED);
+
+ RecordingBlock *pBlock = new RecordingBlock();
+
+ switch (enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
+ AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+
+ pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
+ AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
+ pFrame->cbBuf = cbData;
+
+ memcpy(pFrame->pvBuf, pvData, cbData);
+
+ pBlock->enmType = enmType;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
+ pBlock->cRefs = m_cStreamsEnabled;
+ pBlock->msTimestamp = msTimestamp;
+ pBlock->uFlags = uFlags;
+
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ lock();
+
+ int vrc;
+
+ try
+ {
+ RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
+ if (itBlocks == mapCommon.end())
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ mapCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ else
+ itBlocks->second->List.push_back(pBlock);
+
+ vrc = VINF_SUCCESS;
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+ vrc = VERR_NO_MEMORY;
+ }
+
+ unlock();
+
+ if (RT_SUCCESS(vrc))
+ vrc = threadNotify();
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Callback function for writing encoded audio data into the common encoded block map.
+ *
+ * This is called by the audio codec when finishing encoding audio data.
+ *
+ * @copydoc RECORDINGCODECCALLBACKS::pfnWriteData
+ */
+/* static */
+DECLCALLBACK(int) RecordingContext::audioCodecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingContext *pThis = (RecordingContext *)pvUser;
+ return pThis->writeCommonData(pThis->m_mapBlocksEncoded, pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the audio codec for a (multiplexing) recording context.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Reference to recording screen settings to use for initialization.
+ */
+int RecordingContext::audioInit(const settings::RecordingScreenSettings &screenSettings)
+{
+ RecordingAudioCodec_T const enmCodec = screenSettings.Audio.enmCodec;
+
+ if (enmCodec == RecordingAudioCodec_None)
+ {
+ LogRel2(("Recording: No audio codec configured, skipping audio init\n"));
+ return VINF_SUCCESS;
+ }
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingContext::audioCodecWriteDataCallback;
+
+ int vrc = recordingCodecCreateAudio(&m_CodecAudio, enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(&m_CodecAudio, &Callbacks, screenSettings);
+
+ return vrc;
+}
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+/**
+ * Creates a recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::createInternal(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Copy the settings to our context. */
+ m_Settings = Settings;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ settings::RecordingScreenSettingsMap::const_iterator itScreen0 = m_Settings.mapScreens.begin();
+ AssertReturn(itScreen0 != m_Settings.mapScreens.end(), VERR_WRONG_ORDER);
+
+ /* We always use the audio settings from screen 0, as we multiplex the audio data anyway. */
+ settings::RecordingScreenSettings const &screen0Settings = itScreen0->second;
+
+ vrc = this->audioInit(screen0Settings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ m_pConsole = ptrConsole;
+
+ settings::RecordingScreenSettingsMap::const_iterator itScreen = m_Settings.mapScreens.begin();
+ while (itScreen != m_Settings.mapScreens.end())
+ {
+ RecordingStream *pStream = NULL;
+ try
+ {
+ pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
+ m_vecStreams.push_back(pStream);
+ if (itScreen->second.fEnabled)
+ m_cStreamsEnabled++;
+ LogFlowFunc(("pStream=%p\n", pStream));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ catch (int vrc_thrown) /* Catch vrc thrown by constructor. */
+ {
+ vrc = vrc_thrown;
+ break;
+ }
+
+ ++itScreen;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_tsStartMs = RTTimeMilliTS();
+ m_enmState = RECORDINGSTS_CREATED;
+ m_fShutdown = false;
+
+ vrc = RTSemEventCreate(&m_WaitEvent);
+ AssertRCReturn(vrc, vrc);
+ }
+
+ if (RT_FAILURE(vrc))
+ destroyInternal();
+
+ return vrc;
+}
+
+/**
+ * Starts a recording context by creating its worker thread.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::startInternal(void)
+{
+ if (m_enmState == RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ Assert(m_enmState == RECORDINGSTS_CREATED);
+
+ int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
+ RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
+
+ if (RT_SUCCESS(vrc)) /* Wait for the thread to start. */
+ vrc = RTThreadUserWait(m_Thread, RT_MS_30SEC /* 30s timeout */);
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Started\n"));
+ m_enmState = RECORDINGSTS_STARTED;
+ }
+ else
+ Log(("Recording: Failed to start (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Stops a recording context by telling the worker thread to stop and finalizing its operation.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::stopInternal(void)
+{
+ if (m_enmState != RECORDINGSTS_STARTED)
+ return VINF_SUCCESS;
+
+ LogThisFunc(("Shutting down thread ...\n"));
+
+ /* Set shutdown indicator. */
+ ASMAtomicWriteBool(&m_fShutdown, true);
+
+ /* Signal the thread and wait for it to shut down. */
+ int vrc = threadNotify();
+ if (RT_SUCCESS(vrc))
+ vrc = RTThreadWait(m_Thread, RT_MS_30SEC /* 30s timeout */, NULL);
+
+ lock();
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Stopped\n"));
+ m_enmState = RECORDINGSTS_CREATED;
+ }
+ else
+ Log(("Recording: Failed to stop (%Rrc)\n", vrc));
+
+ unlock();
+
+ LogFlowThisFunc(("%Rrc\n", vrc));
+ return vrc;
+}
+
+/**
+ * Destroys a recording context, internal version.
+ */
+void RecordingContext::destroyInternal(void)
+{
+ lock();
+
+ if (m_enmState == RECORDINGSTS_UNINITIALIZED)
+ {
+ unlock();
+ return;
+ }
+
+ int vrc = stopInternal();
+ AssertRCReturnVoid(vrc);
+
+ vrc = RTSemEventDestroy(m_WaitEvent);
+ AssertRCReturnVoid(vrc);
+
+ m_WaitEvent = NIL_RTSEMEVENT;
+
+ RecordingStreams::iterator it = m_vecStreams.begin();
+ while (it != m_vecStreams.end())
+ {
+ RecordingStream *pStream = (*it);
+
+ vrc = pStream->Uninit();
+ AssertRC(vrc);
+
+ delete pStream;
+ pStream = NULL;
+
+ m_vecStreams.erase(it);
+ it = m_vecStreams.begin();
+ }
+
+ /* Sanity. */
+ Assert(m_vecStreams.empty());
+ Assert(m_mapBlocksRaw.size() == 0);
+ Assert(m_mapBlocksEncoded.size() == 0);
+
+ m_enmState = RECORDINGSTS_UNINITIALIZED;
+
+ unlock();
+}
+
+/**
+ * Returns a recording context's current settings.
+ *
+ * @returns The recording context's current settings.
+ */
+const settings::RecordingSettings &RecordingContext::GetConfig(void) const
+{
+ return m_Settings;
+}
+
+/**
+ * Returns the recording stream for a specific screen.
+ *
+ * @returns Recording stream for a specific screen, or NULL if not found.
+ * @param uScreen Screen ID to retrieve recording stream for.
+ */
+RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
+{
+ RecordingStream *pStream;
+
+ try
+ {
+ pStream = m_vecStreams.at(uScreen);
+ }
+ catch (std::out_of_range &)
+ {
+ pStream = NULL;
+ }
+
+ return pStream;
+}
+
+/**
+ * Locks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unlocks the recording context for serializing access.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+ return vrc;
+}
+
+/**
+ * Retrieves a specific recording stream of a recording context.
+ *
+ * @returns Pointer to recording stream if found, or NULL if not found.
+ * @param uScreen Screen number of recording stream to look up.
+ */
+RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
+{
+ return getStreamInternal(uScreen);
+}
+
+/**
+ * Returns the number of configured recording streams for a recording context.
+ *
+ * @returns Number of configured recording streams.
+ */
+size_t RecordingContext::GetStreamCount(void) const
+{
+ return m_vecStreams.size();
+}
+
+/**
+ * Creates a new recording context.
+ *
+ * @returns VBox status code.
+ * @param ptrConsole Pointer to console object this context is bound to (weak pointer).
+ * @param Settings Reference to recording settings to use for creation.
+ */
+int RecordingContext::Create(Console *ptrConsole, const settings::RecordingSettings &Settings)
+{
+ return createInternal(ptrConsole, Settings);
+}
+
+/**
+ * Destroys a recording context.
+ */
+void RecordingContext::Destroy(void)
+{
+ destroyInternal();
+}
+
+/**
+ * Starts a recording context.
+ *
+ * @returns VBox status code.
+ */
+int RecordingContext::Start(void)
+{
+ return startInternal();
+}
+
+/**
+ * Stops a recording context.
+ */
+int RecordingContext::Stop(void)
+{
+ return stopInternal();
+}
+
+/**
+ * Returns if a specific recoding feature is enabled for at least one of the attached
+ * recording streams or not.
+ *
+ * @returns @c true if at least one recording stream has this feature enabled, or @c false if
+ * no recording stream has this feature enabled.
+ * @param enmFeature Recording feature to check for.
+ */
+bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
+{
+ lock();
+
+ RecordingStreams::const_iterator itStream = m_vecStreams.begin();
+ while (itStream != m_vecStreams.end())
+ {
+ if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
+ {
+ unlock();
+ return true;
+ }
+ ++itStream;
+ }
+
+ unlock();
+
+ return false;
+}
+
+/**
+ * Returns if this recording context is ready to start recording.
+ *
+ * @returns @c true if recording context is ready, @c false if not.
+ */
+bool RecordingContext::IsReady(void)
+{
+ lock();
+
+ const bool fIsReady = m_enmState >= RECORDINGSTS_CREATED;
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns if this recording context is ready to accept new recording data for a given screen.
+ *
+ * @returns @c true if the specified screen is ready, @c false if not.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms). Currently not being used.
+ */
+bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
+{
+ RT_NOREF(msTimestamp);
+
+ lock();
+
+ bool fIsReady = false;
+
+ if (m_enmState != RECORDINGSTS_STARTED)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fIsReady = pStream->IsReady();
+
+ /* Note: Do not check for other constraints like the video FPS rate here,
+ * as this check then also would affect other (non-FPS related) stuff
+ * like audio data. */
+ }
+
+ unlock();
+
+ return fIsReady;
+}
+
+/**
+ * Returns whether a given recording context has been started or not.
+ *
+ * @returns true if active, false if not.
+ */
+bool RecordingContext::IsStarted(void)
+{
+ lock();
+
+ const bool fIsStarted = m_enmState == RECORDINGSTS_STARTED;
+
+ unlock();
+
+ return fIsStarted;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ */
+bool RecordingContext::IsLimitReached(void)
+{
+ lock();
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ const bool fLimitReached = m_cStreamsEnabled == 0;
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Checks if a specified limit for recording has been reached.
+ *
+ * @returns true if any limit has been reached.
+ * @param uScreen Screen ID.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fLimitReached = false;
+
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if ( !pStream
+ || pStream->IsLimitReached(msTimestamp))
+ {
+ fLimitReached = true;
+ }
+
+ unlock();
+
+ return fLimitReached;
+}
+
+/**
+ * Returns if a specific screen needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param uScreen Screen ID to retrieve update stats for.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingContext::NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp)
+{
+ lock();
+
+ bool fNeedsUpdate = false;
+
+ if (m_enmState == RECORDINGSTS_STARTED)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if ( recordingCodecIsInitialized(&m_CodecAudio)
+ && recordingCodecGetWritable(&m_CodecAudio, msTimestamp) > 0)
+ {
+ fNeedsUpdate = true;
+ }
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ if (!fNeedsUpdate)
+ {
+ const RecordingStream *pStream = getStreamInternal(uScreen);
+ if (pStream)
+ fNeedsUpdate = pStream->NeedsUpdate(msTimestamp);
+ }
+ }
+
+ unlock();
+
+ return fNeedsUpdate;
+}
+
+DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int vrc)
+{
+ RT_NOREF(uScreen, vrc);
+ LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, vrc));
+
+ lock();
+
+ Assert(m_cStreamsEnabled);
+ m_cStreamsEnabled--;
+
+ LogFlowThisFunc(("cStreamsEnabled=%RU16\n", m_cStreamsEnabled));
+
+ unlock();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends an audio frame to the recording thread.
+ *
+ * @returns VBox status code.
+ * @param pvData Audio frame data to send.
+ * @param cbData Size (in bytes) of (encoded) audio frame data.
+ * @param msTimestamp Timestamp (PTS, in ms) of audio playback.
+ */
+int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ return writeCommonData(m_mapBlocksRaw, &m_CodecAudio,
+ pvData, cbData, msTimestamp, RECORDINGCODEC_ENC_F_BLOCK_IS_KEY);
+#else
+ RT_NOREF(pvData, cbData, msTimestamp);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Sends a video frame to the recording thread.
+ *
+ * @thread EMT
+ *
+ * @returns VBox status code.
+ * @param uScreen Screen number to send video frame to.
+ * @param x Starting x coordinate of the video frame.
+ * @param y Starting y coordinate of the video frame.
+ * @param uPixelFormat Pixel format.
+ * @param uBPP Bits Per Pixel (BPP).
+ * @param uBytesPerLine Bytes per scanline.
+ * @param uSrcWidth Width of the video frame.
+ * @param uSrcHeight Height of the video frame.
+ * @param puSrcData Pointer to video frame data.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
+ uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
+ uint64_t msTimestamp)
+{
+ AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(puSrcData, VERR_INVALID_POINTER);
+
+ lock();
+
+ RecordingStream *pStream = getStreamInternal(uScreen);
+ if (!pStream)
+ {
+ unlock();
+
+ AssertFailed();
+ return VERR_NOT_FOUND;
+ }
+
+ int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
+
+ unlock();
+
+ if ( RT_SUCCESS(vrc)
+ && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
+ {
+ threadNotify();
+ }
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-client/RecordingCodec.cpp b/src/VBox/Main/src-client/RecordingCodec.cpp
new file mode 100644
index 00000000..2de401c3
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingCodec.cpp
@@ -0,0 +1,894 @@
+/* $Id: RecordingCodec.cpp $ */
+/** @file
+ * Recording codec wrapper.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* This code makes use of Vorbis (libvorbis):
+ *
+ * Copyright (c) 2002-2020 Xiph.org Foundation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Xiph.org Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <VBox/com/string.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "RecordingInternals.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+#include <math.h>
+
+
+/*********************************************************************************************************************************
+* VPX (VP8 / VP9) codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVPX
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ pCodec->Parms.csFrame = 0;
+ pCodec->Parms.cbFrame = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */;
+ pCodec->Parms.msFrame = 1; /* 1ms per frame. */
+
+# ifdef VBOX_WITH_LIBVPX_VP9
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
+# else /* Default is using VP8. */
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
+# endif
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Target bitrate in kilobits per second. */
+ pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
+ /* Frame width. */
+ pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth;
+ /* Frame height. */
+ pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight;
+ /* ms per frame. */
+ pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
+ pVPX->Cfg.g_timebase.den = 1000;
+ /* Disable multithreading. */
+ pVPX->Cfg.g_threads = 0;
+
+ /* Initialize codec. */
+ rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
+ pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1))
+ {
+ LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Save a pointer to the first raw YUV plane. */
+ pVPX->pu8YuvBuf = pVPX->RawImage.planes[0];
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_img_free(&pVPX->RawImage);
+ pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
+
+ vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx);
+ Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnParseOptions */
+static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_REALTIME;
+ else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25);
+ pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS;
+ }
+ else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY;
+ else
+ pVPX->uEncoderDeadline = value.toUInt32();
+ }
+ else
+ LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
+ size_t *pcEncoded, size_t *pcbEncoded)
+{
+ RT_NOREF(pcEncoded, pcbEncoded);
+
+ AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
+
+ PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr;
+
+ int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt,
+ /* Destination */
+ pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
+ /* Source */
+ pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight);
+
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ /* Presentation TimeStamp (PTS). */
+ vpx_codec_pts_t pts = pFrame->msTimestamp;
+ vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx,
+ &pVPX->RawImage,
+ pts /* Timestamp */,
+ pCodec->Parms.Video.uDelayMs /* How long to show this frame */,
+ 0 /* Flags */,
+ pVPX->uEncoderDeadline /* Quality setting */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */
+ LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ pCodec->State.cEncErrors = 0;
+
+ vpx_codec_iter_t iter = NULL;
+ vrc = VERR_NO_DATA;
+ for (;;)
+ {
+ const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
+ if (!pPkt)
+ break;
+
+ switch (pPkt->kind)
+ {
+ case VPX_CODEC_CX_FRAME_PKT:
+ {
+ /* Calculate the absolute PTS of this frame (in ms). */
+ uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000
+ * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den;
+
+ const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
+
+ uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE;
+ if (fKeyframe)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY;
+ if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
+ tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ LogFunc(("Unexpected video packet type %ld\n", pPkt->kind));
+ break;
+ }
+ }
+
+ return vrc;
+}
+#endif /* VBOX_WITH_LIBVPX */
+
+
+/*********************************************************************************************************************************
+* Ogg Vorbis codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVORBIS
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
+ oggpack_buffer b;
+ oggpack_writeinit(&b);
+
+ vorbis_info_init(&pCodec->Audio.Vorbis.info);
+
+ int vorbis_rc;
+ if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */
+ vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info,
+ PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */);
+ else
+ vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n",
+ pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Initialize the analysis state and encoding storage. */
+ vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */
+ pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis;
+
+ vorbis_block_clear(&pVorbis->block_cur);
+ vorbis_dsp_clear (&pVorbis->dsp_state);
+ vorbis_info_clear (&pVorbis->info);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ Assert (pCodec->Parms.cbFrame);
+ AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
+ Assert (pFrame->Audio.cbBuf);
+ AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
+ int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame);
+
+ /* Write non-interleaved frames. */
+ float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ int16_t *puSrc = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc);
+
+ /* Convert samples into floating point. */
+ /** @todo This is sloooooooooooow! Optimize this! */
+ uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps);
+ AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED);
+
+ float const div = 1.0f / 32768.0f;
+
+ for(int f = 0; f < cFrames; f++)
+ {
+ buffer[0][f] = (float)puSrc[0] * div;
+ buffer[1][f] = (float)puSrc[1] * div;
+ puSrc += cChannels;
+ }
+
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcEncoded)
+ *pcEncoded = 0;
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+
+ size_t cBlocksEncoded = 0;
+ size_t cBytesEncoded = 0;
+
+ uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
+
+ while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
+ {
+ vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ /* Vorbis expects us to flush packets one at a time directly to the container.
+ *
+ * If we flush more than one packet in a row, players can't decode this then. */
+ ogg_packet op;
+ while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
+ {
+ cBytesEncoded += op.bytes;
+ AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
+ cBlocksEncoded++;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
+ RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
+ pCodec->Callbacks.pvUser);
+ }
+
+ RT_NOREF(puDst);
+
+ /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+ }
+
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+ if (pcEncoded)
+ *pcEncoded = 0;
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding Vorbis audio data failed, vrc=%Rrc\n", vrc));
+
+ Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
+ pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
+{
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_WITH_LIBVORBIS */
+
+
+/*********************************************************************************************************************************
+* Codec API *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ com::Utf8Str strCodec;
+ settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
+ LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ PDMAudioPropsInit(pPCMProps,
+ Settings.Audio.cBits / 8,
+ true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
+ pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if (RT_SUCCESS(vrc))
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(PDMAudioPropsAreValid(pPCMProps));
+
+ uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
+
+ LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
+ PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
+ LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
+
+ if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
+ pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
+
+ pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
+ pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
+
+ LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
+ PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
+ }
+ else
+ LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Initializes a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ com::Utf8Str strTemp;
+ settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
+ LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
+
+ pCodec->Parms.uBitrate = Settings.Video.ulRate;
+ pCodec->Parms.Video.uFPS = Settings.Video.ulFPS;
+ pCodec->Parms.Video.uWidth = Settings.Video.ulWidth;
+ pCodec->Parms.Video.uHeight = Settings.Video.ulHeight;
+ pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS;
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */
+
+ AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if ( RT_SUCCESS(vrc)
+ && pCodec->Ops.pfnInit)
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
+ }
+ else
+ LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Lets an audio codec parse advanced options given from a string.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to parse options for.
+ * @param strOptions Options string to parse.
+ */
+static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
+ }
+ else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
+ {
+ /* Stay with the defaults. */
+ }
+ else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
+ }
+ }
+ else
+ LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+#endif
+
+static void recordingCodecReset(PRECORDINGCODEC pCodec)
+{
+ pCodec->State.tsLastWrittenMs = 0;
+
+ pCodec->State.cEncErrors = 0;
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks = 0;
+ pCodec->STAM.msEncTotal = 0;
+#endif
+}
+
+/**
+ * Common code for codec creation.
+ *
+ * @param pCodec Codec instance to create.
+ */
+static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
+{
+ RT_ZERO(pCodec->Ops);
+ RT_ZERO(pCodec->Callbacks);
+}
+
+/**
+ * Creates an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmAudioCodec Audio codec to create.
+ */
+int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmAudioCodec)
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ case RecordingAudioCodec_OggVorbis:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVorbisInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
+ pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVORBIS */
+
+ default:
+ LogRel(("Recording: Selected codec is not supported!\n"));
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
+ pCodec->Parms.enmAudioCodec = enmAudioCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Creates a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmVideoCodec Video codec to create.
+ */
+int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmVideoCodec)
+ {
+# ifdef VBOX_WITH_LIBVPX
+ case RecordingVideoCodec_VP8:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVPXInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVPX */
+
+ default:
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = enmVideoCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Initializes a codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to initialize.
+ * @param pCallbacks Codec callback table to use. Optional and may be NULL.
+ * @param Settings Settings to use for initializing the codec.
+ */
+int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ recordingCodecReset(pCodec);
+
+ int vrc;
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ return vrc;
+}
+
+/**
+ * Destroys an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys the codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+int recordingCodecDestroy(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecDestroyAudio(pCodec);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc =recordingCodecDestroyVideo(pCodec);
+ else
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pCodec->pvScratch)
+ {
+ Assert(pCodec->cbScratch);
+ RTMemFree(pCodec->pvScratch);
+ pCodec->pvScratch = NULL;
+ pCodec->cbScratch = 0;
+ }
+
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
+ }
+
+ return vrc;
+}
+
+/**
+ * Feeds the codec encoder with data to encode.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to use.
+ * @param pFrame Pointer to frame data to encode.
+ * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional.
+ * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional.
+ */
+int recordingCodecEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
+
+ size_t cEncoded, cbEncoded;
+ int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded);
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
+
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks += cEncoded;
+ pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded;
+#endif
+ if (pcEncoded)
+ *pcEncoded = cEncoded;
+ if (pcbEncoded)
+ *pcbEncoded = cbEncoded;
+ }
+
+ return vrc;
+}
+
+/**
+ * Tells the codec that has to finalize the stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to finalize stream for.
+ */
+int recordingCodecFinalize(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Ops.pfnFinalize)
+ return pCodec->Ops.pfnFinalize(pCodec);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns whether the codec has been initialized or not.
+ *
+ * @returns @c true if initialized, or @c false if not.
+ * @param pCodec Codec to return initialization status for.
+ */
+bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
+{
+ return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
+}
+
+/**
+ * Returns the number of writable bytes for a given timestamp.
+ *
+ * This basically is a helper function to respect the set frames per second (FPS).
+ *
+ * @returns Number of writable bytes.
+ * @param pCodec Codec to return number of writable bytes for.
+ * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
+ */
+uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
+{
+ Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
+ msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs));
+
+ if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
+ return 0; /* Too early for writing (respect set FPS). */
+
+ /* For now we just return the complete frame space. */
+ AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
+ return pCodec->Parms.cbFrame;
+}
diff --git a/src/VBox/Main/src-client/RecordingInternals.cpp b/src/VBox/Main/src-client/RecordingInternals.cpp
new file mode 100644
index 00000000..9c407018
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingInternals.cpp
@@ -0,0 +1,158 @@
+/* $Id: RecordingInternals.cpp $ */
+/** @file
+ * Recording internals code.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "RecordingInternals.h"
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+
+/**
+ * Initializes a recording frame.
+ *
+ * @param pFrame Pointer to video frame to initialize.
+ * @param w Width (in pixel) of video frame.
+ * @param h Height (in pixel) of video frame.
+ * @param uBPP Bits per pixel (BPP).
+ * @param enmPixelFmt Pixel format to use.
+ */
+int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt)
+{
+ /* Calculate bytes per pixel and set pixel format. */
+ const unsigned uBytesPerPixel = uBPP / 8;
+ const size_t cbRGBBuf = w * h * uBytesPerPixel;
+ AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
+
+ pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+ AssertPtrReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY);
+ pFrame->cbRGBBuf = cbRGBBuf;
+
+ pFrame->uX = 0;
+ pFrame->uY = 0;
+ pFrame->uWidth = w;
+ pFrame->uHeight = h;
+ pFrame->enmPixelFmt = enmPixelFmt;
+ pFrame->uBPP = uBPP;
+ pFrame->uBytesPerLine = w * uBytesPerPixel;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a recording audio frame.
+ *
+ * @param pFrame Pointer to audio frame to destroy.
+ */
+static void recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame)
+{
+ if (pFrame->pvBuf)
+ {
+ Assert(pFrame->cbBuf);
+ RTMemFree(pFrame->pvBuf);
+ pFrame->cbBuf = 0;
+ }
+}
+
+/**
+ * Frees a previously allocated recording audio frame.
+ *
+ * @param pFrame Audio frame to free. The pointer will be invalid after return.
+ */
+void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ recordingAudioFrameDestroy(pFrame);
+
+ RTMemFree(pFrame);
+ pFrame = NULL;
+}
+
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+/**
+ * Destroys a recording video frame.
+ *
+ * @param pFrame Pointer to video frame to destroy.
+ */
+void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame)
+{
+ if (pFrame->pu8RGBBuf)
+ {
+ Assert(pFrame->cbRGBBuf);
+ RTMemFree(pFrame->pu8RGBBuf);
+ pFrame->cbRGBBuf = 0;
+ }
+}
+
+/**
+ * Frees a recording video frame.
+ *
+ * @param pFrame Pointer to video frame to free. The pointer will be invalid after return.
+ */
+void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ RecordingVideoFrameDestroy(pFrame);
+
+ RTMemFree(pFrame);
+}
+
+/**
+ * Frees a recording frame.
+ *
+ * @param pFrame Pointer to recording frame to free. The pointer will be invalid after return.
+ */
+void RecordingFrameFree(PRECORDINGFRAME pFrame)
+{
+ if (!pFrame)
+ return;
+
+ switch (pFrame->enmType)
+ {
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ case RECORDINGFRAME_TYPE_AUDIO:
+ recordingAudioFrameDestroy(&pFrame->Audio);
+ break;
+#endif
+ case RECORDINGFRAME_TYPE_VIDEO:
+ RecordingVideoFrameDestroy(&pFrame->Video);
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ RTMemFree(pFrame);
+ pFrame = NULL;
+}
+
diff --git a/src/VBox/Main/src-client/RecordingStream.cpp b/src/VBox/Main/src-client/RecordingStream.cpp
new file mode 100644
index 00000000..dad3e5c5
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingStream.cpp
@@ -0,0 +1,1039 @@
+/* $Id: RecordingStream.cpp $ */
+/** @file
+ * Recording stream code.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <iprt/path.h>
+
+#ifdef VBOX_RECORDING_DUMP
+# include <iprt/formats/bmp.h>
+#endif
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include <VBox/vmm/pdmaudioinline.h>
+#endif
+
+#include "Recording.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+
+RecordingStream::RecordingStream(RecordingContext *a_pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+ : m_enmState(RECORDINGSTREAMSTATE_UNINITIALIZED)
+{
+ int vrc2 = initInternal(a_pCtx, uScreen, Settings);
+ if (RT_FAILURE(vrc2))
+ throw vrc2;
+}
+
+RecordingStream::~RecordingStream(void)
+{
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+}
+
+/**
+ * Opens a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Recording settings to use.
+ */
+int RecordingStream::open(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ Assert(screenSettings.enmDest != RecordingDestination_None);
+
+ int vrc;
+
+ switch (screenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(screenSettings.File.strName.isNotEmpty());
+
+ const char *pszFile = screenSettings.File.strName.c_str();
+
+ RTFILE hFile = NIL_RTFILE;
+ vrc = RTFileOpen(&hFile, pszFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel2(("Recording: Opened file '%s'\n", pszFile));
+
+ try
+ {
+ Assert(File.m_pWEBM == NULL);
+ File.m_pWEBM = new WebMWriter();
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ this->File.m_hFile = hFile;
+ m_ScreenSettings.File.strName = pszFile;
+ }
+ }
+ else
+ LogRel(("Recording: Failed to open file '%s' for screen %RU32, vrc=%Rrc\n",
+ pszFile ? pszFile : "<Unnamed>", m_uScreenID, vrc));
+
+ if (RT_FAILURE(vrc))
+ {
+ if (hFile != NIL_RTFILE)
+ RTFileClose(hFile);
+ }
+
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Returns the recording stream's used configuration.
+ *
+ * @returns The recording stream's used configuration.
+ */
+const settings::RecordingScreenSettings &RecordingStream::GetConfig(void) const
+{
+ return m_ScreenSettings;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached, internal version.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::isLimitReachedInternal(uint64_t msTimestamp) const
+{
+ LogFlowThisFunc(("msTimestamp=%RU64, ulMaxTimeS=%RU32, tsStartMs=%RU64\n",
+ msTimestamp, m_ScreenSettings.ulMaxTimeS, m_tsStartMs));
+
+ if ( m_ScreenSettings.ulMaxTimeS
+ && msTimestamp >= m_tsStartMs + (m_ScreenSettings.ulMaxTimeS * RT_MS_1SEC))
+ {
+ LogRel(("Recording: Time limit for stream #%RU16 has been reached (%RU32s)\n",
+ m_uScreenID, m_ScreenSettings.ulMaxTimeS));
+ return true;
+ }
+
+ if (m_ScreenSettings.enmDest == RecordingDestination_File)
+ {
+ if (m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ uint64_t sizeInMB = this->File.m_pWEBM->GetFileSize() / _1M;
+ if(sizeInMB >= m_ScreenSettings.File.ulMaxSizeMB)
+ {
+ LogRel(("Recording: File size limit for stream #%RU16 has been reached (%RU64MB)\n",
+ m_uScreenID, m_ScreenSettings.File.ulMaxSizeMB));
+ return true;
+ }
+ }
+
+ /* Check for available free disk space */
+ if ( this->File.m_pWEBM
+ && this->File.m_pWEBM->GetAvailableSpace() < 0x100000) /** @todo r=andy WTF? Fix this. */
+ {
+ LogRel(("Recording: Not enough free storage space available, stopping recording\n"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Internal iteration main loop.
+ * Does housekeeping and recording context notification.
+ *
+ * @returns VBox status code.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::iterateInternal(uint64_t msTimestamp)
+{
+ if (!m_fEnabled)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (isLimitReachedInternal(msTimestamp))
+ {
+ vrc = VINF_RECORDING_LIMIT_REACHED;
+ }
+ else
+ vrc = VINF_SUCCESS;
+
+ AssertPtr(m_pCtx);
+
+ switch (vrc)
+ {
+ case VINF_RECORDING_LIMIT_REACHED:
+ {
+ m_fEnabled = false;
+
+ int vrc2 = m_pCtx->OnLimitReached(m_uScreenID, VINF_SUCCESS /* vrc */);
+ AssertRC(vrc2);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Checks if a specified limit for a recording stream has been reached.
+ *
+ * @returns @c true if any limit has been reached, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms) to check for.
+ */
+bool RecordingStream::IsLimitReached(uint64_t msTimestamp) const
+{
+ if (!IsReady())
+ return true;
+
+ return isLimitReachedInternal(msTimestamp);
+}
+
+/**
+ * Returns whether a recording stream is ready (e.g. enabled and active) or not.
+ *
+ * @returns @c true if ready, @c false if not.
+ */
+bool RecordingStream::IsReady(void) const
+{
+ return m_fEnabled;
+}
+
+/**
+ * Returns if a recording stream needs to be fed with an update or not.
+ *
+ * @returns @c true if an update is needed, @c false if not.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+bool RecordingStream::NeedsUpdate(uint64_t msTimestamp) const
+{
+ return recordingCodecGetWritable((const PRECORDINGCODEC)&m_CodecVideo, msTimestamp) > 0;
+}
+
+/**
+ * Processes a recording stream.
+ * This function takes care of the actual encoding and writing of a certain stream.
+ * As this can be very CPU intensive, this function usually is called from a separate thread.
+ *
+ * @returns VBox status code.
+ * @param mapBlocksCommon Map of common block to process for this stream.
+ *
+ * @note Runs in recording thread.
+ */
+int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
+{
+ LogFlowFuncEnter();
+
+ lock();
+
+ if (!m_ScreenSettings.fEnabled)
+ {
+ unlock();
+ return VINF_SUCCESS;
+ }
+
+ int vrc = VINF_SUCCESS;
+
+ RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
+ while (itStreamBlocks != m_Blocks.Map.end())
+ {
+ uint64_t const msTimestamp = itStreamBlocks->first;
+ RecordingBlocks *pBlocks = itStreamBlocks->second;
+
+ AssertPtr(pBlocks);
+
+ while (!pBlocks->List.empty())
+ {
+ RecordingBlock *pBlock = pBlocks->List.front();
+ AssertPtr(pBlock);
+
+ switch (pBlock->enmType)
+ {
+ case RECORDINGBLOCKTYPE_VIDEO:
+ {
+ RECORDINGFRAME Frame;
+ Frame.VideoPtr = (PRECORDINGVIDEOFRAME)pBlock->pvData;
+ Frame.msTimestamp = msTimestamp;
+
+ int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+
+ break;
+ }
+
+ default:
+ /* Note: Audio data already is encoded. */
+ break;
+ }
+
+ pBlocks->List.pop_front();
+ delete pBlock;
+ }
+
+ Assert(pBlocks->List.empty());
+ delete pBlocks;
+
+ m_Blocks.Map.erase(itStreamBlocks);
+ itStreamBlocks = m_Blocks.Map.begin();
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* Do we need to multiplex the common audio data to this stream? */
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Audio))
+ {
+ /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
+ * written to the screen's assigned recording stream. */
+ RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
+ while (itCommonBlocks != mapBlocksCommon.end())
+ {
+ RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
+ while (itBlock != itCommonBlocks->second->List.end())
+ {
+ RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
+ switch (pBlockCommon->enmType)
+ {
+ case RECORDINGBLOCKTYPE_AUDIO:
+ {
+ PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
+ AssertPtr(pAudioFrame);
+ AssertPtr(pAudioFrame->pvBuf);
+ Assert(pAudioFrame->cbBuf);
+
+ AssertPtr(this->File.m_pWEBM);
+ int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
+ AssertRC(vrc2);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ Assert(pBlockCommon->cRefs);
+ pBlockCommon->cRefs--;
+ if (pBlockCommon->cRefs == 0)
+ {
+ itCommonBlocks->second->List.erase(itBlock);
+ delete pBlockCommon;
+ itBlock = itCommonBlocks->second->List.begin();
+ }
+ else
+ ++itBlock;
+ }
+
+ /* If no entries are left over in the block map, remove it altogether. */
+ if (itCommonBlocks->second->List.empty())
+ {
+ delete itCommonBlocks->second;
+ mapBlocksCommon.erase(itCommonBlocks);
+ itCommonBlocks = mapBlocksCommon.begin();
+ }
+ else
+ ++itCommonBlocks;
+
+ LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
+ }
+ }
+#else
+ RT_NOREF(mapBlocksCommon);
+#endif /* VBOX_WITH_AUDIO_RECORDING */
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) audio frame to the recording stream.
+ *
+ * @returns VBox status code.
+ * @param pvData Pointer to audio data.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
+
+ /* As audio data is common across all streams, re-route this to the recording context, where
+ * the data is being encoded and stored in the common blocks queue. */
+ return m_pCtx->SendAudioFrame(pvData, cbData, msTimestamp);
+}
+
+/**
+ * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
+ *
+ * @returns VBox status code. Will return VINF_RECORDING_LIMIT_REACHED if the stream's recording
+ * limit has been reached or VINF_RECORDING_THROTTLED if the frame is too early for the current
+ * FPS setting.
+ * @param x Upper left (X) coordinate where the video frame starts.
+ * @param y Upper left (Y) coordinate where the video frame starts.
+ * @param uPixelFormat Pixel format of the video frame.
+ * @param uBPP Bits per pixel (BPP) of the video frame.
+ * @param uBytesPerLine Bytes per line of the video frame.
+ * @param uSrcWidth Width (in pixels) of the video frame.
+ * @param uSrcHeight Height (in pixels) of the video frame.
+ * @param puSrcData Actual pixel data of the video frame.
+ * @param msTimestamp Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
+ uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
+{
+ AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+ AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
+
+ lock();
+
+ Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
+
+ PRECORDINGVIDEOFRAME pFrame = NULL;
+
+ int vrc = iterateInternal(msTimestamp);
+ if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
+ {
+ unlock();
+ return vrc;
+ }
+
+ do
+ {
+ int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
+ uint32_t w = uSrcWidth;
+ if ((int)w + xDiff + (int)x <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destX;
+ if ((int)x < -xDiff)
+ {
+ w += xDiff + x;
+ x = -xDiff;
+ destX = 0;
+ }
+ else
+ destX = x + xDiff;
+
+ uint32_t h = uSrcHeight;
+ int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
+ if ((int)h + yDiff + (int)y <= 0) /* Nothing visible. */
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t destY;
+ if ((int)y < -yDiff)
+ {
+ h += yDiff + (int)y;
+ y = -yDiff;
+ destY = 0;
+ }
+ else
+ destY = y + yDiff;
+
+ if ( destX > m_ScreenSettings.Video.ulWidth
+ || destY > m_ScreenSettings.Video.ulHeight)
+ {
+ vrc = VERR_INVALID_PARAMETER; /* Nothing visible. */
+ break;
+ }
+
+ if (destX + w > m_ScreenSettings.Video.ulWidth)
+ w = m_ScreenSettings.Video.ulWidth - destX;
+
+ if (destY + h > m_ScreenSettings.Video.ulHeight)
+ h = m_ScreenSettings.Video.ulHeight - destY;
+
+ pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
+ AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
+
+ /* Calculate bytes per pixel and set pixel format. */
+ const unsigned uBytesPerPixel = uBPP / 8;
+ if (uPixelFormat == BitmapFormat_BGR)
+ {
+ switch (uBPP)
+ {
+ case 32:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
+ break;
+ case 24:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
+ break;
+ case 16:
+ pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
+ break;
+ default:
+ AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
+ break;
+ }
+ }
+ else
+ AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
+
+ const size_t cbRGBBuf = m_ScreenSettings.Video.ulWidth
+ * m_ScreenSettings.Video.ulHeight
+ * uBytesPerPixel;
+ AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
+
+ pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+ AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
+ pFrame->cbRGBBuf = cbRGBBuf;
+ pFrame->uWidth = uSrcWidth;
+ pFrame->uHeight = uSrcHeight;
+
+ /* If the current video frame is smaller than video resolution we're going to encode,
+ * clear the frame beforehand to prevent artifacts. */
+ if ( uSrcWidth < m_ScreenSettings.Video.ulWidth
+ || uSrcHeight < m_ScreenSettings.Video.ulHeight)
+ {
+ RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
+ }
+
+ /* Calculate start offset in source and destination buffers. */
+ uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
+ uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
+
+#ifdef VBOX_RECORDING_DUMP
+ BMPFILEHDR fileHdr;
+ RT_ZERO(fileHdr);
+
+ BMPWIN3XINFOHDR coreHdr;
+ RT_ZERO(coreHdr);
+
+ fileHdr.uType = BMP_HDR_MAGIC;
+ fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
+ fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
+
+ coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
+ coreHdr.uWidth = w;
+ coreHdr.uHeight = h;
+ coreHdr.cPlanes = 1;
+ coreHdr.cBits = uBPP;
+ coreHdr.uXPelsPerMeter = 5000;
+ coreHdr.uYPelsPerMeter = 5000;
+
+ char szFileName[RTPATH_MAX];
+ RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
+
+ RTFILE fh;
+ int vrc2 = RTFileOpen(&fh, szFileName,
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc2))
+ {
+ RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
+ RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
+ }
+#endif
+ Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
+
+ /* Do the copy. */
+ for (unsigned int i = 0; i < h; i++)
+ {
+ /* Overflow check. */
+ Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
+ Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
+
+ memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(rc2))
+ RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
+#endif
+ offSrc += uBytesPerLine;
+ offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
+ }
+
+#ifdef VBOX_RECORDING_DUMP
+ if (RT_SUCCESS(vrc2))
+ RTFileClose(fh);
+#endif
+
+ } while (0);
+
+ if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
+ {
+ RecordingBlock *pBlock = new RecordingBlock();
+ if (pBlock)
+ {
+ AssertPtr(pFrame);
+
+ pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
+ pBlock->pvData = pFrame;
+ pBlock->cbData = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
+
+ try
+ {
+ RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
+ pRecordingBlocks->List.push_back(pBlock);
+
+ Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
+ m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+ }
+ catch (const std::exception &ex)
+ {
+ RT_NOREF(ex);
+
+ delete pBlock;
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ RecordingVideoFrameFree(pFrame);
+
+ unlock();
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Initializes a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param Settings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::Init(RecordingContext *pCtx, uint32_t uScreen, const settings::RecordingScreenSettings &Settings)
+{
+ return initInternal(pCtx, uScreen, Settings);
+}
+
+/**
+ * Initializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Pointer to recording context.
+ * @param uScreen Screen number to use for this recording stream.
+ * @param screenSettings Recording screen configuration to use for initialization.
+ */
+int RecordingStream::initInternal(RecordingContext *pCtx, uint32_t uScreen,
+ const settings::RecordingScreenSettings &screenSettings)
+{
+ AssertReturn(m_enmState == RECORDINGSTREAMSTATE_UNINITIALIZED, VERR_WRONG_ORDER);
+
+ m_pCtx = pCtx;
+ m_uTrackAudio = UINT8_MAX;
+ m_uTrackVideo = UINT8_MAX;
+ m_tsStartMs = 0;
+ m_uScreenID = uScreen;
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ /* We use the codec from the recording context, as this stream only receives multiplexed data (same audio for all streams). */
+ m_pCodecAudio = m_pCtx->GetCodecAudio();
+#endif
+ m_ScreenSettings = screenSettings;
+
+ settings::RecordingScreenSettings *pSettings = &m_ScreenSettings;
+
+ int vrc = RTCritSectInit(&m_CritSect);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ this->File.m_pWEBM = NULL;
+ this->File.m_hFile = NIL_RTFILE;
+
+ vrc = open(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ const bool fVideoEnabled = pSettings->isFeatureEnabled(RecordingFeature_Video);
+ const bool fAudioEnabled = pSettings->isFeatureEnabled(RecordingFeature_Audio);
+
+ if (fVideoEnabled)
+ {
+ vrc = initVideo(*pSettings);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ switch (pSettings->enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ Assert(pSettings->File.strName.isNotEmpty());
+ const char *pszFile = pSettings->File.strName.c_str();
+
+ AssertPtr(File.m_pWEBM);
+ vrc = File.m_pWEBM->OpenEx(pszFile, &this->File.m_hFile,
+ fAudioEnabled ? pSettings->Audio.enmCodec : RecordingAudioCodec_None,
+ fVideoEnabled ? pSettings->Video.enmCodec : RecordingVideoCodec_None);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to create output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ if (fVideoEnabled)
+ {
+ vrc = this->File.m_pWEBM->AddVideoTrack(&m_CodecVideo,
+ pSettings->Video.ulWidth, pSettings->Video.ulHeight, pSettings->Video.ulFPS,
+ &m_uTrackVideo);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
+ m_uScreenID, pSettings->Video.ulWidth, pSettings->Video.ulHeight,
+ pSettings->Video.ulRate, pSettings->Video.ulFPS, m_uTrackVideo));
+ }
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ AssertPtr(m_pCodecAudio);
+ vrc = this->File.m_pWEBM->AddAudioTrack(m_pCodecAudio,
+ pSettings->Audio.uHz, pSettings->Audio.cChannels, pSettings->Audio.cBits,
+ &m_uTrackAudio);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, vrc));
+ break;
+ }
+
+ LogRel(("Recording: Recording audio of screen #%u in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
+ m_uScreenID, pSettings->Audio.uHz, pSettings->Audio.cBits, pSettings->Audio.cChannels,
+ pSettings->Audio.cChannels ? "channels" : "channel", m_uTrackAudio));
+ }
+#endif
+
+ if ( fVideoEnabled
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ || fAudioEnabled
+#endif
+ )
+ {
+ char szWhat[32] = { 0 };
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), "video");
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ if (fAudioEnabled)
+ {
+ if (fVideoEnabled)
+ RTStrCat(szWhat, sizeof(szWhat), " + ");
+ RTStrCat(szWhat, sizeof(szWhat), "audio");
+ }
+#endif
+ LogRel(("Recording: Recording %s of screen #%u to '%s'\n", szWhat, m_uScreenID, pszFile));
+ }
+
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ m_enmState = RECORDINGSTREAMSTATE_INITIALIZED;
+ m_fEnabled = true;
+ m_tsStartMs = RTTimeProgramMilliTS();
+
+ return VINF_SUCCESS;
+ }
+
+ int vrc2 = uninitInternal();
+ AssertRC(vrc2);
+
+ LogRel(("Recording: Stream #%RU32 initialization failed with %Rrc\n", uScreen, vrc));
+ return vrc;
+}
+
+/**
+ * Closes a recording stream.
+ * Depending on the stream's recording destination, this function closes all associated handles
+ * and finalizes recording.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::close(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (this->File.m_pWEBM)
+ vrc = this->File.m_pWEBM->Close();
+ break;
+ }
+
+ default:
+ AssertFailed(); /* Should never happen. */
+ break;
+ }
+
+ m_Blocks.Clear();
+
+ LogRel(("Recording: Recording screen #%u stopped\n", m_uScreenID));
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("Recording: Error stopping recording screen #%u, vrc=%Rrc\n", m_uScreenID, vrc));
+ return vrc;
+ }
+
+ switch (m_ScreenSettings.enmDest)
+ {
+ case RecordingDestination_File:
+ {
+ if (RTFileIsValid(this->File.m_hFile))
+ {
+ vrc = RTFileClose(this->File.m_hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("Recording: Closed file '%s'\n", m_ScreenSettings.File.strName.c_str()));
+ }
+ else
+ {
+ LogRel(("Recording: Error closing file '%s', vrc=%Rrc\n", m_ScreenSettings.File.strName.c_str(), vrc));
+ break;
+ }
+ }
+
+ WebMWriter *pWebMWriter = this->File.m_pWEBM;
+ AssertPtr(pWebMWriter);
+
+ if (pWebMWriter)
+ {
+ /* If no clusters (= data) was written, delete the file again. */
+ if (pWebMWriter->GetClusters() == 0)
+ {
+ int vrc2 = RTFileDelete(m_ScreenSettings.File.strName.c_str());
+ AssertRC(vrc2); /* Ignore vrc on non-debug builds. */
+ }
+
+ delete pWebMWriter;
+ pWebMWriter = NULL;
+
+ this->File.m_pWEBM = NULL;
+ }
+ break;
+ }
+
+ default:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Uninitializes a recording stream.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::Uninit(void)
+{
+ return uninitInternal();
+}
+
+/**
+ * Uninitializes a recording stream, internal version.
+ *
+ * @returns VBox status code.
+ */
+int RecordingStream::uninitInternal(void)
+{
+ if (m_enmState != RECORDINGSTREAMSTATE_INITIALIZED)
+ return VINF_SUCCESS;
+
+ int vrc = close();
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ m_pCodecAudio = NULL;
+#endif
+
+ if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
+ {
+ vrc = recordingCodecFinalize(&m_CodecVideo);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecDestroy(&m_CodecVideo);
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectDelete(&m_CritSect);
+
+ m_enmState = RECORDINGSTREAMSTATE_UNINITIALIZED;
+ m_fEnabled = false;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes encoded data to a WebM file instance.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ */
+int RecordingStream::codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags)
+{
+ AssertPtr(this->File.m_pWEBM);
+ AssertPtr(pvData);
+ Assert (cbData);
+
+ WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
+ if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
+ {
+ /* All set. */
+ }
+ else
+ {
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_KEY)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
+ if (uFlags & RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE)
+ blockFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
+ }
+
+ return this->File.m_pWEBM->WriteBlock( pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+ ? m_uTrackAudio : m_uTrackVideo,
+ pvData, cbData, msAbsPTS, blockFlags);
+}
+
+/**
+ * Codec callback for writing encoded data to a recording stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec which has encoded the data.
+ * @param pvData Encoded data to write.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param msAbsPTS Absolute PTS (in ms) of written data.
+ * @param uFlags Encoding flags of type RECORDINGCODEC_ENC_F_XXX.
+ * @param pvUser User-supplied pointer.
+ */
+/* static */
+DECLCALLBACK(int) RecordingStream::codecWriteDataCallback(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData,
+ uint64_t msAbsPTS, uint32_t uFlags, void *pvUser)
+{
+ RecordingStream *pThis = (RecordingStream *)pvUser;
+ AssertPtr(pThis);
+
+ /** @todo For now this is hardcoded to always write to a WebM file. Add other stuff later. */
+ return pThis->codecWriteToWebM(pCodec, pvData, cbData, msAbsPTS, uFlags);
+}
+
+/**
+ * Initializes the video recording for a recording stream.
+ *
+ * @returns VBox status code.
+ * @param screenSettings Screen settings to use.
+ */
+int RecordingStream::initVideo(const settings::RecordingScreenSettings &screenSettings)
+{
+ /* Sanity. */
+ AssertReturn(screenSettings.Video.ulRate, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(screenSettings.Video.ulFPS, VERR_INVALID_PARAMETER);
+
+ PRECORDINGCODEC pCodec = &m_CodecVideo;
+
+ RECORDINGCODECCALLBACKS Callbacks;
+ Callbacks.pvUser = this;
+ Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
+
+ int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Initializing video codec failed with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Locks a recording stream.
+ */
+void RecordingStream::lock(void)
+{
+ int vrc = RTCritSectEnter(&m_CritSect);
+ AssertRC(vrc);
+}
+
+/**
+ * Unlocks a locked recording stream.
+ */
+void RecordingStream::unlock(void)
+{
+ int vrc = RTCritSectLeave(&m_CritSect);
+ AssertRC(vrc);
+}
+
diff --git a/src/VBox/Main/src-client/RecordingUtils.cpp b/src/VBox/Main/src-client/RecordingUtils.cpp
new file mode 100644
index 00000000..f571cb4b
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingUtils.cpp
@@ -0,0 +1,290 @@
+/* $Id: RecordingUtils.cpp $ */
+/** @file
+ * Recording utility code.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "Recording.h"
+#include "RecordingUtils.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#ifdef DEBUG
+#include <iprt/file.h>
+#include <iprt/formats/bmp.h>
+#endif
+
+
+/**
+ * Convert an image to YUV420p format.
+ *
+ * @return \c true on success, \c false on failure.
+ * @param aDstBuf The destination image buffer.
+ * @param aDstWidth Width (in pixel) of destination buffer.
+ * @param aDstHeight Height (in pixel) of destination buffer.
+ * @param aSrcBuf The source image buffer.
+ * @param aSrcWidth Width (in pixel) of source buffer.
+ * @param aSrcHeight Height (in pixel) of source buffer.
+ */
+template <class T>
+inline bool recordingUtilsColorConvWriteYUV420p(uint8_t *aDstBuf, unsigned aDstWidth, unsigned aDstHeight,
+ uint8_t *aSrcBuf, unsigned aSrcWidth, unsigned aSrcHeight)
+{
+ RT_NOREF(aDstWidth, aDstHeight);
+
+ AssertReturn(!(aSrcWidth & 1), false);
+ AssertReturn(!(aSrcHeight & 1), false);
+
+ bool fRc = true;
+ T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
+ T iter2 = iter1;
+ iter2.skip(aSrcWidth);
+ unsigned cPixels = aSrcWidth * aSrcHeight;
+ unsigned offY = 0;
+ unsigned offU = cPixels;
+ unsigned offV = cPixels + cPixels / 4;
+ unsigned const cyHalf = aSrcHeight / 2;
+ unsigned const cxHalf = aSrcWidth / 2;
+ for (unsigned i = 0; i < cyHalf && fRc; ++i)
+ {
+ for (unsigned j = 0; j < cxHalf; ++j)
+ {
+ unsigned red, green, blue;
+ fRc = iter1.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ unsigned v = (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter1.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter2.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ fRc = iter2.getRGB(&red, &green, &blue);
+ AssertReturn(fRc, false);
+ aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
+ u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
+ v += (((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128) / 4;
+
+ aDstBuf[offU] = u;
+ aDstBuf[offV] = v;
+ offY += 2;
+ ++offU;
+ ++offV;
+ }
+
+ iter1.skip(aSrcWidth);
+ iter2.skip(aSrcWidth);
+ offY += aSrcWidth;
+ }
+
+ return true;
+}
+
+/**
+ * Convert an image to RGB24 format.
+ *
+ * @returns true on success, false on failure.
+ * @param aWidth Width of image.
+ * @param aHeight Height of image.
+ * @param aDestBuf An allocated memory buffer large enough to hold the
+ * destination image (i.e. width * height * 12bits).
+ * @param aSrcBuf The source image as an array of bytes.
+ */
+template <class T>
+inline bool RecordingUtilsColorConvWriteRGB24(unsigned aWidth, unsigned aHeight,
+ uint8_t *aDestBuf, uint8_t *aSrcBuf)
+{
+ enum { PIX_SIZE = 3 };
+ bool fRc = true;
+ AssertReturn(0 == (aWidth & 1), false);
+ AssertReturn(0 == (aHeight & 1), false);
+ T iter(aWidth, aHeight, aSrcBuf);
+ unsigned cPixels = aWidth * aHeight;
+ for (unsigned i = 0; i < cPixels && fRc; ++i)
+ {
+ unsigned red, green, blue;
+ fRc = iter.getRGB(&red, &green, &blue);
+ if (fRc)
+ {
+ aDestBuf[i * PIX_SIZE ] = red;
+ aDestBuf[i * PIX_SIZE + 1] = green;
+ aDestBuf[i * PIX_SIZE + 2] = blue;
+ }
+ }
+ return fRc;
+}
+
+/**
+ * Converts a RGB to YUV buffer.
+ *
+ * @returns IPRT status code.
+ * @param enmPixelFormat Pixel format to use for conversion.
+ * @param paDst Pointer to destination buffer.
+ * @param uDstWidth Width (X, in pixels) of destination buffer.
+ * @param uDstHeight Height (Y, in pixels) of destination buffer.
+ * @param paSrc Pointer to source buffer.
+ * @param uSrcWidth Width (X, in pixels) of source buffer.
+ * @param uSrcHeight Height (Y, in pixels) of source buffer.
+ */
+int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat,
+ uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
+ uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
+{
+ switch (enmPixelFormat)
+ {
+ case RECORDINGPIXELFMT_RGB32:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ case RECORDINGPIXELFMT_RGB24:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ case RECORDINGPIXELFMT_RGB565:
+ if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
+ paSrc, uSrcWidth, uSrcHeight))
+ return VERR_INVALID_PARAMETER;
+ break;
+ default:
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+ return VINF_SUCCESS;
+}
+
+#ifdef DEBUG
+/**
+ * Dumps a video recording frame to a bitmap (BMP) file, extended version.
+ *
+ * @returns VBox status code.
+ * @param pu8RGBBuf Pointer to actual RGB frame data.
+ * @param cbRGBBuf Size (in bytes) of \a pu8RGBBuf.
+ * @param pszPath Absolute path to dump file to. Must exist.
+ * Specify NULL to use the system's temp directory.
+ * Existing frame files will be overwritten.
+ * @param pszPrefx Naming prefix to use. Optional and can be NULL.
+ * @param uWidth Width (in pixel) to write.
+ * @param uHeight Height (in pixel) to write.
+ * @param uBPP Bits in pixel.
+ */
+int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx,
+ uint16_t uWidth, uint32_t uHeight, uint8_t uBPP)
+{
+ RT_NOREF(cbRGBBuf);
+
+ const uint8_t uBytesPerPixel = uBPP / 8 /* Bits */;
+ const size_t cbData = uWidth * uHeight * uBytesPerPixel;
+
+ if (!cbData) /* No data to write? Bail out early. */
+ return VINF_SUCCESS;
+
+ BMPFILEHDR fileHdr;
+ RT_ZERO(fileHdr);
+
+ BMPWIN3XINFOHDR coreHdr;
+ RT_ZERO(coreHdr);
+
+ fileHdr.uType = BMP_HDR_MAGIC;
+ fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + cbData);
+ fileHdr.offBits = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
+
+ coreHdr.cbSize = sizeof(BMPWIN3XINFOHDR);
+ coreHdr.uWidth = uWidth ;
+ coreHdr.uHeight = uHeight;
+ coreHdr.cPlanes = 1;
+ coreHdr.cBits = uBPP;
+ coreHdr.uXPelsPerMeter = 5000;
+ coreHdr.uYPelsPerMeter = 5000;
+
+ static uint64_t s_iCount = 0;
+
+ char szPath[RTPATH_MAX];
+ if (!pszPath)
+ RTPathTemp(szPath, sizeof(szPath));
+
+ char szFileName[RTPATH_MAX];
+ if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s/RecDump-%04RU64-%s-w%RU16h%RU16.bmp",
+ pszPath ? pszPath : szPath, s_iCount, pszPrefx ? pszPrefx : "Frame", uWidth, uHeight) <= 0)
+ {
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ s_iCount++;
+
+ RTFILE fh;
+ int vrc = RTFileOpen(&fh, szFileName,
+ RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFileWrite(fh, &fileHdr, sizeof(fileHdr), NULL);
+ RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
+
+ /* Bitmaps (DIBs) are stored upside-down (thanks, OS/2), so work from the bottom up. */
+ uint32_t offSrc = (uHeight * uWidth * uBytesPerPixel) - uWidth * uBytesPerPixel;
+
+ /* Do the copy. */
+ for (unsigned int i = 0; i < uHeight; i++)
+ {
+ RTFileWrite(fh, pu8RGBBuf + offSrc, uWidth * uBytesPerPixel, NULL);
+ offSrc -= uWidth * uBytesPerPixel;
+ }
+
+ RTFileClose(fh);
+ }
+
+ return vrc;
+}
+
+/**
+ * Dumps a video recording frame to a bitmap (BMP) file.
+ *
+ * @returns VBox status code.
+ * @param pFrame Video frame to dump.
+ */
+int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame)
+{
+ AssertReturn(pFrame->enmType == RECORDINGFRAME_TYPE_VIDEO, VERR_INVALID_PARAMETER);
+ return RecordingUtilsDbgDumpFrameEx(pFrame->Video.pu8RGBBuf, pFrame->Video.cbRGBBuf,
+ NULL /* Use temp directory */, NULL /* pszPrefix */,
+ pFrame->Video.uWidth, pFrame->Video.uHeight, pFrame->Video.uBPP);
+}
+#endif /* DEBUG */
+
diff --git a/src/VBox/Main/src-client/RemoteUSBBackend.cpp b/src/VBox/Main/src-client/RemoteUSBBackend.cpp
new file mode 100644
index 00000000..0b858786
--- /dev/null
+++ b/src/VBox/Main/src-client/RemoteUSBBackend.cpp
@@ -0,0 +1,1416 @@
+/* $Id: RemoteUSBBackend.cpp $ */
+/** @file
+ * VirtualBox Remote USB backend
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_USB_REMOTE
+#include "LoggingNew.h"
+
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "RemoteUSBBackend.h"
+#include "RemoteUSBDeviceImpl.h"
+
+#include <VBox/RemoteDesktop/VRDE.h>
+#include <VBox/vrdpusb.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <VBox/vusb.h>
+
+#include <iprt/time.h>
+
+/** @page pg_vrdb_usb Async Remote USB
+ *
+ *
+ * USB backend functions are called in EMT so care must be taken to prevent
+ * delays in the functions execution.
+ *
+ * Among 11 backend functions 10 just return a success indicator.
+ *
+ * Such a function usually will check pending error code and if everything is ok,
+ * submit asynchronous RDP request and return success immediately.
+ *
+ * On actual completion of each request, the status will be saved as
+ * pending, so in case of an error all further functions will fail with
+ * device disconnected condition.
+ * @todo May be a device disconnect notification for console is required?
+ *
+ * The only remaining function that needs special processing is
+ * the reap_urb. It has a timeout parameter.
+ * Normally, the timeout is 0, as result of polling from VUSB frame timer.
+ * It is ok for async processing, the backend will periodically reap urbs from client.
+ * And already reaped URBs from client will be returned for the call.
+ * Exceptions:
+ * 1) during device initialization, when obtaining device descriptions
+ * the timeout is -1, and the request is expected to be processed synchronously.
+ * It looks like only 3 URBs with some information are retrieved that way.
+ * Probably, one can return this information in DEVICE_LIST together with the
+ * device description and when such request are submitted, just return
+ * the prefetched data.
+ * 2) during suspend timeout is non zero (10 or less milliseconds),
+ * and URB's are reaped for about 1 second. But here network delays
+ * will not affect the timeout, so it is ok.
+ *
+ *
+ * @section sub_vrdb_usb_dad Device attaching/detaching
+ *
+ * Devices are attached when client is connected or when a new device is connected to client.
+ * Devices are detached when client is disconnected (all devices) or a device is disconnected
+ * the client side.
+ *
+ * The backend polls the client for list of attached USB devices from RemoteUSBThread.
+ *
+ */
+
+/* Queued URB submitted to VRDP client. */
+typedef struct _REMOTEUSBQURB
+{
+ struct _REMOTEUSBQURB *next;
+ struct _REMOTEUSBQURB *prev;
+
+ PREMOTEUSBDEVICE pDevice; /* Device, the URB is queued for. */
+
+ uint32_t u32Handle; /* The handle of the URB. Generated by the Remote USB backend. */
+
+ void *pvData; /* Pointer to URB data allocated by VUSB. */
+ void *pvURB; /* Pointer to URB known to VUSB. */
+
+ uint32_t u32Len; /* Data length returned by the VRDP client. */
+ uint32_t u32Err; /* URB error code returned by the VRDP client. */
+
+ bool fCompleted; /* The URB has been returned back by VRDP client. */
+ bool fInput; /* This URB receives data from the client. */
+
+ uint32_t u32TransferredLen; /* For VRDE_USB_DIRECTION_OUT URBs = bytes written.
+ * For VRDE_USB_DIRECTION_IN URBs = bytes received.
+ */
+} REMOTEUSBQURB;
+
+/* Remote USB device instance data. */
+typedef struct _REMOTEUSBDEVICE
+{
+ struct _REMOTEUSBDEVICE *prev;
+ struct _REMOTEUSBDEVICE *next;
+
+ RemoteUSBBackend *pOwner;
+
+ VRDEUSBDEVID id; /* The remote identifier, assigned by client. */
+
+ uint32_t u32ClientId; /* The identifier of the remote client. */
+
+ REMOTEUSBQURB *pHeadQURBs; /* List of URBs queued for the device. */
+ REMOTEUSBQURB *pTailQURBs;
+
+ volatile uint32_t hURB; /* Source for URB's handles. */
+ bool fFailed; /* True if an operation has failed for the device. */
+ RTCRITSECT critsect; /* Protects the queued urb list. */
+ volatile bool fWokenUp; /* Flag whther the reaper was woken up. */
+} REMOTEUSBDEVICE;
+
+
+
+static void requestDevice(REMOTEUSBDEVICE *pDevice)
+{
+ int vrc = RTCritSectEnter(&pDevice->critsect);
+ AssertRC(vrc);
+}
+
+static void releaseDevice(REMOTEUSBDEVICE *pDevice)
+{
+ RTCritSectLeave(&pDevice->critsect);
+}
+
+static REMOTEUSBQURB *qurbAlloc(PREMOTEUSBDEVICE pDevice)
+{
+ /** @todo reuse URBs. */
+ REMOTEUSBQURB *pQURB = (REMOTEUSBQURB *)RTMemAllocZ (sizeof (REMOTEUSBQURB));
+
+ if (pQURB)
+ {
+ pQURB->pDevice = pDevice;
+ }
+
+ return pQURB;
+}
+
+static void qurbFree (REMOTEUSBQURB *pQURB)
+{
+ RTMemFree (pQURB);
+ return;
+}
+
+
+/* Called by VRDP server when the client responds to a request on USB channel. */
+DECLCALLBACK(int) USBClientResponseCallback(void *pv, uint32_t u32ClientId, uint8_t code, const void *pvRet, uint32_t cbRet)
+{
+ RT_NOREF(u32ClientId);
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("USBClientResponseCallback: id = %d, pv = %p, code = %d, pvRet = %p, cbRet = %d\n",
+ u32ClientId, pv, code, pvRet, cbRet));
+
+ RemoteUSBBackend *pThis = (RemoteUSBBackend *)pv;
+
+ switch (code)
+ {
+ case VRDE_USB_REQ_DEVICE_LIST:
+ {
+ vrc = pThis->saveDeviceList(pvRet, cbRet);
+ } break;
+
+ case VRDE_USB_REQ_NEGOTIATE:
+ {
+ if (pvRet && cbRet >= sizeof(VRDEUSBREQNEGOTIATERET))
+ {
+ VRDEUSBREQNEGOTIATERET *pret = (VRDEUSBREQNEGOTIATERET *)pvRet;
+
+ vrc = pThis->negotiateResponse(pret, cbRet);
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
+ pvRet, cbRet, sizeof(VRDEUSBREQNEGOTIATERET)));
+
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ } break;
+
+ case VRDE_USB_REQ_REAP_URB:
+ {
+ vrc = pThis->reapURB(pvRet, cbRet);
+
+ LogFlow(("USBClientResponseCallback: reap URB, vrc = %Rrc.\n", vrc));
+ } break;
+
+ case VRDE_USB_REQ_QUEUE_URB:
+ case VRDE_USB_REQ_CLOSE:
+ case VRDE_USB_REQ_CANCEL_URB:
+ {
+ /* Do nothing, actually this should not happen. */
+ Log(("USBClientResponseCallback: WARNING: response to a request %d is not expected!!!\n", code));
+ } break;
+
+ case VRDE_USB_REQ_OPEN:
+ case VRDE_USB_REQ_RESET:
+ case VRDE_USB_REQ_SET_CONFIG:
+ case VRDE_USB_REQ_CLAIM_INTERFACE:
+ case VRDE_USB_REQ_RELEASE_INTERFACE:
+ case VRDE_USB_REQ_INTERFACE_SETTING:
+ case VRDE_USB_REQ_CLEAR_HALTED_EP:
+ {
+ /*
+ * Device specific responses with status codes.
+ */
+ if (pvRet && cbRet >= sizeof(VRDEUSBREQRETHDR))
+ {
+ VRDEUSBREQRETHDR *pret = (VRDEUSBREQRETHDR *)pvRet;
+
+ if (pret->status != VRDE_USB_STATUS_SUCCESS)
+ {
+ REMOTEUSBDEVICE *pDevice = pThis->deviceFromId(pret->id);
+
+ if (!pDevice)
+ {
+ Log(("USBClientResponseCallback: WARNING: invalid device id %08X.\n", pret->id));
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: the operation failed, status %d\n", pret->status));
+ pDevice->fFailed = true;
+ }
+ }
+ }
+ else
+ {
+ Log(("USBClientResponseCallback: WARNING: not enough data in response: pv = %p, cb = %d, expected %d.\n",
+ pvRet, cbRet, sizeof(VRDEUSBREQRETHDR)));
+ }
+ } break;
+
+ default:
+ {
+ Log(("USBClientResponseCallback: WARNING: invalid code %d\n", code));
+ } break;
+ }
+
+ return vrc;
+}
+
+/*
+ * Backend entry points.
+ */
+static DECLCALLBACK(int) iface_Open(PREMOTEUSBBACKEND pInstance, const char *pszAddress,
+ size_t cbAddress, PREMOTEUSBDEVICE *ppDevice)
+{
+ RT_NOREF(cbAddress);
+ int vrc = VINF_SUCCESS;
+
+ RemoteUSBBackend *pThis = (RemoteUSBBackend *)pInstance;
+
+ REMOTEUSBDEVICE *pDevice = (REMOTEUSBDEVICE *)RTMemAllocZ(sizeof(REMOTEUSBDEVICE));
+
+ if (!pDevice)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Parse given address string to find the device identifier.
+ * The format is "REMOTEUSB0xAAAABBBB&0xCCCCDDDD", where AAAABBBB is hex device identifier
+ * and CCCCDDDD is hex client id.
+ */
+ if (strncmp(pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) != 0)
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ /* Initialize the device structure. */
+ pDevice->pOwner = pThis;
+ pDevice->fWokenUp = false;
+
+ vrc = RTCritSectInit(&pDevice->critsect);
+ AssertRC(vrc);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pDevice->id = RTStrToUInt32(&pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN]);
+
+ size_t l = strlen(pszAddress);
+
+ if (l >= REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678&0x87654321"))
+ {
+ const char *p = &pszAddress[REMOTE_USB_BACKEND_PREFIX_LEN + strlen("0x12345678")];
+ if (*p == '&')
+ {
+ pDevice->u32ClientId = RTStrToUInt32(p + 1);
+ }
+ else
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ VRDE_USB_REQ_OPEN_PARM parm;
+
+ parm.code = VRDE_USB_REQ_OPEN;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+ }
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppDevice = pDevice;
+
+ pThis->addDevice(pDevice);
+ }
+ else
+ {
+ RTMemFree(pDevice);
+ }
+
+ return vrc;
+}
+
+static DECLCALLBACK(void) iface_Close(PREMOTEUSBDEVICE pDevice)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_CLOSE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLOSE;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ pThis->removeDevice(pDevice);
+
+ if (RTCritSectIsInitialized(&pDevice->critsect))
+ {
+ RTCritSectDelete(&pDevice->critsect);
+ }
+
+ RTMemFree(pDevice);
+
+ return;
+}
+
+static DECLCALLBACK(int) iface_Reset(PREMOTEUSBDEVICE pDevice)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_RESET_PARM parm;
+
+ parm.code = VRDE_USB_REQ_RESET;
+ parm.id = pDevice->id;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_SetConfig(PREMOTEUSBDEVICE pDevice, uint8_t u8Cfg)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_SET_CONFIG_PARM parm;
+
+ parm.code = VRDE_USB_REQ_SET_CONFIG;
+ parm.id = pDevice->id;
+ parm.configuration = u8Cfg;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ClaimInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_CLAIM_INTERFACE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLAIM_INTERFACE;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ReleaseInterface(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_RELEASE_INTERFACE_PARM parm;
+
+ parm.code = VRDE_USB_REQ_RELEASE_INTERFACE;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_InterfaceSetting(PREMOTEUSBDEVICE pDevice, uint8_t u8Ifnum, uint8_t u8Setting)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_INTERFACE_SETTING_PARM parm;
+
+ parm.code = VRDE_USB_REQ_INTERFACE_SETTING;
+ parm.id = pDevice->id;
+ parm.iface = u8Ifnum;
+ parm.setting = u8Setting;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) iface_ClearHaltedEP(PREMOTEUSBDEVICE pDevice, uint8_t u8Ep)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VRDE_USB_REQ_CLEAR_HALTED_EP_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CLEAR_HALTED_EP;
+ parm.id = pDevice->id;
+ parm.ep = u8Ep;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(void) iface_CancelURB(PREMOTEUSBDEVICE pDevice, PREMOTEUSBQURB pRemoteURB)
+{
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_CANCEL_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_CANCEL_URB;
+ parm.id = pDevice->id;
+ parm.handle = pRemoteURB->u32Handle;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+ requestDevice(pDevice);
+
+ /* Remove this urb from the queue. It is safe because if
+ * client will return the URB, it will be just ignored
+ * in reapURB.
+ */
+ if (pRemoteURB->prev)
+ {
+ pRemoteURB->prev->next = pRemoteURB->next;
+ }
+ else
+ {
+ pDevice->pHeadQURBs = pRemoteURB->next;
+ }
+
+ if (pRemoteURB->next)
+ {
+ pRemoteURB->next->prev = pRemoteURB->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = pRemoteURB->prev;
+ }
+
+ qurbFree(pRemoteURB);
+
+ releaseDevice(pDevice);
+
+ return;
+}
+
+static DECLCALLBACK(int) iface_QueueURB(PREMOTEUSBDEVICE pDevice, uint8_t u8Type, uint8_t u8Ep, uint8_t u8Direction,
+ uint32_t u32Len, void *pvData, void *pvURB, PREMOTEUSBQURB *ppRemoteURB)
+{
+ int vrc = VINF_SUCCESS;
+
+#ifdef DEBUG_sunlover
+ LogFlow(("RemoteUSBBackend::iface_QueueURB: u8Type = %d, u8Ep = %d, u8Direction = %d, data\n%.*Rhxd\n",
+ u8Type, u8Ep, u8Direction, u32Len, pvData));
+#endif /* DEBUG_sunlover */
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ VRDE_USB_REQ_QUEUE_URB_PARM parm;
+ uint32_t u32Handle = 0;
+ uint32_t u32DataLen = 0;
+
+ REMOTEUSBQURB *qurb = qurbAlloc(pDevice);
+
+ if (qurb == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ goto l_leave;
+ }
+
+ /*
+ * Compute length of data which need to be transferred to the client.
+ */
+ switch(u8Direction)
+ {
+ case VUSB_DIRECTION_IN:
+ {
+ if (u8Type == VUSBXFERTYPE_MSG)
+ {
+ u32DataLen = 8; /* 8 byte header. */
+ // u32DataLen = u32Len; /// @todo do messages need all information?
+ }
+ } break;
+
+ case VUSB_DIRECTION_OUT:
+ {
+ u32DataLen = u32Len;
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ vrc = VERR_INVALID_PARAMETER;
+ goto l_leave;
+ }
+ }
+
+ parm.code = VRDE_USB_REQ_QUEUE_URB;
+ parm.id = pDevice->id;
+
+ u32Handle = pDevice->hURB++;
+ if (u32Handle == 0)
+ {
+ u32Handle = pDevice->hURB++;
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_QueueURB: handle = %d\n", u32Handle));
+
+ parm.handle = u32Handle;
+
+ switch(u8Type)
+ {
+ case VUSBXFERTYPE_CTRL: parm.type = VRDE_USB_TRANSFER_TYPE_CTRL; break;
+ case VUSBXFERTYPE_ISOC: parm.type = VRDE_USB_TRANSFER_TYPE_ISOC; break;
+ case VUSBXFERTYPE_BULK: parm.type = VRDE_USB_TRANSFER_TYPE_BULK; break;
+ case VUSBXFERTYPE_INTR: parm.type = VRDE_USB_TRANSFER_TYPE_INTR; break;
+ case VUSBXFERTYPE_MSG: parm.type = VRDE_USB_TRANSFER_TYPE_MSG; break;
+ default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave;
+ }
+
+ parm.ep = u8Ep;
+
+ switch(u8Direction)
+ {
+ case VUSB_DIRECTION_SETUP: AssertFailed(); parm.direction = VRDE_USB_DIRECTION_SETUP; break;
+ case VUSB_DIRECTION_IN: parm.direction = VRDE_USB_DIRECTION_IN; break;
+ case VUSB_DIRECTION_OUT: parm.direction = VRDE_USB_DIRECTION_OUT; break;
+ default: AssertFailed(); vrc = VERR_INVALID_PARAMETER; goto l_leave;
+ }
+
+ parm.urblen = u32Len;
+ parm.datalen = u32DataLen;
+
+ if (u32DataLen)
+ {
+ parm.data = pvData;
+ }
+
+ requestDevice (pDevice);
+
+ /* Add at tail of queued urb list. */
+ qurb->next = NULL;
+ qurb->prev = pDevice->pTailQURBs;
+ qurb->u32Err = VRDE_USB_XFER_OK;
+ qurb->u32Len = u32Len;
+ qurb->pvData = pvData;
+ qurb->pvURB = pvURB;
+ qurb->u32Handle = u32Handle;
+ qurb->fCompleted = false;
+ qurb->fInput = (u8Direction == VUSB_DIRECTION_IN);
+ qurb->u32TransferredLen = 0;
+
+ if (pDevice->pTailQURBs)
+ {
+ Assert(pDevice->pTailQURBs->next == NULL);
+ pDevice->pTailQURBs->next = qurb;
+ }
+ else
+ {
+ /* This is the first URB to be added. */
+ Assert(pDevice->pHeadQURBs == NULL);
+ pDevice->pHeadQURBs = qurb;
+ }
+
+ pDevice->pTailQURBs = qurb;
+
+ releaseDevice(pDevice);
+
+ *ppRemoteURB = qurb;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+
+l_leave:
+ if (RT_FAILURE(vrc))
+ {
+ qurbFree(qurb);
+ }
+
+ return vrc;
+}
+
+/* The function checks the URB queue for completed URBs. Also if the client
+ * has requested URB polling, the function will send URB poll requests.
+ */
+static DECLCALLBACK(int) iface_ReapURB(PREMOTEUSBDEVICE pDevice, uint32_t u32Millies, void **ppvURB,
+ uint32_t *pu32Len, uint32_t *pu32Err)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB %d ms\n", u32Millies));
+
+ if (pDevice->fFailed)
+ {
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ RemoteUSBBackend *pThis = pDevice->pOwner;
+
+ /* Wait for transaction completion. */
+ uint64_t u64StartTime = RTTimeMilliTS();
+
+ if (pThis->pollingEnabledURB())
+ {
+ VRDE_USB_REQ_REAP_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_REAP_URB;
+
+ pThis->VRDPServer()->SendUSBRequest(pDevice->u32ClientId, &parm, sizeof(parm));
+ }
+
+ REMOTEUSBQURB *qurb = NULL;
+
+ for (;;)
+ {
+ uint32_t u32ClientId;
+
+ if (ASMAtomicXchgBool(&pDevice->fWokenUp, false))
+ break;
+
+ /* Scan queued URBs, look for completed. */
+ requestDevice(pDevice);
+
+ u32ClientId = pDevice->u32ClientId;
+
+ qurb = pDevice->pHeadQURBs;
+
+ while (qurb)
+ {
+ if (qurb->fCompleted)
+ {
+ /* Remove this completed urb from the queue. */
+ if (qurb->prev)
+ {
+ qurb->prev->next = qurb->next;
+ }
+ else
+ {
+ pDevice->pHeadQURBs = qurb->next;
+ }
+
+ if (qurb->next)
+ {
+ qurb->next->prev = qurb->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = qurb->prev;
+ }
+
+ qurb->next = NULL;
+ qurb->prev = NULL;
+
+ break;
+ }
+
+ qurb = qurb->next;
+ }
+
+ releaseDevice(pDevice);
+
+ if ( qurb
+ || !pDevice->pHeadQURBs
+ || u32Millies == 0
+ || pDevice->fFailed
+ || (RTTimeMilliTS() - u64StartTime >= (uint64_t)u32Millies))
+ {
+ /* Got an URB or do not have to wait for an URB. */
+ break;
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB iteration.\n"));
+
+ RTThreadSleep(10);
+
+ if (pThis->pollingEnabledURB())
+ {
+ VRDE_USB_REQ_REAP_URB_PARM parm;
+
+ parm.code = VRDE_USB_REQ_REAP_URB;
+
+ pThis->VRDPServer()->SendUSBRequest(u32ClientId, &parm, sizeof(parm));
+ }
+ }
+
+ LogFlow(("RemoteUSBBackend::iface_ReapURB completed in %lld ms, qurb = %p\n", RTTimeMilliTS () - u64StartTime, qurb));
+
+ if (!qurb)
+ {
+ *ppvURB = NULL;
+ *pu32Len = 0;
+ *pu32Err = VUSBSTATUS_OK;
+ }
+ else
+ {
+ *ppvURB = qurb->pvURB;
+ *pu32Len = qurb->u32Len;
+ *pu32Err = qurb->u32Err;
+
+#ifdef LOG_ENABLED
+ Log(("URB len = %d, data = %p\n", qurb->u32Len, qurb->pvURB));
+ if (qurb->u32Len)
+ {
+ Log(("Received URB content:\n%.*Rhxd\n", qurb->u32Len, qurb->pvData));
+ }
+#endif
+
+ qurbFree(qurb);
+ }
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) iface_Wakeup(PREMOTEUSBDEVICE pDevice)
+{
+ ASMAtomicXchgBool(&pDevice->fWokenUp, true);
+ return VINF_SUCCESS;
+}
+
+void RemoteUSBBackend::AddRef(void)
+{
+ cRefs++;
+}
+
+void RemoteUSBBackend::Release(void)
+{
+ cRefs--;
+
+ if (cRefs <= 0)
+ {
+ delete this;
+ }
+}
+
+void RemoteUSBBackend::PollRemoteDevices(void)
+{
+ if ( mfWillBeDeleted
+ && menmPollRemoteDevicesStatus != PollRemoteDevicesStatus_Dereferenced)
+ {
+ /* Unmount all remote USB devices. */
+ mConsole->i_processRemoteUSBDevices(mu32ClientId, NULL, 0, false);
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_Dereferenced;
+
+ Release();
+
+ return;
+ }
+
+ switch(menmPollRemoteDevicesStatus)
+ {
+ case PollRemoteDevicesStatus_Negotiate:
+ {
+ VRDEUSBREQNEGOTIATEPARM parm;
+
+ parm.code = VRDE_USB_REQ_NEGOTIATE;
+ parm.version = VRDE_USB_VERSION;
+ /* VRDE_USB_VERSION_3: support VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
+ parm.flags = VRDE_USB_SERVER_CAPS_PORT_VERSION;
+
+ mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm));
+
+ /* Reference the object. When the client disconnects and
+ * the backend is about to be deleted, the method must be called
+ * to disconnect the USB devices (as stated above).
+ */
+ AddRef();
+
+ /* Goto the disabled state. When a response will be received
+ * the state will be changed to the SendRequest.
+ */
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitNegotiateResponse;
+ } break;
+
+ case PollRemoteDevicesStatus_WaitNegotiateResponse:
+ {
+ LogFlow(("USB::PollRemoteDevices: WaitNegotiateResponse\n"));
+ /* Do nothing. */
+ } break;
+
+ case PollRemoteDevicesStatus_SendRequest:
+ {
+ LogFlow(("USB::PollRemoteDevices: SendRequest\n"));
+
+ /* Send a request for device list. */
+ VRDE_USB_REQ_DEVICE_LIST_PARM parm;
+
+ parm.code = VRDE_USB_REQ_DEVICE_LIST;
+
+ mServer->SendUSBRequest(mu32ClientId, &parm, sizeof(parm));
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_WaitResponse;
+ } break;
+
+ case PollRemoteDevicesStatus_WaitResponse:
+ {
+ LogFlow(("USB::PollRemoteDevices: WaitResponse\n"));
+
+ if (mfHasDeviceList)
+ {
+ mConsole->i_processRemoteUSBDevices(mu32ClientId, (VRDEUSBDEVICEDESC *)mpvDeviceList, mcbDeviceList, mfDescExt);
+ LogFlow(("USB::PollRemoteDevices: WaitResponse after process\n"));
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
+
+ mfHasDeviceList = false;
+ }
+ } break;
+
+ case PollRemoteDevicesStatus_Dereferenced:
+ {
+ LogFlow(("USB::PollRemoteDevices: Dereferenced\n"));
+ /* Do nothing. */
+ } break;
+
+ default:
+ {
+ AssertFailed();
+ } break;
+ }
+}
+
+void RemoteUSBBackend::NotifyDelete(void)
+{
+ mfWillBeDeleted = true;
+}
+
+/*
+ * The backend maintains a list of UUIDs of devices
+ * which are managed by the backend.
+ */
+bool RemoteUSBBackend::addUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i].isZero())
+ {
+ aGuids[i] = *pUuid;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool RemoteUSBBackend::findUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i] == *pUuid)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RemoteUSBBackend::removeUUID(const Guid *pUuid)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(aGuids); i++)
+ {
+ if (aGuids[i] == *pUuid)
+ {
+ aGuids[i].clear();
+ break;
+ }
+ }
+}
+
+RemoteUSBBackend::RemoteUSBBackend(Console *console, ConsoleVRDPServer *server, uint32_t u32ClientId)
+ :
+ mConsole(console),
+ mServer(server),
+ cRefs(0),
+ mu32ClientId(u32ClientId),
+ mfHasDeviceList(false),
+ mpvDeviceList(NULL),
+ mcbDeviceList(0),
+ menmPollRemoteDevicesStatus(PollRemoteDevicesStatus_Negotiate),
+ mfPollURB(true),
+ mpDevices(NULL),
+ mfWillBeDeleted(false),
+ mClientVersion(0), /* VRDE_USB_VERSION_2: the client version. */
+ mfDescExt(false) /* VRDE_USB_VERSION_3: VRDE_USB_REQ_DEVICE_LIST_EXT_RET. */
+{
+ Assert(console);
+ Assert(server);
+
+ int vrc = RTCritSectInit(&mCritsect);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ RT_ZERO(mCritsect);
+ }
+
+ mCallback.pInstance = (PREMOTEUSBBACKEND)this;
+ mCallback.pfnOpen = iface_Open;
+ mCallback.pfnClose = iface_Close;
+ mCallback.pfnReset = iface_Reset;
+ mCallback.pfnSetConfig = iface_SetConfig;
+ mCallback.pfnClaimInterface = iface_ClaimInterface;
+ mCallback.pfnReleaseInterface = iface_ReleaseInterface;
+ mCallback.pfnInterfaceSetting = iface_InterfaceSetting;
+ mCallback.pfnQueueURB = iface_QueueURB;
+ mCallback.pfnReapURB = iface_ReapURB;
+ mCallback.pfnClearHaltedEP = iface_ClearHaltedEP;
+ mCallback.pfnCancelURB = iface_CancelURB;
+ mCallback.pfnWakeup = iface_Wakeup;
+}
+
+RemoteUSBBackend::~RemoteUSBBackend()
+{
+ Assert(cRefs == 0);
+
+ if (RTCritSectIsInitialized(&mCritsect))
+ {
+ RTCritSectDelete(&mCritsect);
+ }
+
+ RTMemFree(mpvDeviceList);
+
+ mServer->usbBackendRemoveFromList(this);
+}
+
+int RemoteUSBBackend::negotiateResponse(const VRDEUSBREQNEGOTIATERET *pret, uint32_t cbRet)
+{
+ int vrc = VINF_SUCCESS;
+
+ Log(("RemoteUSBBackend::negotiateResponse: flags = %02X.\n", pret->flags));
+
+ LogRel(("Remote USB: Received negotiate response. Flags 0x%02X.\n",
+ pret->flags));
+
+ if (pret->flags & VRDE_USB_CAPS_FLAG_POLL)
+ {
+ Log(("RemoteUSBBackend::negotiateResponse: client requested URB polling.\n"));
+ mfPollURB = true;
+ }
+ else
+ {
+ mfPollURB = false;
+ }
+
+ /* VRDE_USB_VERSION_2: check the client version. */
+ if (pret->flags & VRDE_USB_CAPS2_FLAG_VERSION)
+ {
+ /* This could be a client version > 1. */
+ if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_2))
+ {
+ VRDEUSBREQNEGOTIATERET_2 *pret2 = (VRDEUSBREQNEGOTIATERET_2 *)pret;
+
+ if (pret2->u32Version <= VRDE_USB_VERSION)
+ {
+ /* This is OK. The client wants a version supported by the server. */
+ mClientVersion = pret2->u32Version;
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: unsupported remote USB protocol client version %d.\n", pret2->u32Version));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ /* This is a client version 1. */
+ mClientVersion = VRDE_USB_VERSION_1;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("VRDP: remote USB protocol version %d.\n", mClientVersion));
+
+ /* VRDE_USB_VERSION_3: check the client capabilities: VRDE_USB_CLIENT_CAPS_*. */
+ if (mClientVersion == VRDE_USB_VERSION_3)
+ {
+ if (cbRet >= sizeof(VRDEUSBREQNEGOTIATERET_3))
+ {
+ VRDEUSBREQNEGOTIATERET_3 *pret3 = (VRDEUSBREQNEGOTIATERET_3 *)pret;
+
+ mfDescExt = (pret3->u32Flags & VRDE_USB_CLIENT_CAPS_PORT_VERSION) != 0;
+ }
+ else
+ {
+ LogRel(("VRDP: ERROR: invalid remote USB negotiate request packet size %d.\n", cbRet));
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ menmPollRemoteDevicesStatus = PollRemoteDevicesStatus_SendRequest;
+ }
+
+ return vrc;
+}
+
+int RemoteUSBBackend::saveDeviceList(const void *pvList, uint32_t cbList)
+{
+ Log(("RemoteUSBBackend::saveDeviceList: pvList = %p, cbList = %d\n", pvList, cbList));
+
+ if (!mfHasDeviceList)
+ {
+ RTMemFree(mpvDeviceList);
+ mpvDeviceList = NULL;
+ mcbDeviceList = 0;
+
+ if (cbList > 0)
+ {
+ mpvDeviceList = RTMemAlloc(cbList);
+ AssertPtrReturn(mpvDeviceList, VERR_NO_MEMORY);
+
+ memcpy(mpvDeviceList, pvList, cbList);
+ mcbDeviceList = cbList;
+ }
+
+ mfHasDeviceList = true;
+ }
+
+ return VINF_SUCCESS;
+}
+
+void RemoteUSBBackend::request(void)
+{
+ int vrc = RTCritSectEnter(&mCritsect);
+ AssertRC(vrc);
+}
+
+void RemoteUSBBackend::release(void)
+{
+ RTCritSectLeave(&mCritsect);
+}
+
+PREMOTEUSBDEVICE RemoteUSBBackend::deviceFromId(VRDEUSBDEVID id)
+{
+ request();
+
+ REMOTEUSBDEVICE *pDevice = mpDevices;
+
+ while (pDevice && pDevice->id != id)
+ {
+ pDevice = pDevice->next;
+ }
+
+ release();
+
+ return pDevice;
+}
+
+void RemoteUSBBackend::addDevice(PREMOTEUSBDEVICE pDevice)
+{
+ request();
+
+ pDevice->next = mpDevices;
+
+ if (mpDevices)
+ {
+ mpDevices->prev = pDevice;
+ }
+
+ mpDevices = pDevice;
+
+ release();
+}
+
+void RemoteUSBBackend::removeDevice(PREMOTEUSBDEVICE pDevice)
+{
+ request();
+
+ if (pDevice->prev)
+ {
+ pDevice->prev->next = pDevice->next;
+ }
+ else
+ {
+ mpDevices = pDevice->next;
+ }
+
+ if (pDevice->next)
+ {
+ pDevice->next->prev = pDevice->prev;
+ }
+
+ release();
+}
+
+int RemoteUSBBackend::reapURB(const void *pvBody, uint32_t cbBody)
+{
+ int vrc = VINF_SUCCESS;
+
+ LogFlow(("RemoteUSBBackend::reapURB: pvBody = %p, cbBody = %d\n", pvBody, cbBody));
+
+ VRDEUSBREQREAPURBBODY *pBody = (VRDEUSBREQREAPURBBODY *)pvBody;
+
+ /* 'pvBody' memory buffer can contain multiple URBs. */
+ while (cbBody >= sizeof(VRDEUSBREQREAPURBBODY))
+ {
+ Log(("RemoteUSBBackend::reapURB: id = %d, flags = %02X, error = %d, handle %d, len = %d.\n",
+ pBody->id, pBody->flags, pBody->error, pBody->handle, pBody->len));
+
+ uint8_t fu8ReapValidFlags;
+
+ if (mClientVersion == VRDE_USB_VERSION_1 || mClientVersion == VRDE_USB_VERSION_2)
+ {
+ fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS;
+ }
+ else
+ {
+ fu8ReapValidFlags = VRDE_USB_REAP_VALID_FLAGS_3;
+ }
+
+ /* Verify client's data. */
+ if ( (pBody->flags & ~fu8ReapValidFlags) != 0
+ || pBody->handle == 0)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid reply data. Skipping the reply.\n"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ PREMOTEUSBDEVICE pDevice = deviceFromId(pBody->id);
+
+ if (!pDevice)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: WARNING: invalid device id. Skipping the reply.\n"));
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ uint32_t cbBodyData = 0; /* Data contained in the URB body structure for input URBs. i.e. beyond VRDEUSBREQREAPURBBODY. */
+
+ requestDevice(pDevice);
+
+ /* Search the queued URB for given handle. */
+ REMOTEUSBQURB *qurb = pDevice->pHeadQURBs;
+
+ while (qurb && qurb->u32Handle != pBody->handle)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: searching: %p handle = %d.\n", qurb, qurb->u32Handle));
+ qurb = qurb->next;
+ }
+
+ if (!qurb)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: Queued URB not found, probably already canceled. Skipping the URB.\n"));
+ }
+ else
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: qurb = %p, u32Err = %d\n", qurb, qurb->u32Err));
+
+ /* Update the URB error field, if it does not yet indicate an error. */
+ if (qurb->u32Err == VUSBSTATUS_OK)
+ {
+ if (mClientVersion == VRDE_USB_VERSION_1)
+ {
+ switch(pBody->error)
+ {
+ case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
+ case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
+ case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
+ case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
+ default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
+ qurb->u32Err = VUSBSTATUS_DNR; break;
+ }
+ }
+ else if ( mClientVersion == VRDE_USB_VERSION_2
+ || mClientVersion == VRDE_USB_VERSION_3)
+ {
+ switch(pBody->error)
+ {
+ case VRDE_USB_XFER_OK: qurb->u32Err = VUSBSTATUS_OK; break;
+ case VRDE_USB_XFER_STALL: qurb->u32Err = VUSBSTATUS_STALL; break;
+ case VRDE_USB_XFER_DNR: qurb->u32Err = VUSBSTATUS_DNR; break;
+ case VRDE_USB_XFER_CRC: qurb->u32Err = VUSBSTATUS_CRC; break;
+ case VRDE_USB_XFER_DO: qurb->u32Err = VUSBSTATUS_DATA_OVERRUN; break;
+ case VRDE_USB_XFER_DU: qurb->u32Err = VUSBSTATUS_DATA_UNDERRUN; break;
+
+ /* Unmapped errors. */
+ case VRDE_USB_XFER_BS:
+ case VRDE_USB_XFER_DTM:
+ case VRDE_USB_XFER_PCF:
+ case VRDE_USB_XFER_UPID:
+ case VRDE_USB_XFER_BO:
+ case VRDE_USB_XFER_BU:
+ case VRDE_USB_XFER_ERR:
+ default: Log(("RemoteUSBBackend::reapURB: Invalid error %d\n", pBody->error));
+ qurb->u32Err = VUSBSTATUS_DNR; break;
+ }
+ }
+ else
+ {
+ qurb->u32Err = VUSBSTATUS_DNR;
+ }
+ }
+
+ /* Get the URB data. The URB is completed unless the client tells that this is a fragment of an IN URB. */
+ bool fURBCompleted = true;
+
+ if (qurb->fInput)
+ {
+ if (pBody->len <= cbBody - sizeof(VRDEUSBREQREAPURBBODY))
+ cbBodyData = pBody->len; /* VRDE_USB_DIRECTION_IN URBs include some data. */
+ else
+ {
+ cbBodyData = cbBody - sizeof(VRDEUSBREQREAPURBBODY);
+ qurb->u32Err = VUSBSTATUS_DNR;
+ }
+
+ if (qurb->u32Err == VUSBSTATUS_OK)
+ {
+ LogFlow(("RemoteUSBBackend::reapURB: copying data %d bytes\n", pBody->len));
+ if (pBody->len > qurb->u32Len - qurb->u32TransferredLen)
+ {
+ /* Received more data than expected for this URB. If there more fragments follow,
+ * they will be discarded because the URB handle will not be valid anymore.
+ */
+ qurb->u32Err = VUSBSTATUS_DNR;
+ qurb->u32TransferredLen = qurb->u32Len;
+ }
+ else
+ {
+ memcpy ((uint8_t *)qurb->pvData + qurb->u32TransferredLen, &pBody[1], pBody->len);
+ qurb->u32TransferredLen += pBody->len;
+ }
+
+ if ( qurb->u32Err == VUSBSTATUS_OK
+ && (pBody->flags & VRDE_USB_REAP_FLAG_FRAGMENT) != 0)
+ {
+ /* If the client sends fragmented packets, accumulate the URB data. */
+ fURBCompleted = false;
+ }
+ }
+ }
+ else
+ qurb->u32TransferredLen += pBody->len; /* Update the value for OUT URBs. */
+
+ if (fURBCompleted)
+ {
+ /* Move the URB near the head of URB list, so that iface_ReapURB can
+ * find it faster. Note that the order of completion must be preserved!
+ */
+ if (qurb->prev)
+ {
+ /* The URB is not in the head. Unlink it from its current position. */
+ qurb->prev->next = qurb->next;
+
+ if (qurb->next)
+ {
+ qurb->next->prev = qurb->prev;
+ }
+ else
+ {
+ pDevice->pTailQURBs = qurb->prev;
+ }
+
+ /* And insert it to its new place. */
+ if (pDevice->pHeadQURBs->fCompleted)
+ {
+ /* At least one other completed URB; insert after the
+ * last completed URB.
+ */
+ REMOTEUSBQURB *prev_qurb = pDevice->pHeadQURBs;
+ while (prev_qurb->next && prev_qurb->next->fCompleted)
+ prev_qurb = prev_qurb->next;
+
+ qurb->next = prev_qurb->next;
+ qurb->prev = prev_qurb;
+
+ if (prev_qurb->next)
+ prev_qurb->next->prev = qurb;
+ else
+ pDevice->pTailQURBs = qurb;
+ prev_qurb->next = qurb;
+ }
+ else
+ {
+ /* No other completed URBs; insert at head. */
+ qurb->next = pDevice->pHeadQURBs;
+ qurb->prev = NULL;
+
+ pDevice->pHeadQURBs->prev = qurb;
+ pDevice->pHeadQURBs = qurb;
+ }
+ }
+
+ qurb->u32Len = qurb->u32TransferredLen; /* Update the final length. */
+ qurb->fCompleted = true;
+ }
+ }
+
+ releaseDevice (pDevice);
+
+ if (pBody->flags & VRDE_USB_REAP_FLAG_LAST)
+ {
+ break;
+ }
+
+ /* There is probably a further URB body. */
+ if (cbBodyData > cbBody - sizeof(VRDEUSBREQREAPURBBODY))
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ cbBody -= sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData;
+ pBody = (VRDEUSBREQREAPURBBODY *)((uint8_t *)pBody + sizeof(VRDEUSBREQREAPURBBODY) + cbBodyData);
+ }
+
+ LogFlow(("RemoteUSBBackend::reapURB: returns %Rrc\n", vrc));
+
+ return vrc;
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp
new file mode 100644
index 00000000..90181d0f
--- /dev/null
+++ b/src/VBox/Main/src-client/RemoteUSBDeviceImpl.cpp
@@ -0,0 +1,314 @@
+/* $Id: RemoteUSBDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox IHostUSBDevice COM interface implementation for remote (VRDP) USB devices.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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 const *pDevDesc, bool fDescExt)
+{
+ LogFlowThisFunc(("u32ClientId=%d,pDevDesc=%p\n", u32ClientId, pDevDesc));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mData.id).create();
+
+ unconst(mData.vendorId) = pDevDesc->idVendor;
+ unconst(mData.productId) = pDevDesc->idProduct;
+ unconst(mData.revision) = pDevDesc->bcdRev;
+
+ unconst(mData.manufacturer) = pDevDesc->oManufacturer ? (char *)pDevDesc + pDevDesc->oManufacturer : "";
+ unconst(mData.product) = pDevDesc->oProduct ? (char *)pDevDesc + pDevDesc->oProduct : "";
+ unconst(mData.serialNumber) = pDevDesc->oSerialNumber ? (char *)pDevDesc + pDevDesc->oSerialNumber : "";
+
+ char id[64];
+ RTStrPrintf(id, sizeof(id), REMOTE_USB_BACKEND_PREFIX_S "0x%08X&0x%08X", pDevDesc->id, u32ClientId);
+ unconst(mData.address) = id;
+ unconst(mData.backend) = "vrdp";
+
+ char port[16];
+ RTStrPrintf(port, sizeof(port), "%u", pDevDesc->idPort);
+ unconst(mData.portPath) = port;
+
+ unconst(mData.port) = pDevDesc->idPort;
+ unconst(mData.version) = (uint16_t)(pDevDesc->bcdUSB >> 8);
+ if (fDescExt)
+ {
+ VRDEUSBDEVICEDESCEXT *pDevDescExt = (VRDEUSBDEVICEDESCEXT *)pDevDesc;
+ switch (pDevDescExt->u16DeviceSpeed)
+ {
+ default:
+ case VRDE_USBDEVICESPEED_UNKNOWN:
+ case VRDE_USBDEVICESPEED_LOW:
+ case VRDE_USBDEVICESPEED_FULL:
+ unconst(mData.speed) = USBConnectionSpeed_Full;
+ break;
+
+ case VRDE_USBDEVICESPEED_HIGH:
+ case VRDE_USBDEVICESPEED_VARIABLE:
+ unconst(mData.speed) = USBConnectionSpeed_High;
+ break;
+
+ case VRDE_USBDEVICESPEED_SUPERSPEED:
+ unconst(mData.speed) = USBConnectionSpeed_Super;
+ break;
+ }
+ }
+ else
+ {
+ unconst(mData.speed) = mData.version == 3 ? USBConnectionSpeed_Super
+ : mData.version == 2 ? USBConnectionSpeed_High
+ : USBConnectionSpeed_Full;
+ }
+
+ mData.state = USBDeviceState_Available;
+
+ mData.dirty = false;
+ unconst(mData.devId) = (uint16_t)pDevDesc->id;
+
+ unconst(mData.clientId) = u32ClientId;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void RemoteUSBDevice::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mData.id).clear();
+
+ unconst(mData.vendorId) = 0;
+ unconst(mData.productId) = 0;
+ unconst(mData.revision) = 0;
+
+ unconst(mData.manufacturer).setNull();
+ unconst(mData.product).setNull();
+ unconst(mData.serialNumber).setNull();
+
+ unconst(mData.address).setNull();
+ unconst(mData.backend).setNull();
+
+ unconst(mData.port) = 0;
+ unconst(mData.portPath).setNull();
+ unconst(mData.version) = 1;
+
+ unconst(mData.dirty) = FALSE;
+
+ unconst(mData.devId) = 0;
+ unconst(mData.clientId) = 0;
+}
+
+// IUSBDevice properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT RemoteUSBDevice::getId(com::Guid &aId)
+{
+ aId = mData.id;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getVendorId(USHORT *aVendorId)
+{
+ /* this is const, no need to lock */
+ *aVendorId = mData.vendorId;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getProductId(USHORT *aProductId)
+{
+ /* this is const, no need to lock */
+ *aProductId = mData.productId;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getRevision(USHORT *aRevision)
+{
+ /* this is const, no need to lock */
+ *aRevision = mData.revision;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ /* this is const, no need to lock */
+ aManufacturer = mData.manufacturer;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getProduct(com::Utf8Str &aProduct)
+{
+ /* this is const, no need to lock */
+ aProduct = mData.product;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ /* this is const, no need to lock */
+ aSerialNumber = mData.serialNumber;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getAddress(com::Utf8Str &aAddress)
+{
+ /* this is const, no need to lock */
+ aAddress = mData.address;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getPort(USHORT *aPort)
+{
+ /* this is const, no need to lock */
+ *aPort = mData.port;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getPortPath(com::Utf8Str &aPortPath)
+{
+ /* this is const, no need to lock */
+ aPortPath = mData.portPath;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getVersion(USHORT *aVersion)
+{
+ /* this is const, no need to lock */
+ *aVersion = mData.version;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed)
+{
+ /* this is const, no need to lock */
+ *aSpeed = mData.speed;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getRemote(BOOL *aRemote)
+{
+ /* RemoteUSBDevice is always remote. */
+ /* this is const, no need to lock */
+ *aRemote = TRUE;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getBackend(com::Utf8Str &aBackend)
+{
+ /* this is const, no need to lock */
+ aBackend = mData.backend;
+
+ return S_OK;
+}
+
+HRESULT RemoteUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo)
+{
+ /* this is const, no need to lock */
+ aInfo.resize(2);
+ aInfo[0] = mData.manufacturer;
+ aInfo[1] = mData.product;
+
+ return S_OK;
+}
+
+// IHostUSBDevice properties
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT RemoteUSBDevice::getState(USBDeviceState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mData.state;
+
+ return S_OK;
+}
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/SessionImpl.cpp b/src/VBox/Main/src-client/SessionImpl.cpp
new file mode 100644
index 00000000..8f45b27c
--- /dev/null
+++ b/src/VBox/Main/src-client/SessionImpl.cpp
@@ -0,0 +1,1339 @@
+/* $Id: SessionImpl.cpp $ */
+/** @file
+ * VBox Client Session COM Class implementation in VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SESSION
+#include "LoggingNew.h"
+
+#include "SessionImpl.h"
+#include "ConsoleImpl.h"
+#include "ClientTokenHolder.h"
+#include "Global.h"
+#include "StringifyEnums.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/errcore.h>
+#include <iprt/process.h>
+
+
+/**
+ * Local macro to check whether the session is open and return an error if not.
+ * @note Don't forget to do |Auto[Reader]Lock alock (this);| before using this
+ * macro.
+ */
+#define CHECK_OPEN() \
+ do { \
+ if (mState != SessionState_Locked) \
+ return setError(E_UNEXPECTED, Session::tr("The session is not locked (session state: %s)"), \
+ Global::stringifySessionState(mState)); \
+ } while (0)
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+Session::Session()
+{
+}
+
+Session::~Session()
+{
+}
+
+HRESULT Session::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+
+ HRESULT hrc = init();
+
+ BaseFinalConstruct();
+
+ return hrc;
+}
+
+void Session::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+
+ uninit();
+
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the Session object.
+ */
+HRESULT Session::init()
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ LogFlowThisFuncEnter();
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ mClientTokenHolder = NULL;
+
+ /* Confirm a successful initialization when it's the case */
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the Session object.
+ *
+ * @note Locks this object for writing.
+ */
+void Session::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ LogFlowThisFuncLeave();
+ return;
+ }
+
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState != SessionState_Unlocked)
+ {
+ Assert(mState == SessionState_Locked ||
+ mState == SessionState_Spawning);
+
+ HRESULT hrc = i_unlockMachine(true /* aFinalRelease */, false /* aFromServer */, alock);
+ AssertComRC(hrc);
+ }
+
+ LogFlowThisFuncLeave();
+}
+
+// ISession properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Session::getState(SessionState_T *aState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aState = mState;
+
+ return S_OK;
+}
+
+HRESULT Session::getType(SessionType_T *aType)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ *aType = mType;
+ return S_OK;
+}
+
+HRESULT Session::getName(com::Utf8Str &aName)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mName;
+ return S_OK;
+}
+
+HRESULT Session::setName(const com::Utf8Str &aName)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState != SessionState_Unlocked)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Trying to set name for a session which is not in state \"unlocked\""));
+
+ mName = aName;
+ return S_OK;
+}
+
+HRESULT Session::getMachine(ComPtr<IMachine> &aMachine)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ HRESULT hrc;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ hrc = mConsole->i_machine().queryInterfaceTo(aMachine.asOutParam());
+ else
+#endif
+ hrc = mRemoteMachine.queryInterfaceTo(aMachine.asOutParam());
+ if (FAILED(hrc))
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ setError(hrc, tr("Failed to query the session machine"));
+ else
+#endif
+ if (FAILED_DEAD_INTERFACE(hrc))
+ setError(hrc, tr("Peer process crashed"));
+ else
+ setError(hrc, tr("Failed to query the remote session machine"));
+ }
+
+ return hrc;
+}
+
+HRESULT Session::getConsole(ComPtr<IConsole> &aConsole)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+
+ HRESULT hrc = S_OK;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ hrc = mConsole.queryInterfaceTo(aConsole.asOutParam());
+ else
+#endif
+ hrc = mRemoteConsole.queryInterfaceTo(aConsole.asOutParam());
+
+ if (FAILED(hrc))
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mConsole)
+ setError(hrc, tr("Failed to query the console"));
+ else
+#endif
+ if (FAILED_DEAD_INTERFACE(hrc))
+ setError(hrc, tr("Peer process crashed"));
+ else
+ setError(hrc, tr("Failed to query the remote console"));
+ }
+
+ return hrc;
+}
+
+// ISession methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Session::unlockMachine()
+{
+ LogFlowThisFunc(("mState=%d, mType=%d\n", mState, mType));
+
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CHECK_OPEN();
+ return i_unlockMachine(false /* aFinalRelease */, false /* aFromServer */, alock);
+}
+
+// IInternalSessionControl methods
+/////////////////////////////////////////////////////////////////////////////
+HRESULT Session::getPID(ULONG *aPid)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPid = (ULONG)RTProcSelf();
+ AssertCompile(sizeof(*aPid) == sizeof(RTPROCESS));
+
+ return S_OK;
+}
+
+HRESULT Session::getRemoteConsole(ComPtr<IConsole> &aConsole)
+{
+ LogFlowThisFuncEnter();
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mType == SessionType_WriteLock && !!mConsole)
+ {
+ /* return a failure if the session already transitioned to Closing
+ * but the server hasn't processed Machine::OnSessionEnd() yet. */
+ if (mState == SessionState_Locked)
+ {
+ mConsole.queryInterfaceTo(aConsole.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+ }
+ return VBOX_E_INVALID_VM_STATE;
+ }
+ return setError(VBOX_E_INVALID_OBJECT_STATE, "This is not a direct session");
+
+#else /* VBOX_COM_INPROC_API_CLIENT */
+ RT_NOREF(aConsole);
+ AssertFailed();
+ return VBOX_E_INVALID_OBJECT_STATE;
+#endif /* VBOX_COM_INPROC_API_CLIENT */
+}
+
+HRESULT Session::getNominalState(MachineState_T *aNominalState)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_getNominalState(*aNominalState);
+#else
+ RT_NOREF(aNominalState);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const com::Utf8Str &aTokenId)
+#else
+HRESULT Session::assignMachine(const ComPtr<IMachine> &aMachine,
+ LockType_T aLockType,
+ const ComPtr<IToken> &aToken)
+#endif /* !VBOX_WITH_GENERIC_SESSION_WATCHER */
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mState == SessionState_Unlocked, VBOX_E_INVALID_VM_STATE);
+
+ if (!aMachine)
+ {
+ /*
+ * A special case: the server informs us that this session has been
+ * passed to IMachine::launchVMProcess() so this session will become
+ * remote (but not existing) when AssignRemoteMachine() is called.
+ */
+
+ AssertReturn(mType == SessionType_Null, VBOX_E_INVALID_OBJECT_STATE);
+ mType = SessionType_Remote;
+ mState = SessionState_Spawning;
+
+ return S_OK;
+ }
+
+ /* query IInternalMachineControl interface */
+ mControl = aMachine;
+ AssertReturn(!!mControl, E_FAIL);
+
+ HRESULT hrc = S_OK;
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (aLockType == LockType_VM)
+ {
+ /* This is what is special about VM processes: they have a Console
+ * object which is the root of all VM related activity. */
+ hrc = mConsole.createObject();
+ AssertComRCReturn(hrc, hrc);
+
+ hrc = mConsole->initWithMachine(aMachine, mControl, aLockType);
+ AssertComRCReturn(hrc, hrc);
+ }
+ else
+ mRemoteMachine = aMachine;
+#else
+ RT_NOREF(aLockType);
+ mRemoteMachine = aMachine;
+#endif
+
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ Utf8Str strTokenId(aTokenId);
+ Assert(!strTokenId.isEmpty());
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ Assert(!aToken.isNull());
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ /* create the machine client token */
+ try
+ {
+#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER
+ mClientTokenHolder = new ClientTokenHolder(strTokenId);
+#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ mClientTokenHolder = new ClientTokenHolder(aToken);
+#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */
+ if (!mClientTokenHolder->isReady())
+ {
+ delete mClientTokenHolder;
+ mClientTokenHolder = NULL;
+ hrc = E_FAIL;
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+
+ /*
+ * Reference the VirtualBox object to ensure the server is up
+ * until the session is closed
+ */
+ if (SUCCEEDED(hrc))
+ hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ mType = SessionType_WriteLock;
+ mState = SessionState_Locked;
+ }
+ else
+ {
+ /* some cleanup */
+ mControl.setNull();
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (!mConsole.isNull())
+ {
+ mConsole->uninit();
+ mConsole.setNull();
+ }
+#endif
+ }
+
+ return hrc;
+}
+
+HRESULT Session::assignRemoteMachine(const ComPtr<IMachine> &aMachine,
+ const ComPtr<IConsole> &aConsole)
+
+{
+ AssertReturn(aMachine, E_INVALIDARG);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mState == SessionState_Unlocked ||
+ mState == SessionState_Spawning, VBOX_E_INVALID_VM_STATE);
+
+ HRESULT hrc = E_FAIL;
+
+ /* query IInternalMachineControl interface */
+ mControl = aMachine;
+ AssertReturn(!!mControl, E_FAIL);
+
+ /// @todo (dmik)
+ // currently, the remote session returns the same machine and
+ // console objects as the direct session, thus giving the
+ // (remote) client full control over the direct session. For the
+ // console, it is the desired behavior (the ability to control
+ // VM execution is a must for the remote session). What about
+ // the machine object, we may want to prevent the remote client
+ // from modifying machine data. In this case, we must:
+ // 1) assign the Machine object (instead of the SessionMachine
+ // object that is passed to this method) to mRemoteMachine;
+ // 2) remove GetMachine() property from the IConsole interface
+ // because it always returns the SessionMachine object
+ // (alternatively, we can supply a separate IConsole
+ // implementation that will return the Machine object in
+ // response to GetMachine()).
+
+ mRemoteMachine = aMachine;
+ mRemoteConsole = aConsole;
+
+ /*
+ * Reference the VirtualBox object to ensure the server is up
+ * until the session is closed
+ */
+ hrc = aMachine->COMGETTER(Parent)(mVirtualBox.asOutParam());
+
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * RemoteSession type can be already set by AssignMachine() when its
+ * argument is NULL (a special case)
+ */
+ if (mType != SessionType_Remote)
+ mType = SessionType_Shared;
+ else
+ Assert(mState == SessionState_Spawning);
+
+ mState = SessionState_Locked;
+ }
+ else
+ {
+ /* some cleanup */
+ mControl.setNull();
+ mRemoteMachine.setNull();
+ mRemoteConsole.setNull();
+ }
+
+ LogFlowThisFunc(("hrc=%08X\n", hrc));
+ LogFlowThisFuncLeave();
+
+ return hrc;
+}
+
+HRESULT Session::updateMachineState(MachineState_T aMachineState)
+{
+
+ if (getObjectState().getState() != ObjectState::Ready)
+ {
+ /*
+ * We might have already entered Session::uninit() at this point, so
+ * return silently (not interested in the state change during uninit)
+ */
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ return S_OK;
+ }
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mState == SessionState_Unlocking)
+ {
+ LogFlowThisFunc(("Already being unlocked.\n"));
+ return S_OK;
+ }
+
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+
+ AssertReturn(!mControl.isNull(), E_FAIL);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(!mConsole.isNull(), E_FAIL);
+
+ return mConsole->i_updateMachineState(aMachineState);
+#else
+ RT_NOREF(aMachineState);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::uninitialize()
+{
+ LogFlowThisFuncEnter();
+
+ AutoCaller autoCaller(this);
+
+ HRESULT hrc = S_OK;
+
+ if (getObjectState().getState() == ObjectState::Ready)
+ {
+ /* close() needs write lock */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType));
+
+ if (mState == SessionState_Unlocking)
+ {
+ LogFlowThisFunc(("Already being unlocked.\n"));
+ return S_OK;
+ }
+
+ if ( mState == SessionState_Locked
+ || mState == SessionState_Spawning)
+ { /* likely */ }
+ else
+ {
+#ifndef DEBUG_bird /* bird: hitting this all the time running tdAddBaseic1.py. */
+ AssertMsgFailed(("Session is in wrong state (%d), expected locked (%d) or spawning (%d)\n",
+ mState, SessionState_Locked, SessionState_Spawning));
+#endif
+ return VBOX_E_INVALID_VM_STATE;
+ }
+
+ /* close ourselves */
+ hrc = i_unlockMachine(false /* aFinalRelease */, true /* aFromServer */, alock);
+ }
+ else if (getObjectState().getState() == ObjectState::InUninit)
+ {
+ /*
+ * We might have already entered Session::uninit() at this point,
+ * return silently
+ */
+ LogFlowThisFunc(("Already uninitialized.\n"));
+ }
+ else
+ {
+ Log1WarningThisFunc(("UNEXPECTED uninitialization!\n"));
+ hrc = autoCaller.hrc();
+ }
+
+ LogFlowThisFunc(("hrc=%08X\n", hrc));
+ LogFlowThisFuncLeave();
+
+ return hrc;
+}
+
+HRESULT Session::onNetworkAdapterChange(const ComPtr<INetworkAdapter> &aNetworkAdapter,
+ BOOL aChangeAdapter)
+
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onNetworkAdapterChange(aNetworkAdapter, aChangeAdapter);
+#else
+ RT_NOREF(aNetworkAdapter, aChangeAdapter);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onAudioAdapterChange(const ComPtr<IAudioAdapter> &aAudioAdapter)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onAudioAdapterChange(aAudioAdapter);
+#else
+ RT_NOREF(aAudioAdapter);
+ return S_OK;
+#endif
+
+}
+
+HRESULT Session::onHostAudioDeviceChange(const ComPtr<IHostAudioDevice> &aDevice,
+ BOOL aNew, AudioDeviceState_T aState,
+ const ComPtr<IVirtualBoxErrorInfo> &aErrInfo)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onHostAudioDeviceChange(aDevice, aNew, aState, aErrInfo);
+#else
+ RT_NOREF(aDevice, aNew, aState, aErrInfo);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onSerialPortChange(const ComPtr<ISerialPort> &aSerialPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onSerialPortChange(aSerialPort);
+#else
+ RT_NOREF(aSerialPort);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onParallelPortChange(const ComPtr<IParallelPort> &aParallelPort)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onParallelPortChange(aParallelPort);
+#else
+ RT_NOREF(aParallelPort);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onStorageControllerChange(const Guid &aMachineId, const Utf8Str &aControllerName)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onStorageControllerChange(aMachineId, aControllerName);
+#else
+ NOREF(aMachineId);
+ NOREF(aControllerName);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onMediumChange(const ComPtr<IMediumAttachment> &aMediumAttachment,
+ BOOL aForce)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onMediumChange(aMediumAttachment, aForce);
+#else
+ RT_NOREF(aMediumAttachment, aForce);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onVMProcessPriorityChange(VMProcPriority_T priority)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onVMProcessPriorityChange(priority);
+#else
+ RT_NOREF(priority);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onCPUChange(ULONG aCpu, BOOL aAdd)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onCPUChange(aCpu, aAdd);
+#else
+ RT_NOREF(aCpu, aAdd);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onCPUExecutionCapChange(ULONG aExecutionCap)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onCPUExecutionCapChange(aExecutionCap);
+#else
+ RT_NOREF(aExecutionCap);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onVRDEServerChange(BOOL aRestart)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onVRDEServerChange(aRestart);
+#else
+ RT_NOREF(aRestart);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onRecordingChange(BOOL aEnable)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onRecordingChange(aEnable);
+#else
+ RT_NOREF(aEnable);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBControllerChange()
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBControllerChange();
+#else
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onSharedFolderChange(BOOL aGlobal)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onSharedFolderChange(aGlobal);
+#else
+ RT_NOREF(aGlobal);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onClipboardModeChange(ClipboardMode_T aClipboardMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onClipboardModeChange(aClipboardMode);
+#else
+ RT_NOREF(aClipboardMode);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onClipboardFileTransferModeChange(BOOL aEnabled)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onClipboardFileTransferModeChange(RT_BOOL(aEnabled));
+#else
+ RT_NOREF(aEnabled);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onDnDModeChange(DnDMode_T aDndMode)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onDnDModeChange(aDndMode);
+#else
+ RT_NOREF(aDndMode);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onGuestDebugControlChange(const ComPtr<IGuestDebugControl> &aGuestDebugControl)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onGuestDebugControlChange(aGuestDebugControl);
+#else
+ RT_NOREF(aGuestDebugControl);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBDeviceAttach(const ComPtr<IUSBDevice> &aDevice,
+ const ComPtr<IVirtualBoxErrorInfo> &aError,
+ ULONG aMaskedInterfaces,
+ const com::Utf8Str &aCaptureFilename)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBDeviceAttach(aDevice, aError, aMaskedInterfaces, aCaptureFilename);
+#else
+ RT_NOREF(aDevice, aError, aMaskedInterfaces, aCaptureFilename);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onUSBDeviceDetach(const com::Guid &aId,
+ const ComPtr<IVirtualBoxErrorInfo> &aError)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onUSBDeviceDetach(aId.toUtf16().raw(), aError);
+#else
+ RT_NOREF(aId, aError);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onShowWindow(BOOL aCheck, BOOL *aCanShow, LONG64 *aWinId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+#endif
+
+ if (mState != SessionState_Locked)
+ {
+ /* the call from Machine issued when the session is open can arrive
+ * after the session starts closing or gets closed. Note that when
+ * aCheck is false, we return E_FAIL to indicate that aWinId we return
+ * is not valid */
+ *aCanShow = FALSE;
+ *aWinId = 0;
+ return aCheck ? S_OK : E_FAIL;
+ }
+
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ return mConsole->i_onShowWindow(aCheck, aCanShow, aWinId);
+#else
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onBandwidthGroupChange(const ComPtr<IBandwidthGroup> &aBandwidthGroup)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onBandwidthGroupChange(aBandwidthGroup);
+#else
+ RT_NOREF(aBandwidthGroup);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::onStorageDeviceChange(const ComPtr<IMediumAttachment> &aMediumAttachment, BOOL aRemove, BOOL aSilent)
+{
+ LogFlowThisFunc(("\n"));
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onStorageDeviceChange(aMediumAttachment, aRemove, aSilent);
+#else
+ RT_NOREF(aMediumAttachment, aRemove, aSilent);
+ return S_OK;
+#endif
+}
+
+HRESULT Session::accessGuestProperty(const com::Utf8Str &aName, const com::Utf8Str &aValue, const com::Utf8Str &aFlags,
+ ULONG aAccessMode, com::Utf8Str &aRetValue, LONG64 *aRetTimestamp, com::Utf8Str &aRetFlags)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+# ifndef VBOX_COM_INPROC_API_CLIENT
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ if (aName.isEmpty())
+ return E_INVALIDARG;
+ if (aAccessMode == 0 && !RT_VALID_PTR(aRetTimestamp))
+ return E_POINTER;
+
+ /* If this session is not in a VM process fend off the call. The caller
+ * handles this correctly, by doing the operation in VBoxSVC. */
+ if (!mConsole)
+ return E_ACCESSDENIED;
+
+ HRESULT hr;
+ if (aAccessMode == 2)
+ hr = mConsole->i_deleteGuestProperty(aName);
+ else if (aAccessMode == 1)
+ hr = mConsole->i_setGuestProperty(aName, aValue, aFlags);
+ else if (aAccessMode == 0)
+ hr = mConsole->i_getGuestProperty(aName, &aRetValue, aRetTimestamp, &aRetFlags);
+ else
+ hr = E_INVALIDARG;
+
+ return hr;
+# else /* VBOX_COM_INPROC_API_CLIENT */
+ /** @todo This is nonsense, non-VM API users shouldn't need to deal with this
+ * method call, VBoxSVC should be clever enough to see that the
+ * session doesn't have a console! */
+ RT_NOREF(aName, aValue, aFlags, aAccessMode, aRetValue, aRetTimestamp, aRetFlags);
+ return E_ACCESSDENIED;
+# endif /* VBOX_COM_INPROC_API_CLIENT */
+
+#else /* VBOX_WITH_GUEST_PROPS */
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_GUEST_PROPS */
+}
+
+HRESULT Session::enumerateGuestProperties(const com::Utf8Str &aPatterns,
+ std::vector<com::Utf8Str> &aKeys,
+ std::vector<com::Utf8Str> &aValues,
+ std::vector<LONG64> &aTimestamps,
+ std::vector<com::Utf8Str> &aFlags)
+{
+#if defined(VBOX_WITH_GUEST_PROPS) && !defined(VBOX_COM_INPROC_API_CLIENT)
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+
+ /* If this session is not in a VM process fend off the call. The caller
+ * handles this correctly, by doing the operation in VBoxSVC. */
+ if (!mConsole)
+ return E_ACCESSDENIED;
+
+ return mConsole->i_enumerateGuestProperties(aPatterns, aKeys, aValues, aTimestamps, aFlags);
+
+#else /* VBOX_WITH_GUEST_PROPS not defined */
+ RT_NOREF(aPatterns, aKeys, aValues, aTimestamps, aFlags);
+ ReturnComNotImplemented();
+#endif /* VBOX_WITH_GUEST_PROPS not defined */
+}
+
+HRESULT Session::onlineMergeMedium(const ComPtr<IMediumAttachment> &aMediumAttachment, ULONG aSourceIdx,
+ ULONG aTargetIdx, const ComPtr<IProgress> &aProgress)
+{
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_onlineMergeMedium(aMediumAttachment,
+ aSourceIdx, aTargetIdx,
+ aProgress);
+#else
+ RT_NOREF(aMediumAttachment, aSourceIdx, aTargetIdx, aProgress);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::reconfigureMediumAttachments(const std::vector<ComPtr<IMediumAttachment> > &aAttachments)
+{
+ if (mState != SessionState_Locked)
+ return setError(VBOX_E_INVALID_VM_STATE,
+ tr("Machine is not locked by session (session state: %s)."),
+ Global::stringifySessionState(mState));
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_reconfigureMediumAttachments(aAttachments);
+#else
+ RT_NOREF(aAttachments);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::enableVMMStatistics(BOOL aEnable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ mConsole->i_enableVMMStatistics(aEnable);
+
+ return S_OK;
+#else
+ RT_NOREF(aEnable);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::pauseWithReason(Reason_T aReason)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_pause(aReason);
+#else
+ RT_NOREF(aReason);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::resumeWithReason(Reason_T aReason)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ AutoWriteLock dummyLock(mConsole COMMA_LOCKVAL_SRC_POS);
+ return mConsole->i_resume(aReason, dummyLock);
+#else
+ RT_NOREF(aReason);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::saveStateWithReason(Reason_T aReason,
+ const ComPtr<IProgress> &aProgress,
+ const ComPtr<ISnapshot> &aSnapshot,
+ const Utf8Str &aStateFilePath,
+ BOOL aPauseVM, BOOL *aLeftPaused)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ bool fLeftPaused = false;
+ HRESULT hrc = mConsole->i_saveState(aReason, aProgress, aSnapshot, aStateFilePath, !!aPauseVM, fLeftPaused);
+ if (aLeftPaused)
+ *aLeftPaused = fLeftPaused;
+ return hrc;
+#else
+ RT_NOREF(aReason, aProgress, aSnapshot, aStateFilePath, aPauseVM, aLeftPaused);
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT Session::cancelSaveStateWithReason()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ AssertReturn(mState == SessionState_Locked, VBOX_E_INVALID_VM_STATE);
+ AssertReturn(mType == SessionType_WriteLock, VBOX_E_INVALID_OBJECT_STATE);
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ AssertReturn(mConsole, VBOX_E_INVALID_OBJECT_STATE);
+
+ return mConsole->i_cancelSaveState();
+#else
+ AssertFailed();
+ return E_NOTIMPL;
+#endif
+}
+
+// private methods
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Unlocks a machine associated with the current session.
+ *
+ * @param aFinalRelease called as a result of FinalRelease()
+ * @param aFromServer called as a result of Uninitialize()
+ * @param aLockW The write lock this object is protected with.
+ * Must be acquired already and will be released
+ * and later reacquired during the unlocking.
+ *
+ * @note To be called only from #uninit(), ISession::UnlockMachine() or
+ * ISession::Uninitialize().
+ */
+HRESULT Session::i_unlockMachine(bool aFinalRelease, bool aFromServer, AutoWriteLock &aLockW)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aFinalRelease=%d, isFromServer=%d\n",
+ aFinalRelease, aFromServer));
+
+ LogFlowThisFunc(("mState=%s, mType=%d\n", ::stringifySessionState(mState), mType));
+
+ Assert(aLockW.isWriteLockOnCurrentThread());
+
+ if (mState != SessionState_Locked)
+ {
+ Assert(mState == SessionState_Spawning);
+
+ /* The session object is going to be uninitialized before it has been
+ * assigned a direct console of the machine the client requested to open
+ * a remote session to using IVirtualBox:: openRemoteSession(). It is OK
+ * only if this close request comes from the server (for example, it
+ * detected that the VM process it started terminated before opening a
+ * direct session). Otherwise, it means that the client is too fast and
+ * trying to close the session before waiting for the progress object it
+ * got from IVirtualBox:: openRemoteSession() to complete, so assert. */
+ Assert(aFromServer);
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ Assert(!mClientTokenHolder);
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+ }
+
+ /* go to the closing state */
+ mState = SessionState_Unlocking;
+
+ if (mType == SessionType_WriteLock)
+ {
+#ifndef VBOX_COM_INPROC_API_CLIENT
+ if (!mConsole.isNull())
+ {
+ mConsole->uninit();
+ mConsole.setNull();
+ }
+#else
+ mRemoteMachine.setNull();
+#endif
+ }
+ else
+ {
+ mRemoteMachine.setNull();
+ mRemoteConsole.setNull();
+ }
+
+ ComPtr<IProgress> progress;
+
+ if (!aFinalRelease && !aFromServer)
+ {
+ /*
+ * We trigger OnSessionEnd() only when the session closes itself using
+ * Close(). Note that if isFinalRelease = TRUE here, this means that
+ * the client process has already initialized the termination procedure
+ * without issuing Close() and the IPC channel is no more operational --
+ * so we cannot call the server's method (it will definitely fail). The
+ * server will instead simply detect the abnormal client death (since
+ * OnSessionEnd() is not called) and reset the machine state to Aborted.
+ */
+
+ /*
+ * while waiting for OnSessionEnd() to complete one of our methods
+ * can be called by the server (for example, Uninitialize(), if the
+ * direct session has initiated a closure just a bit before us) so
+ * we need to release the lock to avoid deadlocks. The state is already
+ * SessionState_Closing here, so it's safe.
+ */
+ aLockW.release();
+
+ Assert(!aLockW.isWriteLockOnCurrentThread());
+
+ LogFlowThisFunc(("Calling mControl->OnSessionEnd()...\n"));
+ HRESULT hrc = mControl->OnSessionEnd(this, progress.asOutParam());
+ LogFlowThisFunc(("mControl->OnSessionEnd()=%08X\n", hrc));
+
+ aLockW.acquire();
+
+ /*
+ * If we get E_UNEXPECTED this means that the direct session has already
+ * been closed, we're just too late with our notification and nothing more
+ *
+ * bird: Seems E_ACCESSDENIED is what gets returned these days; see
+ * ObjectState::addCaller.
+ */
+ if (mType != SessionType_WriteLock && (hrc == E_UNEXPECTED || hrc == E_ACCESSDENIED))
+ hrc = S_OK;
+
+#if !defined(DEBUG_bird) && !defined(DEBUG_andy) /* I don't want clients crashing on me just because VBoxSVC went belly up. */
+ AssertComRC(hrc);
+#endif
+ }
+
+ mControl.setNull();
+
+ if (mType == SessionType_WriteLock)
+ {
+ if (mClientTokenHolder)
+ {
+ delete mClientTokenHolder;
+ mClientTokenHolder = NULL;
+ }
+
+ if (!aFinalRelease && !aFromServer)
+ {
+ /*
+ * Wait for the server to grab the semaphore and destroy the session
+ * machine (allowing us to open a new session with the same machine
+ * once this method returns)
+ */
+ Assert(!!progress);
+ if (progress)
+ progress->WaitForCompletion(-1);
+ }
+ }
+
+ mState = SessionState_Unlocked;
+ mType = SessionType_Null;
+
+ /* release the VirtualBox instance as the very last step */
+ mVirtualBox.setNull();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/USBDeviceImpl.cpp b/src/VBox/Main/src-client/USBDeviceImpl.cpp
new file mode 100644
index 00000000..cfc26071
--- /dev/null
+++ b/src/VBox/Main/src-client/USBDeviceImpl.cpp
@@ -0,0 +1,351 @@
+/* $Id: USBDeviceImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_USBDEVICE
+#include "LoggingNew.h"
+
+#include "USBDeviceImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/cpp/utils.h>
+
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR (OUSBDevice)
+
+HRESULT OUSBDevice::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void OUSBDevice::FinalRelease()
+{
+ uninit ();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the USB device object.
+ *
+ * @returns COM result indicator
+ * @param aUSBDevice The USB device (interface) to clone.
+ */
+HRESULT OUSBDevice::init(IUSBDevice *aUSBDevice)
+{
+ LogFlowThisFunc(("aUSBDevice=%p\n", aUSBDevice));
+
+ ComAssertRet(aUSBDevice, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = aUSBDevice->COMGETTER(VendorId)(&unconst(mData.vendorId));
+ ComAssertComRCRet(hrc, hrc);
+ ComAssertRet(mData.vendorId, E_INVALIDARG);
+
+ hrc = aUSBDevice->COMGETTER(ProductId)(&unconst(mData.productId));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Revision)(&unconst(mData.revision));
+ ComAssertComRCRet(hrc, hrc);
+
+ Bstr bstr;
+
+ hrc = aUSBDevice->COMGETTER(Manufacturer)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.manufacturer) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Product)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.product) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(SerialNumber)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.serialNumber) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Address)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.address) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Backend)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.backend) = bstr;
+
+ hrc = aUSBDevice->COMGETTER(Port)(&unconst(mData.port));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(PortPath)(bstr.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Version)(&unconst(mData.version));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Speed)(&unconst(mData.speed));
+ ComAssertComRCRet(hrc, hrc);
+
+ hrc = aUSBDevice->COMGETTER(Remote)(&unconst(mData.remote));
+ ComAssertComRCRet(hrc, hrc);
+
+ Bstr uuid;
+ hrc = aUSBDevice->COMGETTER(Id)(uuid.asOutParam());
+ ComAssertComRCRet(hrc, hrc);
+ unconst(mData.id) = Guid(uuid);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void OUSBDevice::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mData.id).clear();
+
+ unconst(mData.vendorId) = 0;
+ unconst(mData.productId) = 0;
+ unconst(mData.revision) = 0;
+
+ unconst(mData.manufacturer).setNull();
+ unconst(mData.product).setNull();
+ unconst(mData.serialNumber).setNull();
+
+ unconst(mData.address).setNull();
+ unconst(mData.backend).setNull();
+
+ unconst(mData.port) = 0;
+ unconst(mData.portPath).setNull();
+ unconst(mData.version) = 1;
+
+ unconst(mData.remote) = FALSE;
+}
+
+// IUSBDevice properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the GUID.
+ *
+ * @returns COM status code
+ * @param aId Address of result variable.
+ */
+HRESULT OUSBDevice::getId(com::Guid &aId)
+{
+ /* this is const, no need to lock */
+ aId = mData.id;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the vendor Id.
+ *
+ * @returns COM status code
+ * @param aVendorId Where to store the vendor id.
+ */
+HRESULT OUSBDevice::getVendorId(USHORT *aVendorId)
+{
+ /* this is const, no need to lock */
+ *aVendorId = mData.vendorId;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the product Id.
+ *
+ * @returns COM status code
+ * @param aProductId Where to store the product id.
+ */
+HRESULT OUSBDevice::getProductId(USHORT *aProductId)
+{
+ /* this is const, no need to lock */
+ *aProductId = mData.productId;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the revision BCD.
+ *
+ * @returns COM status code
+ * @param aRevision Where to store the revision BCD.
+ */
+HRESULT OUSBDevice::getRevision(USHORT *aRevision)
+{
+ /* this is const, no need to lock */
+ *aRevision = mData.revision;
+
+ return S_OK;
+}
+
+/**
+ * Returns the manufacturer string.
+ *
+ * @returns COM status code
+ * @param aManufacturer Where to put the return string.
+ */
+HRESULT OUSBDevice::getManufacturer(com::Utf8Str &aManufacturer)
+{
+ /* this is const, no need to lock */
+ aManufacturer = mData.manufacturer;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the product string.
+ *
+ * @returns COM status code
+ * @param aProduct Where to put the return string.
+ */
+HRESULT OUSBDevice::getProduct(com::Utf8Str &aProduct)
+{
+ /* this is const, no need to lock */
+ aProduct = mData.product;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the serial number string.
+ *
+ * @returns COM status code
+ * @param aSerialNumber Where to put the return string.
+ */
+HRESULT OUSBDevice::getSerialNumber(com::Utf8Str &aSerialNumber)
+{
+ /* this is const, no need to lock */
+ aSerialNumber = mData.serialNumber;
+
+ return S_OK;
+}
+
+
+/**
+ * Returns the host specific device address.
+ *
+ * @returns COM status code
+ * @param aAddress Where to put the return string.
+ */
+HRESULT OUSBDevice::getAddress(com::Utf8Str &aAddress)
+{
+ /* this is const, no need to lock */
+ aAddress = mData.address;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getPort(USHORT *aPort)
+{
+ /* this is const, no need to lock */
+ *aPort = mData.port;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getPortPath(com::Utf8Str &aPortPath)
+{
+ /* this is const, no need to lock */
+ aPortPath = mData.portPath;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getVersion(USHORT *aVersion)
+{
+ /* this is const, no need to lock */
+ *aVersion = mData.version;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getSpeed(USBConnectionSpeed_T *aSpeed)
+{
+ /* this is const, no need to lock */
+ *aSpeed = mData.speed;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getRemote(BOOL *aRemote)
+{
+ /* this is const, no need to lock */
+ *aRemote = mData.remote;
+
+ return S_OK;
+}
+
+/**
+ * Returns the device specific backend.
+ *
+ * @returns COM status code
+ * @param aBackend Where to put the return string.
+ */
+HRESULT OUSBDevice::getBackend(com::Utf8Str &aBackend)
+{
+ /* this is const, no need to lock */
+ aBackend = mData.backend;
+
+ return S_OK;
+}
+
+HRESULT OUSBDevice::getDeviceInfo(std::vector<com::Utf8Str> &aInfo)
+{
+ /* this is const, no need to lock */
+ aInfo.resize(2);
+ aInfo[0] = mData.manufacturer;
+ aInfo[1] = mData.product;
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/UsbCardReader.cpp b/src/VBox/Main/src-client/UsbCardReader.cpp
new file mode 100644
index 00000000..0b65088a
--- /dev/null
+++ b/src/VBox/Main/src-client/UsbCardReader.cpp
@@ -0,0 +1,1892 @@
+/* $Id: UsbCardReader.cpp $ */
+/** @file
+ * UsbCardReader - Driver Interface to USB Smart Card Reader emulation.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#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 *pbSendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ LogFlowFunc(("ENTER: pvUser:%p, pIoSendRequest:%p, pbSendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n",
+ pvUser, pIoSendRequest, pbSendBuffer, 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, pbSendBuffer, cbSendBuffer, cbRecvBuffer);
+
+ /* Clean up buffers allocated by driver */
+ RTMemFree(pIoSendRequest);
+ RTMemFree(pbSendBuffer);
+
+ 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
+ */
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnConnect} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdConnect, 5,
+ pThis, pvUser, pcszCardReaderName, u32ShareMode, u32PreferredProtocols);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnDisconnect} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdDisconnect, 3,
+ pThis, pvUser, u32Disposition);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnEstablishContext} */
+static DECLCALLBACK(int) drvCardReaderDownEstablishContext(PPDMICARDREADERDOWN pInterface)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER:\n"));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ int vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdEstablishContext, 1,
+ pThis);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnReleaseContext} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdReleaseContext, 2,
+ pThis, pvUser);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnStatus} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdStatus, 2,
+ pThis, pvUser);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnGetStatusChange} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdStatusChange, 5,
+ pThis, pvUser, u32Timeout, paReaderStats, cReaderStats);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnBeginTransaction} */
+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 vrc = VERR_NOT_SUPPORTED;
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnEndTransaction} */
+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 vrc = VERR_NOT_SUPPORTED;
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnTransmit} */
+static DECLCALLBACK(int) drvCardReaderDownTransmit(PPDMICARDREADERDOWN pInterface,
+ void *pvUser,
+ const PDMICARDREADER_IO_REQUEST *pIoSendRequest,
+ const uint8_t *pbSendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("ENTER: pvUser:%p, pIoSendRequest:%p, pbSendBuffer:%p, cbSendBuffer:%d, cbRecvBuffer:%d\n",
+ pvUser, pIoSendRequest, pbSendBuffer, cbSendBuffer, cbRecvBuffer));
+ PUSBCARDREADER pThis = RT_FROM_MEMBER(pInterface, USBCARDREADER, ICardReaderDown);
+ uint8_t *pbSendBufferCopy = NULL;
+ if ( pbSendBuffer
+ && cbSendBuffer)
+ {
+ pbSendBufferCopy = (uint8_t *)RTMemDup(pbSendBuffer, cbSendBuffer);
+ if (!pbSendBufferCopy)
+ return VERR_NO_MEMORY;
+ }
+ PDMICARDREADER_IO_REQUEST *pIoSendRequestCopy = NULL;
+ if (pIoSendRequest)
+ {
+ pIoSendRequestCopy = (PDMICARDREADER_IO_REQUEST *)RTMemDup(pIoSendRequest, pIoSendRequest->cbPciLength);
+ if (!pIoSendRequestCopy)
+ {
+ RTMemFree(pbSendBufferCopy);
+ return VERR_NO_MEMORY;
+ }
+ }
+ int vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0,RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdTransmit, 6,
+ pThis, pvUser, pIoSendRequestCopy, pbSendBufferCopy, cbSendBuffer, cbRecvBuffer);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnGetAttr} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdGetAttr, 4,
+ pThis, pvUser, u32AttribId, cbAttrib);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnSetAttr} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdSetAttr, 5,
+ pThis, pvUser, u32AttribId, pvAttribCopy, cbAttrib);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/** @interface_method_impl{PDMICARDREADERDOWN,pfnControl} */
+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 vrc = RTReqQueueCallEx(pThis->hReqQCardReaderCmd, NULL, 0, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvCardReaderCmdControl, 6,
+ pThis, pvUser, u32ControlCode, pvInBufferCopy, cbInBuffer, cbOutBuffer);
+ AssertRC(vrc);
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/*
+ * Cardreader driver thread routines
+ */
+static DECLCALLBACK(int) drvCardReaderThreadCmd(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ 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;
+ }
+
+ int vrc = VINF_SUCCESS;
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ vrc = RTReqQueueProcess(pThis->hReqQCardReaderCmd, RT_INDEFINITE_WAIT);
+
+ AssertMsg(vrc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED vrc=%Rrc\n", vrc));
+ }
+
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+static DECLCALLBACK(int) drvCardReaderWakeupFunc(PUSBCARDREADER pThis)
+{
+ NOREF(pThis);
+ /* Returning a VINF_* will cause RTReqQueueProcess return. */
+ return VWRN_STATE_CHANGED;
+}
+
+static DECLCALLBACK(int) drvCardReaderThreadCmdWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ LogFlowFunc(("ENTER: pDrvIns:%i\n", pDrvIns->iInstance));
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ AssertReturn(pThis->hReqQCardReaderCmd != NIL_RTREQQUEUE, VERR_INVALID_STATE);
+
+ PRTREQ pReq;
+ int vrc = RTReqQueueCall(pThis->hReqQCardReaderCmd, &pReq, 10000, (PFNRT)drvCardReaderWakeupFunc, 1, pThis);
+ AssertMsgRC(vrc, ("Inserting request into queue failed vrc=%Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ RTReqRelease(pReq);
+ /** @todo handle VERR_TIMEOUT */
+
+ return vrc;
+}
+
+
+/*
+ * 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 vrc = mParent->i_consoleVRDPServer()->SCardRequest(pvUser, u32Function, pvData, cbData);
+ LogFlowFunc(("%d %Rrc\n", u32Function, vrc));
+ return vrc;
+}
+
+int UsbCardReader::VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData)
+{
+ RT_NOREF(cbData);
+ int vrc = 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();
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ UCRREMOTE *pRemote = (UCRREMOTE *)RTMemAllocZ(sizeof(UCRREMOTE));
+ if (pRemote == NULL)
+ {
+ vrc = 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;
+
+ vrc = 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:
+ vrc = VERR_INVALID_PARAMETER;
+ AssertFailed();
+ break;
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::VRDEResponse(int rcRequest, void *pvUser, uint32_t u32Function, void *pvData, uint32_t cbData)
+{
+ RT_NOREF(cbData);
+ int vrc = 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;
+
+ vrc = 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;
+ }
+
+ if (pRsp->apszNames[i])
+ RTStrCopy(pRemote->reader.szReaderName, sizeof(pRemote->reader.szReaderName), pRsp->apszNames[i]);
+ else
+ RT_ZERO(pRemote->reader.szReaderName);
+ 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();
+ vrc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ return vrc;
+}
+
+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 vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_RELEASECONTEXT, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ else
+ m_pRemote->fContext = false;
+ }
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::GetStatusChange(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32Timeout,
+ PDMICARDREADER_READERSTATE *paReaderStats,
+ uint32_t cReaderStats)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc;
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable)
+ vrc = mpDrv->pICardReaderUp->pfnSetStatusChange(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ paReaderStats,
+ cReaderStats);
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETSTATUSCHANGE, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+ }
+
+ return vrc;
+}
+
+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 vrc;
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable)
+ vrc = mpDrv->pICardReaderUp->pfnConnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD,
+ VRDE_SCARD_PROTOCOL_T0);
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONNECT, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::Disconnect(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32Mode)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc;
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ vrc = mpDrv->pICardReaderUp->pfnDisconnect(mpDrv->pICardReaderUp,
+ pvUser,
+ VRDE_SCARD_E_NO_SMARTCARD);
+ else
+ {
+ UCRREQCTX *pCtx = (UCRREQCTX *)RTMemAlloc(sizeof(UCRREQCTX));
+ if (!pCtx)
+ vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_DISCONNECT, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ else
+ m_pRemote->reader.fHandle = false;
+ }
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::Status(struct USBCARDREADER *pDrv,
+ void *pvUser)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc;
+ if ( !m_pRemote
+ || !m_pRemote->fContext
+ || !m_pRemote->reader.fAvailable
+ || !m_pRemote->reader.fHandle)
+ vrc = 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)
+ vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_STATUS, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::Transmit(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ PDMICARDREADER_IO_REQUEST *pIoSendRequest,
+ uint8_t *pbSendBuffer,
+ uint32_t cbSendBuffer,
+ uint32_t cbRecvBuffer)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc = 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);
+
+ vrc = 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 = pbSendBuffer;
+ req.u32RecvLength = cbRecvBuffer;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_TRANSMIT, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+
+ return vrc;
+}
+
+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 vrc = 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);
+
+ vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_CONTROL, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::GetAttrib(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32AttrId,
+ uint32_t cbAttrib)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_GETATTRIB, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+
+ return vrc;
+}
+
+int UsbCardReader::SetAttrib(struct USBCARDREADER *pDrv,
+ void *pvUser,
+ uint32_t u32AttrId,
+ uint8_t *pu8Attrib,
+ uint32_t cbAttrib)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc = 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;
+
+ vrc = vrdeSCardRequest(pCtx, VRDE_SCARD_FN_SETATTRIB, &req, sizeof(req));
+ if (RT_FAILURE(vrc))
+ RTMemFree(pCtx);
+ }
+
+ return vrc;
+}
+
+
+/*
+ * 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 vrc = RTReqQueueDestroy(pThis->hReqQCardReaderCmd);
+ AssertRC(vrc);
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+ }
+
+ pThis->pUsbCardReader->mpDrv = NULL;
+ pThis->pUsbCardReader = NULL;
+ LogFlowFuncLeave();
+}
+
+/* static */ DECLCALLBACK(int) UsbCardReader::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags, pCfg);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ LogFlowFunc(("iInstance/%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags));
+ PUSBCARDREADER pThis = PDMINS_2_DATA(pDrvIns, PUSBCARDREADER);
+
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ com::Guid uuid(USBCARDREADER_OID);
+ pThis->pUsbCardReader = (UsbCardReader *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ AssertMsgReturn(RT_VALID_PTR(pThis->pUsbCardReader), ("Configuration error: No/bad USB card reader object value!\n"), VERR_NOT_FOUND);
+
+ pThis->pUsbCardReader->mpDrv = pThis;
+ pThis->pDrvIns = pDrvIns;
+
+ pDrvIns->IBase.pfnQueryInterface = UsbCardReader::drvQueryInterface;
+
+ pThis->ICardReaderDown.pfnEstablishContext = drvCardReaderDownEstablishContext;
+ pThis->ICardReaderDown.pfnReleaseContext = drvCardReaderDownReleaseContext;
+ pThis->ICardReaderDown.pfnConnect = drvCardReaderDownConnect;
+ pThis->ICardReaderDown.pfnDisconnect = drvCardReaderDownDisconnect;
+ pThis->ICardReaderDown.pfnStatus = drvCardReaderDownStatus;
+ pThis->ICardReaderDown.pfnGetStatusChange = drvCardReaderDownGetStatusChange;
+ pThis->ICardReaderDown.pfnBeginTransaction = drvCardReaderDownBeginTransaction;
+ pThis->ICardReaderDown.pfnEndTransaction = drvCardReaderDownEndTransaction;
+ pThis->ICardReaderDown.pfnTransmit = drvCardReaderDownTransmit;
+ pThis->ICardReaderDown.pfnGetAttr = drvCardReaderDownGetAttr;
+ pThis->ICardReaderDown.pfnSetAttr = drvCardReaderDownSetAttr;
+ pThis->ICardReaderDown.pfnControl = drvCardReaderDownControl;
+
+ pThis->pICardReaderUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMICARDREADERUP);
+ AssertReturn(pThis->pICardReaderUp, VERR_PDM_MISSING_INTERFACE);
+
+ /* Command Thread Synchronization primitives */
+ int vrc = RTReqQueueCreate(&pThis->hReqQCardReaderCmd);
+ AssertLogRelRCReturn(vrc, vrc);
+
+ vrc = PDMDrvHlpThreadCreate(pDrvIns,
+ &pThis->pThrCardReaderCmd,
+ pThis,
+ drvCardReaderThreadCmd /* worker routine */,
+ drvCardReaderThreadCmdWakeup /* wakeup routine */,
+ 128 * _1K, RTTHREADTYPE_IO, "UCRCMD");
+ if (RT_FAILURE(vrc))
+ {
+ RTReqQueueDestroy(pThis->hReqQCardReaderCmd);
+ pThis->hReqQCardReaderCmd = NIL_RTREQQUEUE;
+ }
+
+ LogFlowFunc(("LEAVE: %Rrc\n", vrc));
+ return vrc;
+}
+
+/* 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..15fdfeb7
--- /dev/null
+++ b/src/VBox/Main/src-client/UsbWebcamInterface.cpp
@@ -0,0 +1,492 @@
+/* $Id: UsbWebcamInterface.cpp $ */
+/** @file
+ * UsbWebcamInterface - Driver Interface for USB Webcam emulation.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_USB_WEBCAM
+#include "LoggingNew.h"
+
+#include "UsbWebcamInterface.h"
+#include "ConsoleImpl.h"
+#include "ConsoleVRDPServer.h"
+#include "EmulatedUSBImpl.h"
+
+#include <VBox/vmm/pdmwebcaminfs.h>
+#include <VBox/err.h>
+
+
+typedef struct EMWEBCAMREMOTE
+{
+ EmWebcam *pEmWebcam;
+
+ VRDEVIDEOINDEVICEHANDLE deviceHandle; /* The remote identifier. */
+
+ /* Received from the remote client. */
+ uint32_t u32Version; /* VRDE_VIDEOIN_NEGOTIATE_VERSION */
+ uint32_t fu32Capabilities; /* VRDE_VIDEOIN_NEGOTIATE_CAP_* */
+ VRDEVIDEOINDEVICEDESC *pDeviceDesc;
+ uint32_t cbDeviceDesc;
+
+ /* The device identifier for the PDM device.*/
+ uint64_t u64DeviceId;
+} EMWEBCAMREMOTE;
+
+typedef struct EMWEBCAMDRV
+{
+ EMWEBCAMREMOTE *pRemote;
+ PPDMIWEBCAMDEV pIWebcamUp;
+ PDMIWEBCAMDRV IWebcamDrv;
+} EMWEBCAMDRV, *PEMWEBCAMDRV;
+
+typedef struct EMWEBCAMREQCTX
+{
+ EMWEBCAMREMOTE *pRemote;
+ void *pvUser;
+} EMWEBCAMREQCTX;
+
+
+static DECLCALLBACK(void) drvEmWebcamReady(PPDMIWEBCAMDRV pInterface,
+ bool fReady)
+{
+ NOREF(fReady);
+
+ PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("pRemote:%p\n", pThis->pRemote));
+
+ if (pThis->pIWebcamUp)
+ {
+ pThis->pIWebcamUp->pfnAttached(pThis->pIWebcamUp,
+ pRemote->u64DeviceId,
+ pRemote->pDeviceDesc,
+ pRemote->cbDeviceDesc,
+ pRemote->u32Version,
+ pRemote->fu32Capabilities);
+ }
+}
+
+static DECLCALLBACK(int) drvEmWebcamControl(PPDMIWEBCAMDRV pInterface,
+ void *pvUser,
+ uint64_t u64DeviceId,
+ const struct VRDEVIDEOINCTRLHDR *pCtrl,
+ uint32_t cbCtrl)
+{
+ PEMWEBCAMDRV pThis = RT_FROM_MEMBER(pInterface, EMWEBCAMDRV, IWebcamDrv);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("pRemote:%p, u64DeviceId %lld\n", pRemote, u64DeviceId));
+
+ return pRemote->pEmWebcam->SendControl(pThis, pvUser, u64DeviceId, pCtrl, cbCtrl);
+}
+
+
+EmWebcam::EmWebcam(ConsoleVRDPServer *pServer)
+ :
+ mParent(pServer),
+ mpDrv(NULL),
+ mpRemote(NULL),
+ mu64DeviceIdSrc(0)
+{
+}
+
+EmWebcam::~EmWebcam()
+{
+ if (mpDrv)
+ {
+ mpDrv->pRemote = NULL;
+ mpDrv = NULL;
+ }
+}
+
+void EmWebcam::EmWebcamConstruct(EMWEBCAMDRV *pDrv)
+{
+ AssertReturnVoid(mpDrv == NULL);
+
+ mpDrv = pDrv;
+}
+
+void EmWebcam::EmWebcamDestruct(EMWEBCAMDRV *pDrv)
+{
+ AssertReturnVoid(pDrv == mpDrv);
+
+ if (mpRemote)
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+
+ RTMemFree(mpRemote->pDeviceDesc);
+ mpRemote->pDeviceDesc = NULL;
+ mpRemote->cbDeviceDesc = 0;
+
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ }
+
+ mpDrv->pRemote = NULL;
+ mpDrv = NULL;
+}
+
+void EmWebcam::EmWebcamCbNotify(uint32_t u32Id, const void *pvData, uint32_t cbData)
+{
+ int vrc = VINF_SUCCESS;
+
+ switch (u32Id)
+ {
+ case VRDE_VIDEOIN_NOTIFY_ID_ATTACH:
+ {
+ VRDEVIDEOINNOTIFYATTACH *p = (VRDEVIDEOINNOTIFYATTACH *)pvData;
+
+ /* Older versions did not report u32Version and fu32Capabilities. */
+ uint32_t u32Version = 1;
+ uint32_t fu32Capabilities = VRDE_VIDEOIN_NEGOTIATE_CAP_VOID;
+
+ if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, u32Version) + sizeof(p->u32Version))
+ u32Version = p->u32Version;
+
+ if (cbData >= RT_UOFFSETOF(VRDEVIDEOINNOTIFYATTACH, fu32Capabilities) + sizeof(p->fu32Capabilities))
+ fu32Capabilities = p->fu32Capabilities;
+
+ LogFlowFunc(("ATTACH[%d,%d] version %d, caps 0x%08X\n",
+ p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId,
+ u32Version, fu32Capabilities));
+
+ /* Currently only one device is allowed. */
+ if (mpRemote)
+ {
+ AssertFailed();
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)RTMemAllocZ(sizeof(EMWEBCAMREMOTE));
+ if (pRemote == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pRemote->pEmWebcam = this;
+ pRemote->deviceHandle = p->deviceHandle;
+ pRemote->u32Version = u32Version;
+ pRemote->fu32Capabilities = fu32Capabilities;
+ pRemote->pDeviceDesc = NULL;
+ pRemote->cbDeviceDesc = 0;
+ pRemote->u64DeviceId = ASMAtomicIncU64(&mu64DeviceIdSrc);
+
+ mpRemote = pRemote;
+
+ /* Tell the server that this webcam will be used. */
+ vrc = mParent->VideoInDeviceAttach(&mpRemote->deviceHandle, mpRemote);
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ break;
+ }
+
+ /* Get the device description. */
+ vrc = mParent->VideoInGetDeviceDesc(NULL, &mpRemote->deviceHandle);
+
+ if (RT_FAILURE(vrc))
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ break;
+ }
+
+ LogFlowFunc(("sent DeviceDesc\n"));
+ } break;
+
+ case VRDE_VIDEOIN_NOTIFY_ID_DETACH:
+ {
+ VRDEVIDEOINNOTIFYDETACH *p = (VRDEVIDEOINNOTIFYDETACH *)pvData; NOREF(p);
+ Assert(cbData == sizeof(VRDEVIDEOINNOTIFYDETACH));
+
+ LogFlowFunc(("DETACH[%d,%d]\n", p->deviceHandle.u32ClientId, p->deviceHandle.u32DeviceId));
+
+ /** @todo */
+ if (mpRemote)
+ {
+ if (mpDrv && mpDrv->pIWebcamUp)
+ mpDrv->pIWebcamUp->pfnDetached(mpDrv->pIWebcamUp, mpRemote->u64DeviceId);
+ /* mpRemote is deallocated in EmWebcamDestruct */
+ }
+ } break;
+
+ default:
+ vrc = VERR_INVALID_PARAMETER;
+ AssertFailed();
+ break;
+ }
+
+ return;
+}
+
+void EmWebcam::EmWebcamCbDeviceDesc(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINDEVICEDESC *pDeviceDesc, uint32_t cbDeviceDesc)
+{
+ RT_NOREF(pvUser);
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx;
+ Assert(pRemote == mpRemote);
+
+ LogFlowFunc(("mpDrv %p, rcRequest %Rrc %p %p %p %d\n",
+ mpDrv, rcRequest, pDeviceCtx, pvUser, pDeviceDesc, cbDeviceDesc));
+
+ if (RT_SUCCESS(rcRequest))
+ {
+ /* Save device description. */
+ Assert(pRemote->pDeviceDesc == NULL);
+ pRemote->pDeviceDesc = (VRDEVIDEOINDEVICEDESC *)RTMemDup(pDeviceDesc, cbDeviceDesc);
+ pRemote->cbDeviceDesc = cbDeviceDesc;
+
+ /* Try to attach the device. */
+ EmulatedUSB *pEUSB = mParent->getConsole()->i_getEmulatedUSB();
+ pEUSB->i_webcamAttachInternal("", "", "EmWebcam", pRemote);
+ }
+ else
+ {
+ mParent->VideoInDeviceDetach(&mpRemote->deviceHandle);
+ RTMemFree(mpRemote);
+ mpRemote = NULL;
+ }
+}
+
+void EmWebcam::EmWebcamCbControl(int rcRequest, void *pDeviceCtx, void *pvUser,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl)
+{
+ RT_NOREF(rcRequest);
+ EMWEBCAMREMOTE *pRemote = (EMWEBCAMREMOTE *)pDeviceCtx; NOREF(pRemote);
+ Assert(pRemote == mpRemote);
+
+ LogFlowFunc(("rcRequest %Rrc %p %p %p %d\n",
+ rcRequest, pDeviceCtx, pvUser, pControl, cbControl));
+
+ bool fResponse = (pvUser != NULL);
+
+ if (mpDrv && mpDrv->pIWebcamUp)
+ {
+ mpDrv->pIWebcamUp->pfnControl(mpDrv->pIWebcamUp,
+ fResponse,
+ pvUser,
+ mpRemote->u64DeviceId,
+ pControl,
+ cbControl);
+ }
+
+ RTMemFree(pvUser);
+}
+
+void EmWebcam::EmWebcamCbFrame(int rcRequest, void *pDeviceCtx,
+ const VRDEVIDEOINPAYLOADHDR *pFrame, uint32_t cbFrame)
+{
+ RT_NOREF(rcRequest, pDeviceCtx);
+ LogFlowFunc(("rcRequest %Rrc %p %p %d\n",
+ rcRequest, pDeviceCtx, pFrame, cbFrame));
+
+ if (mpDrv && mpDrv->pIWebcamUp)
+ {
+ if ( cbFrame >= sizeof(VRDEVIDEOINPAYLOADHDR)
+ && cbFrame >= pFrame->u8HeaderLength)
+ {
+ uint32_t cbImage = cbFrame - pFrame->u8HeaderLength;
+ const uint8_t *pu8Image = cbImage > 0? (const uint8_t *)pFrame + pFrame->u8HeaderLength: NULL;
+
+ mpDrv->pIWebcamUp->pfnFrame(mpDrv->pIWebcamUp,
+ mpRemote->u64DeviceId,
+ pFrame,
+ pFrame->u8HeaderLength,
+ pu8Image,
+ cbImage);
+ }
+ }
+}
+
+int EmWebcam::SendControl(EMWEBCAMDRV *pDrv, void *pvUser, uint64_t u64DeviceId,
+ const VRDEVIDEOINCTRLHDR *pControl, uint32_t cbControl)
+{
+ AssertReturn(pDrv == mpDrv, VERR_NOT_SUPPORTED);
+
+ int vrc = VINF_SUCCESS;
+
+ EMWEBCAMREQCTX *pCtx = NULL;
+
+ /* Verify that there is a remote device. */
+ if ( !mpRemote
+ || mpRemote->u64DeviceId != u64DeviceId)
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCtx = (EMWEBCAMREQCTX *)RTMemAlloc(sizeof(EMWEBCAMREQCTX));
+ if (!pCtx)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCtx->pRemote = mpRemote;
+ pCtx->pvUser = pvUser;
+
+ vrc = mParent->VideoInControl(pCtx, &mpRemote->deviceHandle, pControl, cbControl);
+
+ if (RT_FAILURE(vrc))
+ {
+ RTMemFree(pCtx);
+ }
+ }
+
+ return vrc;
+}
+
+/* static */ DECLCALLBACK(void *) EmWebcam::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+
+ LogFlowFunc(("pszIID:%s\n", pszIID));
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIWEBCAMDRV, &pThis->IWebcamDrv);
+ return NULL;
+}
+
+/* static */ DECLCALLBACK(void) EmWebcam::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+ EMWEBCAMREMOTE *pRemote = pThis->pRemote;
+
+ LogFlowFunc(("iInstance %d, pRemote %p, pIWebcamUp %p\n",
+ pDrvIns->iInstance, pRemote, pThis->pIWebcamUp));
+
+ if (pRemote && pRemote->pEmWebcam)
+ {
+ pRemote->pEmWebcam->EmWebcamDestruct(pThis);
+ }
+}
+
+/* static */ DECLCALLBACK(int) EmWebcam::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ LogFlowFunc(("iInstance:%d, pCfg:%p, fFlags:%x\n", pDrvIns->iInstance, pCfg, fFlags));
+
+ PEMWEBCAMDRV pThis = PDMINS_2_DATA(pDrvIns, PEMWEBCAMDRV);
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /* Check early that there is a device. No need to init anything if there is no device. */
+ pThis->pIWebcamUp = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIWEBCAMDEV);
+ if (pThis->pIWebcamUp == NULL)
+ {
+ LogRel(("USBWEBCAM: Emulated webcam device does not exist.\n"));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ char *pszId = NULL;
+ int vrc = pDrvIns->pHlpR3->pfnCFGMQueryStringAlloc(pCfg, "Id", &pszId);
+ if (RT_SUCCESS(vrc))
+ {
+ RTUUID UuidEmulatedUsbIf;
+ vrc = RTUuidFromStr(&UuidEmulatedUsbIf, EMULATEDUSBIF_OID); AssertRC(vrc);
+
+ PEMULATEDUSBIF pEmulatedUsbIf = (PEMULATEDUSBIF)PDMDrvHlpQueryGenericUserObject(pDrvIns, &UuidEmulatedUsbIf);
+ AssertPtrReturn(pEmulatedUsbIf, VERR_INVALID_PARAMETER);
+
+ vrc = pEmulatedUsbIf->pfnQueryEmulatedUsbDataById(pEmulatedUsbIf->pvUser, pszId,
+ NULL /*ppvEmUsbCb*/, NULL /*ppvEmUsbCbData*/, (void **)&pThis->pRemote);
+ pDrvIns->pHlpR3->pfnMMHeapFree(pDrvIns, pszId);
+ AssertRCReturn(vrc, vrc);
+ }
+ else
+ return vrc;
+
+ /* Everything ok. Initialize. */
+ pThis->pRemote->pEmWebcam->EmWebcamConstruct(pThis);
+
+ pDrvIns->IBase.pfnQueryInterface = drvQueryInterface;
+
+ pThis->IWebcamDrv.pfnReady = drvEmWebcamReady;
+ pThis->IWebcamDrv.pfnControl = drvEmWebcamControl;
+
+ return VINF_SUCCESS;
+}
+
+/* static */ const PDMDRVREG EmWebcam::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName[32] */
+ "EmWebcam",
+ /* szRCMod[32] */
+ "",
+ /* szR0Mod[32] */
+ "",
+ /* pszDescription */
+ "Main Driver communicating with VRDE",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_USB,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(EMWEBCAMDRV),
+ /* pfnConstruct */
+ EmWebcam::drvConstruct,
+ /* pfnDestruct */
+ EmWebcam::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/VBoxDriversRegister.cpp b/src/VBox/Main/src-client/VBoxDriversRegister.cpp
new file mode 100644
index 00000000..93bfc79c
--- /dev/null
+++ b/src/VBox/Main/src-client/VBoxDriversRegister.cpp
@@ -0,0 +1,124 @@
+/* $Id: VBoxDriversRegister.cpp $ */
+/** @file
+ *
+ * Main driver registration.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "LoggingNew.h"
+
+#include "MouseImpl.h"
+#include "KeyboardImpl.h"
+#include "DisplayImpl.h"
+#include "VMMDev.h"
+#include "NvramStoreImpl.h"
+#ifdef VBOX_WITH_AUDIO_VRDE
+# include "DrvAudioVRDE.h"
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+# include "DrvAudioRec.h"
+#endif
+#include "UsbWebcamInterface.h"
+#ifdef VBOX_WITH_USB_CARDREADER
+# include "UsbCardReader.h"
+#endif
+#include "ConsoleImpl.h"
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+# include "PCIRawDevImpl.h"
+#endif
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/version.h>
+
+
+/**
+ * Register the main drivers.
+ *
+ * @returns VBox status code.
+ * @param pCallbacks Pointer to the callback table.
+ * @param u32Version VBox version number.
+ */
+extern "C" DECLEXPORT(int) VBoxDriversRegister(PCPDMDRVREGCB pCallbacks, uint32_t u32Version)
+{
+ LogFlow(("VBoxDriversRegister: u32Version=%#x\n", u32Version));
+ AssertReleaseMsg(u32Version == VBOX_VERSION, ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION));
+
+ int vrc = pCallbacks->pfnRegister(pCallbacks, &Mouse::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &Keyboard::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &Display::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &VMMDev::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#ifdef VBOX_WITH_AUDIO_VRDE
+ vrc = pCallbacks->pfnRegister(pCallbacks, &AudioVRDE::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+#ifdef VBOX_WITH_AUDIO_RECORDING
+ vrc = pCallbacks->pfnRegister(pCallbacks, &AudioVideoRec::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &EmWebcam::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#ifdef VBOX_WITH_USB_CARDREADER
+ vrc = pCallbacks->pfnRegister(pCallbacks, &UsbCardReader::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &Console::DrvStatusReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#ifdef VBOX_WITH_PCI_PASSTHROUGH
+ vrc = pCallbacks->pfnRegister(pCallbacks, &PCIRawDev::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#endif
+
+ vrc = pCallbacks->pfnRegister(pCallbacks, &NvramStore::DrvReg);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ 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..9d67e9bb
--- /dev/null
+++ b/src/VBox/Main/src-client/VMMDevInterface.cpp
@@ -0,0 +1,1265 @@
+/* $Id: VMMDevInterface.cpp $ */
+/** @file
+ * VirtualBox Driver Interface to VMM device.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VMMDEVINTERFACES
+#include "LoggingNew.h"
+
+#include "VMMDev.h"
+#include "ConsoleImpl.h"
+#include "DisplayImpl.h"
+#include "GuestImpl.h"
+#include "MouseImpl.h"
+
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/VMMDev.h>
+#include <VBox/shflsvc.h>
+#include <iprt/asm.h>
+
+#ifdef VBOX_WITH_HGCM
+# include "HGCM.h"
+# include "HGCMObjects.h"
+#endif
+
+//
+// defines
+//
+
+#ifdef RT_OS_OS2
+# define VBOXSHAREDFOLDERS_DLL "VBoxSFld"
+#else
+# define VBOXSHAREDFOLDERS_DLL "VBoxSharedFolders"
+#endif
+
+//
+// globals
+//
+
+
+/**
+ * VMMDev driver instance data.
+ */
+typedef struct DRVMAINVMMDEV
+{
+ /** Pointer to the VMMDev object. */
+ VMMDev *pVMMDev;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the VMMDev port interface of the driver/device above us. */
+ PPDMIVMMDEVPORT pUpPort;
+ /** Our VMM device connector interface. */
+ PDMIVMMDEVCONNECTOR Connector;
+
+#ifdef VBOX_WITH_HGCM
+ /** Pointer to the HGCM port interface of the driver/device above us. */
+ PPDMIHGCMPORT pHGCMPort;
+ /** Our HGCM connector interface. */
+ PDMIHGCMCONNECTOR HGCMConnector;
+#endif
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ HGCMSVCEXTHANDLE hHgcmSvcExtGstProps;
+#endif
+#ifdef VBOX_WITH_GUEST_CONTROL
+ HGCMSVCEXTHANDLE hHgcmSvcExtGstCtrl;
+#endif
+} DRVMAINVMMDEV, *PDRVMAINVMMDEV;
+
+//
+// constructor / destructor
+//
+VMMDev::VMMDev(Console *console)
+ : mpDrv(NULL)
+ , mParent(console)
+{
+ int vrc = RTSemEventCreate(&mCredentialsEvent);
+ AssertRC(vrc);
+#ifdef VBOX_WITH_HGCM
+ vrc = HGCMHostInit();
+ AssertRC(vrc);
+ m_fHGCMActive = true;
+#endif /* VBOX_WITH_HGCM */
+ mu32CredentialsFlags = 0;
+}
+
+VMMDev::~VMMDev()
+{
+#ifdef VBOX_WITH_HGCM
+ if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true))
+ HGCMHostShutdown(true /*fUvmIsInvalid*/);
+#endif
+ RTSemEventDestroy(mCredentialsEvent);
+ if (mpDrv)
+ mpDrv->pVMMDev = NULL;
+ mpDrv = NULL;
+}
+
+PPDMIVMMDEVPORT VMMDev::getVMMDevPort()
+{
+ if (!mpDrv)
+ return NULL;
+ return mpDrv->pUpPort;
+}
+
+
+
+//
+// public methods
+//
+
+/**
+ * Wait on event semaphore for guest credential judgement result.
+ */
+int VMMDev::WaitCredentialsJudgement(uint32_t u32Timeout, uint32_t *pu32CredentialsFlags)
+{
+ if (u32Timeout == 0)
+ {
+ u32Timeout = 5000;
+ }
+
+ int vrc = RTSemEventWait(mCredentialsEvent, u32Timeout);
+
+ if (RT_SUCCESS(vrc))
+ {
+ *pu32CredentialsFlags = mu32CredentialsFlags;
+ }
+
+ return vrc;
+}
+
+int VMMDev::SetCredentialsJudgementResult(uint32_t u32Flags)
+{
+ mu32CredentialsFlags = u32Flags;
+
+ int vrc = RTSemEventSignal(mCredentialsEvent);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestStatus}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestStatus(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFacility, uint16_t uStatus,
+ uint32_t fFlags, PCRTTIMESPEC pTimeSpecTS)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(guest);
+
+ guest->i_setAdditionsStatus((VBoxGuestFacilityType)uFacility, (VBoxGuestFacilityStatus)uStatus, fFlags, pTimeSpecTS);
+ pConsole->i_onAdditionsStateChange();
+}
+
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestUserState}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestUserState(PPDMIVMMDEVCONNECTOR pInterface,
+ const char *pszUser, const char *pszDomain,
+ uint32_t uState,
+ const uint8_t *pabDetails, uint32_t cbDetails)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pDrv);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ AssertPtr(pConsole);
+
+ /* Store that information in IGuest. */
+ Guest* pGuest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ pGuest->i_onUserStateChanged(Utf8Str(pszUser), Utf8Str(pszDomain), (VBoxGuestUserState)uState, pabDetails, cbDetails);
+}
+
+
+/**
+ * Reports Guest Additions API and OS version.
+ *
+ * Called whenever the Additions issue a guest version report request or the VM
+ * is reset.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param guestInfo Pointer to guest information structure.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestInfo(PPDMIVMMDEVCONNECTOR pInterface, const VBoxGuestInfo *guestInfo)
+{
+ AssertPtrReturnVoid(guestInfo);
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(guest);
+
+ if (guestInfo->interfaceVersion != 0)
+ {
+ char version[16];
+ RTStrPrintf(version, sizeof(version), "%d", guestInfo->interfaceVersion);
+ guest->i_setAdditionsInfo(Bstr(version), guestInfo->osType);
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ pConsole->i_onAdditionsStateChange();
+
+ if (guestInfo->interfaceVersion < VMMDEV_VERSION)
+ pConsole->i_onAdditionsOutdated();
+ }
+ else
+ {
+ /*
+ * The Guest Additions was disabled because of a reset
+ * or driver unload.
+ */
+ guest->i_setAdditionsInfo(Bstr(), guestInfo->osType); /* Clear interface version + OS type. */
+ /** @todo Would be better if GuestImpl.cpp did all this in the above method call
+ * while holding down the. */
+ guest->i_setAdditionsInfo2(0, "", 0, 0); /* Clear Guest Additions version. */
+ RTTIMESPEC TimeSpecTS;
+ RTTimeNow(&TimeSpecTS);
+ guest->i_setAdditionsStatus(VBoxGuestFacilityType_All, VBoxGuestFacilityStatus_Inactive, 0 /*fFlags*/, &TimeSpecTS);
+ pConsole->i_onAdditionsStateChange();
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateGuestInfo2}
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestInfo2(PPDMIVMMDEVCONNECTOR pInterface, uint32_t uFullVersion,
+ const char *pszName, uint32_t uRevision, uint32_t fFeatures)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pszName);
+ Assert(uFullVersion);
+
+ /* Store that information in IGuest. */
+ Guest *pGuest = pDrv->pVMMDev->getParent()->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ /* Just pass it on... */
+ pGuest->i_setAdditionsInfo2(uFullVersion, pszName, uRevision, fFeatures);
+
+ /*
+ * No need to tell the console interface about the update;
+ * vmmdevUpdateGuestInfo takes care of that when called as the
+ * last event in the chain.
+ */
+}
+
+/**
+ * Update the Guest Additions capabilities.
+ * This is called when the Guest Additions capabilities change. The new capabilities
+ * are given and the connector should update its internal state.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param newCapabilities New capabilities.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateGuestCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t newCapabilities)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ AssertPtr(pDrv);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* store that information in IGuest */
+ Guest* pGuest = pConsole->i_getGuest();
+ AssertPtrReturnVoid(pGuest);
+
+ /*
+ * Report our current capabilities (and assume none is active yet).
+ */
+ pGuest->i_setSupportedFeatures(newCapabilities);
+
+ /*
+ * Tell the Display, so that it can update the "supports graphics"
+ * capability if the graphics card has not asserted it.
+ */
+ Display* pDisplay = pConsole->i_getDisplay();
+ AssertPtrReturnVoid(pDisplay);
+ pDisplay->i_handleUpdateVMMDevSupportsGraphics(RT_BOOL(newCapabilities & VMMDEV_GUEST_SUPPORTS_GRAPHICS));
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ pConsole->i_onAdditionsStateChange();
+}
+
+/**
+ * Update the mouse capabilities.
+ * This is called when the mouse capabilities change. The new capabilities
+ * are given and the connector should update its internal state.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param fNewCaps New capabilities.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdateMouseCapabilities(PPDMIVMMDEVCONNECTOR pInterface, uint32_t fNewCaps)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /*
+ * Tell the console interface about the event
+ * so that it can notify its consumers.
+ */
+ Mouse *pMouse = pConsole->i_getMouse();
+ if (pMouse) /** @todo and if not? Can that actually happen? */
+ pMouse->i_onVMMDevGuestCapsChange(fNewCaps & VMMDEV_MOUSE_GUEST_MASK);
+}
+
+/**
+ * Update the pointer shape or visibility.
+ *
+ * This is called when the mouse pointer shape changes or pointer is hidden/displaying.
+ * The new shape is passed as a caller allocated buffer that will be freed after returning.
+ *
+ * @param pInterface Pointer to this interface.
+ * @param fVisible Whether the pointer is visible or not.
+ * @param fAlpha Alpha channel information is present.
+ * @param xHot Horizontal coordinate of the pointer hot spot.
+ * @param yHot Vertical coordinate of the pointer hot spot.
+ * @param width Pointer width in pixels.
+ * @param height Pointer height in pixels.
+ * @param pShape The shape buffer. If NULL, then only pointer visibility is being changed.
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(void) vmmdevUpdatePointerShape(PPDMIVMMDEVCONNECTOR pInterface, bool fVisible, bool fAlpha,
+ uint32_t xHot, uint32_t yHot,
+ uint32_t width, uint32_t height,
+ void *pShape)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* tell the console about it */
+ uint32_t cbShape = 0;
+ if (pShape)
+ {
+ cbShape = (width + 7) / 8 * height; /* size of the AND mask */
+ cbShape = ((cbShape + 3) & ~3) + width * 4 * height; /* + gap + size of the XOR mask */
+ }
+ pConsole->i_onMousePointerShapeChange(fVisible, fAlpha, xHot, yHot, width, height, (uint8_t *)pShape, cbShape);
+}
+
+DECLCALLBACK(int) iface_VideoAccelEnable(PPDMIVMMDEVCONNECTOR pInterface, bool fEnable, VBVAMEMORY *pVbvaMemory)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ Display *display = pConsole->i_getDisplay();
+
+ if (display)
+ {
+ Log9(("MAIN::VMMDevInterface::iface_VideoAccelEnable: %d, %p\n", fEnable, pVbvaMemory));
+ return display->VideoAccelEnableVMMDev(fEnable, pVbvaMemory);
+ }
+
+ return VERR_NOT_SUPPORTED;
+}
+DECLCALLBACK(void) iface_VideoAccelFlush(PPDMIVMMDEVCONNECTOR pInterface)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ Display *display = pConsole->i_getDisplay();
+
+ if (display)
+ {
+ Log9(("MAIN::VMMDevInterface::iface_VideoAccelFlush\n"));
+ display->VideoAccelFlushVMMDev();
+ }
+}
+
+DECLCALLBACK(int) vmmdevVideoModeSupported(PPDMIVMMDEVCONNECTOR pInterface, uint32_t display, uint32_t width, uint32_t height,
+ uint32_t bpp, bool *fSupported)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!fSupported)
+ return VERR_INVALID_PARAMETER;
+#ifdef DEBUG_sunlover
+ Log(("vmmdevVideoModeSupported: [%d]: %dx%dx%d\n", display, width, height, bpp));
+#endif
+ IFramebuffer *framebuffer = NULL;
+ HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(display, &framebuffer);
+ if (SUCCEEDED(hrc) && framebuffer)
+ {
+ framebuffer->VideoModeSupported(width, height, bpp, (BOOL*)fSupported);
+ framebuffer->Release();
+ }
+ else
+ {
+#ifdef DEBUG_sunlover
+ Log(("vmmdevVideoModeSupported: hrc %x, framebuffer %p!!!\n", hrc, framebuffer));
+#endif
+ *fSupported = true;
+ }
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevGetHeightReduction(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *heightReduction)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!heightReduction)
+ return VERR_INVALID_PARAMETER;
+ IFramebuffer *framebuffer = NULL;
+ HRESULT hrc = pConsole->i_getDisplay()->QueryFramebuffer(0, &framebuffer);
+ if (SUCCEEDED(hrc) && framebuffer)
+ {
+ framebuffer->COMGETTER(HeightReduction)((ULONG*)heightReduction);
+ framebuffer->Release();
+ }
+ else
+ *heightReduction = 0;
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevSetCredentialsJudgementResult(PPDMIVMMDEVCONNECTOR pInterface, uint32_t u32Flags)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+
+ if (pDrv->pVMMDev)
+ return pDrv->pVMMDev->SetCredentialsJudgementResult(u32Flags);
+
+ return VERR_GENERAL_FAILURE;
+}
+
+DECLCALLBACK(int) vmmdevSetVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cRect, PRTRECT pRect)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Forward to Display, which calls corresponding framebuffers. */
+ pConsole->i_getDisplay()->i_handleSetVisibleRegion(cRect, pRect);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIVMMDEVCONNECTOR,pfnUpdateMonitorPositions}
+ */
+static DECLCALLBACK(int) vmmdevUpdateMonitorPositions(PPDMIVMMDEVCONNECTOR pInterface, uint32_t cPositions, PCRTPOINT paPositions)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ pConsole->i_getDisplay()->i_handleUpdateMonitorPositions(cPositions, paPositions);
+
+ return VINF_SUCCESS;
+}
+
+DECLCALLBACK(int) vmmdevQueryVisibleRegion(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcRects, PRTRECT paRects)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ /* Forward to Display, which calls corresponding framebuffers. */
+ pConsole->i_getDisplay()->i_handleQueryVisibleRegion(pcRects, paRects);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Request the statistics interval
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pulInterval Pointer to interval in seconds
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevQueryStatisticsInterval(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pulInterval)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ ULONG val = 0;
+
+ if (!pulInterval)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ guest->COMGETTER(StatisticsUpdateInterval)(&val);
+ *pulInterval = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Query the current balloon size
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pcbBalloon Balloon size
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevQueryBalloonSize(PPDMIVMMDEVCONNECTOR pInterface, uint32_t *pcbBalloon)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+ ULONG val = 0;
+
+ if (!pcbBalloon)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ guest->COMGETTER(MemoryBalloonSize)(&val);
+ *pcbBalloon = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Query the current page fusion setting
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pfPageFusionEnabled Pointer to boolean
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevIsPageFusionEnabled(PPDMIVMMDEVCONNECTOR pInterface, bool *pfPageFusionEnabled)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ if (!pfPageFusionEnabled)
+ return VERR_INVALID_POINTER;
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ *pfPageFusionEnabled = !!guest->i_isPageFusionEnabled();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Report new guest statistics
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface.
+ * @param pGuestStats Guest statistics
+ * @thread The emulation thread.
+ */
+DECLCALLBACK(int) vmmdevReportStatistics(PPDMIVMMDEVCONNECTOR pInterface, VBoxGuestStatistics *pGuestStats)
+{
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, Connector);
+ Console *pConsole = pDrv->pVMMDev->getParent();
+
+ AssertPtrReturn(pGuestStats, VERR_INVALID_POINTER);
+
+ /* store that information in IGuest */
+ Guest* guest = pConsole->i_getGuest();
+ AssertPtrReturn(guest, VERR_GENERAL_FAILURE);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_IDLE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUIDLE, pGuestStats->u32CpuLoad_Idle);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_KERNEL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUKERNEL, pGuestStats->u32CpuLoad_Kernel);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_CPU_LOAD_USER)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_CPUUSER, pGuestStats->u32CpuLoad_User);
+
+
+ /** @todo r=bird: Convert from 4KB to 1KB units?
+ * CollectorGuestHAL::i_getGuestMemLoad says it returns KB units to
+ * preCollect(). I might be wrong ofc, this is convoluted code... */
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_TOTAL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMTOTAL, pGuestStats->u32PhysMemTotal);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_AVAIL)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMFREE, pGuestStats->u32PhysMemAvail);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PHYS_MEM_BALLOON)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMBALLOON, pGuestStats->u32PhysMemBalloon);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_MEM_SYSTEM_CACHE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_MEMCACHE, pGuestStats->u32MemSystemCache);
+
+ if (pGuestStats->u32StatCaps & VBOX_GUEST_STAT_PAGE_FILE_SIZE)
+ guest->i_setStatistic(pGuestStats->u32CpuId, GUESTSTATTYPE_PAGETOTAL, pGuestStats->u32PageFileSize);
+
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_HGCM
+
+/* HGCM connector interface */
+
+static DECLCALLBACK(int) iface_hgcmConnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd,
+ PHGCMSERVICELOCATION pServiceLocation,
+ uint32_t *pu32ClientID)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if ( !pServiceLocation
+ || ( pServiceLocation->type != VMMDevHGCMLoc_LocalHost
+ && pServiceLocation->type != VMMDevHGCMLoc_LocalHost_Existing))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Check if service name is a string terminated by zero*/
+ size_t cchInfo = 0;
+ if (RTStrNLenEx(pServiceLocation->u.host.achName, sizeof(pServiceLocation->u.host.achName), &cchInfo) != VINF_SUCCESS)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+ return HGCMGuestConnect(pDrv->pHGCMPort, pCmd, pServiceLocation->u.host.achName, pu32ClientID);
+}
+
+static DECLCALLBACK(int) iface_hgcmDisconnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ return HGCMGuestDisconnect(pDrv->pHGCMPort, pCmd, u32ClientID);
+}
+
+static DECLCALLBACK(int) iface_hgcmCall(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID,
+ uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms, uint64_t tsArrival)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+
+ if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ return HGCMGuestCall(pDrv->pHGCMPort, pCmd, u32ClientID, u32Function, cParms, paParms, tsArrival);
+}
+
+static DECLCALLBACK(void) iface_hgcmCancelled(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t idClient)
+{
+ Log9(("Enter\n"));
+
+ PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);
+ if ( pDrv->pVMMDev
+ && pDrv->pVMMDev->hgcmIsActive())
+ return HGCMGuestCancelled(pDrv->pHGCMPort, pCmd, idClient);
+}
+
+/**
+ * Execute state save operation.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+/*static*/ DECLCALLBACK(int) VMMDev::hgcmSave(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ Log9(("Enter\n"));
+
+ AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2);
+ Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent);
+ AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3);
+ return HGCMHostSaveState(pSSM, ptrVM.vtable());
+}
+
+
+/**
+ * Execute state load operation.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Driver instance of the driver which registered the data unit.
+ * @param pSSM SSM operation handle.
+ * @param uVersion Data layout version.
+ * @param uPass The data pass.
+ */
+/*static*/ DECLCALLBACK(int) VMMDev::hgcmLoad(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlowFunc(("Enter\n"));
+
+ if ( uVersion != HGCM_SAVED_STATE_VERSION
+ && uVersion != HGCM_SAVED_STATE_VERSION_V2)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ AssertReturn(pThis->pVMMDev, VERR_INTERNAL_ERROR_2);
+ Console::SafeVMPtrQuiet ptrVM(pThis->pVMMDev->mParent);
+ AssertReturn(ptrVM.isOk(), VERR_INTERNAL_ERROR_3);
+ return HGCMHostLoadState(pSSM, ptrVM.vtable(), uVersion);
+}
+
+int VMMDev::hgcmLoadService(const char *pszServiceLibrary, const char *pszServiceName)
+{
+ if (!hgcmIsActive())
+ return VERR_INVALID_STATE;
+
+ /** @todo Construct all the services in the VMMDev::drvConstruct()!! */
+ Assert( (mpDrv && mpDrv->pHGCMPort)
+ || !strcmp(pszServiceLibrary, "VBoxHostChannel")
+ || !strcmp(pszServiceLibrary, "VBoxSharedClipboard")
+ || !strcmp(pszServiceLibrary, "VBoxDragAndDropSvc")
+ || !strcmp(pszServiceLibrary, "VBoxGuestPropSvc")
+ || !strcmp(pszServiceLibrary, "VBoxSharedCrOpenGL")
+ );
+ Console::SafeVMPtrQuiet ptrVM(mParent);
+ return HGCMHostLoad(pszServiceLibrary, pszServiceName, ptrVM.rawUVM(), ptrVM.vtable(), mpDrv ? mpDrv->pHGCMPort : NULL);
+}
+
+int VMMDev::hgcmHostCall(const char *pszServiceName, uint32_t u32Function,
+ uint32_t cParms, PVBOXHGCMSVCPARM paParms)
+{
+ if (!hgcmIsActive())
+ return VERR_INVALID_STATE;
+ return HGCMHostCall(pszServiceName, u32Function, cParms, paParms);
+}
+
+/**
+ * Used by Console::i_powerDown to shut down the services before the VM is destroyed.
+ */
+void VMMDev::hgcmShutdown(bool fUvmIsInvalid /*= false*/)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (mpDrv && mpDrv->hHgcmSvcExtGstProps)
+ {
+ HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstProps);
+ mpDrv->hHgcmSvcExtGstProps = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ if (mpDrv && mpDrv->hHgcmSvcExtGstCtrl)
+ {
+ HGCMHostUnregisterServiceExtension(mpDrv->hHgcmSvcExtGstCtrl);
+ mpDrv->hHgcmSvcExtGstCtrl = NULL;
+ }
+#endif
+
+ if (ASMAtomicCmpXchgBool(&m_fHGCMActive, false, true))
+ HGCMHostShutdown(fUvmIsInvalid);
+}
+
+#endif /* HGCM */
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) VMMDev::drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINVMMDEV pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIVMMDEVCONNECTOR, &pDrv->Connector);
+#ifdef VBOX_WITH_HGCM
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHGCMCONNECTOR, &pDrv->HGCMConnector);
+#endif
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnSuspend}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvSuspend(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_SUSPEND);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnResume}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvResume(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_RESUME);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOff(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOn}
+ */
+/*static*/ DECLCALLBACK(void) VMMDev::drvPowerOn(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+#ifdef VBOX_WITH_HGCM
+ HGCMBroadcastEvent(HGCMNOTIFYEVENT_POWER_ON);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnReset}
+ */
+DECLCALLBACK(void) VMMDev::drvReset(PPDMDRVINS pDrvIns)
+{
+ RT_NOREF(pDrvIns);
+ LogFlow(("VMMDev::drvReset: iInstance=%d\n", pDrvIns->iInstance));
+#ifdef VBOX_WITH_HGCM
+ HGCMHostReset(false /*fForShutdown*/);
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct}
+ */
+DECLCALLBACK(void) VMMDev::drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlow(("VMMDev::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (pThis->hHgcmSvcExtGstProps)
+ {
+ HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstProps);
+ pThis->hHgcmSvcExtGstProps = NULL;
+ }
+#endif
+
+#ifdef VBOX_WITH_GUEST_CONTROL
+ if (pThis->hHgcmSvcExtGstCtrl)
+ {
+ HGCMHostUnregisterServiceExtension(pThis->hHgcmSvcExtGstCtrl);
+ pThis->hHgcmSvcExtGstCtrl = NULL;
+ }
+#endif
+
+ if (pThis->pVMMDev)
+ {
+#ifdef VBOX_WITH_HGCM
+ /* When VM construction goes wrong, we prefer shutting down HGCM here
+ while pUVM is still valid, rather than in ~VMMDev. */
+ if (ASMAtomicCmpXchgBool(&pThis->pVMMDev->m_fHGCMActive, false, true))
+ HGCMHostShutdown();
+#endif
+ pThis->pVMMDev->mpDrv = NULL;
+ }
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/**
+ * Set an array of guest properties
+ */
+void VMMDev::i_guestPropSetMultiple(void *names, void *values, void *timestamps, void *flags)
+{
+ VBOXHGCMSVCPARM parms[4];
+
+ parms[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[0].u.pointer.addr = names;
+ parms[0].u.pointer.size = 0; /* We don't actually care. */
+ parms[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[1].u.pointer.addr = values;
+ parms[1].u.pointer.size = 0; /* We don't actually care. */
+ parms[2].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[2].u.pointer.addr = timestamps;
+ parms[2].u.pointer.size = 0; /* We don't actually care. */
+ parms[3].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[3].u.pointer.addr = flags;
+ parms[3].u.pointer.size = 0; /* We don't actually care. */
+
+ hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROPS, 4, &parms[0]);
+}
+
+/**
+ * Set a single guest property
+ */
+void VMMDev::i_guestPropSet(const char *pszName, const char *pszValue, const char *pszFlags)
+{
+ VBOXHGCMSVCPARM parms[4];
+
+ AssertPtrReturnVoid(pszName);
+ AssertPtrReturnVoid(pszValue);
+ AssertPtrReturnVoid(pszFlags);
+ parms[0].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[0].u.pointer.addr = (void *)pszName;
+ parms[0].u.pointer.size = (uint32_t)strlen(pszName) + 1;
+ parms[1].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[1].u.pointer.addr = (void *)pszValue;
+ parms[1].u.pointer.size = (uint32_t)strlen(pszValue) + 1;
+ parms[2].type = VBOX_HGCM_SVC_PARM_PTR;
+ parms[2].u.pointer.addr = (void *)pszFlags;
+ parms[2].u.pointer.size = (uint32_t)strlen(pszFlags) + 1;
+ hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_PROP, 3, &parms[0]);
+}
+
+/**
+ * Set the global flags value by calling the service
+ * @returns the status returned by the call to the service
+ *
+ * @param pTable the service instance handle
+ * @param eFlags the flags to set
+ */
+int VMMDev::i_guestPropSetGlobalPropertyFlags(uint32_t fFlags)
+{
+ VBOXHGCMSVCPARM parm;
+ HGCMSvcSetU32(&parm, fFlags);
+ int vrc = hgcmHostCall("VBoxGuestPropSvc", GUEST_PROP_FN_HOST_SET_GLOBAL_FLAGS, 1, &parm);
+ if (RT_FAILURE(vrc))
+ {
+ char szFlags[GUEST_PROP_MAX_FLAGS_LEN];
+ if (RT_FAILURE(GuestPropWriteFlags(fFlags, szFlags)))
+ Log(("Failed to set the global flags.\n"));
+ else
+ Log(("Failed to set the global flags \"%s\".\n", szFlags));
+ }
+ return vrc;
+}
+
+
+/**
+ * Set up the Guest Property service, populate it with properties read from
+ * the machine XML and set a couple of initial properties.
+ */
+int VMMDev::i_guestPropLoadAndConfigure()
+{
+ Assert(mpDrv);
+ ComObjPtr<Console> ptrConsole = this->mParent;
+ AssertReturn(ptrConsole.isNotNull(), VERR_INVALID_POINTER);
+
+ /*
+ * Load the service
+ */
+ int vrc = hgcmLoadService("VBoxGuestPropSvc", "VBoxGuestPropSvc");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VBoxGuestPropSvc is not available. vrc = %Rrc\n", vrc));
+ return VINF_SUCCESS; /* That is not a fatal failure. */
+ }
+
+ /*
+ * Pull over the properties from the server.
+ */
+ SafeArray<BSTR> namesOut;
+ SafeArray<BSTR> valuesOut;
+ SafeArray<LONG64> timestampsOut;
+ SafeArray<BSTR> flagsOut;
+ HRESULT hrc = ptrConsole->i_pullGuestProperties(ComSafeArrayAsOutParam(namesOut),
+ ComSafeArrayAsOutParam(valuesOut),
+ ComSafeArrayAsOutParam(timestampsOut),
+ ComSafeArrayAsOutParam(flagsOut));
+ AssertLogRelMsgReturn(SUCCEEDED(hrc), ("hrc=%Rhrc\n", hrc), VERR_MAIN_CONFIG_CONSTRUCTOR_COM_ERROR);
+ size_t const cProps = namesOut.size();
+ size_t const cAlloc = cProps + 1;
+ AssertLogRelReturn(valuesOut.size() == cProps, VERR_INTERNAL_ERROR_2);
+ AssertLogRelReturn(timestampsOut.size() == cProps, VERR_INTERNAL_ERROR_3);
+ AssertLogRelReturn(flagsOut.size() == cProps, VERR_INTERNAL_ERROR_4);
+
+ char szEmpty[] = "";
+ char **papszNames = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ char **papszValues = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ LONG64 *pai64Timestamps = (LONG64 *)RTMemTmpAllocZ(sizeof(LONG64) * cAlloc);
+ char **papszFlags = (char **)RTMemTmpAllocZ(sizeof(char *) * cAlloc);
+ if (papszNames && papszValues && pai64Timestamps && papszFlags)
+ {
+ for (unsigned i = 0; RT_SUCCESS(vrc) && i < cProps; ++i)
+ {
+ AssertPtrBreakStmt(namesOut[i], vrc = VERR_INVALID_PARAMETER);
+ vrc = RTUtf16ToUtf8(namesOut[i], &papszNames[i]);
+ if (RT_FAILURE(vrc))
+ break;
+ if (valuesOut[i])
+ vrc = RTUtf16ToUtf8(valuesOut[i], &papszValues[i]);
+ else
+ papszValues[i] = szEmpty;
+ if (RT_FAILURE(vrc))
+ break;
+ pai64Timestamps[i] = timestampsOut[i];
+ if (flagsOut[i])
+ vrc = RTUtf16ToUtf8(flagsOut[i], &papszFlags[i]);
+ else
+ papszFlags[i] = szEmpty;
+ }
+ if (RT_SUCCESS(vrc))
+ i_guestPropSetMultiple((void *)papszNames, (void *)papszValues, (void *)pai64Timestamps, (void *)papszFlags);
+ for (unsigned i = 0; i < cProps; ++i)
+ {
+ RTStrFree(papszNames[i]);
+ if (valuesOut[i])
+ RTStrFree(papszValues[i]);
+ if (flagsOut[i])
+ RTStrFree(papszFlags[i]);
+ }
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ RTMemTmpFree(papszNames);
+ RTMemTmpFree(papszValues);
+ RTMemTmpFree(pai64Timestamps);
+ RTMemTmpFree(papszFlags);
+ AssertRCReturn(vrc, vrc);
+
+ /*
+ * Register the host notification callback
+ */
+ HGCMHostRegisterServiceExtension(&mpDrv->hHgcmSvcExtGstProps, "VBoxGuestPropSvc", Console::i_doGuestPropNotification, ptrConsole.m_p);
+
+# ifdef VBOX_WITH_GUEST_PROPS_RDONLY_GUEST
+ vrc = i_guestPropSetGlobalPropertyFlags(GUEST_PROP_F_RDONLYGUEST);
+ AssertRCReturn(vrc, vrc);
+# endif
+
+ Log(("Set VBoxGuestPropSvc property store\n"));
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnConstruct}
+ */
+DECLCALLBACK(int) VMMDev::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINVMMDEV pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINVMMDEV);
+ LogFlow(("Keyboard::drvConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "", "");
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * IBase.
+ */
+ pDrvIns->IBase.pfnQueryInterface = VMMDev::drvQueryInterface;
+
+ pThis->Connector.pfnUpdateGuestStatus = vmmdevUpdateGuestStatus;
+ pThis->Connector.pfnUpdateGuestUserState = vmmdevUpdateGuestUserState;
+ pThis->Connector.pfnUpdateGuestInfo = vmmdevUpdateGuestInfo;
+ pThis->Connector.pfnUpdateGuestInfo2 = vmmdevUpdateGuestInfo2;
+ pThis->Connector.pfnUpdateGuestCapabilities = vmmdevUpdateGuestCapabilities;
+ pThis->Connector.pfnUpdateMouseCapabilities = vmmdevUpdateMouseCapabilities;
+ pThis->Connector.pfnUpdatePointerShape = vmmdevUpdatePointerShape;
+ pThis->Connector.pfnVideoAccelEnable = iface_VideoAccelEnable;
+ pThis->Connector.pfnVideoAccelFlush = iface_VideoAccelFlush;
+ pThis->Connector.pfnVideoModeSupported = vmmdevVideoModeSupported;
+ pThis->Connector.pfnGetHeightReduction = vmmdevGetHeightReduction;
+ pThis->Connector.pfnSetCredentialsJudgementResult = vmmdevSetCredentialsJudgementResult;
+ pThis->Connector.pfnSetVisibleRegion = vmmdevSetVisibleRegion;
+ pThis->Connector.pfnUpdateMonitorPositions = vmmdevUpdateMonitorPositions;
+ pThis->Connector.pfnQueryVisibleRegion = vmmdevQueryVisibleRegion;
+ pThis->Connector.pfnReportStatistics = vmmdevReportStatistics;
+ pThis->Connector.pfnQueryStatisticsInterval = vmmdevQueryStatisticsInterval;
+ pThis->Connector.pfnQueryBalloonSize = vmmdevQueryBalloonSize;
+ pThis->Connector.pfnIsPageFusionEnabled = vmmdevIsPageFusionEnabled;
+
+#ifdef VBOX_WITH_HGCM
+ pThis->HGCMConnector.pfnConnect = iface_hgcmConnect;
+ pThis->HGCMConnector.pfnDisconnect = iface_hgcmDisconnect;
+ pThis->HGCMConnector.pfnCall = iface_hgcmCall;
+ pThis->HGCMConnector.pfnCancelled = iface_hgcmCancelled;
+#endif
+
+ /*
+ * Get the IVMMDevPort interface of the above driver/device.
+ */
+ pThis->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIVMMDEVPORT);
+ AssertMsgReturn(pThis->pUpPort, ("Configuration error: No VMMDev port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+#ifdef VBOX_WITH_HGCM
+ pThis->pHGCMPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHGCMPORT);
+ AssertMsgReturn(pThis->pHGCMPort, ("Configuration error: No HGCM port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+#endif
+
+ /*
+ * Get the Console object pointer and update the mpDrv member.
+ */
+ com::Guid uuid(VMMDEV_OID);
+ pThis->pVMMDev = (VMMDev *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pThis->pVMMDev)
+ {
+ AssertMsgFailed(("Configuration error: No/bad VMMDev object!\n"));
+ return VERR_NOT_FOUND;
+ }
+ pThis->pVMMDev->mpDrv = pThis;
+
+ int vrc = VINF_SUCCESS;
+#ifdef VBOX_WITH_HGCM
+ /*
+ * Load & configure the shared folders service.
+ */
+ vrc = pThis->pVMMDev->hgcmLoadService(VBOXSHAREDFOLDERS_DLL, "VBoxSharedFolders");
+ pThis->pVMMDev->fSharedFolderActive = RT_SUCCESS(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ PPDMLED pLed;
+ PPDMILEDPORTS pLedPort;
+
+ LogRel(("Shared Folders service loaded\n"));
+ pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
+ AssertMsgReturn(pLedPort, ("Configuration error: No LED port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+ vrc = pLedPort->pfnQueryStatusLed(pLedPort, 0, &pLed);
+ if (RT_SUCCESS(vrc) && pLed)
+ {
+ VBOXHGCMSVCPARM parm;
+
+ parm.type = VBOX_HGCM_SVC_PARM_PTR;
+ parm.u.pointer.addr = pLed;
+ parm.u.pointer.size = sizeof(*pLed);
+
+ vrc = HGCMHostCall("VBoxSharedFolders", SHFL_FN_SET_STATUS_LED, 1, &parm);
+ }
+ else
+ AssertMsgFailed(("pfnQueryStatusLed failed with %Rrc (pLed=%x)\n", vrc, pLed));
+ }
+ else
+ LogRel(("Failed to load Shared Folders service %Rrc\n", vrc));
+
+
+ /*
+ * Load and configure the guest control service.
+ */
+# ifdef VBOX_WITH_GUEST_CONTROL
+ vrc = pThis->pVMMDev->hgcmLoadService("VBoxGuestControlSvc", "VBoxGuestControlSvc");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = HGCMHostRegisterServiceExtension(&pThis->hHgcmSvcExtGstCtrl, "VBoxGuestControlSvc",
+ &Guest::i_notifyCtrlDispatcher,
+ pThis->pVMMDev->mParent->i_getGuest());
+ if (RT_SUCCESS(vrc))
+ LogRel(("Guest Control service loaded\n"));
+ else
+ LogRel(("Warning: Cannot register VBoxGuestControlSvc extension! vrc=%Rrc\n", vrc));
+ }
+ else
+ LogRel(("Warning!: Failed to load the Guest Control Service! %Rrc\n", vrc));
+# endif /* VBOX_WITH_GUEST_CONTROL */
+
+
+ /*
+ * Load and configure the guest properties service.
+ */
+# ifdef VBOX_WITH_GUEST_PROPS
+ vrc = pThis->pVMMDev->i_guestPropLoadAndConfigure();
+ AssertLogRelRCReturn(vrc, vrc);
+# endif
+
+
+ /*
+ * The HGCM saved state.
+ */
+ vrc = PDMDrvHlpSSMRegisterEx(pDrvIns, HGCM_SAVED_STATE_VERSION, 4096 /* bad guess */,
+ NULL, NULL, NULL,
+ NULL, VMMDev::hgcmSave, NULL,
+ NULL, VMMDev::hgcmLoad, NULL);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+#endif /* VBOX_WITH_HGCM */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VMMDevice driver registration record.
+ */
+const PDMDRVREG VMMDev::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "HGCM",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main VMMDev driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_VMMDEV,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINVMMDEV),
+ /* pfnConstruct */
+ VMMDev::drvConstruct,
+ /* pfnDestruct */
+ VMMDev::drvDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ VMMDev::drvPowerOn,
+ /* pfnReset */
+ VMMDev::drvReset,
+ /* pfnSuspend */
+ VMMDev::drvSuspend,
+ /* pfnResume */
+ VMMDev::drvResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ VMMDev::drvPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp
new file mode 100644
index 00000000..a9f4a276
--- /dev/null
+++ b/src/VBox/Main/src-client/VirtualBoxClientImpl.cpp
@@ -0,0 +1,833 @@
+/* $Id: VirtualBoxClientImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXCLIENT
+#include "LoggingNew.h"
+
+#include "VirtualBoxClientImpl.h"
+
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+#include "VBox/com/ErrorInfo.h"
+#include "VBox/com/listeners.h"
+
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/utf16.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/err.h>
+# include <iprt/ldr.h>
+# include <msi.h>
+# include <WbemIdl.h>
+#endif
+
+#include <new>
+
+
+/** Waiting time between probing whether VBoxSVC is alive. */
+#define VBOXCLIENT_DEFAULT_INTERVAL 30000
+
+
+/** Initialize instance counter class variable */
+uint32_t VirtualBoxClient::g_cInstances = 0;
+
+LONG VirtualBoxClient::s_cUnnecessaryAtlModuleLocks = 0;
+
+#ifdef VBOX_WITH_MAIN_NLS
+
+/* listener class for language updates */
+class VBoxEventListener
+{
+public:
+ VBoxEventListener()
+ {}
+
+
+ HRESULT init(void *)
+ {
+ return S_OK;
+ }
+
+ HRESULT init()
+ {
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+ virtual ~VBoxEventListener()
+ {
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch(aType)
+ {
+ case VBoxEventType_OnLanguageChanged:
+ {
+ /*
+ * Proceed with uttmost care as we might be racing com::Shutdown()
+ * and have the ground open up beneath us.
+ */
+ LogFunc(("VBoxEventType_OnLanguageChanged\n"));
+ VirtualBoxTranslator *pTranslator = VirtualBoxTranslator::tryInstance();
+ if (pTranslator)
+ {
+ ComPtr<ILanguageChangedEvent> pEvent = aEvent;
+ Assert(pEvent);
+
+ /* This call may fail if we're racing COM shutdown. */
+ com::Bstr bstrLanguageId;
+ HRESULT hrc = pEvent->COMGETTER(LanguageId)(bstrLanguageId.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ com::Utf8Str strLanguageId(bstrLanguageId);
+ LogFunc(("New language ID: %s\n", strLanguageId.c_str()));
+ pTranslator->i_loadLanguage(strLanguageId.c_str());
+ }
+ catch (std::bad_alloc &)
+ {
+ LogFunc(("Caught bad_alloc"));
+ }
+ }
+ else
+ LogFunc(("Failed to get new language ID: %Rhrc\n", hrc));
+
+ pTranslator->release();
+ }
+ break;
+ }
+
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+};
+
+typedef ListenerImpl<VBoxEventListener> VBoxEventListenerImpl;
+
+VBOX_LISTENER_DECLARE(VBoxEventListenerImpl)
+
+#endif /* VBOX_WITH_MAIN_NLS */
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+/** @relates VirtualBoxClient::FinalConstruct() */
+HRESULT VirtualBoxClient::FinalConstruct()
+{
+ HRESULT hrc = init();
+ BaseFinalConstruct();
+ return hrc;
+}
+
+void VirtualBoxClient::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the VirtualBoxClient object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT VirtualBoxClient::init()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Important: DO NOT USE any kind of "early return" (except the single
+ * one above, checking the init span success) in this method. It is vital
+ * for correct error handling that it has only one point of return, which
+ * does all the magic on COM to signal object creation success and
+ * reporting the error later for every API method. COM translates any
+ * unsuccessful object creation to REGDB_E_CLASSNOTREG errors or similar
+ * unhelpful ones which cause us a lot of grief with troubleshooting. */
+
+ HRESULT hrc = S_OK;
+ try
+ {
+ if (ASMAtomicIncU32(&g_cInstances) != 1)
+ AssertFailedStmt(throw setError(E_FAIL, "Attempted to create more than one VirtualBoxClient instance"));
+
+ mData.m_ThreadWatcher = NIL_RTTHREAD;
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+
+ hrc = mData.m_pVirtualBox.createLocalObject(CLSID_VirtualBox);
+ if (FAILED(hrc))
+#ifdef RT_OS_WINDOWS
+ throw i_investigateVirtualBoxObjectCreationFailure(hrc);
+#else
+ throw hrc;
+#endif
+
+ /* VirtualBox error return is postponed to method calls, fetch it. */
+ ULONG rev;
+ hrc = mData.m_pVirtualBox->COMGETTER(Revision)(&rev);
+ if (FAILED(hrc))
+ throw hrc;
+
+ hrc = unconst(mData.m_pEventSource).createObject();
+ AssertComRCThrow(hrc, setError(hrc, "Could not create EventSource for VirtualBoxClient"));
+ hrc = mData.m_pEventSource->init();
+ AssertComRCThrow(hrc, setError(hrc, "Could not initialize EventSource for VirtualBoxClient"));
+
+ /* HACK ALERT! This is for DllCanUnloadNow(). */
+ s_cUnnecessaryAtlModuleLocks++;
+ AssertMsg(s_cUnnecessaryAtlModuleLocks == 1, ("%d\n", s_cUnnecessaryAtlModuleLocks));
+
+ int vrc;
+#ifdef VBOX_WITH_MAIN_NLS
+ /* Create the translator singelton (must work) and try load translations (non-fatal). */
+ mData.m_pVBoxTranslator = VirtualBoxTranslator::instance();
+ if (mData.m_pVBoxTranslator == NULL)
+ throw setError(VBOX_E_IPRT_ERROR, "Failed to create translator instance");
+
+ char szNlsPath[RTPATH_MAX];
+ vrc = RTPathAppPrivateNoArch(szNlsPath, sizeof(szNlsPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szNlsPath, sizeof(szNlsPath), "nls" RTPATH_SLASH_STR "VirtualBoxAPI");
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = mData.m_pVBoxTranslator->registerTranslation(szNlsPath, true, &mData.m_pTrComponent);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = i_reloadApiLanguage();
+ if (SUCCEEDED(hrc))
+ i_registerEventListener(); /* for updates */
+ else
+ LogRelFunc(("i_reloadApiLanguage failed: %Rhrc\n", hrc));
+ }
+ else
+ LogRelFunc(("Register translation failed: %Rrc\n", vrc));
+ }
+ else
+ LogRelFunc(("Path constructing failed: %Rrc\n", vrc));
+#endif
+ /* Setting up the VBoxSVC watcher thread. If anything goes wrong here it
+ * is not considered important enough to cause any sort of visible
+ * failure. The monitoring will not be done, but that's all. */
+ vrc = RTSemEventCreate(&mData.m_SemEvWatcher);
+ if (RT_FAILURE(vrc))
+ {
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create semaphore (vrc=%Rrc)"), vrc));
+ }
+
+ vrc = RTThreadCreate(&mData.m_ThreadWatcher, SVCWatcherThread, this, 0,
+ RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "VBoxSVCWatcher");
+ if (RT_FAILURE(vrc))
+ {
+ RTSemEventDestroy(mData.m_SemEvWatcher);
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ AssertRCStmt(vrc, throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to create watcher thread (vrc=%Rrc)"), vrc));
+ }
+ }
+ catch (HRESULT err)
+ {
+ /* we assume that error info is set by the thrower */
+ hrc = err;
+ }
+ catch (...)
+ {
+ hrc = VirtualBoxBase::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+
+ /* Confirm a successful initialization when it's the case. Must be last,
+ * as on failure it will uninitialize the object. */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ else
+ autoInitSpan.setFailed(hrc);
+
+ LogFlowThisFunc(("hrc=%Rhrc\n", hrc));
+ LogFlowThisFuncLeave();
+ /* Unconditionally return success, because the error return is delayed to
+ * the attribute/method calls through the InitFailed object state. */
+ return S_OK;
+}
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Looks into why we failed to create the VirtualBox object.
+ *
+ * @returns hrcCaller thru setError.
+ * @param hrcCaller The failure status code.
+ */
+HRESULT VirtualBoxClient::i_investigateVirtualBoxObjectCreationFailure(HRESULT hrcCaller)
+{
+ HRESULT hrc;
+
+# ifdef VBOX_WITH_SDS
+ /*
+ * Check that the VBoxSDS service is configured to run as LocalSystem and is enabled.
+ */
+ WCHAR wszBuffer[256];
+ uint32_t uStartType;
+ int vrc = i_getServiceAccountAndStartType(L"VBoxSDS", wszBuffer, RT_ELEMENTS(wszBuffer), &uStartType);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFunc(("VBoxSDS service is running under the '%ls' account with start type %u.\n", wszBuffer, uStartType));
+ if (RTUtf16Cmp(wszBuffer, L"LocalSystem") != 0)
+ return setError(hrcCaller,
+ tr("VBoxSDS is misconfigured to run under the '%ls' account instead of the SYSTEM one.\n"
+ "Reinstall VirtualBox to fix it. Alternatively you can fix it using the Windows Service Control "
+ "Manager or by running 'sc config VBoxSDS obj=LocalSystem' on a command line."), wszBuffer);
+ if (uStartType == SERVICE_DISABLED)
+ return setError(hrcCaller,
+ tr("The VBoxSDS windows service is disabled.\n"
+ "Reinstall VirtualBox to fix it. Alternatively try reenable the service by setting it to "
+ " 'Manual' startup type in the Windows Service management console, or by runing "
+ "'sc config VBoxSDS start=demand' on the command line."));
+ }
+ else if (vrc == VERR_NOT_FOUND)
+ return setError(hrcCaller,
+ tr("The VBoxSDS windows service was not found.\n"
+ "Reinstall VirtualBox to fix it. Alternatively you can try start VirtualBox as Administrator, this "
+ "should automatically reinstall the service, or you can run "
+ "'VBoxSDS.exe --regservice' command from an elevated Administrator command line."));
+ else
+ LogRelFunc(("VirtualBoxClient::i_getServiceAccount failed: %Rrc\n", vrc));
+# endif
+
+ /*
+ * First step is to try get an IUnknown interface of the VirtualBox object.
+ *
+ * This will succeed even when oleaut32.msm (see @bugref{8016}, @ticketref{12087})
+ * is accidentally installed and messes up COM. It may also succeed when the COM
+ * registration is partially broken (though that's unlikely to happen these days).
+ */
+ IUnknown *pUnknown = NULL;
+ hrc = CoCreateInstance(CLSID_VirtualBox, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void **)&pUnknown);
+ if (FAILED(hrc))
+ {
+ if (hrc == hrcCaller)
+ return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc"), hrcCaller);
+ return setError(hrcCaller, tr("Completely failed to instantiate CLSID_VirtualBox: %Rhrc & %Rhrc"), hrcCaller, hrc);
+ }
+
+ /*
+ * Try query the IVirtualBox interface (should fail), if it succeed we return
+ * straight away so we have more columns to spend on long messages below.
+ */
+ IVirtualBox *pVirtualBox;
+ hrc = pUnknown->QueryInterface(IID_IVirtualBox, (void **)&pVirtualBox);
+ if (SUCCEEDED(hrc))
+ {
+ pVirtualBox->Release();
+ pUnknown->Release();
+ return setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox the first time, but worked when checking out why ... weird"));
+ }
+
+ /*
+ * Check for oleaut32.msm traces in the registry.
+ */
+ HKEY hKey;
+ LSTATUS lrc = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"CLSID\\{00020420-0000-0000-C000-000000000046}\\InprocServer32",
+ 0 /*fFlags*/, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | STANDARD_RIGHTS_READ, &hKey);
+ if (lrc == ERROR_SUCCESS)
+ {
+ wchar_t wszBuf[8192];
+ DWORD cbBuf = sizeof(wszBuf) - sizeof(wchar_t);
+ DWORD dwType = 0;
+ lrc = RegQueryValueExW(hKey, L"InprocServer32", NULL /*pvReserved*/, &dwType, (BYTE *)&wszBuf[0], &cbBuf);
+ if (lrc == ERROR_SUCCESS)
+ {
+ wszBuf[cbBuf / sizeof(wchar_t)] = '\0';
+ bool fSetError = false;
+
+ /*
+ * Try decode the string and improve the message.
+ */
+ typedef UINT (WINAPI *PFNMSIDECOMPOSEDESCRIPTORW)(PCWSTR pwszDescriptor,
+ LPWSTR pwszProductCode /*[40]*/,
+ LPWSTR pwszFeatureId /*[40]*/,
+ LPWSTR pwszComponentCode /*[40]*/,
+ DWORD *poffArguments);
+ PFNMSIDECOMPOSEDESCRIPTORW pfnMsiDecomposeDescriptorW;
+ pfnMsiDecomposeDescriptorW = (PFNMSIDECOMPOSEDESCRIPTORW)RTLdrGetSystemSymbol("msi.dll", "MsiDecomposeDescriptorW");
+ if ( pfnMsiDecomposeDescriptorW
+ && ( dwType == REG_SZ
+ || dwType == REG_MULTI_SZ))
+ {
+ wchar_t wszProductCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ wchar_t wszFeatureId[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ wchar_t wszComponentCode[RTUUID_STR_LENGTH + 2 + 16] = { 0 };
+ DWORD offArguments = ~(DWORD)0;
+ UINT uRc = pfnMsiDecomposeDescriptorW(wszBuf, wszProductCode, wszFeatureId, wszComponentCode, &offArguments);
+ if (uRc == 0)
+ {
+ /*
+ * Can we resolve the product code into a name?
+ */
+ typedef UINT (WINAPI *PFNMSIOPENPRODUCTW)(PCWSTR, MSIHANDLE *);
+ PFNMSIOPENPRODUCTW pfnMsiOpenProductW;
+ pfnMsiOpenProductW = (PFNMSIOPENPRODUCTW)RTLdrGetSystemSymbol("msi.dll", "MsiOpenProductW");
+
+ typedef UINT (WINAPI *PFNMSICLOSEHANDLE)(MSIHANDLE);
+ PFNMSICLOSEHANDLE pfnMsiCloseHandle;
+ pfnMsiCloseHandle = (PFNMSICLOSEHANDLE)RTLdrGetSystemSymbol("msi.dll", "MsiCloseHandle");
+
+ typedef UINT (WINAPI *PFNGETPRODUCTPROPERTYW)(MSIHANDLE, PCWSTR, PWSTR, PDWORD);
+ PFNGETPRODUCTPROPERTYW pfnMsiGetProductPropertyW;
+ pfnMsiGetProductPropertyW = (PFNGETPRODUCTPROPERTYW)RTLdrGetSystemSymbol("msi.dll", "MsiGetProductPropertyW");
+ if ( pfnMsiGetProductPropertyW
+ && pfnMsiCloseHandle
+ && pfnMsiOpenProductW)
+ {
+ MSIHANDLE hMsi = 0;
+ uRc = pfnMsiOpenProductW(wszProductCode, &hMsi);
+ if (uRc == 0)
+ {
+ static wchar_t const * const s_apwszProps[] =
+ {
+ INSTALLPROPERTY_INSTALLEDPRODUCTNAME,
+ INSTALLPROPERTY_PRODUCTNAME,
+ INSTALLPROPERTY_PACKAGENAME,
+ };
+
+ wchar_t wszProductName[1024];
+ DWORD cwcProductName;
+ unsigned i = 0;
+ do
+ {
+ cwcProductName = RT_ELEMENTS(wszProductName) - 1;
+ uRc = pfnMsiGetProductPropertyW(hMsi, s_apwszProps[i], wszProductName, &cwcProductName);
+ }
+ while ( ++i < RT_ELEMENTS(s_apwszProps)
+ && ( uRc != 0
+ || cwcProductName < 2
+ || cwcProductName >= RT_ELEMENTS(wszProductName)) );
+ uRc = pfnMsiCloseHandle(hMsi);
+ if (uRc == 0 && cwcProductName >= 2)
+ {
+ wszProductName[RT_MIN(cwcProductName, RT_ELEMENTS(wszProductName) - 1)] = '\0';
+ setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by the '%ls' (%ls) program, suspecting that it features the broken oleaut32.msm module as component %ls.\n"
+ "\n"
+ "We suggest you try uninstall '%ls'.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "),
+ wszProductName, wszProductCode, wszComponentCode, wszProductName);
+ fSetError = true;
+ }
+ }
+ }
+
+ /* MSI uses COM and may mess up our stuff. So, we wait with the fallback till afterwards in this case. */
+ if (!fSetError)
+ {
+ setError(hrcCaller,
+ tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by installer %ls featuring the broken oleaut32.msm module as component %ls.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "),
+ wszProductCode, wszComponentCode);
+ fSetError = true;
+ }
+ }
+ }
+ if (!fSetError)
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks broken by some installer featuring the broken oleaut32.msm module as a component.\n"
+ "\n"
+ "See also https://support.microsoft.com/en-us/kb/316911 "));
+ }
+ else if (lrc == ERROR_FILE_NOT_FOUND)
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "PSDispatch looks fine. Weird"));
+ else
+ setError(hrcCaller, tr("Failed to instantiate CLSID_VirtualBox w/ IVirtualBox, but CLSID_VirtualBox w/ IUnknown works.\n"
+ "Checking out PSDispatch registration ended with error: %u (%#x)"), lrc, lrc);
+ RegCloseKey(hKey);
+ }
+
+ pUnknown->Release();
+ return hrcCaller;
+}
+
+# ifdef VBOX_WITH_SDS
+/**
+ * Gets the service account name and start type for the given service.
+ *
+ * @returns IPRT status code (for some reason).
+ * @param pwszServiceName The name of the service.
+ * @param pwszAccountName Where to return the account name.
+ * @param cwcAccountName The length of the account name buffer (in WCHARs).
+ * @param puStartType Where to return the start type.
+ */
+int VirtualBoxClient::i_getServiceAccountAndStartType(const wchar_t *pwszServiceName,
+ wchar_t *pwszAccountName, size_t cwcAccountName, uint32_t *puStartType)
+{
+ AssertPtr(pwszServiceName);
+ AssertPtr(pwszAccountName);
+ Assert(cwcAccountName);
+ *pwszAccountName = '\0';
+ *puStartType = SERVICE_DEMAND_START;
+
+ int vrc;
+
+ // Get a handle to the SCM database.
+ SC_HANDLE hSCManager = OpenSCManagerW(NULL /*pwszMachineName*/, NULL /*pwszDatabaseName*/, SC_MANAGER_CONNECT);
+ if (hSCManager != NULL)
+ {
+ SC_HANDLE hService = OpenServiceW(hSCManager, pwszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ DWORD cbNeeded = sizeof(QUERY_SERVICE_CONFIGW) + _1K;
+ if (!QueryServiceConfigW(hService, NULL, 0, &cbNeeded))
+ {
+ Assert(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ LPQUERY_SERVICE_CONFIGW pSc = (LPQUERY_SERVICE_CONFIGW)RTMemTmpAllocZ(cbNeeded + _1K);
+ if (pSc)
+ {
+ DWORD cbNeeded2 = 0;
+ if (QueryServiceConfigW(hService, pSc, cbNeeded + _1K, &cbNeeded2))
+ {
+ *puStartType = pSc->dwStartType;
+ vrc = RTUtf16Copy(pwszAccountName, cwcAccountName, pSc->lpServiceStartName);
+ if (RT_FAILURE(vrc))
+ LogRel(("Error: SDS service name is too long (%Rrc): %ls\n", vrc, pSc->lpServiceStartName));
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Failed querying '%ls' service config: %Rwc (%u) -> %Rrc; cbNeeded=%d cbNeeded2=%d\n",
+ pwszServiceName, dwError, dwError, vrc, cbNeeded, cbNeeded2));
+ }
+ RTMemTmpFree(pSc);
+ }
+ else
+ {
+ LogRel(("Error: Failed allocating %#x bytes of memory for service config!\n", cbNeeded + _1K));
+ vrc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("Error: QueryServiceConfigW returns success with zero buffer!\n"));
+ vrc = VERR_IPE_UNEXPECTED_STATUS;
+ }
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Could not open service '%ls': %Rwc (%u) -> %Rrc\n", pwszServiceName, dwError, dwError, vrc));
+ }
+ CloseServiceHandle(hSCManager);
+ }
+ else
+ {
+ int dwError = GetLastError();
+ vrc = RTErrConvertFromWin32(dwError);
+ LogRel(("Error: Could not open SCM: %Rwc (%u) -> %Rrc\n", dwError, dwError, vrc));
+ }
+ return vrc;
+}
+# endif /* VBOX_WITH_SDS */
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void VirtualBoxClient::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ {
+ LogFlowThisFunc(("already done\n"));
+ return;
+ }
+
+#ifdef VBOX_WITH_MAIN_NLS
+ i_unregisterEventListener();
+#endif
+
+ if (mData.m_ThreadWatcher != NIL_RTTHREAD)
+ {
+ /* Signal the event semaphore and wait for the thread to terminate.
+ * if it hangs for some reason exit anyway, this can cause a crash
+ * though as the object will no longer be available. */
+ RTSemEventSignal(mData.m_SemEvWatcher);
+ RTThreadWait(mData.m_ThreadWatcher, 30000, NULL);
+ mData.m_ThreadWatcher = NIL_RTTHREAD;
+ RTSemEventDestroy(mData.m_SemEvWatcher);
+ mData.m_SemEvWatcher = NIL_RTSEMEVENT;
+ }
+#ifdef VBOX_WITH_MAIN_NLS
+ if (mData.m_pVBoxTranslator != NULL)
+ {
+ mData.m_pVBoxTranslator->release();
+ mData.m_pVBoxTranslator = NULL;
+ mData.m_pTrComponent = NULL;
+ }
+#endif
+ mData.m_pToken.setNull();
+ mData.m_pVirtualBox.setNull();
+
+ ASMAtomicDecU32(&g_cInstances);
+
+ LogFlowThisFunc(("returns\n"));
+}
+
+// IVirtualBoxClient properties
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns a reference to the VirtualBox object.
+ *
+ * @returns COM status code
+ * @param aVirtualBox Address of result variable.
+ */
+HRESULT VirtualBoxClient::getVirtualBox(ComPtr<IVirtualBox> &aVirtualBox)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aVirtualBox = mData.m_pVirtualBox;
+ return S_OK;
+}
+
+/**
+ * Create a new Session object and return a reference to it.
+ *
+ * @returns COM status code
+ * @param aSession Address of result variable.
+ */
+HRESULT VirtualBoxClient::getSession(ComPtr<ISession> &aSession)
+{
+ /* this is not stored in this object, no need to lock */
+ ComPtr<ISession> pSession;
+ HRESULT hrc = pSession.createInprocObject(CLSID_Session);
+ if (SUCCEEDED(hrc))
+ aSession = pSession;
+ return hrc;
+}
+
+/**
+ * Return reference to the EventSource associated with this object.
+ *
+ * @returns COM status code
+ * @param aEventSource Address of result variable.
+ */
+HRESULT VirtualBoxClient::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* this is const, no need to lock */
+ aEventSource = mData.m_pEventSource;
+ return aEventSource.isNull() ? E_FAIL : S_OK;
+}
+
+// IVirtualBoxClient methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Checks a Machine object for any pending errors.
+ *
+ * @returns COM status code
+ * @param aMachine Machine object to check.
+ */
+HRESULT VirtualBoxClient::checkMachineError(const ComPtr<IMachine> &aMachine)
+{
+ BOOL fAccessible = FALSE;
+ HRESULT hrc = aMachine->COMGETTER(Accessible)(&fAccessible);
+ if (FAILED(hrc))
+ return setError(hrc, tr("Could not check the accessibility status of the VM"));
+ else if (!fAccessible)
+ {
+ ComPtr<IVirtualBoxErrorInfo> pAccessError;
+ hrc = aMachine->COMGETTER(AccessError)(pAccessError.asOutParam());
+ if (FAILED(hrc))
+ return setError(hrc, tr("Could not get the access error message of the VM"));
+ else
+ {
+ ErrorInfo info(pAccessError);
+ ErrorInfoKeeper eik(info);
+ return info.getResultCode();
+ }
+ }
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+
+/// @todo AM Add pinging of VBoxSDS
+/*static*/
+DECLCALLBACK(int) VirtualBoxClient::SVCWatcherThread(RTTHREAD ThreadSelf,
+ void *pvUser)
+{
+ NOREF(ThreadSelf);
+ Assert(pvUser);
+ VirtualBoxClient *pThis = (VirtualBoxClient *)pvUser;
+ RTSEMEVENT sem = pThis->mData.m_SemEvWatcher;
+ RTMSINTERVAL cMillies = VBOXCLIENT_DEFAULT_INTERVAL;
+
+ /* The likelihood of early crashes are high, so start with a short wait. */
+ int vrc = RTSemEventWait(sem, cMillies / 2);
+
+ /* As long as the waiting times out keep retrying the wait. */
+ while (RT_FAILURE(vrc))
+ {
+ {
+ HRESULT hrc = S_OK;
+ ComPtr<IVirtualBox> pV;
+ {
+ AutoReadLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ pV = pThis->mData.m_pVirtualBox;
+ }
+ if (!pV.isNull())
+ {
+ ULONG rev;
+ hrc = pV->COMGETTER(Revision)(&rev);
+ if (FAILED_DEAD_INTERFACE(hrc))
+ {
+ LogRel(("VirtualBoxClient: detected unresponsive VBoxSVC (hrc=%Rhrc)\n", hrc));
+ {
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ /* Throw away the VirtualBox reference, it's no longer
+ * usable as VBoxSVC terminated in the mean time. */
+ pThis->mData.m_pVirtualBox.setNull();
+ }
+ ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, FALSE);
+ }
+ }
+ else
+ {
+ /* Try to get a new VirtualBox reference straight away, and if
+ * this fails use an increased waiting time as very frequent
+ * restart attempts in some wedged config can cause high CPU
+ * and disk load. */
+ ComPtr<IVirtualBox> pVirtualBox;
+ ComPtr<IToken> pToken;
+ hrc = pVirtualBox.createLocalObject(CLSID_VirtualBox);
+ if (FAILED(hrc))
+ cMillies = 3 * VBOXCLIENT_DEFAULT_INTERVAL;
+ else
+ {
+ LogRel(("VirtualBoxClient: detected working VBoxSVC (hrc=%Rhrc)\n", hrc));
+ {
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ /* Update the VirtualBox reference, there's a working
+ * VBoxSVC again from now on. */
+ pThis->mData.m_pVirtualBox = pVirtualBox;
+ pThis->mData.m_pToken = pToken;
+#ifdef VBOX_WITH_MAIN_NLS
+ /* update language using new instance of IVirtualBox in case the language settings was changed */
+ pThis->i_reloadApiLanguage();
+ pThis->i_registerEventListener();
+#endif
+ }
+ ::FireVBoxSVCAvailabilityChangedEvent(pThis->mData.m_pEventSource, TRUE);
+ cMillies = VBOXCLIENT_DEFAULT_INTERVAL;
+ }
+ }
+ }
+ vrc = RTSemEventWait(sem, cMillies);
+ }
+ return 0;
+}
+
+#ifdef VBOX_WITH_MAIN_NLS
+
+HRESULT VirtualBoxClient::i_reloadApiLanguage()
+{
+ if (mData.m_pVBoxTranslator == NULL)
+ return S_OK;
+
+ HRESULT hrc = mData.m_pVBoxTranslator->loadLanguage(mData.m_pVirtualBox);
+ if (FAILED(hrc))
+ setError(hrc, tr("Failed to load user language instance"));
+ return hrc;
+}
+
+HRESULT VirtualBoxClient::i_registerEventListener()
+{
+ HRESULT hrc = mData.m_pVirtualBox->COMGETTER(EventSource)(mData.m_pVBoxEventSource.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ ComObjPtr<VBoxEventListenerImpl> pVBoxListener;
+ pVBoxListener.createObject();
+ pVBoxListener->init(new VBoxEventListener());
+ mData.m_pVBoxEventListener = pVBoxListener;
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnLanguageChanged);
+ hrc = mData.m_pVBoxEventSource->RegisterListener(pVBoxListener, ComSafeArrayAsInParam(eventTypes), true);
+ if (FAILED(hrc))
+ {
+ hrc = setError(hrc, tr("Failed to register listener"));
+ mData.m_pVBoxEventListener.setNull();
+ mData.m_pVBoxEventSource.setNull();
+ }
+ }
+ else
+ hrc = setError(hrc, tr("Failed to get event source from VirtualBox"));
+ return hrc;
+}
+
+void VirtualBoxClient::i_unregisterEventListener()
+{
+ if (mData.m_pVBoxEventListener.isNotNull())
+ {
+ if (mData.m_pVBoxEventSource.isNotNull())
+ mData.m_pVBoxEventSource->UnregisterListener(mData.m_pVBoxEventListener);
+ mData.m_pVBoxEventListener.setNull();
+ }
+ mData.m_pVBoxEventSource.setNull();
+}
+
+#endif /* VBOX_WITH_MAIN_NLS */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-client/WebMWriter.cpp b/src/VBox/Main/src-client/WebMWriter.cpp
new file mode 100644
index 00000000..fed81be6
--- /dev/null
+++ b/src/VBox/Main/src-client/WebMWriter.cpp
@@ -0,0 +1,881 @@
+/* $Id: WebMWriter.cpp $ */
+/** @file
+ * WebMWriter.cpp - WebM container handling.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * For more information, see:
+ * - https://w3c.github.io/media-source/webm-byte-stream-format.html
+ * - https://www.webmproject.org/docs/container/#muxer-guidelines
+ */
+
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+
+#include <VBox/version.h>
+
+#include "RecordingInternals.h"
+#include "WebMWriter.h"
+
+
+WebMWriter::WebMWriter(void)
+{
+ /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
+ m_cbTimecode = 2;
+ m_uTimecodeMax = UINT16_MAX;
+
+ m_fInTracksSection = false;
+}
+
+WebMWriter::~WebMWriter(void)
+{
+ Close();
+}
+
+/**
+ * Opens (creates) an output file using an already open file handle.
+ *
+ * @returns VBox status code.
+ * @param a_pszFilename Name of the file the file handle points at.
+ * @param a_phFile Pointer to open file handle to use.
+ * @param a_enmAudioCodec Audio codec to use.
+ * @param a_enmVideoCodec Video codec to use.
+ */
+int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec)
+{
+ try
+ {
+ LogFunc(("Creating '%s'\n", a_pszFilename));
+
+ int vrc = createEx(a_pszFilename, a_phFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = init(a_enmAudioCodec, a_enmVideoCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = writeHeader();
+ }
+ }
+ catch(int vrc)
+ {
+ return vrc;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Opens an output file.
+ *
+ * @returns VBox status code.
+ * @param a_pszFilename Name of the file to create.
+ * @param a_fOpen File open mode of type RTFILE_O_.
+ * @param a_enmAudioCodec Audio codec to use.
+ * @param a_enmVideoCodec Video codec to use.
+ */
+int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen,
+ RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec)
+{
+ try
+ {
+ LogFunc(("Creating '%s'\n", a_pszFilename));
+
+ int vrc = create(a_pszFilename, a_fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = init(a_enmAudioCodec, a_enmVideoCodec);
+ if (RT_SUCCESS(vrc))
+ vrc = writeHeader();
+ }
+ }
+ catch(int vrc)
+ {
+ return vrc;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Closes the WebM file and drains all queues.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::Close(void)
+{
+ LogFlowFuncEnter();
+
+ if (!isOpen())
+ return VINF_SUCCESS;
+
+ /* Make sure to drain all queues. */
+ processQueue(&m_CurSeg.m_queueBlocks, true /* fForce */);
+
+ writeFooter();
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ WebMTrack *pTrack = itTrack->second;
+ if (pTrack) /* Paranoia. */
+ delete pTrack;
+
+ m_CurSeg.m_mapTracks.erase(itTrack);
+
+ itTrack = m_CurSeg.m_mapTracks.begin();
+ }
+
+ Assert(m_CurSeg.m_queueBlocks.Map.size() == 0);
+ Assert(m_CurSeg.m_mapTracks.size() == 0);
+
+ com::Utf8Str strFileName = getFileName().c_str();
+
+ close();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds an audio track.
+ *
+ * @returns VBox status code.
+ * @param pCodec Audio codec to use.
+ * @param uHz Input sampling rate.
+ * Must be supported by the selected audio codec.
+ * @param cChannels Number of input audio channels.
+ * @param cBits Number of input bits per channel.
+ * @param puTrack Track number on successful creation. Optional.
+ */
+int WebMWriter::AddAudioTrack(PRECORDINGCODEC pCodec, uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
+{
+ AssertReturn(uHz, VERR_INVALID_PARAMETER);
+ AssertReturn(cBits, VERR_INVALID_PARAMETER);
+ AssertReturn(cChannels, VERR_INVALID_PARAMETER);
+
+ /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
+ * Using a track number 0 will show those files as being corrupted. */
+ const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1;
+
+ subStart(MkvElem_TrackEntry);
+
+ serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
+ serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
+ serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
+
+ int vrc = VINF_SUCCESS;
+
+ WebMTrack *pTrack = NULL;
+ try
+ {
+ pTrack = new WebMTrack(WebMTrackType_Audio, pCodec, uTrack, RTFileTell(getFile()));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
+ .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */);
+
+ switch (m_enmAudioCodec)
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ case RecordingAudioCodec_OggVorbis:
+ {
+ pTrack->Audio.msPerBlock = 0; /** @todo */
+ if (!pTrack->Audio.msPerBlock) /* No ms per frame defined? Use default. */
+ pTrack->Audio.msPerBlock = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
+
+ vorbis_comment vc;
+ vorbis_comment_init(&vc);
+ vorbis_comment_add_tag(&vc,"ENCODER", vorbis_version_string());
+
+ ogg_packet pkt_ident;
+ ogg_packet pkt_comments;
+ ogg_packet pkt_setup;
+ vorbis_analysis_headerout(&pCodec->Audio.Vorbis.dsp_state, &vc, &pkt_ident, &pkt_comments, &pkt_setup);
+ AssertMsgBreakStmt(pkt_ident.bytes <= 255 && pkt_comments.bytes <= 255,
+ ("Too long header / comment packets\n"), vrc = VERR_INVALID_PARAMETER);
+
+ WEBMOGGVORBISPRIVDATA vorbisPrivData(pkt_ident.bytes, pkt_comments.bytes, pkt_setup.bytes);
+
+ uint8_t *pabHdr = &vorbisPrivData.abHdr[0];
+ memcpy(pabHdr, pkt_ident.packet, pkt_ident.bytes);
+ pabHdr += pkt_ident.bytes;
+ memcpy(pabHdr, pkt_comments.packet, pkt_comments.bytes);
+ pabHdr += pkt_comments.bytes;
+ memcpy(pabHdr, pkt_setup.packet, pkt_setup.bytes);
+ pabHdr += pkt_setup.bytes;
+
+ vorbis_comment_clear(&vc);
+
+ size_t const offHeaders = RT_OFFSETOF(WEBMOGGVORBISPRIVDATA, abHdr);
+
+ serializeString(MkvElem_CodecID, "A_VORBIS");
+ serializeData(MkvElem_CodecPrivate, &vorbisPrivData,
+ offHeaders + pkt_ident.bytes + pkt_comments.bytes + pkt_setup.bytes);
+ break;
+ }
+# endif /* VBOX_WITH_LIBVORBIS */
+ default:
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); /* Shouldn't ever happen (tm). */
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ serializeUnsignedInteger(MkvElem_CodecDelay, 0)
+ .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
+ .subStart(MkvElem_Audio)
+ .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
+ .serializeUnsignedInteger(MkvElem_Channels, cChannels)
+ .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
+ .subEnd(MkvElem_Audio)
+ .subEnd(MkvElem_TrackEntry);
+
+ pTrack->Audio.uHz = uHz;
+ pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
+
+ LogRel2(("Recording: WebM track #%RU8: Audio codec @ %RU16Hz (%RU16ms, %RU16 frames per block)\n",
+ pTrack->uTrack, pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
+
+ m_CurSeg.m_mapTracks[uTrack] = pTrack;
+
+ if (puTrack)
+ *puTrack = uTrack;
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ if (pTrack)
+ delete pTrack;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Adds a video track.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec data to use.
+ * @param uWidth Width (in pixels) of the video track.
+ * @param uHeight Height (in pixels) of the video track.
+ * @param uFPS FPS (Frames Per Second) of the video track.
+ * @param puTrack Track number of the added video track on success. Optional.
+ */
+int WebMWriter::AddVideoTrack(PRECORDINGCODEC pCodec, uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack)
+{
+#ifdef VBOX_WITH_LIBVPX
+ /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
+ * Using a track number 0 will show those files as being corrupted. */
+ const uint8_t uTrack = (uint8_t)m_CurSeg.m_mapTracks.size() + 1;
+
+ subStart(MkvElem_TrackEntry);
+
+ serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
+ serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
+ serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
+
+ WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, pCodec, uTrack, RTFileTell(getFile()));
+
+ /** @todo Resolve codec type. */
+ serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
+ .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
+ .serializeString(MkvElem_CodecID, "V_VP8")
+ .subStart(MkvElem_Video)
+ .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
+ .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
+ /* Some players rely on the FPS rate for timing calculations.
+ * So make sure to *always* include that. */
+ .serializeFloat (MkvElem_FrameRate, (float)uFPS)
+ .subEnd(MkvElem_Video);
+
+ subEnd(MkvElem_TrackEntry);
+
+ LogRel2(("Recording: WebM track #%RU8: Video\n", pTrack->uTrack));
+
+ m_CurSeg.m_mapTracks[uTrack] = pTrack;
+
+ if (puTrack)
+ *puTrack = uTrack;
+
+ return VINF_SUCCESS;
+#else
+ RT_NOREF(pCodec, uWidth, uHeight, uFPS, puTrack);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Gets file name.
+ *
+ * @returns File name as UTF-8 string.
+ */
+const com::Utf8Str& WebMWriter::GetFileName(void)
+{
+ return getFileName();
+}
+
+/**
+ * Gets current output file size.
+ *
+ * @returns File size in bytes.
+ */
+uint64_t WebMWriter::GetFileSize(void)
+{
+ return getFileSize();
+}
+
+/**
+ * Gets current free storage space available for the file.
+ *
+ * @returns Available storage free space.
+ */
+uint64_t WebMWriter::GetAvailableSpace(void)
+{
+ return getAvailableSpace();
+}
+
+/**
+ * Takes care of the initialization of the instance.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_SUPPORTED if a given codec is not supported.
+ * @param enmAudioCodec Audio codec to use.
+ * @param enmVideoCodec Video codec to use.
+ */
+int WebMWriter::init(RecordingAudioCodec_T enmAudioCodec, RecordingVideoCodec_T enmVideoCodec)
+{
+#ifndef VBOX_WITH_LIBVORBIS
+ AssertReturn(enmAudioCodec != RecordingAudioCodec_OggVorbis, VERR_NOT_SUPPORTED);
+#endif
+ AssertReturn( enmVideoCodec == RecordingVideoCodec_None
+ || enmVideoCodec == RecordingVideoCodec_VP8, VERR_NOT_SUPPORTED);
+
+ m_enmAudioCodec = enmAudioCodec;
+ m_enmVideoCodec = enmVideoCodec;
+
+ return m_CurSeg.init();
+}
+
+/**
+ * Takes care of the destruction of the instance.
+ */
+void WebMWriter::destroy(void)
+{
+ m_CurSeg.uninit();
+}
+
+/**
+ * Writes the WebM file header.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::writeHeader(void)
+{
+ LogFunc(("Header @ %RU64\n", RTFileTell(getFile())));
+
+ subStart(MkvElem_EBML)
+ .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
+ .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
+ .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
+ .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
+ .serializeString(MkvElem_DocType, "webm")
+ .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
+ .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
+ .subEnd(MkvElem_EBML);
+
+ subStart(MkvElem_Segment);
+
+ /* Save offset of current segment. */
+ m_CurSeg.m_offStart = RTFileTell(getFile());
+
+ writeSeekHeader();
+
+ /* Save offset of upcoming tracks segment. */
+ m_CurSeg.m_offTracks = RTFileTell(getFile());
+
+ /* The tracks segment starts right after this header. */
+ subStart(MkvElem_Tracks);
+ m_fInTracksSection = true;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a simple block into the EBML structure.
+ *
+ * @returns VBox status code.
+ * @param a_pTrack Track the simple block is assigned to.
+ * @param a_pBlock Simple block to write.
+ */
+int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
+{
+#ifdef LOG_ENABLED
+ WebMCluster &Cluster = m_CurSeg.m_CurCluster;
+
+ Log3Func(("[T%RU8C%RU64] Off=%RU64, AbsPTSMs=%RU64, RelToClusterMs=%RU16, %zu bytes\n",
+ a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()),
+ a_pBlock->Data.tcAbsPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
+#endif
+ /*
+ * Write a "Simple Block".
+ */
+ writeClassId(MkvElem_SimpleBlock);
+ /* Block size. */
+ writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
+ + m_cbTimecode /* Timecode size .*/
+ + 1 /* Flags size. */
+ + a_pBlock->Data.cb /* Actual frame data size. */), 4);
+ /* Track number. */
+ writeSize(a_pTrack->uTrack);
+ /* Timecode (relative to cluster opening timecode). */
+ writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
+ /* Flags. */
+ writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
+ /* Frame data. */
+ write(a_pBlock->Data.pv, a_pBlock->Data.cb);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes a simple block and enqueues it into the segment's render queue.
+ *
+ * @returns VBox status code.
+ * @param a_pTrack Track the simple block is assigned to.
+ * @param a_pBlock Simple block to write and enqueue.
+ */
+int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
+{
+ RT_NOREF(a_pTrack);
+
+ int vrc = VINF_SUCCESS;
+
+ try
+ {
+ const WebMTimecodeAbs tcAbsPTS = a_pBlock->Data.tcAbsPTSMs;
+
+ /* See if we already have an entry for the specified timecode in our queue. */
+ WebMBlockMap::iterator itQueue = m_CurSeg.m_queueBlocks.Map.find(tcAbsPTS);
+ if (itQueue != m_CurSeg.m_queueBlocks.Map.end()) /* Use existing queue. */
+ {
+ WebMTimecodeBlocks &Blocks = itQueue->second;
+ Blocks.Enqueue(a_pBlock);
+ }
+ else /* Create a new timecode entry. */
+ {
+ WebMTimecodeBlocks Blocks;
+ Blocks.Enqueue(a_pBlock);
+
+ m_CurSeg.m_queueBlocks.Map[tcAbsPTS] = Blocks;
+ }
+
+ vrc = processQueue(&m_CurSeg.m_queueBlocks, false /* fForce */);
+ }
+ catch(...)
+ {
+ delete a_pBlock;
+ a_pBlock = NULL;
+
+ vrc = VERR_NO_MEMORY;
+ }
+
+ return vrc;
+}
+
+/**
+ * Writes a data block to the specified track.
+ *
+ * @returns VBox status code.
+ * @param uTrack Track ID to write data to.
+ * @param pvData Pointer to data block to write.
+ * @param cbData Size (in bytes) of data block to write.
+ * @param tcAbsPTSMs Absolute PTS of simple data block.
+ * @param uFlags WebM block flags to use for this block.
+ */
+int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs, WebMBlockFlags uFlags)
+{
+ int vrc = RTCritSectEnter(&m_CurSeg.m_CritSect);
+ AssertRC(vrc);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.find(uTrack);
+ if (itTrack == m_CurSeg.m_mapTracks.end())
+ {
+ RTCritSectLeave(&m_CurSeg.m_CritSect);
+ return VERR_NOT_FOUND;
+ }
+
+ WebMTrack *pTrack = itTrack->second;
+ AssertPtr(pTrack);
+
+ if (m_fInTracksSection)
+ {
+ subEnd(MkvElem_Tracks);
+ m_fInTracksSection = false;
+ }
+
+ try
+ {
+ vrc = writeSimpleBlockQueued(pTrack,
+ new WebMSimpleBlock(pTrack,
+ tcAbsPTSMs, pvData, cbData, uFlags));
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ int vrc2 = RTCritSectLeave(&m_CurSeg.m_CritSect);
+ AssertRC(vrc2);
+
+ return vrc;
+}
+
+/**
+ * Processes a render queue.
+ *
+ * @returns VBox status code.
+ * @param pQueue Queue to process.
+ * @param fForce Whether forcing to process the render queue or not.
+ * Needed to drain the queues when terminating.
+ */
+int WebMWriter::processQueue(WebMQueue *pQueue, bool fForce)
+{
+ if (pQueue->tsLastProcessedMs == 0)
+ pQueue->tsLastProcessedMs = RTTimeMilliTS();
+
+ if (!fForce)
+ {
+ /* Only process when we reached a certain threshold. */
+ if (RTTimeMilliTS() - pQueue->tsLastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
+ return VINF_SUCCESS;
+ }
+
+ WebMCluster &Cluster = m_CurSeg.m_CurCluster;
+
+ /* Iterate through the block map. */
+ WebMBlockMap::iterator it = pQueue->Map.begin();
+ while (it != m_CurSeg.m_queueBlocks.Map.end())
+ {
+ WebMTimecodeAbs mapAbsPTSMs = it->first;
+ WebMTimecodeBlocks mapBlocks = it->second;
+
+ /* Whether to start a new cluster or not. */
+ bool fClusterStart = false;
+
+ /* If the current segment does not have any clusters (yet),
+ * take the first absolute PTS as the starting point for that segment. */
+ if (m_CurSeg.m_cClusters == 0)
+ {
+ m_CurSeg.m_tcAbsStartMs = mapAbsPTSMs;
+ fClusterStart = true;
+ }
+
+ /* Determine if we need to start a new cluster. */
+ /* No blocks written yet? Start a new cluster. */
+ if ( Cluster.cBlocks == 0
+ /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
+ || mapAbsPTSMs - Cluster.tcAbsStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS
+ /* If the block map indicates that a cluster is needed for this timecode, create one. */
+ || mapBlocks.fClusterNeeded)
+ {
+ fClusterStart = true;
+ }
+
+ if ( fClusterStart
+ && !mapBlocks.fClusterStarted)
+ {
+ /* Last written timecode of the current cluster. */
+ uint64_t tcAbsClusterLastWrittenMs;
+
+ if (Cluster.fOpen) /* Close current cluster first. */
+ {
+ Log2Func(("[C%RU64] End @ %RU64ms (duration = %RU64ms)\n",
+ Cluster.uID, Cluster.tcAbsLastWrittenMs, Cluster.tcAbsLastWrittenMs - Cluster.tcAbsStartMs));
+
+ /* Make sure that the current cluster contained some data. */
+ Assert(Cluster.offStart);
+ Assert(Cluster.cBlocks);
+
+ /* Save the last written timecode of the current cluster before closing it. */
+ tcAbsClusterLastWrittenMs = Cluster.tcAbsLastWrittenMs;
+
+ subEnd(MkvElem_Cluster);
+ Cluster.fOpen = false;
+ }
+ else /* First cluster ever? Use the segment's starting timecode. */
+ tcAbsClusterLastWrittenMs = m_CurSeg.m_tcAbsStartMs;
+
+ Cluster.fOpen = true;
+ Cluster.uID = m_CurSeg.m_cClusters;
+ /* Use the block map's currently processed TC as the cluster's starting TC. */
+ Cluster.tcAbsStartMs = mapAbsPTSMs;
+ Cluster.tcAbsLastWrittenMs = Cluster.tcAbsStartMs;
+ Cluster.offStart = RTFileTell(getFile());
+ Cluster.cBlocks = 0;
+
+ AssertMsg(Cluster.tcAbsStartMs <= mapAbsPTSMs,
+ ("Cluster #%RU64 start TC (%RU64) must not bigger than the block map's currently processed TC (%RU64)\n",
+ Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs));
+
+ Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
+ Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs, Cluster.offStart));
+
+ /* Insert cue points for all tracks if a new cluster has been started. */
+ WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsStartMs);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
+ ++itTrack;
+ }
+
+ m_CurSeg.m_lstCuePoints.push_back(pCuePoint);
+
+ subStart(MkvElem_Cluster)
+ .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs - m_CurSeg.m_tcAbsStartMs);
+
+ m_CurSeg.m_cClusters++;
+
+ mapBlocks.fClusterStarted = true;
+ }
+
+ Log2Func(("[C%RU64] SegTcAbsStartMs=%RU64, ClusterTcAbsStartMs=%RU64, ClusterTcAbsLastWrittenMs=%RU64, mapAbsPTSMs=%RU64\n",
+ Cluster.uID, m_CurSeg.m_tcAbsStartMs, Cluster.tcAbsStartMs, Cluster.tcAbsLastWrittenMs, mapAbsPTSMs));
+
+ /* Iterate through all blocks related to the current timecode. */
+ while (!mapBlocks.Queue.empty())
+ {
+ WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
+ AssertPtr(pBlock);
+
+ WebMTrack *pTrack = pBlock->pTrack;
+ AssertPtr(pTrack);
+
+ /* Calculate the block's relative time code to the current cluster's starting time code. */
+ Assert(pBlock->Data.tcAbsPTSMs >= Cluster.tcAbsStartMs);
+ pBlock->Data.tcRelToClusterMs = pBlock->Data.tcAbsPTSMs - Cluster.tcAbsStartMs;
+
+ int vrc2 = writeSimpleBlockEBML(pTrack, pBlock);
+ AssertRC(vrc2);
+
+ Cluster.cBlocks++;
+ Cluster.tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs;
+
+ pTrack->cTotalBlocks++;
+ pTrack->tcAbsLastWrittenMs = Cluster.tcAbsLastWrittenMs;
+
+ if (m_CurSeg.m_tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs)
+ m_CurSeg.m_tcAbsLastWrittenMs = pTrack->tcAbsLastWrittenMs;
+
+ /* Save a cue point if this is a keyframe (if no new cluster has been started,
+ * as this implies that a cue point already is present. */
+ if ( !fClusterStart
+ && (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME))
+ {
+ /* Insert cue points for all tracks if a new cluster has been started. */
+ WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsLastWrittenMs);
+
+ WebMTracks::iterator itTrack = m_CurSeg.m_mapTracks.begin();
+ while (itTrack != m_CurSeg.m_mapTracks.end())
+ {
+ pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
+ ++itTrack;
+ }
+
+ m_CurSeg.m_lstCuePoints.push_back(pCuePoint);
+ }
+
+ delete pBlock;
+ pBlock = NULL;
+
+ mapBlocks.Queue.pop();
+ }
+
+ Assert(mapBlocks.Queue.empty());
+
+ m_CurSeg.m_queueBlocks.Map.erase(it);
+
+ it = m_CurSeg.m_queueBlocks.Map.begin();
+ }
+
+ Assert(m_CurSeg.m_queueBlocks.Map.empty());
+
+ pQueue->tsLastProcessedMs = RTTimeMilliTS();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes the WebM footer.
+ *
+ * @returns VBox status code.
+ */
+int WebMWriter::writeFooter(void)
+{
+ AssertReturn(isOpen(), VERR_WRONG_ORDER);
+
+ if (m_fInTracksSection)
+ {
+ subEnd(MkvElem_Tracks);
+ m_fInTracksSection = false;
+ }
+
+ if (m_CurSeg.m_CurCluster.fOpen)
+ {
+ subEnd(MkvElem_Cluster);
+ m_CurSeg.m_CurCluster.fOpen = false;
+ }
+
+ /*
+ * Write Cues element.
+ */
+ m_CurSeg.m_offCues = RTFileTell(getFile());
+ LogFunc(("Cues @ %RU64\n", m_CurSeg.m_offCues));
+
+ subStart(MkvElem_Cues);
+
+ WebMCuePointList::iterator itCuePoint = m_CurSeg.m_lstCuePoints.begin();
+ while (itCuePoint != m_CurSeg.m_lstCuePoints.end())
+ {
+ WebMCuePoint *pCuePoint = (*itCuePoint);
+ AssertPtr(pCuePoint);
+
+ LogFunc(("CuePoint @ %RU64: %zu tracks, tcAbs=%RU64)\n",
+ RTFileTell(getFile()), pCuePoint->Pos.size(), pCuePoint->tcAbs));
+
+ subStart(MkvElem_CuePoint)
+ .serializeUnsignedInteger(MkvElem_CueTime, pCuePoint->tcAbs);
+
+ WebMCueTrackPosMap::iterator itTrackPos = pCuePoint->Pos.begin();
+ while (itTrackPos != pCuePoint->Pos.end())
+ {
+ WebMCueTrackPosEntry *pTrackPos = itTrackPos->second;
+ AssertPtr(pTrackPos);
+
+ LogFunc(("TrackPos (track #%RU32) @ %RU64, offCluster=%RU64)\n",
+ itTrackPos->first, RTFileTell(getFile()), pTrackPos->offCluster));
+
+ subStart(MkvElem_CueTrackPositions)
+ .serializeUnsignedInteger(MkvElem_CueTrack, itTrackPos->first)
+ .serializeUnsignedInteger(MkvElem_CueClusterPosition, pTrackPos->offCluster - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_CueTrackPositions);
+
+ ++itTrackPos;
+ }
+
+ subEnd(MkvElem_CuePoint);
+
+ ++itCuePoint;
+ }
+
+ subEnd(MkvElem_Cues);
+ subEnd(MkvElem_Segment);
+
+ /*
+ * Re-Update seek header with final information.
+ */
+
+ writeSeekHeader();
+
+ return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL);
+}
+
+/**
+ * Writes the segment's seek header.
+ */
+void WebMWriter::writeSeekHeader(void)
+{
+ if (m_CurSeg.m_offSeekInfo)
+ RTFileSeek(getFile(), m_CurSeg.m_offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
+ else
+ m_CurSeg.m_offSeekInfo = RTFileTell(getFile());
+
+ LogFunc(("Seek Header @ %RU64\n", m_CurSeg.m_offSeekInfo));
+
+ subStart(MkvElem_SeekHead);
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offTracks - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ if (m_CurSeg.m_offCues)
+ LogFunc(("Updating Cues @ %RU64\n", m_CurSeg.m_offCues));
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offCues - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ subStart(MkvElem_Seek)
+ .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
+ .serializeUnsignedInteger(MkvElem_SeekPosition, m_CurSeg.m_offInfo - m_CurSeg.m_offStart, 8)
+ .subEnd(MkvElem_Seek);
+
+ subEnd(MkvElem_SeekHead);
+
+ /*
+ * Write the segment's info element.
+ */
+
+ /* Save offset of the segment's info element. */
+ m_CurSeg.m_offInfo = RTFileTell(getFile());
+
+ LogFunc(("Info @ %RU64\n", m_CurSeg.m_offInfo));
+
+ char szMux[64];
+ RTStrPrintf(szMux, sizeof(szMux),
+#ifdef VBOX_WITH_LIBVPX
+ "vpxenc%s", vpx_codec_version_str()
+#else
+ "unknown"
+#endif
+ );
+ char szApp[64];
+ RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
+
+ const WebMTimecodeAbs tcAbsDurationMs = m_CurSeg.m_tcAbsLastWrittenMs - m_CurSeg.m_tcAbsStartMs;
+
+ if (!m_CurSeg.m_lstCuePoints.empty())
+ {
+ LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs));
+ AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n"));
+ }
+
+ subStart(MkvElem_Info)
+ .serializeUnsignedInteger(MkvElem_TimecodeScale, m_CurSeg.m_uTimecodeScaleFactor)
+ .serializeFloat(MkvElem_Segment_Duration, tcAbsDurationMs)
+ .serializeString(MkvElem_MuxingApp, szMux)
+ .serializeString(MkvElem_WritingApp, szApp)
+ .subEnd(MkvElem_Info);
+}
diff --git a/src/VBox/Main/src-client/win/Makefile.kup b/src/VBox/Main/src-client/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-client/win/Makefile.kup
diff --git a/src/VBox/Main/src-client/win/VBoxC.def b/src/VBox/Main/src-client/win/VBoxC.def
new file mode 100644
index 00000000..a5aabf2a
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxC.def
@@ -0,0 +1,37 @@
+;; @file
+;
+; VBoxC DLL Definition File.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+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..2cb362e0
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxC.rc
@@ -0,0 +1,72 @@
+/* $Id: VBoxC.rc $ */
+/** @file
+ * VBoxC - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#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..94711fa2
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxClient-x86.def
@@ -0,0 +1,36 @@
+; $Id: VBoxClient-x86.def $
+;; @file
+; VBoxClient-x86 DLL Definition File.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+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..85b3c8da
--- /dev/null
+++ b/src/VBox/Main/src-client/win/VBoxClient-x86.rc
@@ -0,0 +1,73 @@
+/* $Id: VBoxClient-x86.rc $ */
+/** @file
+ * VBoxC - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#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..57b7c839
--- /dev/null
+++ b/src/VBox/Main/src-client/win/dllmain.cpp
@@ -0,0 +1,176 @@
+/* $Id: dllmain.cpp $ */
+/** @file
+ * VBoxC - COM DLL exports and DLL init/term.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBox/com/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..be69459e
--- /dev/null
+++ b/src/VBox/Main/src-client/win/precomp_vcc.h
@@ -0,0 +1,49 @@
+/* $Id: precomp_vcc.h $ */
+/** @file
+ * VirtualBox COM - Visual C++ precompiled header for VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+#include <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..adbad0c9
--- /dev/null
+++ b/src/VBox/Main/src-client/xpcom/module.cpp
@@ -0,0 +1,159 @@
+/* $Id: module.cpp $ */
+/** @file
+ * XPCOM module implementation functions
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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..979c6b13
--- /dev/null
+++ b/src/VBox/Main/src-client/xpcom/precomp_gcc.h
@@ -0,0 +1,53 @@
+/* $Id: precomp_gcc.h $ */
+/** @file
+ * VirtualBox COM - GNU C++ precompiled header for VBoxC.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <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
+