summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-all
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-all
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-all')
-rw-r--r--src/VBox/Main/src-all/AuthLibrary.cpp267
-rw-r--r--src/VBox/Main/src-all/AutoCaller.cpp585
-rw-r--r--src/VBox/Main/src-all/ConsoleSharedFolderImpl.cpp333
-rw-r--r--src/VBox/Main/src-all/CryptoUtils.cpp344
-rw-r--r--src/VBox/Main/src-all/DisplayPNGUtil.cpp233
-rw-r--r--src/VBox/Main/src-all/DisplayResampleImage.cpp159
-rw-r--r--src/VBox/Main/src-all/DisplayUtils.cpp225
-rw-r--r--src/VBox/Main/src-all/EventImpl.cpp1690
-rw-r--r--src/VBox/Main/src-all/ExtPackManagerImpl.cpp3839
-rw-r--r--src/VBox/Main/src-all/ExtPackUtil.cpp1473
-rw-r--r--src/VBox/Main/src-all/Global.cpp791
-rw-r--r--src/VBox/Main/src-all/GlobalStatusConversion.cpp148
-rw-r--r--src/VBox/Main/src-all/HashedPw.cpp114
-rw-r--r--src/VBox/Main/src-all/Logging.cpp33
-rw-r--r--src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp289
-rw-r--r--src/VBox/Main/src-all/Makefile.kup0
-rw-r--r--src/VBox/Main/src-all/NvramStoreImpl.cpp1648
-rw-r--r--src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp160
-rw-r--r--src/VBox/Main/src-all/ProgressImpl.cpp1215
-rw-r--r--src/VBox/Main/src-all/QMTranslatorImpl.cpp671
-rw-r--r--src/VBox/Main/src-all/SecretKeyStore.cpp248
-rw-r--r--src/VBox/Main/src-all/SharedFolderImpl.cpp413
-rw-r--r--src/VBox/Main/src-all/TextScript.cpp388
-rw-r--r--src/VBox/Main/src-all/ThreadTask.cpp136
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-end-alternative.d3
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-end.d9
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-start-alternative.d39
-rw-r--r--src/VBox/Main/src-all/VBoxAPI-start.d33
-rw-r--r--src/VBox/Main/src-all/VBoxLibSsh.def35
-rw-r--r--src/VBox/Main/src-all/VirtualBoxBase.cpp884
-rw-r--r--src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp317
-rw-r--r--src/VBox/Main/src-all/VirtualBoxTranslator.cpp593
-rw-r--r--src/VBox/Main/src-all/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h50
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc34
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.c2598
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.def36
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStub.rc34
-rw-r--r--src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc34
-rw-r--r--src/VBox/Main/src-all/win/VirtualBox_rgs.xsl196
-rw-r--r--src/VBox/Main/src-all/win/comregister.cmd212
-rw-r--r--src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h52
42 files changed, 20561 insertions, 0 deletions
diff --git a/src/VBox/Main/src-all/AuthLibrary.cpp b/src/VBox/Main/src-all/AuthLibrary.cpp
new file mode 100644
index 00000000..f58c11ef
--- /dev/null
+++ b/src/VBox/Main/src-all/AuthLibrary.cpp
@@ -0,0 +1,267 @@
+/* $Id: AuthLibrary.cpp $ */
+/** @file
+ * Main - External authentication library interface.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN
+#include "AuthLibrary.h"
+#include "LoggingNew.h"
+
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+typedef struct AuthCtx
+{
+ AuthResult result;
+
+ PAUTHENTRY3 pfnAuthEntry3;
+ PAUTHENTRY2 pfnAuthEntry2;
+ PAUTHENTRY pfnAuthEntry;
+
+ const char *pszCaller;
+ PAUTHUUID pUuid;
+ AuthGuestJudgement guestJudgement;
+ const char *pszUser;
+ const char *pszPassword;
+ const char *pszDomain;
+ int fLogon;
+ unsigned clientId;
+} AuthCtx;
+
+static DECLCALLBACK(int) authThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ AuthCtx *pCtx = (AuthCtx *)pvUser;
+
+ if (pCtx->pfnAuthEntry3)
+ {
+ pCtx->result = pCtx->pfnAuthEntry3(pCtx->pszCaller, pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain,
+ pCtx->fLogon, pCtx->clientId);
+ }
+ else if (pCtx->pfnAuthEntry2)
+ {
+ pCtx->result = pCtx->pfnAuthEntry2(pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain,
+ pCtx->fLogon, pCtx->clientId);
+ }
+ else if (pCtx->pfnAuthEntry)
+ {
+ pCtx->result = pCtx->pfnAuthEntry(pCtx->pUuid, pCtx->guestJudgement,
+ pCtx->pszUser, pCtx->pszPassword, pCtx->pszDomain);
+ }
+ return VINF_SUCCESS;
+}
+
+static AuthResult authCall(AuthCtx *pCtx)
+{
+ AuthResult result = AuthResultAccessDenied;
+
+ /* Use a separate thread because external modules might need a lot of stack space. */
+ RTTHREAD thread = NIL_RTTHREAD;
+ int vrc = RTThreadCreate(&thread, authThread, pCtx, 512*_1K,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "VRDEAuth");
+ LogFlowFunc(("RTThreadCreate %Rrc\n", vrc));
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTThreadWait(thread, RT_INDEFINITE_WAIT, NULL);
+ LogFlowFunc(("RTThreadWait %Rrc\n", vrc));
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Only update the result if the thread finished without errors. */
+ result = pCtx->result;
+ }
+ else
+ {
+ LogRel(("AUTH: Unable to execute the auth thread %Rrc\n", vrc));
+ }
+
+ return result;
+}
+
+int AuthLibLoad(AUTHLIBRARYCONTEXT *pAuthLibCtx, const char *pszLibrary)
+{
+ RT_ZERO(*pAuthLibCtx);
+
+ /* Load the external authentication library. */
+ LogRel(("AUTH: Loading external authentication library '%s'\n", pszLibrary));
+
+ int vrc;
+ if (RTPathHavePath(pszLibrary))
+ vrc = RTLdrLoad(pszLibrary, &pAuthLibCtx->hAuthLibrary);
+ else
+ {
+ vrc = RTLdrLoadAppPriv(pszLibrary, &pAuthLibCtx->hAuthLibrary);
+ if (RT_FAILURE(vrc))
+ {
+ /* Backward compatibility with old default 'VRDPAuth' name.
+ * Try to load new default 'VBoxAuth' instead.
+ */
+ if (RTStrICmp(pszLibrary, "VRDPAuth") == 0)
+ {
+ LogRel(("AUTH: Loading external authentication library 'VBoxAuth'\n"));
+ vrc = RTLdrLoadAppPriv("VBoxAuth", &pAuthLibCtx->hAuthLibrary);
+ }
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("AUTH: Failed to load external authentication library: %Rrc\n", vrc));
+ pAuthLibCtx->hAuthLibrary = NIL_RTLDRMOD;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ typedef struct AuthEntryInfoStruct
+ {
+ const char *pszName;
+ void **ppvAddress;
+ } AuthEntryInfo;
+
+ AuthEntryInfo entries[] =
+ {
+ { AUTHENTRY3_NAME, (void **)&pAuthLibCtx->pfnAuthEntry3 },
+ { AUTHENTRY2_NAME, (void **)&pAuthLibCtx->pfnAuthEntry2 },
+ { AUTHENTRY_NAME, (void **)&pAuthLibCtx->pfnAuthEntry },
+ { NULL, NULL }
+ };
+
+ /* Get the entry point. */
+ AuthEntryInfo *pEntryInfo = &entries[0];
+ while (pEntryInfo->pszName)
+ {
+ *pEntryInfo->ppvAddress = NULL;
+
+ int vrc2 = RTLdrGetSymbol(pAuthLibCtx->hAuthLibrary, pEntryInfo->pszName, pEntryInfo->ppvAddress);
+ if (RT_SUCCESS(vrc2))
+ {
+ /* Found an entry point. */
+ LogRel(("AUTH: Using entry point '%s'\n", pEntryInfo->pszName));
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ if (vrc2 != VERR_SYMBOL_NOT_FOUND)
+ LogRel(("AUTH: Could not resolve import '%s': %Rrc\n", pEntryInfo->pszName, vrc2));
+
+ vrc = vrc2;
+
+ pEntryInfo++;
+ }
+ }
+
+ if (RT_FAILURE(vrc))
+ AuthLibUnload(pAuthLibCtx);
+
+ return vrc;
+}
+
+void AuthLibUnload(AUTHLIBRARYCONTEXT *pAuthLibCtx)
+{
+ if (pAuthLibCtx->hAuthLibrary != NIL_RTLDRMOD)
+ RTLdrClose(pAuthLibCtx->hAuthLibrary);
+
+ RT_ZERO(*pAuthLibCtx);
+ pAuthLibCtx->hAuthLibrary = NIL_RTLDRMOD;
+}
+
+AuthResult AuthLibAuthenticate(const AUTHLIBRARYCONTEXT *pAuthLibCtx,
+ PCRTUUID pUuid, AuthGuestJudgement guestJudgement,
+ const char *pszUser, const char *pszPassword, const char *pszDomain,
+ uint32_t u32ClientId)
+{
+ AuthResult result = AuthResultAccessDenied;
+
+ AUTHUUID rawuuid;
+ memcpy(rawuuid, pUuid, sizeof(rawuuid));
+
+ LogFlowFunc(("pAuthLibCtx = %p, uuid = %RTuuid, guestJudgement = %d, pszUser = %s, pszPassword = %s, pszDomain = %s, u32ClientId = %d\n",
+ pAuthLibCtx, rawuuid, guestJudgement, pszUser, pszPassword, pszDomain, u32ClientId));
+
+ if ( pAuthLibCtx->hAuthLibrary
+ && (pAuthLibCtx->pfnAuthEntry || pAuthLibCtx->pfnAuthEntry2 || pAuthLibCtx->pfnAuthEntry3))
+ {
+ AuthCtx ctx;
+ ctx.result = AuthResultAccessDenied; /* Denied by default. */
+ ctx.pfnAuthEntry3 = pAuthLibCtx->pfnAuthEntry3;
+ ctx.pfnAuthEntry2 = pAuthLibCtx->pfnAuthEntry2;
+ ctx.pfnAuthEntry = pAuthLibCtx->pfnAuthEntry;
+ ctx.pszCaller = "vrde";
+ ctx.pUuid = &rawuuid;
+ ctx.guestJudgement = guestJudgement;
+ ctx.pszUser = pszUser;
+ ctx.pszPassword = pszPassword;
+ ctx.pszDomain = pszDomain;
+ ctx.fLogon = true;
+ ctx.clientId = u32ClientId;
+
+ result = authCall(&ctx);
+ }
+ else
+ {
+ LogRelMax(8, ("AUTH: Invalid authentication module context\n"));
+ AssertFailed();
+ }
+
+ LogFlowFunc(("result = %d\n", result));
+
+ return result;
+}
+
+void AuthLibDisconnect(const AUTHLIBRARYCONTEXT *pAuthLibCtx, PCRTUUID pUuid, uint32_t u32ClientId)
+{
+ AUTHUUID rawuuid;
+ memcpy(rawuuid, pUuid, sizeof(rawuuid));
+
+ LogFlowFunc(("pAuthLibCtx = %p, , uuid = %RTuuid, u32ClientId = %d\n",
+ pAuthLibCtx, rawuuid, u32ClientId));
+
+ if ( pAuthLibCtx->hAuthLibrary
+ && (pAuthLibCtx->pfnAuthEntry || pAuthLibCtx->pfnAuthEntry2 || pAuthLibCtx->pfnAuthEntry3))
+ {
+ AuthCtx ctx;
+ ctx.result = AuthResultAccessDenied; /* Not used. */
+ ctx.pfnAuthEntry3 = pAuthLibCtx->pfnAuthEntry3;
+ ctx.pfnAuthEntry2 = pAuthLibCtx->pfnAuthEntry2;
+ ctx.pfnAuthEntry = NULL; /* Does not use disconnect notification. */
+ ctx.pszCaller = "vrde";
+ ctx.pUuid = &rawuuid;
+ ctx.guestJudgement = AuthGuestNotAsked;
+ ctx.pszUser = NULL;
+ ctx.pszPassword = NULL;
+ ctx.pszDomain = NULL;
+ ctx.fLogon = false;
+ ctx.clientId = u32ClientId;
+
+ authCall(&ctx);
+ }
+}
diff --git a/src/VBox/Main/src-all/AutoCaller.cpp b/src/VBox/Main/src-all/AutoCaller.cpp
new file mode 100644
index 00000000..4e5de550
--- /dev/null
+++ b/src/VBox/Main/src-all/AutoCaller.cpp
@@ -0,0 +1,585 @@
+/* $Id: AutoCaller.cpp $ */
+/** @file
+ * VirtualBox object state 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
+#include <iprt/semaphore.h>
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+
+#include "VBoxNls.h"
+
+
+DECLARE_TRANSLATION_CONTEXT(AutoCallerCtx);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ObjectState methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+ObjectState::ObjectState() : mStateLock(LOCKCLASS_OBJECTSTATE)
+{
+ AssertFailed();
+}
+
+ObjectState::ObjectState(VirtualBoxBase *aObj) :
+ mObj(aObj), mStateLock(LOCKCLASS_OBJECTSTATE)
+{
+ Assert(mObj);
+ mState = NotReady;
+ mStateChangeThread = NIL_RTTHREAD;
+ mCallers = 0;
+ mFailedRC = S_OK;
+ mpFailedEI = NULL;
+ mZeroCallersSem = NIL_RTSEMEVENT;
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ mInitUninitWaiters = 0;
+}
+
+ObjectState::~ObjectState()
+{
+ Assert(mInitUninitWaiters == 0);
+ Assert(mInitUninitSem == NIL_RTSEMEVENTMULTI);
+ if (mZeroCallersSem != NIL_RTSEMEVENT)
+ RTSemEventDestroy(mZeroCallersSem);
+ mCallers = 0;
+ mStateChangeThread = NIL_RTTHREAD;
+ mState = NotReady;
+ mFailedRC = S_OK;
+ if (mpFailedEI)
+ {
+ delete mpFailedEI;
+ mpFailedEI = NULL;
+ }
+ mObj = NULL;
+}
+
+ObjectState::State ObjectState::getState()
+{
+ AutoReadLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+ return mState;
+}
+
+/**
+ * Increments the number of calls to this object by one.
+ *
+ * After this method succeeds, it is guaranteed that the object will remain
+ * in the Ready (or in the Limited) state at least until #releaseCaller() is
+ * called.
+ *
+ * This method is intended to mark the beginning of sections of code within
+ * methods of COM objects that depend on the readiness (Ready) state. The
+ * Ready state is a primary "ready to serve" state. Usually all code that
+ * works with component's data depends on it. On practice, this means that
+ * almost every public method, setter or getter of the object should add
+ * itself as an object's caller at the very beginning, to protect from an
+ * unexpected uninitialization that may happen on a different thread.
+ *
+ * Besides the Ready state denoting that the object is fully functional,
+ * there is a special Limited state. The Limited state means that the object
+ * is still functional, but its functionality is limited to some degree, so
+ * not all operations are possible. The @a aLimited argument to this method
+ * determines whether the caller represents this limited functionality or
+ * not.
+ *
+ * This method succeeds (and increments the number of callers) only if the
+ * current object's state is Ready. Otherwise, it will return E_ACCESSDENIED
+ * to indicate that the object is not operational. There are two exceptions
+ * from this rule:
+ * <ol>
+ * <li>If the @a aLimited argument is |true|, then this method will also
+ * succeed if the object's state is Limited (or Ready, of course).
+ * </li>
+ * <li>If this method is called from the same thread that placed
+ * the object to InInit or InUninit state (i.e. either from within the
+ * AutoInitSpan or AutoUninitSpan scope), it will succeed as well (but
+ * will not increase the number of callers).
+ * </li>
+ * </ol>
+ *
+ * Normally, calling addCaller() never blocks. However, if this method is
+ * called by a thread created from within the AutoInitSpan scope and this
+ * scope is still active (i.e. the object state is InInit), it will block
+ * until the AutoInitSpan destructor signals that it has finished
+ * initialization.
+ *
+ * When this method returns a failure, the caller must not use the object
+ * and should return the failed result code to its own caller.
+ *
+ * @param aLimited |true| to add a limited caller.
+ *
+ * @return S_OK on success or E_ACCESSDENIED on failure.
+ *
+ * @sa #releaseCaller()
+ */
+HRESULT ObjectState::addCaller(bool aLimited /* = false */)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = E_ACCESSDENIED;
+
+ if (mState == Ready || (aLimited && mState == Limited))
+ {
+ /* if Ready or allows Limited, increase the number of callers */
+ ++mCallers;
+ hrc = S_OK;
+ }
+ else
+ if (mState == InInit || mState == InUninit)
+ {
+ if (mStateChangeThread == RTThreadSelf())
+ {
+ /* Called from the same thread that is doing AutoInitSpan or
+ * AutoUninitSpan, just succeed */
+ hrc = S_OK;
+ }
+ else if (mState == InInit)
+ {
+ /* addCaller() is called by a "child" thread while the "parent"
+ * thread is still doing AutoInitSpan/AutoReinitSpan, so wait for
+ * the state to become either Ready/Limited or InitFailed (in
+ * case of init failure).
+ *
+ * Note that we increase the number of callers anyway -- to
+ * prevent AutoUninitSpan from early completion if we are
+ * still not scheduled to pick up the posted semaphore when
+ * uninit() is called.
+ */
+ ++mCallers;
+
+ /* lazy semaphore creation */
+ if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiCreate(&mInitUninitSem);
+ Assert(mInitUninitWaiters == 0);
+ }
+
+ ++mInitUninitWaiters;
+
+ LogFlowThisFunc(("Waiting for AutoInitSpan/AutoReinitSpan to finish...\n"));
+
+ stateLock.release();
+ RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
+ stateLock.acquire();
+
+ if (--mInitUninitWaiters == 0)
+ {
+ /* destroy the semaphore since no more necessary */
+ RTSemEventMultiDestroy(mInitUninitSem);
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (mState == Ready || (aLimited && mState == Limited))
+ hrc = S_OK;
+ else
+ {
+ Assert(mCallers != 0);
+ --mCallers;
+ if (mCallers == 0 && mState == InUninit)
+ {
+ /* inform AutoUninitSpan ctor there are no more callers */
+ RTSemEventSignal(mZeroCallersSem);
+ }
+ }
+ }
+ }
+
+ if (FAILED(hrc))
+ {
+ if (mState == Limited)
+ hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object functionality is limited"));
+ else if (FAILED(mFailedRC) && mFailedRC != E_ACCESSDENIED)
+ {
+ /* replay recorded error information */
+ if (mpFailedEI)
+ ErrorInfoKeeper eik(*mpFailedEI);
+ hrc = mFailedRC;
+ }
+ else
+ hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object is not ready"));
+ }
+
+ return hrc;
+}
+
+/**
+ * Decreases the number of calls to this object by one.
+ *
+ * Must be called after every #addCaller() when protecting the object
+ * from uninitialization is no more necessary.
+ */
+void ObjectState::releaseCaller()
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ if (mState == Ready || mState == Limited)
+ {
+ /* if Ready or Limited, decrease the number of callers */
+ AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
+ --mCallers;
+
+ return;
+ }
+
+ if (mState == InInit || mState == InUninit)
+ {
+ if (mStateChangeThread == RTThreadSelf())
+ {
+ /* Called from the same thread that is doing AutoInitSpan or
+ * AutoUninitSpan: just succeed */
+ return;
+ }
+
+ if (mState == InUninit)
+ {
+ /* the caller is being released after AutoUninitSpan has begun */
+ AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
+ --mCallers;
+
+ if (mCallers == 0)
+ /* inform the Auto*UninitSpan ctor there are no more callers */
+ RTSemEventSignal(mZeroCallersSem);
+
+ return;
+ }
+ }
+
+ AssertMsgFailed(("mState = %d!", mState));
+}
+
+bool ObjectState::autoInitSpanConstructor(ObjectState::State aExpectedState)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ mFailedRC = S_OK;
+ if (mpFailedEI)
+ {
+ delete mpFailedEI;
+ mpFailedEI = NULL;
+ }
+
+ if (mState == aExpectedState)
+ {
+ setState(InInit);
+ return true;
+ }
+ else
+ return false;
+}
+
+void ObjectState::autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *apFailedEI)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState == InInit);
+
+ if (mCallers > 0 && mInitUninitWaiters > 0)
+ {
+ /* We have some pending addCaller() calls on other threads (created
+ * during InInit), signal that InInit is finished and they may go on. */
+ RTSemEventMultiSignal(mInitUninitSem);
+ }
+
+ if (aNewState == InitFailed || aNewState == Limited)
+ {
+ mFailedRC = aFailedRC;
+ /* apFailedEI may be NULL, when there is no explicit setFailed() or
+ * setLimited() call, which also implies that aFailedRC is S_OK.
+ * This case is used by objects (the majority) which don't want
+ * delayed error signalling. */
+ mpFailedEI = apFailedEI;
+ }
+ else
+ {
+ Assert(SUCCEEDED(aFailedRC));
+ Assert(apFailedEI == NULL);
+ Assert(mpFailedEI == NULL);
+ }
+
+ setState(aNewState);
+}
+
+ObjectState::State ObjectState::autoUninitSpanConstructor(bool fTry)
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState != InInit);
+
+ if (mState == NotReady)
+ {
+ /* do nothing if already uninitialized */
+ return mState;
+ }
+ else if (mState == InUninit)
+ {
+ /* Another thread has already started uninitialization, wait for its
+ * completion. This is necessary to make sure that when this method
+ * returns, the object state is well-defined (NotReady). */
+
+ if (fTry)
+ return Ready;
+
+ /* lazy semaphore creation */
+ if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiCreate(&mInitUninitSem);
+ Assert(mInitUninitWaiters == 0);
+ }
+ ++mInitUninitWaiters;
+
+ LogFlowFunc(("{%p}: Waiting for AutoUninitSpan to finish...\n", mObj));
+
+ stateLock.release();
+ RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
+ stateLock.acquire();
+
+ if (--mInitUninitWaiters == 0)
+ {
+ /* destroy the semaphore since no more necessary */
+ RTSemEventMultiDestroy(mInitUninitSem);
+ mInitUninitSem = NIL_RTSEMEVENTMULTI;
+ }
+
+ /* the other thread set it to NotReady */
+ return mState;
+ }
+
+ /* go to InUninit to prevent from adding new callers */
+ setState(InUninit);
+
+ /* wait for already existing callers to drop to zero */
+ if (mCallers > 0)
+ {
+ if (fTry)
+ return Ready;
+
+ /* lazy creation */
+ Assert(mZeroCallersSem == NIL_RTSEMEVENT);
+ RTSemEventCreate(&mZeroCallersSem);
+
+ /* wait until remaining callers release the object */
+ LogFlowFunc(("{%p}: Waiting for callers (%d) to drop to zero...\n",
+ mObj, mCallers));
+
+ stateLock.release();
+ RTSemEventWait(mZeroCallersSem, RT_INDEFINITE_WAIT);
+ }
+ return mState;
+}
+
+void ObjectState::autoUninitSpanDestructor()
+{
+ AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
+
+ Assert(mState == InUninit);
+
+ setState(NotReady);
+}
+
+
+void ObjectState::setState(ObjectState::State aState)
+{
+ Assert(mState != aState);
+ mState = aState;
+ mStateChangeThread = RTThreadSelf();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AutoInitSpan methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart initialization span object that places the object to
+ * InInit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @param aObj |this| pointer of the managed VirtualBoxBase object whose
+ * init() method is being called.
+ * @param aResult Default initialization result.
+ */
+AutoInitSpan::AutoInitSpan(VirtualBoxBase *aObj,
+ Result aResult /* = Failed */)
+ : mObj(aObj),
+ mResult(aResult),
+ mOk(false),
+ mFailedRC(S_OK),
+ mpFailedEI(NULL)
+{
+ Assert(mObj);
+ mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::NotReady);
+ AssertReturnVoid(mOk);
+}
+
+/**
+ * Places the managed VirtualBoxBase object to Ready/Limited state if the
+ * initialization succeeded or partly succeeded, or places it to InitFailed
+ * state and calls the object's uninit() method.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ */
+AutoInitSpan::~AutoInitSpan()
+{
+ /* if the state was other than NotReady, do nothing */
+ if (!mOk)
+ {
+ Assert(SUCCEEDED(mFailedRC));
+ Assert(mpFailedEI == NULL);
+ return;
+ }
+
+ ObjectState::State newState;
+ if (mResult == Succeeded)
+ newState = ObjectState::Ready;
+ else if (mResult == Limited)
+ newState = ObjectState::Limited;
+ else
+ newState = ObjectState::InitFailed;
+ mObj->getObjectState().autoInitSpanDestructor(newState, mFailedRC, mpFailedEI);
+ mFailedRC = S_OK;
+ mpFailedEI = NULL; /* now owned by ObjectState instance */
+ if (newState == ObjectState::InitFailed)
+ {
+ /* call uninit() to let the object uninit itself after failed init() */
+ mObj->uninit();
+ }
+}
+
+// AutoReinitSpan methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart re-initialization span object and places the object to
+ * InInit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @param aObj |this| pointer of the managed VirtualBoxBase object whose
+ * re-initialization method is being called.
+ */
+AutoReinitSpan::AutoReinitSpan(VirtualBoxBase *aObj)
+ : mObj(aObj),
+ mSucceeded(false),
+ mOk(false)
+{
+ Assert(mObj);
+ mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::Limited);
+ AssertReturnVoid(mOk);
+}
+
+/**
+ * Places the managed VirtualBoxBase object to Ready state if the
+ * re-initialization succeeded (i.e. #setSucceeded() has been called) or back to
+ * Limited state otherwise.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ */
+AutoReinitSpan::~AutoReinitSpan()
+{
+ /* if the state was other than Limited, do nothing */
+ if (!mOk)
+ return;
+
+ ObjectState::State newState;
+ if (mSucceeded)
+ newState = ObjectState::Ready;
+ else
+ newState = ObjectState::Limited;
+ mObj->getObjectState().autoInitSpanDestructor(newState, S_OK, NULL);
+ /* If later AutoReinitSpan can truly fail (today there is no way) then
+ * in this place there needs to be an mObj->uninit() call just like in
+ * the AutoInitSpan destructor. In that case it might make sense to
+ * let AutoReinitSpan inherit from AutoInitSpan, as the code can be
+ * made (almost) identical. */
+}
+
+// AutoUninitSpan methods
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a smart uninitialization span object and places this object to
+ * InUninit state.
+ *
+ * Please see the AutoInitSpan class description for more info.
+ *
+ * @note This method blocks the current thread execution until the number of
+ * callers of the managed VirtualBoxBase object drops to zero!
+ *
+ * @param aObj |this| pointer of the VirtualBoxBase object whose uninit()
+ * method is being called.
+ * @param fTry @c true if the wait for other callers should be skipped,
+ * requiring checking if the uninit span is actually operational.
+ */
+AutoUninitSpan::AutoUninitSpan(VirtualBoxBase *aObj, bool fTry /* = false */)
+ : mObj(aObj),
+ mInitFailed(false),
+ mUninitDone(false),
+ mUninitFailed(false)
+{
+ Assert(mObj);
+ ObjectState::State state;
+ state = mObj->getObjectState().autoUninitSpanConstructor(fTry);
+ if (state == ObjectState::InitFailed)
+ mInitFailed = true;
+ else if (state == ObjectState::NotReady)
+ mUninitDone = true;
+ else if (state == ObjectState::Ready)
+ mUninitFailed = true;
+}
+
+/**
+ * Places the managed VirtualBoxBase object to the NotReady state.
+ */
+AutoUninitSpan::~AutoUninitSpan()
+{
+ /* do nothing if already uninitialized */
+ if (mUninitDone || mUninitFailed)
+ return;
+
+ mObj->getObjectState().autoUninitSpanDestructor();
+}
+
+/**
+ * Marks the uninitializion as succeeded.
+ *
+ * Same as the destructor, and makes the destructor do nothing.
+ */
+void AutoUninitSpan::setSucceeded()
+{
+ /* do nothing if already uninitialized */
+ if (mUninitDone || mUninitFailed)
+ return;
+
+ mObj->getObjectState().autoUninitSpanDestructor();
+ mUninitDone = true;
+}
diff --git a/src/VBox/Main/src-all/ConsoleSharedFolderImpl.cpp b/src/VBox/Main/src-all/ConsoleSharedFolderImpl.cpp
new file mode 100644
index 00000000..d7c6ffe7
--- /dev/null
+++ b/src/VBox/Main/src-all/ConsoleSharedFolderImpl.cpp
@@ -0,0 +1,333 @@
+/* $Id: ConsoleSharedFolderImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SHAREDFOLDER
+#include "ConsoleSharedFolderImpl.h"
+#include "ConsoleImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/param.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/path.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// ConsoleSharedFolder::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct ConsoleSharedFolder::Data
+{
+ Data()
+ : fWritable(false),
+ fAutoMount(false)
+ { }
+
+ const Utf8Str strName;
+ const Utf8Str strHostPath;
+ bool fWritable;
+ bool fAutoMount;
+ const Utf8Str strAutoMountPoint;
+ Utf8Str strLastAccessError;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+ConsoleSharedFolder::ConsoleSharedFolder()
+ : mParent(NULL),
+ mConsole(NULL)
+{
+ m = new Data;
+}
+
+ConsoleSharedFolder::~ConsoleSharedFolder()
+{
+ delete m;
+ m = NULL;
+}
+
+HRESULT ConsoleSharedFolder::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void ConsoleSharedFolder::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes an instance that lives in the console address space.
+ *
+ * @param aConsole Console parent object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMount if auto mounted by guest true, false otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT ConsoleSharedFolder::init(Console *aConsole,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mConsole) = aConsole;
+
+ HRESULT hrc = i_protectedInit(aConsole, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+/**
+ * Shared initialization code. Called from the other constructors.
+ *
+ * @note
+ * Must be called from under the object's lock!
+ */
+HRESULT ConsoleSharedFolder::i_protectedInit(VirtualBoxBase *aParent,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ LogFlowThisFunc(("aName={%s}, aHostPath={%s}, aWritable={%d}, aAutoMount={%d}\n",
+ aName.c_str(), aHostPath.c_str(), aWritable, aAutoMount));
+
+ ComAssertRet(aParent && aName.isNotEmpty() && aHostPath.isNotEmpty(), E_INVALIDARG);
+
+ Utf8Str hostPath = aHostPath;
+ size_t hostPathLen = hostPath.length();
+
+ /* Remove the trailing slash unless it's a root directory
+ * (otherwise the comparison with the RTPathAbs() result will fail at least
+ * on Linux). Note that this isn't really necessary for the shared folder
+ * itself, since adding a mapping eventually results into a
+ * RTDirOpenFiltered() call (see HostServices/SharedFolders) that seems to
+ * accept both the slashified paths and not. */
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+ if ( hostPathLen > 2
+ && RTPATH_IS_SEP(hostPath.c_str()[hostPathLen - 1])
+ && RTPATH_IS_VOLSEP(hostPath.c_str()[hostPathLen - 2]))
+ ;
+#else
+ if (hostPathLen == 1 && RTPATH_IS_SEP(hostPath[0]))
+ ;
+#endif
+ else
+ hostPath.stripTrailingSlash();
+
+ if (fFailOnError)
+ {
+ /* Check whether the path is full (absolute) */
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathAbs(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), hostPath.c_str(), vrc);
+
+ if (RTPathCompare(hostPath.c_str(), hostPathFull) != 0)
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), hostPath.c_str());
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(hostPathFull, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(vrc))
+ return setError(E_INVALIDARG, tr("RTPathQueryInfo failed on shared folder path '%s': %Rrc"), hostPathFull, vrc);
+
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not a directory"), hostPathFull);
+ }
+
+ unconst(mParent) = aParent;
+
+ unconst(m->strName) = aName;
+ unconst(m->strHostPath) = hostPath;
+ m->fWritable = aWritable;
+ m->fAutoMount = aAutoMount;
+ unconst(m->strAutoMountPoint) = aAutoMountPoint;
+
+ 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 ConsoleSharedFolder::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+ unconst(mConsole) = NULL;
+}
+
+// wrapped ISharedFolder properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT ConsoleSharedFolder::getName(com::Utf8Str &aName)
+{
+ /* mName is constant during life time, no need to lock */
+ aName = m->strName;
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::getHostPath(com::Utf8Str &aHostPath)
+{
+ /* mHostPath is constant during life time, no need to lock */
+ aHostPath = m->strHostPath;
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::getAccessible(BOOL *aAccessible)
+{
+ /* mName and mHostPath are constant during life time, no need to lock */
+
+ /* check whether the host path exists */
+ Utf8Str hostPath = m->strHostPath;
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathExists(hostPath.c_str()) ? RTPathReal(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull))
+ : VERR_PATH_NOT_FOUND;
+ if (RT_SUCCESS(vrc))
+ {
+ *aAccessible = TRUE;
+ return S_OK;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strLastAccessError = Utf8StrFmt(tr("'%s' is not accessible (%Rrc)"),
+ m->strHostPath.c_str(),
+ vrc);
+
+ Log1WarningThisFunc(("m.lastAccessError=\"%s\"\n", m->strLastAccessError.c_str()));
+
+ *aAccessible = FALSE;
+
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::getWritable(BOOL *aWritable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aWritable = m->fWritable;
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::setWritable(BOOL aWritable)
+{
+ RT_NOREF(aWritable);
+ return E_NOTIMPL;
+}
+
+HRESULT ConsoleSharedFolder::getAutoMount(BOOL *aAutoMount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aAutoMount = m->fAutoMount;
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::setAutoMount(BOOL aAutoMount)
+{
+ RT_NOREF(aAutoMount);
+ return E_NOTIMPL;
+}
+
+HRESULT ConsoleSharedFolder::getAutoMountPoint(com::Utf8Str &aAutoMountPoint)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aAutoMountPoint = m->strAutoMountPoint;
+ return S_OK;
+}
+
+HRESULT ConsoleSharedFolder::setAutoMountPoint(com::Utf8Str const &aAutoMountPoint)
+{
+ RT_NOREF(aAutoMountPoint);
+ return E_NOTIMPL;
+}
+
+HRESULT ConsoleSharedFolder::getLastAccessError(com::Utf8Str &aLastAccessError)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLastAccessError = m->strLastAccessError;
+ return S_OK;
+}
+
+
+const Utf8Str& ConsoleSharedFolder::i_getName() const
+{
+ return m->strName;
+}
+
+const Utf8Str& ConsoleSharedFolder::i_getHostPath() const
+{
+ return m->strHostPath;
+}
+
+bool ConsoleSharedFolder::i_isWritable() const
+{
+ return m->fWritable;
+}
+
+bool ConsoleSharedFolder::i_isAutoMounted() const
+{
+ return m->fAutoMount;
+}
+
+const Utf8Str &ConsoleSharedFolder::i_getAutoMountPoint() const
+{
+ return m->strAutoMountPoint;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/CryptoUtils.cpp b/src/VBox/Main/src-all/CryptoUtils.cpp
new file mode 100644
index 00000000..15de22fa
--- /dev/null
+++ b/src/VBox/Main/src-all/CryptoUtils.cpp
@@ -0,0 +1,344 @@
+/* $Id: CryptoUtils.cpp $ */
+/** @file
+ * Main - Cryptographic utility functions used by both VBoxSVC and VBoxC.
+ */
+
+/*
+ * 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
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+
+#include "CryptoUtils.h"
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoWrite(void *pvUser, uint64_t offStream, const void *pvBuf, size_t cbToWrite)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileWriteAt(pThis->m_hVfsFile, (RTFOFF)offStream, pvBuf, cbToWrite, NULL /*pcbWritten*/);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileReadAt(pThis->m_hVfsFile, (RTFOFF)offStream, pvBuf, cbToRead, pcbRead);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoSeek(void *pvUser, int64_t offSeek, unsigned uMethod, uint64_t *poffActual)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileSeek(pThis->m_hVfsFile, (RTFOFF)offSeek, uMethod, poffActual);
+}
+
+
+/*static*/
+DECLCALLBACK(uint64_t) SsmStream::i_ssmCryptoTell(void *pvUser)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return (uint64_t)RTVfsFileTell(pThis->m_hVfsFile);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoSize(void *pvUser, uint64_t *pcb)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ return RTVfsFileQuerySize(pThis->m_hVfsFile, pcb);
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoIsOk(void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/*static*/
+DECLCALLBACK(int) SsmStream::i_ssmCryptoClose(void *pvUser, bool fCancelled)
+{
+ SsmStream *pThis = static_cast<SsmStream *>(pvUser);
+
+ RT_NOREF(fCancelled); /** @todo */
+ RTVfsFileRelease(pThis->m_hVfsFile);
+ pThis->m_hVfsFile = NIL_RTVFSFILE;
+ return VINF_SUCCESS;
+}
+
+
+#ifdef VBOX_COM_INPROC
+SsmStream::SsmStream(Console *pParent, PCVMMR3VTABLE pVMM, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore)
+#else
+SsmStream::SsmStream(VirtualBox *pParent, SecretKeyStore *pKeyStore, const Utf8Str &strKeyId, const Utf8Str &strKeyStore)
+#endif
+{
+ m_StrmOps.u32Version = SSMSTRMOPS_VERSION;
+ m_StrmOps.pfnWrite = SsmStream::i_ssmCryptoWrite;
+ m_StrmOps.pfnRead = SsmStream::i_ssmCryptoRead;
+ m_StrmOps.pfnSeek = SsmStream::i_ssmCryptoSeek;
+ m_StrmOps.pfnTell = SsmStream::i_ssmCryptoTell;
+ m_StrmOps.pfnSize = SsmStream::i_ssmCryptoSize;
+ m_StrmOps.pfnIsOk = SsmStream::i_ssmCryptoIsOk;
+ m_StrmOps.pfnClose = SsmStream::i_ssmCryptoClose;
+ m_StrmOps.u32EndVersion = SSMSTRMOPS_VERSION;
+
+ m_pKeyStore = pKeyStore;
+ m_strKeyId = strKeyId;
+ m_strKeyStore = strKeyStore;
+ m_pParent = pParent;
+ m_hVfsFile = NIL_RTVFSFILE;
+ m_pSsm = NULL;
+ m_pCryptoIf = NULL;
+#ifdef VBOX_COM_INPROC
+ m_pVMM = pVMM;
+#endif
+}
+
+
+SsmStream::~SsmStream()
+{
+ close();
+
+ if (m_pCryptoIf)
+ m_pParent->i_releaseCryptoIf(m_pCryptoIf);
+
+ m_pCryptoIf = NULL;
+ m_pKeyStore = NULL;
+}
+
+
+int SsmStream::open(const Utf8Str &strFilename, bool fWrite, PSSMHANDLE *ppSsmHandle)
+{
+ /* Fast path, if the saved state is not encrypted we can skip everything and let SSM handle the file. */
+ if (m_strKeyId.isEmpty())
+ {
+ AssertReturn(!fWrite, VERR_NOT_SUPPORTED);
+
+#ifdef VBOX_COM_INPROC
+ int vrc = m_pVMM->pfnSSMR3Open(strFilename.c_str(), NULL /*pStreamOps*/, NULL /*pvStreamOps*/,
+ 0 /*fFlags*/, &m_pSsm);
+#else
+ int vrc = SSMR3Open(strFilename.c_str(), NULL /*pStreamOps*/, NULL /*pvStreamOps*/,
+ 0 /*fFlags*/, &m_pSsm);
+#endif
+ if ( RT_SUCCESS(vrc)
+ && ppSsmHandle)
+ *ppSsmHandle = m_pSsm;
+
+ return vrc;
+ }
+
+ int vrc = VINF_SUCCESS;
+ if (!m_pCryptoIf)
+ {
+#ifdef VBOX_COM_INPROC
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+ if (RT_FAILURE(vrc))
+ return vrc;
+#else
+ HRESULT hrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+ if (FAILED(hrc))
+ return VERR_COM_IPRT_ERROR;
+#endif
+ }
+
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = fWrite
+ ? RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE
+ : RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE;
+
+ vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+#ifdef VBOX_COM_INPROC
+ vrc = m_pVMM->pfnSSMR3Open(NULL /*pszFilename*/, &m_StrmOps, this, 0 /*fFlags*/, &m_pSsm);
+#else
+ vrc = SSMR3Open(NULL /*pszFilename*/, &m_StrmOps, this, 0 /*fFlags*/, &m_pSsm);
+#endif
+ if ( RT_SUCCESS(vrc)
+ && ppSsmHandle)
+ *ppSsmHandle = m_pSsm;
+
+ if (RT_FAILURE(vrc))
+ {
+ RTVfsFileRelease(m_hVfsFile);
+ m_hVfsFile = NIL_RTVFSFILE;
+ }
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ }
+
+ pKey->release();
+ }
+
+ return vrc;
+}
+
+
+int SsmStream::open(const Utf8Str &strFilename)
+{
+#ifdef VBOX_COM_INPROC
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE;
+
+ int vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ if (m_strKeyId.isNotEmpty())
+ {
+ /* File is encrypted, set up machinery. */
+ if (!m_pCryptoIf)
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ pKey->release();
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ }
+ }
+ else /* File is not encrypted. */
+ m_hVfsFile = hVfsFileSsm;
+ }
+
+ return vrc;
+#else
+ RT_NOREF(strFilename);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+int SsmStream::create(const Utf8Str &strFilename)
+{
+#ifdef VBOX_COM_INPROC
+ RTVFSFILE hVfsFileSsm = NIL_RTVFSFILE;
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE;
+
+ int vrc = RTVfsFileOpenNormal(strFilename.c_str(), fOpen, &hVfsFileSsm);
+ if (RT_SUCCESS(vrc))
+ {
+ if (m_strKeyId.isNotEmpty())
+ {
+ /* File is encrypted, set up machinery. */
+ if (!m_pCryptoIf)
+ vrc = m_pParent->i_retainCryptoIf(&m_pCryptoIf);
+
+ if (RT_SUCCESS(vrc))
+ {
+ SecretKey *pKey;
+ vrc = m_pKeyStore->retainSecretKey(m_strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszPassword = (const char *)pKey->getKeyBuffer();
+
+ vrc = m_pCryptoIf->pfnCryptoFileFromVfsFile(hVfsFileSsm, m_strKeyStore.c_str(), pszPassword, &m_hVfsFile);
+ pKey->release();
+ }
+
+ /* Also release in success case because the encrypted file handle retained a new reference to it. */
+ RTVfsFileRelease(hVfsFileSsm);
+ if (RT_FAILURE(vrc))
+ RTFileDelete(strFilename.c_str());
+ }
+ }
+ else /* File doesn't need to be encrypted. */
+ m_hVfsFile = hVfsFileSsm;
+ }
+
+ return vrc;
+#else
+ RT_NOREF(strFilename);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+int SsmStream::querySsmStrmOps(PCSSMSTRMOPS *ppStrmOps, void **ppvStrmOpsUser)
+{
+ AssertReturn(m_hVfsFile != NIL_RTVFSFILE, VERR_INVALID_STATE);
+
+ *ppStrmOps = &m_StrmOps;
+ *ppvStrmOpsUser = this;
+ return VINF_SUCCESS;
+}
+
+
+int SsmStream::close(void)
+{
+ if (m_pSsm)
+ {
+#ifdef VBOX_COM_INPROC
+ int vrc = m_pVMM->pfnSSMR3Close(m_pSsm);
+#else
+ int vrc = SSMR3Close(m_pSsm);
+#endif
+ AssertRCReturn(vrc, vrc);
+ }
+
+ if (m_hVfsFile != NIL_RTVFSFILE)
+ RTVfsFileRelease(m_hVfsFile);
+
+ m_hVfsFile = NIL_RTVFSFILE;
+ m_pSsm = NULL;
+#ifdef VBOX_COM_INPROC
+ m_pVMM = NULL;
+#endif
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/DisplayPNGUtil.cpp b/src/VBox/Main/src-all/DisplayPNGUtil.cpp
new file mode 100644
index 00000000..3322f3c9
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayPNGUtil.cpp
@@ -0,0 +1,233 @@
+/* $Id: DisplayPNGUtil.cpp $ */
+/** @file
+ * PNG utilities
+ */
+
+/*
+ * 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_DISPLAY
+#include "DisplayImpl.h"
+
+#include <iprt/alloc.h>
+
+#include <png.h>
+
+#define kMaxSizePNG 1024
+
+typedef struct PNGWriteCtx
+{
+ uint8_t *pu8PNG;
+ uint32_t cbPNG;
+ uint32_t cbAllocated;
+ int vrc;
+} PNGWriteCtx;
+
+static void PNGAPI png_write_data_fn(png_structp png_ptr, png_bytep p, png_size_t cb) RT_NOTHROW_DEF
+{
+ PNGWriteCtx *pCtx = (PNGWriteCtx *)png_get_io_ptr(png_ptr);
+ LogFlowFunc(("png_ptr %p, p %p, cb %d, pCtx %p\n", png_ptr, p, cb, pCtx));
+
+ if (pCtx && RT_SUCCESS(pCtx->vrc))
+ {
+ if (pCtx->cbAllocated - pCtx->cbPNG < cb)
+ {
+ uint32_t cbNew = pCtx->cbPNG + (uint32_t)cb;
+ AssertReturnVoidStmt(cbNew > pCtx->cbPNG && cbNew <= _1G, pCtx->vrc = VERR_TOO_MUCH_DATA);
+ cbNew = RT_ALIGN_32(cbNew, 4096) + 4096;
+
+ void *pNew = RTMemRealloc(pCtx->pu8PNG, cbNew);
+ if (!pNew)
+ {
+ pCtx->vrc = VERR_NO_MEMORY;
+ return;
+ }
+
+ pCtx->pu8PNG = (uint8_t *)pNew;
+ pCtx->cbAllocated = cbNew;
+ }
+
+ memcpy(pCtx->pu8PNG + pCtx->cbPNG, p, cb);
+ pCtx->cbPNG += (uint32_t)cb;
+ }
+}
+
+static void PNGAPI png_output_flush_fn(png_structp png_ptr) RT_NOTHROW_DEF
+{
+ NOREF(png_ptr);
+ /* Do nothing. */
+}
+
+int DisplayMakePNG(uint8_t *pu8Data, uint32_t cx, uint32_t cy,
+ uint8_t **ppu8PNG, uint32_t *pcbPNG, uint32_t *pcxPNG, uint32_t *pcyPNG,
+ uint8_t fLimitSize)
+{
+ int vrc = VINF_SUCCESS;
+
+ uint8_t * volatile pu8Bitmap = NULL; /* gcc setjmp warning */
+ uint32_t volatile cbBitmap = 0; /* gcc setjmp warning */
+ uint32_t volatile cxBitmap = 0; /* gcc setjmp warning */
+ uint32_t volatile cyBitmap = 0; /* gcc setjmp warning */
+
+ if (!fLimitSize || (cx < kMaxSizePNG && cy < kMaxSizePNG))
+ {
+ /* Save unscaled screenshot. */
+ pu8Bitmap = pu8Data;
+ cbBitmap = cx * 4 * cy;
+ cxBitmap = cx;
+ cyBitmap = cy;
+ }
+ else
+ {
+ /* Large screenshot, scale. */
+ if (cx > cy)
+ {
+ cxBitmap = kMaxSizePNG;
+ cyBitmap = (kMaxSizePNG * cy) / cx;
+ }
+ else
+ {
+ cyBitmap = kMaxSizePNG;
+ cxBitmap = (kMaxSizePNG * cx) / cy;
+ }
+
+ cbBitmap = cxBitmap * 4 * cyBitmap;
+
+ pu8Bitmap = (uint8_t *)RTMemAlloc(cbBitmap);
+
+ if (pu8Bitmap)
+ {
+ BitmapScale32(pu8Bitmap /*dst*/,
+ (int)cxBitmap, (int)cyBitmap,
+ pu8Data /*src*/,
+ (int)cx * 4,
+ (int)cx, (int)cy);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ LogFlowFunc(("%dx%d -> %dx%d\n", cx, cy, cxBitmap, cyBitmap));
+
+ if (RT_SUCCESS(vrc))
+ {
+ png_bytep *row_pointers = (png_bytep *)RTMemAlloc(cyBitmap * sizeof(png_bytep));
+ if (row_pointers)
+ {
+ png_infop info_ptr = NULL;
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ (png_voidp)NULL, /* error/warning context pointer */
+ (png_error_ptr)NULL, /* error function */
+ (png_error_ptr)NULL /* warning function */);
+ if (png_ptr)
+ {
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr)
+ {
+#if RT_MSC_PREREQ(RT_MSC_VER_VC140)
+#pragma warning(push,3)
+ if (!setjmp(png_jmpbuf(png_ptr)))
+#pragma warning(pop)
+#else
+ if (!setjmp(png_jmpbuf(png_ptr)))
+#endif
+ {
+ PNGWriteCtx ctx;
+ ctx.pu8PNG = NULL;
+ ctx.cbPNG = 0;
+ ctx.cbAllocated = 0;
+ ctx.vrc = VINF_SUCCESS;
+
+ png_set_write_fn(png_ptr,
+ (png_voidp)&ctx,
+ png_write_data_fn,
+ png_output_flush_fn);
+
+ png_set_IHDR(png_ptr, info_ptr,
+ cxBitmap, cyBitmap,
+ 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ png_bytep row_pointer = (png_bytep)pu8Bitmap;
+ unsigned i = 0;
+ for (; i < cyBitmap; i++, row_pointer += cxBitmap * 4)
+ {
+ row_pointers[i] = row_pointer;
+ }
+ png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
+
+ png_write_info(png_ptr, info_ptr);
+ png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+ png_set_bgr(png_ptr);
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_IDAT))
+ png_write_image(png_ptr, png_get_rows(png_ptr, info_ptr));
+
+ png_write_end(png_ptr, info_ptr);
+
+ vrc = ctx.vrc;
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppu8PNG = ctx.pu8PNG;
+ *pcbPNG = ctx.cbPNG;
+ *pcxPNG = cxBitmap;
+ *pcyPNG = cyBitmap;
+ LogFlowFunc(("PNG %d bytes, bitmap %d bytes\n", ctx.cbPNG, cbBitmap));
+ }
+ }
+ else
+ {
+ vrc = VERR_GENERAL_FAILURE; /* Something within libpng. */
+ }
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr
+ : (png_infopp)NULL);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ RTMemFree(row_pointers);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (pu8Bitmap && pu8Bitmap != pu8Data)
+ {
+ RTMemFree(pu8Bitmap);
+ }
+
+ return vrc;
+
+}
diff --git a/src/VBox/Main/src-all/DisplayResampleImage.cpp b/src/VBox/Main/src-all/DisplayResampleImage.cpp
new file mode 100644
index 00000000..86b566db
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayResampleImage.cpp
@@ -0,0 +1,159 @@
+/* $Id: DisplayResampleImage.cpp $ */
+/** @file
+ * Image resampling code, used for snapshot thumbnails.
+ */
+
+/*
+ * Copyright (C) 2009-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/types.h>
+
+DECLINLINE(void) imageSetPixel (uint8_t *im, int x, int y, int color, int w)
+{
+ *(int32_t *)(im + y * w * 4 + x * 4) = color;
+}
+
+#define trueColorGetAlpha(c) (((c) & 0x7F000000) >> 24)
+#define trueColorGetRed(c) (((c) & 0xFF0000) >> 16)
+#define trueColorGetGreen(c) (((c) & 0x00FF00) >> 8)
+#define trueColorGetBlue(c) ((c) & 0x0000FF)
+
+/* Fast integer implementation for 32 bpp bitmap scaling.
+ * Using fixed point values * 16.
+ */
+typedef int32_t FIXEDPOINT;
+#define INT_TO_FIXEDPOINT(i) (FIXEDPOINT)((i) << 4)
+#define FIXEDPOINT_TO_INT(v) (int)((v) >> 4)
+#define FIXEDPOINT_FLOOR(v) ((v) & ~0xF)
+#define FIXEDPOINT_FRACTION(v) ((v) & 0xF)
+
+/* For 32 bit source only. */
+void BitmapScale32 (uint8_t *dst,
+ int dstW, int dstH,
+ const uint8_t *src,
+ int iDeltaLine,
+ int srcW, int srcH)
+{
+ int x, y;
+
+ for (y = 0; y < dstH; y++)
+ {
+ FIXEDPOINT sy1 = INT_TO_FIXEDPOINT(y * srcH) / dstH;
+ FIXEDPOINT sy2 = INT_TO_FIXEDPOINT((y + 1) * srcH) / dstH;
+
+ for (x = 0; x < dstW; x++)
+ {
+ FIXEDPOINT red = 0, green = 0, blue = 0;
+
+ FIXEDPOINT sx1 = INT_TO_FIXEDPOINT(x * srcW) / dstW;
+ FIXEDPOINT sx2 = INT_TO_FIXEDPOINT((x + 1) * srcW) / dstW;
+
+ FIXEDPOINT spixels = (sx2 - sx1) * (sy2 - sy1);
+
+ FIXEDPOINT sy = sy1;
+
+ do
+ {
+ FIXEDPOINT yportion;
+ if (FIXEDPOINT_FLOOR (sy) == FIXEDPOINT_FLOOR (sy1))
+ {
+ yportion = INT_TO_FIXEDPOINT(1) - FIXEDPOINT_FRACTION(sy);
+ if (yportion > sy2 - sy1)
+ {
+ yportion = sy2 - sy1;
+ }
+ sy = FIXEDPOINT_FLOOR (sy);
+ }
+ else if (sy == FIXEDPOINT_FLOOR (sy2))
+ {
+ yportion = FIXEDPOINT_FRACTION(sy2);
+ }
+ else
+ {
+ yportion = INT_TO_FIXEDPOINT(1);
+ }
+
+ const uint8_t *pu8SrcLine = src + iDeltaLine * FIXEDPOINT_TO_INT(sy);
+ FIXEDPOINT sx = sx1;
+ do
+ {
+ FIXEDPOINT xportion;
+ FIXEDPOINT pcontribution;
+ if (FIXEDPOINT_FLOOR (sx) == FIXEDPOINT_FLOOR (sx1))
+ {
+ xportion = INT_TO_FIXEDPOINT(1) - FIXEDPOINT_FRACTION(sx);
+ if (xportion > sx2 - sx1)
+ {
+ xportion = sx2 - sx1;
+ }
+ pcontribution = xportion * yportion;
+ sx = FIXEDPOINT_FLOOR (sx);
+ }
+ else if (sx == FIXEDPOINT_FLOOR (sx2))
+ {
+ xportion = FIXEDPOINT_FRACTION(sx2);
+ pcontribution = xportion * yportion;
+ }
+ else
+ {
+ xportion = INT_TO_FIXEDPOINT(1);
+ pcontribution = xportion * yportion;
+ }
+ /* Color depth specific code begin */
+ int32_t p = *(int32_t *)(pu8SrcLine + FIXEDPOINT_TO_INT(sx) * 4);
+ /* Color depth specific code end */
+ red += trueColorGetRed (p) * pcontribution;
+ green += trueColorGetGreen (p) * pcontribution;
+ blue += trueColorGetBlue (p) * pcontribution;
+
+ sx += INT_TO_FIXEDPOINT(1);
+ } while (sx < sx2);
+
+ sy += INT_TO_FIXEDPOINT(1);
+ } while (sy < sy2);
+
+ if (spixels != 0)
+ {
+ red /= spixels;
+ green /= spixels;
+ blue /= spixels;
+ }
+ /* Clamping to allow for rounding errors above */
+ if (red > 255)
+ {
+ red = 255;
+ }
+ if (green > 255)
+ {
+ green = 255;
+ }
+ if (blue > 255)
+ {
+ blue = 255;
+ }
+ imageSetPixel (dst,
+ x, y,
+ ( ((int) red) << 16) + (((int) green) << 8) + ((int) blue),
+ dstW);
+ }
+ }
+}
diff --git a/src/VBox/Main/src-all/DisplayUtils.cpp b/src/VBox/Main/src-all/DisplayUtils.cpp
new file mode 100644
index 00000000..410529ab
--- /dev/null
+++ b/src/VBox/Main/src-all/DisplayUtils.cpp
@@ -0,0 +1,225 @@
+/* $Id: DisplayUtils.cpp $ */
+/** @file
+ * Implementation of IDisplay helpers, currently only used in VBoxSVC.
+ */
+
+/*
+ * 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
+ */
+
+#include <DisplayUtils.h>
+
+#include <iprt/log.h>
+#include <VBox/err.h>
+#include <VBox/vmm/ssm.h>
+#include <VBoxVideo.h>
+
+int readSavedDisplayScreenshot(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32Type, uint8_t **ppu8Data, uint32_t *pcbData,
+ uint32_t *pu32Width, uint32_t *pu32Height)
+{
+ LogFlowFunc(("u32Type = %d [%s]\n", u32Type, strStateFilePath.c_str()));
+
+ /** @todo cache read data */
+ if (strStateFilePath.isEmpty())
+ {
+ /* No saved state data. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ uint8_t *pu8Data = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Width = 0;
+ uint32_t u32Height = 0;
+
+ PSSMHANDLE pSSM;
+ int vrc = ssmStream.open(strStateFilePath.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion;
+ vrc = SSMR3Seek(pSSM, "DisplayScreenshot", 1100 /*iInstance*/, &uVersion);
+ if (RT_SUCCESS(vrc))
+ {
+ if (uVersion == sSSMDisplayScreenshotVer)
+ {
+ uint32_t cBlocks;
+ vrc = SSMR3GetU32(pSSM, &cBlocks);
+ AssertRCReturn(vrc, vrc);
+
+ for (uint32_t i = 0; i < cBlocks; i++)
+ {
+ uint32_t cbBlock;
+ vrc = SSMR3GetU32(pSSM, &cbBlock);
+ AssertRCBreak(vrc);
+
+ uint32_t typeOfBlock;
+ vrc = SSMR3GetU32(pSSM, &typeOfBlock);
+ AssertRCBreak(vrc);
+
+ LogFlowFunc(("[%d] type %d, size %d bytes\n", i, typeOfBlock, cbBlock));
+
+ if (typeOfBlock == u32Type)
+ {
+ if (cbBlock > 2 * sizeof(uint32_t))
+ {
+ cbData = (uint32_t)(cbBlock - 2 * sizeof(uint32_t));
+ pu8Data = (uint8_t *)RTMemAlloc(cbData);
+ if (pu8Data == NULL)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+
+ vrc = SSMR3GetU32(pSSM, &u32Width);
+ AssertRCBreak(vrc);
+ vrc = SSMR3GetU32(pSSM, &u32Height);
+ AssertRCBreak(vrc);
+ vrc = SSMR3GetMem(pSSM, pu8Data, cbData);
+ AssertRCBreak(vrc);
+ }
+ else
+ {
+ /* No saved state data. */
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ break;
+ }
+ else
+ {
+ /* displaySSMSaveScreenshot did not write any data, if
+ * cbBlock was == 2 * sizeof (uint32_t).
+ */
+ if (cbBlock > 2 * sizeof (uint32_t))
+ {
+ vrc = SSMR3Skip(pSSM, cbBlock);
+ AssertRCBreak(vrc);
+ }
+ }
+ }
+ }
+ else
+ {
+ vrc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+ }
+
+ ssmStream.close();
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (u32Type == 0 && cbData % 4 != 0)
+ {
+ /* Bitmap is 32bpp, so data is invalid. */
+ vrc = VERR_SSM_UNEXPECTED_DATA;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ *ppu8Data = pu8Data;
+ *pcbData = cbData;
+ *pu32Width = u32Width;
+ *pu32Height = u32Height;
+ LogFlowFunc(("cbData %d, u32Width %d, u32Height %d\n", cbData, u32Width, u32Height));
+ }
+
+ LogFlowFunc(("vrc %Rrc\n", vrc));
+ return vrc;
+}
+
+void freeSavedDisplayScreenshot(uint8_t *pu8Data)
+{
+ /** @todo not necessary when caching is implemented. */
+ RTMemFree(pu8Data);
+}
+
+int readSavedGuestScreenInfo(SsmStream &ssmStream, const Utf8Str &strStateFilePath,
+ uint32_t u32ScreenId, uint32_t *pu32OriginX, uint32_t *pu32OriginY,
+ uint32_t *pu32Width, uint32_t *pu32Height, uint16_t *pu16Flags)
+{
+ LogFlowFunc(("u32ScreenId = %d [%s]\n", u32ScreenId, strStateFilePath.c_str()));
+
+ /** @todo cache read data */
+ if (strStateFilePath.isEmpty())
+ {
+ /* No saved state data. */
+ return VERR_NOT_SUPPORTED;
+ }
+
+ PSSMHANDLE pSSM;
+ int vrc = ssmStream.open(strStateFilePath.c_str(), false /*fWrite*/, &pSSM);
+ if (RT_SUCCESS(vrc))
+ {
+ uint32_t uVersion;
+ vrc = SSMR3Seek(pSSM, "DisplayData", 0 /*iInstance*/, &uVersion);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Starting from sSSMDisplayVer2 we have pu32Width and pu32Height.
+ * Starting from sSSMDisplayVer3 we have all the rest of parameters we need. */
+ if (uVersion >= sSSMDisplayVer2)
+ {
+ uint32_t cMonitors;
+ SSMR3GetU32(pSSM, &cMonitors);
+ if (u32ScreenId > cMonitors)
+ {
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ if (uVersion == sSSMDisplayVer2)
+ {
+ /* Skip all previous monitors, each 5 uint32_t, and the first 3 uint32_t entries. */
+ SSMR3Skip(pSSM, u32ScreenId * 5 * sizeof(uint32_t) + 3 * sizeof(uint32_t));
+ SSMR3GetU32(pSSM, pu32Width);
+ SSMR3GetU32(pSSM, pu32Height);
+ *pu32OriginX = 0;
+ *pu32OriginY = 0;
+ *pu16Flags = VBVA_SCREEN_F_ACTIVE;
+ }
+ else
+ {
+ /* Skip all previous monitors, each 8 uint32_t, and the first 3 uint32_t entries. */
+ SSMR3Skip(pSSM, u32ScreenId * 8 * sizeof(uint32_t) + 3 * sizeof(uint32_t));
+ SSMR3GetU32(pSSM, pu32Width);
+ SSMR3GetU32(pSSM, pu32Height);
+ SSMR3GetU32(pSSM, pu32OriginX);
+ SSMR3GetU32(pSSM, pu32OriginY);
+ uint32_t u32Flags = 0;
+ SSMR3GetU32(pSSM, &u32Flags);
+ *pu16Flags = (uint16_t)u32Flags;
+ }
+ }
+ }
+ else
+ {
+ vrc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ ssmStream.close();
+ }
+
+ LogFlowFunc(("vrc %Rrc\n", vrc));
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-all/EventImpl.cpp b/src/VBox/Main/src-all/EventImpl.cpp
new file mode 100644
index 00000000..c8711f00
--- /dev/null
+++ b/src/VBox/Main/src-all/EventImpl.cpp
@@ -0,0 +1,1690 @@
+/* $Id: EventImpl.cpp $ */
+/** @file
+ * VirtualBox COM Event 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
+ */
+
+/** @page pg_main_events Events
+ *
+ * Theory of operations.
+ *
+ * This code implements easily extensible event mechanism, letting us
+ * to make any VirtualBox object an event source (by aggregating an EventSource instance).
+ * Another entity could subscribe to the event source for events it is interested in.
+ * If an event is waitable, it's possible to wait until all listeners
+ * registered at the moment of firing event as ones interested in this
+ * event acknowledged that they finished event processing (thus allowing
+ * vetoable events).
+ *
+ * Listeners can be registered as active or passive ones, defining policy of delivery.
+ * For *active* listeners, their HandleEvent() method is invoked when event is fired by
+ * the event source (pretty much callbacks).
+ * For *passive* listeners, it's up to an event consumer to perform GetEvent() operation
+ * with given listener, and then perform desired operation with returned event, if any.
+ * For passive listeners case, listener instance serves as merely a key referring to
+ * particular event consumer, thus HandleEvent() implementation isn't that important.
+ * IEventSource's CreateListener() could be used to create such a listener.
+ * Passive mode is designed for transports not allowing callbacks, such as webservices
+ * running on top of HTTP, and for situations where consumer wants exact control on
+ * context where event handler is executed (such as GUI thread for some toolkits).
+ *
+ * Internal EventSource data structures are optimized for fast event delivery, while
+ * listener registration/unregistration operations are expected being pretty rare.
+ * Passive mode listeners keep an internal event queue for all events they receive,
+ * and all waitable events are added to the pending events map. This map keeps track
+ * of how many listeners are still not acknowledged their event, and once this counter
+ * reach zero, element is removed from pending events map, and event is marked as processed.
+ * Thus if passive listener's user forgets to call IEventSource's EventProcessed()
+ * waiters may never know that event processing finished.
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_EVENT
+#include <list>
+#include <map>
+#include <deque>
+
+#include "EventImpl.h"
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "VBoxEvents.h"
+
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/errcore.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+
+#include <VBox/com/array.h>
+
+class ListenerRecord;
+
+struct VBoxEvent::Data
+{
+ Data()
+ : mType(VBoxEventType_Invalid),
+ mWaitEvent(NIL_RTSEMEVENT),
+ mWaitable(FALSE),
+ mProcessed(FALSE)
+ {}
+
+ VBoxEventType_T mType;
+ RTSEMEVENT mWaitEvent;
+ BOOL mWaitable;
+ BOOL mProcessed;
+ ComPtr<IEventSource> mSource;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(VBoxEvent)
+
+HRESULT VBoxEvent::FinalConstruct()
+{
+ m = new Data;
+ return BaseFinalConstruct();
+}
+
+void VBoxEvent::FinalRelease()
+{
+ if (m)
+ {
+ uninit();
+ delete m;
+ m = NULL;
+ }
+ BaseFinalRelease();
+}
+
+HRESULT VBoxEvent::init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable)
+{
+ AssertReturn(aSource != NULL, E_INVALIDARG);
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m->mSource = aSource;
+ m->mType = aType;
+ m->mWaitable = aWaitable;
+ m->mProcessed = !aWaitable;
+
+ do
+ {
+ if (aWaitable)
+ {
+ int vrc = ::RTSemEventCreate(&m->mWaitEvent);
+
+ if (RT_FAILURE(vrc))
+ {
+ AssertFailed();
+ return setError(E_FAIL,
+ tr("Internal error (%Rrc)"), vrc);
+ }
+ }
+ } while (0);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void VBoxEvent::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (!m)
+ return;
+
+ m->mProcessed = TRUE;
+ m->mType = VBoxEventType_Invalid;
+ m->mSource.setNull();
+
+ if (m->mWaitEvent != NIL_RTSEMEVENT)
+ {
+ Assert(m->mWaitable);
+ ::RTSemEventDestroy(m->mWaitEvent);
+ m->mWaitEvent = NIL_RTSEMEVENT;
+ }
+}
+
+HRESULT VBoxEvent::getType(VBoxEventType_T *aType)
+{
+ // never changes while event alive, no locking
+ *aType = m->mType;
+ return S_OK;
+}
+
+HRESULT VBoxEvent::getSource(ComPtr<IEventSource> &aSource)
+{
+ m->mSource.queryInterfaceTo(aSource.asOutParam());
+ return S_OK;
+}
+
+HRESULT VBoxEvent::getWaitable(BOOL *aWaitable)
+{
+ // never changes while event alive, no locking
+ *aWaitable = m->mWaitable;
+ return S_OK;
+}
+
+HRESULT VBoxEvent::setProcessed()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->mProcessed)
+ return S_OK;
+
+ m->mProcessed = TRUE;
+
+ // notify waiters
+ ::RTSemEventSignal(m->mWaitEvent);
+
+ return S_OK;
+}
+
+HRESULT VBoxEvent::waitProcessed(LONG aTimeout, BOOL *aResult)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->mProcessed)
+ {
+ *aResult = TRUE;
+ return S_OK;
+ }
+
+ if (aTimeout == 0)
+ {
+ *aResult = m->mProcessed;
+ return S_OK;
+ }
+
+ // must drop lock while waiting, because setProcessed() needs synchronization.
+ alock.release();
+ /** @todo maybe while loop for spurious wakeups? */
+ int vrc = ::RTSemEventWait(m->mWaitEvent, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout);
+ AssertMsg(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED,
+ ("RTSemEventWait returned %Rrc\n", vrc));
+ alock.acquire();
+
+ if (RT_SUCCESS(vrc))
+ {
+ AssertMsg(m->mProcessed,
+ ("mProcessed must be set here\n"));
+ *aResult = m->mProcessed;
+ }
+ else
+ {
+ *aResult = FALSE;
+ /*
+ * If we timed out then one or more passive listeners didn't process this event
+ * within the time limit most likely due to the listener no longer being alive (e.g.
+ * the VirtualBox GUI crashed) so we flag this to our caller so it can remove this
+ * event from the list of events the passive listener is interested in. This avoids
+ * incurring this timeout every time the event is fired.
+ */
+ if (vrc == VERR_TIMEOUT)
+ return E_ABORT;
+ }
+
+ return S_OK;
+}
+
+typedef std::list<Utf8Str> VetoList;
+typedef std::list<Utf8Str> ApprovalList;
+struct VBoxVetoEvent::Data
+{
+ Data() :
+ mVetoed(FALSE)
+ {}
+ ComObjPtr<VBoxEvent> mEvent;
+ BOOL mVetoed;
+ VetoList mVetoList;
+ ApprovalList mApprovalList;
+};
+
+HRESULT VBoxVetoEvent::FinalConstruct()
+{
+ m = new Data;
+ HRESULT hrc = m->mEvent.createObject();
+ BaseFinalConstruct();
+ return hrc;
+}
+
+void VBoxVetoEvent::FinalRelease()
+{
+ if (m)
+ {
+ uninit();
+ delete m;
+ m = NULL;
+ }
+ BaseFinalRelease();
+}
+
+DEFINE_EMPTY_CTOR_DTOR(VBoxVetoEvent)
+
+HRESULT VBoxVetoEvent::init(IEventSource *aSource, VBoxEventType_T aType)
+{
+ // all veto events are waitable
+ HRESULT hrc = m->mEvent->init(aSource, aType, TRUE);
+ if (FAILED(hrc))
+ return hrc;
+
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m->mVetoed = FALSE;
+ m->mVetoList.clear();
+ m->mApprovalList.clear();
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+void VBoxVetoEvent::uninit()
+{
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ if (!m)
+ return;
+
+ m->mVetoed = FALSE;
+ if (!m->mEvent.isNull())
+ {
+ m->mEvent->uninit();
+ m->mEvent.setNull();
+ }
+}
+
+HRESULT VBoxVetoEvent::getType(VBoxEventType_T *aType)
+{
+ return m->mEvent->COMGETTER(Type)(aType);
+}
+
+HRESULT VBoxVetoEvent::getSource(ComPtr<IEventSource> &aSource)
+{
+ return m->mEvent->COMGETTER(Source)(aSource.asOutParam());
+}
+
+HRESULT VBoxVetoEvent::getWaitable(BOOL *aWaitable)
+{
+ return m->mEvent->COMGETTER(Waitable)(aWaitable);
+}
+
+HRESULT VBoxVetoEvent::setProcessed()
+{
+ return m->mEvent->SetProcessed();
+}
+
+HRESULT VBoxVetoEvent::waitProcessed(LONG aTimeout, BOOL *aResult)
+{
+ return m->mEvent->WaitProcessed(aTimeout, aResult);
+}
+
+HRESULT VBoxVetoEvent::addVeto(const com::Utf8Str &aReason)
+{
+ // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (aReason.length())
+ m->mVetoList.push_back(aReason);
+
+ m->mVetoed = TRUE;
+
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::isVetoed(BOOL *aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aResult = m->mVetoed;
+
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::getVetos(std::vector<com::Utf8Str> &aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aResult.resize(m->mVetoList.size());
+ size_t i = 0;
+ for (VetoList::const_iterator it = m->mVetoList.begin(); it != m->mVetoList.end(); ++it, ++i)
+ aResult[i] = (*it);
+
+ return S_OK;
+
+}
+
+HRESULT VBoxVetoEvent::addApproval(const com::Utf8Str &aReason)
+{
+ // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->mApprovalList.push_back(aReason);
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::isApproved(BOOL *aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aResult = !m->mApprovalList.empty();
+ return S_OK;
+}
+
+HRESULT VBoxVetoEvent::getApprovals(std::vector<com::Utf8Str> &aResult)
+{
+ // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aResult.resize(m->mApprovalList.size());
+ size_t i = 0;
+ for (ApprovalList::const_iterator it = m->mApprovalList.begin(); it != m->mApprovalList.end(); ++it, ++i)
+ aResult[i] = (*it);
+ return S_OK;
+}
+
+static const int FirstEvent = (int)VBoxEventType_LastWildcard + 1;
+static const int LastEvent = (int)VBoxEventType_End;
+static const int NumEvents = LastEvent - FirstEvent;
+
+/**
+ * Class replacing std::list and able to provide required stability
+ * during iteration. It's acheived by delaying structural modifications
+ * to the list till the moment particular element is no longer used by
+ * current iterators.
+ */
+class EventMapRecord
+{
+public:
+ /**
+ * We have to be double linked, as structural modifications in list are delayed
+ * till element removed, so we have to know our previous one to update its next
+ */
+ EventMapRecord *mNext;
+ bool mAlive;
+private:
+ EventMapRecord *mPrev;
+ ListenerRecord *mRef; /* must be weak reference */
+ int32_t mRefCnt;
+
+public:
+ EventMapRecord(ListenerRecord *aRef) :
+ mNext(0), mAlive(true), mPrev(0), mRef(aRef), mRefCnt(1)
+ {}
+
+ EventMapRecord(EventMapRecord &aOther)
+ {
+ mNext = aOther.mNext;
+ mPrev = aOther.mPrev;
+ mRef = aOther.mRef;
+ mRefCnt = aOther.mRefCnt;
+ mAlive = aOther.mAlive;
+ }
+
+ ~EventMapRecord()
+ {
+ if (mNext)
+ mNext->mPrev = mPrev;
+ if (mPrev)
+ mPrev->mNext = mNext;
+ }
+
+ void addRef()
+ {
+ ASMAtomicIncS32(&mRefCnt);
+ }
+
+ void release()
+ {
+ if (ASMAtomicDecS32(&mRefCnt) <= 0)
+ delete this;
+ }
+
+ // Called when an element is no longer needed
+ void kill()
+ {
+ mAlive = false;
+ release();
+ }
+
+ ListenerRecord *ref()
+ {
+ return mAlive ? mRef : 0;
+ }
+
+ friend class EventMapList;
+};
+
+
+class EventMapList
+{
+ EventMapRecord *mHead;
+ uint32_t mSize;
+public:
+ EventMapList()
+ :
+ mHead(0),
+ mSize(0)
+ {}
+ ~EventMapList()
+ {
+ EventMapRecord *pCur = mHead;
+ while (pCur)
+ {
+ EventMapRecord *pNext = pCur->mNext;
+ pCur->release();
+ pCur = pNext;
+ }
+ }
+
+ /*
+ * Elements have to be added to the front of the list, to make sure
+ * that iterators doesn't see newly added listeners, and iteration
+ * will always complete.
+ */
+ void add(ListenerRecord *aRec)
+ {
+ EventMapRecord *pNew = new EventMapRecord(aRec);
+ pNew->mNext = mHead;
+ if (mHead)
+ mHead->mPrev = pNew;
+ mHead = pNew;
+ mSize++;
+ }
+
+ /*
+ * Mark element as removed, actual removal could be delayed until
+ * all consumers release it too. This helps to keep list stable
+ * enough for iterators to allow long and probably intrusive callbacks.
+ */
+ void remove(ListenerRecord *aRec)
+ {
+ EventMapRecord *pCur = mHead;
+ while (pCur)
+ {
+ EventMapRecord *aNext = pCur->mNext;
+ if (pCur->ref() == aRec)
+ {
+ if (pCur == mHead)
+ mHead = aNext;
+ pCur->kill();
+ mSize--;
+ // break?
+ }
+ pCur = aNext;
+ }
+ }
+
+ uint32_t size() const
+ {
+ return mSize;
+ }
+
+ struct iterator
+ {
+ EventMapRecord *mCur;
+
+ iterator() :
+ mCur(0)
+ {}
+
+ explicit
+ iterator(EventMapRecord *aCur) :
+ mCur(aCur)
+ {
+ // Prevent element removal, till we're at it
+ if (mCur)
+ mCur->addRef();
+ }
+
+ ~iterator()
+ {
+ if (mCur)
+ mCur->release();
+ }
+
+ ListenerRecord *
+ operator*() const
+ {
+ return mCur->ref();
+ }
+
+ EventMapList::iterator &
+ operator++()
+ {
+ EventMapRecord *pPrev = mCur;
+ do {
+ mCur = mCur->mNext;
+ } while (mCur && !mCur->mAlive);
+
+ // now we can safely release previous element
+ pPrev->release();
+
+ // And grab the new current
+ if (mCur)
+ mCur->addRef();
+
+ return *this;
+ }
+
+ bool
+ operator==(const EventMapList::iterator &aOther) const
+ {
+ return mCur == aOther.mCur;
+ }
+
+ bool
+ operator!=(const EventMapList::iterator &aOther) const
+ {
+ return mCur != aOther.mCur;
+ }
+ };
+
+ iterator begin()
+ {
+ return iterator(mHead);
+ }
+
+ iterator end()
+ {
+ return iterator(0);
+ }
+};
+
+typedef EventMapList EventMap[NumEvents];
+typedef std::map<IEvent *, int32_t> PendingEventsMap;
+typedef std::deque<ComPtr<IEvent> > PassiveQueue;
+
+class ListenerRecord
+{
+private:
+ ComPtr<IEventListener> mListener;
+ BOOL const mActive;
+ EventSource *mOwner;
+
+ RTSEMEVENT mQEvent;
+ int32_t volatile mQEventBusyCnt;
+ RTCRITSECT mcsQLock;
+ PassiveQueue mQueue;
+ int32_t volatile mRefCnt;
+ uint64_t mLastRead;
+
+public:
+ ListenerRecord(IEventListener *aListener,
+ com::SafeArray<VBoxEventType_T> &aInterested,
+ BOOL aActive,
+ EventSource *aOwner);
+ ~ListenerRecord();
+
+ HRESULT process(IEvent *aEvent, BOOL aWaitable, PendingEventsMap::iterator &pit, AutoLockBase &alock);
+ HRESULT enqueue(IEvent *aEvent);
+ HRESULT dequeue(IEvent **aEvent, LONG aTimeout, AutoLockBase &aAlock);
+ HRESULT eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit);
+ void shutdown();
+
+ void addRef()
+ {
+ ASMAtomicIncS32(&mRefCnt);
+ }
+
+ void release()
+ {
+ if (ASMAtomicDecS32(&mRefCnt) <= 0)
+ delete this;
+ }
+
+ BOOL isActive()
+ {
+ return mActive;
+ }
+
+ friend class EventSource;
+};
+
+/* Handy class with semantics close to ComPtr, but for list records */
+template<typename Held>
+class RecordHolder
+{
+public:
+ RecordHolder(Held *lr) :
+ held(lr)
+ {
+ addref();
+ }
+ RecordHolder(const RecordHolder &that) :
+ held(that.held)
+ {
+ addref();
+ }
+ RecordHolder()
+ :
+ held(0)
+ {
+ }
+ ~RecordHolder()
+ {
+ release();
+ }
+
+ Held *obj()
+ {
+ return held;
+ }
+
+ RecordHolder &operator=(const RecordHolder &that)
+ {
+ safe_assign(that.held);
+ return *this;
+ }
+private:
+ Held *held;
+
+ void addref()
+ {
+ if (held)
+ held->addRef();
+ }
+ void release()
+ {
+ if (held)
+ held->release();
+ }
+ void safe_assign(Held *that_p)
+ {
+ if (that_p)
+ that_p->addRef();
+ release();
+ held = that_p;
+ }
+};
+
+typedef std::map<IEventListener *, RecordHolder<ListenerRecord> > Listeners;
+
+struct EventSource::Data
+{
+ Data() : fShutdown(false)
+ {}
+
+ Listeners mListeners;
+ EventMap mEvMap;
+ PendingEventsMap mPendingMap;
+ bool fShutdown;
+};
+
+/**
+ * This function defines what wildcard expands to.
+ */
+static BOOL implies(VBoxEventType_T who, VBoxEventType_T what)
+{
+ switch (who)
+ {
+ case VBoxEventType_Any:
+ return TRUE;
+ case VBoxEventType_Vetoable:
+ return (what == VBoxEventType_OnExtraDataCanChange)
+ || (what == VBoxEventType_OnCanShowWindow);
+ case VBoxEventType_MachineEvent:
+ return (what == VBoxEventType_OnMachineStateChanged)
+ || (what == VBoxEventType_OnMachineDataChanged)
+ || (what == VBoxEventType_OnMachineRegistered)
+ || (what == VBoxEventType_OnSessionStateChanged)
+ || (what == VBoxEventType_OnGuestPropertyChanged);
+ case VBoxEventType_SnapshotEvent:
+ return (what == VBoxEventType_OnSnapshotTaken)
+ || (what == VBoxEventType_OnSnapshotDeleted)
+ || (what == VBoxEventType_OnSnapshotChanged) ;
+ case VBoxEventType_InputEvent:
+ return (what == VBoxEventType_OnKeyboardLedsChanged)
+ || (what == VBoxEventType_OnMousePointerShapeChanged)
+ || (what == VBoxEventType_OnMouseCapabilityChanged);
+ case VBoxEventType_Invalid:
+ return FALSE;
+ default:
+ break;
+ }
+
+ return who == what;
+}
+
+ListenerRecord::ListenerRecord(IEventListener *aListener,
+ com::SafeArray<VBoxEventType_T> &aInterested,
+ BOOL aActive,
+ EventSource *aOwner) :
+ mListener(aListener), mActive(aActive), mOwner(aOwner), mQEventBusyCnt(0), mRefCnt(0)
+{
+ EventMap *aEvMap = &aOwner->m->mEvMap;
+
+ for (size_t i = 0; i < aInterested.size(); ++i)
+ {
+ VBoxEventType_T interested = aInterested[i];
+ for (int j = FirstEvent; j < LastEvent; j++)
+ {
+ VBoxEventType_T candidate = (VBoxEventType_T)j;
+ if (implies(interested, candidate))
+ {
+ (*aEvMap)[j - FirstEvent].add(this);
+ }
+ }
+ }
+
+ if (!mActive)
+ {
+ ::RTCritSectInit(&mcsQLock);
+ ::RTSemEventCreate(&mQEvent);
+ mLastRead = RTTimeMilliTS();
+ }
+ else
+ {
+ mQEvent = NIL_RTSEMEVENT;
+ RT_ZERO(mcsQLock);
+ mLastRead = 0;
+ }
+}
+
+ListenerRecord::~ListenerRecord()
+{
+ /* Remove references to us from the event map */
+ EventMap *aEvMap = &mOwner->m->mEvMap;
+ for (int j = FirstEvent; j < LastEvent; j++)
+ {
+ (*aEvMap)[j - FirstEvent].remove(this);
+ }
+
+ if (!mActive)
+ {
+ // at this moment nobody could add elements to our queue, so we can safely
+ // clean it up, otherwise there will be pending events map elements
+ PendingEventsMap *aPem = &mOwner->m->mPendingMap;
+ while (true)
+ {
+ ComPtr<IEvent> aEvent;
+
+ if (mQueue.empty())
+ break;
+
+ mQueue.front().queryInterfaceTo(aEvent.asOutParam());
+ mQueue.pop_front();
+
+ BOOL fWaitable = FALSE;
+ aEvent->COMGETTER(Waitable)(&fWaitable);
+ if (fWaitable)
+ {
+ PendingEventsMap::iterator pit = aPem->find(aEvent);
+ if (pit != aPem->end())
+ eventProcessed(aEvent, pit);
+ }
+ }
+
+ ::RTCritSectDelete(&mcsQLock);
+ }
+ shutdown();
+}
+
+HRESULT ListenerRecord::process(IEvent *aEvent,
+ BOOL aWaitable,
+ PendingEventsMap::iterator &pit,
+ AutoLockBase &aAlock)
+{
+ if (mActive)
+ {
+ /*
+ * We release lock here to allow modifying ops on EventSource inside callback.
+ */
+ HRESULT hrc = S_OK;
+ if (mListener)
+ {
+ aAlock.release();
+ hrc = mListener->HandleEvent(aEvent);
+#ifdef RT_OS_WINDOWS
+ Assert(hrc != RPC_E_WRONG_THREAD);
+#endif
+ aAlock.acquire();
+ }
+ if (aWaitable)
+ eventProcessed(aEvent, pit);
+ return hrc;
+ }
+ return enqueue(aEvent);
+}
+
+
+HRESULT ListenerRecord::enqueue(IEvent *aEvent)
+{
+ AssertMsg(!mActive, ("must be passive\n"));
+
+ // put an event the queue
+ ::RTCritSectEnter(&mcsQLock);
+
+ // If there was no events reading from the listener for the long time,
+ // and events keep coming, or queue is oversized we shall unregister this listener.
+ uint64_t sinceRead = RTTimeMilliTS() - mLastRead;
+ size_t queueSize = mQueue.size();
+ if (queueSize > 1000 || (queueSize > 500 && sinceRead > 60 * 1000))
+ {
+ ::RTCritSectLeave(&mcsQLock);
+ LogRel(("Event: forcefully unregistering passive event listener %p due to excessive queue size\n", this));
+ return E_ABORT;
+ }
+
+
+ RTSEMEVENT hEvt = mQEvent;
+ if (queueSize != 0 && mQueue.back() == aEvent)
+ /* if same event is being pushed multiple times - it's reusable event and
+ we don't really need multiple instances of it in the queue */
+ hEvt = NIL_RTSEMEVENT;
+ else if (hEvt != NIL_RTSEMEVENT) /* don't bother queuing after shutdown */
+ {
+ mQueue.push_back(aEvent);
+ ASMAtomicIncS32(&mQEventBusyCnt);
+ }
+
+ ::RTCritSectLeave(&mcsQLock);
+
+ // notify waiters unless we've been shut down.
+ if (hEvt != NIL_RTSEMEVENT)
+ {
+ ::RTSemEventSignal(hEvt);
+ ASMAtomicDecS32(&mQEventBusyCnt);
+ }
+
+ return S_OK;
+}
+
+HRESULT ListenerRecord::dequeue(IEvent **aEvent,
+ LONG aTimeout,
+ AutoLockBase &aAlock)
+{
+ if (mActive)
+ return VBOX_E_INVALID_OBJECT_STATE;
+
+ // retain listener record
+ RecordHolder<ListenerRecord> holder(this);
+
+ ::RTCritSectEnter(&mcsQLock);
+
+ mLastRead = RTTimeMilliTS();
+
+ /*
+ * If waiting both desired and necessary, then try grab the event
+ * semaphore and mark it busy. If it's NIL we've been shut down already.
+ */
+ if (aTimeout != 0 && mQueue.empty())
+ {
+ RTSEMEVENT hEvt = mQEvent;
+ if (hEvt != NIL_RTSEMEVENT)
+ {
+ ASMAtomicIncS32(&mQEventBusyCnt);
+ ::RTCritSectLeave(&mcsQLock);
+
+ // release lock while waiting, listener will not go away due to above holder
+ aAlock.release();
+
+ ::RTSemEventWait(hEvt, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout);
+ ASMAtomicDecS32(&mQEventBusyCnt);
+
+ // reacquire lock
+ aAlock.acquire();
+ ::RTCritSectEnter(&mcsQLock);
+ }
+ }
+
+ if (mQueue.empty())
+ *aEvent = NULL;
+ else
+ {
+ mQueue.front().queryInterfaceTo(aEvent);
+ mQueue.pop_front();
+ }
+
+ ::RTCritSectLeave(&mcsQLock);
+ return S_OK;
+}
+
+HRESULT ListenerRecord::eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit)
+{
+ if (--pit->second == 0)
+ {
+ Assert(pit->first == aEvent);
+ aEvent->SetProcessed();
+ mOwner->m->mPendingMap.erase(pit);
+ }
+
+ return S_OK;
+}
+
+void ListenerRecord::shutdown()
+{
+ if (mQEvent != NIL_RTSEMEVENT)
+ {
+ /* Grab the event semaphore. Must do this while owning the CS or we'll
+ be racing user wanting to use the handle. */
+ ::RTCritSectEnter(&mcsQLock);
+ RTSEMEVENT hEvt = mQEvent;
+ mQEvent = NIL_RTSEMEVENT;
+ ::RTCritSectLeave(&mcsQLock);
+
+ /*
+ * Signal waiters and wait for them and any other signallers to stop using the sempahore.
+ *
+ * Note! RTSemEventDestroy does not necessarily guarantee that waiting threads are
+ * out of RTSemEventWait or even woken up when it returns. Darwin is (or was?)
+ * an example of this, the result was undesirable freezes on shutdown.
+ */
+ int32_t cBusy = ASMAtomicReadS32(&mQEventBusyCnt);
+ if (cBusy > 0)
+ {
+ Log(("Wait for %d waiters+signalers to release.\n", cBusy));
+ while (cBusy-- > 0)
+ ::RTSemEventSignal(hEvt);
+
+ for (uint32_t cLoops = 0;; cLoops++)
+ {
+ RTThreadSleep(RT_MIN(8, cLoops));
+ if (ASMAtomicReadS32(&mQEventBusyCnt) <= 0)
+ break;
+ ::RTSemEventSignal(hEvt); /* (Technically unnecessary, but just in case.) */
+ }
+ Log(("All waiters+signalers just released the lock.\n"));
+ }
+
+ ::RTSemEventDestroy(hEvt);
+ }
+}
+
+EventSource::EventSource()
+{}
+
+EventSource::~EventSource()
+{}
+
+HRESULT EventSource::FinalConstruct()
+{
+ m = new Data;
+ return BaseFinalConstruct();
+}
+
+void EventSource::FinalRelease()
+{
+ uninit();
+ delete m;
+ BaseFinalRelease();
+}
+
+HRESULT EventSource::init()
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+void EventSource::uninit()
+{
+ {
+ // First of all (before even thinking about entering the uninit span):
+ // make sure that all listeners are are shut down (no pending events or
+ // wait calls), because they cannot be alive without the associated
+ // event source. Otherwise API clients which use long-term (or
+ // indefinite) waits will block VBoxSVC termination (just one example)
+ // for a long time or even infinitely long.
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if (!m->fShutdown)
+ {
+ m->fShutdown = true;
+ for (Listeners::iterator it = m->mListeners.begin();
+ it != m->mListeners.end();
+ ++it)
+ {
+ it->second.obj()->shutdown();
+ }
+ }
+ }
+
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ m->mListeners.clear();
+ // m->mEvMap shall be cleared at this point too by destructors, assert?
+}
+
+HRESULT EventSource::registerListener(const ComPtr<IEventListener> &aListener,
+ const std::vector<VBoxEventType_T> &aInteresting,
+ BOOL aActive)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::const_iterator it = m->mListeners.find(aListener);
+ if (it != m->mListeners.end())
+ return setError(E_INVALIDARG,
+ tr("This listener already registered"));
+
+ com::SafeArray<VBoxEventType_T> interested(aInteresting);
+ RecordHolder<ListenerRecord> lrh(new ListenerRecord(aListener, interested, aActive, this));
+ m->mListeners.insert(Listeners::value_type((IEventListener *)aListener, lrh));
+
+ ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, TRUE /*add*/);
+
+ return S_OK;
+}
+
+HRESULT EventSource::unregisterListener(const ComPtr<IEventListener> &aListener)
+{
+ HRESULT hrc = S_OK;;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+
+ if (it != m->mListeners.end())
+ {
+ it->second.obj()->shutdown();
+ m->mListeners.erase(it);
+ // destructor removes refs from the event map
+ ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, FALSE /*add*/);
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ return hrc;
+}
+
+HRESULT EventSource::fireEvent(const ComPtr<IEvent> &aEvent,
+ LONG aTimeout,
+ BOOL *aResult)
+{
+ /* Get event attributes before take the source lock: */
+ BOOL fWaitable = FALSE;
+ HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable);
+ AssertComRC(hrc);
+
+ VBoxEventType_T evType;
+ hrc = aEvent->COMGETTER(Type)(&evType);
+ AssertComRCReturn(hrc, hrc);
+
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent];
+
+ /* Anyone interested in this event? */
+ uint32_t cListeners = listeners.size();
+ if (cListeners == 0)
+ {
+ aEvent->SetProcessed();
+ // just leave the lock and update event object state
+ }
+ else
+ {
+ PendingEventsMap::iterator pit;
+ if (fWaitable)
+ {
+ m->mPendingMap.insert(PendingEventsMap::value_type(aEvent, cListeners));
+ // we keep iterator here to allow processing active listeners without
+ // pending events lookup
+ pit = m->mPendingMap.find(aEvent);
+ }
+
+ for (EventMapList::iterator it = listeners.begin();
+ it != listeners.end();
+ ++it)
+ {
+ // keep listener record reference, in case someone will remove it while in callback
+ RecordHolder<ListenerRecord> record(*it);
+
+ /*
+ * We pass lock here to allow modifying ops on EventSource inside callback
+ * in active mode. Note that we expect list iterator stability as 'alock'
+ * could be temporary released when calling event handler.
+ */
+ HRESULT cbRc = record.obj()->process(aEvent, fWaitable, pit, alock);
+
+ /* Note that E_ABORT is used above to signal that a passive
+ * listener was unregistered due to not picking up its event.
+ * This overlaps with XPCOM specific use of E_ABORT to signal
+ * death of an active listener, but that's irrelevant here. */
+ if (FAILED_DEAD_INTERFACE(cbRc) || cbRc == E_ABORT)
+ {
+ Listeners::iterator lit = m->mListeners.find(record.obj()->mListener);
+ if (lit != m->mListeners.end())
+ {
+ lit->second.obj()->shutdown();
+ m->mListeners.erase(lit);
+ }
+ }
+ // anything else to do with cbRc?
+ }
+ }
+ }
+ /* We leave the lock here */
+
+ if (fWaitable)
+ {
+ hrc = aEvent->WaitProcessed(aTimeout, aResult);
+
+ /*
+ * If a passive listener times out without processing a vetoable event then we
+ * remove that event from the list of events this listener is interested in.
+ */
+ if (!*aResult && hrc == E_ABORT && implies(VBoxEventType_Vetoable, evType))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent];
+ for (EventMapList::iterator it = listeners.begin();
+ it != listeners.end();
+ ++it)
+ {
+ RecordHolder<ListenerRecord> record(*it);
+ if (record.obj()->mQueue.size() != 0 && record.obj()->mQueue.back() == aEvent)
+ m->mEvMap[(int)evType - FirstEvent].remove(record.obj());
+ }
+
+ PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent);
+ if (pit != m->mPendingMap.end())
+ m->mPendingMap.erase(pit);
+
+ /*
+ * VBoxEventDesc::fire() requires TRUE to be returned so it can handle
+ * vetoable events.
+ */
+ return S_OK;
+ }
+ }
+ else
+ *aResult = TRUE;
+
+ return hrc;
+}
+
+HRESULT EventSource::getEvent(const ComPtr<IEventListener> &aListener,
+ LONG aTimeout,
+ ComPtr<IEvent> &aEvent)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+ HRESULT hrc = S_OK;
+
+ if (it != m->mListeners.end())
+ hrc = it->second.obj()->dequeue(aEvent.asOutParam(), aTimeout, alock);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ if (hrc == VBOX_E_INVALID_OBJECT_STATE)
+ return setError(hrc, tr("Listener must be passive"));
+
+ return hrc;
+}
+
+HRESULT EventSource::eventProcessed(const ComPtr<IEventListener> &aListener,
+ const ComPtr<IEvent> &aEvent)
+{
+ BOOL fWaitable = FALSE;
+ HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable);
+ AssertComRC(hrc);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->fShutdown)
+ return setError(VBOX_E_INVALID_OBJECT_STATE,
+ tr("This event source is already shut down"));
+
+ Listeners::iterator it = m->mListeners.find(aListener);
+
+ if (it != m->mListeners.end())
+ {
+ ListenerRecord *aRecord = it->second.obj();
+
+ if (aRecord->isActive())
+ return setError(E_INVALIDARG,
+ tr("Only applicable to passive listeners"));
+
+ if (fWaitable)
+ {
+ PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent);
+
+ if (pit == m->mPendingMap.end())
+ {
+ AssertFailed();
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Unknown event"));
+ }
+ else
+ hrc = aRecord->eventProcessed(aEvent, pit);
+ }
+ else
+ {
+ // for non-waitable events we're done
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND,
+ tr("Listener was never registered"));
+
+ return hrc;
+}
+
+/**
+ * This class serves as feasible listener implementation
+ * which could be used by clients not able to create local
+ * COM objects, but still willing to receive event
+ * notifications in passive mode, such as webservices.
+ */
+class ATL_NO_VTABLE PassiveEventListener :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventListener)
+{
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(PassiveEventListener, IEventListener)
+
+ DECLARE_NOT_AGGREGATABLE(PassiveEventListener)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(PassiveEventListener)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventListener)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventListener)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventListener)
+ END_COM_MAP()
+
+ PassiveEventListener()
+ {}
+ ~PassiveEventListener()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ BaseFinalRelease();
+ }
+
+ // IEventListener methods
+ STDMETHOD(HandleEvent)(IEvent *)
+ {
+ ComAssertMsgRet(false, (tr("HandleEvent() of wrapper shall never be called")),
+ E_FAIL);
+ }
+};
+
+/* Proxy listener class, used to aggregate multiple event sources into one */
+class ATL_NO_VTABLE ProxyEventListener :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventListener)
+{
+ ComPtr<IEventSource> mSource;
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(ProxyEventListener, IEventListener)
+
+ DECLARE_NOT_AGGREGATABLE(ProxyEventListener)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(ProxyEventListener)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventListener)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventListener)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventListener)
+ END_COM_MAP()
+
+ ProxyEventListener()
+ {}
+ ~ProxyEventListener()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ BaseFinalRelease();
+ }
+
+ HRESULT init(IEventSource *aSource)
+ {
+ mSource = aSource;
+ return S_OK;
+ }
+
+ // IEventListener methods
+ STDMETHOD(HandleEvent)(IEvent *aEvent)
+ {
+ BOOL fProcessed = FALSE;
+ if (mSource)
+ return mSource->FireEvent(aEvent, 0, &fProcessed);
+ else
+ return S_OK;
+ }
+};
+
+class ATL_NO_VTABLE EventSourceAggregator :
+ public VirtualBoxBase,
+ VBOX_SCRIPTABLE_IMPL(IEventSource)
+{
+ typedef std::list <ComPtr<IEventSource> > EventSourceList;
+ /* key is weak reference */
+ typedef std::map<IEventListener *, ComPtr<IEventListener> > ProxyListenerMap;
+
+ EventSourceList mEventSources;
+ ProxyListenerMap mListenerProxies;
+ ComObjPtr<EventSource> mSource;
+
+public:
+
+ VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(EventSourceAggregator, IEventSource)
+
+ DECLARE_NOT_AGGREGATABLE(EventSourceAggregator)
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_COM_MAP(EventSourceAggregator)
+ COM_INTERFACE_ENTRY(ISupportErrorInfo)
+ COM_INTERFACE_ENTRY(IEventSource)
+ COM_INTERFACE_ENTRY2(IDispatch, IEventSource)
+ VBOX_TWEAK_INTERFACE_ENTRY(IEventSource)
+ END_COM_MAP()
+
+ EventSourceAggregator()
+ {}
+ ~EventSourceAggregator()
+ {}
+
+ HRESULT FinalConstruct()
+ {
+ return BaseFinalConstruct();
+ }
+ void FinalRelease()
+ {
+ mEventSources.clear();
+ mListenerProxies.clear();
+ mSource->uninit();
+ BaseFinalRelease();
+ }
+
+ // internal public
+ HRESULT init(const std::vector<ComPtr<IEventSource> > aSourcesIn);
+
+ // IEventSource methods
+ STDMETHOD(CreateListener)(IEventListener **aListener);
+ STDMETHOD(CreateAggregator)(ComSafeArrayIn(IEventSource *, aSubordinates),
+ IEventSource **aAggregator);
+ STDMETHOD(RegisterListener)(IEventListener *aListener,
+ ComSafeArrayIn(VBoxEventType_T, aInterested),
+ BOOL aActive);
+ STDMETHOD(UnregisterListener)(IEventListener *aListener);
+ STDMETHOD(FireEvent)(IEvent *aEvent,
+ LONG aTimeout,
+ BOOL *aProcessed);
+ STDMETHOD(GetEvent)(IEventListener *aListener,
+ LONG aTimeout,
+ IEvent **aEvent);
+ STDMETHOD(EventProcessed)(IEventListener *aListener,
+ IEvent *aEvent);
+
+ protected:
+ HRESULT createProxyListener(IEventListener *aListener,
+ IEventListener **aProxy);
+ HRESULT getProxyListener(IEventListener *aListener,
+ IEventListener **aProxy);
+ HRESULT removeProxyListener(IEventListener *aListener);
+};
+
+#ifdef VBOX_WITH_XPCOM
+NS_DECL_CLASSINFO(ProxyEventListener)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(ProxyEventListener, IEventListener)
+NS_DECL_CLASSINFO(PassiveEventListener)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(PassiveEventListener, IEventListener)
+NS_DECL_CLASSINFO(EventSourceAggregator)
+NS_IMPL_THREADSAFE_ISUPPORTS1_CI(EventSourceAggregator, IEventSource)
+#endif
+
+
+HRESULT EventSource::createListener(ComPtr<IEventListener> &aListener)
+{
+ ComObjPtr<PassiveEventListener> listener;
+
+ HRESULT hrc = listener.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create wrapper object (%Rhrc)"), hrc),
+ E_FAIL);
+ listener.queryInterfaceTo(aListener.asOutParam());
+ return S_OK;
+}
+
+HRESULT EventSource::createAggregator(const std::vector<ComPtr<IEventSource> > &aSubordinates,
+ ComPtr<IEventSource> &aResult)
+{
+ ComObjPtr<EventSourceAggregator> agg;
+
+ HRESULT hrc = agg.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create aggregator (%Rhrc)"), hrc),
+ E_FAIL);
+
+ hrc = agg->init(aSubordinates);
+ if (FAILED(hrc))
+ return hrc;
+
+ agg.queryInterfaceTo(aResult.asOutParam());
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::init(const std::vector<ComPtr<IEventSource> > aSourcesIn)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = mSource.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create source (%Rhrc)"), hrc),
+ E_FAIL);
+ hrc = mSource->init();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not init source (%Rhrc)"), hrc),
+ E_FAIL);
+
+ for (size_t i = 0; i < aSourcesIn.size(); i++)
+ {
+ if (aSourcesIn[i] != NULL)
+ mEventSources.push_back(aSourcesIn[i]);
+ }
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+STDMETHODIMP EventSourceAggregator::CreateListener(IEventListener **aListener)
+{
+ return mSource->CreateListener(aListener);
+}
+
+STDMETHODIMP EventSourceAggregator::CreateAggregator(ComSafeArrayIn(IEventSource *, aSubordinates),
+ IEventSource **aResult)
+{
+ return mSource->CreateAggregator(ComSafeArrayInArg(aSubordinates), aResult);
+}
+
+STDMETHODIMP EventSourceAggregator::RegisterListener(IEventListener *aListener,
+ ComSafeArrayIn(VBoxEventType_T, aInterested),
+ BOOL aActive)
+{
+ CheckComArgNotNull(aListener);
+ CheckComArgSafeArrayNotNull(aInterested);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ ComPtr<IEventListener> proxy;
+ HRESULT hrc = createProxyListener(aListener, proxy.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ /* Register active proxy listener on real event source */
+ hrc = es->RegisterListener(proxy, ComSafeArrayInArg(aInterested), TRUE);
+ }
+ /* And add real listener on our event source */
+ hrc = mSource->RegisterListener(aListener, ComSafeArrayInArg(aInterested), aActive);
+
+ return S_OK;
+}
+
+STDMETHODIMP EventSourceAggregator::UnregisterListener(IEventListener *aListener)
+{
+ CheckComArgNotNull(aListener);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<IEventListener> proxy;
+ HRESULT hrc = getProxyListener(aListener, proxy.asOutParam());
+ if (FAILED(hrc))
+ return hrc;
+
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ hrc = es->UnregisterListener(proxy);
+ }
+ hrc = mSource->UnregisterListener(aListener);
+
+ return removeProxyListener(aListener);
+
+}
+
+STDMETHODIMP EventSourceAggregator::FireEvent(IEvent *aEvent,
+ LONG aTimeout,
+ BOOL *aProcessed)
+{
+ CheckComArgNotNull(aEvent);
+ CheckComArgOutPointerValid(aProcessed);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.hrc()))
+ return autoCaller.hrc();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ /* Aggregator event source shall not have direct event firing, but we may
+ wish to support aggregation chains */
+ for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end();
+ ++it)
+ {
+ ComPtr<IEventSource> es = *it;
+ HRESULT hrc = es->FireEvent(aEvent, aTimeout, aProcessed);
+ /* Current behavior is that aggregator's FireEvent() always succeeds,
+ so that multiple event sources don't affect each other. */
+ NOREF(hrc);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP EventSourceAggregator::GetEvent(IEventListener *aListener,
+ LONG aTimeout,
+ IEvent **aEvent)
+{
+ return mSource->GetEvent(aListener, aTimeout, aEvent);
+}
+
+STDMETHODIMP EventSourceAggregator::EventProcessed(IEventListener *aListener,
+ IEvent *aEvent)
+{
+ return mSource->EventProcessed(aListener, aEvent);
+}
+
+HRESULT EventSourceAggregator::createProxyListener(IEventListener *aListener,
+ IEventListener **aProxy)
+{
+ ComObjPtr<ProxyEventListener> proxy;
+
+ HRESULT hrc = proxy.createObject();
+ ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create proxy (%Rhrc)"), hrc),
+ E_FAIL);
+
+ hrc = proxy->init(mSource);
+ if (FAILED(hrc))
+ return hrc;
+
+ ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener);
+ if (it != mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener already registered"));
+
+ mListenerProxies.insert(ProxyListenerMap::value_type(aListener, proxy));
+
+ proxy.queryInterfaceTo(aProxy);
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::getProxyListener(IEventListener *aListener,
+ IEventListener **aProxy)
+{
+ ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener);
+ if (it == mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener never registered"));
+
+ (*it).second.queryInterfaceTo(aProxy);
+ return S_OK;
+}
+
+HRESULT EventSourceAggregator::removeProxyListener(IEventListener *aListener)
+{
+ ProxyListenerMap::iterator it = mListenerProxies.find(aListener);
+ if (it == mListenerProxies.end())
+ return setError(E_INVALIDARG,
+ tr("This listener never registered"));
+
+ mListenerProxies.erase(it);
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ExtPackManagerImpl.cpp b/src/VBox/Main/src-all/ExtPackManagerImpl.cpp
new file mode 100644
index 00000000..9526a252
--- /dev/null
+++ b/src/VBox/Main/src-all/ExtPackManagerImpl.cpp
@@ -0,0 +1,3839 @@
+/* $Id: ExtPackManagerImpl.cpp $ */
+/** @file
+ * VirtualBox Main - interface for Extension Packs, VBoxSVC & VBoxC.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#include "ExtPackManagerImpl.h"
+#include "CloudProviderManagerImpl.h"
+#include "ExtPackUtil.h"
+#include "ThreadTask.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/locale.h>
+#include <iprt/manifest.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+
+#include <VBox/com/array.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/sup.h>
+#include <VBox/version.h>
+
+#include <algorithm>
+
+#include "AutoCaller.h"
+#include "Global.h"
+#include "ProgressImpl.h"
+#ifdef VBOX_COM_INPROC
+# include "ConsoleImpl.h"
+#else
+# include "VirtualBoxImpl.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBOX_EXTPACK_HELPER_NAME
+ * The name of the utility application we employ to install and uninstall the
+ * extension packs. This is a set-uid-to-root binary on unixy platforms, which
+ * is why it has to be a separate application.
+ */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define VBOX_EXTPACK_HELPER_NAME "VBoxExtPackHelperApp.exe"
+#else
+# define VBOX_EXTPACK_HELPER_NAME "VBoxExtPackHelperApp"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct ExtPackBaseData
+{
+public:
+ /** The extension pack descriptor (loaded from the XML, mostly). */
+ VBOXEXTPACKDESC Desc;
+ /** The file system object info of the XML file.
+ * This is for detecting changes and save time in refresh(). */
+ RTFSOBJINFO ObjInfoDesc;
+ /** Whether it's usable or not. */
+ bool fUsable;
+ /** Why it is unusable. */
+ Utf8Str strWhyUnusable;
+};
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Private extension pack data.
+ */
+struct ExtPackFile::Data : public ExtPackBaseData
+{
+public:
+ /** The path to the tarball. */
+ Utf8Str strExtPackFile;
+ /** The SHA-256 hash of the file (as string). */
+ Utf8Str strDigest;
+ /** The file handle of the extension pack file. */
+ RTFILE hExtPackFile;
+ /** Our manifest for the tarball. */
+ RTMANIFEST hOurManifest;
+ /** Pointer to the extension pack manager. */
+ ComObjPtr<ExtPackManager> ptrExtPackMgr;
+ /** Pointer to the VirtualBox object so we can create a progress object. */
+ VirtualBox *pVirtualBox;
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+#endif
+
+/**
+ * Private extension pack data.
+ */
+struct ExtPack::Data : public ExtPackBaseData
+{
+public:
+ /** Where the extension pack is located. */
+ Utf8Str strExtPackPath;
+ /** The file system object info of the extension pack directory.
+ * This is for detecting changes and save time in refresh(). */
+ RTFSOBJINFO ObjInfoExtPack;
+ /** The full path to the main module. */
+ Utf8Str strMainModPath;
+ /** The file system object info of the main module.
+ * This is used to determin whether to bother try reload it. */
+ RTFSOBJINFO ObjInfoMainMod;
+ /** The module handle of the main extension pack module. */
+ RTLDRMOD hMainMod;
+
+ /** The helper callbacks for the extension pack. */
+ VBOXEXTPACKHLP Hlp;
+ /** Pointer back to the extension pack object (for Hlp methods). */
+ ExtPack *pThis;
+#ifndef VBOX_COM_INPROC
+ /** The extension pack main registration structure. */
+ PCVBOXEXTPACKREG pReg;
+#else
+ /** The extension pack main VM registration structure. */
+ PCVBOXEXTPACKVMREG pReg;
+#endif
+ /** The current context. */
+ VBOXEXTPACKCTX enmContext;
+ /** Set if we've made the pfnVirtualBoxReady or pfnConsoleReady call. */
+ bool fMadeReadyCall;
+#ifndef VBOX_COM_INPROC
+ /** Pointer to the VirtualBox object so we can create a progress object. */
+ VirtualBox *pVirtualBox;
+#endif
+#ifdef VBOX_WITH_MAIN_NLS
+ PTRCOMPONENT pTrComponent;
+#endif
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+
+/** List of extension packs. */
+typedef std::list< ComObjPtr<ExtPack> > ExtPackList;
+
+/**
+ * Private extension pack manager data.
+ */
+struct ExtPackManager::Data
+{
+ Data()
+ : cUpdate(0)
+ {}
+
+ /** The directory where the extension packs are installed. */
+ Utf8Str strBaseDir;
+ /** The directory where the certificates this installation recognizes are
+ * stored. */
+ Utf8Str strCertificatDirPath;
+ /** The list of installed extension packs. */
+ ExtPackList llInstalledExtPacks;
+#ifndef VBOX_COM_INPROC
+ /** Pointer to the VirtualBox object, our parent. */
+ VirtualBox *pVirtualBox;
+#endif
+ /** The current context. */
+ VBOXEXTPACKCTX enmContext;
+ /** Update counter for the installed extension packs, increased in every list update. */
+ uint64_t cUpdate;
+
+ RTMEMEF_NEW_AND_DELETE_OPERATORS();
+};
+
+#ifndef VBOX_COM_INPROC
+
+/**
+ * Extension pack installation job.
+ */
+class ExtPackInstallTask : public ThreadTask
+{
+public:
+ explicit ExtPackInstallTask() : ThreadTask("ExtPackInst") { }
+ ~ExtPackInstallTask() { }
+
+ DECLARE_TRANSLATE_METHODS(ExtPackInstallTask)
+
+ void handler()
+ {
+ HRESULT hrc = ptrExtPackMgr->i_doInstall(ptrExtPackFile, fReplace, &strDisplayInfo);
+ ptrProgress->i_notifyComplete(hrc);
+ }
+
+ HRESULT Init(const ComPtr<ExtPackFile> &a_strExtPackFile, bool a_fReplace,
+ const Utf8Str &strDispInfo, const ComPtr<ExtPackManager> &a_ptrExtPackMgr)
+ {
+ ptrExtPackFile = a_strExtPackFile;
+ fReplace = a_fReplace;
+ strDisplayInfo = strDispInfo;
+ ptrExtPackMgr = a_ptrExtPackMgr;
+
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrDescription(tr("Installing extension pack"));
+ hrc = ptrProgress->init(ptrExtPackFile->m->pVirtualBox,
+ static_cast<IExtPackFile *>(ptrExtPackFile),
+ bstrDescription.raw(),
+ FALSE /*aCancelable*/);
+ }
+
+ return hrc;
+ }
+
+ /** Smart pointer to the progress object for this job. */
+ ComObjPtr<Progress> ptrProgress;
+private:
+ /** Smart pointer to the extension pack file. */
+ ComPtr<ExtPackFile> ptrExtPackFile;
+ /** The replace argument. */
+ bool fReplace;
+ /** The display info argument. */
+ Utf8Str strDisplayInfo;
+ /** Smart pointer to the extension manager. */
+ ComPtr<ExtPackManager> ptrExtPackMgr;
+};
+
+/**
+ * Extension pack uninstallation job.
+ */
+class ExtPackUninstallTask : public ThreadTask
+{
+public:
+ explicit ExtPackUninstallTask() : ThreadTask("ExtPackUninst") { }
+ ~ExtPackUninstallTask() { }
+ DECLARE_TRANSLATE_METHODS(ExtPackUninstallTask)
+
+ void handler()
+ {
+ HRESULT hrc = ptrExtPackMgr->i_doUninstall(&strName, fForcedRemoval, &strDisplayInfo);
+ ptrProgress->i_notifyComplete(hrc);
+ }
+
+ HRESULT Init(const ComPtr<ExtPackManager> &a_ptrExtPackMgr, const Utf8Str &a_strName,
+ bool a_fForcedRemoval, const Utf8Str &a_strDisplayInfo)
+ {
+ ptrExtPackMgr = a_ptrExtPackMgr;
+ strName = a_strName;
+ fForcedRemoval = a_fForcedRemoval;
+ strDisplayInfo = a_strDisplayInfo;
+
+ HRESULT hrc = ptrProgress.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ Bstr bstrDescription(tr("Uninstalling extension pack"));
+ hrc = ptrProgress->init(ptrExtPackMgr->m->pVirtualBox,
+ static_cast<IExtPackManager *>(ptrExtPackMgr),
+ bstrDescription.raw(),
+ FALSE /*aCancelable*/);
+ }
+
+ return hrc;
+ }
+
+ /** Smart pointer to the progress object for this job. */
+ ComObjPtr<Progress> ptrProgress;
+private:
+ /** Smart pointer to the extension manager. */
+ ComPtr<ExtPackManager> ptrExtPackMgr;
+ /** The name of the extension pack. */
+ Utf8Str strName;
+ /** The replace argument. */
+ bool fForcedRemoval;
+ /** The display info argument. */
+ Utf8Str strDisplayInfo;
+};
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPackFile)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in initWithDir().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPackFile::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack by reading its file.
+ *
+ * @returns COM status code.
+ * @param a_pszFile The path to the extension pack file.
+ * @param a_pszDigest The SHA-256 digest of the file. Or an empty string.
+ * @param a_pExtPackMgr Pointer to the extension pack manager.
+ * @param a_pVirtualBox Pointer to the VirtualBox object.
+ */
+HRESULT ExtPackFile::initWithFile(const char *a_pszFile, const char *a_pszDigest, ExtPackManager *a_pExtPackMgr,
+ VirtualBox *a_pVirtualBox)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Allocate + initialize our private data.
+ */
+ m = new ExtPackFile::Data;
+ VBoxExtPackInitDesc(&m->Desc);
+ RT_ZERO(m->ObjInfoDesc);
+ m->fUsable = false;
+ m->strWhyUnusable = tr("ExtPack::init failed");
+ m->strExtPackFile = a_pszFile;
+ m->strDigest = a_pszDigest;
+ m->hExtPackFile = NIL_RTFILE;
+ m->hOurManifest = NIL_RTMANIFEST;
+ m->ptrExtPackMgr = a_pExtPackMgr;
+ m->pVirtualBox = a_pVirtualBox;
+
+ RTCString *pstrTarName = VBoxExtPackExtractNameFromTarballPath(a_pszFile);
+ if (pstrTarName)
+ {
+ m->Desc.strName = *pstrTarName;
+ delete pstrTarName;
+ pstrTarName = NULL;
+ }
+
+ autoInitSpan.setSucceeded();
+
+ /*
+ * Try open the extension pack and check that it is a regular file.
+ */
+ int vrc = RTFileOpen(&m->hExtPackFile, a_pszFile,
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ return initFailed(tr("'%s' file not found"), a_pszFile);
+ return initFailed(tr("RTFileOpen('%s',,) failed with %Rrc"), a_pszFile, vrc);
+ }
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTFileQueryInfo(m->hExtPackFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(vrc))
+ return initFailed(tr("RTFileQueryInfo failed with %Rrc on '%s'"), vrc, a_pszFile);
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return initFailed(tr("Not a regular file: %s"), a_pszFile);
+
+ /*
+ * Validate the tarball and extract the XML file.
+ */
+ char szError[8192];
+ RTVFSFILE hXmlFile;
+ vrc = VBoxExtPackValidateTarball(m->hExtPackFile, NULL /*pszExtPackName*/, a_pszFile, a_pszDigest,
+ szError, sizeof(szError), &m->hOurManifest, &hXmlFile, &m->strDigest);
+ if (RT_FAILURE(vrc))
+ return initFailed("%s", szError);
+
+ /*
+ * Parse the XML.
+ */
+ RTCString strSavedName(m->Desc.strName);
+ RTCString *pStrLoadErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &m->Desc, &m->ObjInfoDesc);
+ RTVfsFileRelease(hXmlFile);
+ if (pStrLoadErr != NULL)
+ {
+ m->strWhyUnusable.printf(tr("Failed to the xml file: %s"), pStrLoadErr->c_str());
+ m->Desc.strName = strSavedName;
+ delete pStrLoadErr;
+ return S_OK;
+ }
+
+ /*
+ * Match the tarball name with the name from the XML.
+ */
+ /** @todo drop this restriction after the old install interface is
+ * dropped. */
+ if (!strSavedName.equalsIgnoreCase(m->Desc.strName))
+ return initFailed(tr("Extension pack name mismatch between the downloaded file and the XML inside it (xml='%s' file='%s')"),
+ m->Desc.strName.c_str(), strSavedName.c_str());
+
+
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return S_OK;
+}
+
+/**
+ * Protected helper that formats the strWhyUnusable value.
+ *
+ * @returns S_OK
+ * @param a_pszWhyFmt Why it failed, format string.
+ * @param ... The format arguments.
+ */
+HRESULT ExtPackFile::initFailed(const char *a_pszWhyFmt, ...)
+{
+ va_list va;
+ va_start(va, a_pszWhyFmt);
+ m->strWhyUnusable.printfV(a_pszWhyFmt, va);
+ va_end(va);
+ return S_OK;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPackFile::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPackFile::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ VBoxExtPackFreeDesc(&m->Desc);
+ RTFileClose(m->hExtPackFile);
+ m->hExtPackFile = NIL_RTFILE;
+ RTManifestRelease(m->hOurManifest);
+ m->hOurManifest = NIL_RTMANIFEST;
+
+ delete m;
+ m = NULL;
+ }
+}
+
+HRESULT ExtPackFile::getName(com::Utf8Str &aName)
+{
+ aName = m->Desc.strName;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getDescription(com::Utf8Str &aDescription)
+{
+ aDescription = m->Desc.strDescription;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getVersion(com::Utf8Str &aVersion)
+{
+ aVersion = m->Desc.strVersion;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getEdition(com::Utf8Str &aEdition)
+{
+ aEdition = m->Desc.strEdition;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getRevision(ULONG *aRevision)
+{
+ *aRevision = m->Desc.uRevision;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getVRDEModule(com::Utf8Str &aVRDEModule)
+{
+ aVRDEModule = m->Desc.strVrdeModule;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getCryptoModule(com::Utf8Str &aCryptoModule)
+{
+ aCryptoModule = m->Desc.strCryptoModule;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns)
+{
+ /** @todo implement plug-ins. */
+#ifdef VBOX_WITH_XPCOM
+ NOREF(aPlugIns);
+#endif
+ NOREF(aPlugIns);
+ ReturnComNotImplemented();
+}
+
+HRESULT ExtPackFile::getUsable(BOOL *aUsable)
+{
+ *aUsable = m->fUsable;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getWhyUnusable(com::Utf8Str &aWhyUnusable)
+{
+ aWhyUnusable = m->strWhyUnusable;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getShowLicense(BOOL *aShowLicense)
+{
+ *aShowLicense = m->Desc.fShowLicense;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::getLicense(com::Utf8Str &aLicense)
+{
+ Utf8Str strHtml("html");
+ Utf8Str str("");
+ return queryLicense(str, str, strHtml, aLicense);
+}
+
+/* Same as ExtPack::QueryLicense, should really explore the subject of base classes here... */
+HRESULT ExtPackFile::queryLicense(const com::Utf8Str &aPreferredLocale, const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat, com::Utf8Str &aLicenseText)
+{
+ HRESULT hrc = S_OK;
+
+ /*
+ * Validate input.
+ */
+
+ if (aPreferredLocale.length() != 2 && aPreferredLocale.length() != 0)
+ return setError(E_FAIL, tr("The preferred locale is a two character string or empty."));
+
+ if (aPreferredLanguage.length() != 2 && aPreferredLanguage.length() != 0)
+ return setError(E_FAIL, tr("The preferred language is a two character string or empty."));
+
+ if ( !aFormat.equals("html")
+ && !aFormat.equals("rtf")
+ && !aFormat.equals("txt"))
+ return setError(E_FAIL, tr("The license format can only have the values 'html', 'rtf' and 'txt'."));
+
+ /*
+ * Combine the options to form a file name before locking down anything.
+ */
+ char szName[sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX "-de_DE.html") + 2];
+ if (aPreferredLocale.isNotEmpty() && aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s_%s.%s",
+ aPreferredLocale.c_str(), aPreferredLanguage.c_str(), aFormat.c_str());
+ else if (aPreferredLocale.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else if (aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX ".%s",
+ aFormat.c_str());
+ /*
+ * Lock the extension pack. We need a write lock here as there must not be
+ * concurrent accesses to the tar file handle.
+ */
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Do not permit this query on a pack that isn't considered usable (could
+ * be marked so because of bad license files).
+ */
+ if (!m->fUsable)
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ else
+ {
+ /*
+ * Look it up in the manifest before scanning the tarball for it
+ */
+ if (RTManifestEntryExists(m->hOurManifest, szName))
+ {
+ RTVFSFSSTREAM hTarFss;
+ char szError[8192];
+ int vrc = VBoxExtPackOpenTarFss(m->hExtPackFile, szError, sizeof(szError), &hTarFss, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ /* Get the first/next. */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ vrc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTVfsFsStrmNext failed: %Rrc"), vrc);
+ else
+ hrc = setErrorBoth(E_UNEXPECTED, vrc, tr("'%s' was found in the manifest but not in the tarball"), szName);
+ break;
+ }
+
+ /* Is this it? */
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+ if ( !strcmp(pszAdjName, szName)
+ && ( enmType == RTVFSOBJTYPE_IO_STREAM
+ || enmType == RTVFSOBJTYPE_FILE))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+
+ /* Load the file into memory. */
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ size_t cbFile = (size_t)ObjInfo.cbObject;
+ void *pvFile = RTMemAllocZ(cbFile + 1);
+ if (pvFile)
+ {
+ vrc = RTVfsIoStrmRead(hVfsIos, pvFile, cbFile, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /* try translate it into a string we can return. */
+ Bstr bstrLicense((const char *)pvFile, cbFile);
+ if (bstrLicense.isNotEmpty())
+ {
+ aLicenseText = Utf8Str(bstrLicense);
+ hrc = S_OK;
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
+ szName);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to read '%s': %Rrc"), szName, vrc);
+ RTMemFree(pvFile);
+ }
+ else
+ hrc = setError(E_OUTOFMEMORY, tr("Failed to allocate %zu bytes for '%s'", "", cbFile),
+ cbFile, szName);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTVfsIoStrmQueryInfo on '%s': %Rrc"), szName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ /* Release current. */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+ RTVfsFsStrmRelease(hTarFss);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, "%s", szError);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The license file '%s' was not found in '%s'"),
+ szName, m->strExtPackFile.c_str());
+ }
+ return hrc;
+}
+
+HRESULT ExtPackFile::getFilePath(com::Utf8Str &aFilePath)
+{
+
+ aFilePath = m->strExtPackFile;
+ return S_OK;
+}
+
+HRESULT ExtPackFile::install(BOOL aReplace, const com::Utf8Str &aDisplayInfo, ComPtr<IProgress> &aProgress)
+{
+ HRESULT hrc;
+ if (m->fUsable)
+ {
+ ExtPackInstallTask *pTask = NULL;
+ try
+ {
+ pTask = new ExtPackInstallTask();
+ hrc = pTask->Init(this, aReplace != FALSE, aDisplayInfo, m->ptrExtPackMgr);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->ptrProgress;
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_DEFAULT);
+ pTask = NULL; /* The _completely_ _undocumented_ createThread method always consumes pTask. */
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Starting thread for an extension pack installation failed with %Rrc"), hrc);
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Looks like creating a progress object for ExtraPackInstallTask object failed"));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ LogFlowThisFunc(("Exception was caught in the function ExtPackFile::install() \n"));
+ hrc = hrcXcpt;
+ }
+ if (pTask)
+ delete pTask;
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+
+
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPack)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in initWithDir().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPack::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack by reading its file.
+ *
+ * @returns COM status code.
+ * @param a_pVirtualBox The VirtualBox object.
+ * @param a_enmContext The context we're in.
+ * @param a_pszName The name of the extension pack. This is also the
+ * name of the subdirector under @a a_pszParentDir
+ * where the extension pack is installed.
+ * @param a_pszDir The extension pack directory name.
+ */
+HRESULT ExtPack::initWithDir(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext, const char *a_pszName, const char *a_pszDir)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ static const VBOXEXTPACKHLP s_HlpTmpl =
+ {
+ /* u32Version = */ VBOXEXTPACKHLP_VERSION,
+ /* uVBoxFullVersion = */ VBOX_FULL_VERSION,
+ /* uVBoxVersionRevision = */ 0,
+ /* u32Padding = */ 0,
+ /* pszVBoxVersion = */ "",
+ /* pfnFindModule = */ ExtPack::i_hlpFindModule,
+ /* pfnGetFilePath = */ ExtPack::i_hlpGetFilePath,
+ /* pfnGetContext = */ ExtPack::i_hlpGetContext,
+ /* pfnLoadHGCMService = */ ExtPack::i_hlpLoadHGCMService,
+ /* pfnLoadVDPlugin = */ ExtPack::i_hlpLoadVDPlugin,
+ /* pfnUnloadVDPlugin = */ ExtPack::i_hlpUnloadVDPlugin,
+ /* pfnCreateProgress = */ ExtPack::i_hlpCreateProgress,
+ /* pfnGetCanceledProgress = */ ExtPack::i_hlpGetCanceledProgress,
+ /* pfnUpdateProgress = */ ExtPack::i_hlpUpdateProgress,
+ /* pfnNextOperationProgress = */ ExtPack::i_hlpNextOperationProgress,
+ /* pfnWaitOtherProgress = */ ExtPack::i_hlpWaitOtherProgress,
+ /* pfnCompleteProgress = */ ExtPack::i_hlpCompleteProgress,
+ /* pfnCreateEvent = */ ExtPack::i_hlpCreateEvent,
+ /* pfnCreateVetoEvent = */ ExtPack::i_hlpCreateVetoEvent,
+ /* pfnTranslate = */ ExtPack::i_hlpTranslate,
+ /* pfnReserved1 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved2 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved3 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved4 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved5 = */ ExtPack::i_hlpReservedN,
+ /* pfnReserved6 = */ ExtPack::i_hlpReservedN,
+ /* uReserved7 = */ 0,
+ /* u32EndMarker = */ VBOXEXTPACKHLP_VERSION
+ };
+
+ /*
+ * Allocate + initialize our private data.
+ */
+ m = new Data;
+ VBoxExtPackInitDesc(&m->Desc);
+ m->Desc.strName = a_pszName;
+ RT_ZERO(m->ObjInfoDesc);
+ m->fUsable = false;
+ m->strWhyUnusable = tr("ExtPack::init failed");
+ m->strExtPackPath = a_pszDir;
+ RT_ZERO(m->ObjInfoExtPack);
+ m->strMainModPath.setNull();
+ RT_ZERO(m->ObjInfoMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->Hlp = s_HlpTmpl;
+ m->Hlp.pszVBoxVersion = RTBldCfgVersion();
+ m->Hlp.uVBoxInternalRevision = RTBldCfgRevision();
+ m->pThis = this;
+ m->pReg = NULL;
+ m->enmContext = a_enmContext;
+ m->fMadeReadyCall = false;
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox = a_pVirtualBox;
+#else
+ RT_NOREF(a_pVirtualBox);
+#endif
+#ifdef VBOX_WITH_MAIN_NLS
+ m->pTrComponent = NULL;
+#endif
+ /*
+ * Make sure the SUPR3Hardened API works (ignoring errors for now).
+ */
+ int vrc = SUPR3HardenedVerifyInit();
+ if (RT_FAILURE(vrc))
+ LogRel(("SUPR3HardenedVerifyInit failed: %Rrc\n", vrc));
+
+ /*
+ * Probe the extension pack (this code is shared with refresh()).
+ */
+ i_probeAndLoad();
+
+#ifdef VBOX_WITH_MAIN_NLS
+ /* register language files if exist */
+ if (m->pReg != NULL && m->pReg->pszNlsBaseName != NULL)
+ {
+ char szPath[RTPATH_MAX];
+ vrc = RTPathJoin(szPath, sizeof(szPath), a_pszDir, "nls");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szPath, sizeof(szPath), m->pReg->pszNlsBaseName);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = VirtualBoxTranslator::registerTranslation(szPath, false, &m->pTrComponent);
+ if (RT_FAILURE(vrc))
+ m->pTrComponent = NULL;
+ }
+ }
+ }
+#endif
+
+ autoInitSpan.setSucceeded();
+ return S_OK;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPack::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPack::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ if (m->hMainMod != NIL_RTLDRMOD)
+ {
+ AssertPtr(m->pReg);
+ if (m->pReg->pfnUnload != NULL)
+ m->pReg->pfnUnload(m->pReg);
+
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->pReg = NULL;
+ }
+
+ VBoxExtPackFreeDesc(&m->Desc);
+
+#ifdef VBOX_WITH_MAIN_NLS
+ if (m->pTrComponent != NULL)
+ VirtualBoxTranslator::unregisterTranslation(m->pTrComponent);
+#endif
+ delete m;
+ m = NULL;
+ }
+}
+
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the installed hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pLock The write lock held by the caller.
+ * @param pErrInfo Where to return error information.
+ */
+bool ExtPack::i_callInstalledHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock, PRTERRINFO pErrInfo)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD)
+ {
+ if (m->pReg->pfnInstalled)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ pErrInfo->rc = m->pReg->pfnInstalled(m->pReg, a_pVirtualBox, pErrInfo);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ pErrInfo->rc = VINF_SUCCESS;
+ return false;
+}
+
+/**
+ * Calls the uninstall hook and closes the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_fForcedRemoval When set, we'll ignore complaints from the
+ * uninstall hook.
+ * @remarks The caller holds the manager's write lock, not released.
+ */
+HRESULT ExtPack::i_callUninstallHookAndClose(IVirtualBox *a_pVirtualBox, bool a_fForcedRemoval)
+{
+ HRESULT hrc = S_OK;
+
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD)
+ {
+ if (m->pReg->pfnUninstall && !a_fForcedRemoval)
+ {
+ int vrc = m->pReg->pfnUninstall(m->pReg, a_pVirtualBox);
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("ExtPack pfnUninstall returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ if (!a_fForcedRemoval)
+ hrc = setErrorBoth(E_FAIL, vrc, tr("pfnUninstall returned %Rrc"), vrc);
+ }
+ }
+ if (SUCCEEDED(hrc))
+ {
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+ m->pReg = NULL;
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Calls the pfnVirtualBoxReady hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVirtualBoxReadyHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->fUsable
+ && m->hMainMod != NIL_RTLDRMOD
+ && !m->fMadeReadyCall)
+ {
+ m->fMadeReadyCall = true;
+ if (m->pReg->pfnVirtualBoxReady)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVirtualBoxReady(m->pReg, a_pVirtualBox);
+ i_notifyCloudProviderManager();
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+/**
+ * Calls the pfnConsoleReady hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The Console interface.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callConsoleReadyHook(IConsole *a_pConsole, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->fUsable
+ && m->hMainMod != NIL_RTLDRMOD
+ && !m->fMadeReadyCall)
+ {
+ m->fMadeReadyCall = true;
+ if (m->pReg->pfnConsoleReady)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnConsoleReady(m->pReg, a_pConsole);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* VBOX_COM_INPROC */
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the pfnVMCreate hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pVirtualBox The VirtualBox interface.
+ * @param a_pMachine The machine interface of the new VM.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVmCreatedHook(IVirtualBox *a_pVirtualBox, IMachine *a_pMachine, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMCreated)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVMCreated(m->pReg, a_pVirtualBox, a_pMachine);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+
+/**
+ * Calls the pfnVMConfigureVMM hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ * @param a_pvrc Where to return the status code of the callback. This
+ * is always set. LogRel is called on if a failure status
+ * is returned.
+ */
+bool ExtPack::i_callVmConfigureVmmHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock, int *a_pvrc)
+{
+ *a_pvrc = VINF_SUCCESS;
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMConfigureVMM)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ int vrc = m->pReg->pfnVMConfigureVMM(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ *a_pvrc = vrc;
+ a_pLock->acquire();
+ if (RT_FAILURE(vrc))
+ LogRel(("ExtPack pfnVMConfigureVMM returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Calls the pfnVMPowerOn hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ * @param a_pvrc Where to return the status code of the callback. This
+ * is always set. LogRel is called on if a failure status
+ * is returned.
+ */
+bool ExtPack::i_callVmPowerOnHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock, int *a_pvrc)
+{
+ *a_pvrc = VINF_SUCCESS;
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMPowerOn)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ int vrc = m->pReg->pfnVMPowerOn(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ *a_pvrc = vrc;
+ a_pLock->acquire();
+ if (RT_FAILURE(vrc))
+ LogRel(("ExtPack pfnVMPowerOn returned %Rrc for %s\n", vrc, m->Desc.strName.c_str()));
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Calls the pfnVMPowerOff hook.
+ *
+ * @returns true if we left the lock, false if we didn't.
+ * @param a_pConsole The console interface.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ * @param a_pLock The write lock held by the caller.
+ */
+bool ExtPack::i_callVmPowerOffHook(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM, AutoWriteLock *a_pLock)
+{
+ if ( m != NULL
+ && m->hMainMod != NIL_RTLDRMOD
+ && m->fUsable)
+ {
+ if (m->pReg->pfnVMPowerOff)
+ {
+ ComPtr<ExtPack> ptrSelfRef = this;
+ a_pLock->release();
+ m->pReg->pfnVMPowerOff(m->pReg, a_pConsole, a_pVM, a_pVMM);
+ a_pLock->acquire();
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif /* VBOX_COM_INPROC */
+
+/**
+ * Check if the extension pack is usable and has an VRDE module.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_checkVrde(void)
+{
+ HRESULT hrc;
+ if ( m != NULL
+ && m->fUsable)
+ {
+ if (m->Desc.strVrdeModule.isNotEmpty())
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not include a VRDE module"), m->Desc.strName.c_str());
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+/**
+ * Check if the extension pack is usable and has a cryptographic module.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_checkCrypto(void)
+{
+ HRESULT hrc;
+ if ( m != NULL
+ && m->fUsable)
+ {
+ if (m->Desc.strCryptoModule.isNotEmpty())
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("The extension pack '%s' does not include a cryptographic module"), m->Desc.strName.c_str());
+ }
+ else
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ return hrc;
+}
+
+/**
+ * Same as checkVrde(), except that it also resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pstrVrdeLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getVrdpLibraryName(Utf8Str *a_pstrVrdeLibrary)
+{
+ HRESULT hrc = i_checkVrde();
+ if (SUCCEEDED(hrc))
+ {
+ if (i_findModule(m->Desc.strVrdeModule.c_str(), NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrVrdeLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the VRDE module '%s' in extension pack '%s'"),
+ m->Desc.strVrdeModule.c_str(), m->Desc.strName.c_str());
+ }
+ return hrc;
+}
+
+/**
+ * Same as i_checkCrypto(), except that it also resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pstrCryptoLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getCryptoLibraryName(Utf8Str *a_pstrCryptoLibrary)
+{
+ HRESULT hrc = i_checkCrypto();
+ if (SUCCEEDED(hrc))
+ {
+ if (i_findModule(m->Desc.strCryptoModule.c_str(), NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrCryptoLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the cryptographic module '%s' in extension pack '%s'"),
+ m->Desc.strCryptoModule.c_str(), m->Desc.strName.c_str());
+ }
+ return hrc;
+}
+
+/**
+ * Resolves the path to the module.
+ *
+ * @returns S_OK or COM error status with error information.
+ * @param a_pszModuleName The library.
+ * @param a_pstrLibrary Where to return the path on success.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+HRESULT ExtPack::i_getLibraryName(const char *a_pszModuleName, Utf8Str *a_pstrLibrary)
+{
+ HRESULT hrc;
+ if (i_findModule(a_pszModuleName, NULL, VBOXEXTPACKMODKIND_R3,
+ a_pstrLibrary, NULL /*a_pfNative*/, NULL /*a_pObjInfo*/))
+ hrc = S_OK;
+ else
+ hrc = setError(E_FAIL, tr("Failed to locate the module '%s' in extension pack '%s'"),
+ a_pszModuleName, m->Desc.strName.c_str());
+ return hrc;
+}
+
+/**
+ * Check if this extension pack wishes to be the default VRDE provider.
+ *
+ * @returns @c true if it wants to and it is in a usable state, otherwise
+ * @c false.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+bool ExtPack::i_wantsToBeDefaultVrde(void) const
+{
+ return m->fUsable
+ && m->Desc.strVrdeModule.isNotEmpty();
+}
+
+/**
+ * Check if this extension pack wishes to be the default cryptographic provider.
+ *
+ * @returns @c true if it wants to and it is in a usable state, otherwise
+ * @c false.
+ *
+ * @remarks Caller holds the extension manager lock for reading, no locking
+ * necessary.
+ */
+bool ExtPack::i_wantsToBeDefaultCrypto(void) const
+{
+ return m->fUsable
+ && m->Desc.strCryptoModule.isNotEmpty();
+}
+
+/**
+ * Refreshes the extension pack state.
+ *
+ * This is called by the manager so that the on disk changes are picked up.
+ *
+ * @returns S_OK or COM error status with error information.
+ *
+ * @param a_pfCanDelete Optional can-delete-this-object output indicator.
+ *
+ * @remarks Caller holds the extension manager lock for writing.
+ * @remarks Only called in VBoxSVC.
+ */
+HRESULT ExtPack::i_refresh(bool *a_pfCanDelete)
+{
+ if (a_pfCanDelete)
+ *a_pfCanDelete = false;
+
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS); /* for the COMGETTERs */
+
+ /*
+ * Has the module been deleted?
+ */
+ RTFSOBJINFO ObjInfoExtPack;
+ int vrc = RTPathQueryInfoEx(m->strExtPackPath.c_str(), &ObjInfoExtPack, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if ( RT_FAILURE(vrc)
+ || !RTFS_IS_DIRECTORY(ObjInfoExtPack.Attr.fMode))
+ {
+ if (a_pfCanDelete)
+ *a_pfCanDelete = true;
+ return S_OK;
+ }
+
+ /*
+ * We've got a directory, so try query file system object info for the
+ * files we are interested in as well.
+ */
+ RTFSOBJINFO ObjInfoDesc;
+ char szDescFilePath[RTPATH_MAX];
+ vrc = RTPathJoin(szDescFilePath, sizeof(szDescFilePath), m->strExtPackPath.c_str(), VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathQueryInfoEx(szDescFilePath, &ObjInfoDesc, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ RT_ZERO(ObjInfoDesc);
+
+ RTFSOBJINFO ObjInfoMainMod;
+ if (m->strMainModPath.isNotEmpty())
+ vrc = RTPathQueryInfoEx(m->strMainModPath.c_str(), &ObjInfoMainMod, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (m->strMainModPath.isEmpty() || RT_FAILURE(vrc))
+ RT_ZERO(ObjInfoMainMod);
+
+ /*
+ * If we have a usable module already, just verify that things haven't
+ * changed since we loaded it.
+ */
+ if (m->fUsable)
+ {
+ if (m->hMainMod == NIL_RTLDRMOD)
+ i_probeAndLoad();
+ else if ( !i_objinfoIsEqual(&ObjInfoDesc, &m->ObjInfoDesc)
+ || !i_objinfoIsEqual(&ObjInfoMainMod, &m->ObjInfoMainMod)
+ || !i_objinfoIsEqual(&ObjInfoExtPack, &m->ObjInfoExtPack) )
+ {
+ /** @todo not important, so it can wait. */
+ }
+ }
+ /*
+ * Ok, it is currently not usable. If anything has changed since last time
+ * reprobe the extension pack.
+ */
+ else if ( !i_objinfoIsEqual(&ObjInfoDesc, &m->ObjInfoDesc)
+ || !i_objinfoIsEqual(&ObjInfoMainMod, &m->ObjInfoMainMod)
+ || !i_objinfoIsEqual(&ObjInfoExtPack, &m->ObjInfoExtPack) )
+ i_probeAndLoad();
+
+ return S_OK;
+}
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Checks if there are cloud providers vetoing extension pack uninstall.
+ *
+ * This needs going through the cloud provider singleton in VirtualBox,
+ * the job cannot be done purely by using the code in the extension pack).
+ * It cannot be integrated into i_callUninstallHookAndClose, because it
+ * can only do its job when the extpack lock is not held, whereas the
+ * actual uninstall must be done with the lock held all the time for
+ * consistency reasons.
+ *
+ * This is called when uninstalling or replacing an extension pack.
+ *
+ * @returns true / false
+ */
+bool ExtPack::i_areThereCloudProviderUninstallVetos()
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ ComObjPtr<CloudProviderManager> cpm(m->pVirtualBox->i_getCloudProviderManager());
+ AssertReturn(!cpm.isNull(), false);
+
+ return !cpm->i_canRemoveExtPack(static_cast<IExtPack *>(this));
+}
+
+/**
+ * Notifies the Cloud Provider Manager that there is a new extension pack.
+ *
+ * This is called when installing an extension pack.
+ */
+void ExtPack::i_notifyCloudProviderManager()
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ ComObjPtr<CloudProviderManager> cpm(m->pVirtualBox->i_getCloudProviderManager());
+ AssertReturnVoid(!cpm.isNull());
+
+ cpm->i_addExtPack(static_cast<IExtPack *>(this));
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+/**
+ * Probes the extension pack, loading the main dll and calling its registration
+ * entry point.
+ *
+ * This updates the state accordingly, the strWhyUnusable and fUnusable members
+ * being the most important ones.
+ */
+void ExtPack::i_probeAndLoad(void)
+{
+ m->fUsable = false;
+ m->fMadeReadyCall = false;
+
+ /*
+ * Query the file system info for the extension pack directory. This and
+ * all other file system info we save is for the benefit of refresh().
+ */
+ int vrc = RTPathQueryInfoEx(m->strExtPackPath.c_str(), &m->ObjInfoExtPack, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf(tr("RTPathQueryInfoEx on '%s' failed: %Rrc"), m->strExtPackPath.c_str(), vrc);
+ return;
+ }
+ if (!RTFS_IS_DIRECTORY(m->ObjInfoExtPack.Attr.fMode))
+ {
+ if (RTFS_IS_SYMLINK(m->ObjInfoExtPack.Attr.fMode))
+ m->strWhyUnusable.printf(tr("'%s' is a symbolic link, this is not allowed"),
+ m->strExtPackPath.c_str(), vrc);
+ else if (RTFS_IS_FILE(m->ObjInfoExtPack.Attr.fMode))
+ m->strWhyUnusable.printf(tr("'%s' is a symbolic file, not a directory"),
+ m->strExtPackPath.c_str(), vrc);
+ else
+ m->strWhyUnusable.printf(tr("'%s' is not a directory (fMode=%#x)"),
+ m->strExtPackPath.c_str(), m->ObjInfoExtPack.Attr.fMode);
+ return;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ vrc = SUPR3HardenedVerifyDir(m->strExtPackPath.c_str(), true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf("%s (rc=%Rrc)", ErrInfo.Core.pszMsg, vrc);
+ return;
+ }
+
+ /*
+ * Read the description file.
+ */
+ RTCString strSavedName(m->Desc.strName);
+ RTCString *pStrLoadErr = VBoxExtPackLoadDesc(m->strExtPackPath.c_str(), &m->Desc, &m->ObjInfoDesc);
+ if (pStrLoadErr != NULL)
+ {
+ m->strWhyUnusable.printf(tr("Failed to load '%s/%s': %s"),
+ m->strExtPackPath.c_str(), VBOX_EXTPACK_DESCRIPTION_NAME, pStrLoadErr->c_str());
+ m->Desc.strName = strSavedName;
+ delete pStrLoadErr;
+ return;
+ }
+
+ /*
+ * Make sure the XML name and directory matches.
+ */
+ if (!m->Desc.strName.equalsIgnoreCase(strSavedName))
+ {
+ m->strWhyUnusable.printf(tr("The description name ('%s') and directory name ('%s') does not match"),
+ m->Desc.strName.c_str(), strSavedName.c_str());
+ m->Desc.strName = strSavedName;
+ return;
+ }
+
+ /*
+ * Load the main DLL and call the predefined entry point.
+ */
+#ifndef VBOX_COM_INPROC
+ const char *pszMainModule = m->Desc.strMainModule.c_str();
+#else
+ const char *pszMainModule = m->Desc.strMainVMModule.c_str();
+ if (m->Desc.strMainVMModule.isEmpty())
+ {
+ /*
+ * We're good! The main module for VM processes is optional.
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#endif
+ bool fIsNative;
+ if (!i_findModule(pszMainModule, NULL /* default extension */, VBOXEXTPACKMODKIND_R3,
+ &m->strMainModPath, &fIsNative, &m->ObjInfoMainMod))
+ {
+ m->strWhyUnusable.printf(tr("Failed to locate the main module ('%s')"), pszMainModule);
+ return;
+ }
+
+ vrc = SUPR3HardenedVerifyPlugIn(m->strMainModPath.c_str(), &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->strWhyUnusable.printf("%s", ErrInfo.Core.pszMsg);
+ return;
+ }
+
+ if (fIsNative)
+ {
+ vrc = SUPR3HardenedLdrLoadPlugIn(m->strMainModPath.c_str(), &m->hMainMod, &ErrInfo.Core);
+ if (RT_FAILURE(vrc))
+ {
+ m->hMainMod = NIL_RTLDRMOD;
+ m->strWhyUnusable.printf(tr("Failed to load the main module ('%s'): %Rrc - %s"),
+ m->strMainModPath.c_str(), vrc, ErrInfo.Core.pszMsg);
+ return;
+ }
+ }
+ else
+ {
+ m->strWhyUnusable.printf(tr("Only native main modules are currently supported"));
+ return;
+ }
+
+ /*
+ * Resolve the predefined entry point.
+ */
+#ifndef VBOX_COM_INPROC
+ const char *pszMainEntryPoint = VBOX_EXTPACK_MAIN_MOD_ENTRY_POINT;
+ PFNVBOXEXTPACKREGISTER pfnRegistration;
+ uint32_t uVersion = VBOXEXTPACKREG_VERSION;
+#else
+ const char *pszMainEntryPoint = VBOX_EXTPACK_MAIN_VM_MOD_ENTRY_POINT;
+ PFNVBOXEXTPACKVMREGISTER pfnRegistration;
+ uint32_t uVersion = VBOXEXTPACKVMREG_VERSION;
+#endif
+ vrc = RTLdrGetSymbol(m->hMainMod, pszMainEntryPoint, (void **)&pfnRegistration);
+ if (RT_SUCCESS(vrc))
+ {
+ RTErrInfoClear(&ErrInfo.Core);
+ vrc = pfnRegistration(&m->Hlp, &m->pReg, &ErrInfo.Core);
+ if ( RT_SUCCESS(vrc)
+ && !RTErrInfoIsSet(&ErrInfo.Core)
+ && RT_VALID_PTR(m->pReg))
+ {
+ if ( VBOXEXTPACK_IS_MAJOR_VER_EQUAL(m->pReg->u32Version, uVersion)
+ && m->pReg->u32EndMarker == m->pReg->u32Version)
+ {
+#ifndef VBOX_COM_INPROC
+ if ( (!m->pReg->pfnInstalled || RT_VALID_PTR(m->pReg->pfnInstalled))
+ && (!m->pReg->pfnUninstall || RT_VALID_PTR(m->pReg->pfnUninstall))
+ && (!m->pReg->pfnVirtualBoxReady || RT_VALID_PTR(m->pReg->pfnVirtualBoxReady))
+ && (!m->pReg->pfnUnload || RT_VALID_PTR(m->pReg->pfnUnload))
+ && (!m->pReg->pfnVMCreated || RT_VALID_PTR(m->pReg->pfnVMCreated))
+ && (!m->pReg->pfnQueryObject || RT_VALID_PTR(m->pReg->pfnQueryObject))
+ )
+ {
+ /*
+ * We're good!
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#else
+ if ( (!m->pReg->pfnConsoleReady || RT_VALID_PTR(m->pReg->pfnConsoleReady))
+ && (!m->pReg->pfnUnload || RT_VALID_PTR(m->pReg->pfnUnload))
+ && (!m->pReg->pfnVMConfigureVMM || RT_VALID_PTR(m->pReg->pfnVMConfigureVMM))
+ && (!m->pReg->pfnVMPowerOn || RT_VALID_PTR(m->pReg->pfnVMPowerOn))
+ && (!m->pReg->pfnVMPowerOff || RT_VALID_PTR(m->pReg->pfnVMPowerOff))
+ && (!m->pReg->pfnQueryObject || RT_VALID_PTR(m->pReg->pfnQueryObject))
+ )
+ {
+ /*
+ * We're good!
+ */
+ m->fUsable = true;
+ m->strWhyUnusable.setNull();
+ return;
+ }
+#endif
+
+ m->strWhyUnusable = tr("The registration structure contains one or more invalid function pointers");
+ }
+ else
+ m->strWhyUnusable.printf(tr("Unsupported registration structure version %u.%u"),
+ RT_HIWORD(m->pReg->u32Version), RT_LOWORD(m->pReg->u32Version));
+ }
+ else
+ m->strWhyUnusable.printf(tr("%s returned %Rrc, pReg=%p ErrInfo='%s'"),
+ pszMainEntryPoint, vrc, m->pReg, ErrInfo.Core.pszMsg);
+ m->pReg = NULL;
+ }
+ else
+ m->strWhyUnusable.printf(tr("Failed to resolve exported symbol '%s' in the main module: %Rrc"),
+ pszMainEntryPoint, vrc);
+
+ RTLdrClose(m->hMainMod);
+ m->hMainMod = NIL_RTLDRMOD;
+}
+
+/**
+ * Finds a module.
+ *
+ * @returns true if found, false if not.
+ * @param a_pszName The module base name (no extension).
+ * @param a_pszExt The extension. If NULL we use default
+ * extensions.
+ * @param a_enmKind The kind of module to locate.
+ * @param a_pStrFound Where to return the path to the module we've
+ * found.
+ * @param a_pfNative Where to return whether this is a native module
+ * or an agnostic one. Optional.
+ * @param a_pObjInfo Where to return the file system object info for
+ * the module. Optional.
+ */
+bool ExtPack::i_findModule(const char *a_pszName, const char *a_pszExt, VBOXEXTPACKMODKIND a_enmKind,
+ Utf8Str *a_pStrFound, bool *a_pfNative, PRTFSOBJINFO a_pObjInfo) const
+{
+ /*
+ * Try the native path first.
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), RTBldCfgTargetDotArch());
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ const char *pszDefExt;
+ switch (a_enmKind)
+ {
+ case VBOXEXTPACKMODKIND_RC: pszDefExt = ".rc"; break;
+ case VBOXEXTPACKMODKIND_R0: pszDefExt = ".r0"; break;
+ case VBOXEXTPACKMODKIND_R3: pszDefExt = RTLdrGetSuff(); break;
+ default:
+ AssertFailedReturn(false);
+ }
+ vrc = RTStrCat(szPath, sizeof(szPath), pszDefExt);
+ AssertLogRelRCReturn(vrc, false);
+ }
+
+ RTFSOBJINFO ObjInfo;
+ if (!a_pObjInfo)
+ a_pObjInfo = &ObjInfo;
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = true;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ /*
+ * Try the platform agnostic modules.
+ */
+ /* gcc.x86/module.rel */
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%s.%s", RTBldCfgCompiler(), RTBldCfgTargetArch());
+ vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), szSubDir);
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ vrc = RTStrCat(szPath, sizeof(szPath), ".rel");
+ AssertLogRelRCReturn(vrc, false);
+ }
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = false;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ /* x86/module.rel */
+ vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), RTBldCfgTargetArch());
+ AssertLogRelRCReturn(vrc, false);
+ vrc = RTPathAppend(szPath, sizeof(szPath), a_pszName);
+ AssertLogRelRCReturn(vrc, false);
+ if (!a_pszExt)
+ {
+ vrc = RTStrCat(szPath, sizeof(szPath), ".rel");
+ AssertLogRelRCReturn(vrc, false);
+ }
+ vrc = RTPathQueryInfo(szPath, a_pObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc) && RTFS_IS_FILE(a_pObjInfo->Attr.fMode))
+ {
+ if (a_pfNative)
+ *a_pfNative = false;
+ *a_pStrFound = szPath;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Compares two file system object info structures.
+ *
+ * @returns true if equal, false if not.
+ * @param pObjInfo1 The first.
+ * @param pObjInfo2 The second.
+ * @todo IPRT should do this, really.
+ */
+/* static */ bool ExtPack::i_objinfoIsEqual(PCRTFSOBJINFO pObjInfo1, PCRTFSOBJINFO pObjInfo2)
+{
+ if (!RTTimeSpecIsEqual(&pObjInfo1->ModificationTime, &pObjInfo2->ModificationTime))
+ return false;
+ if (!RTTimeSpecIsEqual(&pObjInfo1->ChangeTime, &pObjInfo2->ChangeTime))
+ return false;
+ if (!RTTimeSpecIsEqual(&pObjInfo1->BirthTime, &pObjInfo2->BirthTime))
+ return false;
+ if (pObjInfo1->cbObject != pObjInfo2->cbObject)
+ return false;
+ if (pObjInfo1->Attr.fMode != pObjInfo2->Attr.fMode)
+ return false;
+ if (pObjInfo1->Attr.enmAdditional == pObjInfo2->Attr.enmAdditional)
+ {
+ switch (pObjInfo1->Attr.enmAdditional)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ if (pObjInfo1->Attr.u.Unix.uid != pObjInfo2->Attr.u.Unix.uid)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.gid != pObjInfo2->Attr.u.Unix.gid)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.INodeIdDevice != pObjInfo2->Attr.u.Unix.INodeIdDevice)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.INodeId != pObjInfo2->Attr.u.Unix.INodeId)
+ return false;
+ if (pObjInfo1->Attr.u.Unix.GenerationId != pObjInfo2->Attr.u.Unix.GenerationId)
+ return false;
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * @interface_method_impl{VBOXEXTPACKHLP,pfnFindModule}
+ */
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpFindModule(PCVBOXEXTPACKHLP pHlp, const char *pszName, const char *pszExt, VBOXEXTPACKMODKIND enmKind,
+ char *pszFound, size_t cbFound, bool *pfNative)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszExt, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFound, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfNative, VERR_INVALID_POINTER);
+ AssertReturn(enmKind > VBOXEXTPACKMODKIND_INVALID && enmKind < VBOXEXTPACKMODKIND_END, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /*
+ * This is just a wrapper around findModule.
+ */
+ Utf8Str strFound;
+ if (pThis->i_findModule(pszName, pszExt, enmKind, &strFound, pfNative, NULL))
+ return RTStrCopy(pszFound, cbFound, strFound.c_str());
+ return VERR_FILE_NOT_FOUND;
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpGetFilePath(PCVBOXEXTPACKHLP pHlp, const char *pszFilename, char *pszPath, size_t cbPath)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath > 0, VERR_BUFFER_OVERFLOW);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ /*
+ * This is a simple RTPathJoin, no checking if things exists or anything.
+ */
+ int vrc = RTPathJoin(pszPath, cbPath, pThis->m->strExtPackPath.c_str(), pszFilename);
+ if (RT_FAILURE(vrc))
+ RT_BZERO(pszPath, cbPath);
+ return vrc;
+}
+
+/*static*/ DECLCALLBACK(VBOXEXTPACKCTX)
+ExtPack::i_hlpGetContext(PCVBOXEXTPACKHLP pHlp)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, VBOXEXTPACKCTX_INVALID);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VBOXEXTPACKCTX_INVALID);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VBOXEXTPACKCTX_INVALID);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VBOXEXTPACKCTX_INVALID);
+
+ return pThis->m->enmContext;
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpLoadHGCMService(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IConsole) *pConsole,
+ const char *pszServiceLibrary, const char *pszServiceName)
+{
+#ifdef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszServiceLibrary, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszServiceName, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pConsole, VERR_INVALID_POINTER);
+
+ Console *pCon = (Console *)pConsole;
+ return pCon->i_hgcmLoadService(pszServiceLibrary, pszServiceName);
+#else
+ NOREF(pHlp); NOREF(pConsole); NOREF(pszServiceLibrary); NOREF(pszServiceName);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpLoadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary)
+{
+#ifndef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszPluginLibrary, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVirtualBox, VERR_INVALID_POINTER);
+
+ VirtualBox *pVBox = (VirtualBox *)pVirtualBox;
+ return pVBox->i_loadVDPlugin(pszPluginLibrary);
+#else
+ NOREF(pHlp); NOREF(pVirtualBox); NOREF(pszPluginLibrary);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpUnloadVDPlugin(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IVirtualBox) *pVirtualBox, const char *pszPluginLibrary)
+{
+#ifndef VBOX_COM_INPROC
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pszPluginLibrary, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVirtualBox, VERR_INVALID_POINTER);
+
+ VirtualBox *pVBox = (VirtualBox *)pVirtualBox;
+ return pVBox->i_unloadVDPlugin(pszPluginLibrary);
+#else
+ NOREF(pHlp); NOREF(pVirtualBox); NOREF(pszPluginLibrary);
+ return VERR_INVALID_STATE;
+#endif
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IUnknown) *pInitiator,
+ const char *pcszDescription, uint32_t cOperations,
+ uint32_t uTotalOperationsWeight, const char *pcszFirstOperationDescription,
+ uint32_t uFirstOperationWeight, VBOXEXTPACK_IF_CS(IProgress) **ppProgressOut)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pcszDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(cOperations >= 1, (uint32_t)E_INVALIDARG);
+ AssertReturn(uTotalOperationsWeight >= 1, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pcszFirstOperationDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(uFirstOperationWeight >= 1, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppProgressOut, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+#ifndef VBOX_COM_INPROC
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+#endif
+
+ ComObjPtr<Progress> pProgress;
+ HRESULT hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return hrc;
+ hrc = pProgress->init(
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox,
+#endif
+ pInitiator, pcszDescription, TRUE /* aCancelable */,
+ cOperations, uTotalOperationsWeight,
+ pcszFirstOperationDescription, uFirstOperationWeight);
+ if (FAILED(hrc))
+ return hrc;
+
+ return pProgress.queryInterfaceTo(ppProgressOut);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpGetCanceledProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ bool *pfCanceled)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pfCanceled, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ BOOL fCanceled = FALSE;
+ HRESULT hrc = pProgress->COMGETTER(Canceled)(&fCanceled);
+ *pfCanceled = !!fCanceled;
+ return hrc;
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpUpdateProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uPercent)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertReturn(uPercent <= 100, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->SetCurrentOperationProgress(uPercent);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpNextOperationProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ const char *pcszNextOperationDescription,
+ uint32_t uNextOperationWeight)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pcszNextOperationDescription, (uint32_t)E_INVALIDARG);
+ AssertReturn(uNextOperationWeight >= 1, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->SetNextOperation(Bstr(pcszNextOperationDescription).raw(), uNextOperationWeight);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpWaitOtherProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ VBOXEXTPACK_IF_CS(IProgress) *pProgressOther, uint32_t cTimeoutMS)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(pProgressOther, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+ return pProgressControl->WaitForOtherProgressCompletion(pProgressOther, cTimeoutMS);
+}
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCompleteProgress(PCVBOXEXTPACKHLP pHlp, VBOXEXTPACK_IF_CS(IProgress) *pProgress,
+ uint32_t uResultCode)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pProgress, (uint32_t)E_INVALIDARG);
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IInternalProgressControl> pProgressControl(pProgress);
+ AssertReturn(!!pProgressControl, (uint32_t)E_INVALIDARG);
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ if (FAILED((HRESULT)uResultCode))
+ {
+ ErrorInfoKeeper eik;
+ eik.getVirtualBoxErrorInfo(errorInfo);
+ }
+ return pProgressControl->NotifyComplete((LONG)uResultCode, errorInfo);
+}
+
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType, bool aWaitable,
+ VBOXEXTPACK_IF_CS(IEvent) **ppEventOut)
+{
+ HRESULT hrc;
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppEventOut, (uint32_t)E_INVALIDARG);
+
+ ComObjPtr<VBoxEvent> pEvent;
+
+ hrc = pEvent.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* default aSource to pVirtualBox? */
+ hrc = pEvent->init(aSource, static_cast<VBoxEventType_T>(aType), aWaitable);
+ if (FAILED(hrc))
+ return hrc;
+
+ return pEvent.queryInterfaceTo(ppEventOut);
+}
+
+
+/*static*/ DECLCALLBACK(uint32_t)
+ExtPack::i_hlpCreateVetoEvent(PCVBOXEXTPACKHLP pHlp,
+ VBOXEXTPACK_IF_CS(IEventSource) *aSource,
+ /* VBoxEventType_T */ uint32_t aType,
+ VBOXEXTPACK_IF_CS(IVetoEvent) **ppEventOut)
+{
+ HRESULT hrc;
+
+ AssertPtrReturn(pHlp, (uint32_t)E_INVALIDARG);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, (uint32_t)E_INVALIDARG);
+ AssertPtrReturn(ppEventOut, (uint32_t)E_INVALIDARG);
+
+ ComObjPtr<VBoxVetoEvent> pEvent;
+
+ hrc = pEvent.createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ /* default aSource to pVirtualBox? */
+ hrc = pEvent->init(aSource, static_cast<VBoxEventType_T>(aType));
+ if (FAILED(hrc))
+ return hrc;
+
+ return pEvent.queryInterfaceTo(ppEventOut);
+}
+
+
+/*static*/ DECLCALLBACK(const char *)
+ExtPack::i_hlpTranslate(PCVBOXEXTPACKHLP pHlp,
+ const char *pszComponent,
+ const char *pszSourceText,
+ const char *pszComment /* = NULL */,
+ const size_t aNum /* = -1 */)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, pszSourceText);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, pszSourceText);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, pszSourceText);
+
+#ifdef VBOX_WITH_MAIN_NLS
+ return VirtualBoxTranslator::translate(m->pTrComponent, pszComponent,
+ pszSourceText, pszComment, aNum);
+#else
+ NOREF(pszComponent);
+ NOREF(pszComment);
+ NOREF(aNum);
+ return pszSourceText;
+#endif
+}
+
+
+/*static*/ DECLCALLBACK(int)
+ExtPack::i_hlpReservedN(PCVBOXEXTPACKHLP pHlp)
+{
+ /*
+ * Validate the input and get our bearings.
+ */
+ AssertPtrReturn(pHlp, VERR_INVALID_POINTER);
+ AssertReturn(pHlp->u32Version == VBOXEXTPACKHLP_VERSION, VERR_INVALID_POINTER);
+ ExtPack::Data *m = RT_FROM_CPP_MEMBER(pHlp, Data, Hlp);
+ AssertPtrReturn(m, VERR_INVALID_POINTER);
+ ExtPack *pThis = m->pThis;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+
+
+HRESULT ExtPack::getName(com::Utf8Str &aName)
+{
+ aName = m->Desc.strName;
+ return S_OK;
+}
+
+HRESULT ExtPack::getDescription(com::Utf8Str &aDescription)
+{
+ aDescription = m->Desc.strDescription;
+ return S_OK;
+}
+
+HRESULT ExtPack::getVersion(com::Utf8Str &aVersion)
+{
+ aVersion = m->Desc.strVersion;
+ return S_OK;
+}
+
+HRESULT ExtPack::getRevision(ULONG *aRevision)
+{
+ *aRevision = m->Desc.uRevision;
+ return S_OK;
+}
+
+HRESULT ExtPack::getEdition(com::Utf8Str &aEdition)
+{
+ aEdition = m->Desc.strEdition;
+ return S_OK;
+}
+
+HRESULT ExtPack::getVRDEModule(com::Utf8Str &aVRDEModule)
+{
+ aVRDEModule = m->Desc.strVrdeModule;
+ return S_OK;
+}
+
+HRESULT ExtPack::getCryptoModule(com::Utf8Str &aCryptoModule)
+{
+ aCryptoModule = m->Desc.strCryptoModule;
+ return S_OK;
+}
+
+HRESULT ExtPack::getPlugIns(std::vector<ComPtr<IExtPackPlugIn> > &aPlugIns)
+{
+ /** @todo implement plug-ins. */
+ NOREF(aPlugIns);
+ ReturnComNotImplemented();
+}
+
+HRESULT ExtPack::getUsable(BOOL *aUsable)
+{
+ *aUsable = m->fUsable;
+ return S_OK;
+}
+
+HRESULT ExtPack::getWhyUnusable(com::Utf8Str &aWhyUnusable)
+{
+ aWhyUnusable = m->strWhyUnusable;
+ return S_OK;
+}
+
+HRESULT ExtPack::getShowLicense(BOOL *aShowLicense)
+{
+ *aShowLicense = m->Desc.fShowLicense;
+ return S_OK;
+}
+
+HRESULT ExtPack::getLicense(com::Utf8Str &aLicense)
+{
+ Utf8Str strHtml("html");
+ Utf8Str str("");
+ return queryLicense(str, str, strHtml, aLicense);
+}
+
+HRESULT ExtPack::queryLicense(const com::Utf8Str &aPreferredLocale, const com::Utf8Str &aPreferredLanguage,
+ const com::Utf8Str &aFormat, com::Utf8Str &aLicenseText)
+{
+ HRESULT hrc = S_OK;
+
+ /*
+ * Validate input.
+ */
+ if (aPreferredLocale.length() != 2 && aPreferredLocale.length() != 0)
+ return setError(E_FAIL, tr("The preferred locale is a two character string or empty."));
+
+ if (aPreferredLanguage.length() != 2 && aPreferredLanguage.length() != 0)
+ return setError(E_FAIL, tr("The preferred language is a two character string or empty."));
+
+ if ( !aFormat.equals("html")
+ && !aFormat.equals("rtf")
+ && !aFormat.equals("txt"))
+ return setError(E_FAIL, tr("The license format can only have the values 'html', 'rtf' and 'txt'."));
+
+ /*
+ * Combine the options to form a file name before locking down anything.
+ */
+ char szName[sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX "-de_DE.html") + 2];
+ if (aPreferredLocale.isNotEmpty() && aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s_%s.%s",
+ aPreferredLocale.c_str(), aPreferredLanguage.c_str(), aFormat.c_str());
+ else if (aPreferredLocale.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else if (aPreferredLanguage.isNotEmpty())
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s",
+ aPreferredLocale.c_str(), aFormat.c_str());
+ else
+ RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX ".%s",
+ aFormat.c_str());
+
+ /*
+ * Effectuate the query.
+ */
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS); /* paranoia */
+
+ if (!m->fUsable)
+ hrc = setError(E_FAIL, "%s", m->strWhyUnusable.c_str());
+ else
+ {
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathJoin(szPath, sizeof(szPath), m->strExtPackPath.c_str(), szName);
+ if (RT_SUCCESS(vrc))
+ {
+ void *pvFile;
+ size_t cbFile;
+ vrc = RTFileReadAllEx(szPath, 0, RTFOFF_MAX, RTFILE_RDALL_O_DENY_READ, &pvFile, &cbFile);
+ if (RT_SUCCESS(vrc))
+ {
+ Bstr bstrLicense((const char *)pvFile, cbFile);
+ if (bstrLicense.isNotEmpty())
+ {
+ aLicenseText = Utf8Str(bstrLicense);
+ hrc = S_OK;
+ }
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR, tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
+ szPath);
+ RTFileReadAllFree(pvFile, cbFile);
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND)
+ hrc = setErrorBoth(VBOX_E_OBJECT_NOT_FOUND, vrc, tr("The license file '%s' was not found in extension pack '%s'"),
+ szName, m->Desc.strName.c_str());
+ else
+ hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to open the license file '%s': %Rrc"), szPath, vrc);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("RTPathJoin failed: %Rrc"), vrc);
+ }
+ return hrc;
+}
+
+HRESULT ExtPack::queryObject(const com::Utf8Str &aObjUuid, ComPtr<IUnknown> &aReturnInterface)
+{
+ com::Guid ObjectId;
+ CheckComArgGuid(aObjUuid, ObjectId);
+
+ HRESULT hrc = S_OK;
+
+ if ( m->pReg
+ && m->pReg->pfnQueryObject)
+ {
+ void *pvUnknown = m->pReg->pfnQueryObject(m->pReg, ObjectId.raw());
+ if (pvUnknown)
+ {
+ aReturnInterface = (IUnknown *)pvUnknown;
+ /* The above assignment increased the refcount. Since pvUnknown
+ * is a dumb pointer we have to do the release ourselves. */
+ ((IUnknown *)pvUnknown)->Release();
+ }
+ else
+ hrc = E_NOINTERFACE;
+ }
+ else
+ hrc = E_NOINTERFACE;
+ return hrc;
+}
+
+DEFINE_EMPTY_CTOR_DTOR(ExtPackManager)
+
+/**
+ * Called by ComObjPtr::createObject when creating the object.
+ *
+ * Just initialize the basic object state, do the rest in init().
+ *
+ * @returns S_OK.
+ */
+HRESULT ExtPackManager::FinalConstruct()
+{
+ m = NULL;
+ return BaseFinalConstruct();
+}
+
+/**
+ * Initializes the extension pack manager.
+ *
+ * @returns COM status code.
+ * @param a_pVirtualBox Pointer to the VirtualBox object.
+ * @param a_enmContext The context we're in.
+ */
+HRESULT ExtPackManager::initExtPackManager(VirtualBox *a_pVirtualBox, VBOXEXTPACKCTX a_enmContext)
+{
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ /*
+ * Figure some stuff out before creating the instance data.
+ */
+ char szBaseDir[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArchTop(szBaseDir, sizeof(szBaseDir));
+ AssertLogRelRCReturn(vrc, E_FAIL);
+ vrc = RTPathAppend(szBaseDir, sizeof(szBaseDir), VBOX_EXTPACK_INSTALL_DIR);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ char szCertificatDir[RTPATH_MAX];
+ vrc = RTPathAppPrivateNoArch(szCertificatDir, sizeof(szCertificatDir));
+ AssertLogRelRCReturn(vrc, E_FAIL);
+ vrc = RTPathAppend(szCertificatDir, sizeof(szCertificatDir), VBOX_EXTPACK_CERT_DIR);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ /*
+ * Allocate and initialize the instance data.
+ */
+ m = new Data;
+ m->strBaseDir = szBaseDir;
+ m->strCertificatDirPath = szCertificatDir;
+ m->enmContext = a_enmContext;
+#ifndef VBOX_COM_INPROC
+ m->pVirtualBox = a_pVirtualBox;
+#else
+ RT_NOREF_PV(a_pVirtualBox);
+#endif
+
+ /*
+ * Go looking for extensions. The RTDirOpen may fail if nothing has been
+ * installed yet, or if root is paranoid and has revoked our access to them.
+ *
+ * We ASSUME that there are no files, directories or stuff in the directory
+ * that exceed the max name length in RTDIRENTRYEX.
+ */
+ HRESULT hrc = S_OK;
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, szBaseDir);
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ RTDIRENTRYEX Entry;
+ vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
+ break;
+ }
+ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
+ && strcmp(Entry.szName, ".") != 0
+ && strcmp(Entry.szName, "..") != 0
+ && VBoxExtPackIsValidMangledName(Entry.szName) )
+ {
+ /*
+ * All directories are extensions, the shall be nothing but
+ * extensions in this subdirectory.
+ */
+ char szExtPackDir[RTPATH_MAX];
+ vrc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), m->strBaseDir.c_str(), Entry.szName);
+ AssertLogRelRC(vrc);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCString *pstrName = VBoxExtPackUnmangleName(Entry.szName, RTSTR_MAX);
+ AssertLogRel(pstrName);
+ if (pstrName)
+ {
+ ComObjPtr<ExtPack> NewExtPack;
+ HRESULT hrc2 = NewExtPack.createObject();
+ if (SUCCEEDED(hrc2))
+ hrc2 = NewExtPack->initWithDir(a_pVirtualBox, a_enmContext, pstrName->c_str(), szExtPackDir);
+ delete pstrName;
+ if (SUCCEEDED(hrc2))
+ {
+ m->llInstalledExtPacks.push_back(NewExtPack);
+ /* Paranoia, there should be no API clients before this method is finished. */
+
+ m->cUpdate++;
+ }
+ else if (SUCCEEDED(hrc))
+ hrc = hrc2;
+ }
+ else
+ hrc = E_UNEXPECTED;
+ }
+ else
+ hrc = E_UNEXPECTED;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ /* else: ignore, the directory probably does not exist or something. */
+
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+ return hrc;
+}
+
+/**
+ * COM cruft.
+ */
+void ExtPackManager::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+/**
+ * Do the actual cleanup.
+ */
+void ExtPackManager::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (!autoUninitSpan.uninitDone() && m != NULL)
+ {
+ delete m;
+ m = NULL;
+ }
+}
+
+HRESULT ExtPackManager::getInstalledExtPacks(std::vector<ComPtr<IExtPack> > &aInstalledExtPacks)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ aInstalledExtPacks.resize(m->llInstalledExtPacks.size());
+ std::copy(m->llInstalledExtPacks.begin(), m->llInstalledExtPacks.end(), aInstalledExtPacks.begin());
+
+ return S_OK;
+}
+
+HRESULT ExtPackManager::find(const com::Utf8Str &aName, ComPtr<IExtPack> &aReturnData)
+{
+ HRESULT hrc = S_OK;
+
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ComPtr<ExtPack> ptrExtPack = i_findExtPack(aName.c_str());
+ if (!ptrExtPack.isNull())
+ ptrExtPack.queryInterfaceTo(aReturnData.asOutParam());
+ else
+ hrc = VBOX_E_OBJECT_NOT_FOUND;
+
+ return hrc;
+}
+
+HRESULT ExtPackManager::openExtPackFile(const com::Utf8Str &aPath, ComPtr<IExtPackFile> &aFile)
+{
+ AssertReturn(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON, E_UNEXPECTED);
+
+#ifndef VBOX_COM_INPROC
+ /* The API can optionally take a ::SHA-256=<hex-digest> attribute at the
+ end of the file name. This is just a temporary measure for
+ backporting, in 4.2 we'll add another parameter to the method. */
+ Utf8Str strTarball;
+ Utf8Str strDigest;
+ size_t offSha256 = aPath.find("::SHA-256=");
+ if (offSha256 == Utf8Str::npos)
+ strTarball = aPath;
+ else
+ {
+ strTarball = aPath.substr(0, offSha256);
+ strDigest = aPath.substr(offSha256 + sizeof("::SHA-256=") - 1);
+ }
+
+ ComObjPtr<ExtPackFile> NewExtPackFile;
+ HRESULT hrc = NewExtPackFile.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = NewExtPackFile->initWithFile(strTarball.c_str(), strDigest.c_str(), this, m->pVirtualBox);
+ if (SUCCEEDED(hrc))
+ NewExtPackFile.queryInterfaceTo(aFile.asOutParam());
+
+ return hrc;
+#else
+ RT_NOREF(aPath, aFile);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ExtPackManager::uninstall(const com::Utf8Str &aName, BOOL aForcedRemoval,
+ const com::Utf8Str &aDisplayInfo, ComPtr<IProgress> &aProgress)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+#ifndef VBOX_COM_INPROC
+
+ HRESULT hrc;
+ ExtPackUninstallTask *pTask = NULL;
+ try
+ {
+ pTask = new ExtPackUninstallTask();
+ hrc = pTask->Init(this, aName, aForcedRemoval != FALSE, aDisplayInfo);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<Progress> ptrProgress = pTask->ptrProgress;
+ hrc = pTask->createThreadWithType(RTTHREADTYPE_DEFAULT);
+ pTask = NULL; /* always consumed by createThread */
+ if (SUCCEEDED(hrc))
+ hrc = ptrProgress.queryInterfaceTo(aProgress.asOutParam());
+ else
+ hrc = setError(VBOX_E_IPRT_ERROR,
+ tr("Starting thread for an extension pack uninstallation failed with %Rrc"), hrc);
+ }
+ else
+ hrc = setError(hrc, tr("Looks like creating a progress object for ExtraPackUninstallTask object failed"));
+ }
+ catch (std::bad_alloc &)
+ {
+ hrc = E_OUTOFMEMORY;
+ }
+ catch (HRESULT hrcXcpt)
+ {
+ LogFlowThisFunc(("Exception was caught in the function ExtPackManager::uninstall()\n"));
+ hrc = hrcXcpt;
+ }
+ if (pTask)
+ delete pTask;
+ return hrc;
+#else
+ RT_NOREF(aName, aForcedRemoval, aDisplayInfo, aProgress);
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ExtPackManager::cleanup(void)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the set-uid-to-root binary that performs the cleanup.
+ *
+ * Take the write lock to prevent conflicts with other calls to this
+ * VBoxSVC instance.
+ */
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ hrc = i_runSetUidToRootHelper(NULL,
+ "cleanup",
+ "--base-dir", m->strBaseDir.c_str(),
+ (const char *)NULL);
+ }
+
+ return hrc;
+}
+
+HRESULT ExtPackManager::queryAllPlugInsForFrontend(const com::Utf8Str &aFrontendName, std::vector<com::Utf8Str> &aPlugInModules)
+{
+ NOREF(aFrontendName);
+ aPlugInModules.resize(0);
+ return S_OK;
+}
+
+HRESULT ExtPackManager::isExtPackUsable(const com::Utf8Str &aName, BOOL *aUsable)
+{
+ *aUsable = i_isExtPackUsable(aName.c_str());
+ return S_OK;
+}
+
+/**
+ * Finds the success indicator string in the stderr output ofr hte helper app.
+ *
+ * @returns Pointer to the indicator.
+ * @param psz The stderr output string. Can be NULL.
+ * @param cch The size of the string.
+ */
+static char *findSuccessIndicator(char *psz, size_t cch)
+{
+ static const char s_szSuccessInd[] = "rcExit=RTEXITCODE_SUCCESS";
+ Assert(!cch || strlen(psz) == cch);
+ if (cch < sizeof(s_szSuccessInd) - 1)
+ return NULL;
+ char *pszInd = &psz[cch - sizeof(s_szSuccessInd) + 1];
+ if (strcmp(s_szSuccessInd, pszInd))
+ return NULL;
+ return pszInd;
+}
+
+/**
+ * Runs the helper application that does the privileged operations.
+ *
+ * @returns S_OK or a failure status with error information set.
+ * @param a_pstrDisplayInfo Platform specific display info hacks.
+ * @param a_pszCommand The command to execute.
+ * @param ... The argument strings that goes along with the
+ * command. Maximum is about 16. Terminated by a
+ * NULL.
+ */
+HRESULT ExtPackManager::i_runSetUidToRootHelper(Utf8Str const *a_pstrDisplayInfo, const char *a_pszCommand, ...)
+{
+ /*
+ * Calculate the path to the helper application.
+ */
+ char szExecName[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szExecName, sizeof(szExecName));
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ vrc = RTPathAppend(szExecName, sizeof(szExecName), VBOX_EXTPACK_HELPER_NAME);
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ /*
+ * Convert the variable argument list to a RTProcCreate argument vector.
+ */
+ const char *apszArgs[20];
+ unsigned cArgs = 0;
+
+ LogRel(("ExtPack: Executing '%s'", szExecName));
+ apszArgs[cArgs++] = &szExecName[0];
+
+ if ( a_pstrDisplayInfo
+ && a_pstrDisplayInfo->isNotEmpty())
+ {
+ LogRel((" '--display-info-hack' '%s'", a_pstrDisplayInfo->c_str()));
+ apszArgs[cArgs++] = "--display-info-hack";
+ apszArgs[cArgs++] = a_pstrDisplayInfo->c_str();
+ }
+
+ LogRel((" '%s'", a_pszCommand));
+ apszArgs[cArgs++] = a_pszCommand;
+
+ va_list va;
+ va_start(va, a_pszCommand);
+ const char *pszLastArg;
+ for (;;)
+ {
+ AssertReturn(cArgs < RT_ELEMENTS(apszArgs) - 1, E_UNEXPECTED);
+ pszLastArg = va_arg(va, const char *);
+ if (!pszLastArg)
+ break;
+ LogRel((" '%s'", pszLastArg));
+ apszArgs[cArgs++] = pszLastArg;
+ };
+ va_end(va);
+
+ LogRel(("\n"));
+ apszArgs[cArgs] = NULL;
+
+ /*
+ * Create a PIPE which we attach to stderr so that we can read the error
+ * message on failure and report it back to the caller.
+ */
+ RTPIPE hPipeR;
+ RTHANDLE hStdErrPipe;
+ hStdErrPipe.enmType = RTHANDLETYPE_PIPE;
+ vrc = RTPipeCreate(&hPipeR, &hStdErrPipe.u.hPipe, RTPIPE_C_INHERIT_WRITE);
+ AssertLogRelRCReturn(vrc, E_UNEXPECTED);
+
+ /*
+ * Spawn the process.
+ */
+ HRESULT hrc;
+ RTPROCESS hProcess;
+ vrc = RTProcCreateEx(szExecName,
+ apszArgs,
+ RTENV_DEFAULT,
+ 0 /*fFlags*/,
+ NULL /*phStdIn*/,
+ NULL /*phStdOut*/,
+ &hStdErrPipe,
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ NULL /*pvExtraData*/,
+ &hProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPipeClose(hStdErrPipe.u.hPipe);
+ hStdErrPipe.u.hPipe = NIL_RTPIPE;
+
+ /*
+ * Read the pipe output until the process completes.
+ */
+ RTPROCSTATUS ProcStatus = { -42, RTPROCEXITREASON_ABEND };
+ size_t cbStdErrBuf = 0;
+ size_t offStdErrBuf = 0;
+ char *pszStdErrBuf = NULL;
+ do
+ {
+ /*
+ * Service the pipe. Block waiting for output or the pipe breaking
+ * when the process terminates.
+ */
+ if (hPipeR != NIL_RTPIPE)
+ {
+ char achBuf[1024];
+ size_t cbRead;
+ vrc = RTPipeReadBlocking(hPipeR, achBuf, sizeof(achBuf), &cbRead);
+ if (RT_SUCCESS(vrc))
+ {
+ /* grow the buffer? */
+ size_t cbBufReq = offStdErrBuf + cbRead + 1;
+ if ( cbBufReq > cbStdErrBuf
+ && cbBufReq < _256K)
+ {
+ size_t cbNew = RT_ALIGN_Z(cbBufReq, 16); // 1024
+ void *pvNew = RTMemRealloc(pszStdErrBuf, cbNew);
+ if (pvNew)
+ {
+ pszStdErrBuf = (char *)pvNew;
+ cbStdErrBuf = cbNew;
+ }
+ }
+
+ /* append if we've got room. */
+ if (cbBufReq <= cbStdErrBuf)
+ {
+ memcpy(&pszStdErrBuf[offStdErrBuf], achBuf, cbRead);
+ offStdErrBuf = offStdErrBuf + cbRead;
+ pszStdErrBuf[offStdErrBuf] = '\0';
+ }
+ }
+ else
+ {
+ AssertLogRelMsg(vrc == VERR_BROKEN_PIPE, ("%Rrc\n", vrc));
+ RTPipeClose(hPipeR);
+ hPipeR = NIL_RTPIPE;
+ }
+ }
+
+ /*
+ * Service the process. Block if we have no pipe.
+ */
+ if (hProcess != NIL_RTPROCESS)
+ {
+ vrc = RTProcWait(hProcess,
+ hPipeR == NIL_RTPIPE ? RTPROCWAIT_FLAGS_BLOCK : RTPROCWAIT_FLAGS_NOBLOCK,
+ &ProcStatus);
+ if (RT_SUCCESS(vrc))
+ hProcess = NIL_RTPROCESS;
+ else
+ AssertLogRelMsgStmt(vrc == VERR_PROCESS_RUNNING, ("%Rrc\n", vrc), hProcess = NIL_RTPROCESS);
+ }
+ } while ( hPipeR != NIL_RTPIPE
+ || hProcess != NIL_RTPROCESS);
+
+ LogRel(("ExtPack: enmReason=%d iStatus=%d stderr='%s'\n",
+ ProcStatus.enmReason, ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : ""));
+
+ /*
+ * Look for rcExit=RTEXITCODE_SUCCESS at the end of the error output,
+ * cut it as it is only there to attest the success.
+ */
+ if (offStdErrBuf > 0)
+ {
+ RTStrStripR(pszStdErrBuf);
+ offStdErrBuf = strlen(pszStdErrBuf);
+ }
+
+ char *pszSuccessInd = findSuccessIndicator(pszStdErrBuf, offStdErrBuf);
+ if (pszSuccessInd)
+ {
+ *pszSuccessInd = '\0';
+ offStdErrBuf = (size_t)(pszSuccessInd - pszStdErrBuf);
+ }
+ else if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ ProcStatus.iStatus = offStdErrBuf ? 667 : 666;
+
+ /*
+ * Compose the status code and, on failure, error message.
+ */
+ if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ hrc = S_OK;
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_NORMAL)
+ {
+ AssertMsg(ProcStatus.iStatus != 0, ("%s\n", pszStdErrBuf));
+ hrc = setError(E_FAIL, tr("The installer failed with exit code %d: %s"),
+ ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+ }
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_SIGNAL)
+ hrc = setError(E_UNEXPECTED, tr("The installer was killed by signal #d (stderr: %s)"),
+ ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+ else if (ProcStatus.enmReason == RTPROCEXITREASON_ABEND)
+ hrc = setError(E_UNEXPECTED, tr("The installer aborted abnormally (stderr: %s)"),
+ offStdErrBuf ? pszStdErrBuf : "");
+ else
+ hrc = setError(E_UNEXPECTED, tr("internal error: enmReason=%d iStatus=%d stderr='%s'"),
+ ProcStatus.enmReason, ProcStatus.iStatus, offStdErrBuf ? pszStdErrBuf : "");
+
+ RTMemFree(pszStdErrBuf);
+ }
+ else
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to launch the helper application '%s' (%Rrc)"), szExecName, vrc);
+
+ RTPipeClose(hPipeR);
+ RTPipeClose(hStdErrPipe.u.hPipe);
+
+ return hrc;
+}
+
+/**
+ * Finds an installed extension pack.
+ *
+ * @returns Pointer to the extension pack if found, NULL if not. (No reference
+ * counting problem here since the caller must be holding the lock.)
+ * @param a_pszName The name of the extension pack.
+ */
+ExtPack *ExtPackManager::i_findExtPack(const char *a_pszName)
+{
+ size_t cchName = strlen(a_pszName);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if ( pExtPackData
+ && pExtPackData->Desc.strName.length() == cchName
+ && pExtPackData->Desc.strName.equalsIgnoreCase(a_pszName))
+ return (*it);
+ }
+ return NULL;
+}
+
+/**
+ * Removes an installed extension pack from the internal list.
+ *
+ * The package is expected to exist!
+ *
+ * @param a_pszName The name of the extension pack.
+ */
+void ExtPackManager::i_removeExtPack(const char *a_pszName)
+{
+ size_t cchName = strlen(a_pszName);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if ( pExtPackData
+ && pExtPackData->Desc.strName.length() == cchName
+ && pExtPackData->Desc.strName.equalsIgnoreCase(a_pszName))
+ {
+ m->llInstalledExtPacks.erase(it);
+ m->cUpdate++;
+ return;
+ }
+ }
+ AssertMsgFailed(("%s\n", a_pszName));
+}
+
+#ifndef VBOX_COM_INPROC
+
+/**
+ * Refreshes the specified extension pack.
+ *
+ * This may remove the extension pack from the list, so any non-smart pointers
+ * to the extension pack object may become invalid.
+ *
+ * @returns S_OK and *a_ppExtPack on success, COM status code and error
+ * message on failure. Note that *a_ppExtPack can be NULL.
+ *
+ * @param a_pszName The extension to update..
+ * @param a_fUnusableIsError If @c true, report an unusable extension pack
+ * as an error.
+ * @param a_ppExtPack Where to store the pointer to the extension
+ * pack of it is still around after the refresh.
+ * This is optional.
+ *
+ * @remarks Caller holds the extension manager lock.
+ * @remarks Only called in VBoxSVC.
+ */
+HRESULT ExtPackManager::i_refreshExtPack(const char *a_pszName, bool a_fUnusableIsError, ExtPack **a_ppExtPack)
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ HRESULT hrc;
+ ExtPack *pExtPack = i_findExtPack(a_pszName);
+ if (pExtPack)
+ {
+ /*
+ * Refresh existing object.
+ */
+ bool fCanDelete;
+ hrc = pExtPack->i_refresh(&fCanDelete);
+ if (SUCCEEDED(hrc))
+ {
+ if (fCanDelete)
+ {
+ i_removeExtPack(a_pszName);
+ pExtPack = NULL;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Do this check here, otherwise VBoxExtPackCalcDir() will fail with a strange
+ * error.
+ */
+ bool fValid = VBoxExtPackIsValidName(a_pszName);
+ if (!fValid)
+ return setError(E_FAIL, "Invalid extension pack name specified");
+
+ /*
+ * Does the dir exist? Make some special effort to deal with case
+ * sensitivie file systems (a_pszName is case insensitive and mangled).
+ */
+ char szDir[RTPATH_MAX];
+ int vrc = VBoxExtPackCalcDir(szDir, sizeof(szDir), m->strBaseDir.c_str(), a_pszName);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+ RTDIRENTRYEX Entry;
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(szDir, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ bool fExists = RT_SUCCESS(vrc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode);
+ if (!fExists)
+ {
+ RTDIR hDir;
+ vrc = RTDirOpen(&hDir, m->strBaseDir.c_str());
+ if (RT_SUCCESS(vrc))
+ {
+ const char *pszMangledName = RTPathFilename(szDir);
+ for (;;)
+ {
+ vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ {
+ AssertLogRelMsg(vrc == VERR_NO_MORE_FILES, ("%Rrc\n", vrc));
+ break;
+ }
+ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
+ && !RTStrICmp(Entry.szName, pszMangledName))
+ {
+ /*
+ * The installed extension pack has a uses different case.
+ * Update the name and directory variables.
+ */
+ vrc = RTPathJoin(szDir, sizeof(szDir), m->strBaseDir.c_str(), Entry.szName); /* not really necessary */
+ AssertLogRelRCReturnStmt(vrc, RTDirClose(hDir), E_UNEXPECTED);
+ a_pszName = Entry.szName;
+ fExists = true;
+ break;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ }
+ if (fExists)
+ {
+ /*
+ * We've got something, create a new extension pack object for it.
+ */
+ ComObjPtr<ExtPack> ptrNewExtPack;
+ hrc = ptrNewExtPack.createObject();
+ if (SUCCEEDED(hrc))
+ hrc = ptrNewExtPack->initWithDir(m->pVirtualBox, m->enmContext, a_pszName, szDir);
+ if (SUCCEEDED(hrc))
+ {
+ m->llInstalledExtPacks.push_back(ptrNewExtPack);
+ m->cUpdate++;
+ if (ptrNewExtPack->m->fUsable)
+ LogRel(("ExtPackManager: Found extension pack '%s'.\n", a_pszName));
+ else
+ LogRel(("ExtPackManager: Found bad extension pack '%s': %s\n",
+ a_pszName, ptrNewExtPack->m->strWhyUnusable.c_str() ));
+ pExtPack = ptrNewExtPack;
+ }
+ }
+ else
+ hrc = S_OK;
+ }
+
+ /*
+ * Report error if not usable, if that is desired.
+ */
+ if ( SUCCEEDED(hrc)
+ && pExtPack
+ && a_fUnusableIsError
+ && !pExtPack->m->fUsable)
+ hrc = setError(E_FAIL, "%s", pExtPack->m->strWhyUnusable.c_str());
+
+ if (a_ppExtPack)
+ *a_ppExtPack = pExtPack;
+ return hrc;
+}
+
+/**
+ * Checks if there are any running VMs.
+ *
+ * This is called when uninstalling or replacing an extension pack.
+ *
+ * @returns true / false
+ */
+bool ExtPackManager::i_areThereAnyRunningVMs(void) const
+{
+ Assert(m->pVirtualBox != NULL); /* Only called from VBoxSVC. */
+
+ /*
+ * Get list of machines and their states.
+ */
+ com::SafeIfaceArray<IMachine> SaMachines;
+ HRESULT hrc = m->pVirtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(SaMachines));
+ if (SUCCEEDED(hrc))
+ {
+ com::SafeArray<MachineState_T> SaStates;
+ hrc = m->pVirtualBox->GetMachineStates(ComSafeArrayAsInParam(SaMachines), ComSafeArrayAsOutParam(SaStates));
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Scan the two parallel arrays for machines in the running state.
+ */
+ Assert(SaStates.size() == SaMachines.size());
+ for (size_t i = 0; i < SaMachines.size(); ++i)
+ if (SaMachines[i] && Global::IsOnline(SaStates[i]))
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Worker for IExtPackFile::Install.
+ *
+ * Called on a worker thread via doInstallThreadProc.
+ *
+ * @returns COM status code.
+ * @param a_pExtPackFile The extension pack file, caller checks that
+ * it's usable.
+ * @param a_fReplace Whether to replace any existing extpack or just
+ * fail.
+ * @param a_pstrDisplayInfo Host specific display information hacks.
+ * be NULL.
+ */
+HRESULT ExtPackManager::i_doInstall(ExtPackFile *a_pExtPackFile, bool a_fReplace, Utf8Str const *a_pstrDisplayInfo)
+{
+ AssertReturn(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON, E_UNEXPECTED);
+ RTCString const * const pStrName = &a_pExtPackFile->m->Desc.strName;
+ RTCString const * const pStrTarball = &a_pExtPackFile->m->strExtPackFile;
+ RTCString const * const pStrTarballDigest = &a_pExtPackFile->m->strDigest;
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Refresh the data we have on the extension pack as it
+ * may be made stale by direct meddling or some other user.
+ */
+ ExtPack *pExtPack;
+ hrc = i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (pExtPack && a_fReplace)
+ {
+ /* We must leave the lock when calling i_areThereAnyRunningVMs,
+ which means we have to redo the refresh call afterwards. */
+ autoLock.release();
+ bool fRunningVMs = i_areThereAnyRunningVMs();
+ bool fVetoingCP = pExtPack->i_areThereCloudProviderUninstallVetos();
+ bool fUnloadedCryptoMod = m->pVirtualBox->i_unloadCryptoIfModule() == S_OK;
+ autoLock.acquire();
+ hrc = i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (fRunningVMs)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because at least one VM is still running.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because at least one VM is still running"),
+ pStrName->c_str());
+ }
+ else if (fVetoingCP)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because at least one Cloud Provider is still busy.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because at least one Cloud Provider is still busy"),
+ pStrName->c_str());
+ }
+ else if (!fUnloadedCryptoMod)
+ {
+ LogRel(("Upgrading extension pack '%s' failed because the cryptographic support module is still in use.", pStrName->c_str()));
+ hrc = setError(E_FAIL, tr("Upgrading extension pack '%s' failed because the cryptographic support module is still in use"),
+ pStrName->c_str());
+ }
+ else if (SUCCEEDED(hrc) && pExtPack)
+ hrc = pExtPack->i_callUninstallHookAndClose(m->pVirtualBox, false /*a_ForcedRemoval*/);
+ }
+ else if (pExtPack)
+ hrc = setError(E_FAIL,
+ tr("Extension pack '%s' is already installed."
+ " In case of a reinstallation, please uninstall it first"),
+ pStrName->c_str());
+ }
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the privileged helper binary that performs the actual
+ * installation. Then create an object for the packet (we do this
+ * even on failure, to be on the safe side).
+ */
+ hrc = i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "install",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--cert-dir", m->strCertificatDirPath.c_str(),
+ "--name", pStrName->c_str(),
+ "--tarball", pStrTarball->c_str(),
+ "--sha-256", pStrTarballDigest->c_str(),
+ pExtPack ? "--replace" : (const char *)NULL,
+ (const char *)NULL);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_refreshExtPack(pStrName->c_str(), true /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc) && pExtPack)
+ {
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+ pExtPack->i_callInstalledHook(m->pVirtualBox, &autoLock, &ErrInfo.Core);
+ if (RT_SUCCESS(ErrInfo.Core.rc))
+ LogRel(("ExtPackManager: Successfully installed extension pack '%s'.\n", pStrName->c_str()));
+ else
+ {
+ LogRel(("ExtPackManager: Installed hook for '%s' failed: %Rrc - %s\n",
+ pStrName->c_str(), ErrInfo.Core.rc, ErrInfo.Core.pszMsg));
+
+ /*
+ * Uninstall the extpack if the error indicates that.
+ */
+ if (ErrInfo.Core.rc == VERR_EXTPACK_UNSUPPORTED_HOST_UNINSTALL)
+ i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "uninstall",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--name", pStrName->c_str(),
+ "--forced",
+ (const char *)NULL);
+ hrc = setErrorBoth(E_FAIL, ErrInfo.Core.rc, tr("The installation hook failed: %Rrc - %s"),
+ ErrInfo.Core.rc, ErrInfo.Core.pszMsg);
+ }
+ }
+ else if (SUCCEEDED(hrc))
+ hrc = setError(E_FAIL, tr("Installing extension pack '%s' failed under mysterious circumstances"),
+ pStrName->c_str());
+ }
+ else
+ {
+ ErrorInfoKeeper Eik;
+ i_refreshExtPack(pStrName->c_str(), false /*a_fUnusableIsError*/, NULL);
+ }
+ }
+
+ /*
+ * Do VirtualBoxReady callbacks now for any freshly installed
+ * extension pack (old ones will not be called).
+ */
+ if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
+ {
+ autoLock.release();
+ i_callAllVirtualBoxReadyHooks();
+ }
+ }
+
+ return hrc;
+}
+
+/**
+ * Worker for IExtPackManager::Uninstall.
+ *
+ * Called on a worker thread via doUninstallThreadProc.
+ *
+ * @returns COM status code.
+ * @param a_pstrName The name of the extension pack to uninstall.
+ * @param a_fForcedRemoval Whether to be skip and ignore certain bits of
+ * the extpack feedback. To deal with misbehaving
+ * extension pack hooks.
+ * @param a_pstrDisplayInfo Host specific display information hacks.
+ */
+HRESULT ExtPackManager::i_doUninstall(Utf8Str const *a_pstrName, bool a_fForcedRemoval, Utf8Str const *a_pstrDisplayInfo)
+{
+ Assert(m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON);
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ /*
+ * Refresh the data we have on the extension pack as it
+ * may be made stale by direct meddling or some other user.
+ */
+ ExtPack *pExtPack;
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc) && pExtPack)
+ {
+ /* We must leave the lock when calling i_areThereAnyRunningVMs,
+ which means we have to redo the refresh call afterwards. */
+ autoLock.release();
+ bool fRunningVMs = i_areThereAnyRunningVMs();
+ bool fVetoingCP = pExtPack->i_areThereCloudProviderUninstallVetos();
+ bool fUnloadedCryptoMod = m->pVirtualBox->i_unloadCryptoIfModule() == S_OK;
+ autoLock.acquire();
+ if (a_fForcedRemoval || (!fRunningVMs && !fVetoingCP && fUnloadedCryptoMod))
+ {
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (!pExtPack)
+ {
+ LogRel(("ExtPackManager: Extension pack '%s' is not installed, so nothing to uninstall.\n", a_pstrName->c_str()));
+ hrc = S_OK; /* nothing to uninstall */
+ }
+ else
+ {
+ /*
+ * Call the uninstall hook and unload the main dll.
+ */
+ hrc = pExtPack->i_callUninstallHookAndClose(m->pVirtualBox, a_fForcedRemoval);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Run the set-uid-to-root binary that performs the
+ * uninstallation. Then refresh the object.
+ *
+ * This refresh is theorically subject to races, but it's of
+ * the don't-do-that variety.
+ */
+ const char *pszForcedOpt = a_fForcedRemoval ? "--forced" : NULL;
+ hrc = i_runSetUidToRootHelper(a_pstrDisplayInfo,
+ "uninstall",
+ "--base-dir", m->strBaseDir.c_str(),
+ "--name", a_pstrName->c_str(),
+ pszForcedOpt, /* Last as it may be NULL. */
+ (const char *)NULL);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, &pExtPack);
+ if (SUCCEEDED(hrc))
+ {
+ if (!pExtPack)
+ LogRel(("ExtPackManager: Successfully uninstalled extension pack '%s'.\n", a_pstrName->c_str()));
+ else
+ hrc = setError(E_FAIL,
+ tr("Uninstall extension pack '%s' failed under mysterious circumstances"),
+ a_pstrName->c_str());
+ }
+ }
+ else
+ {
+ ErrorInfoKeeper Eik;
+ i_refreshExtPack(a_pstrName->c_str(), false /*a_fUnusableIsError*/, NULL);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if (fRunningVMs)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because at least one VM is still running.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because at least one VM is still running"),
+ a_pstrName->c_str());
+ }
+ else if (fVetoingCP)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because at least one Cloud Provider is still busy.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because at least one Cloud Provider is still busy"),
+ a_pstrName->c_str());
+ }
+ else if (!fUnloadedCryptoMod)
+ {
+ LogRel(("Uninstall extension pack '%s' failed because the cryptographic support module is still in use.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed because the cryptographic support module is still in use"),
+ a_pstrName->c_str());
+ }
+ else
+ {
+ LogRel(("Uninstall extension pack '%s' failed for an unknown reason.", a_pstrName->c_str()));
+ hrc = setError(E_FAIL, tr("Uninstall extension pack '%s' failed for an unknown reason"),
+ a_pstrName->c_str());
+
+ }
+ }
+ }
+ else if (SUCCEEDED(hrc) && !pExtPack)
+ {
+ hrc = setError(E_FAIL, tr("Extension pack '%s' is not installed.\n"), a_pstrName->c_str());
+ }
+
+ /*
+ * Do VirtualBoxReady callbacks now for any freshly installed
+ * extension pack (old ones will not be called).
+ */
+ if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
+ {
+ autoLock.release();
+ i_callAllVirtualBoxReadyHooks();
+ }
+ }
+
+ return hrc;
+}
+
+
+/**
+ * Calls the pfnVirtualBoxReady hook for all working extension packs.
+ *
+ * @remarks The caller must not hold any locks.
+ */
+void ExtPackManager::i_callAllVirtualBoxReadyHooks(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ /* advancing below */)
+ {
+ if ((*it)->i_callVirtualBoxReadyHook(m->pVirtualBox, &autoLock))
+ it = m->llInstalledExtPacks.begin();
+ else
+ ++it;
+ }
+}
+
+
+/**
+ * Queries objects of type @a aObjUuid from all the extension packs.
+ *
+ * @returns COM status code.
+ * @param aObjUuid The UUID of the kind of objects we're querying.
+ * @param aObjects Where to return the objects.
+ * @param a_pstrExtPackNames Where to return the corresponding extpack names (may be NULL).
+ *
+ * @remarks The caller must not hold any locks.
+ */
+HRESULT ExtPackManager::i_queryObjects(const com::Utf8Str &aObjUuid, std::vector<ComPtr<IUnknown> > &aObjects, std::vector<com::Utf8Str> *a_pstrExtPackNames)
+{
+ aObjects.clear();
+ if (a_pstrExtPackNames)
+ a_pstrExtPackNames->clear();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ComPtr<IUnknown> ptrIf;
+ HRESULT hrc2 = (*it)->queryObject(aObjUuid, ptrIf);
+ if (SUCCEEDED(hrc2))
+ {
+ aObjects.push_back(ptrIf);
+ if (a_pstrExtPackNames)
+ a_pstrExtPackNames->push_back((*it)->m->Desc.strName);
+ }
+ else if (hrc2 != E_NOINTERFACE)
+ hrc = hrc2;
+ }
+
+ if (aObjects.size() > 0)
+ hrc = S_OK;
+ }
+ return hrc;
+}
+
+#endif /* !VBOX_COM_INPROC */
+
+#ifdef VBOX_COM_INPROC
+/**
+ * Calls the pfnConsoleReady hook for all working extension packs.
+ *
+ * @param a_pConsole The console interface.
+ * @remarks The caller must not hold any locks.
+ */
+void ExtPackManager::i_callAllConsoleReadyHooks(IConsole *a_pConsole)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this;
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ /* advancing below */)
+ {
+ if ((*it)->i_callConsoleReadyHook(a_pConsole, &autoLock))
+ it = m->llInstalledExtPacks.begin();
+ else
+ ++it;
+ }
+}
+#endif
+
+#ifndef VBOX_COM_INPROC
+/**
+ * Calls the pfnVMCreated hook for all working extension packs.
+ *
+ * @param a_pMachine The machine interface of the new VM.
+ */
+void ExtPackManager::i_callAllVmCreatedHooks(IMachine *a_pMachine)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ (*it)->i_callVmCreatedHook(m->pVirtualBox, a_pMachine, &autoLock);
+}
+#endif
+
+#ifdef VBOX_COM_INPROC
+
+/**
+ * Calls the pfnVMConfigureVMM hook for all working extension packs.
+ *
+ * @returns VBox status code. Stops on the first failure, expecting the caller
+ * to signal this to the caller of the CFGM constructor.
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ */
+int ExtPackManager::i_callAllVmConfigureVmmHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ {
+ int vrc;
+ (*it)->i_callVmConfigureVmmHook(a_pConsole, a_pVM, a_pVMM, &autoLock, &vrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the pfnVMPowerOn hook for all working extension packs.
+ *
+ * @returns VBox status code. Stops on the first failure, expecting the caller
+ * to not power on the VM.
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle.
+ * @param a_pVMM The VMM function table.
+ */
+int ExtPackManager::i_callAllVmPowerOnHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return Global::vboxStatusCodeFromCOM(hrc);
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ {
+ int vrc;
+ (*it)->i_callVmPowerOnHook(a_pConsole, a_pVM, a_pVMM, &autoLock, &vrc);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the pfnVMPowerOff hook for all working extension packs.
+ *
+ * @param a_pConsole The console interface for the VM.
+ * @param a_pVM The VM handle. Can be NULL.
+ * @param a_pVMM The VMM function table.
+ */
+void ExtPackManager::i_callAllVmPowerOffHooks(IConsole *a_pConsole, PVM a_pVM, PCVMMR3VTABLE a_pVMM)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return;
+ AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ ComPtr<ExtPackManager> ptrSelfRef = this; /* paranoia */
+ ExtPackList llExtPacks = m->llInstalledExtPacks;
+
+ for (ExtPackList::iterator it = llExtPacks.begin(); it != llExtPacks.end(); ++it)
+ (*it)->i_callVmPowerOffHook(a_pConsole, a_pVM, a_pVMM, &autoLock);
+}
+
+#endif /* VBOX_COM_INPROC */
+
+/**
+ * Checks that the specified extension pack contains a VRDE module and that it
+ * is shipshape.
+ *
+ * @returns S_OK if ok, appropriate failure status code with details.
+ * @param a_pstrExtPack The name of the extension pack.
+ */
+HRESULT ExtPackManager::i_checkVrdeExtPack(Utf8Str const *a_pstrExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_checkVrde();
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pstrExtPack->c_str());
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the full path to the VRDE library of the specified extension pack.
+ *
+ * This will do extacly the same as checkVrdeExtPack and then resolve the
+ * library path.
+ *
+ * @returns VINF_SUCCESS if a path is returned, VBox error status and message
+ * return if not.
+ * @param a_pstrExtPack The extension pack.
+ * @param a_pstrVrdeLibrary Where to return the path.
+ */
+int ExtPackManager::i_getVrdeLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrVrdeLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_getVrdpLibraryName(a_pstrVrdeLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"),
+ a_pstrExtPack->c_str());
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+}
+
+/**
+ * Checks that the specified extension pack contains a cryptographic module and that it
+ * is shipshape.
+ *
+ * @returns S_OK if ok, appropriate failure status code with details.
+ * @param a_pstrExtPack The name of the extension pack.
+ */
+HRESULT ExtPackManager::i_checkCryptoExtPack(Utf8Str const *a_pstrExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_checkCrypto();
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pstrExtPack->c_str());
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the full path to the cryptographic library of the specified extension pack.
+ *
+ * This will do extacly the same as checkCryptoExtPack and then resolve the
+ * library path.
+ *
+ * @returns VINF_SUCCESS if a path is returned, VBox error status and message
+ * return if not.
+ * @param a_pstrExtPack The extension pack.
+ * @param a_pstrCryptoLibrary Where to return the path.
+ */
+int ExtPackManager::i_getCryptoLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrCryptoLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pstrExtPack->c_str());
+ if (pExtPack)
+ hrc = pExtPack->i_getCryptoLibraryName(a_pstrCryptoLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"),
+ a_pstrExtPack->c_str());
+ }
+
+ return Global::vboxStatusCodeFromCOM(hrc);
+}
+
+
+/**
+ * Gets the full path to the specified library of the specified extension pack.
+ *
+ * @returns S_OK if a path is returned, COM error status and message return if
+ * not.
+ * @param a_pszModuleName The library.
+ * @param a_pszExtPack The extension pack.
+ * @param a_pstrLibrary Where to return the path.
+ */
+HRESULT ExtPackManager::i_getLibraryPathForExtPack(const char *a_pszModuleName, const char *a_pszExtPack, Utf8Str *a_pstrLibrary)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pszExtPack);
+ if (pExtPack)
+ hrc = pExtPack->i_getLibraryName(a_pszModuleName, a_pstrLibrary);
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("No extension pack by the name '%s' was found"), a_pszExtPack);
+ }
+
+ return hrc;
+}
+
+/**
+ * Gets the name of the default VRDE extension pack.
+ *
+ * @returns S_OK or some COM error status on red tape failure.
+ * @param a_pstrExtPack Where to return the extension pack name. Returns
+ * empty if no extension pack wishes to be the default
+ * VRDP provider.
+ */
+HRESULT ExtPackManager::i_getDefaultVrdeExtPack(Utf8Str *a_pstrExtPack)
+{
+ a_pstrExtPack->setNull();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ if ((*it)->i_wantsToBeDefaultVrde())
+ {
+ *a_pstrExtPack = (*it)->m->Desc.strName;
+ break;
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Gets the name of the default cryptographic extension pack.
+ *
+ * @returns S_OK or some COM error status on red tape failure.
+ * @param a_pstrExtPack Where to return the extension pack name. Returns
+ * empty if no extension pack wishes to be the default
+ * VRDP provider.
+ */
+HRESULT ExtPackManager::i_getDefaultCryptoExtPack(Utf8Str *a_pstrExtPack)
+{
+ a_pstrExtPack->setNull();
+
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (SUCCEEDED(hrc))
+ {
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ if ((*it)->i_wantsToBeDefaultCrypto())
+ {
+ *a_pstrExtPack = (*it)->m->Desc.strName;
+ break;
+ }
+ }
+ }
+ return hrc;
+}
+
+/**
+ * Checks if an extension pack is (present and) usable.
+ *
+ * @returns @c true if it is, otherwise @c false.
+ * @param a_pszExtPack The name of the extension pack.
+ */
+bool ExtPackManager::i_isExtPackUsable(const char *a_pszExtPack)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return false;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ ExtPack *pExtPack = i_findExtPack(a_pszExtPack);
+ return pExtPack != NULL
+ && pExtPack->m->fUsable;
+}
+
+/**
+ * Dumps all extension packs to the release log.
+ */
+void ExtPackManager::i_dumpAllToReleaseLog(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogRel(("Installed Extension Packs:\n"));
+ for (ExtPackList::iterator it = m->llInstalledExtPacks.begin();
+ it != m->llInstalledExtPacks.end();
+ ++it)
+ {
+ ExtPack::Data *pExtPackData = (*it)->m;
+ if (pExtPackData)
+ {
+ if (pExtPackData->fUsable)
+ LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s; Crypto Module: %s)\n",
+ pExtPackData->Desc.strName.c_str(),
+ pExtPackData->Desc.strVersion.c_str(),
+ pExtPackData->Desc.uRevision,
+ pExtPackData->Desc.strEdition.isEmpty() ? "" : " ",
+ pExtPackData->Desc.strEdition.c_str(),
+ pExtPackData->Desc.strVrdeModule.c_str(),
+ pExtPackData->Desc.strCryptoModule.c_str() ));
+ else
+ LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s; Crypto Module: %s unusable because of '%s')\n",
+ pExtPackData->Desc.strName.c_str(),
+ pExtPackData->Desc.strVersion.c_str(),
+ pExtPackData->Desc.uRevision,
+ pExtPackData->Desc.strEdition.isEmpty() ? "" : " ",
+ pExtPackData->Desc.strEdition.c_str(),
+ pExtPackData->Desc.strVrdeModule.c_str(),
+ pExtPackData->Desc.strCryptoModule.c_str(),
+ pExtPackData->strWhyUnusable.c_str() ));
+ }
+ else
+ LogRel((" pExtPackData is NULL\n"));
+ }
+
+ if (!m->llInstalledExtPacks.size())
+ LogRel((" None installed!\n"));
+}
+
+/**
+ * Gets the update counter (reflecting extpack list updates).
+ */
+uint64_t ExtPackManager::i_getUpdateCounter(void)
+{
+ AutoCaller autoCaller(this);
+ HRESULT hrc = autoCaller.hrc();
+ if (FAILED(hrc))
+ return 0;
+ AutoReadLock autoLock(this COMMA_LOCKVAL_SRC_POS);
+ return m->cUpdate;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/ExtPackUtil.cpp b/src/VBox/Main/src-all/ExtPackUtil.cpp
new file mode 100644
index 00000000..ce183801
--- /dev/null
+++ b/src/VBox/Main/src-all/ExtPackUtil.cpp
@@ -0,0 +1,1473 @@
+/* $Id: ExtPackUtil.cpp $ */
+/** @file
+ * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#include "../include/ExtPackUtil.h"
+
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/manifest.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/xml.h>
+
+#include <VBox/log.h>
+
+#include "../include/VBoxNls.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+DECLARE_TRANSLATION_CONTEXT(ExtPackUtil);
+
+
+/*********************************************************************************************************************************
+* Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for VBoxExtPackLoadDesc that loads the plug-in descriptors.
+ *
+ * @returns Same as VBoxExtPackLoadDesc.
+ * @param pVBoxExtPackElm
+ * @param pcPlugIns Where to return the number of plug-ins in the
+ * array.
+ * @param paPlugIns Where to return the plug-in descriptor array.
+ * (RTMemFree it even on failure)
+ */
+static RTCString *
+vboxExtPackLoadPlugInDescs(const xml::ElementNode *pVBoxExtPackElm,
+ uint32_t *pcPlugIns, PVBOXEXTPACKPLUGINDESC *paPlugIns)
+{
+ *pcPlugIns = 0;
+ *paPlugIns = NULL;
+
+ /** @todo plug-ins */
+ NOREF(pVBoxExtPackElm);
+
+ return NULL;
+}
+
+
+/**
+ * Clears the extension pack descriptor.
+ *
+ * @param a_pExtPackDesc The descriptor to clear.
+ */
+static void vboxExtPackClearDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ a_pExtPackDesc->strName.setNull();
+ a_pExtPackDesc->strDescription.setNull();
+ a_pExtPackDesc->strVersion.setNull();
+ a_pExtPackDesc->strEdition.setNull();
+ a_pExtPackDesc->uRevision = 0;
+ a_pExtPackDesc->strMainModule.setNull();
+ a_pExtPackDesc->strMainVMModule.setNull();
+ a_pExtPackDesc->strVrdeModule.setNull();
+ a_pExtPackDesc->strCryptoModule.setNull();
+ a_pExtPackDesc->cPlugIns = 0;
+ a_pExtPackDesc->paPlugIns = NULL;
+ a_pExtPackDesc->fShowLicense = false;
+}
+
+
+/**
+ * Initializes an extension pack descriptor so that it's safe to call free on
+ * it whatever happens later on.
+ *
+ * @param a_pExtPackDesc The descirptor to initialize.
+ */
+void VBoxExtPackInitDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+}
+
+
+/**
+ * Load the extension pack descriptor from an XML document.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param a_pDoc Pointer to the XML document.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ */
+static RTCString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ /*
+ * Get the main element and check its version.
+ */
+ const xml::ElementNode *pVBoxExtPackElm = a_pDoc->getRootElement();
+ if ( !pVBoxExtPackElm
+ || strcmp(pVBoxExtPackElm->getName(), "VirtualBoxExtensionPack") != 0)
+ return new RTCString(ExtPackUtil::tr("No VirtualBoxExtensionPack element"));
+
+ RTCString strFormatVersion;
+ if (!pVBoxExtPackElm->getAttributeValueN("version", strFormatVersion, RT_XML_ATTR_TINY))
+ return new RTCString(ExtPackUtil::tr("Missing format version"));
+ if (!strFormatVersion.equals("1.0"))
+ return &(new RTCString(ExtPackUtil::tr("Unsupported format version: ")))->append(strFormatVersion);
+
+ /*
+ * Read and validate mandatory bits.
+ */
+ const xml::ElementNode *pNameElm = pVBoxExtPackElm->findChildElement("Name");
+ if (!pNameElm)
+ return new RTCString(ExtPackUtil::tr("The 'Name' element is missing"));
+ const char *pszName = pNameElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!VBoxExtPackIsValidName(pszName))
+ return &(new RTCString(ExtPackUtil::tr("Invalid name: ")))->append(pszName);
+
+ const xml::ElementNode *pDescElm = pVBoxExtPackElm->findChildElement("Description");
+ if (!pDescElm)
+ return new RTCString(ExtPackUtil::tr("The 'Description' element is missing"));
+ const char *pszDesc = pDescElm->getValueN(RT_XML_CONTENT_LARGE);
+ if (!pszDesc || *pszDesc == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'Description' element is empty"));
+ if (strpbrk(pszDesc, "\n\r\t\v\b") != NULL)
+ return new RTCString(ExtPackUtil::tr("The 'Description' must not contain control characters"));
+
+ const xml::ElementNode *pVersionElm = pVBoxExtPackElm->findChildElement("Version");
+ if (!pVersionElm)
+ return new RTCString(ExtPackUtil::tr("The 'Version' element is missing"));
+ const char *pszVersion = pVersionElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszVersion || *pszVersion == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'Version' element is empty"));
+ if (!VBoxExtPackIsValidVersionString(pszVersion))
+ return &(new RTCString(ExtPackUtil::tr("Invalid version string: ")))->append(pszVersion);
+
+ uint32_t uRevision;
+ if (!pVersionElm->getAttributeValue("revision", uRevision))
+ uRevision = 0;
+
+ const char *pszEdition;
+ if (!pVersionElm->getAttributeValueN("edition", pszEdition, RT_XML_ATTR_TINY))
+ pszEdition = "";
+ if (!VBoxExtPackIsValidEditionString(pszEdition))
+ return &(new RTCString(ExtPackUtil::tr("Invalid edition string: ")))->append(pszEdition);
+
+ const xml::ElementNode *pMainModuleElm = pVBoxExtPackElm->findChildElement("MainModule");
+ if (!pMainModuleElm)
+ return new RTCString(ExtPackUtil::tr("The 'MainModule' element is missing"));
+ const char *pszMainModule = pMainModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszMainModule || *pszMainModule == '\0')
+ return new RTCString(ExtPackUtil::tr("The 'MainModule' element is empty"));
+ if (!VBoxExtPackIsValidModuleString(pszMainModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid main module string: ")))->append(pszMainModule);
+
+ /*
+ * The main VM module, optional.
+ * Accept both none and empty as tokens of no main VM module.
+ */
+ const char *pszMainVMModule = NULL;
+ const xml::ElementNode *pMainVMModuleElm = pVBoxExtPackElm->findChildElement("MainVMModule");
+ if (pMainVMModuleElm)
+ {
+ pszMainVMModule = pMainVMModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszMainVMModule || *pszMainVMModule == '\0')
+ pszMainVMModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszMainVMModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid main VM module string: ")))->append(pszMainVMModule);
+ }
+
+ /*
+ * The VRDE module, optional.
+ * Accept both none and empty as tokens of no VRDE module.
+ */
+ const char *pszVrdeModule = NULL;
+ const xml::ElementNode *pVrdeModuleElm = pVBoxExtPackElm->findChildElement("VRDEModule");
+ if (pVrdeModuleElm)
+ {
+ pszVrdeModule = pVrdeModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszVrdeModule || *pszVrdeModule == '\0')
+ pszVrdeModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid VRDE module string: ")))->append(pszVrdeModule);
+ }
+
+ /*
+ * The cryptographic module, optional.
+ * Accept both none and empty as tokens of no cryptographic module.
+ */
+ const char *pszCryptoModule = NULL;
+ const xml::ElementNode *pCryptoModuleElm = pVBoxExtPackElm->findChildElement("CryptoModule");
+ if (pCryptoModuleElm)
+ {
+ pszCryptoModule = pCryptoModuleElm->getValueN(RT_XML_CONTENT_SMALL);
+ if (!pszCryptoModule || *pszCryptoModule == '\0')
+ pszCryptoModule = NULL;
+ else if (!VBoxExtPackIsValidModuleString(pszCryptoModule))
+ return &(new RTCString(ExtPackUtil::tr("Invalid cryptographic module string: ")))->append(pszCryptoModule);
+ }
+
+ /*
+ * Whether to show the license, optional. (presense is enough here)
+ */
+ const xml::ElementNode *pShowLicenseElm = pVBoxExtPackElm->findChildElement("ShowLicense");
+ bool fShowLicense = pShowLicenseElm != NULL;
+
+ /*
+ * Parse plug-in descriptions (last because of the manual memory management).
+ */
+ uint32_t cPlugIns = 0;
+ PVBOXEXTPACKPLUGINDESC paPlugIns = NULL;
+ RTCString *pstrRet = vboxExtPackLoadPlugInDescs(pVBoxExtPackElm, &cPlugIns, &paPlugIns);
+ if (pstrRet)
+ {
+ RTMemFree(paPlugIns);
+ return pstrRet;
+ }
+
+ /*
+ * Everything seems fine, fill in the return values and return successfully.
+ */
+ a_pExtPackDesc->strName = pszName;
+ a_pExtPackDesc->strDescription = pszDesc;
+ a_pExtPackDesc->strVersion = pszVersion;
+ a_pExtPackDesc->strEdition = pszEdition;
+ a_pExtPackDesc->uRevision = uRevision;
+ a_pExtPackDesc->strMainModule = pszMainModule;
+ a_pExtPackDesc->strMainVMModule = pszMainVMModule;
+ a_pExtPackDesc->strVrdeModule = pszVrdeModule;
+ a_pExtPackDesc->strCryptoModule = pszCryptoModule;
+ a_pExtPackDesc->cPlugIns = cPlugIns;
+ a_pExtPackDesc->paPlugIns = paPlugIns;
+ a_pExtPackDesc->fShowLicense = fShowLicense;
+
+ return NULL;
+}
+
+/**
+ * Reads the extension pack descriptor.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param a_pszDir The directory containing the description file.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ * @param a_pObjInfo Where to store the object info for the file (unix
+ * attribs). Optional.
+ */
+RTCString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+
+ /*
+ * Validate, open and parse the XML file.
+ */
+ char szFilePath[RTPATH_MAX];
+ int vrc = RTPathJoin(szFilePath, sizeof(szFilePath), a_pszDir, VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (RT_FAILURE(vrc))
+ return new RTCStringFmt(ExtPackUtil::tr("RTPathJoin failed with %Rrc"), vrc);
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(szFilePath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(vrc))
+ return new RTCStringFmt(ExtPackUtil::tr("RTPathQueryInfoEx failed with %Rrc"), vrc);
+ if (a_pObjInfo)
+ *a_pObjInfo = ObjInfo;
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ return new RTCString(ExtPackUtil::tr("The XML file is symlinked, that is not allowed"));
+ return new RTCStringFmt(ExtPackUtil::tr("The XML file is not a file (fMode=%#x)"), ObjInfo.Attr.fMode);
+ }
+
+ xml::Document Doc;
+ {
+ xml::XmlFileParser Parser;
+ try
+ {
+ Parser.read(szFilePath, Doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ return new RTCString(rErr.what());
+ }
+ }
+
+ /*
+ * Hand the xml doc over to the common code.
+ */
+ try
+ {
+ return vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ return new RTCString(rXcpt.what());
+ }
+}
+
+/**
+ * Reads the extension pack descriptor.
+ *
+ * @returns NULL on success, pointer to an error message on failure (caller
+ * deletes it).
+ * @param hVfsFile The file handle of the description file.
+ * @param a_pExtPackDesc Where to store the extension pack descriptor.
+ * @param a_pObjInfo Where to store the object info for the file (unix
+ * attribs). Optional.
+ */
+RTCString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
+{
+ vboxExtPackClearDesc(a_pExtPackDesc);
+
+ /*
+ * Query the object info.
+ */
+ RTFSOBJINFO ObjInfo;
+ int vrc = RTVfsFileQueryInfo(hVfsFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(vrc))
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileQueryInfo failed: %Rrc"), vrc);
+ if (a_pObjInfo)
+ *a_pObjInfo = ObjInfo;
+
+ /*
+ * The simple approach, read the whole thing into memory and pass this to
+ * the XML parser.
+ */
+
+ /* Check the file size. */
+ if (ObjInfo.cbObject > _1M || ObjInfo.cbObject < 0)
+ return &(new RTCString)->printf(ExtPackUtil::tr("The XML file is too large (%'RU64 bytes)", "", (size_t)ObjInfo.cbObject),
+ ObjInfo.cbObject);
+ size_t const cbFile = (size_t)ObjInfo.cbObject;
+
+ /* Rewind to the start of the file. */
+ vrc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileSeek(,0,BEGIN) failed: %Rrc"), vrc);
+
+ /* Allocate memory and read the file content into it. */
+ void *pvFile = RTMemTmpAlloc(cbFile);
+ if (!pvFile)
+ return &(new RTCString)->printf(ExtPackUtil::tr("RTMemTmpAlloc(%zu) failed"), cbFile);
+
+ RTCString *pstrErr = NULL;
+ vrc = RTVfsFileRead(hVfsFile, pvFile, cbFile, NULL);
+ if (RT_FAILURE(vrc))
+ pstrErr = &(new RTCString)->printf(ExtPackUtil::tr("RTVfsFileRead failed: %Rrc"), vrc);
+
+ /*
+ * Parse the file.
+ */
+ xml::Document Doc;
+ if (RT_SUCCESS(vrc))
+ {
+ xml::XmlMemParser Parser;
+ RTCString strFileName = VBOX_EXTPACK_DESCRIPTION_NAME;
+ try
+ {
+ Parser.read(pvFile, cbFile, strFileName, Doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ pstrErr = new RTCString(rErr.what());
+ vrc = VERR_PARSE_ERROR;
+ }
+ }
+ RTMemTmpFree(pvFile);
+
+ /*
+ * Hand the xml doc over to the common code.
+ */
+ if (RT_SUCCESS(vrc))
+ try
+ {
+ pstrErr = vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ return new RTCString(rXcpt.what());
+ }
+
+ return pstrErr;
+}
+
+/**
+ * Frees all resources associated with a extension pack descriptor.
+ *
+ * @param a_pExtPackDesc The extension pack descriptor which members
+ * should be freed.
+ */
+void VBoxExtPackFreeDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
+{
+ if (!a_pExtPackDesc)
+ return;
+
+ a_pExtPackDesc->strName.setNull();
+ a_pExtPackDesc->strDescription.setNull();
+ a_pExtPackDesc->strVersion.setNull();
+ a_pExtPackDesc->strEdition.setNull();
+ a_pExtPackDesc->uRevision = 0;
+ a_pExtPackDesc->strMainModule.setNull();
+ a_pExtPackDesc->strMainVMModule.setNull();
+ a_pExtPackDesc->strVrdeModule.setNull();
+ a_pExtPackDesc->strCryptoModule.setNull();
+ a_pExtPackDesc->cPlugIns = 0;
+ RTMemFree(a_pExtPackDesc->paPlugIns);
+ a_pExtPackDesc->paPlugIns = NULL;
+ a_pExtPackDesc->fShowLicense = false;
+}
+
+/**
+ * Extract the extension pack name from the tarball path.
+ *
+ * @returns String containing the name on success, the caller must delete it.
+ * NULL if no valid name was found or if we ran out of memory.
+ * @param pszTarball The path to the tarball.
+ */
+RTCString *VBoxExtPackExtractNameFromTarballPath(const char *pszTarball)
+{
+ /*
+ * Skip ahead to the filename part and count the number of characters
+ * that matches the criteria for a mangled extension pack name.
+ */
+ const char *pszSrc = RTPathFilename(pszTarball);
+ if (!pszSrc)
+ return NULL;
+
+ size_t off = 0;
+ while (RT_C_IS_ALNUM(pszSrc[off]) || pszSrc[off] == '_')
+ off++;
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return NULL;
+
+ /*
+ * Return the unmangled name.
+ */
+ return VBoxExtPackUnmangleName(pszSrc, off);
+}
+
+/**
+ * Validates the extension pack name.
+ *
+ * @returns true if valid, false if not.
+ * @param pszName The name to validate.
+ * @sa VBoxExtPackExtractNameFromTarballPath
+ */
+bool VBoxExtPackIsValidName(const char *pszName)
+{
+ if (!pszName)
+ return false;
+
+ /*
+ * Check the characters making up the name, only english alphabet
+ * characters, decimal digits and spaces are allowed.
+ */
+ size_t off = 0;
+ while (pszName[off])
+ {
+ if (!RT_C_IS_ALNUM(pszName[off]) && pszName[off] != ' ')
+ return false;
+ off++;
+ }
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return false;
+
+ return true;
+}
+
+/**
+ * Checks if an alledged manged extension pack name.
+ *
+ * @returns true if valid, false if not.
+ * @param pszMangledName The mangled name to validate.
+ * @param cchMax The max number of chars to test.
+ * @sa VBoxExtPackMangleName
+ */
+bool VBoxExtPackIsValidMangledName(const char *pszMangledName, size_t cchMax /*= RTSTR_MAX*/)
+{
+ if (!pszMangledName)
+ return false;
+
+ /*
+ * Check the characters making up the name, only english alphabet
+ * characters, decimal digits and underscores (=space) are allowed.
+ */
+ size_t off = 0;
+ while (off < cchMax && pszMangledName[off])
+ {
+ if (!RT_C_IS_ALNUM(pszMangledName[off]) && pszMangledName[off] != '_')
+ return false;
+ off++;
+ }
+
+ /*
+ * Check min and max name limits.
+ */
+ if ( off > VBOX_EXTPACK_NAME_MAX_LEN
+ || off < VBOX_EXTPACK_NAME_MIN_LEN)
+ return false;
+
+ return true;
+}
+
+/**
+ * Mangle an extension pack name so it can be used by a directory or file name.
+ *
+ * @returns String containing the mangled name on success, the caller must
+ * delete it. NULL on failure.
+ * @param pszName The unmangled name.
+ * @sa VBoxExtPackUnmangleName, VBoxExtPackIsValidMangledName
+ */
+RTCString *VBoxExtPackMangleName(const char *pszName)
+{
+ AssertReturn(VBoxExtPackIsValidName(pszName), NULL);
+
+ char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
+ size_t off = 0;
+ char ch;
+ while ((ch = pszName[off]) != '\0')
+ {
+ if (ch == ' ')
+ ch = '_';
+ szTmp[off++] = ch;
+ }
+ szTmp[off] = '\0';
+ Assert(VBoxExtPackIsValidMangledName(szTmp));
+
+ return new RTCString(szTmp, off);
+}
+
+/**
+ * Unmangle an extension pack name (reverses VBoxExtPackMangleName).
+ *
+ * @returns String containing the mangled name on success, the caller must
+ * delete it. NULL on failure.
+ * @param pszMangledName The mangled name.
+ * @param cchMax The max name length. RTSTR_MAX is fine.
+ * @sa VBoxExtPackMangleName, VBoxExtPackIsValidMangledName
+ */
+RTCString *VBoxExtPackUnmangleName(const char *pszMangledName, size_t cchMax)
+{
+ AssertReturn(VBoxExtPackIsValidMangledName(pszMangledName, cchMax), NULL);
+
+ char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
+ size_t off = 0;
+ char ch;
+ while ( off < cchMax
+ && (ch = pszMangledName[off]) != '\0')
+ {
+ if (ch == '_')
+ ch = ' ';
+ else
+ AssertReturn(RT_C_IS_ALNUM(ch) || ch == ' ', NULL);
+ szTmp[off++] = ch;
+ }
+ szTmp[off] = '\0';
+ AssertReturn(VBoxExtPackIsValidName(szTmp), NULL);
+
+ return new RTCString(szTmp, off);
+}
+
+/**
+ * Constructs the extension pack directory path.
+ *
+ * A combination of RTPathJoin and VBoxExtPackMangleName.
+ *
+ * @returns IPRT status code like RTPathJoin.
+ * @param pszExtPackDir Where to return the directory path.
+ * @param cbExtPackDir The size of the return buffer.
+ * @param pszParentDir The parent directory (".../Extensions").
+ * @param pszName The extension pack name, unmangled.
+ */
+int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName)
+{
+ AssertReturn(VBoxExtPackIsValidName(pszName), VERR_INTERNAL_ERROR_5);
+
+ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
+ if (!pstrMangledName)
+ return VERR_INTERNAL_ERROR_4;
+
+ int vrc = RTPathJoin(pszExtPackDir, cbExtPackDir, pszParentDir, pstrMangledName->c_str());
+ delete pstrMangledName;
+
+ return vrc;
+}
+
+
+/**
+ * Validates the extension pack version string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszVersion The version string to validate.
+ */
+bool VBoxExtPackIsValidVersionString(const char *pszVersion)
+{
+ if (!pszVersion || *pszVersion == '\0')
+ return false;
+
+ /* 1.x.y.z... */
+ for (;;)
+ {
+ if (!RT_C_IS_DIGIT(*pszVersion))
+ return false;
+ do
+ pszVersion++;
+ while (RT_C_IS_DIGIT(*pszVersion));
+ if (*pszVersion != '.')
+ break;
+ pszVersion++;
+ }
+
+ /* upper case string + numbers indicating the build type */
+ if (*pszVersion == '-' || *pszVersion == '_')
+ {
+ /** @todo Should probably restrict this to known build types (alpha,
+ * beta, release candidate, ++). */
+ do
+ pszVersion++;
+ while ( RT_C_IS_DIGIT(*pszVersion)
+ || RT_C_IS_UPPER(*pszVersion)
+ || *pszVersion == '-'
+ || *pszVersion == '_');
+ }
+
+ return *pszVersion == '\0';
+}
+
+/**
+ * Validates the extension pack edition string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszEdition The edition string to validate.
+ */
+bool VBoxExtPackIsValidEditionString(const char *pszEdition)
+{
+ if (*pszEdition)
+ {
+ if (!RT_C_IS_UPPER(*pszEdition))
+ return false;
+
+ do
+ pszEdition++;
+ while ( RT_C_IS_UPPER(*pszEdition)
+ || RT_C_IS_DIGIT(*pszEdition)
+ || *pszEdition == '-'
+ || *pszEdition == '_');
+ }
+ return *pszEdition == '\0';
+}
+
+/**
+ * Validates an extension pack module string.
+ *
+ * @returns true if valid, false if not.
+ * @param pszModule The module string to validate.
+ */
+bool VBoxExtPackIsValidModuleString(const char *pszModule)
+{
+ if (!pszModule || *pszModule == '\0')
+ return false;
+
+ /* Restricted charset, no extensions (dots). */
+ while ( RT_C_IS_ALNUM(*pszModule)
+ || *pszModule == '-'
+ || *pszModule == '_')
+ pszModule++;
+
+ return *pszModule == '\0';
+}
+
+/**
+ * RTStrPrintfv wrapper.
+ *
+ * @returns @a vrc
+ * @param vrc The status code to return.
+ * @param pszError The error buffer.
+ * @param cbError The size of the buffer.
+ * @param pszFormat The error message format string.
+ * @param ... Format arguments.
+ */
+static int vboxExtPackReturnError(int vrc, char *pszError, size_t cbError, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(pszError, cbError, pszFormat, va);
+ va_end(va);
+ return vrc;
+}
+
+/**
+ * RTStrPrintfv wrapper.
+ *
+ * @param pszError The error buffer.
+ * @param cbError The size of the buffer.
+ * @param pszFormat The error message format string.
+ * @param ... Format arguments.
+ */
+static void vboxExtPackSetError(char *pszError, size_t cbError, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(pszError, cbError, pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Verifies the manifest and its signature.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hXmlFile The xml from the extension pack.
+ * @param pszExtPackName The expected extension pack name. This can be
+ * NULL, in which we don't have any expectations.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackVerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName, char *pszError, size_t cbError)
+{
+ /*
+ * Load the XML.
+ */
+ VBOXEXTPACKDESC ExtPackDesc;
+ RTCString *pstrErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &ExtPackDesc, NULL);
+ if (pstrErr)
+ {
+ RTStrCopy(pszError, cbError, pstrErr->c_str());
+ delete pstrErr;
+ return VERR_PARSE_ERROR;
+ }
+
+ /*
+ * Check the name.
+ */
+ /** @todo drop this restriction after the old install interface is
+ * dropped. */
+ int vrc = VINF_SUCCESS;
+ if ( pszExtPackName
+ && !ExtPackDesc.strName.equalsIgnoreCase(pszExtPackName))
+ vrc = vboxExtPackReturnError(VERR_NOT_EQUAL, pszError, cbError,
+ ExtPackUtil::tr("The name of the downloaded file and the name stored inside the extension pack does not match"
+ " (xml='%s' file='%s')"), ExtPackDesc.strName.c_str(), pszExtPackName);
+ return vrc;
+}
+
+/**
+ * Verifies the manifest and its signature.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hOurManifest The manifest we compiled.
+ * @param hManifestFile The manifest file in the extension pack.
+ * @param hSignatureFile The manifest signature file.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile,
+ char *pszError, size_t cbError)
+{
+ /*
+ * Read the manifest from the extension pack.
+ */
+ int vrc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTVfsFileSeek failed: %Rrc"), vrc);
+
+ RTMANIFEST hTheirManifest;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTManifestCreate failed: %Rrc"), vrc);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile);
+ vrc = RTManifestReadStandard(hTheirManifest, hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Compare the manifests.
+ */
+ static const char *s_apszIgnoreEntries[] =
+ {
+ VBOX_EXTPACK_MANIFEST_NAME,
+ VBOX_EXTPACK_SIGNATURE_NAME,
+ "./" VBOX_EXTPACK_MANIFEST_NAME,
+ "./" VBOX_EXTPACK_SIGNATURE_NAME,
+ NULL
+ };
+ char szError[RTPATH_MAX];
+ vrc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL,
+ RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
+ szError, sizeof(szError));
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Validate the manifest file signature.
+ */
+ /** @todo implement signature stuff */
+ NOREF(hSignatureFile);
+
+ }
+ else if (vrc == VERR_NOT_EQUAL && szError[0])
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Manifest mismatch: %s"), szError);
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestEqualsEx failed: %Rrc"), vrc);
+#if 0
+ RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
+ RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hOurManifest, hVfsIosStdOut);
+ RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL);
+ RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut);
+#endif
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Error parsing '%s': %Rrc"), VBOX_EXTPACK_MANIFEST_NAME, vrc);
+
+ RTManifestRelease(hTheirManifest);
+ return vrc;
+}
+
+
+/**
+ * Verifies the file digest (if specified) and returns the SHA-256 of the file.
+ *
+ * @returns
+ * @param hFileManifest Manifest containing a SHA-256 digest of the file
+ * that was calculated as the file was processed.
+ * @param pszFileDigest SHA-256 digest of the file.
+ * @param pStrDigest Where to return the SHA-256 digest. Optional.
+ * @param pszError Where to write an error message on failure.
+ * @param cbError The size of the @a pszError buffer.
+ */
+static int vboxExtPackVerifyFileDigest(RTMANIFEST hFileManifest, const char *pszFileDigest,
+ RTCString *pStrDigest, char *pszError, size_t cbError)
+{
+ /*
+ * Extract the SHA-256 entry for the extpack file.
+ */
+ char szCalculatedDigest[RTSHA256_DIGEST_LEN + 1];
+ int vrc = RTManifestEntryQueryAttr(hFileManifest, "extpack", NULL /*no name*/, RTMANIFEST_ATTR_SHA256,
+ szCalculatedDigest, sizeof(szCalculatedDigest), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Convert the two strings to binary form before comparing.
+ * We convert the calculated hash even if we don't have anything to
+ * compare with, just to validate it.
+ */
+ uint8_t abCalculatedHash[RTSHA256_HASH_SIZE];
+ vrc = RTSha256FromString(szCalculatedDigest, abCalculatedHash);
+ if (RT_SUCCESS(vrc))
+ {
+ if ( pszFileDigest
+ && *pszFileDigest != '\0')
+ {
+ uint8_t abFileHash[RTSHA256_HASH_SIZE];
+ vrc = RTSha256FromString(pszFileDigest, abFileHash);
+ if (RT_SUCCESS(vrc))
+ {
+ if (memcmp(abFileHash, abCalculatedHash, sizeof(abFileHash)))
+ {
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("The extension pack file has changed (SHA-256 mismatch)"));
+ vrc = VERR_NOT_EQUAL;
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Bad SHA-256 '%s': %Rrc"), szCalculatedDigest, vrc);
+ }
+
+ /*
+ * Set the output hash on success.
+ */
+ if (pStrDigest && RT_SUCCESS(vrc))
+ {
+ try
+ {
+ *pStrDigest = szCalculatedDigest;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("Bad SHA-256 '%s': %Rrc"), szCalculatedDigest, vrc);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, "RTManifestEntryGetAttr: %Rrc", vrc);
+ return vrc;
+}
+
+
+
+/**
+ * Validates a standard file.
+ *
+ * Generally all files are
+ *
+ * @returns VBox status code, failure message in @a pszError.
+ * @param pszAdjName The adjusted member name.
+ * @param enmType The VFS object type.
+ * @param phVfsObj The pointer to the VFS object handle variable.
+ * This is both input and output.
+ * @param phVfsFile Where to store the handle to the memorized
+ * file. This is NULL for license files.
+ * @param pszError Where to write an error message on failure.
+ * @param cbError The size of the @a pszError buffer.
+ */
+static int VBoxExtPackValidateStandardFile(const char *pszAdjName, RTVFSOBJTYPE enmType,
+ PRTVFSOBJ phVfsObj, PRTVFSFILE phVfsFile, char *pszError, size_t cbError)
+{
+ int vrc;
+
+ /*
+ * Make sure it's a file and that it isn't too large.
+ */
+ if (phVfsFile && *phVfsFile != NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_DUPLICATE, pszError, cbError,
+ ExtPackUtil::tr("There can only be one '%s'"), pszAdjName);
+ else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is not a file"), pszAdjName);
+ else
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(*phVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is not a file"), pszAdjName);
+ else if (ObjInfo.cbObject >= _1M)
+ vrc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
+ ExtPackUtil::tr("Standard member '%s' is too large: %'RU64 bytes (max 1 MB)", "",
+ (size_t)ObjInfo.cbObject),
+ pszAdjName, (uint64_t)ObjInfo.cbObject);
+ else
+ {
+ /*
+ * Make an in memory copy of the stream and check that the file
+ * is UTF-8 clean.
+ */
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(*phVfsObj);
+ RTVFSFILE hVfsFile;
+ vrc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsIoStrmValidateUtf8Encoding(hVfsIos,
+ RTVFS_VALIDATE_UTF8_BY_RTC_3629 | RTVFS_VALIDATE_UTF8_NO_NULL,
+ NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Replace *phVfsObj with the memorized file.
+ */
+ vrc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsObjRelease(*phVfsObj);
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTVfsFileSeek failed on '%s': %Rrc"), pszAdjName, vrc);
+ }
+
+ if (phVfsFile && RT_SUCCESS(vrc))
+ *phVfsFile = hVfsFile;
+ else
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc"), pszAdjName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszAdjName, vrc);
+ }
+ return vrc;
+}
+
+
+/**
+ * Validates a name in an extension pack.
+ *
+ * We restrict the charset to try make sure the extension pack can be unpacked
+ * on all file systems.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name to validate.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberName(const char *pszName, char *pszError, size_t cbError)
+{
+ if (RTPathStartsWithRoot(pszName))
+ return vboxExtPackReturnError(VERR_PATH_IS_NOT_RELATIVE, pszError, cbError,
+ ExtPackUtil::tr("'%s': starts with root spec"), pszName);
+
+ const char *pszErr = NULL;
+ const char *psz = pszName;
+ int ch;
+ while ((ch = *psz) != '\0')
+ {
+ /* Character set restrictions. */
+ if (ch < 0 || ch >= 128)
+ {
+ pszErr = "Only 7-bit ASCII allowed";
+ break;
+ }
+ if (ch <= 31 || ch == 127)
+ {
+ pszErr = "No control characters are not allowed";
+ break;
+ }
+ if (ch == '\\')
+ {
+ pszErr = "Only backward slashes are not allowed";
+ break;
+ }
+ if (strchr("'\":;*?|[]<>(){}", ch))
+ {
+ pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
+ break;
+ }
+
+ /* Take the simple way out and ban all ".." sequences. */
+ if ( ch == '.'
+ && psz[1] == '.')
+ {
+ pszErr = "Double dot sequence are not allowed";
+ break;
+ }
+
+ /* Keep the tree shallow or the hardening checks will fail. */
+ if (psz - pszName > VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH)
+ {
+ pszErr = "Too long";
+ break;
+ }
+
+ /* advance */
+ psz++;
+ }
+
+ if (pszErr)
+ return vboxExtPackReturnError(VERR_INVALID_NAME, pszError, cbError,
+ ExtPackUtil::tr("Bad member name '%s' (pos %zu): %s"),
+ pszName, (size_t)(psz - pszName), pszErr);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Validates a file in an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the file.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberFile(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ int vrc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (ObjInfo.cbObject >= 9*_1G64)
+ vrc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
+ ExtPackUtil::tr("'%s': too large (%'RU64 bytes)", "", (size_t)ObjInfo.cbObject),
+ pszName, (uint64_t)ObjInfo.cbObject);
+ if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
+ ExtPackUtil::tr("The alleged file '%s' has a mode mask stating otherwise (%RTfmode)"),
+ pszName, ObjInfo.Attr.fMode);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszName, vrc);
+ }
+ return vrc;
+}
+
+
+/**
+ * Validates a directory in an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the directory.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+static int vboxExtPackValidateMemberDir(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ int vrc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ RTFSOBJINFO ObjInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(vrc))
+ {
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ vrc = vboxExtPackReturnError(VERR_NOT_A_DIRECTORY, pszError, cbError,
+ ExtPackUtil::tr("The alleged directory '%s' has a mode mask saying differently (%RTfmode)"),
+ pszName, ObjInfo.Attr.fMode);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsObjQueryInfo failed on '%s': %Rrc"), pszName, vrc);
+ }
+ return vrc;
+}
+
+/**
+ * Validates a member of an extension pack.
+ *
+ * @returns VBox status code, failures with message.
+ * @param pszName The name of the directory.
+ * @param enmType The object type.
+ * @param hVfsObj The VFS object.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ */
+int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
+{
+ Assert(cbError > 0);
+ *pszError = '\0';
+
+ int vrc;
+ if ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM)
+ vrc = vboxExtPackValidateMemberFile(pszName, hVfsObj, pszError, cbError);
+ else if ( enmType == RTVFSOBJTYPE_DIR
+ || enmType == RTVFSOBJTYPE_BASE)
+ vrc = vboxExtPackValidateMemberDir(pszName, hVfsObj, pszError, cbError);
+ else
+ vrc = vboxExtPackReturnError(VERR_UNEXPECTED_FS_OBJ_TYPE, pszError, cbError,
+ ExtPackUtil::tr("'%s' is not a file or directory (enmType=%d)"), pszName, enmType);
+ return vrc;
+}
+
+
+/**
+ * Rewinds the tarball file handle and creates a gunzip | tar chain that
+ * results in a filesystem stream.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hTarballFile The handle to the tarball file.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ * @param phTarFss Where to return the filesystem stream handle.
+ * @param phFileManifest Where to return a manifest where the tarball is
+ * gettting hashed. The entry will be called
+ * "extpack" and be ready when the file system
+ * stream is at an end. Optional.
+ */
+int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss,
+ PRTMANIFEST phFileManifest)
+{
+ Assert(cbError > 0);
+ *pszError = '\0';
+ *phTarFss = NIL_RTVFSFSSTREAM;
+
+ /*
+ * Rewind the file and set up a VFS chain for it.
+ */
+ int vrc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError,
+ ExtPackUtil::tr("Failed seeking to the start of the tarball: %Rrc"), vrc);
+
+ RTVFSIOSTREAM hTarballIos;
+ vrc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
+ &hTarballIos);
+ if (RT_FAILURE(vrc))
+ return vboxExtPackReturnError(vrc, pszError, cbError, ExtPackUtil::tr("RTVfsIoStrmFromRTFile failed: %Rrc"), vrc);
+
+ RTMANIFEST hFileManifest = NIL_RTMANIFEST;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hFileManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hPtIos;
+ vrc = RTManifestEntryAddPassthruIoStream(hFileManifest, hTarballIos, "extpack", RTMANIFEST_ATTR_SHA256,
+ true /*read*/, &hPtIos);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hGunzipIos;
+ vrc = RTZipGzipDecompressIoStream(hPtIos, 0 /*fFlags*/, &hGunzipIos);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFSSTREAM hTarFss;
+ vrc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsIoStrmRelease(hPtIos);
+ RTVfsIoStrmRelease(hGunzipIos);
+ RTVfsIoStrmRelease(hTarballIos);
+ *phTarFss = hTarFss;
+ if (phFileManifest)
+ *phFileManifest = hFileManifest;
+ else
+ RTManifestRelease(hFileManifest);
+ return VINF_SUCCESS;
+ }
+
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTZipTarFsStreamFromIoStream failed: %Rrc"), vrc);
+ RTVfsIoStrmRelease(hGunzipIos);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTZipGzipDecompressIoStream failed: %Rrc"), vrc);
+ RTVfsIoStrmRelease(hPtIos);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestEntryAddPassthruIoStream failed: %Rrc"), vrc);
+ RTManifestRelease(hFileManifest);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTManifestCreate failed: %Rrc"), vrc);
+
+ RTVfsIoStrmRelease(hTarballIos);
+ return vrc;
+}
+
+
+/**
+ * Validates the extension pack tarball prior to unpacking.
+ *
+ * Operations performed:
+ * - Mandatory files.
+ * - Manifest check.
+ * - Manifest seal check.
+ * - XML check, match name.
+ *
+ * @returns VBox status code, failures with message.
+ * @param hTarballFile The handle to open the @a pszTarball file.
+ * @param pszExtPackName The name of the extension pack name. NULL if
+ * the name is not fixed.
+ * @param pszTarball The name of the tarball in case we have to
+ * complain about something.
+ * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string
+ * if no digest available.
+ * @param pszError Where to store an error message on failure.
+ * @param cbError The size of the buffer @a pszError points to.
+ * @param phValidManifest Where to optionally return the handle to fully
+ * validated the manifest for the extension pack.
+ * This includes all files.
+ * @param phXmlFile Where to optionally return the memorized XML
+ * file.
+ * @param pStrDigest Where to return the digest of the file.
+ * Optional.
+ */
+int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName,
+ const char *pszTarball, const char *pszTarballDigest,
+ char *pszError, size_t cbError,
+ PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile, RTCString *pStrDigest)
+{
+ /*
+ * Clear return values.
+ */
+ if (phValidManifest)
+ *phValidManifest = NIL_RTMANIFEST;
+ if (phXmlFile)
+ *phXmlFile = NIL_RTVFSFILE;
+ Assert(cbError > 1);
+ *pszError = '\0';
+ NOREF(pszTarball);
+
+ /*
+ * Open the tar.gz filesystem stream and set up an manifest in-memory file.
+ */
+ RTMANIFEST hFileManifest;
+ RTVFSFSSTREAM hTarFss;
+ int vrc = VBoxExtPackOpenTarFss(hTarballFile, pszError, cbError, &hTarFss, &hFileManifest);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ RTMANIFEST hOurManifest;
+ vrc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Process the tarball (would be nice to move this to a function).
+ */
+ RTVFSFILE hXmlFile = NIL_RTVFSFILE;
+ RTVFSFILE hManifestFile = NIL_RTVFSFILE;
+ RTVFSFILE hSignatureFile = NIL_RTVFSFILE;
+ for (;;)
+ {
+ /*
+ * Get the next stream object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ vrc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc != VERR_EOF)
+ vboxExtPackSetError(pszError, cbError, ExtPackUtil::tr("RTVfsFsStrmNext failed: %Rrc"), vrc);
+ else
+ vrc = VINF_SUCCESS;
+ break;
+ }
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+
+ /*
+ * Check the type & name validity, performing special tests on
+ * standard extension pack member files.
+ *
+ * N.B. We will always reach the end of the loop before breaking on
+ * failure - cleanup reasons.
+ */
+ vrc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, pszError, cbError);
+ if (RT_SUCCESS(vrc))
+ {
+ PRTVFSFILE phVfsFile = NULL;
+ if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
+ phVfsFile = &hXmlFile;
+ else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
+ phVfsFile = &hManifestFile;
+ else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
+ phVfsFile = &hSignatureFile;
+ else if (!strncmp(pszAdjName, VBOX_EXTPACK_LICENSE_NAME_PREFIX, sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX) - 1))
+ vrc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, NULL, pszError, cbError);
+ if (phVfsFile)
+ vrc = VBoxExtPackValidateStandardFile(pszAdjName, enmType, &hVfsObj, phVfsFile, pszError, cbError);
+ }
+
+ /*
+ * Add any I/O stream to the manifest
+ */
+ if ( RT_SUCCESS(vrc)
+ && ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ vrc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
+ if (RT_FAILURE(vrc))
+ vboxExtPackSetError(pszError, cbError,
+ ExtPackUtil::tr("RTManifestEntryAddIoStream failed on '%s': %Rrc"), pszAdjName, vrc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+
+ /*
+ * Clean up and break out on failure.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ /*
+ * Check the integrity of the tarball file.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ RTVfsFsStrmRelease(hTarFss);
+ hTarFss = NIL_RTVFSFSSTREAM;
+ vrc = vboxExtPackVerifyFileDigest(hFileManifest, pszTarballDigest, pStrDigest, pszError, cbError);
+ }
+
+ /*
+ * If we've successfully processed the tarball, verify that the
+ * mandatory files are present.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ if (hXmlFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_DESCRIPTION_NAME);
+ if (hManifestFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_MANIFEST_NAME);
+ if (hSignatureFile == NIL_RTVFSFILE)
+ vrc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, ExtPackUtil::tr("Mandator file '%s' is missing"),
+ VBOX_EXTPACK_SIGNATURE_NAME);
+ }
+
+ /*
+ * Check the manifest and it's signature.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError);
+
+ /*
+ * Check the XML.
+ */
+ if (RT_SUCCESS(vrc))
+ vrc = vboxExtPackVerifyXml(hXmlFile, pszExtPackName, pszError, cbError);
+
+ /*
+ * Returns objects.
+ */
+ if (RT_SUCCESS(vrc))
+ {
+ if (phValidManifest)
+ {
+ RTManifestRetain(hOurManifest);
+ *phValidManifest = hOurManifest;
+ }
+ if (phXmlFile)
+ {
+ RTVfsFileRetain(hXmlFile);
+ *phXmlFile = hXmlFile;
+ }
+ }
+
+ /*
+ * Release our object references.
+ */
+ RTManifestRelease(hOurManifest);
+ RTVfsFileRelease(hXmlFile);
+ RTVfsFileRelease(hManifestFile);
+ RTVfsFileRelease(hSignatureFile);
+ }
+ else
+ vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", vrc);
+ RTVfsFsStrmRelease(hTarFss);
+ RTManifestRelease(hFileManifest);
+
+ return vrc;
+}
+
diff --git a/src/VBox/Main/src-all/Global.cpp b/src/VBox/Main/src-all/Global.cpp
new file mode 100644
index 00000000..4843fdf9
--- /dev/null
+++ b/src/VBox/Main/src-all/Global.cpp
@@ -0,0 +1,791 @@
+/* $Id: Global.cpp $ */
+/** @file
+ * VirtualBox COM global definitions
+ *
+ * NOTE: This file is part of both VBoxC.dll and VBoxSVC.exe.
+ */
+
+/*
+ * Copyright (C) 2008-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 "Global.h"
+#include "StringifyEnums.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+#include "VBoxNls.h"
+
+DECLARE_TRANSLATION_CONTEXT(GlobalCtx);
+
+
+/* static */
+const Global::OSType Global::sOSTypes[] =
+{
+ /* NOTE1: we assume that unknown is always the first two entries!
+ * NOTE2: please use powers of 2 when specifying the size of harddisks since
+ * '2GB' looks better than '1.95GB' (= 2000MB)
+ * NOTE3: if you add new guest OS types please check if the code in
+ * Machine::getEffectiveParavirtProvider and Console::i_configConstructorInner
+ * are still covering the relevant cases. */
+ { "Other", "Other", "Other", "Other/Unknown",
+ VBOXOSTYPE_Unknown, VBOXOSHINT_NONE,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "Other_64", "Other/Unknown (64-bit)",
+ VBOXOSTYPE_Unknown_x64, VBOXOSHINT_64BIT | VBOXOSHINT_PAE | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows31", "Windows 3.1",
+ VBOXOSTYPE_Win31, VBOXOSHINT_FLOPPY,
+ 1, 32, 4, 1 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows95", "Windows 95",
+ VBOXOSTYPE_Win95, VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows98", "Windows 98",
+ VBOXOSTYPE_Win98, VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "WindowsMe", "Windows ME",
+ VBOXOSTYPE_WinMe, VBOXOSHINT_FLOPPY | VBOXOSHINT_USBTABLET,
+ 1, 128, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT3x", "Windows NT 3.x",
+ VBOXOSTYPE_WinNT3x, VBOXOSHINT_NOUSB | VBOXOSHINT_FLOPPY,
+ 1, 64, 8, _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_BusLogic, StorageBus_SCSI,
+ StorageControllerType_BusLogic, StorageBus_SCSI, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT4", "Windows NT 4",
+ VBOXOSTYPE_WinNT4, VBOXOSHINT_NOUSB,
+ 1, 128, 16, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Windows", "Microsoft Windows", "Windows2000", "Windows 2000",
+ VBOXOSTYPE_Win2k, VBOXOSHINT_USBTABLET,
+ 1, 168, 16, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsXP", "Windows XP (32-bit)",
+ VBOXOSTYPE_WinXP, VBOXOSHINT_USBTABLET,
+ 1, 192, 16, 10 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82543GC, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsXP_64", "Windows XP (64-bit)",
+ VBOXOSTYPE_WinXP_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 10 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows2003", "Windows 2003 (32-bit)",
+ VBOXOSTYPE_Win2k3, VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82543GC, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "Windows2003_64", "Windows 2003 (64-bit)",
+ VBOXOSTYPE_Win2k3_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsVista", "Windows Vista (32-bit)",
+ VBOXOSTYPE_WinVista, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 512, 16, 25 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsVista_64", "Windows Vista (64-bit)",
+ VBOXOSTYPE_WinVista_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 512, 16, 25 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2008", "Windows 2008 (32-bit)",
+ VBOXOSTYPE_Win2k8, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2008_64", "Windows 2008 (64-bit)",
+ VBOXOSTYPE_Win2k8_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows7", "Windows 7 (32-bit)",
+ VBOXOSTYPE_Win7, VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows7_64", "Windows 7 (64-bit)",
+ VBOXOSTYPE_Win7_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows8", "Windows 8 (32-bit)",
+ VBOXOSTYPE_Win8, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows8_64", "Windows 8 (64-bit)",
+ VBOXOSTYPE_Win8_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows81", "Windows 8.1 (32-bit)",
+ VBOXOSTYPE_Win81, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows81_64", "Windows 8.1 (64-bit)",
+ VBOXOSTYPE_Win81_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 40 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2012_64", "Windows 2012 (64-bit)",
+ VBOXOSTYPE_Win2k12_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows10", "Windows 10 (32-bit)",
+ VBOXOSTYPE_Win10, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_PAE | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 1024, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows10_64", "Windows 10 (64-bit)",
+ VBOXOSTYPE_Win10_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2016_64", "Windows 2016 (64-bit)",
+ VBOXOSTYPE_Win2k16_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2019_64", "Windows 2019 (64-bit)",
+ VBOXOSTYPE_Win2k19_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows11_64", "Windows 11 (64-bit)",
+ VBOXOSTYPE_Win11_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_EFI_SECUREBOOT | VBOXOSHINT_TPM2 | VBOXOSHINT_WDDM_GRAPHICS,
+ 2, 4096, 128, 80 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "Windows2022_64", "Windows 2022 (64-bit)",
+ VBOXOSTYPE_Win2k22_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_USB3 | VBOXOSHINT_WDDM_GRAPHICS,
+ 1, 2048, 128, 50 * _1G64, GraphicsControllerType_VBoxSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT", "Other Windows (32-bit)",
+ VBOXOSTYPE_WinNT, VBOXOSHINT_NONE,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Windows", "Microsoft Windows", "WindowsNT_64", "Other Windows (64-bit)",
+ VBOXOSTYPE_WinNT_x64, VBOXOSHINT_64BIT | VBOXOSHINT_PAE | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 512, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+#define VBOX_LINUX_OSHINTS_A_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET | VBOXOSHINT_X2APIC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_A_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_B_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE | VBOXOSHINT_X2APIC)
+#define VBOX_LINUX_OSHINTS_B_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_C_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_X2APIC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_C_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_X2APIC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSHINTS_D_32 (VBOXOSHINT_RTCUTC | VBOXOSHINT_PAE)
+#define VBOX_LINUX_OSHINTS_D_64 (VBOXOSHINT_RTCUTC | VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC)
+
+#define VBOX_LINUX_OSTYPE_32(a_OStype) (VBOXOSTYPE_ ## a_OStype)
+#define VBOX_LINUX_OSTYPE_64(a_OStype) (VBOXOSTYPE_ ## a_OStype ## _x64)
+
+#define VBOX_LINUX_OSID_STR(a_OSid) (# a_OSid)
+#define VBOX_LINUX_OSID_STR_64(a_OSid) VBOX_LINUX_OSID_STR(a_OSid ## _64)
+
+#define VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_OStype, a_OSHint, a_Memory, a_Vram, a_Diskspace, \
+ a_NetworkAdapter, a_HDStorageController, a_HDStorageBusType) \
+ { "Linux", "Linux", VBOX_LINUX_OSID_STR(a_Id), a_Description, VBOX_LINUX_OSTYPE_32(a_OStype), a_OSHint, \
+ 1, a_Memory, a_Vram, a_Diskspace * _1G64, GraphicsControllerType_VMSVGA, a_NetworkAdapter, 0, StorageControllerType_PIIX4, StorageBus_IDE, \
+ a_HDStorageController, a_HDStorageBusType, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_AD1980 }
+
+#define VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_OStype, a_OSHint, a_Memory, a_Vram, a_Diskspace, \
+ a_NetworkAdapter, a_HDStorageController, a_HDStorageBusType) \
+ { "Linux", "Linux", VBOX_LINUX_OSID_STR_64(a_Id), a_Description, VBOX_LINUX_OSTYPE_64(a_OStype), a_OSHint, \
+ 1, a_Memory, a_Vram, a_Diskspace * _1G64, GraphicsControllerType_VMSVGA, a_NetworkAdapter, 0, StorageControllerType_PIIX4, StorageBus_IDE, \
+ a_HDStorageController, a_HDStorageBusType, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_AD1980 }
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with USB-tablet-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_A_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_A_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with USB-tablet-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_A_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_A_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+#define VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_32(a_Id, a_Description, a_OStype, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_OStype, VBOX_LINUX_OSHINTS_A_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+#define VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_64(a_Id, a_Description, a_OStype, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_OStype, VBOX_LINUX_OSHINTS_A_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/PAE-NX/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_B_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_B_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/PAE-NX/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_B_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_B_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_C_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_C_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/Intel-Pro1000/PIIX4+IDE DVD/AHCI+SATA disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_C_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_C_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_IntelAhci, StorageBus_SATA)
+
+/* Linux 32-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/PCnet-FASTIII/PIIX4+IDE DVD/PIIX4+IDE disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_D_32(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_32(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_D_32, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_Am79C973, StorageControllerType_PIIX4, StorageBus_IDE)
+
+/* Linux 64-bit sub-type template defaulting to 1 CPU with PS/2-mouse/VMSVGA/PCnet-FASTIII/PIIX4+IDE DVD/PIIX4+IDE disk/AC97 */
+#define VBOX_LINUX_SUBTYPE_D_64(a_Id, a_Description, a_Memory, a_Vram, a_Diskspace) \
+ VBOX_LINUX_SUBTYPE_TEMPLATE_64(a_Id, a_Description, a_Id, VBOX_LINUX_OSHINTS_D_64, a_Memory, a_Vram, a_Diskspace, \
+ NetworkAdapterType_I82540EM, StorageControllerType_PIIX4, StorageBus_IDE)
+
+ VBOX_LINUX_SUBTYPE_D_32(Linux22, "Linux 2.2 (32-bit)", 64, 4, 2),
+ VBOX_LINUX_SUBTYPE_D_32(Linux24, "Linux 2.4 (32-bit)", 128, 16, 2),
+ VBOX_LINUX_SUBTYPE_D_64(Linux24, "Linux 2.4 (64-bit)", 1024, 16, 4),
+ VBOX_LINUX_SUBTYPE_A_32(Linux26, "Linux 2.6 / 3.x / 4.x / 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Linux26, "Linux 2.6 / 3.x / 4.x / 5.x (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(ArchLinux, "Arch Linux (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(ArchLinux, "Arch Linux (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Debian, "Debian (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian, "Debian (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian31, "Debian 3.1 Sarge (32-bit)", 1024, 16, 8), // 32-bit only
+ VBOX_LINUX_SUBTYPE_A_32(Debian4, "Debian 4.0 Etch (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian4, "Debian 4.0 Etch (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian5, "Debian 5.0 Lenny (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian5, "Debian 5.0 Lenny (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian6, "Debian 6.0 Squeeze (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Debian6, "Debian 6.0 Squeeze (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(Debian7, "Debian 7 Wheezy (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian7, "Debian 7 Wheezy (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian8, "Debian 8 Jessie (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian8, "Debian 8 Jessie (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian9, "Debian 9 Stretch (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian9, "Debian 9 Stretch (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian10, "Debian 10 Buster (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian10, "Debian 10 Buster (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian11, "Debian 11 Bullseye (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian11, "Debian 11 Bullseye (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_32(Debian12, "Debian 12 Bookworm (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_A_64(Debian12, "Debian 12 Bookworm (64-bit)", 2048, 16, 20),
+
+ VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_32(Fedora, "Fedora (32-bit)", FedoraCore, 2048, 16, 15),
+ VBOX_LINUX_SUBTYPE_A_WITH_OSTYPE_64(Fedora, "Fedora (64-bit)", FedoraCore, 2048, 16, 15),
+
+ VBOX_LINUX_SUBTYPE_A_32(Gentoo, "Gentoo (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Gentoo, "Gentoo (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Mandriva, "Mandriva (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Mandriva, "Mandriva (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(OpenMandriva_Lx, "OpenMandriva Lx (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(OpenMandriva_Lx, "OpenMandriva Lx (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(PCLinuxOS, "PCLinuxOS / PCLOS (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(PCLinuxOS, "PCLinuxOS / PCLOS (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Mageia, "Mageia (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Mageia, "Mageia (64-bit)", 2048, 16, 10),
+
+ VBOX_LINUX_SUBTYPE_B_32(Oracle, "Oracle Linux (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle, "Oracle Linux (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle4, "Oracle Linux 4.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle4, "Oracle Linux 4.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle5, "Oracle Linux 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle5, "Oracle Linux 5.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(Oracle6, "Oracle Linux 6.x (32-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle6, "Oracle Linux 6.x (64-bit)", 2048, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(Oracle7, "Oracle Linux 7.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(Oracle8, "Oracle Linux 8.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(Oracle9, "Oracle Linux 9.x (64-bit)", 2048, 16, 20), // 64-bit only
+
+ VBOX_LINUX_SUBTYPE_B_32(RedHat, "Red Hat (32-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat, "Red Hat (64-bit)", 2048, 16, 20),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat3, "Red Hat 3.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat3, "Red Hat 3.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat4, "Red Hat 4.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat4, "Red Hat 4.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat5, "Red Hat 5.x (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat5, "Red Hat 5.x (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_32(RedHat6, "Red Hat 6.x (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat6, "Red Hat 6.x (64-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_B_64(RedHat7, "Red Hat 7.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(RedHat8, "Red Hat 8.x (64-bit)", 2048, 16, 20), // 64-bit only
+ VBOX_LINUX_SUBTYPE_B_64(RedHat9, "Red Hat 9.x (64-bit)", 2048, 16, 20), // 64-bit only
+
+ VBOX_LINUX_SUBTYPE_A_32(OpenSUSE, "openSUSE (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE, "openSUSE (64-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE_Leap, "openSUSE Leap (64-bit)", 2048, 16, 8), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_32(OpenSUSE_Tumbleweed, "openSUSE Tumbleweed (32-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(OpenSUSE_Tumbleweed, "openSUSE Tumbleweed (64-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_32(SUSE_LE, "SUSE Linux Enterprise (32-bit)", 2048, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(SUSE_LE, "SUSE Linux Enterprise (64-bit)", 2048, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Turbolinux, "Turbolinux (32-bit)", 384, 16, 8),
+ VBOX_LINUX_SUBTYPE_A_64(Turbolinux, "Turbolinux (64-bit)", 384, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu, "Ubuntu (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu, "Ubuntu (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu10_LTS, "Ubuntu 10.04 LTS (Lucid Lynx) (32-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu10_LTS, "Ubuntu 10.04 LTS (Lucid Lynx) (64-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu10, "Ubuntu 10.10 (Maverick Meerkat) (32-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu10, "Ubuntu 10.10 (Maverick Meerkat) (64-bit)", 256, 16, 3),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu11, "Ubuntu 11.04 (Natty Narwhal) / 11.10 (Oneiric Ocelot) (32-bit)", 384, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu11, "Ubuntu 11.04 (Natty Narwhal) / 11.10 (Oneiric Ocelot) (64-bit)", 384, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu12_LTS, "Ubuntu 12.04 LTS (Precise Pangolin) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu12_LTS, "Ubuntu 12.04 LTS (Precise Pangolin) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu12, "Ubuntu 12.10 (Quantal Quetzal) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu12, "Ubuntu 12.10 (Quantal Quetzal) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu13, "Ubuntu 13.04 (Raring Ringtail) / 13.10 (Saucy Salamander) (32-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu13, "Ubuntu 13.04 (Raring Ringtail) / 13.10 (Saucy Salamander) (64-bit)", 768, 16, 5),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu14_LTS, "Ubuntu 14.04 LTS (Trusty Tahr) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu14_LTS, "Ubuntu 14.04 LTS (Trusty Tahr) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu14, "Ubuntu 14.10 (Utopic Unicorn) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu14, "Ubuntu 14.10 (Utopic Unicorn) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu15, "Ubuntu 15.04 (Vivid Vervet) / 15.10 (Wily Werewolf) (32-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu15, "Ubuntu 15.04 (Vivid Vervet) / 15.10 (Wily Werewolf) (64-bit)", 1536, 16, 7),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu16_LTS, "Ubuntu 16.04 LTS (Xenial Xerus) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu16_LTS, "Ubuntu 16.04 LTS (Xenial Xerus) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu16, "Ubuntu 16.10 (Yakkety Yak) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu16, "Ubuntu 16.10 (Yakkety Yak) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu17, "Ubuntu 17.04 (Zesty Zapus) / 17.10 (Artful Aardvark) (32-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu17, "Ubuntu 17.04 (Zesty Zapus) / 17.10 (Artful Aardvark) (64-bit)", 1536, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu18_LTS, "Ubuntu 18.04 LTS (Bionic Beaver) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu18_LTS, "Ubuntu 18.04 LTS (Bionic Beaver) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu18, "Ubuntu 18.10 (Cosmic Cuttlefish) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu18, "Ubuntu 18.10 (Cosmic Cuttlefish) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_32(Ubuntu19, "Ubuntu 19.04 (Disco Dingo) / 19.10 (Eoan Ermine) (32-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu19, "Ubuntu 19.04 (Disco Dingo) / 19.10 (Eoan Ermine) (64-bit)", 2048, 16, 25),
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu20_LTS, "Ubuntu 20.04 LTS (Focal Fossa) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu20, "Ubuntu 20.10 (Groovy Gorilla) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu21, "Ubuntu 21.04 (Hirsute Hippo) / 21.10 (Impish Indri) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu22_LTS, "Ubuntu 22.04 LTS (Jammy Jellyfish) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu22, "Ubuntu 22.10 (Kinetic Kudu) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_64(Ubuntu23, "Ubuntu 23.04 (Lunar Lobster) (64-bit)", 2048, 16, 25), // 64-bit only
+ VBOX_LINUX_SUBTYPE_A_32(Lubuntu, "Lubuntu (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Lubuntu, "Lubuntu (64-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_32(Xubuntu, "Xubuntu (32-bit)", 1024, 16, 10),
+ VBOX_LINUX_SUBTYPE_A_64(Xubuntu, "Xubuntu (64-bit)", 1024, 16, 10),
+
+ VBOX_LINUX_SUBTYPE_C_32(Xandros, "Xandros (32-bit)", 1024, 16, 8),
+ VBOX_LINUX_SUBTYPE_C_64(Xandros, "Xandros (64-bit)", 1024, 16, 8),
+
+ VBOX_LINUX_SUBTYPE_A_32(Linux, "Other Linux (32-bit)", 256, 16, 8),
+ VBOX_LINUX_SUBTYPE_B_64(Linux, "Other Linux (64-bit)", 512, 16, 8),
+
+ { "Solaris", "Solaris", "Solaris", "Oracle Solaris 10 5/09 and earlier (32-bit)",
+ VBOXOSTYPE_Solaris, VBOXOSHINT_NONE,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris_64", "Oracle Solaris 10 5/09 and earlier (64-bit)",
+ VBOXOSTYPE_Solaris_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris10U8_or_later", "Oracle Solaris 10 10/09 and later (32-bit)",
+ VBOXOSTYPE_Solaris10U8_or_later, VBOXOSHINT_USBTABLET,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris10U8_or_later_64", "Oracle Solaris 10 10/09 and later (64-bit)",
+ VBOXOSTYPE_Solaris10U8_or_later_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "Solaris11_64", "Oracle Solaris 11 (64-bit)",
+ VBOXOSTYPE_Solaris11_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET | VBOXOSHINT_RTCUTC,
+ 1, 4096, 16, 32 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "OpenSolaris", "OpenSolaris / Illumos / OpenIndiana (32-bit)",
+ VBOXOSTYPE_OpenSolaris, VBOXOSHINT_USBTABLET,
+ 1, 1024, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Solaris", "Solaris", "OpenSolaris_64", "OpenSolaris / Illumos / OpenIndiana (64-bit)",
+ VBOXOSTYPE_OpenSolaris_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 32 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "FreeBSD", "FreeBSD (32-bit)",
+ VBOXOSTYPE_FreeBSD, VBOXOSHINT_NONE,
+ 1, 1024, 16, 2 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "FreeBSD_64", "FreeBSD (64-bit)",
+ VBOXOSTYPE_FreeBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "OpenBSD", "OpenBSD (32-bit)",
+ VBOXOSTYPE_OpenBSD, VBOXOSHINT_HWVIRTEX,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "OpenBSD_64", "OpenBSD (64-bit)",
+ VBOXOSTYPE_OpenBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "NetBSD", "NetBSD (32-bit)",
+ VBOXOSTYPE_NetBSD, VBOXOSHINT_RTCUTC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "BSD", "BSD", "NetBSD_64", "NetBSD (64-bit)",
+ VBOXOSTYPE_NetBSD_x64, VBOXOSHINT_64BIT | VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_RTCUTC,
+ 1, 1024, 16, 16 * _1G64, GraphicsControllerType_VMSVGA, NetworkAdapterType_I82540EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS21x", "OS/2 1.x",
+ VBOXOSTYPE_OS21x, VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB | VBOXOSHINT_TFRESET,
+ 1, 8, 4, 500 * _1M, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp3", "OS/2 Warp 3",
+ VBOXOSTYPE_OS2Warp3, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 48, 4, 1 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp4", "OS/2 Warp 4",
+ VBOXOSTYPE_OS2Warp4, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2Warp45", "OS/2 Warp 4.5",
+ VBOXOSTYPE_OS2Warp45, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 128, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "OS2", "IBM OS/2", "OS2eCS", "eComStation",
+ VBOXOSTYPE_ECS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 256, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS2ArcaOS", "ArcaOS",
+ VBOXOSTYPE_ArcaOS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY,
+ 1, 1024, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82540EM, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "OS2", "IBM OS/2", "OS2", "Other OS/2",
+ VBOXOSTYPE_OS2, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 96, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "MacOS", "Mac OS X", "MacOS", "Mac OS X (32-bit)",
+ VBOXOSTYPE_MacOS, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS_64", "Mac OS X (64-bit)",
+ VBOXOSTYPE_MacOS_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS106", "Mac OS X 10.6 Snow Leopard (32-bit)",
+ VBOXOSTYPE_MacOS106, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS106_64", "Mac OS X 10.6 Snow Leopard (64-bit)",
+ VBOXOSTYPE_MacOS106_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS107_64", "Mac OS X 10.7 Lion (64-bit)",
+ VBOXOSTYPE_MacOS107_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS108_64", "Mac OS X 10.8 Mountain Lion (64-bit)", /* Aka "Mountain Kitten". */
+ VBOXOSTYPE_MacOS108_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 20 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS109_64", "Mac OS X 10.9 Mavericks (64-bit)", /* Not to be confused with McCain. */
+ VBOXOSTYPE_MacOS109_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 25 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1010_64", "Mac OS X 10.10 Yosemite (64-bit)",
+ VBOXOSTYPE_MacOS1010_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 25 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1011_64", "Mac OS X 10.11 El Capitan (64-bit)",
+ VBOXOSTYPE_MacOS1011_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1012_64", "macOS 10.12 Sierra (64-bit)",
+ VBOXOSTYPE_MacOS1012_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "MacOS", "Mac OS X", "MacOS1013_64", "macOS 10.13 High Sierra (64-bit)",
+ VBOXOSTYPE_MacOS1013_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_EFI | VBOXOSHINT_PAE | VBOXOSHINT_64BIT
+ | VBOXOSHINT_USBHID | VBOXOSHINT_HPET | VBOXOSHINT_RTCUTC | VBOXOSHINT_USBTABLET,
+ 1, 2048, 16, 30 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_IntelAhci, StorageBus_SATA,
+ StorageControllerType_IntelAhci, StorageBus_SATA, ChipsetType_ICH9, IommuType_None, AudioControllerType_HDA, AudioCodecType_STAC9221 },
+
+ { "Other", "Other", "DOS", "DOS",
+ VBOXOSTYPE_DOS, VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 32, 4, 500 * _1M, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 1, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_SB16, AudioCodecType_SB16 },
+
+ { "Other", "Other", "Netware", "Netware",
+ VBOXOSTYPE_Netware, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_NOUSB,
+ 1, 512, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "L4", "L4",
+ VBOXOSTYPE_L4, VBOXOSHINT_NONE,
+ 1, 64, 4, 2 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "QNX", "QNX",
+ VBOXOSTYPE_QNX, VBOXOSHINT_HWVIRTEX,
+ 1, 512, 4, 4 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_Am79C973, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "JRockitVE", "JRockitVE",
+ VBOXOSTYPE_JRockitVE, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_IOAPIC | VBOXOSHINT_PAE,
+ 1, 1024, 4, 8 * _1G64, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_BusLogic, StorageBus_SCSI, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+
+ { "Other", "Other", "VBoxBS_64", "VirtualBox Bootsector Test (64-bit)",
+ VBOXOSTYPE_VBoxBS_x64, VBOXOSHINT_HWVIRTEX | VBOXOSHINT_FLOPPY | VBOXOSHINT_IOAPIC | VBOXOSHINT_PAE | VBOXOSHINT_64BIT,
+ 1, 128, 4, 0, GraphicsControllerType_VBoxVGA, NetworkAdapterType_I82545EM, 0, StorageControllerType_PIIX4, StorageBus_IDE,
+ StorageControllerType_PIIX4, StorageBus_IDE, ChipsetType_PIIX3, IommuType_None, AudioControllerType_AC97, AudioCodecType_STAC9700 },
+};
+
+size_t Global::cOSTypes = RT_ELEMENTS(Global::sOSTypes);
+
+/**
+ * Returns an OS Type ID for the given VBOXOSTYPE value.
+ *
+ * The returned ID will correspond to the IGuestOSType::id value of one of the
+ * objects stored in the IVirtualBox::guestOSTypes
+ * (VirtualBoxImpl::COMGETTER(GuestOSTypes)) collection.
+ */
+/* static */
+const char *Global::OSTypeId(VBOXOSTYPE aOSType)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(sOSTypes); ++i)
+ {
+ if (sOSTypes[i].osType == aOSType)
+ return sOSTypes[i].id;
+ }
+
+ return sOSTypes[0].id;
+}
+
+/**
+ * Maps an OS type ID string to index into sOSTypes.
+ *
+ * @returns index on success, UINT32_MAX if not found.
+ * @param pszId The OS type ID string.
+ */
+/* static */ uint32_t Global::getOSTypeIndexFromId(const char *pszId)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(sOSTypes); ++i)
+ if (!RTStrICmp(pszId, Global::sOSTypes[i].id))
+ return (uint32_t)i;
+ return UINT32_MAX;
+}
+
+/*static*/ uint32_t Global::getMaxNetworkAdapters(ChipsetType_T aChipsetType)
+{
+ switch (aChipsetType)
+ {
+ case ChipsetType_PIIX3:
+ return 8;
+ case ChipsetType_ICH9:
+ return 36;
+ default:
+ return 0;
+ }
+}
+
+/*static*/ const char *
+Global::stringifyMachineState(MachineState_T aState)
+{
+ switch (aState)
+ {
+ case MachineState_Null: return GlobalCtx::tr("Null");
+ case MachineState_PoweredOff: return GlobalCtx::tr("PoweredOff");
+ case MachineState_Saved: return GlobalCtx::tr("Saved");
+ case MachineState_Teleported: return GlobalCtx::tr("Teleported");
+ case MachineState_Aborted: return GlobalCtx::tr("Aborted");
+ case MachineState_AbortedSaved: return GlobalCtx::tr("Aborted-Saved");
+ case MachineState_Running: return GlobalCtx::tr("Running");
+ case MachineState_Paused: return GlobalCtx::tr("Paused");
+ case MachineState_Stuck: return GlobalCtx::tr("GuruMeditation");
+ case MachineState_Teleporting: return GlobalCtx::tr("Teleporting");
+ case MachineState_LiveSnapshotting: return GlobalCtx::tr("LiveSnapshotting");
+ case MachineState_Starting: return GlobalCtx::tr("Starting");
+ case MachineState_Stopping: return GlobalCtx::tr("Stopping");
+ case MachineState_Saving: return GlobalCtx::tr("Saving");
+ case MachineState_Restoring: return GlobalCtx::tr("Restoring");
+ case MachineState_TeleportingPausedVM: return GlobalCtx::tr("TeleportingPausedVM");
+ case MachineState_TeleportingIn: return GlobalCtx::tr("TeleportingIn");
+ case MachineState_DeletingSnapshotOnline: return GlobalCtx::tr("DeletingSnapshotOnline");
+ case MachineState_DeletingSnapshotPaused: return GlobalCtx::tr("DeletingSnapshotPaused");
+ case MachineState_OnlineSnapshotting: return GlobalCtx::tr("OnlineSnapshotting");
+ case MachineState_RestoringSnapshot: return GlobalCtx::tr("RestoringSnapshot");
+ case MachineState_DeletingSnapshot: return GlobalCtx::tr("DeletingSnapshot");
+ case MachineState_SettingUp: return GlobalCtx::tr("SettingUp");
+ case MachineState_Snapshotting: return GlobalCtx::tr("Snapshotting");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aState, aState), ::stringifyMachineState(aState));
+ }
+}
+
+/*static*/ const char *
+Global::stringifySessionState(SessionState_T aState)
+{
+ switch (aState)
+ {
+ case SessionState_Null: return GlobalCtx::tr("Null");
+ case SessionState_Unlocked: return GlobalCtx::tr("Unlocked");
+ case SessionState_Locked: return GlobalCtx::tr("Locked");
+ case SessionState_Spawning: return GlobalCtx::tr("Spawning");
+ case SessionState_Unlocking: return GlobalCtx::tr("Unlocking");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aState, aState), ::stringifySessionState(aState));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyStorageControllerType(StorageControllerType_T aType)
+{
+ switch (aType)
+ {
+ case StorageControllerType_Null: return GlobalCtx::tr("Null");
+ case StorageControllerType_LsiLogic: return GlobalCtx::tr("LsiLogic");
+ case StorageControllerType_BusLogic: return GlobalCtx::tr("BusLogic");
+ case StorageControllerType_IntelAhci: return GlobalCtx::tr("AHCI");
+ case StorageControllerType_PIIX3: return GlobalCtx::tr("PIIX3");
+ case StorageControllerType_PIIX4 : return GlobalCtx::tr("PIIX4");
+ case StorageControllerType_ICH6: return GlobalCtx::tr("ICH6");
+ case StorageControllerType_I82078: return GlobalCtx::tr("I82078");
+ case StorageControllerType_LsiLogicSas: return GlobalCtx::tr("LsiLogicSas");
+ case StorageControllerType_USB: return GlobalCtx::tr("USB");
+ case StorageControllerType_NVMe: return GlobalCtx::tr("NVMe");
+ case StorageControllerType_VirtioSCSI: return GlobalCtx::tr("VirtioSCSI");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aType, aType), ::stringifyStorageControllerType(aType));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyDeviceType(DeviceType_T aType)
+{
+ switch (aType)
+ {
+ case DeviceType_Null: return GlobalCtx::tr("Null");
+ case DeviceType_Floppy: return GlobalCtx::tr("Floppy");
+ case DeviceType_DVD: return GlobalCtx::tr("DVD");
+ case DeviceType_HardDisk: return GlobalCtx::tr("HardDisk");
+ case DeviceType_Network: return GlobalCtx::tr("Network");
+ case DeviceType_USB: return GlobalCtx::tr("USB");
+ case DeviceType_SharedFolder: return GlobalCtx::tr("ShardFolder");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aType, aType), ::stringifyDeviceType(aType));
+ }
+}
+
+#if 0 /* unused */
+
+/*static*/ const char *
+Global::stringifyStorageBus(StorageBus_T aBus)
+{
+ switch (aBus)
+ {
+ case StorageBus_Null: return GlobalCtx::tr("Null");
+ case StorageBus_IDE: return GlobalCtx::tr("IDE");
+ case StorageBus_SATA: return GlobalCtx::tr("SATA");
+ case StorageBus_Floppy: return GlobalCtx::tr("Floppy");
+ case StorageBus_SAS: return GlobalCtx::tr("SAS");
+ case StorageBus_USB: return GlobalCtx::tr("USB");
+ case StorageBus_PCIe: return GlobalCtx::tr("PCIe");
+ case StorageBus_VirtioSCSI: return GlobalCtx::tr("VirtioSCSI");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aBus, aBus), ::stringifyStorageBus(aBus));
+ }
+}
+
+/*static*/ const char *
+Global::stringifyReason(Reason_T aReason)
+{
+ switch (aReason)
+ {
+ case Reason_Unspecified: return GlobalCtx::tr("unspecified");
+ case Reason_HostSuspend: return GlobalCtx::tr("host suspend");
+ case Reason_HostResume: return GlobalCtx::tr("host resume");
+ case Reason_HostBatteryLow: return GlobalCtx::tr("host battery low");
+ case Reason_Snapshot: return GlobalCtx::tr("snapshot");
+ default:
+ AssertMsgFailedReturn(("%d (%#x)\n", aReason, aReason), ::stringifyReason(aReason));
+ }
+}
+
+#endif /* unused */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/GlobalStatusConversion.cpp b/src/VBox/Main/src-all/GlobalStatusConversion.cpp
new file mode 100644
index 00000000..5c6394c3
--- /dev/null
+++ b/src/VBox/Main/src-all/GlobalStatusConversion.cpp
@@ -0,0 +1,148 @@
+/* $Id: GlobalStatusConversion.cpp $ */
+/** @file
+ * VirtualBox COM global definitions - status code conversion.
+ *
+ * NOTE: This file is part of both VBoxC.dll and VBoxSVC.exe.
+ */
+
+/*
+ * Copyright (C) 2008-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 "Global.h"
+
+#include <iprt/assert.h>
+#include <VBox/err.h>
+
+
+/*static*/ int
+Global::vboxStatusCodeFromCOM(HRESULT aComStatus)
+{
+ switch (aComStatus)
+ {
+ case S_OK: return VINF_SUCCESS;
+
+ /* Standard COM status codes. See also RTErrConvertFromDarwinCOM */
+ case E_UNEXPECTED: return VERR_COM_UNEXPECTED;
+ case E_NOTIMPL: return VERR_NOT_IMPLEMENTED;
+ case E_OUTOFMEMORY: return VERR_NO_MEMORY;
+ case E_INVALIDARG: return VERR_INVALID_PARAMETER;
+ case E_NOINTERFACE: return VERR_NOT_SUPPORTED;
+ case E_POINTER: return VERR_INVALID_POINTER;
+#ifdef E_HANDLE
+ case E_HANDLE: return VERR_INVALID_HANDLE;
+#endif
+ case E_ABORT: return VERR_CANCELLED;
+ case E_FAIL: return VERR_GENERAL_FAILURE;
+ case E_ACCESSDENIED: return VERR_ACCESS_DENIED;
+
+ /* VirtualBox status codes */
+ case VBOX_E_OBJECT_NOT_FOUND: return VERR_COM_OBJECT_NOT_FOUND;
+ case VBOX_E_INVALID_VM_STATE: return VERR_COM_INVALID_VM_STATE;
+ case VBOX_E_VM_ERROR: return VERR_COM_VM_ERROR;
+ case VBOX_E_FILE_ERROR: return VERR_COM_FILE_ERROR;
+ case VBOX_E_IPRT_ERROR: return VERR_COM_IPRT_ERROR;
+ case VBOX_E_PDM_ERROR: return VERR_COM_PDM_ERROR;
+ case VBOX_E_INVALID_OBJECT_STATE: return VERR_COM_INVALID_OBJECT_STATE;
+ case VBOX_E_HOST_ERROR: return VERR_COM_HOST_ERROR;
+ case VBOX_E_NOT_SUPPORTED: return VERR_COM_NOT_SUPPORTED;
+ case VBOX_E_XML_ERROR: return VERR_COM_XML_ERROR;
+ case VBOX_E_INVALID_SESSION_STATE: return VERR_COM_INVALID_SESSION_STATE;
+ case VBOX_E_OBJECT_IN_USE: return VERR_COM_OBJECT_IN_USE;
+
+ default:
+ if (SUCCEEDED(aComStatus))
+ return VINF_SUCCESS;
+ /** @todo Check for the win32 facility and use the
+ * RTErrConvertFromWin32 function on windows. */
+ return VERR_UNRESOLVED_ERROR;
+ }
+}
+
+
+/*static*/ HRESULT
+Global::vboxStatusCodeToCOM(int aVBoxStatus)
+{
+ switch (aVBoxStatus)
+ {
+ case VINF_SUCCESS: return S_OK;
+
+ /* Standard COM status codes. */
+ case VERR_COM_UNEXPECTED: return E_UNEXPECTED;
+ case VERR_NOT_IMPLEMENTED: return E_NOTIMPL;
+ case VERR_NO_MEMORY: return E_OUTOFMEMORY;
+ case VERR_INVALID_PARAMETER: return E_INVALIDARG;
+ case VERR_NOT_SUPPORTED: return E_NOINTERFACE;
+ case VERR_INVALID_POINTER: return E_POINTER;
+#ifdef E_HANDLE
+ case VERR_INVALID_HANDLE: return E_HANDLE;
+#endif
+ case VERR_CANCELLED: return E_ABORT;
+ case VERR_GENERAL_FAILURE: return E_FAIL;
+ case VERR_ACCESS_DENIED: return E_ACCESSDENIED;
+
+ /* VirtualBox COM status codes. */
+ case VERR_COM_OBJECT_NOT_FOUND: return VBOX_E_OBJECT_NOT_FOUND;
+ case VERR_COM_INVALID_VM_STATE: return VBOX_E_INVALID_VM_STATE;
+ case VERR_COM_VM_ERROR: return VBOX_E_VM_ERROR;
+ case VERR_COM_FILE_ERROR: return VBOX_E_FILE_ERROR;
+ case VERR_COM_IPRT_ERROR: return VBOX_E_IPRT_ERROR;
+ case VERR_COM_PDM_ERROR: return VBOX_E_PDM_ERROR;
+ case VERR_COM_INVALID_OBJECT_STATE: return VBOX_E_INVALID_OBJECT_STATE;
+ case VERR_COM_HOST_ERROR: return VBOX_E_HOST_ERROR;
+ case VERR_COM_NOT_SUPPORTED: return VBOX_E_NOT_SUPPORTED;
+ case VERR_COM_XML_ERROR: return VBOX_E_XML_ERROR;
+ case VERR_COM_INVALID_SESSION_STATE: return VBOX_E_INVALID_SESSION_STATE;
+ case VERR_COM_OBJECT_IN_USE: return VBOX_E_OBJECT_IN_USE;
+
+ /* Other errors. */
+ case VERR_UNRESOLVED_ERROR: return E_FAIL;
+ case VERR_NOT_EQUAL: return VBOX_E_FILE_ERROR;
+ case VERR_FILE_NOT_FOUND: return VBOX_E_OBJECT_NOT_FOUND;
+ case VERR_IO_NOT_READY: return VBOX_E_INVALID_OBJECT_STATE;
+
+ /* Guest Control errors. */
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED: return VBOX_E_MAXIMUM_REACHED;
+ case VERR_GSTCTL_GUEST_ERROR: return VBOX_E_GSTCTL_GUEST_ERROR;
+
+ default:
+ if (RT_SUCCESS(aVBoxStatus))
+ return S_OK;
+
+ /* try categorize it */
+ if ( aVBoxStatus < 0
+ && ( aVBoxStatus > -1000
+ || (aVBoxStatus < -22000 && aVBoxStatus > -32766) )
+ )
+ return VBOX_E_IPRT_ERROR;
+ if ( aVBoxStatus < VERR_PDM_NO_SUCH_LUN / 100 * 10
+ && aVBoxStatus > VERR_PDM_NO_SUCH_LUN / 100 * 10 - 100)
+ return VBOX_E_PDM_ERROR;
+ if ( aVBoxStatus <= -1000
+ && aVBoxStatus > -5000 /* wrong, but so what... */)
+ return VBOX_E_VM_ERROR;
+
+ AssertMsgFailed(("%Rrc\n", aVBoxStatus));
+ return E_FAIL;
+ }
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/HashedPw.cpp b/src/VBox/Main/src-all/HashedPw.cpp
new file mode 100644
index 00000000..a1c601c1
--- /dev/null
+++ b/src/VBox/Main/src-all/HashedPw.cpp
@@ -0,0 +1,114 @@
+/* $Id: HashedPw.cpp $ */
+/** @file
+ * Main - Password Hashing
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#include "HashedPw.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * The prefix of a hashed password.
+ */
+static const char s_szHashedPwPrefix[] = "#SHA-512#";
+
+
+/**
+ * Checks if the password is a hashed one or not.
+ *
+ * Empty password are not considered hashed.
+ *
+ * @returns true if hashed, false if not.
+ * @param a_pstrPassword Password to inspect.
+ */
+bool VBoxIsPasswordHashed(RTCString const *a_pstrPassword)
+{
+ /* prefix */
+ if (!a_pstrPassword->startsWith(s_szHashedPwPrefix))
+ return false;
+
+ /* salt (optional) */
+ const char *pszSalt = a_pstrPassword->c_str() + sizeof(s_szHashedPwPrefix) - 1;
+ const char *pszSaltEnd = strchr(pszSalt, '#');
+ if (!pszSaltEnd)
+ return false;
+ while (pszSalt != pszSaltEnd)
+ {
+ if (!RT_C_IS_XDIGIT(*pszSalt))
+ return false;
+ pszSalt++;
+ }
+
+ /* hash */
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ int vrc = RTSha512FromString(pszSaltEnd + 1, abHash);
+ return RT_SUCCESS(vrc);
+}
+
+
+/**
+ * Hashes a plain text password.
+ *
+ * @param a_pstrPassword Plain text password to hash. This is both
+ * input and output.
+ */
+void VBoxHashPassword(RTCString *a_pstrPassword)
+{
+ AssertReturnVoid(!VBoxIsPasswordHashed(a_pstrPassword));
+
+ char szHashedPw[sizeof(s_szHashedPwPrefix) + 1 + RTSHA512_DIGEST_LEN];
+ if (a_pstrPassword->isEmpty())
+ szHashedPw[0] = '\0';
+ else
+ {
+ /* prefix */
+ char *pszHashedPw = szHashedPw;
+ strcpy(pszHashedPw, s_szHashedPwPrefix);
+ pszHashedPw += sizeof(s_szHashedPwPrefix) - 1;
+
+ /* salt */
+ *pszHashedPw++ = '#'; /* no salt yet */
+
+ /* hash */
+ uint8_t abHash[RTSHA512_HASH_SIZE];
+ RTSha512(a_pstrPassword->c_str(), a_pstrPassword->length(), abHash);
+ int vrc = RTSha512ToString(abHash, pszHashedPw, sizeof(szHashedPw) - (size_t)(pszHashedPw - &szHashedPw[0]));
+ AssertReleaseRC(vrc);
+ }
+
+ *a_pstrPassword = szHashedPw;
+}
+
diff --git a/src/VBox/Main/src-all/Logging.cpp b/src/VBox/Main/src-all/Logging.cpp
new file mode 100644
index 00000000..14d57d22
--- /dev/null
+++ b/src/VBox/Main/src-all/Logging.cpp
@@ -0,0 +1,33 @@
+/* $Id: Logging.cpp $ */
+/** @file
+ * VirtualBox Main Logging
+ */
+
+/*
+ * 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
+ */
+
+
+/*
+ * Main now always uses the defaults for logging, so nothing to do
+ * here for now.
+ */
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp b/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp
new file mode 100644
index 00000000..01d7a9f3
--- /dev/null
+++ b/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp
@@ -0,0 +1,289 @@
+/* $Id: MachineLaunchVMCommonWorker.cpp $ */
+/** @file
+ * VirtualBox Main - VM process launcher helper for VBoxSVC & VBoxSDS.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include "MachineLaunchVMCommonWorker.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define HOSTSUFF_EXE ".exe"
+#else
+# define HOSTSUFF_EXE ""
+#endif
+
+
+/**
+ * Launch a VM process.
+ *
+ * The function starts the new VM process. It is a caller's responsibility
+ * to make any checks before and after calling the function.
+ * The function is a part of both VBoxSVC and VBoxSDS, so any calls to IVirtualBox
+ * and IMachine interfaces are performed using the client API.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS when new VM process started.
+ * @retval VERR_INVALID_PARAMETER when either aMachine is not Machine interface
+ * or invalid aFrontend is specified. Hmm. Come to think of it, it
+ * could also be returned in some other cases, especially if the code
+ * is buggy, so I wouldn't rely on any exact meaning here!
+ * @retval VERR_INTERNAL_ERROR when something wrong.
+ *
+ * @param aNameOrId The Machine name or id interface the VM will start for.
+ * @param aComment The comment for new VM process.
+ * @param aFrontend The desired frontend for started VM.
+ * @param aEnvironmentChanges Additional environment variables in the putenv style
+ * (VAR=VAL for setting, VAR for unsetting) for new VM process.
+ * @param aExtraArg Extra argument for the VM process. Ignored if
+ * empty string.
+ * @param aFilename Start new VM using specified filename. Only filename
+ * without path is allowed. Default filename is used if
+ * empty.
+ * @param aFlags Flags for RTProcCreateEx functions family if
+ * required (RTPROC_FLAGS_XXX).
+ * @param aExtraData Additional data for RTProcCreateX functions family
+ * if required. Content is defined by the flags.
+ * @param aPid The PID of created process is returned here
+ */
+int MachineLaunchVMCommonWorker(const Utf8Str &aNameOrId,
+ const Utf8Str &aComment,
+ const Utf8Str &aFrontend,
+ const std::vector<com::Utf8Str> &aEnvironmentChanges,
+ const Utf8Str &aExtraArg,
+ const Utf8Str &aFilename,
+ uint32_t aFlags,
+ void *aExtraData,
+ RTPROCESS &aPid)
+{
+ NOREF(aNameOrId);
+ NOREF(aComment);
+ NOREF(aFlags);
+ NOREF(aExtraData);
+ NOREF(aExtraArg);
+ NOREF(aFilename);
+
+ /* Get the path to the executable directory w/ trailing slash: */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ AssertRCReturn(vrc, vrc);
+ size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
+ char *pszNamePart = &szPath[cbBufLeft]; NOREF(pszNamePart);
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ /* The process started when launching a VM with separate UI/VM processes is always
+ * the UI process, i.e. needs special handling as it won't claim the session. */
+ bool fSeparate = aFrontend.endsWith("separate", Utf8Str::CaseInsensitive); NOREF(fSeparate);
+
+ aPid = NIL_RTPROCESS;
+
+ RTENV hEnv = RTENV_DEFAULT;
+ if (!aEnvironmentChanges.empty())
+ {
+#ifdef IN_VBOXSVC
+ /* VBoxSVC: clone the current environment */
+ vrc = RTEnvClone(&hEnv, RTENV_DEFAULT);
+#else
+ /* VBoxSDS: Create a change record environment since RTProcCreateEx has to
+ build the final environment from the profile of the VBoxSDS caller. */
+ aFlags |= RTPROC_FLAGS_ENV_CHANGE_RECORD;
+ vrc = RTEnvCreateChangeRecord(&hEnv);
+#endif
+ AssertRCReturn(vrc, vrc);
+
+ /* Apply the specified environment changes. */
+ for (std::vector<com::Utf8Str>::const_iterator itEnv = aEnvironmentChanges.begin();
+ itEnv != aEnvironmentChanges.end();
+ ++itEnv)
+ {
+ vrc = RTEnvPutEx(hEnv, itEnv->c_str());
+ AssertRCReturnStmt(vrc, RTEnvDestroy(hEnv), vrc);
+ }
+ }
+
+#ifdef VBOX_WITH_QTGUI
+ if ( !aFrontend.compare("gui", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/Qt", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("gui/separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/Qt/separate", Utf8Str::CaseInsensitive))
+ {
+# ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */
+
+# define OSX_APP_NAME "VirtualBoxVM"
+# define OSX_APP_PATH_FMT "/Resources/%s.app/Contents/MacOS/VirtualBoxVM"
+# define OSX_APP_PATH_WITH_NAME "/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM"
+
+ /* Modify the base path so that we don't need to use ".." below. */
+ RTPathStripTrailingSlash(szPath);
+ RTPathStripFilename(szPath);
+ cbBufLeft = strlen(szPath);
+ pszNamePart = &szPath[cbBufLeft]; Assert(!*pszNamePart);
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ if (aFilename.isNotEmpty() && strpbrk(aFilename.c_str(), "./\\:") == NULL)
+ {
+ ssize_t cch = RTStrPrintf2(pszNamePart, cbBufLeft, OSX_APP_PATH_FMT, aFilename.c_str());
+ AssertReturn(cch > 0, VERR_FILENAME_TOO_LONG);
+ /* there is a race, but people using this deserve the failure */
+ if (!RTFileExists(szPath))
+ *pszNamePart = '\0';
+ }
+ if (!*pszNamePart)
+ {
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, OSX_APP_PATH_WITH_NAME);
+ AssertRCReturn(vrc, vrc);
+ }
+# else
+ static const char s_szVirtualBox_exe[] = "VirtualBoxVM" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
+ AssertRCReturn(vrc, vrc);
+# endif
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ "--no-startvm-errormsgbox",
+ NULL, /* For "--separate". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 6;
+ if (fSeparate)
+ apszArgs[iArg++] = "--separate";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_QTGUI */
+ if (0)
+ ;
+#endif /* VBOX_WITH_QTGUI */
+
+ else
+
+#ifdef VBOX_WITH_VBOXSDL
+ if ( !aFrontend.compare("sdl", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/SDL", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("sdl/separate", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("GUI/SDL/separate", Utf8Str::CaseInsensitive))
+ {
+ static const char s_szVBoxSDL_exe[] = "VBoxSDL" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxSDL_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ NULL, /* For "--separate". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 5;
+ if (fSeparate)
+ apszArgs[iArg++] = "--separate";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_VBOXSDL */
+ if (0)
+ ;
+#endif /* !VBOX_WITH_VBOXSDL */
+
+ else
+
+#ifdef VBOX_WITH_HEADLESS
+ if ( !aFrontend.compare("headless", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("capture", Utf8Str::CaseInsensitive)
+ || !aFrontend.compare("vrdp", Utf8Str::CaseInsensitive) /* Deprecated. Same as headless. */
+ )
+ {
+ /* On pre-4.0 the "headless" type was used for passing "--vrdp off" to VBoxHeadless to let it work in OSE,
+ * which did not contain VRDP server. In VBox 4.0 the remote desktop server (VRDE) is optional,
+ * and a VM works even if the server has not been installed.
+ * So in 4.0 the "headless" behavior remains the same for default VBox installations.
+ * Only if a VRDE has been installed and the VM enables it, the "headless" will work
+ * differently in 4.0 and 3.x.
+ */
+ static const char s_szVBoxHeadless_exe[] = "VBoxHeadless" HOSTSUFF_EXE;
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxHeadless_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--comment", aComment.c_str(),
+ "--startvm", aNameOrId.c_str(),
+ "--vrde", "config",
+ NULL, /* For "--capture". */
+ NULL, /* For "--sup-startup-log". */
+ NULL
+ };
+ unsigned iArg = 7;
+ if (!aFrontend.compare("capture", Utf8Str::CaseInsensitive))
+ apszArgs[iArg++] = "--capture";
+ if (aExtraArg.isNotEmpty())
+ apszArgs[iArg++] = aExtraArg.c_str();
+
+# ifdef RT_OS_WINDOWS
+ aFlags |= RTPROC_FLAGS_NO_WINDOW;
+# endif
+ vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
+ }
+#else /* !VBOX_WITH_HEADLESS */
+ if (0)
+ ;
+#endif /* !VBOX_WITH_HEADLESS */
+ else
+ vrc = VERR_INVALID_PARAMETER;
+
+ RTEnvDestroy(hEnv);
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/Makefile.kup b/src/VBox/Main/src-all/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-all/Makefile.kup
diff --git a/src/VBox/Main/src-all/NvramStoreImpl.cpp b/src/VBox/Main/src-all/NvramStoreImpl.cpp
new file mode 100644
index 00000000..833ccee8
--- /dev/null
+++ b/src/VBox/Main/src-all/NvramStoreImpl.cpp
@@ -0,0 +1,1648 @@
+/* $Id: NvramStoreImpl.cpp $ */
+/** @file
+ * VirtualBox COM NVRAM store class implementation
+ */
+
+/*
+ * Copyright (C) 2021-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_NVRAMSTORE
+#include "LoggingNew.h"
+
+#include "NvramStoreImpl.h"
+#ifdef VBOX_COM_INPROC
+# include "ConsoleImpl.h"
+#else
+# include "MachineImpl.h"
+# include "GuestOSTypeImpl.h"
+# include "AutoStateDep.h"
+#endif
+#include "UefiVariableStoreImpl.h"
+#include "VirtualBoxImpl.h"
+
+#include "AutoCaller.h"
+
+#include <VBox/com/array.h>
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/err.h>
+
+#include <iprt/cpp/utils.h>
+#include <iprt/efi.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+// defines
+////////////////////////////////////////////////////////////////////////////////
+
+/** Version of the NVRAM saved state unit. */
+#define NVRAM_STORE_SAVED_STATE_VERSION 1
+
+
+// globals
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * NVRAM store driver instance data.
+ */
+typedef struct DRVMAINNVRAMSTORE
+{
+ /** Pointer to the keyboard object. */
+ NvramStore *pNvramStore;
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Our VFS connector interface. */
+ PDMIVFSCONNECTOR IVfs;
+} DRVMAINNVRAMSTORE, *PDRVMAINNVRAMSTORE;
+
+/** The NVRAM store map keyed by namespace/entity. */
+typedef std::map<Utf8Str, RTVFSFILE> NvramStoreMap;
+/** The NVRAM store map iterator. */
+typedef std::map<Utf8Str, RTVFSFILE>::iterator NvramStoreIter;
+
+struct BackupableNvramStoreData
+{
+ BackupableNvramStoreData()
+ { }
+
+ /** The NVRAM file path. */
+ com::Utf8Str strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /** The key id used for encrypting the NVRAM file */
+ com::Utf8Str strKeyId;
+ /** The key store containing the encrypting DEK */
+ com::Utf8Str strKeyStore;
+#endif
+ /** The NVRAM store. */
+ NvramStoreMap mapNvram;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// NvramStore::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct NvramStore::Data
+{
+ Data()
+ : pParent(NULL)
+#ifdef VBOX_COM_INPROC
+ , cRefs(0)
+ , fSsmSaved(false)
+#endif
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ , mpKeyStore(NULL)
+#endif
+ { }
+
+#ifdef VBOX_COM_INPROC
+ /** The Console owning this NVRAM store. */
+ Console * const pParent;
+ /** Number of references held to this NVRAM store from the various devices/drivers. */
+ volatile uint32_t cRefs;
+ /** Flag whether the NVRAM data was saved during a save state operation
+ * preventing it from getting written to the backing file. */
+ bool fSsmSaved;
+#else
+ /** The Machine object owning this NVRAM store. */
+ Machine * const pParent;
+ /** The peer NVRAM store object. */
+ ComObjPtr<NvramStore> pPeer;
+ /** The UEFI variable store. */
+ const ComObjPtr<UefiVariableStore> pUefiVarStore;
+#endif
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ /* Store for secret keys. */
+ SecretKeyStore *mpKeyStore;
+#endif
+
+ Backupable<BackupableNvramStoreData> bd;
+};
+
+// constructor / destructor
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(NvramStore)
+
+HRESULT NvramStore::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void NvramStore::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initialization stuff shared across the different methods.
+ *
+ * @returns COM result indicator
+ */
+int NvramStore::initImpl()
+{
+ m = new Data();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+# ifdef VBOX_COM_INPROC
+ bool fNonPageable = true;
+# else
+ /* Non-pageable memory is not accessible for non-VM process */
+ bool fNonPageable = false;
+# endif
+
+ m->mpKeyStore = new SecretKeyStore(fNonPageable /* fKeyBufNonPageable */);
+ AssertReturn(m->mpKeyStore, VERR_NO_MEMORY);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+#if !defined(VBOX_COM_INPROC)
+/**
+ * Initializes the NVRAM store object.
+ *
+ * @returns COM result indicator
+ */
+HRESULT NvramStore::init(Machine *aParent)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ int vrc = initImpl();
+ if (RT_FAILURE(vrc))
+ return E_FAIL;
+
+ /* share the parent weakly */
+ unconst(m->pParent) = aParent;
+
+ m->bd.allocate();
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the NVRAM store object given another NVRAM store object
+ * (a kind of copy constructor). This object shares data with
+ * the object passed as an argument.
+ *
+ * @note This object must be destroyed before the original object
+ * it shares data with is destroyed.
+ */
+HRESULT NvramStore::init(Machine *aParent, NvramStore *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+ m->pPeer = that;
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.share(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * Initializes the guest object given another guest object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ */
+HRESULT NvramStore::initCopy(Machine *aParent, NvramStore *that)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aParent: %p, that: %p\n", aParent, that));
+
+ ComAssertRet(aParent && that, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+ // mPeer is left null
+
+ AutoWriteLock thatlock(that COMMA_LOCKVAL_SRC_POS);
+ m->bd.attachCopy(that->m->bd);
+
+ autoInitSpan.setSucceeded();
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+#else
+
+/**
+ * Initializes the NVRAM store object.
+ *
+ * @returns COM result indicator
+ * @param aParent Handle of our parent object
+ * @param strNonVolatileStorageFile The NVRAM file path.
+ */
+HRESULT NvramStore::init(Console *aParent, const com::Utf8Str &strNonVolatileStorageFile)
+{
+ LogFlowThisFunc(("aParent=%p\n", aParent));
+
+ ComAssertRet(aParent, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ initImpl();
+
+ unconst(m->pParent) = aParent;
+
+ m->bd.allocate();
+ m->bd->strNvramPath = strNonVolatileStorageFile;
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+#endif /* VBOX_COM_INPROC */
+
+
+/**
+ * Uninitializes the instance and sets the ready flag to FALSE.
+ * Called either from FinalRelease() or by the parent when it gets destroyed.
+ */
+void NvramStore::uninit()
+{
+ LogFlowThisFuncEnter();
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(m->pParent) = NULL;
+#ifndef VBOX_COM_INPROC
+ unconst(m->pUefiVarStore) = NULL;
+#endif
+
+ /* Delete the NVRAM content. */
+ NvramStoreIter it = m->bd->mapNvram.begin();
+ while (it != m->bd->mapNvram.end())
+ {
+ RTVfsFileRelease(it->second);
+ it++;
+ }
+
+ m->bd->mapNvram.clear();
+ m->bd.free();
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (m->mpKeyStore != NULL)
+ delete m->mpKeyStore;
+#endif
+
+ delete m;
+ m = NULL;
+
+ LogFlowThisFuncLeave();
+}
+
+
+HRESULT NvramStore::getNonVolatileStorageFile(com::Utf8Str &aNonVolatileStorageFile)
+{
+#ifndef VBOX_COM_INPROC
+ Utf8Str strTmp;
+ {
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ strTmp = m->bd->strNvramPath;
+ }
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ if (strTmp.isEmpty())
+ strTmp = m->pParent->i_getDefaultNVRAMFilename();
+ if (strTmp.isNotEmpty())
+ m->pParent->i_calculateFullPath(strTmp, aNonVolatileStorageFile);
+#else
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aNonVolatileStorageFile = m->bd->strNvramPath;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::getUefiVariableStore(ComPtr<IUefiVariableStore> &aUefiVarStore)
+{
+#ifndef VBOX_COM_INPROC
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.hrc())) return adep.hrc();
+
+ Utf8Str strPath;
+ NvramStore::getNonVolatileStorageFile(strPath);
+
+ /* We need a write lock because of the lazy initialization. */
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* Check if we have to create the UEFI variable store object */
+ HRESULT hrc = S_OK;
+ if (!m->pUefiVarStore)
+ {
+ /* Load the NVRAM file first if it isn't already. */
+ if (!m->bd->mapNvram.size())
+ {
+ int vrc = i_loadStore(strPath.c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Loading the NVRAM store failed (%Rrc)\n"), vrc);
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ {
+ unconst(m->pUefiVarStore).createObject();
+ m->pUefiVarStore->init(this, m->pParent);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The UEFI NVRAM file is not existing for this machine."));
+ }
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ m->pUefiVarStore.queryInterfaceTo(aUefiVarStore.asOutParam());
+
+ /* Mark the NVRAM store as potentially modified. */
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+
+ return hrc;
+#else
+ NOREF(aUefiVarStore);
+ return E_NOTIMPL;
+#endif
+}
+
+
+HRESULT NvramStore::getKeyId(com::Utf8Str &aKeyId)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyId = m->bd->strKeyId;
+#else
+ aKeyId = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::getKeyStore(com::Utf8Str &aKeyStore)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ aKeyStore = m->bd->strKeyStore;
+#else
+ aKeyStore = com::Utf8Str::Empty;
+#endif
+
+ return S_OK;
+}
+
+
+HRESULT NvramStore::initUefiVariableStore(ULONG aSize)
+{
+#ifndef VBOX_COM_INPROC
+ if (aSize != 0)
+ return setError(E_NOTIMPL, tr("Supporting another NVRAM size apart from the default one is not supported right now"));
+
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.hrc())) return adep.hrc();
+
+ Utf8Str strPath;
+ NvramStore::getNonVolatileStorageFile(strPath);
+
+ /* We need a write lock because of the lazy initialization. */
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->pParent->i_getFirmwareType() == FirmwareType_BIOS)
+ return setError(VBOX_E_NOT_SUPPORTED, tr("The selected firmware type doesn't support a UEFI variable store"));
+
+ /* Load the NVRAM file first if it isn't already. */
+ HRESULT hrc = S_OK;
+ if (!m->bd->mapNvram.size())
+ {
+ int vrc = i_loadStore(strPath.c_str());
+ if (RT_FAILURE(vrc))
+ hrc = setError(E_FAIL, tr("Loading the NVRAM store failed (%Rrc)\n"), vrc);
+ }
+
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = VINF_SUCCESS;
+ RTVFSFILE hVfsUefiVarStore = NIL_RTVFSFILE;
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ hVfsUefiVarStore = it->second;
+ else
+ {
+ /* Create a new file. */
+ vrc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, 0 /*cbEstimate*/, &hVfsUefiVarStore);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo The size is hardcoded to match what the firmware image uses right now which is a gross hack... */
+ vrc = RTVfsFileSetSize(hVfsUefiVarStore, 540672, RTVFSFILE_SIZE_F_NORMAL);
+ if (RT_SUCCESS(vrc))
+ m->bd->mapNvram["efi/nvram"] = hVfsUefiVarStore;
+ else
+ RTVfsFileRelease(hVfsUefiVarStore);
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTEfiVarStoreCreate(hVfsUefiVarStore, 0 /*offStore*/, 0 /*cbStore*/, RTEFIVARSTORE_CREATE_F_DEFAULT, 0 /*cbBlock*/,
+ NULL /*pErrInfo*/);
+ if (RT_FAILURE(vrc))
+ return setError(E_FAIL, tr("Failed to initialize the UEFI variable store (%Rrc)"), vrc);
+ }
+ else
+ return setError(E_FAIL, tr("Failed to initialize the UEFI variable store (%Rrc)"), vrc);
+
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+
+ return hrc;
+#else
+ NOREF(aSize);
+ return E_NOTIMPL;
+#endif
+}
+
+
+Utf8Str NvramStore::i_getNonVolatileStorageFile()
+{
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), Utf8Str::Empty);
+
+ Utf8Str strTmp;
+ NvramStore::getNonVolatileStorageFile(strTmp);
+ return strTmp;
+}
+
+
+/**
+ * Loads the NVRAM store from the given TAR filesystem stream.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFssTar Handle to the tar filesystem stream.
+ */
+int NvramStore::i_loadStoreFromTar(RTVFSFSSTREAM hVfsFssTar)
+{
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrieve the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ vrc = RTVfsFsStrmNext(hVfsFssTar, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(vrc))
+ {
+ if (vrc == VERR_EOF)
+ vrc = VINF_SUCCESS;
+ break;
+ }
+
+ RTFSOBJINFO UnixInfo;
+ vrc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(vrc))
+ {
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ {
+ LogRel(("NvramStore: Loading '%s' from archive\n", pszName));
+ RTVFSIOSTREAM hVfsIosEntry = RTVfsObjToIoStream(hVfsObj);
+ Assert(hVfsIosEntry != NIL_RTVFSIOSTREAM);
+
+ RTVFSFILE hVfsFileEntry;
+ vrc = RTVfsMemorizeIoStreamAsFile(hVfsIosEntry, RTFILE_O_READ | RTFILE_O_WRITE, &hVfsFileEntry);
+ if (RT_FAILURE(vrc))
+ break;
+ RTVfsIoStrmRelease(hVfsIosEntry);
+
+ m->bd->mapNvram[Utf8Str(pszName)] = hVfsFileEntry;
+ break;
+ }
+ case RTFS_TYPE_DIRECTORY:
+ break;
+ default:
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+
+/**
+ * Sets up the encryption or decryption machinery.
+ *
+ * @returns VBox status code.
+ * @param hVfsIosInOut Handle to the input stream to be decrypted or the destination to the encrypted
+ * output is written to.
+ * @param fEncrypt Flag whether to setup encryption or decryption.
+ * @param ppCryptoIf Where to store the pointer to the cryptographic interface which needs to be released
+ * when done.
+ * @param ppKey Where to store the pointer to the secret key buffer which needs to be released when done.
+ * @param phVfsIos Where to store the handle to the plaintext I/O stream (either input or output) on success.
+ */
+int NvramStore::i_setupEncryptionOrDecryption(RTVFSIOSTREAM hVfsIosInOut, bool fEncrypt,
+ PCVBOXCRYPTOIF *ppCryptoIf, SecretKey **ppKey,
+ PRTVFSIOSTREAM phVfsIos)
+{
+ int vrc = VINF_SUCCESS;
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+ const char *pszPassword = NULL;
+
+ vrc = i_retainCryptoIf(&pCryptoIf);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = m->mpKeyStore->retainSecretKey(m->bd->strKeyId, &pKey);
+ if (RT_SUCCESS(vrc))
+ {
+ pszPassword = (const char *)pKey->getKeyBuffer();
+ if (fEncrypt)
+ vrc = pCryptoIf->pfnCryptoIoStrmFromVfsIoStrmEncrypt(hVfsIosInOut, m->bd->strKeyStore.c_str(), pszPassword,
+ phVfsIos);
+ else
+ vrc = pCryptoIf->pfnCryptoIoStrmFromVfsIoStrmDecrypt(hVfsIosInOut, m->bd->strKeyStore.c_str(), pszPassword,
+ phVfsIos);
+ if (RT_SUCCESS(vrc))
+ {
+ *ppCryptoIf = pCryptoIf;
+ *ppKey = pKey;
+ return VINF_SUCCESS;
+ }
+ else
+ LogRelMax(10, ("Failed to decrypt the NVRAM store using secret key ID '%s' with %Rrc\n",
+ m->bd->strKeyId.c_str(), vrc));
+
+ m->mpKeyStore->releaseSecretKey(m->bd->strKeyId);
+ }
+ else
+ LogRelMax(10, ("Failed to retain the secret key ID '%s' with %Rrc\n",
+ m->bd->strKeyId.c_str(), vrc));
+
+ i_releaseCryptoIf(pCryptoIf);
+ }
+ else
+ LogRelMax(10, ("Failed to retain the cryptographic interface with %Rrc\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Releases all resources acquired in NvramStore::i_setupEncryptionOrDecryption().
+ *
+ * @param hVfsIos Handle to the I/O stream previously created.
+ * @param pCryptoIf Pointer to the cryptographic interface being released.
+ * @param pKey Pointer to the key buffer being released.
+ */
+void NvramStore::i_releaseEncryptionOrDecryptionResources(RTVFSIOSTREAM hVfsIos, PCVBOXCRYPTOIF pCryptoIf,
+ SecretKey *pKey)
+{
+ Assert(hVfsIos != NIL_RTVFSIOSTREAM);
+ AssertPtr(pCryptoIf);
+ AssertPtr(pKey);
+
+ i_releaseCryptoIf(pCryptoIf);
+ pKey->release();
+ RTVfsIoStrmRelease(hVfsIos);
+}
+
+#endif /* VBOX_WITH_FULL_VM_ENCRYPTION */
+
+/**
+ * Loads the NVRAM store.
+ *
+ * @returns IPRT status code.
+ */
+int NvramStore::i_loadStore(const char *pszPath)
+{
+ uint64_t cbStore = 0;
+ int vrc = RTFileQuerySizeByPath(pszPath, &cbStore);
+ if (RT_SUCCESS(vrc))
+ {
+ if (cbStore <= _1M) /* Arbitrary limit to fend off bogus files because the file will be read into memory completely. */
+ {
+ /*
+ * Old NVRAM files just consist of the EFI variable store whereas starting
+ * with VirtualBox 7.0 and the introduction of the TPM the need to handle multiple
+ * independent NVRAM files came up. For those scenarios all NVRAM states are collected
+ * in a tar archive.
+ *
+ * Here we detect whether the file is the new tar archive format or whether it is just
+ * the plain EFI variable store file.
+ */
+ RTVFSIOSTREAM hVfsIosNvram;
+ vrc = RTVfsIoStrmOpenNormal(pszPath, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE,
+ &hVfsIosNvram);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosDecrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIosNvram, false /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosDecrypted);
+#endif
+ if (RT_SUCCESS(vrc))
+ {
+ /* Read the content. */
+ RTVFSFILE hVfsFileNvram;
+ vrc = RTVfsMemorizeIoStreamAsFile( hVfsIosDecrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosDecrypted
+ : hVfsIosNvram,
+ RTFILE_O_READ, &hVfsFileNvram);
+ if (RT_SUCCESS(vrc))
+ {
+ if (RT_SUCCESS(vrc))
+ {
+ /* Try to parse it as an EFI variable store. */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFS hVfsEfiVarStore;
+ vrc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, RTVFSMNT_F_READ_ONLY, 0 /*fVarStoreFlags*/,
+ &hVfsEfiVarStore, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVfsFileRetain(hVfsFileNvram); /* Retain a new reference for the map. */
+ m->bd->mapNvram[Utf8Str("efi/nvram")] = hVfsFileNvram;
+
+ RTVfsRelease(hVfsEfiVarStore);
+ }
+ else if (vrc == VERR_VFS_UNKNOWN_FORMAT)
+ {
+ /* Check for the new style tar archive. */
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVFSIOSTREAM hVfsIosTar = RTVfsFileToIoStream(hVfsFileNvram);
+ Assert(hVfsIosTar != NIL_RTVFSIOSTREAM);
+
+ RTVFSFSSTREAM hVfsFssTar;
+ vrc = RTZipTarFsStreamFromIoStream(hVfsIosTar, 0 /*fFlags*/, &hVfsFssTar);
+ RTVfsIoStrmRelease(hVfsIosTar);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = i_loadStoreFromTar(hVfsFssTar);
+ RTVfsFsStrmRelease(hVfsFssTar);
+ }
+ else
+ LogRel(("The given NVRAM file is neither a raw UEFI variable store nor a tar archive (opening failed with %Rrc)\n", vrc));
+ }
+ else
+ LogRel(("Opening the UEFI variable store '%s' failed with %Rrc%RTeim\n", pszPath, vrc, &ErrInfo.Core));
+
+ RTVfsFileRelease(hVfsFileNvram);
+ }
+ else
+ LogRel(("Failed to memorize NVRAM store '%s' with %Rrc\n", pszPath, vrc));
+ }
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosDecrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosDecrypted, pCryptoIf, pKey);
+#endif
+
+ RTVfsIoStrmRelease(hVfsIosNvram);
+ }
+ else
+ LogRelMax(10, ("NVRAM store '%s' couldn't be opened with %Rrc\n", pszPath, vrc));
+ }
+ else
+ {
+ LogRelMax(10, ("NVRAM store '%s' exceeds limit of %u bytes, actual size is %u\n",
+ pszPath, _1M, cbStore));
+ vrc = VERR_OUT_OF_RANGE;
+ }
+ }
+ else if (vrc == VERR_FILE_NOT_FOUND) /* Valid for the first run where no NVRAM file is there. */
+ vrc = VINF_SUCCESS;
+
+ return vrc;
+}
+
+
+/**
+ * Saves the NVRAM store as a tar archive.
+ */
+int NvramStore::i_saveStoreAsTar(const char *pszPath)
+{
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSIOSTREAM hVfsIos;
+
+ int vrc = RTVfsChainOpenIoStream(pszPath, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosEncrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIos, true /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosEncrypted);
+#endif
+
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSFSSTREAM hVfsFss;
+ vrc = RTZipTarFsStreamToIoStream( hVfsIosEncrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosEncrypted
+ : hVfsIos,
+ RTZIPTARFORMAT_GNU, 0 /*fFlags*/, &hVfsFss);
+ if (RT_SUCCESS(vrc))
+ {
+ NvramStoreIter it = m->bd->mapNvram.begin();
+
+ while (it != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+
+ RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFile);
+ vrc = RTVfsFsStrmAdd(hVfsFss, it->first.c_str(), hVfsObj, 0 /*fFlags*/);
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(vrc))
+ break;
+
+ it++;
+ }
+
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosEncrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosEncrypted, pCryptoIf, pKey);
+#endif
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+
+ return vrc;
+}
+
+
+int NvramStore::i_retainCryptoIf(PCVBOXCRYPTOIF *ppCryptoIf)
+{
+#ifdef VBOX_COM_INPROC
+ return m->pParent->i_retainCryptoIf(ppCryptoIf);
+#else
+ HRESULT hrc = m->pParent->i_getVirtualBox()->i_retainCryptoIf(ppCryptoIf);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+
+ return VERR_COM_IPRT_ERROR;
+#endif
+}
+
+
+int NvramStore::i_releaseCryptoIf(PCVBOXCRYPTOIF pCryptoIf)
+{
+#ifdef VBOX_COM_INPROC
+ return m->pParent->i_releaseCryptoIf(pCryptoIf);
+#else
+ HRESULT hrc = m->pParent->i_getVirtualBox()->i_releaseCryptoIf(pCryptoIf);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+
+ return VERR_COM_IPRT_ERROR;
+#endif
+}
+
+
+/**
+ * Saves the NVRAM store.
+ *
+ * @returns IPRT status code.
+ */
+int NvramStore::i_saveStore(void)
+{
+ int vrc = VINF_SUCCESS;
+
+ Utf8Str strTmp;
+ NvramStore::getNonVolatileStorageFile(strTmp);
+
+ /*
+ * Only store the NVRAM content if the path is not empty, if it is
+ * this means the VM was just created and the store was nnot saved yet,
+ * see @bugref{10191}.
+ */
+ if (strTmp.isNotEmpty())
+ {
+ /*
+ * Skip creating the tar archive if only the UEFI NVRAM content is available in order
+ * to maintain backwards compatibility. As soon as there is more than one entry or
+ * it doesn't belong to the UEFI the tar archive will be created.
+ */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ if ( m->bd->mapNvram.size() == 1
+ && m->bd->mapNvram.find(Utf8Str("efi/nvram")) != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFileNvram = m->bd->mapNvram[Utf8Str("efi/nvram")];
+
+ vrc = RTVfsFileSeek(hVfsFileNvram, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc); RT_NOREF(vrc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ vrc = RTVfsIoStrmOpenNormal(strTmp.c_str(), RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
+ &hVfsIosDst);
+ if (RT_SUCCESS(vrc))
+ {
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsFileToIoStream(hVfsFileNvram);
+ Assert(hVfsIosSrc != NIL_RTVFSIOSTREAM);
+
+ RTVFSIOSTREAM hVfsIosEncrypted = NIL_RTVFSIOSTREAM;
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ PCVBOXCRYPTOIF pCryptoIf = NULL;
+ SecretKey *pKey = NULL;
+
+ if ( m->bd->strKeyId.isNotEmpty()
+ && m->bd->strKeyStore.isNotEmpty())
+ vrc = i_setupEncryptionOrDecryption(hVfsIosDst, true /*fEncrypt*/,
+ &pCryptoIf, &pKey, &hVfsIosEncrypted);
+#endif
+
+ vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc,
+ hVfsIosEncrypted != NIL_RTVFSIOSTREAM
+ ? hVfsIosEncrypted
+ : hVfsIosDst
+ , 0 /*cbBufHint*/);
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ if (hVfsIosEncrypted != NIL_RTVFSIOSTREAM)
+ i_releaseEncryptionOrDecryptionResources(hVfsIosEncrypted, pCryptoIf, pKey);
+#endif
+
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ }
+ else if (m->bd->mapNvram.size())
+ vrc = i_saveStoreAsTar(strTmp.c_str());
+ /* else: No NVRAM content to store so we are done here. */
+ }
+
+ return vrc;
+}
+
+
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+HRESULT NvramStore::i_updateEncryptionSettings(const com::Utf8Str &strKeyId,
+ const com::Utf8Str &strKeyStore)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd.backup();
+ m->bd->strKeyId = strKeyId;
+ m->bd->strKeyStore = strKeyStore;
+
+ /* clear all passwords because they are invalid now */
+ m->mpKeyStore->deleteAllSecretKeys(false, true);
+
+ alock.release();
+ AutoWriteLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+#ifndef VBOX_COM_INPROC
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+#endif
+ return S_OK;
+}
+
+
+HRESULT NvramStore::i_getEncryptionSettings(com::Utf8Str &strKeyId,
+ com::Utf8Str &strKeyStore)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ strKeyId = m->bd->strKeyId;
+ strKeyStore = m->bd->strKeyStore;
+
+ return S_OK;
+}
+
+
+int NvramStore::i_addPassword(const Utf8Str &strKeyId, const Utf8Str &strPassword)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.hrc(), VERR_INVALID_STATE);
+
+ /* keep only required password */
+ if (strKeyId != m->bd->strKeyId)
+ return VINF_SUCCESS;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->mpKeyStore->addSecretKey(strKeyId, (const uint8_t *)strPassword.c_str(), strPassword.length() + 1);
+}
+
+
+int NvramStore::i_removePassword(const Utf8Str &strKeyId)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.hrc(), VERR_INVALID_STATE);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ return m->mpKeyStore->deleteSecretKey(strKeyId);
+}
+
+
+int NvramStore::i_removeAllPasswords()
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturn(autoCaller.hrc(), VERR_INVALID_STATE);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->mpKeyStore->deleteAllSecretKeys(false, true);
+ return VINF_SUCCESS;
+}
+#endif
+
+
+#ifndef VBOX_COM_INPROC
+HRESULT NvramStore::i_retainUefiVarStore(PRTVFS phVfs, bool fReadonly)
+{
+ /* the machine needs to be mutable */
+ AutoMutableStateDependency adep(m->pParent);
+ if (FAILED(adep.hrc())) return adep.hrc();
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ HRESULT hrc = S_OK;
+ NvramStoreIter it = m->bd->mapNvram.find("efi/nvram");
+ if (it != m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFileNvram = it->second;
+ RTVFS hVfsEfiVarStore;
+ uint32_t fMntFlags = fReadonly ? RTVFSMNT_F_READ_ONLY : 0;
+
+ int vrc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, fMntFlags, 0 /*fVarStoreFlags*/, &hVfsEfiVarStore,
+ NULL /*pErrInfo*/);
+ if (RT_SUCCESS(vrc))
+ {
+ *phVfs = hVfsEfiVarStore;
+ if (!fReadonly)
+ m->pParent->i_setModified(Machine::IsModified_NvramStore);
+ }
+ else
+ hrc = setError(E_FAIL, tr("Opening the UEFI variable store failed (%Rrc)."), vrc);
+ }
+ else
+ hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The UEFI NVRAM file is not existing for this machine."));
+
+ return hrc;
+}
+
+
+HRESULT NvramStore::i_releaseUefiVarStore(RTVFS hVfs)
+{
+ RTVfsRelease(hVfs);
+ return S_OK;
+}
+
+
+/**
+ * Loads settings from the given machine node.
+ * May be called once right after this object creation.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT NvramStore::i_loadSettings(const settings::NvramSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->bd->strNvramPath = data.strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ m->bd->strKeyId = data.strKeyId;
+ m->bd->strKeyStore = data.strKeyStore;
+#endif
+
+ Utf8Str strTmp(m->bd->strNvramPath);
+ if (strTmp.isNotEmpty())
+ m->pParent->i_copyPathRelativeToMachine(strTmp, m->bd->strNvramPath);
+ if ( m->pParent->i_getFirmwareType() == FirmwareType_BIOS
+ || m->bd->strNvramPath == m->pParent->i_getDefaultNVRAMFilename())
+ m->bd->strNvramPath.setNull();
+
+ return S_OK;
+}
+
+/**
+ * Saves settings to the given machine node.
+ *
+ * @param data Configuration settings.
+ *
+ * @note Locks this object for writing.
+ */
+HRESULT NvramStore::i_saveSettings(settings::NvramSettings &data)
+{
+ AutoCaller autoCaller(this);
+ AssertComRCReturnRC(autoCaller.hrc());
+
+ AutoWriteLock wlock(this COMMA_LOCKVAL_SRC_POS);
+
+ data.strNvramPath = m->bd->strNvramPath;
+#ifdef VBOX_WITH_FULL_VM_ENCRYPTION
+ data.strKeyId = m->bd->strKeyId;
+ data.strKeyStore = m->bd->strKeyStore;
+#endif
+
+ int vrc = i_saveStore();
+ if (RT_FAILURE(vrc))
+ return setError(E_FAIL, tr("Failed to save the NVRAM content to disk (%Rrc)"), vrc);
+
+ return S_OK;
+}
+
+void NvramStore::i_rollback()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ m->bd.rollback();
+}
+
+void NvramStore::i_commit()
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertReturnVoid(autoCaller.isOk());
+
+ /* sanity too */
+ AutoCaller peerCaller(m->pPeer);
+ AssertReturnVoid(peerCaller.isOk());
+
+ /* lock both for writing since we modify both (mPeer is "master" so locked
+ * first) */
+ AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS);
+
+ if (m->bd.isBackedUp())
+ {
+ m->bd.commit();
+ if (m->pPeer)
+ {
+ /* attach new data to the peer and reshare it */
+ AutoWriteLock peerlock(m->pPeer COMMA_LOCKVAL_SRC_POS);
+ m->pPeer->m->bd.attach(m->bd);
+ }
+ }
+}
+
+void NvramStore::i_copyFrom(NvramStore *aThat)
+{
+ AssertReturnVoid(aThat != NULL);
+
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertReturnVoid(autoCaller.isOk());
+
+ /* sanity too */
+ AutoCaller thatCaller(aThat);
+ AssertReturnVoid(thatCaller.isOk());
+
+ /* peer is not modified, lock it for reading (aThat is "master" so locked
+ * first) */
+ AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);
+
+ /* this will back up current data */
+ m->bd.assignCopy(aThat->m->bd);
+
+ // Intentionally "forget" the NVRAM file since it must be unique and set
+ // to the correct value before the copy of the settings makes sense.
+ m->bd->strNvramPath.setNull();
+}
+
+HRESULT NvramStore::i_applyDefaults(GuestOSType *aOSType)
+{
+ HRESULT hrc = S_OK;
+
+ if (aOSType->i_recommendedEFISecureBoot())
+ {
+ /* Initialize the UEFI variable store and enroll default keys. */
+ hrc = initUefiVariableStore(0 /*aSize*/);
+ if (SUCCEEDED(hrc))
+ {
+ ComPtr<IUefiVariableStore> pVarStore;
+
+ hrc = getUefiVariableStore(pVarStore);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pVarStore->EnrollOraclePlatformKey();
+ if (SUCCEEDED(hrc))
+ hrc = pVarStore->EnrollDefaultMsSignatures();
+ }
+ }
+ }
+
+ return hrc;
+}
+
+void NvramStore::i_updateNonVolatileStorageFile(const Utf8Str &aNonVolatileStorageFile)
+{
+ /* sanity */
+ AutoCaller autoCaller(this);
+ AssertComRCReturnVoid(autoCaller.hrc());
+
+ AutoReadLock mlock(m->pParent COMMA_LOCKVAL_SRC_POS);
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ Utf8Str strTmp(aNonVolatileStorageFile);
+ if (strTmp == m->pParent->i_getDefaultNVRAMFilename())
+ strTmp.setNull();
+
+ if (strTmp == m->bd->strNvramPath)
+ return;
+
+ m->bd.backup();
+ m->bd->strNvramPath = strTmp;
+}
+
+#else
+//
+// private methods
+//
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreQuerySize(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ uint64_t *pcb)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoReadLock rlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ return RTVfsFileQuerySize(hVfsFile, pcb);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreReadAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ void *pvBuf, size_t cbRead)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoReadLock rlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ int vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc); RT_NOREF(vrc);
+
+ return RTVfsFileRead(hVfsFile, pvBuf, cbRead, NULL /*pcbRead*/);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreWriteAll(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath,
+ const void *pvBuf, size_t cbWrite)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+
+ vrc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ AssertRC(vrc);
+ vrc = RTVfsFileSetSize(hVfsFile, cbWrite, RTVFSFILE_SIZE_F_NORMAL);
+ if (RT_SUCCESS(vrc))
+ vrc = RTVfsFileWrite(hVfsFile, pvBuf, cbWrite, NULL /*pcbWritten*/);
+ }
+ else
+ {
+ /* Create a new entry. */
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ vrc = RTVfsFileFromBuffer(RTFILE_O_READ | RTFILE_O_WRITE, pvBuf, cbWrite, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ pThis->pNvramStore->m->bd->mapNvram[Utf8StrFmt("%s/%s", pszNamespace, pszPath)] = hVfsFile;
+ }
+
+ return vrc;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_nvramStoreDelete(PPDMIVFSCONNECTOR pInterface, const char *pszNamespace, const char *pszPath)
+{
+ PDRVMAINNVRAMSTORE pThis = RT_FROM_MEMBER(pInterface, DRVMAINNVRAMSTORE, IVfs);
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.find(Utf8StrFmt("%s/%s", pszNamespace, pszPath));
+ if (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ pThis->pNvramStore->m->bd->mapNvram.erase(it);
+ RTVfsFileRelease(hVfsFile);
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_SsmSaveExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ size_t cEntries = pThis->pNvramStore->m->bd->mapNvram.size();
+ AssertReturn(cEntries < 32, VERR_OUT_OF_RANGE); /* Some sanity checking. */
+ pHlp->pfnSSMPutU32(pSSM, (uint32_t)cEntries);
+
+ void *pvData = NULL;
+ size_t cbDataMax = 0;
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.begin();
+
+ while (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVFSFILE hVfsFile = it->second;
+ uint64_t cbFile;
+
+ int vrc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cbFile < _1M, VERR_OUT_OF_RANGE);
+
+ if (cbDataMax < cbFile)
+ {
+ pvData = RTMemRealloc(pvData, cbFile);
+ AssertPtrReturn(pvData, VERR_NO_MEMORY);
+ cbDataMax = cbFile;
+ }
+
+ vrc = RTVfsFileReadAt(hVfsFile, 0 /*off*/, pvData, cbFile, NULL /*pcbRead*/);
+ AssertRCReturn(vrc, vrc);
+
+ pHlp->pfnSSMPutStrZ(pSSM, it->first.c_str());
+ pHlp->pfnSSMPutU64(pSSM, cbFile);
+ pHlp->pfnSSMPutMem(pSSM, pvData, cbFile);
+ it++;
+ }
+
+ if (pvData)
+ RTMemFree(pvData);
+
+ pThis->pNvramStore->m->fSsmSaved = true;
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+
+/*static*/
+DECLCALLBACK(int) NvramStore::i_SsmLoadExec(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ AssertMsgReturn(uVersion >= NVRAM_STORE_SAVED_STATE_VERSION, ("%d\n", uVersion),
+ VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ AutoWriteLock wlock(pThis->pNvramStore COMMA_LOCKVAL_SRC_POS);
+
+ /* Clear any content first. */
+ NvramStoreIter it = pThis->pNvramStore->m->bd->mapNvram.begin();
+ while (it != pThis->pNvramStore->m->bd->mapNvram.end())
+ {
+ RTVfsFileRelease(it->second);
+ it++;
+ }
+
+ pThis->pNvramStore->m->bd->mapNvram.clear();
+
+ uint32_t cEntries = 0;
+ int vrc = pHlp->pfnSSMGetU32(pSSM, &cEntries);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cEntries < 32, VERR_OUT_OF_RANGE);
+
+ void *pvData = NULL;
+ size_t cbDataMax = 0;
+ while (cEntries--)
+ {
+ char szId[_1K]; /* Lazy developer */
+ uint64_t cbFile = 0;
+
+ vrc = pHlp->pfnSSMGetStrZ(pSSM, &szId[0], sizeof(szId));
+ AssertRCReturn(vrc, vrc);
+
+ vrc = pHlp->pfnSSMGetU64(pSSM, &cbFile);
+ AssertRCReturn(vrc, vrc);
+ AssertReturn(cbFile < _1M, VERR_OUT_OF_RANGE);
+
+ if (cbDataMax < cbFile)
+ {
+ pvData = RTMemRealloc(pvData, cbFile);
+ AssertPtrReturn(pvData, VERR_NO_MEMORY);
+ cbDataMax = cbFile;
+ }
+
+ vrc = pHlp->pfnSSMGetMem(pSSM, pvData, cbFile);
+ AssertRCReturn(vrc, vrc);
+
+ RTVFSFILE hVfsFile;
+ vrc = RTVfsFileFromBuffer(RTFILE_O_READWRITE, pvData, cbFile, &hVfsFile);
+ AssertRCReturn(vrc, vrc);
+
+ pThis->pNvramStore->m->bd->mapNvram[Utf8Str(szId)] = hVfsFile;
+ }
+
+ if (pvData)
+ RTMemFree(pvData);
+
+ /* The marker. */
+ uint32_t u32;
+ vrc = pHlp->pfnSSMGetU32(pSSM, &u32);
+ AssertRCReturn(vrc, vrc);
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+DECLCALLBACK(void *) NvramStore::i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMAINNVRAMSTORE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIVFSCONNECTOR, &pDrv->IVfs);
+ return NULL;
+}
+
+
+/**
+ * Destruct a NVRAM store driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance data.
+ */
+DECLCALLBACK(void) NvramStore::i_drvDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ LogFlow(("NvramStore::drvDestruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ if (pThis->pNvramStore)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->pNvramStore->m->cRefs);
+ if ( !cRefs
+ && !pThis->pNvramStore->m->fSsmSaved)
+ {
+ int vrc = pThis->pNvramStore->i_saveStore();
+ AssertRC(vrc); /** @todo Disk full error? */
+ }
+ }
+}
+
+
+/**
+ * Construct a NVRAM store driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+DECLCALLBACK(int) NvramStore::i_drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ RT_NOREF(fFlags, pCfg);
+ PDRVMAINNVRAMSTORE pThis = PDMINS_2_DATA(pDrvIns, PDRVMAINNVRAMSTORE);
+ LogFlow(("NvramStore::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 = NvramStore::i_drvQueryInterface;
+
+ pThis->IVfs.pfnQuerySize = NvramStore::i_nvramStoreQuerySize;
+ pThis->IVfs.pfnReadAll = NvramStore::i_nvramStoreReadAll;
+ pThis->IVfs.pfnWriteAll = NvramStore::i_nvramStoreWriteAll;
+ pThis->IVfs.pfnDelete = NvramStore::i_nvramStoreDelete;
+
+ /*
+ * Get the NVRAM store object pointer.
+ */
+ com::Guid uuid(COM_IIDOF(INvramStore));
+ pThis->pNvramStore = (NvramStore *)PDMDrvHlpQueryGenericUserObject(pDrvIns, uuid.raw());
+ if (!pThis->pNvramStore)
+ {
+ AssertMsgFailed(("Configuration error: No/bad NVRAM store object!\n"));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Only the first instance will register the SSM handlers and will do the work on behalf
+ * of all other NVRAM store driver instances when it comes to SSM.
+ */
+ if (pDrvIns->iInstance == 0)
+ {
+ int vrc = PDMDrvHlpSSMRegister(pDrvIns, NVRAM_STORE_SAVED_STATE_VERSION, 0 /*cbGuess*/,
+ NvramStore::i_SsmSaveExec, NvramStore::i_SsmLoadExec);
+ if (RT_FAILURE(vrc))
+ return PDMDrvHlpVMSetError(pDrvIns, vrc, RT_SRC_POS,
+ N_("Failed to register the saved state unit for the NVRAM store"));
+ }
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->pNvramStore->m->cRefs);
+ if (cRefs == 1)
+ {
+ int vrc = pThis->pNvramStore->i_loadStore(pThis->pNvramStore->m->bd->strNvramPath.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ ASMAtomicDecU32(&pThis->pNvramStore->m->cRefs);
+ return PDMDrvHlpVMSetError(pDrvIns, vrc, RT_SRC_POS,
+ N_("Failed to load the NVRAM store from the file"));
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NVRAM store driver registration record.
+ */
+const PDMDRVREG NvramStore::DrvReg =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "NvramStore",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Main NVRAM store driver (Main as in the API).",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STATUS,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMAINNVRAMSTORE),
+ /* pfnConstruct */
+ NvramStore::i_drvConstruct,
+ /* pfnDestruct */
+ NvramStore::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
+};
+#endif /* !VBOX_COM_INPROC */
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp b/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp
new file mode 100644
index 00000000..675ad2b6
--- /dev/null
+++ b/src/VBox/Main/src-all/PCIDeviceAttachmentImpl.cpp
@@ -0,0 +1,160 @@
+/* $Id: PCIDeviceAttachmentImpl.cpp $ */
+/** @file
+ * PCI attachment information implmentation.
+ */
+
+/*
+ * 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_PCIDEVICEATTACHMENT
+#include "PCIDeviceAttachmentImpl.h"
+#include "AutoCaller.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+#include <VBox/settings.h>
+
+struct PCIDeviceAttachment::Data
+{
+ Data(const Utf8Str &aDevName,
+ LONG aHostAddress,
+ LONG aGuestAddress,
+ BOOL afPhysical) :
+ DevName(aDevName),
+ HostAddress(aHostAddress),
+ GuestAddress(aGuestAddress),
+ fPhysical(afPhysical)
+ {
+ }
+
+ Utf8Str DevName;
+ LONG HostAddress;
+ LONG GuestAddress;
+ BOOL fPhysical;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+DEFINE_EMPTY_CTOR_DTOR(PCIDeviceAttachment)
+
+HRESULT PCIDeviceAttachment::FinalConstruct()
+{
+ LogFlowThisFunc(("\n"));
+ return BaseFinalConstruct();
+}
+
+void PCIDeviceAttachment::FinalRelease()
+{
+ LogFlowThisFunc(("\n"));
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+HRESULT PCIDeviceAttachment::init(IMachine *aParent,
+ const Utf8Str &aDevName,
+ LONG aHostAddress,
+ LONG aGuestAddress,
+ BOOL fPhysical)
+{
+ NOREF(aParent);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ m = new Data(aDevName, aHostAddress, aGuestAddress, fPhysical);
+
+ /* Confirm a successful initialization */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::initCopy(IMachine *aParent, PCIDeviceAttachment *aThat)
+{
+ LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat));
+
+ ComAssertRet(aParent && aThat, E_INVALIDARG);
+
+ return init(aParent, aThat->m->DevName, aThat->m->HostAddress, aThat->m->GuestAddress, aThat->m->fPhysical);
+}
+
+HRESULT PCIDeviceAttachment::i_loadSettings(IMachine *aParent,
+ const settings::HostPCIDeviceAttachment &hpda)
+{
+ /** @todo r=bird: Inconsistent signed/unsigned crap. */
+ return init(aParent, hpda.strDeviceName, (LONG)hpda.uHostAddress, (LONG)hpda.uGuestAddress, TRUE);
+}
+
+
+HRESULT PCIDeviceAttachment::i_saveSettings(settings::HostPCIDeviceAttachment &data)
+{
+ Assert(m);
+ /** @todo r=bird: Inconsistent signed/unsigned crap. */
+ data.uHostAddress = (uint32_t)m->HostAddress;
+ data.uGuestAddress = (uint32_t)m->GuestAddress;
+ data.strDeviceName = m->DevName;
+
+ return S_OK;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease().
+ */
+void PCIDeviceAttachment::uninit()
+{
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ delete m;
+ m = NULL;
+}
+
+// IPCIDeviceAttachment properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT PCIDeviceAttachment::getName(com::Utf8Str &aName)
+{
+ aName = m->DevName;
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::getIsPhysicalDevice(BOOL *aIsPhysicalDevice)
+{
+ *aIsPhysicalDevice = m->fPhysical;
+ return S_OK;
+}
+
+HRESULT PCIDeviceAttachment::getHostAddress(LONG *aHostAddress)
+{
+ *aHostAddress = m->HostAddress;
+ return S_OK;
+}
+HRESULT PCIDeviceAttachment::getGuestAddress(LONG *aGuestAddress)
+{
+ *aGuestAddress = m->GuestAddress;
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ProgressImpl.cpp b/src/VBox/Main/src-all/ProgressImpl.cpp
new file mode 100644
index 00000000..82efddc9
--- /dev/null
+++ b/src/VBox/Main/src-all/ProgressImpl.cpp
@@ -0,0 +1,1215 @@
+/* $Id: ProgressImpl.cpp $ */
+/** @file
+ * VirtualBox Progress 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_PROGRESS
+#include <iprt/types.h>
+
+#if defined(VBOX_WITH_XPCOM)
+#include <nsIServiceManager.h>
+#include <nsIExceptionService.h>
+#include <nsCOMPtr.h>
+#endif /* defined(VBOX_WITH_XPCOM) */
+
+#include "ProgressImpl.h"
+
+#if !defined(VBOX_COM_INPROC)
+# include "VirtualBoxImpl.h"
+#endif
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/cpp/utils.h>
+
+#include <iprt/errcore.h>
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "VBoxEvents.h"
+
+
+Progress::Progress()
+#if !defined(VBOX_COM_INPROC)
+ : mParent(NULL)
+#endif
+{
+}
+
+Progress::~Progress()
+{
+}
+
+
+HRESULT Progress::FinalConstruct()
+{
+ mCancelable = FALSE;
+ mCompleted = FALSE;
+ mCanceled = FALSE;
+ mResultCode = S_OK;
+
+ m_cOperations
+ = m_ulTotalOperationsWeight
+ = m_ulOperationsCompletedWeight
+ = m_ulCurrentOperation
+ = m_ulCurrentOperationWeight
+ = m_ulOperationPercent
+ = m_cMsTimeout
+ = 0;
+
+ // get creation timestamp
+ m_ullTimestamp = RTTimeMilliTS();
+
+ m_pfnCancelCallback = NULL;
+ m_pvCancelUserArg = NULL;
+
+ mCompletedSem = NIL_RTSEMEVENTMULTI;
+ mWaitersCount = 0;
+
+ return Progress::BaseFinalConstruct();
+}
+
+void Progress::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the normal progress object. With this variant, one can have
+ * an arbitrary number of sub-operation which IProgress can analyze to
+ * have a weighted progress computed.
+ *
+ * For example, say that one IProgress is supposed to track the cloning
+ * of two hard disk images, which are 100 MB and 1000 MB in size, respectively,
+ * and each of these hard disks should be one sub-operation of the IProgress.
+ *
+ * Obviously the progress would be misleading if the progress displayed 50%
+ * after the smaller image was cloned and would then take much longer for
+ * the second half.
+ *
+ * With weighted progress, one can invoke the following calls:
+ *
+ * 1) create progress object with cOperations = 2 and ulTotalOperationsWeight =
+ * 1100 (100 MB plus 1100, but really the weights can be any ULONG); pass
+ * in ulFirstOperationWeight = 100 for the first sub-operation
+ *
+ * 2) Then keep calling setCurrentOperationProgress() with a percentage
+ * for the first image; the total progress will increase up to a value
+ * of 9% (100MB / 1100MB * 100%).
+ *
+ * 3) Then call setNextOperation with the second weight (1000 for the megabytes
+ * of the second disk).
+ *
+ * 4) Then keep calling setCurrentOperationProgress() with a percentage for
+ * the second image, where 100% of the operation will then yield a 100%
+ * progress of the entire task.
+ *
+ * Weighting is optional; you can simply assign a weight of 1 to each operation
+ * and pass ulTotalOperationsWeight == cOperations to this constructor (but
+ * for that variant and for backwards-compatibility a simpler constructor exists
+ * in ProgressImpl.h as well).
+ *
+ * Even simpler, if you need no sub-operations at all, pass in cOperations =
+ * ulTotalOperationsWeight = ulFirstOperationWeight = 1.
+ *
+ * @param aParent Parent object (only for server-side Progress objects).
+ * @param aInitiator Initiator of the task (for server-side objects. Can be
+ * NULL which means initiator = parent, otherwise must not
+ * be NULL).
+ * @param aDescription Overall task description.
+ * @param aCancelable Flag whether the task maybe canceled.
+ * @param cOperations Number of operations within this task (at least 1).
+ * @param ulTotalOperationsWeight Total weight of operations; must be the sum of ulFirstOperationWeight and
+ * what is later passed with each subsequent setNextOperation() call.
+ * @param aFirstOperationDescription Description of the first operation.
+ * @param ulFirstOperationWeight Weight of first sub-operation.
+ */
+HRESULT Progress::init(
+#if !defined(VBOX_COM_INPROC)
+ VirtualBox *aParent,
+#endif
+ IUnknown *aInitiator,
+ const Utf8Str &aDescription,
+ BOOL aCancelable,
+ ULONG cOperations,
+ ULONG ulTotalOperationsWeight,
+ const Utf8Str &aFirstOperationDescription,
+ ULONG ulFirstOperationWeight)
+{
+ LogFlowThisFunc(("aDescription=\"%s\", cOperations=%d, ulTotalOperationsWeight=%d, aFirstOperationDescription=\"%s\", ulFirstOperationWeight=%d\n",
+ aDescription.c_str(),
+ cOperations,
+ ulTotalOperationsWeight,
+ aFirstOperationDescription.c_str(),
+ ulFirstOperationWeight));
+
+ AssertReturn(ulTotalOperationsWeight >= 1, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ HRESULT hrc = unconst(pEventSource).createObject();
+ if (FAILED(hrc))
+ return hrc;
+
+ hrc = pEventSource->init();
+ if (FAILED(hrc))
+ return hrc;
+
+#if !defined(VBOX_COM_INPROC)
+ AssertReturn(aParent, E_INVALIDARG);
+#else
+ AssertReturn(aInitiator, E_INVALIDARG);
+#endif
+
+#if !defined(VBOX_COM_INPROC)
+ /* share parent weakly */
+ unconst(mParent) = aParent;
+#endif
+
+#if !defined(VBOX_COM_INPROC)
+ /* assign (and therefore addref) initiator only if it is not VirtualBox
+ * (to avoid cycling); otherwise mInitiator will remain null which means
+ * that it is the same as the parent */
+ if (aInitiator)
+ {
+ ComObjPtr<VirtualBox> pVirtualBox(mParent);
+ if (!(pVirtualBox == aInitiator))
+ unconst(mInitiator) = aInitiator;
+ }
+#else
+ unconst(mInitiator) = aInitiator;
+#endif
+
+ unconst(mId).create();
+
+#if !defined(VBOX_COM_INPROC)
+ /* add to the global collection of progress operations (note: after
+ * creating mId) */
+ mParent->i_addProgress(this);
+#endif
+
+ unconst(mDescription) = aDescription;
+
+ mCancelable = aCancelable;
+
+ m_cOperations = cOperations;
+ m_ulTotalOperationsWeight = ulTotalOperationsWeight;
+ m_ulOperationsCompletedWeight = 0;
+ m_ulCurrentOperation = 0;
+ m_operationDescription = aFirstOperationDescription;
+ m_ulCurrentOperationWeight = ulFirstOperationWeight;
+ m_ulOperationPercent = 0;
+
+ int vrc = RTSemEventMultiCreate(&mCompletedSem);
+ ComAssertRCRet(vrc, E_FAIL);
+
+ RTSemEventMultiReset(mCompletedSem);
+
+ /* Confirm a successful initialization. */
+ autoInitSpan.setSucceeded();
+
+ return S_OK;
+}
+
+/**
+ * Initializes the sub-progress object that represents a specific operation of
+ * the whole task.
+ *
+ * Objects initialized with this method are then combined together into the
+ * single task using a Progress instance, so it doesn't require the
+ * parent, initiator, description and doesn't create an ID. Note that calling
+ * respective getter methods on an object initialized with this method is
+ * useless. Such objects are used only to provide a separate wait semaphore and
+ * store individual operation descriptions.
+ *
+ * @param aCancelable Flag whether the task maybe canceled.
+ * @param aOperationCount Number of sub-operations within this task (at least 1).
+ * @param aOperationDescription Description of the individual operation.
+ */
+HRESULT Progress::init(BOOL aCancelable,
+ ULONG aOperationCount,
+ const Utf8Str &aOperationDescription)
+{
+ LogFlowThisFunc(("aOperationDescription=\"%s\"\n", aOperationDescription.c_str()));
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ mCancelable = aCancelable;
+
+ // for this variant we assume for now that all operations are weighed "1"
+ // and equal total weight = operation count
+ m_cOperations = aOperationCount;
+ m_ulTotalOperationsWeight = aOperationCount;
+ m_ulOperationsCompletedWeight = 0;
+ m_ulCurrentOperation = 0;
+ m_operationDescription = aOperationDescription;
+ m_ulCurrentOperationWeight = 1;
+ m_ulOperationPercent = 0;
+
+ int vrc = RTSemEventMultiCreate(&mCompletedSem);
+ ComAssertRCRet(vrc, E_FAIL);
+
+ RTSemEventMultiReset(mCompletedSem);
+
+ /* 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 Progress::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ /* wake up all threads still waiting on occasion */
+ if (mWaitersCount > 0)
+ {
+ LogFlow(("WARNING: There are still %d threads waiting for '%s' completion!\n",
+ mWaitersCount, mDescription.c_str()));
+ RTSemEventMultiSignal(mCompletedSem);
+ }
+
+ RTSemEventMultiDestroy(mCompletedSem);
+
+ /* release initiator (effective only if mInitiator has been assigned in init()) */
+ unconst(mInitiator).setNull();
+
+#if !defined(VBOX_COM_INPROC)
+ if (mParent)
+ {
+ /* remove the added progress on failure to complete the initialization */
+ if (autoUninitSpan.initFailed() && mId.isValid() && !mId.isZero())
+ mParent->i_removeProgress(mId.ref());
+
+ unconst(mParent) = NULL;
+ }
+#endif
+}
+
+
+// public methods only for internal purposes
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Marks the whole task as complete and sets the result code.
+ *
+ * If the result code indicates a failure (|FAILED(@a aResultCode)|) then this
+ * method will import the error info from the current thread and assign it to
+ * the errorInfo attribute (it will return an error if no info is available in
+ * such case).
+ *
+ * If the result code indicates a success (|SUCCEEDED(@a aResultCode)|) then
+ * the current operation is set to the last.
+ *
+ * Note that this method may be called only once for the given Progress object.
+ * Subsequent calls will assert.
+ *
+ * @param aResultCode Operation result code.
+ */
+HRESULT Progress::i_notifyComplete(HRESULT aResultCode)
+{
+ HRESULT hrc;
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ if (FAILED(aResultCode))
+ {
+ /* try to import error info from the current thread */
+#if !defined(VBOX_WITH_XPCOM)
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (hrc == S_OK && err)
+ hrc = err.queryInterfaceTo(errorInfo.asOutParam());
+#else /* !defined(VBOX_WITH_XPCOM) */
+ nsCOMPtr<nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (NS_SUCCEEDED(hrc))
+ {
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (NS_SUCCEEDED(hrc) && ex)
+ hrc = ex.queryInterfaceTo(errorInfo.asOutParam());
+ }
+ }
+#endif /* !defined(VBOX_WITH_XPCOM) */
+ }
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Wrapper around Progress:notifyCompleteV.
+ */
+HRESULT Progress::i_notifyComplete(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = i_notifyCompleteV(aResultCode, aIID, pcszComponent, aText, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aIID IID of the interface that defines the error.
+ * @param pcszComponent Name of the component that generates the error.
+ * @param aText Error message (must not be null), an RTStrPrintf-like
+ * format string in UTF-8 encoding.
+ * @param va List of arguments for the format string.
+ */
+HRESULT Progress::i_notifyCompleteV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ va_list va)
+{
+ /* expected to be used only in case of error */
+ Assert(FAILED(aResultCode));
+
+ Utf8Str text(aText, va);
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ AssertComRCReturnRC(hrc);
+ errorInfo->init(aResultCode, aIID, pcszComponent, text);
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Wrapper around Progress:notifyCompleteBothV.
+ */
+HRESULT Progress::i_notifyCompleteBoth(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const char *aText,
+ ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hrc = i_notifyCompleteBothV(aResultCode, vrc, aIID, pcszComponent, aText, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param vrc VBox status code to associate with the error.
+ * @param aIID IID of the interface that defines the error.
+ * @param pszComponent Name of the component that generates the error.
+ * @param pszFormat Error message (must not be null), an RTStrPrintf-like
+ * format string in UTF-8 encoding.
+ * @param va List of arguments for the format string.
+ */
+HRESULT Progress::i_notifyCompleteBothV(HRESULT aResultCode,
+ int vrc,
+ const GUID &aIID,
+ const char *pszComponent,
+ const char *pszFormat,
+ va_list va)
+{
+ /* expected to be used only in case of error */
+ Assert(FAILED(aResultCode));
+
+ Utf8Str text(pszFormat, va);
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ AssertComRCReturnRC(hrc);
+ errorInfo->initEx(aResultCode, vrc, aIID, pszComponent, text);
+
+ return i_notifyCompleteWorker(aResultCode, errorInfo);
+}
+
+/**
+ * Sets the cancelation callback, checking for cancelation first.
+ *
+ * @returns Success indicator.
+ * @retval true on success.
+ * @retval false if the progress object has already been canceled or is in an
+ * invalid state
+ *
+ * @param pfnCallback The function to be called upon cancelation.
+ * @param pvUser The callback argument.
+ */
+bool Progress::i_setCancelCallback(void (*pfnCallback)(void *), void *pvUser)
+{
+ AutoCaller autoCaller(this);
+ AssertReturn(autoCaller.isOk(), false);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_checkForAutomaticTimeout();
+ if (mCanceled)
+ return false;
+
+ m_pvCancelUserArg = pvUser;
+ m_pfnCancelCallback = pfnCallback;
+ return true;
+}
+
+/**
+ * @callback_method_impl{FNRTPROGRESS,
+ * Works the progress of the current operation.}
+ */
+/*static*/ DECLCALLBACK(int) Progress::i_iprtProgressCallback(unsigned uPercentage, void *pvUser)
+{
+ Progress *pThis = (Progress *)pvUser;
+
+ /*
+ * Same as setCurrentOperationProgress, except we don't fail on mCompleted.
+ */
+ AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
+ int vrc = VINF_SUCCESS;
+ if (!pThis->mCompleted)
+ {
+ pThis->i_checkForAutomaticTimeout();
+ if (!pThis->mCanceled)
+ {
+ if (uPercentage > pThis->m_ulOperationPercent)
+ pThis->setCurrentOperationProgress(uPercentage);
+ }
+ else
+ {
+ Assert(pThis->mCancelable);
+ vrc = VERR_CANCELLED;
+ }
+ }
+ /* else ignored */
+ return vrc;
+}
+
+/**
+ * @callback_method_impl{FNVDPROGRESS,
+ * Progress::i_iprtProgressCallback with parameters switched around.}
+ */
+/*static*/ DECLCALLBACK(int) Progress::i_vdProgressCallback(void *pvUser, unsigned uPercentage)
+{
+ return i_iprtProgressCallback(uPercentage, pvUser);
+}
+
+
+// IProgress properties
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT Progress::getId(com::Guid &aId)
+{
+ /* mId is constant during life time, no need to lock */
+ aId = mId;
+
+ return S_OK;
+}
+
+HRESULT Progress::getDescription(com::Utf8Str &aDescription)
+{
+ /* mDescription is constant during life time, no need to lock */
+ aDescription = mDescription;
+
+ return S_OK;
+}
+HRESULT Progress::getInitiator(ComPtr<IUnknown> &aInitiator)
+{
+ /* mInitiator/mParent are constant during life time, no need to lock */
+#if !defined(VBOX_COM_INPROC)
+ if (mInitiator)
+ mInitiator.queryInterfaceTo(aInitiator.asOutParam());
+ else
+ {
+ ComObjPtr<VirtualBox> pVirtualBox(mParent);
+ pVirtualBox.queryInterfaceTo(aInitiator.asOutParam());
+ }
+#else
+ mInitiator.queryInterfaceTo(aInitiator.asOutParam());
+#endif
+
+ return S_OK;
+}
+
+HRESULT Progress::getCancelable(BOOL *aCancelable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCancelable = mCancelable;
+
+ return S_OK;
+}
+
+HRESULT Progress::getPercent(ULONG *aPercent)
+{
+ /* i_checkForAutomaticTimeout requires a write lock. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted && SUCCEEDED(mResultCode))
+ *aPercent = 100;
+ else
+ {
+ ULONG ulPercent = (ULONG)i_calcTotalPercent();
+ // do not report 100% until we're really really done with everything
+ // as the Qt GUI dismisses progress dialogs in that case
+ if ( ulPercent == 100
+ && ( m_ulOperationPercent < 100
+ || (m_ulCurrentOperation < m_cOperations -1)
+ )
+ )
+ *aPercent = 99;
+ else
+ *aPercent = ulPercent;
+ }
+
+ i_checkForAutomaticTimeout();
+
+ return S_OK;
+}
+
+HRESULT Progress::getTimeRemaining(LONG *aTimeRemaining)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted)
+ *aTimeRemaining = 0;
+ else
+ {
+ double dPercentDone = i_calcTotalPercent();
+ if (dPercentDone < 1)
+ *aTimeRemaining = -1; // unreliable, or avoid division by 0 below
+ else
+ {
+ uint64_t ullTimeNow = RTTimeMilliTS();
+ uint64_t ullTimeElapsed = ullTimeNow - m_ullTimestamp;
+ uint64_t ullTimeTotal = (uint64_t)((double)ullTimeElapsed * 100 / dPercentDone);
+ uint64_t ullTimeRemaining = (ullTimeTotal < ullTimeElapsed) ? 0 : ullTimeTotal - ullTimeElapsed;
+
+// LogFunc(("dPercentDone = %RI32, ullTimeNow = %RI64, ullTimeElapsed = %RI64, ullTimeTotal = %RI64, ullTimeRemaining = %RI64\n",
+// (uint32_t)dPercentDone, ullTimeNow, ullTimeElapsed, ullTimeTotal, ullTimeRemaining));
+
+ *aTimeRemaining = (LONG)(RT_MIN(ullTimeRemaining, RT_MS_1HOUR_64*24*365) / 1000);
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Progress::getCompleted(BOOL *aCompleted)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCompleted = mCompleted;
+
+ return S_OK;
+}
+
+HRESULT Progress::getCanceled(BOOL *aCanceled)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aCanceled = mCanceled;
+
+ return S_OK;
+}
+
+HRESULT Progress::getResultCode(LONG *aResultCode)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCompleted)
+ return setError(E_FAIL, tr("Result code is not available, operation is still in progress"));
+
+ *aResultCode = (LONG)mResultCode;
+
+ return S_OK;
+}
+
+HRESULT Progress::getErrorInfo(ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCompleted)
+ return setError(E_FAIL, tr("Error info is not available, operation is still in progress"));
+
+ mErrorInfo.queryInterfaceTo(aErrorInfo.asOutParam());
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationCount(ULONG *aOperationCount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperationCount = m_cOperations;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperation(ULONG *aOperation)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperation = m_ulCurrentOperation;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationDescription(com::Utf8Str &aOperationDescription)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aOperationDescription = m_operationDescription;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationPercent(ULONG *aOperationPercent)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCompleted && SUCCEEDED(mResultCode))
+ *aOperationPercent = 100;
+ else
+ *aOperationPercent = m_ulOperationPercent;
+
+ return S_OK;
+}
+
+HRESULT Progress::getOperationWeight(ULONG *aOperationWeight)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aOperationWeight = m_ulCurrentOperationWeight;
+
+ return S_OK;
+}
+
+HRESULT Progress::getTimeout(ULONG *aTimeout)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aTimeout = m_cMsTimeout;
+
+ return S_OK;
+}
+
+HRESULT Progress::setTimeout(ULONG aTimeout)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCancelable)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
+ m_cMsTimeout = aTimeout;
+
+ return S_OK;
+}
+
+HRESULT Progress::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ /* event source is const, no need to lock */
+ pEventSource.queryInterfaceTo(aEventSource.asOutParam());
+ return S_OK;
+}
+
+
+// IProgress methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @note XPCOM: when this method is not called on the main XPCOM thread, it
+ * simply blocks the thread until mCompletedSem is signalled. If the
+ * thread has its own event queue (hmm, what for?) that it must run, then
+ * calling this method will definitely freeze event processing.
+ */
+HRESULT Progress::waitForCompletion(LONG aTimeout)
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aTimeout=%d\n", aTimeout));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* if we're already completed, take a shortcut */
+ if (!mCompleted && aTimeout != 0)
+ {
+ RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
+ uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
+
+ for (;;)
+ {
+ mWaitersCount++;
+ alock.release();
+ int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
+ alock.acquire();
+ mWaitersCount--;
+
+ /* the last waiter resets the semaphore */
+ if (mWaitersCount == 0)
+ RTSemEventMultiReset(mCompletedSem);
+
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Failed to wait for the task completion (%Rrc)"), vrc);
+
+ if (mCompleted)
+ break;
+
+ if (aTimeout >= 0)
+ {
+ uint64_t msNow = RTTimeMilliTS();
+ uint64_t cMsElapsed = msNow - msLast;
+ if (cMsWait <= cMsElapsed)
+ break;
+ cMsWait -= (RTMSINTERVAL)cMsElapsed;
+ msLast = msNow;
+ }
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+/**
+ * @note XPCOM: when this method is not called on the main XPCOM thread, it
+ * simply blocks the thread until mCompletedSem is signalled. If the
+ * thread has its own event queue (hmm, what for?) that it must run, then
+ * calling this method will definitely freeze event processing.
+ */
+HRESULT Progress::waitForOperationCompletion(ULONG aOperation, LONG aTimeout)
+
+{
+ LogFlowThisFuncEnter();
+ LogFlowThisFunc(("aOperation=%d, aTimeout=%d\n", aOperation, aTimeout));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ CheckComArgExpr(aOperation, aOperation < m_cOperations);
+
+ /* if we're already completed or if the given operation is already done,
+ * then take a shortcut */
+ if ( !mCompleted
+ && aOperation >= m_ulCurrentOperation
+ && aTimeout != 0)
+ {
+ RTMSINTERVAL cMsWait = aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout;
+ uint64_t msLast = aTimeout < 0 ? 0 : RTTimeMilliTS();
+
+ for (;;)
+ {
+ mWaitersCount ++;
+ alock.release();
+ int vrc = RTSemEventMultiWait(mCompletedSem, cMsWait);
+ alock.acquire();
+ mWaitersCount--;
+
+ /* the last waiter resets the semaphore */
+ if (mWaitersCount == 0)
+ RTSemEventMultiReset(mCompletedSem);
+
+ if (RT_FAILURE(vrc) && vrc != VERR_TIMEOUT)
+ return setErrorBoth(E_FAIL, vrc, tr("Failed to wait for the operation completion (%Rrc)"), vrc);
+
+ if (mCompleted || aOperation >= m_ulCurrentOperation)
+ break;
+
+ if (aTimeout >= 0)
+ {
+ uint64_t msNow = RTTimeMilliTS();
+ uint64_t cMsElapsed = msNow - msLast;
+ if (cMsWait <= cMsElapsed)
+ break;
+ cMsWait -= (RTMSINTERVAL)cMsElapsed;
+ msLast = msNow;
+ }
+ }
+ }
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT Progress::cancel()
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (!mCancelable)
+ return setError(VBOX_E_INVALID_OBJECT_STATE, tr("Operation cannot be canceled"));
+
+ if (!mCanceled)
+ {
+ LogThisFunc(("Canceling\n"));
+ mCanceled = TRUE;
+ if (m_pfnCancelCallback)
+ m_pfnCancelCallback(m_pvCancelUserArg);
+
+ }
+ else
+ LogThisFunc(("Already canceled\n"));
+
+ return S_OK;
+}
+
+
+// IInternalProgressControl methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Updates the percentage value of the current operation.
+ *
+ * @param aPercent New percentage value of the operation in progress
+ * (in range [0, 100]).
+ */
+HRESULT Progress::setCurrentOperationProgress(ULONG aPercent)
+{
+ AssertMsgReturn(aPercent <= 100, ("%u\n", aPercent), E_INVALIDARG);
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ i_checkForAutomaticTimeout();
+ if (mCancelable && mCanceled)
+ AssertReturn(!mCompleted, E_FAIL);
+ AssertReturn(!mCompleted && !mCanceled, E_FAIL);
+
+ if (m_ulOperationPercent != aPercent)
+ {
+ m_ulOperationPercent = aPercent;
+ ULONG actualPercent = 0;
+ getPercent(&actualPercent);
+ ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
+ }
+
+ return S_OK;
+}
+
+HRESULT Progress::waitForOtherProgressCompletion(const ComPtr<IProgress> &aProgressOther,
+ ULONG aTimeoutMS)
+{
+ LogFlowThisFuncEnter();
+
+ /* Note: no locking needed, because we just use public methods. */
+
+ BOOL fCancelable = FALSE;
+ BOOL fCompleted = FALSE;
+ BOOL fCanceled = FALSE;
+ ULONG prevPercent = UINT32_MAX;
+ ULONG currentPercent = 0;
+ ULONG cOp = 0;
+ /* Is the async process cancelable? */
+ HRESULT hrc = aProgressOther->COMGETTER(Cancelable)(&fCancelable);
+ if (FAILED(hrc)) return hrc;
+
+ uint64_t u64StopTime = UINT64_MAX;
+ if (aTimeoutMS > 0)
+ u64StopTime = RTTimeMilliTS() + aTimeoutMS;
+ /* Loop as long as the sync process isn't completed. */
+ while (SUCCEEDED(aProgressOther->COMGETTER(Completed(&fCompleted))))
+ {
+ /* We can forward any cancel request to the async process only when
+ * it is cancelable. */
+ if (fCancelable)
+ {
+ hrc = COMGETTER(Canceled)(&fCanceled);
+ if (FAILED(hrc)) return hrc;
+ if (fCanceled)
+ {
+ hrc = aProgressOther->Cancel();
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+ /* Even if the user canceled the process, we have to wait until the
+ async task has finished his work (cleanup and such). Otherwise there
+ will be sync trouble (still wrong state, dead locks, ...) on the
+ used objects. So just do nothing, but wait for the complete
+ notification. */
+ if (!fCanceled)
+ {
+ /* Check if the current operation has changed. It is also possible that
+ * in the meantime more than one async operation was finished. So we
+ * have to loop as long as we reached the same operation count. */
+ ULONG curOp;
+ for (;;)
+ {
+ hrc = aProgressOther->COMGETTER(Operation(&curOp));
+ if (FAILED(hrc)) return hrc;
+ if (cOp != curOp)
+ {
+ Bstr bstr;
+ ULONG currentWeight;
+ hrc = aProgressOther->COMGETTER(OperationDescription(bstr.asOutParam()));
+ if (FAILED(hrc)) return hrc;
+ hrc = aProgressOther->COMGETTER(OperationWeight(&currentWeight));
+ if (FAILED(hrc)) return hrc;
+ hrc = SetNextOperation(bstr.raw(), currentWeight);
+ if (FAILED(hrc)) return hrc;
+ ++cOp;
+ }
+ else
+ break;
+ }
+
+ hrc = aProgressOther->COMGETTER(OperationPercent(&currentPercent));
+ if (FAILED(hrc)) return hrc;
+ if (currentPercent != prevPercent)
+ {
+ prevPercent = currentPercent;
+ hrc = SetCurrentOperationProgress(currentPercent);
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+ if (fCompleted)
+ break;
+
+ if (aTimeoutMS != 0)
+ {
+ /* Make sure the loop is not too tight */
+ uint64_t u64Now = RTTimeMilliTS();
+ uint64_t u64RemainingMS = u64StopTime - u64Now;
+ if (u64RemainingMS < 10)
+ u64RemainingMS = 10;
+ else if (u64RemainingMS > 200)
+ u64RemainingMS = 200;
+ hrc = aProgressOther->WaitForCompletion((LONG)u64RemainingMS);
+ if (FAILED(hrc)) return hrc;
+
+ if (RTTimeMilliTS() >= u64StopTime)
+ return VBOX_E_TIMEOUT;
+ }
+ else
+ {
+ /* Make sure the loop is not too tight */
+ hrc = aProgressOther->WaitForCompletion(200);
+ if (FAILED(hrc)) return hrc;
+ }
+ }
+
+ /* Transfer error information if applicable and report the error status
+ * back to the caller to make this as easy as possible. */
+ LONG iRc;
+ hrc = aProgressOther->COMGETTER(ResultCode)(&iRc);
+ if (FAILED(hrc)) return hrc;
+ if (FAILED((HRESULT)iRc))
+ {
+ setError(ProgressErrorInfo(aProgressOther));
+ hrc = (HRESULT)iRc;
+ }
+
+ LogFlowThisFuncLeave();
+ return hrc;
+}
+
+/**
+ * Signals that the current operation is successfully completed and advances to
+ * the next operation. The operation percentage is reset to 0.
+ *
+ * @param aNextOperationDescription Description of the next operation.
+ * @param aNextOperationsWeight Weight of the next operation.
+ *
+ * @note The current operation must not be the last one.
+ */
+HRESULT Progress::setNextOperation(const com::Utf8Str &aNextOperationDescription, ULONG aNextOperationsWeight)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCanceled)
+ return E_FAIL;
+ AssertReturn(!mCompleted, E_FAIL);
+ AssertReturn(m_ulCurrentOperation + 1 < m_cOperations, E_FAIL);
+
+ ++m_ulCurrentOperation;
+ m_ulOperationsCompletedWeight += m_ulCurrentOperationWeight;
+
+ m_operationDescription = aNextOperationDescription;
+ m_ulCurrentOperationWeight = aNextOperationsWeight;
+ m_ulOperationPercent = 0;
+
+ LogThisFunc(("%s: aNextOperationsWeight = %d; m_ulCurrentOperation is now %d, m_ulOperationsCompletedWeight is now %d\n",
+ m_operationDescription.c_str(), aNextOperationsWeight, m_ulCurrentOperation, m_ulOperationsCompletedWeight));
+
+ /* wake up all waiting threads */
+ if (mWaitersCount > 0)
+ RTSemEventMultiSignal(mCompletedSem);
+
+ ULONG actualPercent = 0;
+ getPercent(&actualPercent);
+ ::FireProgressPercentageChangedEvent(pEventSource, mId.toString(), (LONG)actualPercent);
+
+ return S_OK;
+}
+
+/**
+ * Notify the progress object that we're almost at the point of no return.
+ *
+ * This atomically checks for and disables cancelation. Calls to
+ * IProgress::Cancel() made after a successful call to this method will fail
+ * and the user can be told. While this isn't entirely clean behavior, it
+ * prevents issues with an irreversible actually operation succeeding while the
+ * user believe it was rolled back.
+ *
+ * @returns COM error status.
+ * @retval S_OK on success.
+ * @retval E_FAIL if the progress object has already been canceled or is in an
+ * invalid state
+ */
+HRESULT Progress::notifyPointOfNoReturn(void)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mCanceled)
+ {
+ LogThisFunc(("returns failure\n"));
+ return E_FAIL;
+ }
+
+ mCancelable = FALSE;
+ LogThisFunc(("returns success\n"));
+ return S_OK;
+}
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * This is where the actual work is done, the related methods all end up here.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aErrorInfo List of arguments for the format string.
+ */
+HRESULT Progress::notifyComplete(LONG aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ return i_notifyCompleteWorker((HRESULT)aResultCode, aErrorInfo);
+}
+
+
+// private internal helpers
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Marks the operation as complete and attaches full error info.
+ *
+ * This is where the actual work is done, the related methods all end up here.
+ *
+ * @param aResultCode Operation result (error) code, must not be S_OK.
+ * @param aErrorInfo List of arguments for the format string.
+ *
+ * @note This is just notifyComplete with the correct aResultCode type.
+ */
+HRESULT Progress::i_notifyCompleteWorker(HRESULT aResultCode, const ComPtr<IVirtualBoxErrorInfo> &aErrorInfo)
+{
+ LogThisFunc(("aResultCode=%Rhrc\n", aResultCode));
+ /* on failure we expect error info, on success there must be none */
+ AssertMsg(FAILED(aResultCode) ^ aErrorInfo.isNull(),
+ ("No error info but trying to set a failed result (%08X/%Rhrc)!\n", aResultCode, aResultCode));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ AssertReturn(mCompleted == FALSE, E_FAIL);
+
+ if (mCanceled && SUCCEEDED(aResultCode))
+ aResultCode = E_FAIL;
+
+ mCompleted = TRUE;
+ mResultCode = aResultCode;
+ if (SUCCEEDED(aResultCode))
+ {
+ m_ulCurrentOperation = m_cOperations - 1; /* last operation */
+ m_ulOperationPercent = 100;
+ }
+ mErrorInfo = aErrorInfo;
+
+#if !defined(VBOX_COM_INPROC)
+ /* remove from the global collection of pending progress operations */
+ if (mParent)
+ mParent->i_removeProgress(mId.ref());
+#endif
+
+ /* wake up all waiting threads */
+ if (mWaitersCount > 0)
+ RTSemEventMultiSignal(mCompletedSem);
+
+ ::FireProgressTaskCompletedEvent(pEventSource, mId.toString());
+
+ return S_OK;
+}
+
+/**
+ * Internal helper to compute the total percent value based on the member values and
+ * returns it as a "double". This is used both by GetPercent (which returns it as a
+ * rounded ULONG) and GetTimeRemaining().
+ *
+ * Requires locking by the caller!
+ *
+ * @return fractional percentage as a double value.
+ */
+double Progress::i_calcTotalPercent()
+{
+ // avoid division by zero
+ if (m_ulTotalOperationsWeight == 0)
+ return 0.0;
+
+ double dPercent = ( (double)m_ulOperationsCompletedWeight // weight of operations that have been completed
+ + ((double)m_ulOperationPercent *
+ (double)m_ulCurrentOperationWeight / 100.0) // plus partial weight of the current operation
+ ) * 100.0 / (double)m_ulTotalOperationsWeight;
+
+ return dPercent;
+}
+
+/**
+ * Internal helper for automatically timing out the operation.
+ *
+ * The caller must hold the object write lock.
+ */
+void Progress::i_checkForAutomaticTimeout(void)
+{
+ AssertReturnVoid(isWriteLockOnCurrentThread());
+
+ if ( m_cMsTimeout
+ && mCancelable
+ && !mCanceled
+ && RTTimeMilliTS() - m_ullTimestamp > m_cMsTimeout)
+ Cancel();
+}
diff --git a/src/VBox/Main/src-all/QMTranslatorImpl.cpp b/src/VBox/Main/src-all/QMTranslatorImpl.cpp
new file mode 100644
index 00000000..741560ee
--- /dev/null
+++ b/src/VBox/Main/src-all/QMTranslatorImpl.cpp
@@ -0,0 +1,671 @@
+/* $Id: QMTranslatorImpl.cpp $ */
+/** @file
+ * VirtualBox API translation handling 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
+ */
+
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <iprt/sanitized/iterator>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include <VBox/com/string.h>
+#include <VBox/log.h>
+#include <QMTranslator.h>
+
+/* QM File Magic Number */
+static const size_t g_cbMagic = 16;
+static const uint8_t g_abMagic[g_cbMagic] =
+{
+ 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
+ 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
+};
+
+/* Used internally */
+class QMException : public std::exception
+{
+ const char *m_str;
+public:
+ QMException(const char *str) : m_str(str) {}
+ virtual const char *what() const throw() { return m_str; }
+};
+
+/* Bytes stream. Used by the parser to iterate through the data */
+class QMBytesStream
+{
+ size_t m_cbSize;
+ const uint8_t * const m_dataStart;
+ const uint8_t *m_iter;
+ const uint8_t *m_end;
+
+public:
+
+ QMBytesStream(const uint8_t *const dataStart, size_t cbSize)
+ : m_cbSize(dataStart ? cbSize : 0)
+ , m_dataStart(dataStart)
+ , m_iter(dataStart)
+ {
+ setEnd();
+ }
+
+ /** Sets end pointer.
+ * Used in message reader to detect the end of message block */
+ inline void setEnd(size_t pos = 0)
+ {
+ m_end = m_dataStart + (pos && pos < m_cbSize ? pos : m_cbSize);
+ }
+
+ inline uint8_t read8()
+ {
+ checkSize(1);
+ return *m_iter++;
+ }
+
+ inline uint32_t read32()
+ {
+ checkSize(4);
+ uint32_t result = *reinterpret_cast<const uint32_t *>(m_iter);
+ m_iter += 4;
+ return RT_BE2H_U32(result);
+ }
+
+ /** Reads string in UTF16 and converts it into a UTF8 string */
+ inline com::Utf8Str readUtf16String()
+ {
+ uint32_t size = read32();
+ checkSize(size);
+ if (size & 1)
+ throw QMException("Incorrect string size");
+
+ /* UTF-16 can encode up to codepoint U+10ffff, which UTF-8 needs 4 bytes
+ to encode, so reserve twice the size plus a terminator for the result. */
+ com::Utf8Str result;
+ result.reserve(size * 2 + 1);
+ char *pszStr = result.mutableRaw();
+ int vrc = RTUtf16BigToUtf8Ex((PCRTUTF16)m_iter, size >> 1, &pszStr, result.capacity(), NULL);
+ if (RT_SUCCESS(vrc))
+ result.jolt();
+ else
+ throw QMException("Translation from UTF-16 to UTF-8 failed");
+
+ m_iter += size;
+ return result;
+ }
+
+ /**
+ * Reads a string, forcing UTF-8 encoding.
+ */
+ inline com::Utf8Str readString()
+ {
+ uint32_t size = read32();
+ checkSize(size);
+
+ com::Utf8Str result(reinterpret_cast<const char *>(m_iter), size);
+ if (size > 0)
+ {
+ RTStrPurgeEncoding(result.mutableRaw());
+ result.jolt();
+ }
+
+ m_iter += size;
+ return result;
+ }
+
+ /**
+ * Reads memory block
+ * Returns number of bytes read
+ */
+ inline uint32_t read(char *bBuf, uint32_t cbSize)
+ {
+ if (!bBuf || !cbSize)
+ return 0;
+ cbSize = RT_MIN(cbSize, (uint32_t)(m_end - m_iter));
+ memcpy(bBuf, m_iter, cbSize);
+ m_iter += cbSize;
+ return cbSize;
+ }
+
+ /** Checks the magic number.
+ * Should be called when in the beginning of the data
+ * @throws exception on mismatch */
+ inline void checkMagic()
+ {
+ checkSize(g_cbMagic);
+ if (RT_LIKELY(memcmp(&(*m_iter), g_abMagic, g_cbMagic) == 0))
+ m_iter += g_cbMagic;
+ else
+ throw QMException("Wrong magic number");
+ }
+
+ /** Has we reached the end pointer? */
+ inline bool hasFinished()
+ {
+ return m_iter == m_end;
+ }
+
+ /** Returns current stream position */
+ inline size_t tellPos()
+ {
+ return (size_t)(m_iter - m_dataStart);
+ }
+
+ /** Moves current pointer to a desired position */
+ inline void seek(uint32_t offSkip)
+ {
+ size_t cbLeft = (size_t)(m_end - m_iter);
+ if (cbLeft >= offSkip)
+ m_iter += offSkip;
+ else
+ m_iter = m_end; /** @todo r=bird: Or throw exception via checkSize? */
+ }
+
+ /** Checks whether stream has enough data to read size bytes */
+ inline void checkSize(size_t size)
+ {
+ if (RT_LIKELY((size_t)(m_end - m_iter) >= size))
+ return;
+ throw QMException("Incorrect item size");
+ }
+};
+
+/* Internal QMTranslator implementation */
+class QMTranslator_Impl
+{
+ /** Used while parsing */
+ struct QMMessageParse
+ {
+ /* Everything is in UTF-8 */
+ std::vector<com::Utf8Str> astrTranslations;
+ com::Utf8Str strContext;
+ com::Utf8Str strComment;
+ com::Utf8Str strSource;
+
+ QMMessageParse() {}
+ };
+
+ struct QMMessage
+ {
+ const char *pszContext;
+ const char *pszSource;
+ const char *pszComment;
+ std::vector<const char *> vecTranslations;
+ uint32_t hash;
+
+ QMMessage() : pszContext(NULL), pszSource(NULL), pszComment(NULL), hash(0)
+ {}
+
+ QMMessage(RTSTRCACHE hStrCache, const QMMessageParse &rSrc)
+ : pszContext(addStr(hStrCache, rSrc.strContext))
+ , pszSource(addStr(hStrCache, rSrc.strSource))
+ , pszComment(addStr(hStrCache, rSrc.strComment))
+ , hash(RTStrHash1(pszSource))
+ {
+ for (size_t i = 0; i < rSrc.astrTranslations.size(); i++)
+ vecTranslations.push_back(addStr(hStrCache, rSrc.astrTranslations[i]));
+ }
+
+ /** Helper. */
+ static const char *addStr(RTSTRCACHE hStrCache, const com::Utf8Str &rSrc)
+ {
+ if (rSrc.isNotEmpty())
+ {
+ const char *psz = RTStrCacheEnterN(hStrCache, rSrc.c_str(), rSrc.length());
+ if (RT_LIKELY(psz))
+ return psz;
+ throw std::bad_alloc();
+ }
+ return NULL;
+ }
+
+ };
+
+ struct HashOffset
+ {
+ uint32_t hash;
+ uint32_t offset;
+
+ HashOffset(uint32_t a_hash = 0, uint32_t a_offs = 0) : hash(a_hash), offset(a_offs) {}
+
+ bool operator<(const HashOffset &obj) const
+ {
+ return (hash != obj.hash ? hash < obj.hash : offset < obj.offset);
+ }
+
+ };
+
+ typedef std::set<HashOffset> QMHashSet;
+ typedef QMHashSet::const_iterator QMHashSetConstIter;
+ typedef std::vector<QMMessage> QMMessageArray;
+ typedef std::vector<uint8_t> QMByteArray;
+
+ QMHashSet m_hashSet;
+ QMMessageArray m_messageArray;
+ QMByteArray m_pluralRules;
+
+public:
+
+ QMTranslator_Impl() {}
+
+ enum PluralOpCodes
+ {
+ Pl_Eq = 0x01,
+ Pl_Lt = 0x02,
+ Pl_Leq = 0x03,
+ Pl_Between = 0x04,
+
+ Pl_OpMask = 0x07,
+
+ Pl_Not = 0x08,
+ Pl_Mod10 = 0x10,
+ Pl_Mod100 = 0x20,
+ Pl_Lead1000 = 0x40,
+
+ Pl_And = 0xFD,
+ Pl_Or = 0xFE,
+ Pl_NewRule = 0xFF,
+
+ Pl_LMask = 0x80,
+ };
+
+ /*
+ * Rules format:
+ * <O><2>[<3>][<&&><O><2>[<3>]]...[<||><O><2>[<3>][<&&><O><2>[<3>]]...]...[<New><O>...]...
+ * where:
+ * <O> - OpCode
+ * <2> - Second operand
+ * <3> - Third operand
+ * <&&> - 'And' operation
+ * <||> - 'Or' operation
+ * <New> - Start of rule for next plural form
+ * Rules are ordered by plural forms, i.e:
+ * <rule for first form (i.e. single)><New><rule for next form>...
+ */
+ bool checkPlural(const QMByteArray &aRules) const
+ {
+ if (aRules.empty())
+ return true;
+
+ uint32_t iPos = 0;
+ do {
+ uint8_t bOpCode = aRules[iPos];
+
+ /* Invalid place of And/Or/NewRule */
+ if (bOpCode & Pl_LMask)
+ return false;
+
+ /* 2nd operand */
+ iPos++;
+
+ /* 2nd operand missing */
+ if (iPos == aRules.size())
+ return false;
+
+ /* Invalid OpCode */
+ if ((bOpCode & Pl_OpMask) == 0)
+ return false;
+
+ if ((bOpCode & Pl_OpMask) == Pl_Between)
+ {
+ /* 3rd operand */
+ iPos++;
+
+ /* 3rd operand missing */
+ if (iPos == aRules.size())
+ return false;
+ }
+
+ /* And/Or/NewRule */
+ iPos++;
+
+ /* All rules checked */
+ if (iPos == aRules.size())
+ return true;
+
+ } while ( ( (aRules[iPos] == Pl_And)
+ || (aRules[iPos] == Pl_Or)
+ || (aRules[iPos] == Pl_NewRule))
+ && ++iPos != aRules.size());
+
+ return false;
+ }
+
+ size_t plural(size_t aNum) const
+ {
+ if (aNum == ~(size_t)0 || m_pluralRules.empty())
+ return 0;
+
+ size_t uPluralNumber = 0;
+ uint32_t iPos = 0;
+
+ /* Rules loop */
+ for (;;)
+ {
+ bool fOr = false;
+ /* 'Or' loop */
+ for (;;)
+ {
+ bool fAnd = true;
+ /* 'And' loop */
+ for (;;)
+ {
+ int iOpCode = m_pluralRules[iPos++];
+ size_t iOpLeft = aNum;
+ if (iOpCode & Pl_Mod10)
+ iOpLeft %= 10;
+ else if (iOpCode & Pl_Mod100)
+ iOpLeft %= 100;
+ else if (iOpCode & Pl_Lead1000)
+ {
+ while (iOpLeft >= 1000)
+ iOpLeft /= 1000;
+ }
+ size_t iOpRight = m_pluralRules[iPos++];
+ int iOp = iOpCode & Pl_OpMask;
+ size_t iOpRight1 = 0;
+ if (iOp == Pl_Between)
+ iOpRight1 = m_pluralRules[iPos++];
+
+ bool fResult = (iOp == Pl_Eq && iOpLeft == iOpRight)
+ || (iOp == Pl_Lt && iOpLeft < iOpRight)
+ || (iOp == Pl_Leq && iOpLeft <= iOpRight)
+ || (iOp == Pl_Between && iOpLeft >= iOpRight && iOpLeft <= iOpRight1);
+ if (iOpCode & Pl_Not)
+ fResult = !fResult;
+
+ fAnd = fAnd && fResult;
+ if (iPos == m_pluralRules.size() || m_pluralRules[iPos] != Pl_And)
+ break;
+ iPos++;
+ }
+ fOr = fOr || fAnd;
+ if (iPos == m_pluralRules.size() || m_pluralRules[iPos] != Pl_Or)
+ break;
+ iPos++;
+ }
+ if (fOr)
+ return uPluralNumber;
+
+ /* Qt returns last plural number if none of rules are match. */
+ uPluralNumber++;
+
+ if (iPos >= m_pluralRules.size())
+ return uPluralNumber;
+
+ iPos++; // Skip Pl_NewRule
+ }
+ }
+
+ const char *translate(const char *pszContext,
+ const char *pszSource,
+ const char *pszDisamb,
+ const size_t aNum,
+ const char **ppszSafeSource) const RT_NOEXCEPT
+ {
+ QMHashSetConstIter lowerIter, upperIter;
+
+ /* As turned out, comments (pszDisamb) are not kept always in result qm file
+ * Therefore, exclude them from the hash */
+ uint32_t hash = RTStrHash1(pszSource);
+ lowerIter = m_hashSet.lower_bound(HashOffset(hash, 0));
+ upperIter = m_hashSet.upper_bound(HashOffset(hash, UINT32_MAX));
+
+ /*
+ * Check different combinations with and without context and
+ * disambiguation. This can help us to find the translation even
+ * if context or disambiguation are not know or properly defined.
+ */
+ const char *apszCtx[] = {pszContext, pszContext, NULL, NULL};
+ const char *apszDisabm[] = {pszDisamb, NULL, pszDisamb, NULL};
+ AssertCompile(RT_ELEMENTS(apszCtx) == RT_ELEMENTS(apszDisabm));
+
+ for (size_t i = 0; i < RT_ELEMENTS(apszCtx); ++i)
+ {
+ for (QMHashSetConstIter iter = lowerIter; iter != upperIter; ++iter)
+ {
+ const QMMessage &message = m_messageArray[iter->offset];
+ if ( RTStrCmp(message.pszSource, pszSource) == 0
+ && (!apszCtx[i] || !*apszCtx[i] || RTStrCmp(message.pszContext, apszCtx[i]) == 0)
+ && (!apszDisabm[i] || !*apszDisabm[i] || RTStrCmp(message.pszComment, apszDisabm[i]) == 0 ))
+ {
+ *ppszSafeSource = message.pszSource;
+ const std::vector<const char *> &vecTranslations = m_messageArray[iter->offset].vecTranslations;
+ size_t const idxPlural = plural(aNum);
+ return vecTranslations[RT_MIN(idxPlural, vecTranslations.size() - 1)];
+ }
+ }
+ }
+
+ *ppszSafeSource = NULL;
+ return pszSource;
+ }
+
+ void load(QMBytesStream &stream, RTSTRCACHE hStrCache)
+ {
+ /* Load into local variables. If we failed during the load,
+ * it would allow us to keep the object in a valid (previous) state. */
+ QMHashSet hashSet;
+ QMMessageArray messageArray;
+ QMByteArray pluralRules;
+
+ stream.checkMagic();
+
+ while (!stream.hasFinished())
+ {
+ uint32_t sectionCode = stream.read8();
+ uint32_t sLen = stream.read32();
+
+ /* Hashes and Context sections are ignored. They contain hash tables
+ * to speed-up search which is not useful since we recalculate all hashes
+ * and don't perform context search by hash */
+ switch (sectionCode)
+ {
+ case Messages:
+ parseMessages(stream, hStrCache, &hashSet, &messageArray, sLen);
+ break;
+ case Hashes:
+ /* Only get size information to speed-up vector filling
+ * if Hashes section goes in the file before Message section */
+ if (messageArray.empty())
+ messageArray.reserve(sLen >> 3);
+ stream.seek(sLen);
+ break;
+ case NumerusRules:
+ {
+ pluralRules.resize(sLen);
+ uint32_t cbSize = stream.read((char *)&pluralRules[0], sLen);
+ if (cbSize < sLen)
+ throw QMException("Incorrect section size");
+ if (!checkPlural(pluralRules))
+ pluralRules.erase(pluralRules.begin(), pluralRules.end());
+ break;
+ }
+ case Contexts:
+ case Dependencies:
+ case Language:
+ stream.seek(sLen);
+ break;
+ default:
+ throw QMException("Unkown section");
+ }
+ }
+
+ /* Store the data into member variables.
+ * The following functions never generate exceptions */
+ m_hashSet.swap(hashSet);
+ m_messageArray.swap(messageArray);
+ m_pluralRules.swap(pluralRules);
+ }
+
+private:
+
+ /* Some QM stuff */
+ enum SectionType
+ {
+ Contexts = 0x2f,
+ Hashes = 0x42,
+ Messages = 0x69,
+ NumerusRules = 0x88,
+ Dependencies = 0x96,
+ Language = 0xa7
+ };
+
+ enum MessageType
+ {
+ End = 1,
+ SourceText16 = 2,
+ Translation = 3,
+ Context16 = 4,
+ Obsolete1 = 5, /**< was Hash */
+ SourceText = 6,
+ Context = 7,
+ Comment = 8
+ };
+
+ /* Read messages from the stream. */
+ static void parseMessages(QMBytesStream &stream, RTSTRCACHE hStrCache, QMHashSet * const hashSet,
+ QMMessageArray * const messageArray, size_t cbSize)
+ {
+ stream.setEnd(stream.tellPos() + cbSize);
+ uint32_t cMessage = 0;
+ while (!stream.hasFinished())
+ {
+ /* Process the record. Skip anything that doesn't have a source
+ string or any valid translations. Using C++ strings for temporary
+ storage here, as we don't want to pollute the cache we bogus strings
+ in case of duplicate sub-records or invalid records. */
+ QMMessageParse ParsedMsg;
+ parseMessageRecord(stream, &ParsedMsg);
+ if ( ParsedMsg.astrTranslations.size() > 0
+ && ParsedMsg.strSource.isNotEmpty())
+ {
+ /* Copy the strings over into the string cache and a hashed QMMessage,
+ before adding it to the result. */
+ QMMessage HashedMsg(hStrCache, ParsedMsg);
+ hashSet->insert(HashOffset(HashedMsg.hash, cMessage++));
+ messageArray->push_back(HashedMsg);
+
+ }
+ /*else: wtf? */
+ }
+ stream.setEnd();
+ }
+
+ /* Parse one message from the stream */
+ static void parseMessageRecord(QMBytesStream &stream, QMMessageParse * const message)
+ {
+ while (!stream.hasFinished())
+ {
+ uint8_t type = stream.read8();
+ switch (type)
+ {
+ case End:
+ return;
+ /* Ignored as obsolete */
+ case Context16:
+ case SourceText16:
+ stream.seek(stream.read32());
+ break;
+ case Translation:
+ message->astrTranslations.push_back(stream.readUtf16String());
+ break;
+
+ case SourceText:
+ message->strSource = stream.readString();
+ break;
+
+ case Context:
+ message->strContext = stream.readString();
+ break;
+
+ case Comment:
+ message->strComment = stream.readString();
+ break;
+
+ default:
+ /* Ignore unknown/obsolete block */
+ LogRel(("QMTranslator::parseMessageRecord(): Unknown/obsolete message block %x\n", type));
+ break;
+ }
+ }
+ }
+};
+
+/* Inteface functions implementation */
+QMTranslator::QMTranslator() : m_impl(new QMTranslator_Impl) {}
+
+QMTranslator::~QMTranslator() { delete m_impl; }
+
+const char *QMTranslator::translate(const char *pszContext, const char *pszSource, const char **ppszSafeSource,
+ const char *pszDisamb /*= NULL*/, const size_t aNum /*= ~(size_t)0*/) const RT_NOEXCEPT
+
+{
+ return m_impl->translate(pszContext, pszSource, pszDisamb, aNum, ppszSafeSource);
+}
+
+int QMTranslator::load(const char *pszFilename, RTSTRCACHE hStrCache) RT_NOEXCEPT
+{
+ /* To free safely the file in case of exception */
+ struct FileLoader
+ {
+ uint8_t *data;
+ size_t cbSize;
+ int vrc;
+ FileLoader(const char *pszFname)
+ {
+ vrc = RTFileReadAll(pszFname, (void**) &data, &cbSize);
+ }
+
+ ~FileLoader()
+ {
+ if (isSuccess())
+ RTFileReadAllFree(data, cbSize);
+ }
+ bool isSuccess() { return RT_SUCCESS(vrc); }
+ };
+
+ try
+ {
+ FileLoader loader(pszFilename);
+ if (loader.isSuccess())
+ {
+ QMBytesStream stream(loader.data, loader.cbSize);
+ m_impl->load(stream, hStrCache);
+ }
+ return loader.vrc;
+ }
+ catch(std::exception &e)
+ {
+ LogRel(("QMTranslator::load() failed to load file '%s', reason: %s\n", pszFilename, e.what()));
+ return VERR_INTERNAL_ERROR;
+ }
+ catch(...)
+ {
+ LogRel(("QMTranslator::load() failed to load file '%s'\n", pszFilename));
+ return VERR_GENERAL_FAILURE;
+ }
+}
diff --git a/src/VBox/Main/src-all/SecretKeyStore.cpp b/src/VBox/Main/src-all/SecretKeyStore.cpp
new file mode 100644
index 00000000..9c5f87e1
--- /dev/null
+++ b/src/VBox/Main/src-all/SecretKeyStore.cpp
@@ -0,0 +1,248 @@
+/* $Id: SecretKeyStore.cpp $ */
+/** @file
+ * Main - Secret key interface.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/memsafer.h>
+
+#include "SecretKeyStore.h"
+
+SecretKey::SecretKey(const uint8_t *pbKey, size_t cbKey, bool fKeyBufNonPageable)
+{
+ m_cRefs = 0;
+ m_fRemoveOnSuspend = false;
+ m_cUsers = 0;
+ m_cbKey = cbKey;
+
+ int vrc = RTMemSaferAllocZEx((void **)&this->m_pbKey, cbKey,
+ fKeyBufNonPageable ? RTMEMSAFER_F_REQUIRE_NOT_PAGABLE : 0);
+ if (RT_SUCCESS(vrc))
+ {
+ memcpy(this->m_pbKey, pbKey, cbKey);
+
+ /* Scramble content to make retrieving the key more difficult. */
+ vrc = RTMemSaferScramble(this->m_pbKey, cbKey);
+ }
+ else
+ throw vrc;
+}
+
+SecretKey::~SecretKey()
+{
+ Assert(!m_cRefs);
+
+ RTMemSaferFree(m_pbKey, m_cbKey);
+ m_cRefs = 0;
+ m_pbKey = NULL;
+ m_cbKey = 0;
+ m_fRemoveOnSuspend = false;
+ m_cUsers = 0;
+}
+
+uint32_t SecretKey::retain()
+{
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ if (cRefs == 1)
+ {
+ int vrc = RTMemSaferUnscramble(m_pbKey, m_cbKey);
+ AssertRC(vrc);
+ }
+
+ return cRefs;
+}
+
+uint32_t SecretKey::release()
+{
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ if (!cRefs)
+ {
+ int vrc = RTMemSaferScramble(m_pbKey, m_cbKey);
+ AssertRC(vrc);
+ }
+
+ return cRefs;
+}
+
+uint32_t SecretKey::refCount()
+{
+ return m_cRefs;
+}
+
+int SecretKey::setUsers(uint32_t cUsers)
+{
+ m_cUsers = cUsers;
+ return VINF_SUCCESS;
+}
+
+uint32_t SecretKey::getUsers()
+{
+ return m_cUsers;
+}
+
+int SecretKey::setRemoveOnSuspend(bool fRemoveOnSuspend)
+{
+ m_fRemoveOnSuspend = fRemoveOnSuspend;
+ return VINF_SUCCESS;
+}
+
+bool SecretKey::getRemoveOnSuspend()
+{
+ return m_fRemoveOnSuspend;
+}
+
+const void *SecretKey::getKeyBuffer()
+{
+ AssertReturn(m_cRefs > 0, NULL);
+ return m_pbKey;
+}
+
+size_t SecretKey::getKeySize()
+{
+ return m_cbKey;
+}
+
+SecretKeyStore::SecretKeyStore(bool fKeyBufNonPageable)
+{
+ m_fKeyBufNonPageable = fKeyBufNonPageable;
+}
+
+SecretKeyStore::~SecretKeyStore()
+{
+ int vrc = deleteAllSecretKeys(false /* fSuspend */, true /* fForce */);
+ AssertRC(vrc);
+}
+
+int SecretKeyStore::addSecretKey(const com::Utf8Str &strKeyId, const uint8_t *pbKey, size_t cbKey)
+{
+ /* Check that the ID is not existing already. */
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it != m_mapSecretKeys.end())
+ return VERR_ALREADY_EXISTS;
+
+ SecretKey *pKey = NULL;
+ try
+ {
+ pKey = new SecretKey(pbKey, cbKey, m_fKeyBufNonPageable);
+
+ m_mapSecretKeys.insert(std::make_pair(strKeyId, pKey));
+ }
+ catch (int vrc)
+ {
+ return vrc;
+ }
+ catch (std::bad_alloc &)
+ {
+ if (pKey)
+ delete pKey;
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::deleteSecretKey(const com::Utf8Str &strKeyId)
+{
+ SecretKeyMap::iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ if (pKey->refCount() != 0)
+ return VERR_RESOURCE_IN_USE;
+
+ m_mapSecretKeys.erase(it);
+ delete pKey;
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::retainSecretKey(const com::Utf8Str &strKeyId, SecretKey **ppKey)
+{
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ pKey->retain();
+
+ *ppKey = pKey;
+
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::releaseSecretKey(const com::Utf8Str &strKeyId)
+{
+ SecretKeyMap::const_iterator it = m_mapSecretKeys.find(strKeyId);
+ if (it == m_mapSecretKeys.end())
+ return VERR_NOT_FOUND;
+
+ SecretKey *pKey = it->second;
+ pKey->release();
+ return VINF_SUCCESS;
+}
+
+int SecretKeyStore::deleteAllSecretKeys(bool fSuspend, bool fForce)
+{
+ /* First check whether a key is still in use. */
+ if (!fForce)
+ {
+ for (SecretKeyMap::iterator it = m_mapSecretKeys.begin();
+ it != m_mapSecretKeys.end();
+ ++it)
+ {
+ SecretKey *pKey = it->second;
+ if ( pKey->refCount()
+ && ( ( pKey->getRemoveOnSuspend()
+ && fSuspend)
+ || !fSuspend))
+ return VERR_RESOURCE_IN_USE;
+ }
+ }
+
+ SecretKeyMap::iterator it = m_mapSecretKeys.begin();
+ while (it != m_mapSecretKeys.end())
+ {
+ SecretKey *pKey = it->second;
+ if ( pKey->getRemoveOnSuspend()
+ || !fSuspend)
+ {
+ AssertMsg(!pKey->refCount(), ("No one should access the stored key at this point anymore!\n"));
+ delete pKey;
+ SecretKeyMap::iterator itNext = it;
+ ++itNext;
+ m_mapSecretKeys.erase(it);
+ it = itNext;
+ }
+ else
+ ++it;
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-all/SharedFolderImpl.cpp b/src/VBox/Main/src-all/SharedFolderImpl.cpp
new file mode 100644
index 00000000..a544a37f
--- /dev/null
+++ b/src/VBox/Main/src-all/SharedFolderImpl.cpp
@@ -0,0 +1,413 @@
+/* $Id: SharedFolderImpl.cpp $ */
+/** @file
+ * VirtualBox COM class implementation
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_SHAREDFOLDER
+#include "SharedFolderImpl.h"
+#include "VirtualBoxImpl.h"
+#include "MachineImpl.h"
+#include "ConsoleImpl.h"
+
+#include "AutoCaller.h"
+
+#include <iprt/param.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/path.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// SharedFolder::Data structure
+/////////////////////////////////////////////////////////////////////////////
+
+struct SharedFolder::Data
+{
+ Data()
+ : fWritable(false),
+ fAutoMount(false)
+ { }
+
+ const Utf8Str strName;
+ const Utf8Str strHostPath;
+ bool fWritable;
+ bool fAutoMount;
+ const Utf8Str strAutoMountPoint;
+ Utf8Str strLastAccessError;
+};
+
+// constructor / destructor
+/////////////////////////////////////////////////////////////////////////////
+
+SharedFolder::SharedFolder()
+ : mParent(NULL),
+ mMachine(NULL),
+ mVirtualBox(NULL)
+{
+ m = new Data;
+}
+
+SharedFolder::~SharedFolder()
+{
+ delete m;
+ m = NULL;
+}
+
+HRESULT SharedFolder::FinalConstruct()
+{
+ return BaseFinalConstruct();
+}
+
+void SharedFolder::FinalRelease()
+{
+ uninit();
+ BaseFinalRelease();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes a machine instance that lives in the server address space.
+ *
+ * @param aMachine parent Machine object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMount if auto mounted by guest true, false otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::init(Machine *aMachine,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mMachine) = aMachine;
+
+ HRESULT hrc = i_protectedInit(aMachine, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+/**
+ * Initializes the shared folder object given another object
+ * (a kind of copy constructor). This object makes a private copy of data
+ * of the original object passed as an argument.
+ *
+ * @param aMachine parent Machine object
+ * @param aThat shared folder object to copy
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::initCopy(Machine *aMachine, SharedFolder *aThat)
+{
+ ComAssertRet(aThat, E_INVALIDARG);
+
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mMachine) = aMachine;
+
+ HRESULT hrc = i_protectedInit(aMachine,
+ aThat->m->strName,
+ aThat->m->strHostPath,
+ aThat->m->fWritable,
+ aThat->m->fAutoMount,
+ aThat->m->strAutoMountPoint,
+ false /* fFailOnError */ );
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+# if 0
+
+/**
+ * Initializes the shared folder object.
+ *
+ * This variant initializes a global instance that lives in the server address space. It is not presently used.
+ *
+ * @param aVirtualBox VirtualBox parent object
+ * @param aName logical name of the shared folder
+ * @param aHostPath full path to the shared folder on the host
+ * @param aWritable writable if true, readonly otherwise
+ * @param aAutoMountPoint Where the guest should try auto mount it.
+ * @param fFailOnError Whether to fail with an error if the shared folder path is bad.
+ *
+ * @return COM result indicator
+ */
+HRESULT SharedFolder::init(VirtualBox *aVirtualBox,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ /* Enclose the state transition NotReady->InInit->Ready */
+ AutoInitSpan autoInitSpan(this);
+ AssertReturn(autoInitSpan.isOk(), E_FAIL);
+
+ unconst(mVirtualBox) = aVirtualBox;
+
+ HRESULT hrc = protectedInit(aVirtualBox, aName, aHostPath, aWritable, aAutoMount, aAutoMountPoint, fFailOnError);
+
+ /* Confirm a successful initialization when it's the case */
+ if (SUCCEEDED(hrc))
+ autoInitSpan.setSucceeded();
+
+ return hrc;
+}
+
+# endif
+
+/**
+ * Shared initialization code. Called from the other constructors.
+ *
+ * @note
+ * Must be called from under the object's lock!
+ */
+HRESULT SharedFolder::i_protectedInit(VirtualBoxBase *aParent,
+ const Utf8Str &aName,
+ const Utf8Str &aHostPath,
+ bool aWritable,
+ bool aAutoMount,
+ const Utf8Str &aAutoMountPoint,
+ bool fFailOnError)
+{
+ LogFlowThisFunc(("aName={%s}, aHostPath={%s}, aWritable={%d}, aAutoMount={%d}\n",
+ aName.c_str(), aHostPath.c_str(), aWritable, aAutoMount));
+
+ ComAssertRet(aParent && aName.isNotEmpty() && aHostPath.isNotEmpty(), E_INVALIDARG);
+
+ Utf8Str hostPath = aHostPath;
+ size_t hostPathLen = hostPath.length();
+
+ /* Remove the trailing slash unless it's a root directory
+ * (otherwise the comparison with the RTPathAbs() result will fail at least
+ * on Linux). Note that this isn't really necessary for the shared folder
+ * itself, since adding a mapping eventually results into a
+ * RTDirOpenFiltered() call (see HostServices/SharedFolders) that seems to
+ * accept both the slashified paths and not. */
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+ if ( hostPathLen > 2
+ && RTPATH_IS_SEP(hostPath.c_str()[hostPathLen - 1])
+ && RTPATH_IS_VOLSEP(hostPath.c_str()[hostPathLen - 2]))
+ ;
+#else
+ if (hostPathLen == 1 && RTPATH_IS_SEP(hostPath[0]))
+ ;
+#endif
+ else
+ hostPath.stripTrailingSlash();
+
+ if (fFailOnError)
+ {
+ /* Check whether the path is full (absolute) */
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathAbs(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull));
+ if (RT_FAILURE(vrc))
+ return setErrorBoth(E_INVALIDARG, vrc, tr("Invalid shared folder path: '%s' (%Rrc)"), hostPath.c_str(), vrc);
+
+ if (RTPathCompare(hostPath.c_str(), hostPathFull) != 0)
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not absolute"), hostPath.c_str());
+
+ RTFSOBJINFO ObjInfo;
+ vrc = RTPathQueryInfoEx(hostPathFull, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(vrc))
+ return setError(E_INVALIDARG, tr("RTPathQueryInfo failed on shared folder path '%s': %Rrc"), hostPathFull, vrc);
+
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ return setError(E_INVALIDARG, tr("Shared folder path '%s' is not a directory"), hostPathFull);
+ }
+
+ unconst(mParent) = aParent;
+
+ unconst(m->strName) = aName;
+ unconst(m->strHostPath) = hostPath;
+ m->fWritable = aWritable;
+ m->fAutoMount = aAutoMount;
+ unconst(m->strAutoMountPoint) = aAutoMountPoint;
+
+ 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 SharedFolder::uninit()
+{
+ LogFlowThisFunc(("\n"));
+
+ /* Enclose the state transition Ready->InUninit->NotReady */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ unconst(mParent) = NULL;
+ unconst(mMachine) = NULL;
+ unconst(mVirtualBox) = NULL;
+}
+
+// wrapped ISharedFolder properties
+/////////////////////////////////////////////////////////////////////////////
+HRESULT SharedFolder::getName(com::Utf8Str &aName)
+{
+ /* mName is constant during life time, no need to lock */
+ aName = m->strName;
+ return S_OK;
+}
+
+HRESULT SharedFolder::getHostPath(com::Utf8Str &aHostPath)
+{
+ /* mHostPath is constant during life time, no need to lock */
+ aHostPath = m->strHostPath;
+ return S_OK;
+}
+
+HRESULT SharedFolder::getAccessible(BOOL *aAccessible)
+{
+ /* mName and mHostPath are constant during life time, no need to lock */
+
+ /* check whether the host path exists */
+ Utf8Str hostPath = m->strHostPath;
+ char hostPathFull[RTPATH_MAX];
+ int vrc = RTPathExists(hostPath.c_str()) ? RTPathReal(hostPath.c_str(),
+ hostPathFull,
+ sizeof(hostPathFull))
+ : VERR_PATH_NOT_FOUND;
+ if (RT_SUCCESS(vrc))
+ {
+ *aAccessible = TRUE;
+ return S_OK;
+ }
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m->strLastAccessError = Utf8StrFmt(tr("'%s' is not accessible (%Rrc)"),
+ m->strHostPath.c_str(),
+ vrc);
+
+ Log1WarningThisFunc(("m.lastAccessError=\"%s\"\n", m->strLastAccessError.c_str()));
+
+ *aAccessible = FALSE;
+
+ return S_OK;
+}
+
+HRESULT SharedFolder::getWritable(BOOL *aWritable)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aWritable = m->fWritable;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setWritable(BOOL aWritable)
+{
+ RT_NOREF(aWritable);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getAutoMount(BOOL *aAutoMount)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ *aAutoMount = m->fAutoMount;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setAutoMount(BOOL aAutoMount)
+{
+ RT_NOREF(aAutoMount);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getAutoMountPoint(com::Utf8Str &aAutoMountPoint)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aAutoMountPoint = m->strAutoMountPoint;
+ return S_OK;
+}
+
+HRESULT SharedFolder::setAutoMountPoint(com::Utf8Str const &aAutoMountPoint)
+{
+ RT_NOREF(aAutoMountPoint);
+ return E_NOTIMPL;
+}
+
+HRESULT SharedFolder::getLastAccessError(com::Utf8Str &aLastAccessError)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aLastAccessError = m->strLastAccessError;
+ return S_OK;
+}
+
+
+const Utf8Str& SharedFolder::i_getName() const
+{
+ return m->strName;
+}
+
+const Utf8Str& SharedFolder::i_getHostPath() const
+{
+ return m->strHostPath;
+}
+
+bool SharedFolder::i_isWritable() const
+{
+ return m->fWritable;
+}
+
+bool SharedFolder::i_isAutoMounted() const
+{
+ return m->fAutoMount;
+}
+
+const Utf8Str &SharedFolder::i_getAutoMountPoint() const
+{
+ return m->strAutoMountPoint;
+}
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/TextScript.cpp b/src/VBox/Main/src-all/TextScript.cpp
new file mode 100644
index 00000000..a4b70ca8
--- /dev/null
+++ b/src/VBox/Main/src-all/TextScript.cpp
@@ -0,0 +1,388 @@
+/* $Id: TextScript.cpp $ */
+/** @file
+ * Classes for reading/parsing/saving text scripts (unattended installation, ++).
+ */
+
+/*
+ * 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_UNATTENDED
+#include "LoggingNew.h"
+#include "TextScript.h"
+
+#include <VBox/err.h>
+
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+#include <iprt/path.h>
+
+using namespace std;
+
+
+/*********************************************************************************************************************************
+* BaseTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT BaseTextScript::read(const Utf8Str &rStrFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ RTVFSFILE hVfsFile;
+ int vrc = RTVfsFileOpenNormal(rStrFilename.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
+ if (RT_SUCCESS(vrc))
+ {
+ hrc = readFromHandle(hVfsFile, rStrFilename.c_str());
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to open '%s' (%Rrc)"), rStrFilename.c_str(), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::readFromHandle(RTVFSFILE hVfsFile, const char *pszFilename)
+{
+ /*
+ * Open the file for reading and figure it's size. Capping the size
+ * at 16MB so we don't exaust the heap on bad input.
+ */
+ HRESULT hrc;
+ uint64_t cbFile = 0;
+ int vrc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if ( RT_SUCCESS(vrc)
+ && cbFile < _16M)
+ {
+ /*
+ * Exploint the jolt() feature of RTCString and read the content directly into
+ * its storage buffer.
+ */
+ vrc = mStrScriptFullContent.reserveNoThrow((size_t)cbFile + 1);
+ if (RT_SUCCESS(vrc))
+ {
+ char *pszDst = mStrScriptFullContent.mutableRaw();
+ vrc = RTVfsFileReadAt(hVfsFile, 0 /*off*/, pszDst, (size_t)cbFile, NULL);
+ pszDst[(size_t)cbFile] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * We must validate the encoding or we'll be subject to potential security trouble.
+ * If this turns out to be problematic, we will need to implement codeset
+ * conversion coping mechanisms.
+ */
+ vrc = RTStrValidateEncodingEx(pszDst, (size_t)cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(vrc))
+ {
+ mStrScriptFullContent.jolt();
+ return S_OK;
+ }
+
+ hrc = mpSetError->setErrorVrc(vrc, tr("'%s' isn't valid UTF-8: %Rrc"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error reading '%s': %Rrc"), pszFilename, vrc);
+ mStrScriptFullContent.setNull();
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Failed to allocate memory (%'RU64 bytes) for '%s'", "", cbFile),
+ cbFile, pszFilename);
+ }
+ else if (RT_SUCCESS(vrc))
+ hrc = mpSetError->setErrorVrc(VERR_FILE_TOO_BIG, tr("'%s' is too big (max 16MB): %'RU64"), pszFilename, cbFile);
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("RTVfsFileQuerySize failed (%Rrc)"), vrc);
+ return hrc;
+}
+
+HRESULT BaseTextScript::save(const Utf8Str &rStrFilename, bool fOverwrite)
+{
+ /*
+ * We may have to append the default filename to the
+ */
+ const char *pszFilename = rStrFilename.c_str();
+ Utf8Str strWithDefaultFilename;
+ if ( getDefaultFilename() != NULL
+ && *getDefaultFilename() != '\0'
+ && RTDirExists(rStrFilename.c_str()) )
+ {
+ try
+ {
+ strWithDefaultFilename = rStrFilename;
+ strWithDefaultFilename.append(RTPATH_SLASH);
+ strWithDefaultFilename.append(getDefaultFilename());
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pszFilename = strWithDefaultFilename.c_str();
+ }
+
+ /*
+ * Save the filename for later use.
+ */
+ try
+ {
+ mStrSavedPath = pszFilename;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ /*
+ * Use the saveToString method to produce the content.
+ */
+ Utf8Str strDst;
+ HRESULT hrc = saveToString(strDst);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Write the content.
+ */
+ RTFILE hFile;
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_ALL;
+ if (fOverwrite)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ int vrc = RTFileOpen(&hFile, pszFilename, fOpen);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileWrite(hFile, strDst.c_str(), strDst.length(), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTFileClose(hFile);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRelFlow(("GeneralTextScript::save(): saved %zu bytes to '%s'\n", strDst.length(), pszFilename));
+ return S_OK;
+ }
+ }
+ RTFileClose(hFile);
+ RTFileDelete(pszFilename);
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error writing to '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ else
+ hrc = mpSetError->setErrorVrc(vrc, tr("Error creating/replacing '%s' (%Rrc)"), pszFilename, vrc);
+ }
+ return hrc;
+}
+
+
+
+/*********************************************************************************************************************************
+* GeneralTextScript Implementation *
+*********************************************************************************************************************************/
+
+HRESULT GeneralTextScript::parse()
+{
+ AssertReturn(!mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("parse called more than once")));
+
+ /*
+ * Split the raw context into an array of lines.
+ */
+ try
+ {
+ mScriptContentByLines = mStrScriptFullContent.split("\n");
+ }
+ catch (std::bad_alloc &)
+ {
+ mScriptContentByLines.clear();
+ return E_OUTOFMEMORY;
+ }
+
+ mfDataParsed = true;
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::saveToString(Utf8Str &rStrDst)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("saveToString() called before parse()")));
+
+ /*
+ * Calc the required size first.
+ */
+ size_t const cLines = mScriptContentByLines.size();
+ size_t cbTotal = 1;
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ cbTotal = mScriptContentByLines[iLine].length() + 1;
+
+ /*
+ * Clear the output and try reserve sufficient space.
+ */
+ rStrDst.setNull();
+
+ int vrc = rStrDst.reserveNoThrow(cbTotal);
+ if (RT_FAILURE(vrc))
+ return E_OUTOFMEMORY;
+
+ /*
+ * Assemble the output.
+ */
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ {
+ try
+ {
+ rStrDst.append(mScriptContentByLines[iLine]);
+ rStrDst.append('\n');
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ return S_OK;
+}
+
+const RTCString &GeneralTextScript::getContentOfLine(size_t idxLine)
+{
+ if (idxLine < mScriptContentByLines.size())
+ return mScriptContentByLines[idxLine];
+ return Utf8Str::Empty;
+}
+
+
+HRESULT GeneralTextScript::setContentOfLine(size_t idxLine, const Utf8Str &rStrNewLine)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting to set line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+ try
+ {
+ mScriptContentByLines[idxLine] = rStrNewLine;
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+vector<size_t> GeneralTextScript::findTemplate(const Utf8Str &rStrNeedle,
+ RTCString::CaseSensitivity enmCase /*= RTCString::CaseSensitive*/)
+{
+ vector<size_t> vecHitLineNumbers;
+ size_t const cLines = mScriptContentByLines.size();
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ if (mScriptContentByLines[iLine].contains(rStrNeedle, enmCase))
+ vecHitLineNumbers.push_back(iLine);
+
+ return vecHitLineNumbers;
+}
+
+HRESULT GeneralTextScript::findAndReplace(size_t idxLine, const Utf8Str &rStrNeedle, const Utf8Str &rStrReplacement)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("attempting search&replace in line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ size_t const offNeedle = rDstString.find(&rStrNeedle);
+ if (offNeedle != RTCString::npos)
+ {
+ try
+ {
+ RTCString strBefore(rDstString, 0, offNeedle);
+ RTCString strAfter(rDstString, offNeedle + rStrNeedle.length());
+ rDstString = strBefore;
+ strBefore.setNull();
+ rDstString.append(rStrReplacement);
+ rDstString.append(strAfter);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendToLine(size_t idxLine, const Utf8Str &rStrToAppend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("appending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ try
+ {
+ mScriptContentByLines[idxLine].append(rStrToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::prependToLine(size_t idxLine, const Utf8Str &rStrToPrepend)
+{
+ AssertReturn(idxLine < mScriptContentByLines.size(),
+ mpSetError->setErrorBoth(E_FAIL, VERR_OUT_OF_RANGE,
+ tr("prepending to line %zu when there are only %zu lines", "",
+ mScriptContentByLines.size()),
+ idxLine, mScriptContentByLines.size()));
+
+ RTCString &rDstString = mScriptContentByLines[idxLine];
+ try
+ {
+ RTCString strCopy;
+ rDstString.swap(strCopy);
+ rDstString.reserve(strCopy.length() + rStrToPrepend.length() + 1);
+ rDstString = rStrToPrepend;
+ rDstString.append(strCopy);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+HRESULT GeneralTextScript::appendLine(const Utf8Str &rStrLineToAppend)
+{
+ AssertReturn(mfDataParsed, mpSetError->setErrorBoth(E_FAIL, VERR_WRONG_ORDER, tr("appendLine() called before parse()")));
+
+ try
+ {
+ mScriptContentByLines.append(rStrLineToAppend);
+ }
+ catch (std::bad_alloc &)
+ {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
diff --git a/src/VBox/Main/src-all/ThreadTask.cpp b/src/VBox/Main/src-all/ThreadTask.cpp
new file mode 100644
index 00000000..8fa28e0e
--- /dev/null
+++ b/src/VBox/Main/src-all/ThreadTask.cpp
@@ -0,0 +1,136 @@
+/* $Id: ThreadTask.cpp $ */
+/** @file
+ * Implementation of ThreadTask
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/errcore.h>
+#include <iprt/thread.h>
+
+#include "VirtualBoxBase.h"
+#include "ThreadTask.h"
+
+#define LOG_GROUP LOG_GROUP_MAIN_THREAD_TASK
+#include "LoggingNew.h"
+
+/**
+ * Starts the task (on separate thread), consuming @a this.
+ *
+ * The function takes ownership of "this" instance (object instance which calls
+ * this function). And the function is responsible for deletion of "this"
+ * pointer in all cases.
+ *
+ * Possible way of usage:
+ * @code{.cpp}
+ HRESULT hr;
+ SomeTaskInheritedFromThreadTask *pTask = NULL;
+ try
+ {
+ pTask = new SomeTaskInheritedFromThreadTask(this);
+ if (!pTask->Init()) // some init procedure
+ throw E_FAIL;
+ }
+ catch (...)
+ {
+ if (pTask);
+ delete pTask;
+ return E_FAIL;
+ }
+ return pTask->createThread(); // pTask is always consumed
+ @endcode
+ *
+ * @sa createThreadWithType
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThread(void)
+{
+ return createThreadInternal(RTTHREADTYPE_MAIN_WORKER);
+}
+
+
+/**
+ * Same ThreadTask::createThread(), except it takes a thread type parameter.
+ *
+ * @param enmType The thread type.
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThreadWithType(RTTHREADTYPE enmType)
+{
+ return createThreadInternal(enmType);
+}
+
+
+/**
+ * Internal worker for ThreadTask::createThread,
+ * ThreadTask::createThreadWithType.
+ *
+ * @note Always consumes @a this!
+ */
+HRESULT ThreadTask::createThreadInternal(RTTHREADTYPE enmType)
+{
+ LogThisFunc(("Created \"%s\"\n", m_strTaskName.c_str()));
+
+ mAsync = true;
+ int vrc = RTThreadCreate(NULL,
+ taskHandlerThreadProc,
+ (void *)this,
+ 0,
+ enmType,
+ 0,
+ m_strTaskName.c_str());
+ if (RT_SUCCESS(vrc))
+ return S_OK;
+
+ mAsync = false;
+ delete this;
+ return E_FAIL;
+}
+
+
+/**
+ * Static method that can get passed to RTThreadCreate to have a
+ * thread started for a Task.
+ */
+/* static */ DECLCALLBACK(int) ThreadTask::taskHandlerThreadProc(RTTHREAD /* thread */, void *pvUser)
+{
+ if (pvUser == NULL)
+ return VERR_INVALID_POINTER; /* nobody cares */
+
+ ThreadTask *pTask = static_cast<ThreadTask *>(pvUser);
+
+ LogFunc(("Started \"%s\"\n", pTask->m_strTaskName.c_str()));
+
+ /*
+ * handler shall catch and process all possible cases as errors and exceptions.
+ */
+ pTask->handler();
+
+ LogFunc(("Ended \"%s\"\n", pTask->m_strTaskName.c_str()));
+
+ delete pTask;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-end-alternative.d b/src/VBox/Main/src-all/VBoxAPI-end-alternative.d
new file mode 100644
index 00000000..71912691
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-end-alternative.d
@@ -0,0 +1,3 @@
+
+};
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-end.d b/src/VBox/Main/src-all/VBoxAPI-end.d
new file mode 100644
index 00000000..5f88a01b
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-end.d
@@ -0,0 +1,9 @@
+
+};
+
+#pragma D attributes Evolving/Evolving/Common provider vboxapi provider
+#pragma D attributes Private/Private/Unknown provider vboxapi module
+#pragma D attributes Private/Private/Unknown provider vboxapi function
+#pragma D attributes Evolving/Evolving/Common provider vboxapi name
+#pragma D attributes Evolving/Evolving/Common provider vboxapi args
+
diff --git a/src/VBox/Main/src-all/VBoxAPI-start-alternative.d b/src/VBox/Main/src-all/VBoxAPI-start-alternative.d
new file mode 100644
index 00000000..734f2d00
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-start-alternative.d
@@ -0,0 +1,39 @@
+/* $Id: VBoxAPI-start-alternative.d $ */
+/** @file
+ * VBoxAPI - Static dtrace probes.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*#pragma D attributes Evolving/Evolving/Common provider vboxapi provider
+#pragma D attributes Private/Private/Unknown provider vboxapi module
+#pragma D attributes Private/Private/Unknown provider vboxapi function
+#pragma D attributes Evolving/Evolving/Common provider vboxapi name
+#pragma D attributes Evolving/Evolving/Common provider vboxapi args*/
+
+provider vboxapi
+{
+ /* Manually defined probes: */
+ probe machine__state__changed(void *a_pMachine, int a_enmNewState, int a_enmOldState, const char *pszMachineUuid);
+
+ /* The following probes are automatically generated and changes with the API: */
diff --git a/src/VBox/Main/src-all/VBoxAPI-start.d b/src/VBox/Main/src-all/VBoxAPI-start.d
new file mode 100644
index 00000000..6b0164c8
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxAPI-start.d
@@ -0,0 +1,33 @@
+/* $Id: VBoxAPI-start.d $ */
+/** @file
+ * VBoxAPI - Static dtrace probes.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+provider vboxapi
+{
+ /* Manually defined probes: */
+ probe machine__state__changed(void *a_pMachine, int a_enmNewState, int a_enmOldState, const char *pszMachineUuid);
+
+ /* The following probes are automatically generated and changes with the API: */
diff --git a/src/VBox/Main/src-all/VBoxLibSsh.def b/src/VBox/Main/src-all/VBoxLibSsh.def
new file mode 100644
index 00000000..83ea73bc
--- /dev/null
+++ b/src/VBox/Main/src-all/VBoxLibSsh.def
@@ -0,0 +1,35 @@
+; $Id: VBoxLibSsh.def $
+;; @file
+; VBoxLibSsh - Definition file for lazy import generation for VBoxC.
+;
+
+;
+; 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
+;
+
+LIBRARY VBoxLibSsh
+EXPORTS
+ ssh_key_free
+ ssh_pki_generate
+ ssh_pki_export_privkey_base64
+ ssh_pki_export_privkey_file
+ ssh_pki_export_pubkey_base64
+ ssh_string_free_char
diff --git a/src/VBox/Main/src-all/VirtualBoxBase.cpp b/src/VBox/Main/src-all/VirtualBoxBase.cpp
new file mode 100644
index 00000000..0fa8d8d3
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxBase.cpp
@@ -0,0 +1,884 @@
+/* $Id: VirtualBoxBase.cpp $ */
+/** @file
+ * VirtualBox COM base classes 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
+#include <iprt/semaphore.h>
+#include <iprt/asm.h>
+#include <iprt/cpp/exception.h>
+
+#include <typeinfo>
+
+#if !defined(VBOX_WITH_XPCOM)
+# include <iprt/win/windows.h>
+#else /* !defined(VBOX_WITH_XPCOM) */
+/// @todo remove when VirtualBoxErrorInfo goes away from here
+# include <nsIServiceManager.h>
+# include <nsIExceptionService.h>
+#endif /* !defined(VBOX_WITH_XPCOM) */
+
+#include "VirtualBoxBase.h"
+#include "AutoCaller.h"
+#include "VirtualBoxErrorInfoImpl.h"
+#include "VirtualBoxTranslator.h"
+#include "Global.h"
+#include "LoggingNew.h"
+
+#include "VBox/com/ErrorInfo.h"
+#include "VBox/com/MultiResult.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// VirtualBoxBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+CLASSFACTORY_STAT g_aClassFactoryStats[CLASSFACTORYSTATS_MAX] =
+{
+ { "--- totals ---", 0 },
+ { NULL, 0 }
+};
+
+RWLockHandle *g_pClassFactoryStatsLock = NULL;
+
+
+VirtualBoxBase::VirtualBoxBase() :
+ mState(this),
+ iFactoryStat(~0U)
+{
+ mObjectLock = NULL;
+
+ if (!g_pClassFactoryStatsLock)
+ {
+ RWLockHandle *lock = new RWLockHandle(LOCKCLASS_OBJECTSTATE);
+ if (!ASMAtomicCmpXchgPtr(&g_pClassFactoryStatsLock, lock, NULL))
+ delete lock;
+ }
+ Assert(g_pClassFactoryStatsLock);
+}
+
+VirtualBoxBase::~VirtualBoxBase()
+{
+ Assert(iFactoryStat == ~0U);
+ if (mObjectLock)
+ delete mObjectLock;
+}
+
+HRESULT VirtualBoxBase::BaseFinalConstruct()
+{
+ Assert(iFactoryStat == ~0U);
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoWriteLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ g_aClassFactoryStats[0].current++;
+ g_aClassFactoryStats[0].overall++;
+ const char *pszName = getComponentName();
+ uint32_t i = 1;
+ while (i < CLASSFACTORYSTATS_MAX && g_aClassFactoryStats[i].psz)
+ {
+ if (g_aClassFactoryStats[i].psz == pszName)
+ break;
+ i++;
+ }
+ if (i < CLASSFACTORYSTATS_MAX)
+ {
+ if (!g_aClassFactoryStats[i].psz)
+ {
+ g_aClassFactoryStats[i].psz = pszName;
+ g_aClassFactoryStats[i].current = 0;
+ g_aClassFactoryStats[i].overall = 0;
+ }
+ iFactoryStat = i;
+ g_aClassFactoryStats[i].current++;
+ g_aClassFactoryStats[i].overall++;
+ }
+ else
+ AssertMsg(i < CLASSFACTORYSTATS_MAX, ("%u exhausts size of factory housekeeping array\n", i));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+
+#ifdef RT_OS_WINDOWS
+ return CoCreateFreeThreadedMarshaler(this, //GetControllingUnknown(),
+ m_pUnkMarshaler.asOutParam());
+#else
+ return S_OK;
+#endif
+}
+
+void VirtualBoxBase::BaseFinalRelease()
+{
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoWriteLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ g_aClassFactoryStats[0].current--;
+ const char *pszName = getComponentName();
+ if (iFactoryStat < CLASSFACTORYSTATS_MAX)
+ {
+ if (g_aClassFactoryStats[iFactoryStat].psz == pszName)
+ {
+ g_aClassFactoryStats[iFactoryStat].current--;
+ iFactoryStat = ~0U;
+ }
+ else
+ AssertMsgFailed(("could not find factory housekeeping array entry for %s (index %u contains %s)\n", pszName, iFactoryStat, g_aClassFactoryStats[iFactoryStat].psz));
+ }
+ else
+ AssertMsgFailed(("factory housekeeping array corruption, index %u is too large\n", iFactoryStat));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+
+#ifdef RT_OS_WINDOWS
+ m_pUnkMarshaler.setNull();
+#endif
+}
+
+void APIDumpComponentFactoryStats()
+{
+ if (g_pClassFactoryStatsLock)
+ {
+ AutoReadLock alock(g_pClassFactoryStatsLock COMMA_LOCKVAL_SRC_POS);
+ for (uint32_t i = 0; i < CLASSFACTORYSTATS_MAX && g_aClassFactoryStats[i].psz; i++)
+ LogRel(("CFS: component %-30s current %-10u total %-10u\n",
+ g_aClassFactoryStats[i].psz, g_aClassFactoryStats[i].current,
+ g_aClassFactoryStats[i].overall));
+ }
+ else
+ Assert(g_pClassFactoryStatsLock);
+}
+
+/**
+ * This virtual method returns an RWLockHandle that can be used to
+ * protect instance data. This RWLockHandle is generally referred to
+ * as the "object lock"; its locking class (for lock order validation)
+ * must be returned by another virtual method, getLockingClass(), which
+ * by default returns LOCKCLASS_OTHEROBJECT but is overridden by several
+ * subclasses such as VirtualBox, Host, Machine and others.
+ *
+ * On the first call this method lazily creates the RWLockHandle.
+ *
+ * @return
+ */
+/* virtual */
+RWLockHandle *VirtualBoxBase::lockHandle() const
+{
+ /* lazy initialization */
+ if (RT_LIKELY(mObjectLock))
+ return mObjectLock;
+
+ AssertCompile(sizeof(RWLockHandle *) == sizeof(void *));
+
+ // getLockingClass() is overridden by many subclasses to return
+ // one of the locking classes listed at the top of AutoLock.h
+ RWLockHandle *objLock = new RWLockHandle(getLockingClass());
+ if (!ASMAtomicCmpXchgPtr(&mObjectLock, objLock, NULL))
+ {
+ delete objLock;
+ objLock = ASMAtomicReadPtrT(&mObjectLock, RWLockHandle *);
+ }
+ return objLock;
+}
+
+/**
+ * Handles unexpected exceptions by turning them into COM errors in release
+ * builds or by hitting a breakpoint in the release builds.
+ *
+ * Usage pattern:
+ * @code
+ try
+ {
+ // ...
+ }
+ catch (LaLalA)
+ {
+ // ...
+ }
+ catch (...)
+ {
+ hrc = VirtualBox::handleUnexpectedExceptions(this, RT_SRC_POS);
+ }
+ * @endcode
+ *
+ * @param aThis object where the exception happened
+ * @param SRC_POS "RT_SRC_POS" macro instantiation.
+ * */
+/* static */
+HRESULT VirtualBoxBase::handleUnexpectedExceptions(VirtualBoxBase *const aThis, RT_SRC_POS_DECL)
+{
+ try
+ {
+ /* re-throw the current exception */
+ throw;
+ }
+ catch (const RTCError &err) // includes all XML exceptions
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ "%s.\n%s[%d] (%s)",
+ err.what(), pszFile, iLine, pszFunction);
+ }
+ catch (const std::exception &err)
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ tr("Unexpected exception: %s [%s]\n%s[%d] (%s)"),
+ err.what(), typeid(err).name(), pszFile, iLine, pszFunction);
+ }
+ catch (...)
+ {
+ return setErrorInternalF(E_FAIL, aThis->getClassIID(), aThis->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ tr("Unknown exception\n%s[%d] (%s)"),
+ pszFile, iLine, pszFunction);
+ }
+
+#ifndef _MSC_VER /* (unreachable) */
+ /* should not get here */
+ AssertFailed();
+ return E_FAIL;
+#endif
+}
+
+
+/**
+ * Sets error info for the current thread. This is an internal function that
+ * gets eventually called by all public variants. If @a aWarning is
+ * @c true, then the highest (31) bit in the @a aResultCode value which
+ * indicates the error severity is reset to zero to make sure the receiver will
+ * recognize that the created error info object represents a warning rather
+ * than an error.
+ */
+/* static */
+HRESULT VirtualBoxBase::setErrorInternalF(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail,
+ const char *aText, ...)
+{
+ va_list va;
+ va_start(va, aText);
+ HRESULT hres = setErrorInternalV(aResultCode, aIID, aComponent, aText, va,
+ aWarning, aLogIt, aResultDetail);
+ va_end(va);
+ return hres;
+}
+
+/**
+ * Sets error info for the current thread. This is an internal function that
+ * gets eventually called by all public variants. If @a aWarning is
+ * @c true, then the highest (31) bit in the @a aResultCode value which
+ * indicates the error severity is reset to zero to make sure the receiver will
+ * recognize that the created error info object represents a warning rather
+ * than an error.
+ */
+/* static */
+HRESULT VirtualBoxBase::setErrorInternalV(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *aComponent,
+ const char *aText,
+ va_list aArgs,
+ bool aWarning,
+ bool aLogIt,
+ LONG aResultDetail /* = 0*/)
+{
+ /* whether multi-error mode is turned on */
+ bool preserve = MultiResult::isMultiEnabled();
+
+ com::Utf8Str strText;
+ if (aLogIt)
+ {
+#ifdef VBOX_WITH_MAIN_NLS
+ strText = VirtualBoxTranslator::trSource(aText);
+#else
+ strText = aText;
+#endif
+ va_list va2;
+ va_copy(va2, aArgs);
+ LogRel(("%s [COM]: aRC=%Rhrc (%#08x) aIID={%RTuuid} aComponent={%s} aText={%N}, preserve=%RTbool aResultDetail=%d\n",
+ aWarning ? "WARNING" : "ERROR",
+ aResultCode,
+ aResultCode,
+ &aIID,
+ aComponent,
+ strText.c_str(),
+ &va2,
+ preserve,
+ aResultDetail));
+ va_end(va2);
+ }
+
+ /* these are mandatory, others -- not */
+ AssertReturn((!aWarning && FAILED(aResultCode)) ||
+ (aWarning && aResultCode != S_OK),
+ E_FAIL);
+
+ /* reset the error severity bit if it's a warning */
+ if (aWarning)
+ aResultCode &= ~0x80000000;
+
+ HRESULT hrc = S_OK;
+
+ if (aText == NULL || aText[0] == '\0')
+ {
+ /* Some default info */
+ switch (aResultCode)
+ {
+ case E_INVALIDARG: strText = tr("A parameter has an invalid value"); break;
+ case E_POINTER: strText = tr("A parameter is an invalid pointer"); break;
+ case E_UNEXPECTED: strText = tr("The result of the operation is unexpected"); break;
+ case E_ACCESSDENIED: strText = tr("The access to an object is not allowed"); break;
+ case E_OUTOFMEMORY: strText = tr("The allocation of new memory failed"); break;
+ case E_NOTIMPL: strText = tr("The requested operation is not implemented"); break;
+ case E_NOINTERFACE: strText = tr("The requested interface is not implemented"); break;
+ case E_FAIL: strText = tr("A general error occurred"); break;
+ case E_ABORT: strText = tr("The operation was canceled"); break;
+ case VBOX_E_OBJECT_NOT_FOUND: strText = tr("Object corresponding to the supplied arguments does not exist"); break;
+ case VBOX_E_INVALID_VM_STATE: strText = tr("Current virtual machine state prevents the operation"); break;
+ case VBOX_E_VM_ERROR: strText = tr("Virtual machine error occurred attempting the operation"); break;
+ case VBOX_E_FILE_ERROR: strText = tr("File not accessible or erroneous file contents"); break;
+ case VBOX_E_IPRT_ERROR: strText = tr("Runtime subsystem error"); break;
+ case VBOX_E_PDM_ERROR: strText = tr("Pluggable Device Manager error"); break;
+ case VBOX_E_INVALID_OBJECT_STATE: strText = tr("Current object state prohibits operation"); break;
+ case VBOX_E_HOST_ERROR: strText = tr("Host operating system related error"); break;
+ case VBOX_E_NOT_SUPPORTED: strText = tr("Requested operation is not supported"); break;
+ case VBOX_E_XML_ERROR: strText = tr("Invalid XML found"); break;
+ case VBOX_E_INVALID_SESSION_STATE: strText = tr("Current session state prohibits operation"); break;
+ case VBOX_E_OBJECT_IN_USE: strText = tr("Object being in use prohibits operation"); break;
+ case VBOX_E_PASSWORD_INCORRECT: strText = tr("Incorrect password provided"); break;
+ default: strText = tr("Unknown error"); break;
+ }
+ }
+ else
+ {
+ va_list va2;
+ va_copy(va2, aArgs);
+ strText = com::Utf8StrFmt("%N", aText, &va2);
+ va_end(va2);
+ }
+
+ do
+ {
+ ComObjPtr<VirtualBoxErrorInfo> info;
+ hrc = info.createObject();
+ if (FAILED(hrc)) break;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = err.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * IErrorInfo object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(err);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->initEx(aResultCode, aResultDetail, aIID, aComponent, strText, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<IErrorInfo> err;
+ hrc = info.queryInterfaceTo(err.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = ::SetErrorInfo(0, err);
+
+#else // !defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (FAILED(hrc)) break;
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = ex.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * nsIException object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(ex);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->initEx(aResultCode, aResultDetail, aIID, aComponent, Bstr(strText), curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<nsIException> ex;
+ hrc = info.queryInterfaceTo(ex.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = em->SetCurrentException(ex);
+ }
+ else if (hrc == NS_ERROR_UNEXPECTED)
+ {
+ /*
+ * It is possible that setError() is being called by the object
+ * after the XPCOM shutdown sequence has been initiated
+ * (for example, when XPCOM releases all instances it internally
+ * references, which can cause object's FinalConstruct() and then
+ * uninit()). In this case, do_GetService() above will return
+ * NS_ERROR_UNEXPECTED and it doesn't actually make sense to
+ * set the exception (nobody will be able to read it).
+ */
+ Log1WarningFunc(("Will not set an exception because nsIExceptionService is not available (NS_ERROR_UNEXPECTED). XPCOM is being shutdown?\n"));
+ hrc = NS_OK;
+ }
+
+#endif // !defined(VBOX_WITH_XPCOM)
+ }
+ while (0);
+
+ AssertComRC(hrc);
+
+ return SUCCEEDED(hrc) ? aResultCode : hrc;
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param aResultCode
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(HRESULT aResultCode)
+{
+ return setErrorInternalF(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ 0 /* aResultDetail */,
+ NULL);
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Shortcut instance method to calling the static setErrorInternal with the
+ * class interface ID and component name inserted correctly. This uses the
+ * virtual getClassIID() and getComponentName() methods which are automatically
+ * defined by the VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT macro.
+ * @param ei
+ * @return
+ */
+HRESULT VirtualBoxBase::setError(const com::ErrorInfo &ei)
+{
+ /* whether multi-error mode is turned on */
+ bool preserve = MultiResult::isMultiEnabled();
+
+ HRESULT hrc = S_OK;
+
+ do
+ {
+ ComObjPtr<VirtualBoxErrorInfo> info;
+ hrc = info.createObject();
+ if (FAILED(hrc)) break;
+
+#if !defined(VBOX_WITH_XPCOM)
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<IErrorInfo> err;
+ hrc = ::GetErrorInfo(0, err.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = err.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * IErrorInfo object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(err);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->init(ei, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<IErrorInfo> err;
+ hrc = info.queryInterfaceTo(err.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = ::SetErrorInfo(0, err);
+
+#else // !defined(VBOX_WITH_XPCOM)
+
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (FAILED(hrc)) break;
+
+ ComPtr<IVirtualBoxErrorInfo> curInfo;
+ if (preserve)
+ {
+ /* get the current error info if any */
+ ComPtr<nsIException> ex;
+ hrc = em->GetCurrentException(ex.asOutParam());
+ if (FAILED(hrc)) break;
+ hrc = ex.queryInterfaceTo(curInfo.asOutParam());
+ if (FAILED(hrc))
+ {
+ /* create a IVirtualBoxErrorInfo wrapper for the native
+ * nsIException object */
+ ComObjPtr<VirtualBoxErrorInfo> wrapper;
+ hrc = wrapper.createObject();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = wrapper->init(ex);
+ if (SUCCEEDED(hrc))
+ curInfo = wrapper;
+ }
+ }
+ }
+ /* On failure, curInfo will stay null */
+ Assert(SUCCEEDED(hrc) || curInfo.isNull());
+
+ /* set the current error info and preserve the previous one if any */
+ hrc = info->init(ei, curInfo);
+ if (FAILED(hrc)) break;
+
+ ComPtr<nsIException> ex;
+ hrc = info.queryInterfaceTo(ex.asOutParam());
+ if (SUCCEEDED(hrc))
+ hrc = em->SetCurrentException(ex);
+ }
+ else if (hrc == NS_ERROR_UNEXPECTED)
+ {
+ /*
+ * It is possible that setError() is being called by the object
+ * after the XPCOM shutdown sequence has been initiated
+ * (for example, when XPCOM releases all instances it internally
+ * references, which can cause object's FinalConstruct() and then
+ * uninit()). In this case, do_GetService() above will return
+ * NS_ERROR_UNEXPECTED and it doesn't actually make sense to
+ * set the exception (nobody will be able to read it).
+ */
+ Log1WarningFunc(("Will not set an exception because nsIExceptionService is not available (NS_ERROR_UNEXPECTED). XPCOM is being shutdown?\n"));
+ hrc = NS_OK;
+ }
+
+#endif // !defined(VBOX_WITH_XPCOM)
+ }
+ while (0);
+
+ AssertComRC(hrc);
+
+ return SUCCEEDED(hrc) ? ei.getResultCode() : hrc;
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param vrc The VBox status code.
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT)
+ */
+HRESULT VirtualBoxBase::setErrorVrc(int vrc)
+{
+ return setErrorInternalF(Global::vboxStatusCodeToCOM(vrc),
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */,
+ Utf8StrFmt("%Rrc", vrc).c_str());
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param va_args Error message format string.
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorVrcV(int vrc, const char *pcszMsgFmt, va_list va_args)
+{
+ return setErrorInternalV(Global::vboxStatusCodeToCOM(vrc),
+ this->getClassIID(),
+ this->getComponentName(),
+ pcszMsgFmt, va_args,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */);
+}
+
+/**
+ * Converts the VBox status code a COM one and sets the error info.
+ *
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param ... Argument specified in the @a pcszMsgFmt
+ * @return COM status code appropriate for @a vrc.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorVrc(int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ HRESULT hrc = setErrorVrcV(vrc, pcszMsgFmt, va);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Sets error info with both a COM status and an VBox status code.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param hrc The COM status code to return.
+ * @param vrc The VBox status code.
+ * @return Most likely @a hrc, see setErrorInternal.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT)
+ */
+HRESULT VirtualBoxBase::setErrorBoth(HRESULT hrc, int vrc)
+{
+ return setErrorInternalF(hrc,
+ this->getClassIID(),
+ this->getComponentName(),
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */,
+ Utf8StrFmt("%Rrc", vrc).c_str());
+}
+
+/**
+ * Sets error info with a message and both a COM status and an VBox status code.
+ *
+ * The VBox status code is made available to the API user via
+ * IVirtualBoxErrorInfo::resultDetail attribute.
+ *
+ * @param hrc The COM status code to return.
+ * @param vrc The VBox status code.
+ * @param pcszMsgFmt Error message format string.
+ * @param ... Argument specified in the @a pcszMsgFmt
+ * @return Most likely @a hrc, see setErrorInternal.
+ *
+ * @sa VirtualBoxBase::setError(HRESULT, const char *, ...)
+ */
+HRESULT VirtualBoxBase::setErrorBoth(HRESULT hrc, int vrc, const char *pcszMsgFmt, ...)
+{
+ va_list va;
+ va_start(va, pcszMsgFmt);
+ hrc = setErrorInternalV(hrc,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcszMsgFmt, va,
+ false /* aWarning */,
+ true /* aLogIt */,
+ vrc /* aResultDetail */);
+ va_end(va);
+ return hrc;
+}
+
+/**
+ * Like setError(), but sets the "warning" bit in the call to setErrorInternal().
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setWarning(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ true /* aWarning */,
+ true /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Like setError(), but disables the "log" flag in the call to setErrorInternal().
+ * @param aResultCode
+ * @param pcsz
+ * @return
+ */
+HRESULT VirtualBoxBase::setErrorNoLog(HRESULT aResultCode, const char *pcsz, ...)
+{
+ va_list args;
+ va_start(args, pcsz);
+ HRESULT hrc = setErrorInternalV(aResultCode,
+ this->getClassIID(),
+ this->getComponentName(),
+ pcsz, args,
+ false /* aWarning */,
+ false /* aLogIt */);
+ va_end(args);
+ return hrc;
+}
+
+/**
+ * Clear the current error information.
+ */
+/*static*/
+void VirtualBoxBase::clearError(void)
+{
+#if !defined(VBOX_WITH_XPCOM)
+ ::SetErrorInfo(0, NULL);
+#else
+ HRESULT hrc = S_OK;
+ nsCOMPtr <nsIExceptionService> es;
+ es = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ nsCOMPtr <nsIExceptionManager> em;
+ hrc = es->GetCurrentExceptionManager(getter_AddRefs(em));
+ if (SUCCEEDED(hrc))
+ em->SetCurrentException(NULL);
+ }
+#endif
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MultiResult methods
+//
+////////////////////////////////////////////////////////////////////////////////
+
+RTTLS MultiResult::sCounter = NIL_RTTLS;
+
+/*static*/
+void MultiResult::incCounter()
+{
+ if (sCounter == NIL_RTTLS)
+ {
+ sCounter = RTTlsAlloc();
+ AssertReturnVoid(sCounter != NIL_RTTLS);
+ }
+
+ uintptr_t counter = (uintptr_t)RTTlsGet(sCounter);
+ ++counter;
+ RTTlsSet(sCounter, (void*)counter);
+}
+
+/*static*/
+void MultiResult::decCounter()
+{
+ uintptr_t counter = (uintptr_t)RTTlsGet(sCounter);
+ AssertReturnVoid(counter != 0);
+ --counter;
+ RTTlsSet(sCounter, (void*)counter);
+}
+
+/*static*/
+bool MultiResult::isMultiEnabled()
+{
+ if (sCounter == NIL_RTTLS)
+ return false;
+
+ return ((uintptr_t)RTTlsGet(MultiResult::sCounter)) > 0;
+}
+
diff --git a/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp b/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp
new file mode 100644
index 00000000..516ee7b7
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxErrorInfoImpl.cpp
@@ -0,0 +1,317 @@
+/* $Id: VirtualBoxErrorInfoImpl.cpp $ */
+/** @file
+ * VirtualBoxErrorInfo 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
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include <VBox/com/ErrorInfo.h>
+
+// public initializer/uninitializer for internal purposes only
+////////////////////////////////////////////////////////////////////////////////
+
+HRESULT VirtualBoxErrorInfo::init(HRESULT aResultCode,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext)
+{
+ m_resultCode = aResultCode;
+ m_resultDetail = 0; /* Not being used. */
+ m_IID = aIID;
+ m_strComponent = pcszComponent;
+ m_strText = strText;
+ mNext = aNext;
+
+ return S_OK;
+}
+
+HRESULT VirtualBoxErrorInfo::initEx(HRESULT aResultCode,
+ LONG aResultDetail,
+ const GUID &aIID,
+ const char *pcszComponent,
+ const Utf8Str &strText,
+ IVirtualBoxErrorInfo *aNext)
+{
+ HRESULT hrc = init(aResultCode, aIID, pcszComponent, strText, aNext);
+ m_resultDetail = aResultDetail;
+
+ return hrc;
+}
+
+HRESULT VirtualBoxErrorInfo::init(const com::ErrorInfo &info,
+ IVirtualBoxErrorInfo *aNext)
+{
+ m_resultCode = info.getResultCode();
+ m_resultDetail = info.getResultDetail();
+ m_IID = info.getInterfaceID();
+ m_strComponent = info.getComponent();
+ m_strText = info.getText();
+
+ /* Recursively create VirtualBoxErrorInfo instances for the next objects. */
+ const com::ErrorInfo *pInfo = info.getNext();
+ if (pInfo)
+ {
+ ComObjPtr<VirtualBoxErrorInfo> nextEI;
+ HRESULT hrc = nextEI.createObject();
+ if (FAILED(hrc)) return hrc;
+ hrc = nextEI->init(*pInfo, aNext);
+ if (FAILED(hrc)) return hrc;
+ mNext = nextEI;
+ }
+ else
+ mNext = aNext;
+
+ return S_OK;
+}
+
+// IVirtualBoxErrorInfo properties
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(ResultCode)(LONG *aResultCode)
+{
+ CheckComArgOutPointerValid(aResultCode);
+
+ *aResultCode = (LONG)m_resultCode;
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(ResultDetail)(LONG *aResultDetail)
+{
+ CheckComArgOutPointerValid(aResultDetail);
+
+ *aResultDetail = m_resultDetail;
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(InterfaceID)(BSTR *aIID)
+{
+ CheckComArgOutPointerValid(aIID);
+
+ m_IID.toUtf16().cloneTo(aIID);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Component)(BSTR *aComponent)
+{
+ CheckComArgOutPointerValid(aComponent);
+
+ m_strComponent.cloneTo(aComponent);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Text)(BSTR *aText)
+{
+ CheckComArgOutPointerValid(aText);
+
+ m_strText.cloneTo(aText);
+ return S_OK;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::COMGETTER(Next)(IVirtualBoxErrorInfo **aNext)
+{
+ CheckComArgOutPointerValid(aNext);
+
+ /* this will set aNext to NULL if mNext is null */
+ return mNext.queryInterfaceTo(aNext);
+}
+
+#if !defined(VBOX_WITH_XPCOM)
+
+/**
+ * Initializes itself by fetching error information from the given error info
+ * object.
+ */
+HRESULT VirtualBoxErrorInfo::init(IErrorInfo *aInfo)
+{
+ AssertReturn(aInfo, E_FAIL);
+
+ /* We don't return a failure if talking to IErrorInfo fails below to
+ * protect ourselves from bad IErrorInfo implementations (the
+ * corresponding fields will simply remain null in this case). */
+
+ m_resultCode = S_OK;
+ m_resultDetail = 0;
+ HRESULT hrc = aInfo->GetGUID(m_IID.asOutParam());
+ AssertComRC(hrc);
+ Bstr bstrComponent;
+ hrc = aInfo->GetSource(bstrComponent.asOutParam());
+ AssertComRC(hrc);
+ m_strComponent = bstrComponent;
+ Bstr bstrText;
+ hrc = aInfo->GetDescription(bstrText.asOutParam());
+ AssertComRC(hrc);
+ m_strText = bstrText;
+
+ return S_OK;
+}
+
+// IErrorInfo methods
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP VirtualBoxErrorInfo::GetDescription(BSTR *description)
+{
+ return COMGETTER(Text)(description);
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetGUID(GUID *guid)
+{
+ Bstr iid;
+ HRESULT hrc = COMGETTER(InterfaceID)(iid.asOutParam());
+ if (SUCCEEDED(hrc))
+ *guid = Guid(iid).ref();
+ return hrc;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetHelpContext(DWORD *pdwHelpContext)
+{
+ RT_NOREF(pdwHelpContext);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetHelpFile(BSTR *pBstrHelpFile)
+{
+ RT_NOREF(pBstrHelpFile);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP VirtualBoxErrorInfo::GetSource(BSTR *pBstrSource)
+{
+ return COMGETTER(Component)(pBstrSource);
+}
+
+#else // defined(VBOX_WITH_XPCOM)
+
+/**
+ * Initializes itself by fetching error information from the given error info
+ * object.
+ */
+HRESULT VirtualBoxErrorInfo::init(nsIException *aInfo)
+{
+ AssertReturn(aInfo, E_FAIL);
+
+ /* We don't return a failure if talking to nsIException fails below to
+ * protect ourselves from bad nsIException implementations (the
+ * corresponding fields will simply remain null in this case). */
+
+ HRESULT hrc = aInfo->GetResult(&m_resultCode);
+ AssertComRC(hrc);
+ m_resultDetail = 0; /* Not being used. */
+
+ char *pszMsg; /* No Utf8Str.asOutParam, different allocator! */
+ hrc = aInfo->GetMessage(&pszMsg);
+ AssertComRC(hrc);
+ if (NS_SUCCEEDED(hrc))
+ {
+ m_strText = pszMsg;
+ nsMemory::Free(pszMsg);
+ }
+ else
+ m_strText.setNull();
+
+ return S_OK;
+}
+
+// nsIException methods
+////////////////////////////////////////////////////////////////////////////////
+
+/* readonly attribute string message; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetMessage(char **aMessage)
+{
+ CheckComArgOutPointerValid(aMessage);
+
+ m_strText.cloneTo(aMessage);
+ return S_OK;
+}
+
+/* readonly attribute nsresult result; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetResult(nsresult *aResult)
+{
+ AssertReturn(aResult, NS_ERROR_INVALID_POINTER);
+
+ PRInt32 lrc;
+ nsresult hrc = COMGETTER(ResultCode)(&lrc);
+ if (SUCCEEDED(hrc))
+ *aResult = (nsresult)lrc;
+ return hrc;
+}
+
+/* readonly attribute string name; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetName(char ** /* aName */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute string filename; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetFilename(char ** /* aFilename */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute PRUint32 lineNumber; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetLineNumber(PRUint32 * /* aLineNumber */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute PRUint32 columnNumber; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetColumnNumber(PRUint32 * /*aColumnNumber */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIStackFrame location; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetLocation(nsIStackFrame ** /* aLocation */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* readonly attribute nsIException inner; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetInner(nsIException **aInner)
+{
+ ComPtr<IVirtualBoxErrorInfo> info;
+ nsresult rv = COMGETTER(Next)(info.asOutParam());
+ if (FAILED(rv)) return rv;
+ return info.queryInterfaceTo(aInner);
+}
+
+/* readonly attribute nsISupports data; */
+NS_IMETHODIMP VirtualBoxErrorInfo::GetData(nsISupports ** /* aData */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* string toString(); */
+NS_IMETHODIMP VirtualBoxErrorInfo::ToString(char ** /* retval */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(VirtualBoxErrorInfo,
+ nsIException, IVirtualBoxErrorInfo)
+
+#endif // defined(VBOX_WITH_XPCOM)
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/VirtualBoxTranslator.cpp b/src/VBox/Main/src-all/VirtualBoxTranslator.cpp
new file mode 100644
index 00000000..acd4a6dc
--- /dev/null
+++ b/src/VBox/Main/src-all/VirtualBoxTranslator.cpp
@@ -0,0 +1,593 @@
+/* $Id: VirtualBoxTranslator.cpp $ */
+/** @file
+ * VirtualBox Translator 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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXCLIENT /** @todo add separate logging group! */
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/locale.h>
+#include <iprt/once.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/strcache.h>
+
+#ifdef RT_OS_DARWIN
+#include <CoreFoundation/CFLocale.h>
+#include <CoreFoundation/CFString.h>
+#endif
+
+#include "Global.h"
+#include "VirtualBoxBase.h"
+#include "QMTranslator.h"
+#include "VirtualBoxTranslator.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define TRANSLATOR_CACHE_SIZE 32
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Init once for the critical section. */
+static RTONCE g_Once = RTONCE_INITIALIZER;
+RTCRITSECTRW VirtualBoxTranslator::s_instanceRwLock;
+VirtualBoxTranslator *VirtualBoxTranslator::s_pInstance = NULL;
+/** TLS index that points to the translated text. */
+static RTTLS g_idxTlsTr = NIL_RTTLS;
+/** TLS index that points to the original text. */
+static RTTLS g_idxTlsSrc = NIL_RTTLS;
+
+
+/**
+ * @callback_method_impl{FNRTONCE}
+ */
+static DECLCALLBACK(int32_t) initLock(void *pvUser)
+{
+ RT_NOREF(pvUser);
+ return VirtualBoxTranslator::initCritSect();
+}
+
+
+/**
+ * Obtains the user language code in ll_CC form depending on platform
+ *
+ * @returns VBox status code
+ * @param pszName The buffer for storing user language code
+ * @param cbName Size of the pszName buffer
+ */
+static int vboxGetDefaultUserLanguage(char *pszName, size_t cbName)
+{
+ AssertReturn(pszName, VERR_INVALID_PARAMETER);
+ AssertReturn(cbName >= 6, VERR_INVALID_PARAMETER); /* 5 chars for language + null termination */
+
+#ifdef RT_OS_WINDOWS
+ if ( GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO639LANGNAME, pszName, (int)cbName) == 3
+ && GetLocaleInfoA(GetUserDefaultLCID(), LOCALE_SISO3166CTRYNAME, &pszName[3], (int)cbName - 4) == 3)
+ {
+ pszName[2] = '_';
+ Assert(RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszName));
+ return VINF_SUCCESS;
+ }
+#elif RT_OS_DARWIN
+ CFLocaleRef locale = CFLocaleCopyCurrent();
+ CFTypeRef localeId = CFLocaleGetValue (locale, kCFLocaleIdentifier);
+ char szLocale[256] = { 0 };
+ if (CFGetTypeID(localeId) == CFStringGetTypeID())
+ CFStringGetCString((CFStringRef)localeId, szLocale, sizeof(szLocale), kCFStringEncodingUTF8);
+ /* Some cleanup */
+ CFRelease(locale);
+ if (szLocale[0] == '\0')
+ {
+ pszName[0] = 'C';
+ pszName[1] = 0;
+ return VINF_SUCCESS;
+ }
+ else
+ return RTStrCopy(pszName, cbName, szLocale);
+
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS)
+ const char *pszValue = RTEnvGet("LC_ALL");
+ if (pszValue == 0)
+ pszValue = RTEnvGet("LC_MESSAGES");
+ if (pszValue == 0)
+ pszValue = RTEnvGet("LANG");
+ if (pszValue != 0)
+ {
+ /* ignore codepage part, i.e. ignore ".UTF-8" in "ru_RU.UTF-8" */
+ const char *pszDot = strchr(pszValue, '.');
+ size_t cbValue = strlen(pszValue);
+ if (pszDot != NULL)
+ cbValue = RT_MIN(cbValue, (size_t)(pszDot - pszValue));
+
+ if ( ( cbValue == 2
+ && RT_C_IS_LOWER(pszValue[0])
+ && RT_C_IS_LOWER(pszValue[1]))
+ || ( cbValue == 5
+ && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszValue)))
+ return RTStrCopyEx(pszName, cbName, pszValue, cbValue);
+ }
+#endif
+ return RTLocaleQueryNormalizedBaseLocaleName(pszName, cbName);
+}
+
+VirtualBoxTranslator::VirtualBoxTranslator()
+ : util::RWLockHandle(util::LOCKCLASS_TRANSLATOR)
+ , m_cInstanceRefs(0)
+ , m_pDefaultComponent(NULL)
+ , m_strLanguage("C")
+ , m_hStrCache(NIL_RTSTRCACHE)
+{
+ g_idxTlsTr = RTTlsAlloc();
+ g_idxTlsSrc = RTTlsAlloc();
+ int vrc = RTStrCacheCreate(&m_hStrCache, "API Translation");
+ m_rcCache = vrc;
+ if (RT_FAILURE(vrc))
+ m_hStrCache = NIL_RTSTRCACHE; /* (loadLanguage will fail) */
+ LogFlowFunc(("m_rcCache=%Rrc g_idxTlsTr=%#x g_idxTlsSrc=%#x\n", m_rcCache, g_idxTlsTr, g_idxTlsSrc));
+}
+
+
+VirtualBoxTranslator::~VirtualBoxTranslator()
+{
+ LogFlowFunc(("enter\n"));
+
+ /* Write-lock the object as we could be racing language change
+ notifications processing during XPCOM shutdown. (risky?) */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ RTTlsFree(g_idxTlsTr);
+ g_idxTlsTr = NIL_RTTLS;
+ RTTlsFree(g_idxTlsSrc);
+ g_idxTlsSrc = NIL_RTTLS;
+
+ m_pDefaultComponent = NULL;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (it->pTranslator != NULL)
+ delete it->pTranslator;
+ it->pTranslator = NULL;
+ }
+ if (m_hStrCache != NIL_RTSTRCACHE)
+ {
+ RTStrCacheDestroy(m_hStrCache);
+ m_hStrCache = NIL_RTSTRCACHE;
+ m_rcCache = VERR_WRONG_ORDER;
+ }
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Get or create a translator instance (singelton), referenced.
+ *
+ * The main reference is held by the main VBox singelton objects (VirtualBox,
+ * VirtualBoxClient) tying it's lifetime to theirs.
+ */
+/* static */
+VirtualBoxTranslator *VirtualBoxTranslator::instance()
+{
+ int vrc = RTOnce(&g_Once, initLock, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ VirtualBoxTranslator *pInstance = s_pInstance;
+ if (RT_LIKELY(pInstance != NULL))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ Assert(cRefs > 1); Assert(cRefs < _8K); RT_NOREF(cRefs);
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ return pInstance;
+ }
+
+ /* Maybe create the instance: */
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ RTCritSectRwEnterExcl(&s_instanceRwLock);
+ pInstance = s_pInstance;
+ if (pInstance == NULL)
+ s_pInstance = pInstance = new VirtualBoxTranslator();
+ ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ RTCritSectRwLeaveExcl(&s_instanceRwLock);
+ return pInstance;
+ }
+ return NULL;
+}
+
+
+/* static */
+VirtualBoxTranslator *VirtualBoxTranslator::tryInstance() RT_NOEXCEPT
+{
+ int vrc = RTOnce(&g_Once, initLock, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ VirtualBoxTranslator *pInstance = s_pInstance;
+ if (RT_LIKELY(pInstance != NULL))
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pInstance->m_cInstanceRefs);
+ Assert(cRefs > 1); Assert(cRefs < _8K); RT_NOREF(cRefs);
+ }
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ return pInstance;
+ }
+ return NULL;
+}
+
+
+/**
+ * Release translator reference previous obtained via instance() or
+ * tryinstance().
+ */
+void VirtualBoxTranslator::release()
+{
+ RTCritSectRwEnterShared(&s_instanceRwLock);
+ uint32_t cRefs = ASMAtomicDecU32(&m_cInstanceRefs);
+ Assert(cRefs < _8K);
+ if (RT_LIKELY(cRefs > 0))
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ else
+ {
+ /* Looks like we've got the last reference. Must switch to exclusive
+ mode for safe cleanup. */
+ ASMAtomicIncU32(&m_cInstanceRefs);
+ RTCritSectRwLeaveShared(&s_instanceRwLock);
+ RTCritSectRwEnterExcl(&s_instanceRwLock);
+ cRefs = ASMAtomicDecU32(&m_cInstanceRefs);
+ Assert(cRefs < _8K);
+ if (cRefs == 0)
+ {
+ s_pInstance = NULL;
+ delete this;
+ }
+ RTCritSectRwLeaveExcl(&s_instanceRwLock);
+ }
+}
+
+
+HRESULT VirtualBoxTranslator::loadLanguage(ComPtr<IVirtualBox> aVirtualBox)
+{
+ AssertReturn(aVirtualBox, E_INVALIDARG);
+
+ ComPtr<ISystemProperties> pSystemProperties;
+ HRESULT hrc = aVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ com::Bstr bstrLocale;
+ hrc = pSystemProperties->COMGETTER(LanguageId)(bstrLocale.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ int vrc = i_loadLanguage(com::Utf8Str(bstrLocale).c_str());
+ if (RT_FAILURE(vrc))
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ }
+ return hrc;
+}
+
+
+com::Utf8Str VirtualBoxTranslator::language()
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return m_strLanguage;
+}
+
+
+int VirtualBoxTranslator::i_loadLanguage(const char *pszLang)
+{
+ LogFlowFunc(("pszLang=%s\n", pszLang));
+ int vrc = VINF_SUCCESS;
+ char szLocale[256];
+ if (pszLang == NULL || *pszLang == '\0')
+ {
+ vrc = vboxGetDefaultUserLanguage(szLocale, sizeof(szLocale));
+ if (RT_SUCCESS(vrc))
+ pszLang = szLocale;
+ }
+ else
+ {
+ /* check the pszLang looks like language code, i.e. {ll} or {ll}_{CC} */
+ size_t cbLang = strlen(pszLang);
+ if ( !(cbLang == 1 && pszLang[0] == 'C')
+ && !(cbLang == 2 && RT_C_IS_LOWER(pszLang[0]) && RT_C_IS_LOWER(pszLang[1]))
+ && !(cbLang == 5 && RTLOCALE_IS_LANGUAGE2_UNDERSCORE_COUNTRY2(pszLang)))
+ vrc = VERR_INVALID_PARAMETER;
+ }
+ if (RT_SUCCESS(vrc))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ m_strLanguage = pszLang;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ /* ignore errors from particular translator allowing the use of others */
+ i_loadLanguageForComponent(&(*it), pszLang);
+ }
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_loadLanguageForComponent(TranslatorComponent *aComponent, const char *aLang)
+{
+ AssertReturn(aComponent, VERR_INVALID_PARAMETER);
+ LogFlow(("aComponent=%s aLang=%s\n", aComponent->strPath.c_str(), aLang));
+
+ int vrc;
+ if (strcmp(aLang, "C") != 0)
+ {
+ /* Construct the base filename for the translations: */
+ char szNlsPath[RTPATH_MAX];
+ /* Try load language file on form 'VirtualBoxAPI_ll_CC.qm' if it exists
+ where 'll_CC' could for example be 'en_US' or 'de_CH': */
+ ssize_t cchOkay = RTStrPrintf2(szNlsPath, sizeof(szNlsPath), "%s_%s.qm",
+ aComponent->strPath.c_str(), aLang);
+ if (cchOkay > 0)
+ vrc = i_setLanguageFile(aComponent, szNlsPath);
+ else
+ vrc = VERR_FILENAME_TOO_LONG;
+ if (RT_FAILURE(vrc))
+ {
+ /* No luck, drop the country part, i.e. 'VirtualBoxAPI_de.qm' or 'VirtualBoxAPI_en.qm': */
+ const char *pszDash = strchr(aLang, '_');
+ if (pszDash && pszDash != aLang)
+ {
+ cchOkay = RTStrPrintf2(szNlsPath, sizeof(szNlsPath), "%s_%.*s.qm",
+ aComponent->strPath.c_str(), pszDash - aLang, aLang);
+ if (cchOkay > 0)
+ vrc = i_setLanguageFile(aComponent, szNlsPath);
+ }
+ }
+ }
+ else
+ {
+ /* No translator needed for 'C' */
+ delete aComponent->pTranslator;
+ aComponent->pTranslator = NULL;
+ vrc = VINF_SUCCESS;
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_setLanguageFile(TranslatorComponent *aComponent, const char *aFileName)
+{
+ AssertReturn(aComponent, VERR_INVALID_PARAMETER);
+
+ int vrc = m_rcCache;
+ if (m_hStrCache != NIL_RTSTRCACHE)
+ {
+ QMTranslator *pNewTranslator;
+ try { pNewTranslator = new QMTranslator(); }
+ catch (std::bad_alloc &) { pNewTranslator = NULL; }
+ if (pNewTranslator)
+ {
+ vrc = pNewTranslator->load(aFileName, m_hStrCache);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aComponent->pTranslator)
+ delete aComponent->pTranslator;
+ aComponent->pTranslator = pNewTranslator;
+ }
+ else
+ delete pNewTranslator;
+ }
+ else
+ vrc = VERR_NO_MEMORY;
+ }
+ else
+ Assert(RT_FAILURE_NP(vrc));
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent)
+{
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ int vrc = VERR_GENERAL_FAILURE;
+ if (pCurrInstance != NULL)
+ {
+ vrc = pCurrInstance->i_registerTranslation(aTranslationPath, aDefault, aComponent);
+ pCurrInstance->release();
+ }
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_registerTranslation(const char *aTranslationPath,
+ bool aDefault,
+ PTRCOMPONENT *aComponent)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ TranslatorComponent *pComponent;
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (it->strPath == aTranslationPath)
+ {
+ pComponent = &(*it);
+ if (aDefault)
+ m_pDefaultComponent = pComponent;
+ *aComponent = (PTRCOMPONENT)pComponent;
+ return VINF_SUCCESS;
+ }
+ }
+
+ try
+ {
+ m_lTranslators.push_back(TranslatorComponent());
+ pComponent = &m_lTranslators.back();
+ }
+ catch(std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ pComponent->strPath = aTranslationPath;
+ if (aDefault)
+ m_pDefaultComponent = pComponent;
+ *aComponent = (PTRCOMPONENT)pComponent;
+ /* ignore the error during loading because path
+ * could contain no translation for current language */
+ i_loadLanguageForComponent(pComponent, m_strLanguage.c_str());
+ return VINF_SUCCESS;
+}
+
+
+int VirtualBoxTranslator::unregisterTranslation(PTRCOMPONENT aComponent)
+{
+ int vrc;
+ if (aComponent != NULL)
+ {
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ if (pCurrInstance != NULL)
+ {
+ vrc = pCurrInstance->i_unregisterTranslation(aComponent);
+ pCurrInstance->release();
+ }
+ else
+ vrc = VERR_GENERAL_FAILURE;
+ }
+ else
+ vrc = VWRN_NOT_FOUND;
+ return vrc;
+}
+
+
+int VirtualBoxTranslator::i_unregisterTranslation(PTRCOMPONENT aComponent)
+{
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aComponent == m_pDefaultComponent)
+ m_pDefaultComponent = NULL;
+
+ for (TranslatorList::iterator it = m_lTranslators.begin();
+ it != m_lTranslators.end();
+ ++it)
+ {
+ if (&(*it) == aComponent)
+ {
+ delete aComponent->pTranslator;
+ m_lTranslators.erase(it);
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+const char *VirtualBoxTranslator::translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment,
+ const size_t aNum) RT_NOEXCEPT
+{
+ VirtualBoxTranslator *pCurrInstance = VirtualBoxTranslator::tryInstance();
+ const char *pszTranslation = aSourceText;
+ if (pCurrInstance != NULL)
+ {
+ pszTranslation = pCurrInstance->i_translate(aComponent, aContext, aSourceText, aComment, aNum);
+ pCurrInstance->release();
+ }
+ return pszTranslation;
+}
+
+
+const char *VirtualBoxTranslator::i_translate(PTRCOMPONENT aComponent,
+ const char *aContext,
+ const char *aSourceText,
+ const char *aComment,
+ const size_t aNum) RT_NOEXCEPT
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (aComponent == NULL)
+ aComponent = m_pDefaultComponent;
+
+ if ( aComponent == NULL
+ || aComponent->pTranslator == NULL)
+ return aSourceText;
+
+ const char *pszSafeSource = NULL;
+ const char *pszTranslation = aComponent->pTranslator->translate(aContext, aSourceText, &pszSafeSource, aComment, aNum);
+ if (pszSafeSource && g_idxTlsSrc != NIL_RTTLS && g_idxTlsTr != NIL_RTTLS)
+ {
+ RTTlsSet(g_idxTlsTr, (void *)pszTranslation);
+ RTTlsSet(g_idxTlsSrc, (void *)pszSafeSource);
+ }
+
+ return pszTranslation;
+}
+
+
+const char *VirtualBoxTranslator::trSource(const char *aTranslation) RT_NOEXCEPT
+{
+ const char *pszSource = aTranslation;
+ VirtualBoxTranslator *pCurInstance = VirtualBoxTranslator::tryInstance(); /* paranoia */
+ if (pCurInstance != NULL)
+ {
+ if (g_idxTlsSrc != NIL_RTTLS && g_idxTlsTr != NIL_RTTLS)
+ {
+ const char * const pszTranslationTls = (const char *)RTTlsGet(g_idxTlsTr);
+ const char * const pszSourceTls = (const char *)RTTlsGet(g_idxTlsSrc);
+ if ( pszSourceTls != NULL
+ && pszTranslationTls != NULL
+ && ( pszTranslationTls == aTranslation
+ || strcmp(pszTranslationTls, aTranslation) == 0))
+ pszSource = pszSourceTls;
+ }
+ pCurInstance->release();
+ }
+ return pszSource;
+}
+
+
+int32_t VirtualBoxTranslator::initCritSect()
+{
+ return RTCritSectRwInit(&s_instanceRwLock);
+}
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */
diff --git a/src/VBox/Main/src-all/win/Makefile.kup b/src/VBox/Main/src-all/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-all/win/Makefile.kup
diff --git a/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h b/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h
new file mode 100644
index 00000000..8b6ea4e4
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxAPIWrap-precomp_vcc.h
@@ -0,0 +1,50 @@
+/* $Id: VBoxAPIWrap-precomp_vcc.h $ */
+/** @file
+ * VirtualBox COM - Visual C++ precompiled header for the API wrappers.
+ */
+
+/*
+ * 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/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"
+
+#include "VirtualBoxBase.h"
+#include "Wrapper.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-all/win/VBoxProxyStub-x86.rc b/src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc
new file mode 100644
index 00000000..da6e2140
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub-x86.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStub-x86.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * 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 IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Typelib"
+#define IN_FILE_BASENAME "VBoxProxyStub-x86"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox-x86.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.c b/src/VBox/Main/src-all/win/VBoxProxyStub.c
new file mode 100644
index 00000000..87125091
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.c
@@ -0,0 +1,2598 @@
+/* $Id: VBoxProxyStub.c $ */
+/** @file
+ * VBoxProxyStub - Proxy Stub and Typelib, COM DLL exports and DLL init/term.
+ *
+ * @remarks This is a C file and not C++ because rpcproxy.h isn't C++ clean,
+ * at least not in SDK v7.1.
+ */
+
+/*
+ * 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
+#define PROXY_DELEGATION /* see generated dlldata.c */
+#include <iprt/nt/nt-and-windows.h>
+#include <rpcproxy.h>
+#include <iprt/win/shlwapi.h>
+#include <stdio.h>
+
+#include "VirtualBox.h"
+#include <VBox/cdefs.h> /* for VBOX_STRICT */
+#include <VBox/log.h>
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/utf16.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef DEBUG_bird
+# define VBSP_LOG_ENABLED
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_VALUE_CHANGE(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_VALUE_CHANGE(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_SET_VALUE(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_SET_VALUE(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_NEW_KEY(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_NEW_KEY(a) do { } while (0)
+#endif
+
+#ifdef VBSP_LOG_ENABLED
+# define VBSP_LOG_DEL_KEY(a) RTAssertMsg2 a
+#else
+# define VBSP_LOG_DEL_KEY(a) do { } while (0)
+#endif
+
+/**
+ * Selects the proxy stub DLL based on 32-on-64-bit and host OS version.
+ *
+ * The legacy DLL covers 64-bit pre Windows 7 versions of Windows. W2K3-amd64
+ * has trouble parsing the result when MIDL /target NT51 or higher. Vista and
+ * windows server 2008 seems to have trouble with newer IDL compilers.
+ */
+#if ARCH_BITS == 64 || defined(VBOX_IN_32_ON_64_MAIN_API)
+# define VBPS_PROXY_STUB_FILE(a_fIs32On64) ( (a_fIs32On64) ? "x86\\VBoxProxyStub-x86.dll" : VBPS_PROXY_STUB_FILE_SUB() )
+#else
+# define VBPS_PROXY_STUB_FILE(a_fIs32On64) VBPS_PROXY_STUB_FILE_SUB()
+#endif
+#define VBPS_PROXY_STUB_FILE_SUB() \
+ ( RT_MAKE_U64(((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMinorVersion, \
+ ((PKUSER_SHARED_DATA)MM_SHARED_USER_DATA_VA)->NtMajorVersion) >= RT_MAKE_U64(1/*Lo*/,6/*Hi*/) \
+ ? "VBoxProxyStub.dll" : "VBoxProxyStubLegacy.dll" )
+
+/** For use with AssertLogRel except a_Expr1 from assertions but not LogRel. */
+#ifdef RT_STRICT
+# define VBPS_LOGREL_NO_ASSERT(a_Expr) (a_Expr)
+#else
+# define VBPS_LOGREL_NO_ASSERT(a_Expr) false
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For NdrXxx. */
+CStdPSFactoryBuffer g_ProxyStubFactory = /* see generated dlldata.c */
+{
+ NULL,
+ 0,
+ NULL,
+ 0
+};
+/** Reference to VirtualBox_p.c structure. */
+EXTERN_PROXY_FILE(VirtualBox) /* see generated dlldata.c */
+/** For NdrXxx and for returning. */
+static const ProxyFileInfo *g_apProxyFiles[] =
+{
+ REFERENCE_PROXY_FILE(VirtualBox),
+ NULL /* terminator */
+};
+/** The class ID for this proxy stub factory (see Makefile). */
+static const CLSID g_ProxyClsId = PROXY_CLSID_IS;
+/** The instance handle of this DLL. For use in registration routines. */
+static HINSTANCE g_hDllSelf;
+
+
+/** Type library GUIDs to clean up manually.
+ * Must be upper case! */
+static PCRTUTF16 const g_apwszTypeLibIds[] =
+{
+ L"{46137EEC-703B-4FE5-AFD4-7C9BBBBA0259}",
+ L"{D7569351-1750-46F0-936E-BD127D5BC264}",
+};
+
+/** Type library version to clean up manually. */
+static PCRTUTF16 const g_apwszTypelibVersions[] =
+{
+ L"1.0",
+ L"1.3",
+};
+
+/** Proxy stub class IDs we wish to clean up manually.
+ * Must be upper case! */
+static PCRTUTF16 const g_apwszProxyStubClsIds[] =
+{
+ L"{0BB3B78C-1807-4249-5BA5-EA42D66AF0BF}",
+ L"{327E3C00-EE61-462F-AED3-0DFF6CBF9904}",
+};
+
+
+/**
+ * DLL main function.
+ *
+ * @returns TRUE (/ FALSE).
+ * @param hInstance The DLL handle.
+ * @param dwReason The rason for the call (DLL_XXX).
+ * @param lpReserved Reserved.
+ */
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ /* Save the DLL handle so we can get the path to this DLL during
+ registration and updating. */
+ g_hDllSelf = hInstance;
+
+ /* We don't need callbacks for thread creation and destruction. */
+ DisableThreadLibraryCalls(hInstance);
+
+ /* Init IPRT. */
+ RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+ Log12(("VBoxProxyStub[%u]/DllMain: DLL_PROCESS_ATTACH\n", GetCurrentProcessId()));
+
+#ifdef VBOX_STRICT
+ {
+ /*
+ * Check that no interface has more than 256 methods in the stub vtable.
+ */
+ const ProxyFileInfo **ppProxyFile = &g_apProxyFiles[0];
+ const ProxyFileInfo *pProxyFile;
+ while ((pProxyFile = *ppProxyFile++) != NULL)
+ {
+ const PCInterfaceStubVtblList * const papStubVtbls = pProxyFile->pStubVtblList;
+ const char * const *papszNames = pProxyFile->pNamesArray;
+ unsigned iIf = pProxyFile->TableSize;
+ AssertStmt(iIf < 1024, iIf = 0);
+ Assert(pProxyFile->TableVersion == 2);
+
+ while (iIf-- > 0)
+ AssertMsg(papStubVtbls[iIf]->header.DispatchTableCount <= 256,
+ ("%s: DispatchTableCount=%d\n", papszNames[iIf], papStubVtbls[iIf]->header.DispatchTableCount));
+ }
+ }
+#endif
+ break;
+
+ case DLL_PROCESS_DETACH:
+ Log12(("VBoxProxyStub[%u]/DllMain: DLL_PROCESS_DETACH\n", GetCurrentProcessId()));
+ break;
+ }
+
+ NOREF(lpReserved);
+ return TRUE;
+}
+
+
+/**
+ * RPC entry point returning info about the proxy.
+ */
+void RPC_ENTRY GetProxyDllInfo(const ProxyFileInfo ***ppapInfo, const CLSID **ppClsid)
+{
+ *ppapInfo = &g_apProxyFiles[0];
+ *ppClsid = &g_ProxyClsId;
+ Log12(("VBoxProxyStub[%u]/GetProxyDllInfo:\n", GetCurrentProcessId()));
+}
+
+
+/**
+ * Instantiate the proxy stub class object.
+ *
+ * @returns COM status code
+ * @param rclsid Reference to the ID of the call to instantiate (our
+ * g_ProxyClsId).
+ * @param riid The interface ID to return (IID_IPSFactoryBuffer).
+ * @param ppv Where to return the interface pointer on success.
+ */
+HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
+{
+ HRESULT hrc;
+ Assert(memcmp(rclsid, &g_ProxyClsId, sizeof(g_ProxyClsId)) == 0);
+
+ hrc = NdrDllGetClassObject(rclsid, riid, ppv, /* see DLLGETCLASSOBJECTROUTINE in RpcProxy.h */
+ g_apProxyFiles, &g_ProxyClsId, &g_ProxyStubFactory);
+
+ /*
+ * This may fail if the IDL compiler generates code that is incompatible
+ * with older windows releases. Like for instance 64-bit W2K8 SP1 not
+ * liking the output of MIDL 7.00.0555 (from the v7.1 SDK), despite
+ * /target being set to NT51.
+ */
+ AssertLogRelMsg(hrc == S_OK, ("%Rhrc\n", hrc));
+ Log12(("VBoxProxyStub[%u]/DllGetClassObject(%RTuuid, %RTuuid, %p): %#x + *ppv=%p\n",
+ GetCurrentProcessId(), rclsid, riid, ppv, hrc, ppv ? *ppv : NULL));
+ return hrc;
+}
+
+
+/**
+ * Checks whether the DLL can be unloaded or not.
+ *
+ * @returns S_OK if it can be unloaded, S_FALSE if not.
+ */
+HRESULT STDAPICALLTYPE DllCanUnloadNow(void)
+{
+ HRESULT hrc = NdrDllCanUnloadNow(&g_ProxyStubFactory); /* see DLLCANUNLOADNOW in RpcProxy.h */
+ Log12(("VBoxProxyStub[%u]/DllCanUnloadNow: %Rhrc\n", GetCurrentProcessId(), hrc));
+ return hrc;
+}
+
+
+
+/**
+ * Release call that could be referenced by VirtualBox_p.c via
+ * CStdStubBuffer_METHODS.
+ *
+ * @returns New reference count.
+ * @param pThis Buffer to release.
+ */
+ULONG STDMETHODCALLTYPE CStdStubBuffer_Release(IRpcStubBuffer *pThis) /* see CSTDSTUBBUFFERRELEASE in RpcProxy.h */
+{
+ ULONG cRefs = NdrCStdStubBuffer_Release(pThis, (IPSFactoryBuffer *)&g_ProxyStubFactory);
+ Log12(("VBoxProxyStub[%u]/CStdStubBuffer_Release: %p -> %#x\n", GetCurrentProcessId(), pThis, cRefs));
+ return cRefs;
+}
+
+
+/**
+ * Release call referenced by VirtualBox_p.c via
+ * CStdStubBuffer_DELEGATING_METHODS.
+ *
+ * @returns New reference count.
+ * @param pThis Buffer to release.
+ */
+ULONG WINAPI CStdStubBuffer2_Release(IRpcStubBuffer *pThis) /* see CSTDSTUBBUFFER2RELEASE in RpcProxy.h */
+{
+ ULONG cRefs = NdrCStdStubBuffer2_Release(pThis, (IPSFactoryBuffer *)&g_ProxyStubFactory);
+ Log12(("VBoxProxyStub[%u]/CStdStubBuffer2_Release: %p -> %#x\n", GetCurrentProcessId(), pThis, cRefs));
+ return cRefs;
+}
+
+
+/**
+ * Pure virtual method implementation referenced by VirtualBox_p.c
+ */
+void __cdecl _purecall(void) /* see DLLDUMMYPURECALL in RpcProxy.h */
+{
+ AssertFailed();
+}
+
+
+#ifdef VBSP_LOG_ENABLED
+# include <iprt/asm.h>
+
+/** For logging full key names. */
+static PCRTUTF16 vbpsDebugKeyToWSZ(HKEY hKey)
+{
+ static union
+ {
+ KEY_NAME_INFORMATION NameInfo;
+ WCHAR awchPadding[260];
+ } s_aBufs[4];
+ static uint32_t volatile iNext = 0;
+ uint32_t i = ASMAtomicIncU32(&iNext) % RT_ELEMENTS(s_aBufs);
+ ULONG cbRet = 0;
+ NTSTATUS rcNt;
+
+ memset(&s_aBufs[i], 0, sizeof(s_aBufs[i]));
+ rcNt = NtQueryKey(hKey, KeyNameInformation, &s_aBufs[i], sizeof(s_aBufs[i]) - sizeof(WCHAR), &cbRet);
+ if (!NT_SUCCESS(rcNt))
+ s_aBufs[i].NameInfo.NameLength = 0;
+ s_aBufs[i].NameInfo.Name[s_aBufs[i].NameInfo.NameLength] = '\0';
+ return s_aBufs[i].NameInfo.Name;
+}
+#endif
+
+/**
+ * Registry modifier state.
+ */
+typedef struct VBPSREGSTATE
+{
+ /** Where the classes and stuff are to be registered. */
+ HKEY hkeyClassesRootDst;
+ /** The handle to the CLSID key under hkeyClassesRootDst. */
+ HKEY hkeyClsidRootDst;
+ /** The handle to the Interface key under hkeyClassesRootDst. */
+ HKEY hkeyInterfaceRootDst;
+
+ /** Alternative locations where data needs to be deleted, but never updated. */
+ struct
+ {
+ /** The classes root key handle. */
+ HKEY hkeyClasses;
+ /** The classes/CLSID key handle. */
+ HKEY hkeyClsid;
+ /** The classes/Interface key handle. */
+ HKEY hkeyInterface;
+ } aAltDeletes[3];
+ /** Alternative delete locations. */
+ uint32_t cAltDeletes;
+
+ /** The current total result. */
+ LSTATUS lrc;
+
+ /** KEY_WOW64_32KEY, KEY_WOW64_64KEY or 0 (for default). Allows doing all
+ * almost the work from one process (at least W7+ due to aliases). */
+ DWORD fSamWow;
+ /** Desired key access when only deleting. */
+ DWORD fSamDelete;
+ /** Desired key access when only doing updates. */
+ DWORD fSamUpdate;
+ /** Desired key access when both deleting and updating. */
+ DWORD fSamBoth;
+ /** Whether to delete registrations first. */
+ bool fDelete;
+ /** Whether to update registry value and keys. */
+ bool fUpdate;
+
+} VBPSREGSTATE;
+
+
+/**
+ * Initializes a registry modification job state.
+ *
+ * Always call vbpsRegTerm!
+ *
+ * @returns Windows error code (ERROR_SUCCESS on success).
+ * @param pState The state to init.
+ * @param hkeyRoot The registry root tree constant.
+ * @param pszSubRoot The path to the where the classes are registered,
+ * NULL if @a hkeyRoot.
+ * @param fDelete Whether to delete registrations first.
+ * @param fUpdate Whether to update registrations.
+ * @param fSamWow KEY_WOW64_32KEY or 0.
+ */
+static LSTATUS vbpsRegInit(VBPSREGSTATE *pState, HKEY hkeyRoot, const char *pszSubRoot, bool fDelete, bool fUpdate, DWORD fSamWow)
+{
+ LSTATUS lrc;
+ unsigned i = 0;
+
+ /*
+ * Initialize the whole structure first so we can safely call vbpsRegTerm on failure.
+ */
+ pState->hkeyClassesRootDst = NULL;
+ pState->hkeyClsidRootDst = NULL;
+ pState->hkeyInterfaceRootDst = NULL;
+ for (i = 0; i < RT_ELEMENTS(pState->aAltDeletes); i++)
+ {
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ pState->cAltDeletes = 0;
+ pState->lrc = ERROR_SUCCESS;
+ pState->fDelete = fDelete;
+ pState->fUpdate = fUpdate;
+ pState->fSamWow = fSamWow;
+ pState->fSamDelete = 0;
+ if (fDelete)
+ pState->fSamDelete = pState->fSamWow | DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE
+ | STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE;
+ pState->fSamUpdate = 0;
+ if (fUpdate)
+ pState->fSamUpdate = pState->fSamWow | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY
+ | STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE;
+ pState->fSamBoth = pState->fSamDelete | pState->fSamUpdate;
+
+ /*
+ * Open the root keys.
+ */
+ lrc = RegOpenKeyExA(hkeyRoot, pszSubRoot, 0 /*fOptions*/, pState->fSamBoth, &pState->hkeyClassesRootDst);
+ if (lrc == ERROR_SUCCESS)
+ {
+ lrc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"CLSID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyClsidRootDst, NULL /*pdwDisposition*/);
+ if (lrc == ERROR_SUCCESS)
+ return ERROR_SUCCESS;
+
+ /* Ignore access denied errors as these may easily happen for
+ non-admin users. Just give up when this happens */
+ AssertLogRelMsgReturn(lrc == ERROR_ACCESS_DENIED, ("%u\n", lrc), pState->lrc = lrc);
+ }
+ else
+ AssertLogRelMsgReturn(lrc == ERROR_ACCESS_DENIED, ("%u\n", lrc), pState->lrc = lrc);
+ return pState->lrc = lrc;
+}
+
+
+/**
+ * Terminates the state, closing all open keys.
+ *
+ * @param pState The state to clean up.
+ */
+static void vbpsRegTerm(VBPSREGSTATE *pState)
+{
+ LSTATUS lrc;
+ if (pState->hkeyClassesRootDst)
+ {
+ lrc = RegCloseKey(pState->hkeyClassesRootDst);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->hkeyClassesRootDst = NULL;
+ }
+ if (pState->hkeyClsidRootDst)
+ {
+ lrc = RegCloseKey(pState->hkeyClsidRootDst);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->hkeyClsidRootDst = NULL;
+ }
+ if (pState->hkeyInterfaceRootDst)
+ {
+ lrc = RegCloseKey(pState->hkeyInterfaceRootDst);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->hkeyInterfaceRootDst = NULL;
+ }
+
+ while (pState->cAltDeletes > 0 && pState->cAltDeletes <= RT_ELEMENTS(pState->aAltDeletes))
+ {
+ unsigned i = --pState->cAltDeletes;
+ if (pState->aAltDeletes[i].hkeyClasses)
+ {
+ lrc = RegCloseKey(pState->aAltDeletes[i].hkeyClasses);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ }
+ if (pState->aAltDeletes[i].hkeyClsid)
+ {
+ lrc = RegCloseKey(pState->aAltDeletes[i].hkeyClsid);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ }
+ if (pState->aAltDeletes[i].hkeyInterface)
+ {
+ lrc = RegCloseKey(pState->aAltDeletes[i].hkeyInterface);
+ Assert(lrc == ERROR_SUCCESS);
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ }
+}
+
+
+/**
+ * Add an alternative registry classes tree from which to remove keys.
+ *
+ * @returns ERROR_SUCCESS if we successfully opened the destination root, other
+ * wise windows error code (remebered).
+ * @param pState The registry modifier state.
+ * @param hkeyAltRoot The root of the alternate registry classes
+ * location.
+ * @param pszAltSubRoot The path to the 'classes' sub-key, or NULL if
+ * hkeyAltRoot is it.
+ */
+static LSTATUS vbpsRegAddAltDelete(VBPSREGSTATE *pState, HKEY hkeyAltRoot, const char *pszAltSubRoot)
+{
+ unsigned i;
+ LSTATUS lrc;
+
+ /* Ignore call if not in delete mode. */
+ if (!pState->fDelete)
+ return ERROR_SUCCESS;
+
+ /* Check that there is space in the state. */
+ i = pState->cAltDeletes;
+ AssertReturn(i < RT_ELEMENTS(pState->aAltDeletes), pState->lrc = ERROR_TOO_MANY_NAMES);
+
+
+ /* Open the root. */
+ lrc = RegOpenKeyExA(hkeyAltRoot, pszAltSubRoot, 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyClasses);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /* Try open the CLSID subkey, it's fine if it doesn't exists. */
+ lrc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyClsid);
+ if (lrc == ERROR_SUCCESS || lrc == ERROR_FILE_NOT_FOUND)
+ {
+ if (lrc == ERROR_FILE_NOT_FOUND)
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ pState->cAltDeletes = i + 1;
+ return ERROR_SUCCESS;
+ }
+ AssertLogRelMsgFailed(("%u\n", lrc));
+ RegCloseKey(pState->aAltDeletes[i].hkeyClasses);
+ }
+ /* No need to add non-existing alternative roots, nothing to delete in the void. */
+ else if (lrc == ERROR_FILE_NOT_FOUND)
+ lrc = ERROR_SUCCESS;
+ else
+ {
+ AssertLogRelMsgFailed(("%u (%#x %s)\n", lrc));
+ pState->lrc = lrc;
+ }
+
+ pState->aAltDeletes[i].hkeyClasses = NULL;
+ pState->aAltDeletes[i].hkeyClsid = NULL;
+ return lrc;
+}
+
+
+/**
+ * Open the 'Interface' keys under the current classes roots.
+ *
+ * We don't do this during vbpsRegInit as it's only needed for updating.
+ *
+ * @returns ERROR_SUCCESS if we successfully opened the destination root, other
+ * wise windows error code (remebered).
+ * @param pState The registry modifier state.
+ */
+static LSTATUS vbpsRegOpenInterfaceKeys(VBPSREGSTATE *pState)
+{
+ unsigned i;
+ LSTATUS lrc;
+
+ /*
+ * Under the root destination.
+ */
+ if (pState->hkeyInterfaceRootDst == NULL)
+ {
+ if (pState->fSamUpdate)
+ lrc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyInterfaceRootDst, NULL /*pdwDisposition*/);
+ else
+ lrc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*fOptions*/, pState->fSamBoth,
+ &pState->hkeyClsidRootDst);
+ if (lrc == ERROR_ACCESS_DENIED)
+ {
+ pState->hkeyInterfaceRootDst = NULL;
+ return pState->lrc = lrc;
+ }
+ AssertLogRelMsgReturnStmt(lrc == ERROR_SUCCESS, ("%u\n", lrc), pState->hkeyInterfaceRootDst = NULL, pState->lrc = lrc);
+ }
+
+ /*
+ * Under the alternative delete locations.
+ */
+ i = pState->cAltDeletes;
+ while (i-- > 0)
+ if (pState->aAltDeletes[i].hkeyInterface == NULL)
+ {
+ lrc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"Interface", 0 /*fOptions*/, pState->fSamDelete,
+ &pState->aAltDeletes[i].hkeyInterface);
+ if (lrc != ERROR_SUCCESS)
+ {
+ AssertMsgStmt(lrc == ERROR_FILE_NOT_FOUND || lrc == ERROR_ACCESS_DENIED, ("%u\n", lrc), pState->lrc = lrc);
+ pState->aAltDeletes[i].hkeyInterface = NULL;
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+
+/** The destination buffer size required by vbpsFormatUuidInCurly. */
+#define CURLY_UUID_STR_BUF_SIZE 40
+
+/**
+ * Formats a UUID to a string, inside curly braces.
+ *
+ * @returns @a pszString
+ * @param pszString Output buffer of size CURLY_UUID_STR_BUF_SIZE.
+ * @param pUuidIn The UUID to format.
+ */
+static const char *vbpsFormatUuidInCurly(char pszString[CURLY_UUID_STR_BUF_SIZE], const CLSID *pUuidIn)
+{
+ static const char s_achDigits[17] = "0123456789abcdef";
+ PCRTUUID pUuid = (PCRTUUID)pUuidIn;
+ uint32_t u32TimeLow;
+ unsigned u;
+
+ pszString[ 0] = '{';
+ u32TimeLow = RT_H2LE_U32(pUuid->Gen.u32TimeLow);
+ pszString[ 1] = s_achDigits[(u32TimeLow >> 28)/*& 0xf*/];
+ pszString[ 2] = s_achDigits[(u32TimeLow >> 24) & 0xf];
+ pszString[ 3] = s_achDigits[(u32TimeLow >> 20) & 0xf];
+ pszString[ 4] = s_achDigits[(u32TimeLow >> 16) & 0xf];
+ pszString[ 5] = s_achDigits[(u32TimeLow >> 12) & 0xf];
+ pszString[ 6] = s_achDigits[(u32TimeLow >> 8) & 0xf];
+ pszString[ 7] = s_achDigits[(u32TimeLow >> 4) & 0xf];
+ pszString[ 8] = s_achDigits[(u32TimeLow/*>>0*/)& 0xf];
+ pszString[ 9] = '-';
+ u = RT_H2LE_U16(pUuid->Gen.u16TimeMid);
+ pszString[10] = s_achDigits[(u >> 12)/*& 0xf*/];
+ pszString[11] = s_achDigits[(u >> 8) & 0xf];
+ pszString[12] = s_achDigits[(u >> 4) & 0xf];
+ pszString[13] = s_achDigits[(u/*>>0*/)& 0xf];
+ pszString[14] = '-';
+ u = RT_H2LE_U16(pUuid->Gen.u16TimeHiAndVersion);
+ pszString[15] = s_achDigits[(u >> 12)/*& 0xf*/];
+ pszString[16] = s_achDigits[(u >> 8) & 0xf];
+ pszString[17] = s_achDigits[(u >> 4) & 0xf];
+ pszString[18] = s_achDigits[(u/*>>0*/)& 0xf];
+ pszString[19] = '-';
+ pszString[20] = s_achDigits[pUuid->Gen.u8ClockSeqHiAndReserved >> 4];
+ pszString[21] = s_achDigits[pUuid->Gen.u8ClockSeqHiAndReserved & 0xf];
+ pszString[22] = s_achDigits[pUuid->Gen.u8ClockSeqLow >> 4];
+ pszString[23] = s_achDigits[pUuid->Gen.u8ClockSeqLow & 0xf];
+ pszString[24] = '-';
+ pszString[25] = s_achDigits[pUuid->Gen.au8Node[0] >> 4];
+ pszString[26] = s_achDigits[pUuid->Gen.au8Node[0] & 0xf];
+ pszString[27] = s_achDigits[pUuid->Gen.au8Node[1] >> 4];
+ pszString[28] = s_achDigits[pUuid->Gen.au8Node[1] & 0xf];
+ pszString[29] = s_achDigits[pUuid->Gen.au8Node[2] >> 4];
+ pszString[30] = s_achDigits[pUuid->Gen.au8Node[2] & 0xf];
+ pszString[31] = s_achDigits[pUuid->Gen.au8Node[3] >> 4];
+ pszString[32] = s_achDigits[pUuid->Gen.au8Node[3] & 0xf];
+ pszString[33] = s_achDigits[pUuid->Gen.au8Node[4] >> 4];
+ pszString[34] = s_achDigits[pUuid->Gen.au8Node[4] & 0xf];
+ pszString[35] = s_achDigits[pUuid->Gen.au8Node[5] >> 4];
+ pszString[36] = s_achDigits[pUuid->Gen.au8Node[5] & 0xf];
+ pszString[37] = '}';
+ pszString[38] = '\0';
+
+ return pszString;
+
+}
+
+
+/**
+ * Sets a registry string value, wide char variant.
+ *
+ * @returns See RegSetValueExA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to add the value to.
+ * @param pwszValueNm The value name. NULL for setting the default.
+ * @param pwszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsSetRegValueWW(VBPSREGSTATE *pState, HKEY hkey, PCRTUTF16 pwszValueNm, PCRTUTF16 pwszValue, unsigned uLine)
+{
+ DWORD const cbValue = (DWORD)((RTUtf16Len(pwszValue) + 1) * sizeof(RTUTF16));
+ LSTATUS lrc;
+ Assert(pState->fUpdate);
+
+ /*
+ * If we're not deleting the key prior to updating, we're in gentle update
+ * mode where we will query if the existing value matches the incoming one.
+ */
+ if (!pState->fDelete)
+ {
+ DWORD cbExistingData = cbValue + 128;
+ PRTUTF16 pwszExistingData = (PRTUTF16)alloca(cbExistingData);
+ DWORD dwExistingType;
+ lrc = RegQueryValueExW(hkey, pwszValueNm, 0 /*Reserved*/, &dwExistingType, (BYTE *)pwszExistingData, &cbExistingData);
+ if (lrc == ERROR_SUCCESS)
+ {
+ if ( dwExistingType == REG_SZ
+ && cbExistingData == cbValue)
+ {
+ if (memcmp(pwszValue, pwszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ }
+ VBSP_LOG_VALUE_CHANGE(("vbpsSetRegValueWW: Value difference: dwExistingType=%d cbExistingData=%#x cbValue=%#x\n"
+ " hkey=%#x %ls; value name=%ls\n"
+ "existing: %.*Rhxs (%.*ls)\n"
+ " new: %.*Rhxs (%ls)\n",
+ dwExistingType, cbExistingData, cbValue,
+ hkey, vbpsDebugKeyToWSZ(hkey), pwszValueNm ? pwszValueNm : L"(default)",
+ cbExistingData, pwszExistingData, cbExistingData / sizeof(RTUTF16), pwszExistingData,
+ cbValue, pwszValue, pwszValue));
+ }
+ else
+ Assert(lrc == ERROR_FILE_NOT_FOUND || lrc == ERROR_MORE_DATA);
+ }
+
+ /*
+ * Set the value.
+ */
+ lrc = RegSetValueExW(hkey, pwszValueNm, 0 /*Reserved*/, REG_SZ, (const BYTE *)pwszValue, cbValue);
+ if (lrc == ERROR_SUCCESS)
+ {
+ VBSP_LOG_SET_VALUE(("vbpsSetRegValueWW: %ls/%ls=%ls (at %d)\n",
+ vbpsDebugKeyToWSZ(hkey), pwszValueNm ? pwszValueNm : L"(Default)", pwszValue, uLine));
+ return ERROR_SUCCESS;
+ }
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: '%ls'='%ls' -> %u\n", uLine, pwszValueNm, pwszValue, lrc));
+ pState->lrc = lrc;
+ return lrc;
+}
+
+
+/**
+ * Sets a registry string value.
+ *
+ * @returns See RegSetValueExA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to add the value to.
+ * @param pszValueNm The value name. NULL for setting the default.
+ * @param pszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsSetRegValueAA(VBPSREGSTATE *pState, HKEY hkey, const char *pszValueNm, const char *pszValue, unsigned uLine)
+{
+ DWORD const cbValue = (DWORD)strlen(pszValue) + 1;
+ LSTATUS lrc;
+ Assert(pState->fUpdate);
+
+ /*
+ * If we're not deleting the key prior to updating, we're in gentle update
+ * mode where we will query if the existing value matches the incoming one.
+ */
+ if (!pState->fDelete)
+ {
+ DWORD cbExistingData = cbValue + 128;
+ char *pszExistingData = alloca(cbExistingData);
+ DWORD dwExistingType;
+ lrc = RegQueryValueExA(hkey, pszValueNm, 0 /*Reserved*/, &dwExistingType, (PBYTE)pszExistingData, &cbExistingData);
+ if (lrc == ERROR_SUCCESS)
+ {
+ if ( dwExistingType == REG_SZ
+ && cbExistingData == cbValue)
+ {
+ if (memcmp(pszValue, pszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ if (memicmp(pszValue, pszExistingData, cbValue) == 0)
+ return ERROR_SUCCESS;
+ }
+ VBSP_LOG_VALUE_CHANGE(("vbpsSetRegValueAA: Value difference: dwExistingType=%d cbExistingData=%#x cbValue=%#x\n"
+ " hkey=%#x %ls; value name=%s\n"
+ "existing: %.*Rhxs (%.*s)\n"
+ " new: %.*Rhxs (%s)\n",
+ dwExistingType, cbExistingData, cbValue,
+ hkey, vbpsDebugKeyToWSZ(hkey), pszValueNm ? pszValueNm : "(default)",
+ cbExistingData, pszExistingData, cbExistingData, pszExistingData,
+ cbValue, pszValue, pszValue));
+ }
+ else
+ Assert(lrc == ERROR_FILE_NOT_FOUND || lrc == ERROR_MORE_DATA);
+ }
+
+ /*
+ * Set the value.
+ */
+ lrc = RegSetValueExA(hkey, pszValueNm, 0 /*Reserved*/, REG_SZ, (PBYTE)pszValue, cbValue);
+ if (lrc == ERROR_SUCCESS)
+ {
+ VBSP_LOG_SET_VALUE(("vbpsSetRegValueAA: %ls/%s=%s (at %d)\n",
+ vbpsDebugKeyToWSZ(hkey), pszValueNm ? pszValueNm : "(Default)", pszValue, uLine));
+ return ERROR_SUCCESS;
+ }
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: '%s'='%s' -> %u\n", uLine, pszValueNm, pszValue, lrc));
+ pState->lrc = lrc;
+ return lrc;
+}
+
+
+/**
+ * Closes a registry key.
+ *
+ * @returns See RegCloseKey (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkey The key to close.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCloseKey(VBPSREGSTATE *pState, HKEY hkey, unsigned uLine)
+{
+ LSTATUS lrc = RegCloseKey(hkey);
+ if (lrc == ERROR_SUCCESS)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsgFailed(("%d: close key -> %u\n", uLine, lrc));
+ pState->lrc = lrc;
+ return lrc;
+}
+
+
+/**
+ * Creates a registry key.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param phkey Where to return the handle to the new key.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey, PHKEY phkey, unsigned uLine)
+{
+ /*
+ * This will open if it exists and create if new, which is exactly what we want.
+ */
+ HKEY hNewKey;
+ DWORD dwDisposition = 0;
+ LSTATUS lrc = RegCreateKeyExA(hkeyParent, pszKey, 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &hNewKey, &dwDisposition);
+ if (lrc == ERROR_SUCCESS)
+ {
+ *phkey = hNewKey;
+ if (dwDisposition == REG_CREATED_NEW_KEY)
+ VBSP_LOG_NEW_KEY(("vbpsCreateRegKeyA: %ls/%s (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pszKey, uLine));
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s' -> %u\n", uLine, pszKey, lrc));
+ pState->lrc = lrc;
+ *phkey = NULL;
+ }
+ return lrc;
+}
+
+
+/**
+ * Creates a registry key with a default string value.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ const char *pszValue, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS lrc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (lrc == ERROR_SUCCESS)
+ {
+ lrc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine);
+ vbpsCloseKey(pState, hNewKey, uLine);
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, lrc));
+ pState->lrc = lrc;
+ }
+ return lrc;
+}
+
+
+/**
+ * Creates a registry key with a default wide string value.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pwszValue The value string.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAW(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ PCRTUTF16 pwszValue, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS lrc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (lrc == ERROR_SUCCESS)
+ {
+ lrc = vbpsSetRegValueWW(pState, hNewKey, NULL /*pwszValueNm*/, pwszValue, uLine);
+ vbpsCloseKey(pState, hNewKey, uLine);
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%ls') -> %u\n", uLine, pszKey, pwszValue, lrc));
+ pState->lrc = lrc;
+ }
+ return lrc;
+}
+
+
+/**
+ * Creates a registry key with a default string value, return the key.
+ *
+ * @returns See RegCreateKeyA and RegSetValueExA (errors are remembered in the
+ * state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The new key under @a hkeyParent.
+ * @param pszValue The value string.
+ * @param phkey Where to return the handle to the new key.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsCreateRegKeyWithDefaultValueAAEx(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey,
+ const char *pszValue, PHKEY phkey, unsigned uLine)
+{
+ HKEY hNewKey;
+ LSTATUS lrc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine);
+ if (lrc == ERROR_SUCCESS)
+ {
+ lrc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine);
+ *phkey = hNewKey;
+ }
+ else
+ {
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, lrc));
+ pState->lrc = lrc;
+ *phkey = NULL;
+ }
+ return lrc;
+}
+
+
+/**
+ * Recursively deletes a registry key.
+ *
+ * @returns See SHDeleteKeyA (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pszKey The key under @a hkeyParent that should be
+ * deleted.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsDeleteKeyRecursiveA(VBPSREGSTATE *pState, HKEY hkeyParent, const char *pszKey, unsigned uLine)
+{
+ LSTATUS lrc;
+
+ Assert(pState->fDelete);
+ Assert(pszKey);
+ AssertReturn(*pszKey != '\0', pState->lrc = ERROR_INVALID_PARAMETER);
+
+#ifdef VBSP_LOG_ENABLED
+ {
+ HKEY hkeyLog;
+ lrc = RegOpenKeyExA(hkeyParent, pszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog);
+ if (lrc != ERROR_FILE_NOT_FOUND)
+ VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveA: %ls/%s (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pszKey, uLine));
+ if (lrc == ERROR_SUCCESS)
+ RegCloseKey(hkeyLog);
+ }
+#endif
+
+ lrc = SHDeleteKeyA(hkeyParent, pszKey);
+ if (lrc == ERROR_SUCCESS || lrc == ERROR_FILE_NOT_FOUND)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: delete key '%s' -> %u\n", uLine, pszKey, lrc));
+ pState->lrc = lrc;
+ return lrc;
+}
+
+
+/**
+ * Recursively deletes a registry key, wide char version.
+ *
+ * @returns See SHDeleteKeyW (errors are remembered in the state).
+ * @param pState The registry modifier state.
+ * @param hkeyParent The parent key.
+ * @param pwszKey The key under @a hkeyParent that should be
+ * deleted.
+ * @param uLine The line we're called from.
+ */
+static LSTATUS vbpsDeleteKeyRecursiveW(VBPSREGSTATE *pState, HKEY hkeyParent, PCRTUTF16 pwszKey, unsigned uLine)
+{
+ LSTATUS lrc;
+
+ Assert(pState->fDelete);
+ Assert(pwszKey);
+ AssertReturn(*pwszKey != '\0', pState->lrc = ERROR_INVALID_PARAMETER);
+
+#ifdef VBSP_LOG_ENABLED
+ {
+ HKEY hkeyLog;
+ lrc = RegOpenKeyExW(hkeyParent, pwszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog);
+ if (lrc != ERROR_FILE_NOT_FOUND)
+ VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveW: %ls/%ls (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pwszKey, uLine));
+ if (lrc == ERROR_SUCCESS)
+ RegCloseKey(hkeyLog);
+ }
+#endif
+
+ lrc = SHDeleteKeyW(hkeyParent, pwszKey);
+ if (lrc == ERROR_SUCCESS || lrc == ERROR_FILE_NOT_FOUND)
+ return ERROR_SUCCESS;
+
+ AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(lrc == ERROR_ACCESS_DENIED),
+ ("%d: delete key '%ls' -> %u\n", uLine, pwszKey, lrc));
+ pState->lrc = lrc;
+ return lrc;
+}
+
+
+/**
+ * Register an application id.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pszModuleName The module name.
+ * @param pszAppId The application UUID string.
+ * @param pszDescription The description string.
+ * @param pszServiceName The window service name if the application is a
+ * service, otherwise this must be NULL.
+ */
+LSTATUS VbpsRegisterAppId(VBPSREGSTATE *pState, const char *pszModuleName, const char *pszAppId,
+ const char *pszDescription, const char *pszServiceName)
+{
+ LSTATUS lrc;
+ HKEY hkeyAppIds;
+ Assert(*pszAppId == '{');
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ {
+ lrc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"AppID", 0 /*fOptions*/, pState->fSamDelete, &hkeyAppIds);
+ AssertLogRelMsgStmt(lrc == ERROR_SUCCESS || lrc == ERROR_FILE_NOT_FOUND, ("%u\n", lrc), pState->lrc = lrc);
+ if (lrc == ERROR_SUCCESS)
+ {
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__);
+ vbpsCloseKey(pState, hkeyAppIds, __LINE__);
+ }
+ }
+ }
+
+ if (pState->fUpdate)
+ {
+ lrc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/,
+ pState->fSamBoth, NULL /*pSecAttr*/, &hkeyAppIds, NULL /*pdwDisposition*/);
+ if (lrc == ERROR_ACCESS_DENIED)
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ lrc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*fOptions*/, pState->fSamBoth, &hkeyAppIds);
+ if (lrc == ERROR_FILE_NOT_FOUND || lrc == ERROR_ACCESS_DENIED)
+ return ERROR_SUCCESS;
+ }
+ if (lrc == ERROR_ACCESS_DENIED)
+ return pState->lrc = lrc;
+ AssertLogRelMsgReturn(lrc == ERROR_SUCCESS, ("%u\n", lrc), pState->lrc = lrc);
+
+ if (pState->fDelete)
+ {
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszModuleName, __LINE__);
+ }
+
+ /*
+ * Register / update.
+ */
+ if (pState->fUpdate)
+ {
+ HKEY hkey;
+ lrc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszAppId, &hkey, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, pszDescription, __LINE__);
+ if (pszServiceName)
+ vbpsSetRegValueAA(pState, hkey, "LocalService", pszServiceName, __LINE__);
+ vbpsCloseKey(pState, hkey, __LINE__);
+ }
+
+ lrc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszModuleName, &hkey, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, "", __LINE__);
+ vbpsSetRegValueAA(pState, hkey, "AppID", pszAppId, __LINE__);
+ vbpsCloseKey(pState, hkey, __LINE__);
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyAppIds, __LINE__);
+
+ return pState->lrc;
+}
+
+
+/**
+ * Register an class name.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pszClassName The name of the class.
+ * @param pszDescription The description string
+ * @param pClsId The UUID for the class.
+ * @param pszCurVerSuffIfRootName This is the current version suffix to
+ * append to @a pszClassName when
+ * registering the version idependent name.
+ */
+LSTATUS VbpsRegisterClassName(VBPSREGSTATE *pState, const char *pszClassName, const char *pszDescription,
+ const CLSID *pClsId, const char *pszCurVerSuffIfRootName)
+{
+ LSTATUS lrc;
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ vbpsDeleteKeyRecursiveA(pState, pState->aAltDeletes[i].hkeyClasses, pszClassName, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, pState->hkeyClassesRootDst, pszClassName, __LINE__);
+ }
+
+ /*
+ * Update.
+ */
+ if (pState->fUpdate)
+ {
+ /* pszClassName/Default = description. */
+ HKEY hkeyClass;
+ lrc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClassesRootDst, pszClassName, pszDescription,
+ &hkeyClass, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ char szClsId[CURLY_UUID_STR_BUF_SIZE];
+
+ /* CLSID/Default = pClsId. */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "CLSID", vbpsFormatUuidInCurly(szClsId, pClsId), __LINE__);
+
+ /* CurVer/Default = pszClassName+Suffix. */
+ if (pszCurVerSuffIfRootName != NULL)
+ {
+ char szCurClassNameVer[128];
+ lrc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName);
+ if (RT_SUCCESS(lrc))
+ lrc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurVerSuffIfRootName);
+ AssertStmt(RT_SUCCESS(lrc), pState->lrc = lrc = ERROR_INVALID_DATA);
+ if (lrc == ERROR_SUCCESS)
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "CurVer", szCurClassNameVer, __LINE__);
+ }
+
+ vbpsCloseKey(pState, hkeyClass, __LINE__);
+ }
+ }
+
+ return pState->lrc;
+}
+
+
+/**
+ * Registers a class ID.
+ *
+ * @returns Windows error code (errors are rememberd in the state).
+ * @param pState The registry modifier state.
+ * @param pClsId The UUID for the class.
+ * @param pszDescription The description string.
+ * @param pszAppId The application ID.
+ * @param pszClassName The version idependent class name.
+ * @param pszCurClassNameVerSuffix The suffix to add to @a pszClassName for
+ * the current version.
+ * @param pTypeLibId The UUID for the typelib this class
+ * belongs to.
+ * @param pszServerType The server type (InprocServer32 or
+ * LocalServer32).
+ * @param pwszVBoxDir The VirtualBox install directory
+ * (unicode), trailing slash.
+ * @param pszServerSubPath What to append to @a pwszVBoxDir to
+ * construct the server module name.
+ * @param pszThreadingModel The threading model for inproc servers,
+ * NULL for local servers.
+ */
+LSTATUS VbpsRegisterClassId(VBPSREGSTATE *pState, const CLSID *pClsId, const char *pszDescription, const char *pszAppId,
+ const char *pszClassName, const char *pszCurClassNameVerSuffix, const CLSID *pTypeLibId,
+ const char *pszServerType, PCRTUTF16 pwszVBoxDir, const char *pszServerSubPath,
+ const char *pszThreadingModel)
+{
+ LSTATUS lrc;
+ char szClsId[CURLY_UUID_STR_BUF_SIZE];
+ RT_NOREF(pszAppId);
+
+ Assert(!pszAppId || *pszAppId == '{');
+ Assert((pwszVBoxDir == NULL && !pState->fUpdate) || pwszVBoxDir[RTUtf16Len(pwszVBoxDir) - 1] == '\\');
+
+ /*
+ * We need this, whatever we end up having to do.
+ */
+ vbpsFormatUuidInCurly(szClsId, pClsId);
+
+ /*
+ * Delete.
+ */
+ if (pState->fDelete)
+ {
+ unsigned i = pState->cAltDeletes;
+ while (i-- > 0)
+ if (pState->aAltDeletes[i].hkeyClsid != NULL)
+ vbpsDeleteKeyRecursiveA(pState, pState->aAltDeletes[i].hkeyClsid, szClsId, __LINE__);
+ vbpsDeleteKeyRecursiveA(pState, pState->hkeyClsidRootDst, szClsId, __LINE__);
+ }
+
+ /*
+ * Update.
+ */
+ if (pState->fUpdate)
+ {
+ HKEY hkeyClass;
+ lrc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClsidRootDst, szClsId, pszDescription,
+ &hkeyClass, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ bool const fIsLocalServer32 = strcmp(pszServerType, "LocalServer32") == 0;
+ HKEY hkeyServerType;
+ char szCurClassNameVer[128];
+
+ /* pszServerType/Default = module. */
+ lrc = vbpsCreateRegKeyA(pState, hkeyClass, pszServerType, &hkeyServerType, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ RTUTF16 wszModule[MAX_PATH * 2];
+ PRTUTF16 pwszCur = wszModule;
+ if (fIsLocalServer32)
+ *pwszCur++ = '"';
+
+ int vrc = RTUtf16Copy(pwszCur, MAX_PATH, pwszVBoxDir); AssertRC(vrc);
+ pwszCur += RTUtf16Len(pwszCur);
+ vrc = RTUtf16CopyAscii(pwszCur, MAX_PATH - 3, pszServerSubPath); AssertRC(vrc);
+ pwszCur += RTUtf16Len(pwszCur);
+
+ if (fIsLocalServer32)
+ *pwszCur++ = '"';
+ *pwszCur++ = '\0'; /* included, so ++. */
+
+ vbpsSetRegValueWW(pState, hkeyServerType, NULL /*pszValueNm*/, wszModule, __LINE__);
+
+ /* pszServerType/ThreadingModel = pszThreading Model. */
+ if (pszThreadingModel)
+ vbpsSetRegValueAA(pState, hkeyServerType, "ThreadingModel", pszThreadingModel, __LINE__);
+
+ vbpsCloseKey(pState, hkeyServerType, __LINE__);
+ }
+
+ /* ProgId/Default = pszClassName + pszCurClassNameVerSuffix. */
+ if (pszClassName)
+ {
+ int vrc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName);
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurClassNameVerSuffix);
+ AssertStmt(RT_SUCCESS(vrc), pState->lrc = lrc = ERROR_INVALID_DATA);
+ if (lrc == ERROR_SUCCESS)
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "ProgId", szCurClassNameVer, __LINE__);
+
+ /* VersionIndependentProgID/Default = pszClassName. */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "VersionIndependentProgID", pszClassName, __LINE__);
+ }
+
+ /* TypeLib/Default = pTypeLibId. */
+ if (pTypeLibId)
+ {
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "TypeLib",
+ vbpsFormatUuidInCurly(szTypeLibId, pTypeLibId), __LINE__);
+ }
+
+ /* AppID = pszAppId */
+ if (pszAppId && fIsLocalServer32)
+ vbpsSetRegValueAA(pState, hkeyClass, "AppID", pszAppId, __LINE__);
+
+ vbpsCloseKey(pState, hkeyClass, __LINE__);
+ }
+ }
+
+ return pState->lrc;
+}
+
+
+/**
+ * Register modules and classes from the VirtualBox.xidl file.
+ *
+ * @param pState
+ * @param pwszVBoxDir The VirtualBox application directory.
+ * @param fIs32On64 Set if this is the 32-bit on 64-bit component.
+ *
+ * @todo convert to XSLT.
+ */
+void RegisterXidlModulesAndClassesGenerated(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ const char *pszAppId = "{819B4D85-9CEE-493C-B6FC-64FFE759B3C9}";
+ const char *pszInprocDll = !fIs32On64 ? "VBoxC.dll" : "x86\\VBoxClient-x86.dll";
+ const char *pszLocalServer = "VBoxSVC.exe";
+#ifdef VBOX_WITH_SDS
+ const char *pszSdsAppId = "{EC0E78E8-FA43-43E8-AC0A-02C784C4A4FA}";
+ const char *pszSdsExe = "VBoxSDS.exe";
+ const char *pszSdsServiceName = "VBoxSDS";
+#endif
+
+ /* VBoxSVC */
+ VbpsRegisterAppId(pState, pszLocalServer, pszAppId, "VirtualBox Application", NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBox.1", "VirtualBox Class", &CLSID_VirtualBox, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBox", "VirtualBox Class", &CLSID_VirtualBox, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBox, "VirtualBox Class", pszAppId, "VirtualBox.VirtualBox", ".1",
+ &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszLocalServer, NULL /*N/A*/);
+ /* VBoxC */
+ VbpsRegisterClassName(pState, "VirtualBox.Session.1", "Session Class", &CLSID_Session, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.Session", "Session Class", &CLSID_Session, ".1");
+ VbpsRegisterClassId(pState, &CLSID_Session, "Session Class", pszAppId, "VirtualBox.Session", ".1",
+ &LIBID_VirtualBox, "InprocServer32", pwszVBoxDir, pszInprocDll, "Free");
+
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClient.1", "VirtualBoxClient Class", &CLSID_VirtualBoxClient, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxClient", "VirtualBoxClient Class", &CLSID_VirtualBoxClient, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBoxClient, "VirtualBoxClient Class", pszAppId,
+ "VirtualBox.VirtualBoxClient", ".1",
+ &LIBID_VirtualBox, "InprocServer32", pwszVBoxDir, pszInprocDll, "Free");
+
+#ifdef VBOX_WITH_SDS
+ /* VBoxSDS */
+ VbpsRegisterAppId(pState, pszSdsExe, pszSdsAppId, "VirtualBox System Service", pszSdsServiceName);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxSDS.1", "VirtualBoxSDS Class", &CLSID_VirtualBoxSDS, NULL);
+ VbpsRegisterClassName(pState, "VirtualBox.VirtualBoxSDS", "VirtualBoxSDS Class", &CLSID_VirtualBoxSDS, ".1");
+ VbpsRegisterClassId(pState, &CLSID_VirtualBoxSDS, "VirtualBoxSDS Class", pszSdsAppId, "VirtualBox.VirtualBoxSDS", ".1",
+ &LIBID_VirtualBox, "LocalServer32", pwszVBoxDir, pszSdsExe, NULL /*N/A*/);
+#endif
+}
+
+
+/**
+ * Updates the VBox type lib registration.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the RegisterTypeLib and
+ * UnRegisterTypeLib APIs.
+ *
+ * @param pState The registry modifier state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fIs32On64 Set if we're registering the 32-bit proxy stub
+ * on a 64-bit system.
+ */
+static void vbpsUpdateTypeLibRegistration(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ const char * const pszTypeLibDll = VBPS_PROXY_STUB_FILE(fIs32On64);
+#if ARCH_BITS == 32 && !defined(VBOX_IN_32_ON_64_MAIN_API)
+ const char * const pszWinXx = "win32";
+#else
+ const char * const pszWinXx = !fIs32On64 ? "win64" : "win32";
+#endif
+ const char * const pszDescription = "VirtualBox Type Library";
+
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ HKEY hkeyTypeLibs;
+ HKEY hkeyTypeLibId;
+ LSTATUS lrc;
+ RT_NOREF(fIs32On64);
+
+ Assert(pState->fUpdate && !pState->fDelete);
+
+ /*
+ * Type library registration (w/o interfaces).
+ */
+
+ /* Open Classes/TypeLib/. */
+ lrc = vbpsCreateRegKeyA(pState, pState->hkeyClassesRootDst, "TypeLib", &hkeyTypeLibs, __LINE__);
+ if (lrc != ERROR_SUCCESS)
+ return;
+
+ /* Create TypeLib/{UUID}. */
+ lrc = vbpsCreateRegKeyA(pState, hkeyTypeLibs, vbpsFormatUuidInCurly(szTypeLibId, &LIBID_VirtualBox), &hkeyTypeLibId, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /* {UUID}/Major.Minor/Default = pszDescription. */
+ HKEY hkeyMajMin;
+ char szMajMin[64];
+ sprintf(szMajMin, "%u.%u", kTypeLibraryMajorVersion, kTypeLibraryMinorVersion);
+ lrc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyTypeLibId, szMajMin, pszDescription, &hkeyMajMin, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ RTUTF16 wszBuf[MAX_PATH * 2];
+
+ /* {UUID}/Major.Minor/0. */
+ HKEY hkey0;
+ lrc = vbpsCreateRegKeyA(pState, hkeyMajMin, "0", &hkey0, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /* {UUID}/Major.Minor/0/winXX/Default = VBoxProxyStub. */
+ int vrc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(vrc);
+ vrc = RTUtf16CatAscii(wszBuf, MAX_PATH * 2, pszTypeLibDll); AssertRC(vrc);
+
+ vbpsCreateRegKeyWithDefaultValueAW(pState, hkey0, pszWinXx, wszBuf, __LINE__);
+ vbpsCloseKey(pState, hkey0, __LINE__);
+ }
+
+ /* {UUID}/Major.Minor/FLAGS */
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyMajMin, "FLAGS", "0", __LINE__);
+
+ /* {UUID}/Major.Minor/HELPDIR */
+ int vrc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(vrc);
+#if 0 /* MSI: trailing slash; regsvr32/comregister: strip unnecessary trailing slash. Go with MSI to avoid user issues. */
+ {
+ size_t off = RTUtf16Len(wszBuf);
+ while (off > 2 && wszBuf[off - 2] != ':' && RTPATH_IS_SLASH(wszBuf[off - 1]))
+ off--;
+ wszBuf[off] = '\0';
+ }
+#endif
+ vbpsCreateRegKeyWithDefaultValueAW(pState, hkeyMajMin, "HELPDIR", wszBuf, __LINE__);
+
+ vbpsCloseKey(pState, hkeyMajMin, __LINE__);
+ }
+ vbpsCloseKey(pState, hkeyTypeLibId, __LINE__);
+ }
+ vbpsCloseKey(pState, hkeyTypeLibs, __LINE__);
+}
+
+
+/**
+ * Update the VBox proxy stub registration.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the NdrDllRegisterProxy
+ * and NdrDllUnregisterProxy.
+ *
+ * @param pState The registry modifier state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fIs32On64 Set if we're registering the 32-bit proxy stub
+ * on a 64-bit system.
+ */
+static void vbpsUpdateProxyStubRegistration(VBPSREGSTATE *pState, PCRTUTF16 pwszVBoxDir, bool fIs32On64)
+{
+ /*
+ * Register the proxy stub factory class ID.
+ * It's simple compared to the VBox classes, thus all the NULL parameters.
+ */
+ const char *pszPsDll = VBPS_PROXY_STUB_FILE(fIs32On64);
+ RT_NOREF(fIs32On64);
+ Assert(pState->fUpdate && !pState->fDelete);
+ VbpsRegisterClassId(pState, &g_ProxyClsId, "PSFactoryBuffer", NULL /*pszAppId*/,
+ NULL /*pszClassName*/, NULL /*pszCurClassNameVerSuffix*/, NULL /*pTypeLibId*/,
+ "InprocServer32", pwszVBoxDir, pszPsDll, "Both");
+}
+
+
+/**
+ * Updates the VBox interface registrations.
+ *
+ * This is only used when updating COM registrations during com::Initialize.
+ * For normal registration and unregistrations we use the NdrDllRegisterProxy
+ * and NdrDllUnregisterProxy.
+ *
+ * @param pState The registry modifier state.
+ */
+static void vbpsUpdateInterfaceRegistrations(VBPSREGSTATE *pState)
+{
+ const ProxyFileInfo **ppProxyFile = &g_apProxyFiles[0];
+ const ProxyFileInfo *pProxyFile;
+ LSTATUS lrc;
+ char szProxyClsId[CURLY_UUID_STR_BUF_SIZE];
+ char szTypeLibId[CURLY_UUID_STR_BUF_SIZE];
+ char szTypeLibVersion[64];
+
+ vbpsFormatUuidInCurly(szProxyClsId, &g_ProxyClsId);
+ vbpsFormatUuidInCurly(szTypeLibId, &LIBID_VirtualBox);
+ sprintf(szTypeLibVersion, "%u.%u", kTypeLibraryMajorVersion, kTypeLibraryMinorVersion);
+
+ Assert(pState->fUpdate && !pState->fDelete);
+ lrc = vbpsRegOpenInterfaceKeys(pState);
+ if (lrc != ERROR_SUCCESS)
+ return;
+
+ /*
+ * We walk the proxy file list (even if we only have one).
+ */
+ while ((pProxyFile = *ppProxyFile++) != NULL)
+ {
+ const PCInterfaceStubVtblList * const papStubVtbls = pProxyFile->pStubVtblList;
+ const char * const *papszNames = pProxyFile->pNamesArray;
+ unsigned iIf = pProxyFile->TableSize;
+ AssertStmt(iIf < 1024, iIf = 0);
+ Assert(pProxyFile->TableVersion == 2);
+
+ /*
+ * Walk the interfaces in that file, picking data from the various tables.
+ */
+ while (iIf-- > 0)
+ {
+ char szIfId[CURLY_UUID_STR_BUF_SIZE];
+ const char * const pszIfNm = papszNames[iIf];
+ size_t const cchIfNm = RT_VALID_PTR(pszIfNm) ? strlen(pszIfNm) : 0;
+ char szMethods[32];
+ uint32_t const cMethods = papStubVtbls[iIf]->header.DispatchTableCount;
+ HKEY hkeyIfId;
+
+ AssertReturnVoidStmt(cchIfNm >= 3 && cchIfNm <= 72, pState->lrc = ERROR_INVALID_DATA);
+
+ AssertReturnVoidStmt(cMethods >= 3 && cMethods < 1024, pState->lrc = ERROR_INVALID_DATA);
+ sprintf(szMethods, "%u", cMethods);
+
+ lrc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyInterfaceRootDst,
+ vbpsFormatUuidInCurly(szIfId, papStubVtbls[iIf]->header.piid),
+ pszIfNm, &hkeyIfId, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ HKEY hkeyTypeLib;
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyIfId, "ProxyStubClsid32", szProxyClsId, __LINE__);
+ vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyIfId, "NumMethods", szMethods, __LINE__);
+
+ /* The MSI seems to still be putting TypeLib keys here. So, let's do that too. */
+ lrc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyIfId, "TypeLib", szTypeLibId, &hkeyTypeLib, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ vbpsSetRegValueAA(pState, hkeyTypeLib, "Version", szTypeLibVersion, __LINE__);
+ vbpsCloseKey(pState, hkeyTypeLib, __LINE__);
+ }
+
+ vbpsCloseKey(pState, hkeyIfId, __LINE__);
+ }
+ }
+ }
+}
+
+
+static bool vbpsIsUpToDate(VBPSREGSTATE *pState)
+{
+ /** @todo read some registry key and */
+ NOREF(pState);
+ return false;
+}
+
+static bool vbpsMarkUpToDate(VBPSREGSTATE *pState)
+{
+ /** @todo write the key vbpsIsUpToDate uses, if pState indicates success. */
+ NOREF(pState);
+ return false;
+}
+
+
+
+/**
+ * Strips the stub dll name and any x86 subdir off the full DLL path to get a
+ * path to the VirtualBox application directory.
+ *
+ * @param pwszDllPath The path to strip, returns will end with a slash.
+ */
+static void vbpsDllPathToVBoxDir(PRTUTF16 pwszDllPath)
+{
+ RTUTF16 wc;
+ size_t off = RTUtf16Len(pwszDllPath);
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) >= 127U
+ || !RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ /*
+ * The -x86 variant is in a x86 subdirectory, drop it.
+ */
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) < 127U
+ && RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+ while ( off > 0
+ && ( (wc = pwszDllPath[off - 1]) >= 127U
+ || !RTPATH_IS_SEP((unsigned char)wc)))
+ off--;
+#endif
+ pwszDllPath[off] = '\0';
+}
+
+
+/**
+ * Wrapper around RegisterXidlModulesAndClassesGenerated for the convenience of
+ * the standard registration entry points.
+ *
+ * @returns COM status code.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param fDelete Whether to delete registration keys and values.
+ * @param fUpdate Whether to update registration keys and values.
+ */
+HRESULT RegisterXidlModulesAndClasses(PRTUTF16 pwszVBoxDir, bool fDelete, bool fUpdate)
+{
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ bool const fIs32On64 = true;
+#else
+ bool const fIs32On64 = false;
+#endif
+ VBPSREGSTATE State;
+ LSTATUS lrc;
+
+ /*
+ * Do registration for the current execution mode of the DLL.
+ */
+ lrc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL /* Alt: HKEY_LOCAL_MACHINE, "Software\\Classes", */, fDelete, fUpdate, 0);
+ if (lrc == ERROR_SUCCESS)
+ {
+ if (!fUpdate)
+ {
+ /* When only unregistering, really purge everything twice or trice. :-) */
+ vbpsRegAddAltDelete(&State, HKEY_LOCAL_MACHINE, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CURRENT_USER, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CLASSES_ROOT, NULL);
+ }
+
+ RegisterXidlModulesAndClassesGenerated(&State, pwszVBoxDir, fIs32On64);
+ lrc = State.lrc;
+ }
+ vbpsRegTerm(&State);
+
+ /*
+ * Translate error code? Return.
+ */
+ if (lrc == ERROR_SUCCESS)
+ return S_OK;
+ return E_FAIL;
+}
+
+
+/**
+ * Checks if the string matches any of our type library versions.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pwszTypeLibVersion The type library version string.
+ */
+DECLINLINE(bool) vbpsIsTypeLibVersionToRemove(PCRTUTF16 pwszTypeLibVersion)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszTypelibVersions) == 2);
+
+ /* ASSUMES: 1.x version strings and that the input buffer is at least 3 wchars long. */
+ if ( g_apwszTypelibVersions[0][3] == pwszTypeLibVersion[3]
+ && RTUtf16Cmp(g_apwszTypelibVersions[0], pwszTypeLibVersion) == 0)
+ return true;
+ if ( g_apwszTypelibVersions[1][3] == pwszTypeLibVersion[3]
+ && RTUtf16Cmp(g_apwszTypelibVersions[1], pwszTypeLibVersion) == 0)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Quick check whether the given string looks like a UUID in braces.
+ *
+ * This does not check the whole string, just do a quick sweep.
+ *
+ * @returns true if possible UUID, false if definitely not.
+ * @param pwszUuid Alleged UUID in braces.
+ */
+DECLINLINE(bool) vbpsIsUuidInBracesQuickW(PCRTUTF16 pwszUuid)
+{
+ return pwszUuid[ 0] == '{'
+ && pwszUuid[ 9] == '-'
+ && pwszUuid[14] == '-'
+ && pwszUuid[19] == '-'
+ && pwszUuid[24] == '-'
+ && pwszUuid[37] == '}'
+ && pwszUuid[38] == '\0'
+ && RT_C_IS_XDIGIT(pwszUuid[1]);
+}
+
+
+/**
+ * Compares two UUIDs (in braces).
+ *
+ * @returns true on match, false if no match.
+ * @param pwszUuid1 The first UUID.
+ * @param pwszUuid2 The second UUID.
+ */
+static bool vbpsCompareUuidW(PCRTUTF16 pwszUuid1, PCRTUTF16 pwszUuid2)
+{
+#define COMPARE_EXACT_RET(a_wch1, a_wch2) \
+ if ((a_wch1) == (a_wch2)) { } else return false
+
+#define COMPARE_XDIGITS_RET(a_wch1, a_wch2) \
+ if ((a_wch1) == (a_wch2)) { } \
+ else if (RT_C_TO_UPPER(a_wch1) != RT_C_TO_UPPER(a_wch2) || (a_wch1) >= 127U || (a_wch2) >= 127U) \
+ return false
+ COMPARE_EXACT_RET( pwszUuid1[ 0], pwszUuid2[ 0]); /* { */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 1], pwszUuid2[ 1]); /* 5 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 2], pwszUuid2[ 2]); /* e */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 3], pwszUuid2[ 3]); /* 5 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 4], pwszUuid2[ 4]); /* e */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 5], pwszUuid2[ 5]); /* 3 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 6], pwszUuid2[ 6]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 7], pwszUuid2[ 7]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[ 8], pwszUuid2[ 8]); /* 0 */
+ COMPARE_EXACT_RET( pwszUuid1[ 9], pwszUuid2[ 9]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[10], pwszUuid2[10]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[11], pwszUuid2[11]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[12], pwszUuid2[12]); /* f */
+ COMPARE_XDIGITS_RET(pwszUuid1[13], pwszUuid2[13]); /* 3 */
+ COMPARE_EXACT_RET( pwszUuid1[14], pwszUuid2[14]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[15], pwszUuid2[15]); /* 4 */
+ COMPARE_XDIGITS_RET(pwszUuid1[16], pwszUuid2[16]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[17], pwszUuid2[17]); /* 8 */
+ COMPARE_XDIGITS_RET(pwszUuid1[18], pwszUuid2[18]); /* 9 */
+ COMPARE_EXACT_RET( pwszUuid1[19], pwszUuid2[19]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[20], pwszUuid2[20]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[21], pwszUuid2[21]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[22], pwszUuid2[22]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[23], pwszUuid2[23]); /* f */
+ COMPARE_EXACT_RET( pwszUuid1[24], pwszUuid2[24]); /* - */
+ COMPARE_XDIGITS_RET(pwszUuid1[25], pwszUuid2[25]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[26], pwszUuid2[26]); /* b */
+ COMPARE_XDIGITS_RET(pwszUuid1[27], pwszUuid2[27]); /* 1 */
+ COMPARE_XDIGITS_RET(pwszUuid1[28], pwszUuid2[28]); /* b */
+ COMPARE_XDIGITS_RET(pwszUuid1[29], pwszUuid2[29]); /* 8 */
+ COMPARE_XDIGITS_RET(pwszUuid1[30], pwszUuid2[30]); /* d */
+ COMPARE_XDIGITS_RET(pwszUuid1[31], pwszUuid2[31]); /* 7 */
+ COMPARE_XDIGITS_RET(pwszUuid1[32], pwszUuid2[32]); /* 6 */
+ COMPARE_XDIGITS_RET(pwszUuid1[33], pwszUuid2[33]); /* 0 */
+ COMPARE_XDIGITS_RET(pwszUuid1[34], pwszUuid2[34]); /* 9 */
+ COMPARE_XDIGITS_RET(pwszUuid1[35], pwszUuid2[35]); /* a */
+ COMPARE_XDIGITS_RET(pwszUuid1[36], pwszUuid2[36]); /* 5 */
+ COMPARE_EXACT_RET( pwszUuid1[37], pwszUuid2[37]); /* } */
+ COMPARE_EXACT_RET( pwszUuid1[38], pwszUuid2[38]); /* \0 */
+#undef COMPARE_EXACT_RET
+#undef COMPARE_XDIGITS_RET
+ return true;
+}
+
+
+/**
+ * Checks if the type library ID is one of the ones we wish to clean up.
+ *
+ * @returns true if it should be cleaned up, false if not.
+ * @param pwszTypeLibId The type library ID as a bracketed string.
+ */
+DECLINLINE(bool) vbpsIsTypeLibIdToRemove(PRTUTF16 pwszTypeLibId)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszTypeLibIds) == 2);
+#ifdef VBOX_STRICT
+ static bool s_fDoneStrict = false;
+ if (s_fDoneStrict) { }
+ else
+ {
+ Assert(RT_ELEMENTS(g_apwszTypeLibIds) == 2);
+ Assert(g_apwszTypeLibIds[0][0] == '{');
+ Assert(g_apwszTypeLibIds[1][0] == '{');
+ Assert(RT_C_IS_XDIGIT(g_apwszTypeLibIds[0][1]));
+ Assert(RT_C_IS_XDIGIT(g_apwszTypeLibIds[1][1]));
+ Assert(RT_C_IS_UPPER(g_apwszTypeLibIds[0][1]) || RT_C_IS_DIGIT(g_apwszTypeLibIds[0][1]));
+ Assert(RT_C_IS_UPPER(g_apwszTypeLibIds[1][1]) || RT_C_IS_DIGIT(g_apwszTypeLibIds[1][1]));
+ s_fDoneStrict = true;
+ }
+#endif
+
+ /*
+ * Rolled out matching with inlined check of the opening braces
+ * and first two digits.
+ *
+ * ASSUMES input buffer is at least 3 wchars big and uppercased UUID in
+ * our matching array.
+ */
+ if (pwszTypeLibId[0] == '{')
+ {
+ RTUTF16 const wcFirstDigit = RT_C_TO_UPPER(pwszTypeLibId[1]);
+ RTUTF16 const wcSecondDigit = RT_C_TO_UPPER(pwszTypeLibId[2]);
+ PCRTUTF16 pwsz2 = g_apwszTypeLibIds[0];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszTypeLibId, pwsz2))
+ return true;
+ pwsz2 = g_apwszTypeLibIds[1];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszTypeLibId, pwsz2))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Checks if the proxy stub class ID is one of the ones we wish to clean up.
+ *
+ * @returns true if it should be cleaned up, false if not.
+ * @param pwszProxyStubId The proxy stub class ID.
+ */
+DECLINLINE(bool) vbpsIsProxyStubClsIdToRemove(PRTUTF16 pwszProxyStubId)
+{
+ AssertCompile(RT_ELEMENTS(g_apwszProxyStubClsIds) == 2);
+#ifdef VBOX_STRICT
+ static bool s_fDoneStrict = false;
+ if (s_fDoneStrict) { }
+ else
+ {
+ Assert(RT_ELEMENTS(g_apwszProxyStubClsIds) == 2);
+ Assert(g_apwszProxyStubClsIds[0][0] == '{');
+ Assert(g_apwszProxyStubClsIds[1][0] == '{');
+ Assert(RT_C_IS_XDIGIT(g_apwszProxyStubClsIds[0][1]));
+ Assert(RT_C_IS_XDIGIT(g_apwszProxyStubClsIds[1][1]));
+ Assert(RT_C_IS_UPPER(g_apwszProxyStubClsIds[0][1]) || RT_C_IS_DIGIT(g_apwszProxyStubClsIds[0][1]));
+ Assert(RT_C_IS_UPPER(g_apwszProxyStubClsIds[1][1]) || RT_C_IS_DIGIT(g_apwszProxyStubClsIds[1][1]));
+ s_fDoneStrict = true;
+ }
+#endif
+
+ /*
+ * Rolled out matching with inlined check of the opening braces
+ * and first two digits.
+ *
+ * ASSUMES input buffer is at least 3 wchars big and uppercased UUID in
+ * our matching array.
+ */
+ if (pwszProxyStubId[0] == '{')
+ {
+ RTUTF16 const wcFirstDigit = RT_C_TO_UPPER(pwszProxyStubId[1]);
+ RTUTF16 const wcSecondDigit = RT_C_TO_UPPER(pwszProxyStubId[2]);
+ PCRTUTF16 pwsz2 = g_apwszProxyStubClsIds[0];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszProxyStubId, pwsz2))
+ return true;
+ pwsz2 = g_apwszProxyStubClsIds[1];
+ if ( wcFirstDigit == pwsz2[1]
+ && wcSecondDigit == pwsz2[2]
+ && vbpsCompareUuidW(pwszProxyStubId, pwsz2))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Hack to clean out the interfaces belonging to obsolete typelibs on
+ * development boxes and such likes.
+ */
+static void vbpsRemoveOldInterfaces(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the interface root key. Not using the vbpsRegOpenInterfaceKeys feature
+ * here in case it messes things up by keeping the special HKEY_CLASSES_ROOT key
+ * open with possibly pending deletes in parent views or other weird stuff.
+ */
+ HKEY hkeyInterfaces;
+ LRESULT lrc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"Interface",
+ 0 /*fOptions*/, pState->fSamDelete, &hkeyInterfaces);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * This is kind of expensive, but we have to check all registered interfaces.
+ * Only use wide APIs to avoid wasting time on string conversion.
+ */
+ DWORD idxKey;
+ for (idxKey = 0;; idxKey++)
+ {
+ RTUTF16 wszCurNm[128 + 48];
+ DWORD cwcCurNm = 128;
+ lrc = RegEnumKeyExW(hkeyInterfaces, idxKey, wszCurNm, &cwcCurNm,
+ NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * We match the interface by type library ID or proxy stub class ID.
+ *
+ * We have to check the proxy ID last, as it is almost always there
+ * and we can safely skip it if there is a mismatching type lib
+ * associated with the interface.
+ */
+ static RTUTF16 const s_wszTypeLib[] = L"\\TypeLib";
+ bool fDeleteMe = false;
+ HKEY hkeySub;
+ RTUTF16 wszValue[128];
+ DWORD cbValue;
+ DWORD dwType;
+
+ /* Skip this entry if it doesn't look like a braced UUID. */
+ wszCurNm[cwcCurNm] = '\0'; /* paranoia */
+ if (vbpsIsUuidInBracesQuickW(wszCurNm)) { }
+ else continue;
+
+ /* Try the TypeLib sub-key. */
+ memcpy(&wszCurNm[cwcCurNm], s_wszTypeLib, sizeof(s_wszTypeLib));
+ lrc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (lrc == ERROR_SUCCESS)
+ {
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ lrc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (lrc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( lrc == ERROR_SUCCESS
+ && vbpsIsTypeLibIdToRemove(wszValue))
+ {
+ /* Check the TypeLib/Version value to make sure. */
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ lrc = RegQueryValueExW(hkeySub, L"Version", 0 /*pdwReserved*/, &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (lrc != ERROR_SUCCESS)
+ cbValue = 0;
+ wszValue[cbValue] = '\0';
+
+ if ( lrc == ERROR_SUCCESS
+ && vbpsIsTypeLibVersionToRemove(wszValue))
+ fDeleteMe = true;
+ }
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ else if (lrc == ERROR_FILE_NOT_FOUND)
+ {
+ /* No TypeLib, try the ProxyStubClsid32 sub-key next. */
+ static RTUTF16 const s_wszProxyStubClsid32[] = L"\\ProxyStubClsid32";
+ memcpy(&wszCurNm[cwcCurNm], s_wszProxyStubClsid32, sizeof(s_wszProxyStubClsid32));
+ lrc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (lrc == ERROR_SUCCESS)
+ {
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ lrc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (lrc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( lrc == ERROR_SUCCESS
+ && vbpsIsProxyStubClsIdToRemove(wszValue))
+ fDeleteMe = true;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ }
+
+ if (fDeleteMe)
+ {
+ /*
+ * Ok, it's an orphaned VirtualBox interface. Delete it.
+ */
+ wszCurNm[cwcCurNm] = '\0';
+ vbpsDeleteKeyRecursiveW(pState, hkeyInterfaces, wszCurNm, __LINE__);
+ }
+ }
+ else
+ {
+ Assert(lrc == ERROR_NO_MORE_ITEMS);
+ break;
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyInterfaces, __LINE__);
+ }
+ }
+}
+
+
+/**
+ * Hack to clean out the class IDs belonging to obsolete typelibs on development
+ * boxes and such likes.
+ */
+static void vbpsRemoveOldClassIDs(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the CLSID key if it exists.
+ * We don't use the hKeyClsid member for the same paranoid reasons as
+ * already stated in vbpsRemoveOldInterfaces.
+ */
+ HKEY hkeyClsIds;
+ LRESULT lrc;
+ lrc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete, &hkeyClsIds);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * This is kind of expensive, but we have to check all registered interfaces.
+ * Only use wide APIs to avoid wasting time on string conversion.
+ */
+ DWORD idxKey;
+ for (idxKey = 0;; idxKey++)
+ {
+ RTUTF16 wszCurNm[128 + 48];
+ DWORD cwcCurNm = 128;
+ lrc = RegEnumKeyExW(hkeyClsIds, idxKey, wszCurNm, &cwcCurNm,
+ NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * Match both the type library ID and the program ID.
+ */
+ static RTUTF16 const s_wszTypeLib[] = L"\\TypeLib";
+ HKEY hkeySub;
+ RTUTF16 wszValue[128];
+ DWORD cbValue;
+ DWORD dwType;
+
+
+ /* Skip this entry if it doesn't look like a braced UUID. (Microsoft
+ has one two malformed ones plus a hack.) */
+ wszCurNm[cwcCurNm] = '\0'; /* paranoia */
+ if (vbpsIsUuidInBracesQuickW(wszCurNm)) { }
+ else continue;
+
+ /* The TypeLib sub-key. */
+ memcpy(&wszCurNm[cwcCurNm], s_wszTypeLib, sizeof(s_wszTypeLib));
+ lrc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (lrc == ERROR_SUCCESS)
+ {
+ bool fDeleteMe = false;
+
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ lrc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (lrc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( lrc == ERROR_SUCCESS
+ && vbpsIsTypeLibIdToRemove(wszValue))
+ fDeleteMe = true;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+
+ if (fDeleteMe)
+ {
+ /* The ProgId sub-key. */
+ static RTUTF16 const s_wszProgId[] = L"\\ProgId";
+ memcpy(&wszCurNm[cwcCurNm], s_wszProgId, sizeof(s_wszProgId));
+ lrc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub);
+ if (lrc == ERROR_SUCCESS)
+ {
+ static RTUTF16 const s_wszProgIdPrefix[] = L"VirtualBox.";
+
+ cbValue = sizeof(wszValue) - sizeof(RTUTF16);
+ lrc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/,
+ &dwType, (PBYTE)&wszValue[0], &cbValue);
+ if (lrc != ERROR_SUCCESS || dwType != REG_SZ)
+ cbValue = 0;
+ wszValue[cbValue / sizeof(RTUTF16)] = '\0';
+
+ if ( cbValue < sizeof(s_wszProgIdPrefix)
+ || memcmp(wszValue, s_wszProgIdPrefix, sizeof(s_wszProgIdPrefix) - sizeof(RTUTF16)) != 0)
+ fDeleteMe = false;
+
+ vbpsCloseKey(pState, hkeySub, __LINE__);
+ }
+ else
+ AssertStmt(lrc == ERROR_FILE_NOT_FOUND, fDeleteMe = false);
+
+ if (fDeleteMe)
+ {
+ /*
+ * Ok, it's an orphaned VirtualBox interface. Delete it.
+ */
+ wszCurNm[cwcCurNm] = '\0';
+ vbpsDeleteKeyRecursiveW(pState, hkeyClsIds, wszCurNm, __LINE__);
+ }
+ }
+ }
+ else
+ Assert(lrc == ERROR_FILE_NOT_FOUND);
+ }
+ else
+ {
+ Assert(lrc == ERROR_NO_MORE_ITEMS);
+ break;
+ }
+ }
+
+ vbpsCloseKey(pState, hkeyClsIds, __LINE__);
+ }
+ else
+ Assert(lrc == ERROR_FILE_NOT_FOUND);
+ }
+}
+
+
+/**
+ * Hack to clean obsolete typelibs on development boxes and such.
+ */
+static void vbpsRemoveOldTypeLibs(VBPSREGSTATE *pState)
+{
+ unsigned iAlt = pState->cAltDeletes;
+ while (iAlt-- > 0)
+ {
+ /*
+ * Open the TypeLib key, if it exists.
+ */
+ HKEY hkeyTypeLibs;
+ LSTATUS lrc;
+ lrc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"TypeLib", 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibs);
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * Look for our type library IDs.
+ */
+ unsigned iTlb = RT_ELEMENTS(g_apwszTypeLibIds);
+ while (iTlb-- > 0)
+ {
+ HKEY hkeyTypeLibId;
+ lrc = RegOpenKeyExW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb], 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibId);
+ if (lrc == ERROR_SUCCESS)
+ {
+ unsigned iVer = RT_ELEMENTS(g_apwszTypelibVersions);
+ while (iVer-- > 0)
+ {
+ HKEY hkeyVer;
+ lrc = RegOpenKeyExW(hkeyTypeLibId, g_apwszTypelibVersions[iVer], 0, KEY_READ, &hkeyVer);
+ if (lrc == ERROR_SUCCESS)
+ {
+ char szValue[128];
+ DWORD cbValue = sizeof(szValue) - 1;
+ lrc = RegQueryValueExA(hkeyVer, NULL, NULL, NULL, (PBYTE)&szValue[0], &cbValue);
+ vbpsCloseKey(pState, hkeyVer, __LINE__);
+ if (lrc == ERROR_SUCCESS)
+ {
+ szValue[cbValue] = '\0';
+ if (!strcmp(szValue, "VirtualBox Type Library"))
+ {
+ /*
+ * Delete the type library version.
+ * We do not delete the whole type library ID, just this version of it.
+ */
+ vbpsDeleteKeyRecursiveW(pState, hkeyTypeLibId, g_apwszTypelibVersions[iVer], __LINE__);
+ }
+ }
+ }
+ }
+ vbpsCloseKey(pState, hkeyTypeLibId, __LINE__);
+
+ /*
+ * The type library ID key should be empty now, so we can try remove it (non-recursively).
+ */
+ lrc = RegDeleteKeyW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb]);
+ Assert(lrc == ERROR_SUCCESS);
+ }
+ }
+ }
+ else
+ Assert(lrc == ERROR_FILE_NOT_FOUND);
+ }
+}
+
+
+/**
+ * Hack to clean out obsolete typelibs on development boxes and such.
+ */
+static void vbpsRemoveOldMessSub(REGSAM fSamWow)
+{
+ /*
+ * Note! The worker procedures does not use the default destination,
+ * because it's much much simpler to enumerate alternative locations.
+ */
+ VBPSREGSTATE State;
+ LRESULT lrc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, true /*fDelete*/, false /*fUpdate*/, fSamWow);
+ if (lrc == ERROR_SUCCESS)
+ {
+ vbpsRegAddAltDelete(&State, HKEY_CURRENT_USER, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_LOCAL_MACHINE, "Software\\Classes");
+ vbpsRegAddAltDelete(&State, HKEY_CLASSES_ROOT, NULL);
+
+ vbpsRemoveOldInterfaces(&State);
+ vbpsRemoveOldClassIDs(&State);
+ vbpsRemoveOldTypeLibs(&State);
+ }
+ vbpsRegTerm(&State);
+}
+
+
+/**
+ * Hack to clean out obsolete typelibs on development boxes and such.
+ */
+static void removeOldMess(void)
+{
+ vbpsRemoveOldMessSub(0 /*fSamWow*/);
+#if ARCH_BITS == 64 || defined(VBOX_IN_32_ON_64_MAIN_API)
+ vbpsRemoveOldMessSub(KEY_WOW64_32KEY);
+#endif
+}
+
+
+
+/**
+ * Register the interfaces proxied by this DLL, and to avoid duplication and
+ * minimize work the VBox type library, classes and servers are also registered.
+ *
+ * This is normally only used by developers via comregister.cmd and the heat.exe
+ * tool during MSI creation. The only situation where users may end up here is
+ * if they're playing around or we recommend it as a solution to COM problems.
+ * So, no problem if this approach is less gentle, though we leave the cleaning
+ * up of orphaned interfaces to DllUnregisterServer.
+ *
+ * @returns COM status code.
+ */
+HRESULT STDAPICALLTYPE DllRegisterServer(void)
+{
+ HRESULT hrc;
+
+ /*
+ * Register the type library first.
+ */
+ ITypeLib *pITypeLib;
+ WCHAR wszDllName[MAX_PATH];
+ DWORD cwcRet = GetModuleFileNameW(g_hDllSelf, wszDllName, RT_ELEMENTS(wszDllName));
+ AssertReturn(cwcRet > 0 && cwcRet < RT_ELEMENTS(wszDllName), CO_E_PATHTOOLONG);
+
+ hrc = LoadTypeLib(wszDllName, &pITypeLib);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+ hrc = RegisterTypeLib(pITypeLib, wszDllName, NULL /*pszHelpDir*/);
+ pITypeLib->lpVtbl->Release(pITypeLib);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ /*
+ * Register proxy stub.
+ */
+ hrc = NdrDllRegisterProxy(g_hDllSelf, &g_apProxyFiles[0], &g_ProxyClsId); /* see DLLREGISTRY_ROUTINES in RpcProxy.h */
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ /*
+ * Register the VBox modules and classes.
+ */
+ vbpsDllPathToVBoxDir(wszDllName);
+ hrc = RegisterXidlModulesAndClasses(wszDllName, true /*fDelete*/, true /*fUpdate*/);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), hrc);
+
+ return S_OK;
+}
+
+
+/**
+ * Reverse of DllRegisterServer.
+ *
+ * This is normally only used by developers via comregister.cmd. Users may be
+ * asked to perform it in order to fix some COM issue. So, it's OK if we spend
+ * some extra time and clean up orphaned interfaces, because developer boxes
+ * will end up with a bunch of those as interface UUIDs changes.
+ *
+ * @returns COM status code.
+ */
+HRESULT STDAPICALLTYPE DllUnregisterServer(void)
+{
+ HRESULT hrc = S_OK;
+ HRESULT hrc2;
+
+ /*
+ * Unregister the type library.
+ *
+ * We ignore TYPE_E_REGISTRYACCESS as that is what is returned if the
+ * type lib hasn't been registered (W10).
+ */
+ hrc2 = UnRegisterTypeLib(&LIBID_VirtualBox, kTypeLibraryMajorVersion, kTypeLibraryMinorVersion,
+ 0 /*LCid*/, RT_CONCAT(SYS_WIN, ARCH_BITS));
+ AssertMsgStmt(SUCCEEDED(hrc2) || hrc2 == TYPE_E_REGISTRYACCESS, ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Unregister the proxy stub.
+ *
+ * We ignore ERROR_FILE_NOT_FOUND as that is returned if not registered (W10).
+ */
+ hrc2 = NdrDllUnregisterProxy(g_hDllSelf, &g_apProxyFiles[0], &g_ProxyClsId); /* see DLLREGISTRY_ROUTINES in RpcProxy.h */
+ AssertMsgStmt( SUCCEEDED(hrc2)
+ || hrc2 == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND)
+ || hrc2 == REGDB_E_INVALIDVALUE,
+ ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Register the VBox modules and classes.
+ */
+ hrc2 = RegisterXidlModulesAndClasses(NULL, true /*fDelete*/, false /*fUpdate*/);
+ AssertMsgStmt(SUCCEEDED(hrc2), ("%Rhrc\n", hrc2), if (SUCCEEDED(hrc)) hrc = hrc2);
+
+ /*
+ * Purge old mess.
+ */
+ removeOldMess();
+
+ return hrc;
+}
+
+
+#ifdef VBOX_WITH_SDS
+/**
+ * Update a SCM service.
+ *
+ * @param pState The state.
+ * @param pwszVBoxDir The VirtualBox install directory (unicode),
+ * trailing slash.
+ * @param pwszModule The service module.
+ * @param pwszServiceName The service name.
+ * @param pwszDisplayName The service display name.
+ * @param pwszDescription The service description.
+ */
+static void vbpsUpdateWindowsService(VBPSREGSTATE *pState, const WCHAR *pwszVBoxDir, const WCHAR *pwszModule,
+ const WCHAR *pwszServiceName, const WCHAR *pwszDisplayName, const WCHAR *pwszDescription)
+{
+ SC_HANDLE hSCM;
+
+ /* Configuration options that are currently standard. */
+ uint32_t const uServiceType = SERVICE_WIN32_OWN_PROCESS;
+ uint32_t const uStartType = SERVICE_DEMAND_START;
+ uint32_t const uErrorControl = SERVICE_ERROR_NORMAL;
+ WCHAR const * const pwszServiceStartName = L"LocalSystem";
+ static WCHAR const wszzDependencies[] = L"RPCSS\0";
+
+ /*
+ * Make double quoted executable file path. ASSUMES pwszVBoxDir ends with a slash!
+ */
+ WCHAR wszFilePath[MAX_PATH + 2];
+ int vrc = RTUtf16CopyAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\"");
+ if (RT_SUCCESS(vrc))
+ vrc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszVBoxDir);
+ if (RT_SUCCESS(vrc))
+ vrc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszModule);
+ if (RT_SUCCESS(vrc))
+ vrc = RTUtf16CatAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\"");
+ AssertLogRelRCReturnVoid(vrc);
+
+ /*
+ * Open the service manager for the purpose of checking the configuration.
+ */
+ hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
+ if (hSCM != NULL)
+ {
+ union
+ {
+ QUERY_SERVICE_CONFIGW Config;
+ SERVICE_STATUS Status;
+ SERVICE_DESCRIPTIONW Desc;
+ uint8_t abPadding[sizeof(QUERY_SERVICE_CONFIGW) + 5 * _1K];
+ } uBuf;
+ SC_HANDLE hService;
+ bool fCreateIt = pState->fUpdate;
+ bool fDeleteIt = true;
+
+ /*
+ * Step #1: Open the service and validate the configuration.
+ */
+ if (pState->fUpdate)
+ {
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ DWORD cbNeeded = 0;
+ if (QueryServiceConfigW(hService, &uBuf.Config, sizeof(uBuf), &cbNeeded))
+ {
+ if (uBuf.Config.dwErrorControl)
+ {
+ uint32_t cErrors = 0;
+ if (uBuf.Config.dwServiceType != uServiceType)
+ {
+ LogRel(("update service '%ls': dwServiceType %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwServiceType, uServiceType));
+ cErrors++;
+ }
+ if (uBuf.Config.dwStartType != uStartType)
+ {
+ LogRel(("update service '%ls': dwStartType %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwStartType, uStartType));
+ cErrors++;
+ }
+ if (uBuf.Config.dwErrorControl != uErrorControl)
+ {
+ LogRel(("update service '%ls': dwErrorControl %u, expected %u\n",
+ pwszServiceName, uBuf.Config.dwErrorControl, uErrorControl));
+ cErrors++;
+ }
+ if (RTUtf16ICmp(uBuf.Config.lpBinaryPathName, wszFilePath) != 0)
+ {
+ LogRel(("update service '%ls': lpBinaryPathName '%ls', expected '%ls'\n",
+ pwszServiceName, uBuf.Config.lpBinaryPathName, wszFilePath));
+ cErrors++;
+ }
+ if ( uBuf.Config.lpServiceStartName != NULL
+ && *uBuf.Config.lpServiceStartName != L'\0'
+ && RTUtf16ICmp(uBuf.Config.lpServiceStartName, pwszServiceStartName) != 0)
+ {
+ LogRel(("update service '%ls': lpServiceStartName '%ls', expected '%ls'\n",
+ pwszServiceName, uBuf.Config.lpBinaryPathName, pwszServiceStartName));
+ cErrors++;
+ }
+
+ fDeleteIt = fCreateIt = cErrors > 0;
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("QueryServiceConfigW returned %u (cbNeeded=%u vs %zu)\n",
+ GetLastError(), cbNeeded, sizeof(uBuf)));
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ fDeleteIt = dwErr != ERROR_SERVICE_DOES_NOT_EXIST;
+ AssertLogRelMsg(dwErr == ERROR_SERVICE_DOES_NOT_EXIST, ("OpenServiceW('%ls') -> %u\n", pwszServiceName, dwErr));
+ }
+ CloseServiceHandle(hService);
+ }
+
+ /*
+ * Step #2: Stop and delete the service if needed.
+ * We can do this without reopening the service manager.
+ */
+ if (fDeleteIt)
+ {
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_STOP | DELETE);
+ if (hService)
+ {
+ BOOL fRet;
+ DWORD dwErr;
+ RT_ZERO(uBuf.Status);
+ SetLastError(ERROR_SERVICE_NOT_ACTIVE);
+ fRet = ControlService(hService, SERVICE_CONTROL_STOP, &uBuf.Status);
+ dwErr = GetLastError();
+ if ( fRet
+ || dwErr == ERROR_SERVICE_NOT_ACTIVE
+ || ( dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL
+ && uBuf.Status.dwCurrentState == SERVICE_STOP_PENDING) )
+ {
+ if (DeleteService(hService))
+ LogRel(("update service '%ls': deleted\n", pwszServiceName));
+ else
+ AssertLogRelMsgFailed(("Failed to not delete service %ls: %u\n", pwszServiceName, GetLastError()));
+ }
+ else
+ AssertMsg(dwErr == ERROR_ACCESS_DENIED,
+ ("Failed to stop service %ls: %u (state=%u)\n", pwszServiceName, dwErr, uBuf.Status.dwCurrentState));
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ pState->lrc = GetLastError();
+ LogRel(("Failed to not open service %ls for stop+delete: %u\n", pwszServiceName, pState->lrc));
+ hService = OpenServiceW(hSCM, pwszServiceName, SERVICE_CHANGE_CONFIG);
+ }
+ CloseServiceHandle(hService);
+ }
+
+ CloseServiceHandle(hSCM);
+
+ /*
+ * Step #3: Create the service (if requested).
+ * Need to have the SC_MANAGER_CREATE_SERVICE access right for this.
+ */
+ if (fCreateIt)
+ {
+ Assert(pState->fUpdate);
+ hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+ if (hSCM)
+ {
+ hService = CreateServiceW(hSCM,
+ pwszServiceName,
+ pwszDisplayName,
+ SERVICE_CHANGE_CONFIG /* dwDesiredAccess */,
+ uServiceType,
+ uStartType,
+ uErrorControl,
+ wszFilePath,
+ NULL /* pwszLoadOrderGroup */,
+ NULL /* pdwTagId */,
+ wszzDependencies,
+ NULL /* pwszServiceStartName */,
+ NULL /* pwszPassword */);
+ if (hService != NULL)
+ {
+ uBuf.Desc.lpDescription = (WCHAR *)pwszDescription;
+ if (ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &uBuf.Desc))
+ LogRel(("update service '%ls': created\n", pwszServiceName));
+ else
+ AssertMsgFailed(("Failed to set service description for %ls: %u\n", pwszServiceName, GetLastError()));
+ CloseServiceHandle(hService);
+ }
+ else
+ {
+ pState->lrc = GetLastError();
+ AssertMsgFailed(("Failed to create service '%ls': %u\n", pwszServiceName, pState->lrc));
+ }
+ CloseServiceHandle(hSCM);
+ }
+ else
+ {
+ pState->lrc = GetLastError();
+ LogRel(("Failed to open service manager with create service access: %u\n", pState->lrc));
+ }
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("OpenSCManagerW failed: %u\n", GetLastError()));
+}
+#endif /* VBOX_WITH_SDS */
+
+
+
+/**
+ * Gently update the COM registrations for VirtualBox.
+ *
+ * API that com::Initialize (VBoxCOM/initterm.cpp) calls the first time COM is
+ * initialized in a process. ASSUMES that the caller has initialized IPRT.
+ *
+ * @returns Windows error code.
+ */
+DECLEXPORT(uint32_t) VbpsUpdateRegistrations(void)
+{
+ LSTATUS lrc;
+ VBPSREGSTATE State;
+#ifdef VBOX_IN_32_ON_64_MAIN_API
+ bool const fIs32On64 = true;
+#else
+ bool const fIs32On64 = false;
+#endif
+
+ /** @todo Should probably skip this when VBoxSVC is already running... Use
+ * some mutex or something for checking. */
+
+ /*
+ * Find the VirtualBox application directory first.
+ */
+ WCHAR wszVBoxDir[MAX_PATH];
+ DWORD cwcRet = GetModuleFileNameW(g_hDllSelf, wszVBoxDir, RT_ELEMENTS(wszVBoxDir));
+ AssertReturn(cwcRet > 0 && cwcRet < RT_ELEMENTS(wszVBoxDir), ERROR_BUFFER_OVERFLOW);
+ vbpsDllPathToVBoxDir(wszVBoxDir);
+
+ /*
+ * Update registry entries for the current CPU bitness.
+ */
+ lrc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/, 0);
+ if (lrc == ERROR_SUCCESS && !vbpsIsUpToDate(&State))
+ {
+
+#ifdef VBOX_WITH_SDS
+ vbpsUpdateWindowsService(&State, wszVBoxDir, L"VBoxSDS.exe", L"VBoxSDS",
+ L"VirtualBox system service", L"Used as a COM server for VirtualBox API.");
+#endif
+ vbpsUpdateTypeLibRegistration(&State, wszVBoxDir, fIs32On64);
+ vbpsUpdateProxyStubRegistration(&State, wszVBoxDir, fIs32On64);
+ vbpsUpdateInterfaceRegistrations(&State);
+ RegisterXidlModulesAndClassesGenerated(&State, wszVBoxDir, fIs32On64);
+ vbpsMarkUpToDate(&State);
+ lrc = State.lrc;
+ }
+ vbpsRegTerm(&State);
+
+
+#if (ARCH_BITS == 64 && defined(VBOX_WITH_32_ON_64_MAIN_API)) /*|| defined(VBOX_IN_32_ON_64_MAIN_API) ??*/
+ /*
+ * Update registry entries for the other CPU bitness.
+ */
+ if (lrc == ERROR_SUCCESS)
+ {
+ lrc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/,
+ !fIs32On64 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY);
+ if (lrc == ERROR_SUCCESS && !vbpsIsUpToDate(&State))
+ {
+ vbpsUpdateTypeLibRegistration(&State, wszVBoxDir, !fIs32On64);
+ vbpsUpdateProxyStubRegistration(&State, wszVBoxDir, !fIs32On64);
+ vbpsUpdateInterfaceRegistrations(&State);
+ RegisterXidlModulesAndClassesGenerated(&State, wszVBoxDir, !fIs32On64);
+ vbpsMarkUpToDate(&State);
+ lrc = State.lrc;
+ }
+ vbpsRegTerm(&State);
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.def b/src/VBox/Main/src-all/win/VBoxProxyStub.def
new file mode 100644
index 00000000..abf6f043
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.def
@@ -0,0 +1,36 @@
+; $Id: VBoxProxyStub.def $
+;; @file
+; VBoxProxyStub 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 VBoxProxyStub.dll
+
+EXPORTS
+ DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ GetProxyDllInfo PRIVATE
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStub.rc b/src/VBox/Main/src-all/win/VBoxProxyStub.rc
new file mode 100644
index 00000000..fd5296ff
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStub.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStub.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * 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 IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Typelib"
+#define IN_FILE_BASENAME "VBoxProxyStub"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc b/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc
new file mode 100644
index 00000000..103178d3
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VBoxProxyStubLegacy.rc
@@ -0,0 +1,34 @@
+/* $Id: VBoxProxyStubLegacy.rc $ */
+/** @file
+ * VBoxProxyStub - Resource file containing version info, icon and typelib.
+ */
+
+/*
+ * 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 IN_FILE_DESCRIPTION "VirtualBox COM Proxy Stub and Type Library (pre Windows 7)"
+#define IN_FILE_BASENAME "VBoxProxyStubLegacy"
+
+#include "../../../Artwork/win/TemplateDll.rc"
+
+1 TYPELIB "VirtualBox.tlb"
+
diff --git a/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl b/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl
new file mode 100644
index 00000000..0856659b
--- /dev/null
+++ b/src/VBox/Main/src-all/win/VirtualBox_rgs.xsl
@@ -0,0 +1,196 @@
+<?xml version="1.0"?>
+
+<!--
+ * A template to generate a RGS resource script that contains
+ * registry definitions necessary to properly register
+ * VirtualBox Main API COM components.
+-->
+<!--
+ Copyright (C) 2007-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
+-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:output method="text"/>
+
+<xsl:strip-space elements="*"/>
+
+<!--
+// parameters
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!-- Name of the application to generate the RGS script for -->
+<xsl:param name="Application"/>
+<!-- Name of the module to generate the RGS script for -->
+<xsl:param name="Module"/>
+
+
+<!--
+// templates
+/////////////////////////////////////////////////////////////////////////////
+-->
+
+<!--
+ * header
+-->
+<xsl:template match="/idl">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * libraries
+-->
+<xsl:template match="library">
+ <xsl:apply-templates/>
+</xsl:template>
+
+
+<!--
+ * applications
+-->
+<xsl:template match="application">
+ <xsl:if test="@name=$Application">
+ <xsl:variable name="context" select="//module[@name=$Module]/@context"/>
+<xsl:text>HKCR
+{
+ NoRemove AppID
+ {
+ ForceRemove {</xsl:text><xsl:value-of select="@uuid"/>} = s '<xsl:value-of select="@name"/><xsl:text> </xsl:text>
+ <xsl:choose>
+ <xsl:when test="$context='LocalService'">
+ <xsl:text>Service</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>Application</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>'
+</xsl:text>
+ <xsl:if test="$context='LocalService'">
+ <xsl:text> {
+ val LocalService = s '</xsl:text><xsl:value-of select="$Module"/><xsl:text>'
+ }
+</xsl:text>
+ </xsl:if>
+ <xsl:text> '</xsl:text><xsl:value-of select="$Module"/>
+ <xsl:choose>
+ <xsl:when test="$context='InprocServer'">
+ <xsl:text>.dll</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>.exe</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>'
+ {
+ val AppID = s '{</xsl:text><xsl:value-of select="//library/application[@name=$Application]/@uuid"/><xsl:text>}'
+ }
+ }
+
+</xsl:text>
+ <xsl:apply-templates select="module[@name=$Module]/class"/>
+<xsl:text>}
+</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+
+<!--
+ * classes
+-->
+<xsl:template match="library//module/class">
+ <xsl:variable name="cname" select="concat(//library/application/@name,'.',@name)"/>
+ <xsl:variable name="desc" select="concat(@name,' Class')"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="concat($cname,'.1')"/> = s '<xsl:value-of select="$desc"/>'
+ {
+ CLSID = s '{<xsl:value-of select="@uuid"/>}'
+ }
+ <xsl:value-of select="$cname"/> = s '<xsl:value-of select="$desc"/>'
+ {
+ CLSID = s '{<xsl:value-of select="@uuid"/>}'
+ CurVer = s '<xsl:value-of select="concat($cname,'.1')"/>'
+ }
+ NoRemove CLSID
+ {
+ ForceRemove {<xsl:value-of select="@uuid"/>} = s '<xsl:value-of select="$desc"/>'
+ {
+ val AppID = s '{<xsl:value-of select="//library/application[@name=$Application]/@uuid"/><xsl:text>}'
+</xsl:text>
+ <xsl:if test="../@context!='LocalService'">
+ <xsl:text> ProgID = s '</xsl:text><xsl:value-of select="concat($cname,'.1')"/><xsl:text>'
+ VersionIndependentProgID = s '</xsl:text><xsl:value-of select="$cname"/><xsl:text>'
+ </xsl:text>
+ <xsl:choose>
+ <xsl:when test="../@context='InprocServer'">InprocServer32</xsl:when>
+ <xsl:when test="../@context='LocalServer'">LocalServer32</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../@name,'::',../@name,': ')"/>
+ <xsl:text>module context </xsl:text>
+ <xsl:value-of select="concat('&quot;',../@context,'&quot;')"/>
+ <xsl:text> is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose><xsl:text> = s '%MODULE%'
+</xsl:text>
+ <xsl:if test="../@context='InprocServer'">
+ <xsl:variable name="tmodel" select="(./@threadingModel | ../@threadingModel)[last()]"/><xsl:text> {
+ val ThreadingModel = s '</xsl:text>
+ <xsl:choose>
+ <xsl:when test="$tmodel='Apartment'">Apartment</xsl:when>
+ <xsl:when test="$tmodel='Free'">Free</xsl:when>
+ <xsl:when test="$tmodel='Both'">Both</xsl:when>
+ <xsl:when test="$tmodel='Neutral'">Neutral</xsl:when>
+ <xsl:when test="$tmodel='Single'">Single</xsl:when>
+ <xsl:when test="$tmodel='Rental'">Rental</xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:value-of select="concat(../../@name,'::',@name,': ')"/>
+ <xsl:text>class (or module) threading model </xsl:text>
+ <xsl:value-of select="concat('&quot;',$tmodel,'&quot;')"/>
+ <xsl:text> is invalid!</xsl:text>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose><xsl:text>'
+ }
+</xsl:text>
+ </xsl:if>
+ <xsl:text> TypeLib = s '{</xsl:text><xsl:value-of select="//library/@uuid"/><xsl:text>}'
+</xsl:text>
+ </xsl:if>
+ <xsl:text> }
+ }
+</xsl:text>
+</xsl:template>
+
+
+<!--
+ * eat everything else not explicitly matched
+-->
+<xsl:template match="*">
+</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/src/VBox/Main/src-all/win/comregister.cmd b/src/VBox/Main/src-all/win/comregister.cmd
new file mode 100644
index 00000000..31cab344
--- /dev/null
+++ b/src/VBox/Main/src-all/win/comregister.cmd
@@ -0,0 +1,212 @@
+@echo off
+REM $Id: comregister.cmd $
+REM
+REM Script to register the VirtualBox COM classes
+REM (both inproc and out-of-process)
+REM
+
+REM
+REM Copyright (C) 2006-2023 Oracle and/or its affiliates.
+REM
+REM This file is part of VirtualBox base platform packages, as
+REM available from https://www.virtualbox.org.
+REM
+REM This program is free software; you can redistribute it and/or
+REM modify it under the terms of the GNU General Public License
+REM as published by the Free Software Foundation, in version 3 of the
+REM License.
+REM
+REM This program is distributed in the hope that it will be useful, but
+REM WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+REM General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with this program; if not, see <https://www.gnu.org/licenses>.
+REM
+REM SPDX-License-Identifier: GPL-3.0-only
+REM
+
+setlocal
+
+REM Check if the current user is an administrator. Otherwise
+REM all the COM registration will fail silently.
+NET FILE 1>NUL 2>NUL & IF ERRORLEVEL 1 (ECHO Must be run as Administrator. Exiting.) & GOTO end
+
+REM
+REM Figure out where the script lives first, so that we can invoke the
+REM correct VBoxSVC and register the right VBoxC.dll.
+REM
+
+REM Determine the current directory.
+set _SCRIPT_CURDIR=%CD%
+for /f "tokens=*" %%d in ('cd') do set _SCRIPT_CURDIR=%%d
+
+REM Determine a correct self - by %0.
+set _SCRIPT_SELF=%0
+if exist "%_SCRIPT_SELF%" goto found_self
+set _SCRIPT_SELF=%_SCRIPT_SELF%.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+
+REM Determine a correct self - by current working directory.
+set _SCRIPT_SELF=%_SCRIPT_CURDIR%\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+
+REM Determine a correct self - by the PATH
+REM This is very verbose because nested for loops didn't work out.
+for /f "tokens=1 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=2 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=3 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=4 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=5 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=6 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=7 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=8 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=9 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=10 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=11 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=12 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=13 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=14 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=15 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=16 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=17 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=18 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=19 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+for /f "tokens=20 delims=;" %%d in ("%PATH%") do set _SCRIPT_SELF=%%d\comregister.cmd
+if exist "%_SCRIPT_SELF%" goto found_self
+echo Warning: Not able to determin the comregister.cmd location.
+set _VBOX_DIR=
+goto register
+
+:found_self
+set _VBOX_DIR=
+cd "%_SCRIPT_SELF%\.."
+for /f "tokens=*" %%d in ('cd') do set _VBOX_DIR=%%d\
+cd "%_SCRIPT_CURDIR%"
+
+REM
+REM Check for 64-bitness.
+REM
+set fIs64BitWindows=0
+if not "%ProgramW6432%x" == "x" set fIs64BitWindows=1
+if exist "%windir\syswow64\kernel32.dll" set fIs64BitWindows=1
+
+REM
+REM Figure out the Windows version as the proxy stub requires 6.0 or later (at least for 64-bit).
+REM
+set WinVer=Version 4.0.1381
+set WinVerMajor=4
+set WinVerMinor=0
+set WinVerBuild=1381
+for /f "tokens=2 delims=[]" %%a in ('ver') do set WinVer=%%a
+for /f "tokens=2,3,4 delims=. " %%a in ("%WinVer%") do (
+ set WinVerMajor=%%a
+ set WinVerMinor=%%b
+ set WinVerBuild=%%c
+)
+REM echo WinVerMajor=%WinVerMajor% WinVerMinor=%WinVerMinor% WinVerBuild=%WinVerBuild% WinVer=%WinVer%
+
+REM
+REM Parse arguments.
+REM
+set fNoProxy=0
+set fUninstallOnly=0
+
+:arg_loop
+if "%1x" == "x" goto arg_done
+
+if "%1" == "-u" goto arg_uninstall
+if "%1" == "--uninstall" goto arg_uninstall
+if "%1" == "--proxy" goto arg_proxy
+if "%1" == "--no-proxy" goto arg_no_proxy
+echo syntax error: Unknown option %1
+echo usage: comregister.cmd [-u,--uninstall] [--no-proxy] [--proxy]
+goto end
+
+:arg_uninstall
+set fUninstallOnly=1
+goto arg_next
+
+:arg_proxy
+set fNoProxy=0
+goto arg_next
+
+:arg_no_proxy
+set fNoProxy=1
+goto arg_next
+
+:arg_next
+shift
+goto arg_loop
+:arg_done
+
+REM
+REM Do the registrations.
+REM
+@if %fIs64BitWindows% == 1 goto register_amd64
+
+:register_x86
+@echo on
+"%_VBOX_DIR%VBoxSVC.exe" /UnregServer
+regsvr32 /s /u "%_VBOX_DIR%VBoxC.dll"
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%VBoxProxyStub.dll"
+@if %fUninstallOnly% == 1 goto end
+"%_VBOX_DIR%VBoxSVC.exe" /RegServer
+"%_VBOX_DIR%VBoxSDS.exe" /RegService
+regsvr32 /s "%_VBOX_DIR%VBoxC.dll"
+@if %fNoProxy% == 1 goto end
+if exist "%_VBOX_DIR%VBoxProxyStub.dll" %windir%\system32\regsvr32 /s "%_VBOX_DIR%VBoxProxyStub.dll"
+@echo off
+goto end
+
+REM Unregister all first, then register them. The order matters here.
+:register_amd64
+if "%WinVerMajor%" == "5" goto register_amd64_legacy
+if not "%WinVerMajor%" == "6" goto register_amd64_not_legacy
+if not "%WinVerMinor%" == "0" goto register_amd64_not_legacy
+:register_amd64_legacy
+set s64BitProxyStub=VBoxProxyStubLegacy.dll
+goto register_amd64_begin
+:register_amd64_not_legacy
+set s64BitProxyStub=VBoxProxyStub.dll
+:register_amd64_begin
+echo s64BitProxyStub=%s64BitProxyStub%
+@echo on
+"%_VBOX_DIR%VBoxSVC.exe" /UnregServer
+"%_VBOX_DIR%VBoxSDS.exe" /UnregService
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%VBoxC.dll"
+%windir%\syswow64\regsvr32 /s /u "%_VBOX_DIR%x86\VBoxClient-x86.dll"
+%windir%\system32\regsvr32 /s /u "%_VBOX_DIR%%s64BitProxyStub%"
+%windir%\syswow64\regsvr32 /s /u "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll"
+if %fUninstallOnly% == 1 goto end
+"%_VBOX_DIR%VBoxSVC.exe" /RegServer
+"%_VBOX_DIR%VBoxSDS.exe" /RegService
+%windir%\system32\regsvr32 /s "%_VBOX_DIR%VBoxC.dll"
+%windir%\syswow64\regsvr32 /s "%_VBOX_DIR%x86\VBoxClient-x86.dll"
+if %fNoProxy% == 1 goto end
+if exist "%_VBOX_DIR%%s64BitProxyStub%" %windir%\system32\regsvr32 /s "%_VBOX_DIR%%s64BitProxyStub%"
+if exist "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll" %windir%\syswow64\regsvr32 /s "%_VBOX_DIR%x86\VBoxProxyStub-x86.dll"
+@echo off
+
+:end
+@endlocal
diff --git a/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h b/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h
new file mode 100644
index 00000000..6f041709
--- /dev/null
+++ b/src/VBox/Main/src-all/xpcom/VBoxAPIWrap-precomp_gcc.h
@@ -0,0 +1,52 @@
+/* $Id: VBoxAPIWrap-precomp_gcc.h $ */
+/** @file
+ * VirtualBox COM - GCC precompiled header for the API wrappers.
+ */
+
+/*
+ * 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/stdarg.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"
+
+#include "VirtualBoxBase.h"
+#include "Wrapper.h"
+
+#ifdef VBOX_WITH_DTRACE_R3_MAIN
+# include "dtrace/VBoxAPI.h"
+#endif
+
+#if defined(Log) || defined(LogIsEnabled)
+# error "Log() from iprt/log.h cannot be defined in the precompiled header!"
+#endif
+