diff options
Diffstat (limited to 'src/VBox/Main/src-all')
41 files changed, 20275 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..6bce2ef9 --- /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-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 +#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..a91b7946 --- /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-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 +#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/CryptoUtils.cpp b/src/VBox/Main/src-all/CryptoUtils.cpp new file mode 100644 index 00000000..3411f3e0 --- /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 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..6323c85e --- /dev/null +++ b/src/VBox/Main/src-all/DisplayPNGUtil.cpp @@ -0,0 +1,233 @@ +/* $Id: DisplayPNGUtil.cpp $ */ +/** @file + * PNG utilities + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN_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..04f0cdbe --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <iprt/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..a75001d3 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <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..85953d30 --- /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-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 + */ + +/** @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.rc())) + return autoCaller.rc(); + + 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.rc())) + return autoCaller.rc(); + + 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.rc())) + return autoCaller.rc(); + + 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..a7298213 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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.rc(); + 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..045f02bf --- /dev/null +++ b/src/VBox/Main/src-all/ExtPackUtil.cpp @@ -0,0 +1,1474 @@ +/* $Id: ExtPackUtil.cpp $ */ +/** @file + * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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/tar.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..6e0b2116 --- /dev/null +++ b/src/VBox/Main/src-all/Global.cpp @@ -0,0 +1,787 @@ +/* $Id: Global.cpp $ */ +/** @file + * VirtualBox COM global definitions + * + * NOTE: This file is part of both VBoxC.dll and VBoxSVC.exe. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "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_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_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..f2cf0493 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "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..d3912989 --- /dev/null +++ b/src/VBox/Main/src-all/HashedPw.cpp @@ -0,0 +1,114 @@ +/* $Id: HashedPw.cpp $ */ +/** @file + * Main - Password Hashing + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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..1a426a2d --- /dev/null +++ b/src/VBox/Main/src-all/Logging.cpp @@ -0,0 +1,33 @@ +/* $Id: Logging.cpp $ */ +/** @file + * VirtualBox Main Logging + */ + +/* + * 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 + */ + + +/* + * 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..c03b38ff --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <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..d81c9651 --- /dev/null +++ b/src/VBox/Main/src-all/NvramStoreImpl.cpp @@ -0,0 +1,1645 @@ +/* $Id: NvramStoreImpl.cpp $ */ +/** @file + * VirtualBox COM NVRAM store class implementation + */ + +/* + * Copyright (C) 2021-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_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.rc())) return adep.rc(); + + 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.rc())) return adep.rc(); + + 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(). + * + * @returns nothing. + * @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 + + +/** + * 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. */ + RTVFS hVfsEfiVarStore; + vrc = RTEfiVarStoreOpenAsVfs(hVfsFileNvram, RTVFSMNT_F_READ_ONLY, 0 /*fVarStoreFlags*/, &hVfsEfiVarStore, + NULL /*pErrInfo*/); + 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\n", pszPath, vrc)); + + 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)); + } + 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.rc()); + + 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.rc()); + + 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.rc(), 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.rc(), VERR_INVALID_STATE); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + return m->mpKeyStore->deleteSecretKey(strKeyId); +} + + +int NvramStore::i_removeAllPasswords() +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), 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.rc())) return adep.rc(); + + 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.rc()); + + 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.rc()); + + 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.rc()); + + 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..cde23d6a --- /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-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_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..a4fc20f6 --- /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-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_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(¤tWeight)); + if (FAILED(hrc)) return hrc; + hrc = SetNextOperation(bstr.raw(), currentWeight); + if (FAILED(hrc)) return hrc; + ++cOp; + } + else + break; + } + + hrc = aProgressOther->COMGETTER(OperationPercent(¤tPercent)); + 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..f1d19aab --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <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..ca2b4396 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <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..6e531c2b --- /dev/null +++ b/src/VBox/Main/src-all/SharedFolderImpl.cpp @@ -0,0 +1,465 @@ +/* $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" +#if !defined(VBOX_COM_INPROC) +# include "VirtualBoxImpl.h" +# include "MachineImpl.h" +#endif +#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), +#if !defined(VBOX_COM_INPROC) + mMachine(NULL), + mVirtualBox(NULL) +#else + mConsole(NULL) +#endif +{ + 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 +///////////////////////////////////////////////////////////////////////////// + +#if !defined(VBOX_COM_INPROC) +/** + * 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 + +#else + +/** + * 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 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(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; +} +#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; + +#if !defined(VBOX_COM_INPROC) + unconst(mMachine) = NULL; + unconst(mVirtualBox) = NULL; +#else + unconst(mConsole) = NULL; +#endif +} + +// 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..94811057 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_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..3fd702e8 --- /dev/null +++ b/src/VBox/Main/src-all/ThreadTask.cpp @@ -0,0 +1,136 @@ +/* $Id: ThreadTask.cpp $ */ +/** @file + * Implementation of ThreadTask + */ + +/* + * Copyright (C) 2015-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <iprt/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..c60fb51e --- /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-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 + */ + +/*#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..72c01782 --- /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-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 + */ + +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..4f85d707 --- /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 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..17bb6a2f --- /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-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 +#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..eb7fbc92 --- /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-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 +#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..34ce27d2 --- /dev/null +++ b/src/VBox/Main/src-all/VirtualBoxTranslator.cpp @@ -0,0 +1,593 @@ +/* $Id: VirtualBoxTranslator.cpp $ */ +/** @file + * VirtualBox Translator class. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..5c5c2f62 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +#include <iprt/cdefs.h> +#include <iprt/win/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..3aceef6e --- /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-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 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..44b9cd84 --- /dev/null +++ b/src/VBox/Main/src-all/win/VBoxProxyStub.c @@ -0,0 +1,2599 @@ +/* $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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN +#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 rc; + + /** 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 rc; + 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->rc = 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. + */ + rc = RegOpenKeyExA(hkeyRoot, pszSubRoot, 0 /*fOptions*/, pState->fSamBoth, &pState->hkeyClassesRootDst); + if (rc == ERROR_SUCCESS) + { + rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"CLSID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyClsidRootDst, NULL /*pdwDisposition*/); + if (rc == 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(rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc); + } + else + AssertLogRelMsgReturn(rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc); + return pState->rc = rc; +} + + +/** + * Terminates the state, closing all open keys. + * + * @param pState The state to clean up. + */ +static void vbpsRegTerm(VBPSREGSTATE *pState) +{ + LSTATUS rc; + if (pState->hkeyClassesRootDst) + { + rc = RegCloseKey(pState->hkeyClassesRootDst); + Assert(rc == ERROR_SUCCESS); + pState->hkeyClassesRootDst = NULL; + } + if (pState->hkeyClsidRootDst) + { + rc = RegCloseKey(pState->hkeyClsidRootDst); + Assert(rc == ERROR_SUCCESS); + pState->hkeyClsidRootDst = NULL; + } + if (pState->hkeyInterfaceRootDst) + { + rc = RegCloseKey(pState->hkeyInterfaceRootDst); + Assert(rc == ERROR_SUCCESS); + pState->hkeyInterfaceRootDst = NULL; + } + + while (pState->cAltDeletes > 0 && pState->cAltDeletes <= RT_ELEMENTS(pState->aAltDeletes)) + { + unsigned i = --pState->cAltDeletes; + if (pState->aAltDeletes[i].hkeyClasses) + { + rc = RegCloseKey(pState->aAltDeletes[i].hkeyClasses); + Assert(rc == ERROR_SUCCESS); + pState->aAltDeletes[i].hkeyClasses = NULL; + } + if (pState->aAltDeletes[i].hkeyClsid) + { + rc = RegCloseKey(pState->aAltDeletes[i].hkeyClsid); + Assert(rc == ERROR_SUCCESS); + pState->aAltDeletes[i].hkeyClsid = NULL; + } + if (pState->aAltDeletes[i].hkeyInterface) + { + rc = RegCloseKey(pState->aAltDeletes[i].hkeyInterface); + Assert(rc == 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 rc; + + /* 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->rc = ERROR_TOO_MANY_NAMES); + + + /* Open the root. */ + rc = RegOpenKeyExA(hkeyAltRoot, pszAltSubRoot, 0 /*fOptions*/, pState->fSamDelete, + &pState->aAltDeletes[i].hkeyClasses); + if (rc == ERROR_SUCCESS) + { + /* Try open the CLSID subkey, it's fine if it doesn't exists. */ + rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete, + &pState->aAltDeletes[i].hkeyClsid); + if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND) + { + if (rc == ERROR_FILE_NOT_FOUND) + pState->aAltDeletes[i].hkeyClsid = NULL; + pState->cAltDeletes = i + 1; + return ERROR_SUCCESS; + } + AssertLogRelMsgFailed(("%u\n", rc)); + RegCloseKey(pState->aAltDeletes[i].hkeyClasses); + } + /* No need to add non-existing alternative roots, nothing to delete in the void. */ + else if (rc == ERROR_FILE_NOT_FOUND) + rc = ERROR_SUCCESS; + else + { + AssertLogRelMsgFailed(("%u (%#x %s)\n", rc)); + pState->rc = rc; + } + + pState->aAltDeletes[i].hkeyClasses = NULL; + pState->aAltDeletes[i].hkeyClsid = NULL; + return rc; +} + + +/** + * 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 rc; + + /* + * Under the root destination. + */ + if (pState->hkeyInterfaceRootDst == NULL) + { + if (pState->fSamUpdate) + rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + pState->fSamBoth, NULL /*pSecAttr*/, &pState->hkeyInterfaceRootDst, NULL /*pdwDisposition*/); + else + rc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"Interface", 0 /*fOptions*/, pState->fSamBoth, + &pState->hkeyClsidRootDst); + if (rc == ERROR_ACCESS_DENIED) + { + pState->hkeyInterfaceRootDst = NULL; + return pState->rc = rc; + } + AssertLogRelMsgReturnStmt(rc == ERROR_SUCCESS, ("%u\n", rc), pState->hkeyInterfaceRootDst = NULL, pState->rc = rc); + } + + /* + * Under the alternative delete locations. + */ + i = pState->cAltDeletes; + while (i-- > 0) + if (pState->aAltDeletes[i].hkeyInterface == NULL) + { + rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"Interface", 0 /*fOptions*/, pState->fSamDelete, + &pState->aAltDeletes[i].hkeyInterface); + if (rc != ERROR_SUCCESS) + { + AssertMsgStmt(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_ACCESS_DENIED, ("%u\n", rc), pState->rc = rc); + 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 rc; + 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; + rc = RegQueryValueExW(hkey, pwszValueNm, 0 /*Reserved*/, &dwExistingType, (BYTE *)pwszExistingData, &cbExistingData); + if (rc == 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(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_MORE_DATA); + } + + /* + * Set the value. + */ + rc = RegSetValueExW(hkey, pwszValueNm, 0 /*Reserved*/, REG_SZ, (const BYTE *)pwszValue, cbValue); + if (rc == 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(rc == ERROR_ACCESS_DENIED), + ("%d: '%ls'='%ls' -> %u\n", uLine, pwszValueNm, pwszValue, rc)); + pState->rc = rc; + return rc; +} + + +/** + * 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 rc; + 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; + rc = RegQueryValueExA(hkey, pszValueNm, 0 /*Reserved*/, &dwExistingType, (PBYTE)pszExistingData, &cbExistingData); + if (rc == 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(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_MORE_DATA); + } + + /* + * Set the value. + */ + rc = RegSetValueExA(hkey, pszValueNm, 0 /*Reserved*/, REG_SZ, (PBYTE)pszValue, cbValue); + if (rc == 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(rc == ERROR_ACCESS_DENIED), + ("%d: '%s'='%s' -> %u\n", uLine, pszValueNm, pszValue, rc)); + pState->rc = rc; + return rc; +} + + +/** + * 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 rc = RegCloseKey(hkey); + if (rc == ERROR_SUCCESS) + return ERROR_SUCCESS; + + AssertLogRelMsgFailed(("%d: close key -> %u\n", uLine, rc)); + pState->rc = rc; + return rc; +} + + +/** + * 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 rc = RegCreateKeyExA(hkeyParent, pszKey, 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + pState->fSamBoth, NULL /*pSecAttr*/, &hNewKey, &dwDisposition); + if (rc == 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(rc == ERROR_ACCESS_DENIED), + ("%d: create key '%s' -> %u\n", uLine, pszKey, rc)); + pState->rc = rc; + *phkey = NULL; + } + return rc; +} + + +/** + * 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 rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine); + if (rc == ERROR_SUCCESS) + { + rc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine); + vbpsCloseKey(pState, hNewKey, uLine); + } + else + { + AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED), + ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, rc)); + pState->rc = rc; + } + return rc; +} + + +/** + * 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 rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine); + if (rc == ERROR_SUCCESS) + { + rc = vbpsSetRegValueWW(pState, hNewKey, NULL /*pwszValueNm*/, pwszValue, uLine); + vbpsCloseKey(pState, hNewKey, uLine); + } + else + { + AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED), + ("%d: create key '%s'(/Default='%ls') -> %u\n", uLine, pszKey, pwszValue, rc)); + pState->rc = rc; + } + return rc; +} + + +/** + * 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 rc = vbpsCreateRegKeyA(pState, hkeyParent, pszKey, &hNewKey, uLine); + if (rc == ERROR_SUCCESS) + { + rc = vbpsSetRegValueAA(pState, hNewKey, NULL /*pszValueNm*/, pszValue, uLine); + *phkey = hNewKey; + } + else + { + AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED), + ("%d: create key '%s'(/Default='%s') -> %u\n", uLine, pszKey, pszValue, rc)); + pState->rc = rc; + *phkey = NULL; + } + return rc; +} + + +/** + * 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 rc; + + Assert(pState->fDelete); + Assert(pszKey); + AssertReturn(*pszKey != '\0', pState->rc = ERROR_INVALID_PARAMETER); + +#ifdef VBSP_LOG_ENABLED + { + HKEY hkeyLog; + rc = RegOpenKeyExA(hkeyParent, pszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog); + if (rc != ERROR_FILE_NOT_FOUND) + VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveA: %ls/%s (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pszKey, uLine)); + if (rc == ERROR_SUCCESS) + RegCloseKey(hkeyLog); + } +#endif + + rc = SHDeleteKeyA(hkeyParent, pszKey); + if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND) + return ERROR_SUCCESS; + + AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED), + ("%d: delete key '%s' -> %u\n", uLine, pszKey, rc)); + pState->rc = rc; + return rc; +} + + +/** + * 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 rc; + + Assert(pState->fDelete); + Assert(pwszKey); + AssertReturn(*pwszKey != '\0', pState->rc = ERROR_INVALID_PARAMETER); + +#ifdef VBSP_LOG_ENABLED + { + HKEY hkeyLog; + rc = RegOpenKeyExW(hkeyParent, pwszKey, 0 /*fOptions*/, pState->fSamDelete, &hkeyLog); + if (rc != ERROR_FILE_NOT_FOUND) + VBSP_LOG_DEL_KEY(("vbpsDeleteKeyRecursiveW: %ls/%ls (at %d)\n", vbpsDebugKeyToWSZ(hkeyParent), pwszKey, uLine)); + if (rc == ERROR_SUCCESS) + RegCloseKey(hkeyLog); + } +#endif + + rc = SHDeleteKeyW(hkeyParent, pwszKey); + if (rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND) + return ERROR_SUCCESS; + + AssertLogRelMsg(VBPS_LOGREL_NO_ASSERT(rc == ERROR_ACCESS_DENIED), + ("%d: delete key '%ls' -> %u\n", uLine, pwszKey, rc)); + pState->rc = rc; + return rc; +} + + +/** + * 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 rc; + HKEY hkeyAppIds; + Assert(*pszAppId == '{'); + + /* + * Delete. + */ + if (pState->fDelete) + { + unsigned i = pState->cAltDeletes; + while (i-- > 0) + { + rc = RegOpenKeyExW(pState->aAltDeletes[i].hkeyClasses, L"AppID", 0 /*fOptions*/, pState->fSamDelete, &hkeyAppIds); + AssertLogRelMsgStmt(rc == ERROR_SUCCESS || rc == ERROR_FILE_NOT_FOUND, ("%u\n", rc), pState->rc = rc); + if (rc == ERROR_SUCCESS) + { + vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__); + vbpsCloseKey(pState, hkeyAppIds, __LINE__); + } + } + } + + if (pState->fUpdate) + { + rc = RegCreateKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + pState->fSamBoth, NULL /*pSecAttr*/, &hkeyAppIds, NULL /*pdwDisposition*/); + if (rc == ERROR_ACCESS_DENIED) + return ERROR_SUCCESS; + } + else + { + rc = RegOpenKeyExW(pState->hkeyClassesRootDst, L"AppID", 0 /*fOptions*/, pState->fSamBoth, &hkeyAppIds); + if (rc == ERROR_FILE_NOT_FOUND || rc == ERROR_ACCESS_DENIED) + return ERROR_SUCCESS; + } + if (rc == ERROR_ACCESS_DENIED) + return pState->rc = rc; + AssertLogRelMsgReturn(rc == ERROR_SUCCESS, ("%u\n", rc), pState->rc = rc); + + if (pState->fDelete) + { + vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszAppId, __LINE__); + vbpsDeleteKeyRecursiveA(pState, hkeyAppIds, pszModuleName, __LINE__); + } + + /* + * Register / update. + */ + if (pState->fUpdate) + { + HKEY hkey; + rc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszAppId, &hkey, __LINE__); + if (rc == ERROR_SUCCESS) + { + vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, pszDescription, __LINE__); + if (pszServiceName) + vbpsSetRegValueAA(pState, hkey, "LocalService", pszServiceName, __LINE__); + vbpsCloseKey(pState, hkey, __LINE__); + } + + rc = vbpsCreateRegKeyA(pState, hkeyAppIds, pszModuleName, &hkey, __LINE__); + if (rc == ERROR_SUCCESS) + { + vbpsSetRegValueAA(pState, hkey, NULL /*pszValueNm*/, "", __LINE__); + vbpsSetRegValueAA(pState, hkey, "AppID", pszAppId, __LINE__); + vbpsCloseKey(pState, hkey, __LINE__); + } + } + + vbpsCloseKey(pState, hkeyAppIds, __LINE__); + + return pState->rc; +} + + +/** + * 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 rc; + + /* + * 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; + rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClassesRootDst, pszClassName, pszDescription, + &hkeyClass, __LINE__); + if (rc == 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]; + rc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurVerSuffIfRootName); + AssertStmt(RT_SUCCESS(rc), pState->rc = rc = ERROR_INVALID_DATA); + if (rc == ERROR_SUCCESS) + vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyClass, "CurVer", szCurClassNameVer, __LINE__); + } + + vbpsCloseKey(pState, hkeyClass, __LINE__); + } + } + + return pState->rc; +} + + +/** + * 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 rc; + 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; + rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyClsidRootDst, szClsId, pszDescription, + &hkeyClass, __LINE__); + if (rc == ERROR_SUCCESS) + { + bool const fIsLocalServer32 = strcmp(pszServerType, "LocalServer32") == 0; + HKEY hkeyServerType; + char szCurClassNameVer[128]; + + /* pszServerType/Default = module. */ + rc = vbpsCreateRegKeyA(pState, hkeyClass, pszServerType, &hkeyServerType, __LINE__); + if (rc == ERROR_SUCCESS) + { + RTUTF16 wszModule[MAX_PATH * 2]; + PRTUTF16 pwszCur = wszModule; + if (fIsLocalServer32) + *pwszCur++ = '"'; + + rc = RTUtf16Copy(pwszCur, MAX_PATH, pwszVBoxDir); AssertRC(rc); + pwszCur += RTUtf16Len(pwszCur); + rc = RTUtf16CopyAscii(pwszCur, MAX_PATH - 3, pszServerSubPath); AssertRC(rc); + 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) + { + rc = RTStrCopy(szCurClassNameVer, sizeof(szCurClassNameVer), pszClassName); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szCurClassNameVer, sizeof(szCurClassNameVer), pszCurClassNameVerSuffix); + AssertStmt(RT_SUCCESS(rc), pState->rc = rc = ERROR_INVALID_DATA); + if (rc == 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->rc; +} + + +/** + * Register modules and classes from the VirtualBox.xidl file. + * + * @returns COM status code. + * @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 rc; + RT_NOREF(fIs32On64); + + Assert(pState->fUpdate && !pState->fDelete); + + /* + * Type library registration (w/o interfaces). + */ + + /* Open Classes/TypeLib/. */ + rc = vbpsCreateRegKeyA(pState, pState->hkeyClassesRootDst, "TypeLib", &hkeyTypeLibs, __LINE__); + if (rc != ERROR_SUCCESS) + return; + + /* Create TypeLib/{UUID}. */ + rc = vbpsCreateRegKeyA(pState, hkeyTypeLibs, vbpsFormatUuidInCurly(szTypeLibId, &LIBID_VirtualBox), &hkeyTypeLibId, __LINE__); + if (rc == ERROR_SUCCESS) + { + /* {UUID}/Major.Minor/Default = pszDescription. */ + HKEY hkeyMajMin; + char szMajMin[64]; + sprintf(szMajMin, "%u.%u", kTypeLibraryMajorVersion, kTypeLibraryMinorVersion); + rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyTypeLibId, szMajMin, pszDescription, &hkeyMajMin, __LINE__); + if (rc == ERROR_SUCCESS) + { + RTUTF16 wszBuf[MAX_PATH * 2]; + + /* {UUID}/Major.Minor/0. */ + HKEY hkey0; + rc = vbpsCreateRegKeyA(pState, hkeyMajMin, "0", &hkey0, __LINE__); + if (rc == ERROR_SUCCESS) + { + /* {UUID}/Major.Minor/0/winXX/Default = VBoxProxyStub. */ + rc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(rc); + rc = RTUtf16CatAscii(wszBuf, MAX_PATH * 2, pszTypeLibDll); AssertRC(rc); + + vbpsCreateRegKeyWithDefaultValueAW(pState, hkey0, pszWinXx, wszBuf, __LINE__); + vbpsCloseKey(pState, hkey0, __LINE__); + } + + /* {UUID}/Major.Minor/FLAGS */ + vbpsCreateRegKeyWithDefaultValueAA(pState, hkeyMajMin, "FLAGS", "0", __LINE__); + + /* {UUID}/Major.Minor/HELPDIR */ + rc = RTUtf16Copy(wszBuf, MAX_PATH, pwszVBoxDir); AssertRC(rc); +#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 rc; + 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); + rc = vbpsRegOpenInterfaceKeys(pState); + if (rc != 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->rc = ERROR_INVALID_DATA); + + AssertReturnVoidStmt(cMethods >= 3 && cMethods < 1024, pState->rc = ERROR_INVALID_DATA); + sprintf(szMethods, "%u", cMethods); + + rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, pState->hkeyInterfaceRootDst, + vbpsFormatUuidInCurly(szIfId, papStubVtbls[iIf]->header.piid), + pszIfNm, &hkeyIfId, __LINE__); + if (rc == 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. */ + rc = vbpsCreateRegKeyWithDefaultValueAAEx(pState, hkeyIfId, "TypeLib", szTypeLibId, &hkeyTypeLib, __LINE__); + if (rc == 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 rc; + + /* + * Do registration for the current execution mode of the DLL. + */ + rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL /* Alt: HKEY_LOCAL_MACHINE, "Software\\Classes", */, fDelete, fUpdate, 0); + if (rc == 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); + rc = State.rc; + } + vbpsRegTerm(&State); + + /* + * Translate error code? Return. + */ + if (rc == 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 rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"Interface", + 0 /*fOptions*/, pState->fSamDelete, &hkeyInterfaces); + if (rc == 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; + rc = RegEnumKeyExW(hkeyInterfaces, idxKey, wszCurNm, &cwcCurNm, + NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/); + if (rc == 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)); + rc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub); + if (rc == ERROR_SUCCESS) + { + cbValue = sizeof(wszValue) - sizeof(RTUTF16); + rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/, + &dwType, (PBYTE)&wszValue[0], &cbValue); + if (rc != ERROR_SUCCESS || dwType != REG_SZ) + cbValue = 0; + wszValue[cbValue / sizeof(RTUTF16)] = '\0'; + + if ( rc == ERROR_SUCCESS + && vbpsIsTypeLibIdToRemove(wszValue)) + { + /* Check the TypeLib/Version value to make sure. */ + cbValue = sizeof(wszValue) - sizeof(RTUTF16); + rc = RegQueryValueExW(hkeySub, L"Version", 0 /*pdwReserved*/, &dwType, (PBYTE)&wszValue[0], &cbValue); + if (rc != ERROR_SUCCESS) + cbValue = 0; + wszValue[cbValue] = '\0'; + + if ( rc == ERROR_SUCCESS + && vbpsIsTypeLibVersionToRemove(wszValue)) + fDeleteMe = true; + } + vbpsCloseKey(pState, hkeySub, __LINE__); + } + else if (rc == 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)); + rc = RegOpenKeyExW(hkeyInterfaces, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub); + if (rc == ERROR_SUCCESS) + { + cbValue = sizeof(wszValue) - sizeof(RTUTF16); + rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/, + &dwType, (PBYTE)&wszValue[0], &cbValue); + if (rc != ERROR_SUCCESS || dwType != REG_SZ) + cbValue = 0; + wszValue[cbValue / sizeof(RTUTF16)] = '\0'; + + if ( rc == 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(rc == 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 rc; + rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"CLSID", 0 /*fOptions*/, pState->fSamDelete, &hkeyClsIds); + if (rc == 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; + rc = RegEnumKeyExW(hkeyClsIds, idxKey, wszCurNm, &cwcCurNm, + NULL /*pdwReserved*/, NULL /*pwszClass*/, NULL /*pcwcClass*/, NULL /*pLastWriteTime*/); + if (rc == 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)); + rc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub); + if (rc == ERROR_SUCCESS) + { + bool fDeleteMe = false; + + cbValue = sizeof(wszValue) - sizeof(RTUTF16); + rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/, + &dwType, (PBYTE)&wszValue[0], &cbValue); + if (rc != ERROR_SUCCESS || dwType != REG_SZ) + cbValue = 0; + wszValue[cbValue / sizeof(RTUTF16)] = '\0'; + + if ( rc == 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)); + rc = RegOpenKeyExW(hkeyClsIds, wszCurNm, 0 /*fOptions*/, KEY_QUERY_VALUE, &hkeySub); + if (rc == ERROR_SUCCESS) + { + static RTUTF16 const s_wszProgIdPrefix[] = L"VirtualBox."; + + cbValue = sizeof(wszValue) - sizeof(RTUTF16); + rc = RegQueryValueExW(hkeySub, NULL /*pszValueNm*/, NULL /*pdwReserved*/, + &dwType, (PBYTE)&wszValue[0], &cbValue); + if (rc != 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(rc == 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(rc == ERROR_FILE_NOT_FOUND); + } + else + { + Assert(rc == ERROR_NO_MORE_ITEMS); + break; + } + } + + vbpsCloseKey(pState, hkeyClsIds, __LINE__); + } + else + Assert(rc == 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 rc; + rc = RegOpenKeyExW(pState->aAltDeletes[iAlt].hkeyClasses, L"TypeLib", 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibs); + if (rc == ERROR_SUCCESS) + { + /* + * Look for our type library IDs. + */ + unsigned iTlb = RT_ELEMENTS(g_apwszTypeLibIds); + while (iTlb-- > 0) + { + HKEY hkeyTypeLibId; + rc = RegOpenKeyExW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb], 0 /*fOptions*/, pState->fSamDelete, &hkeyTypeLibId); + if (rc == ERROR_SUCCESS) + { + unsigned iVer = RT_ELEMENTS(g_apwszTypelibVersions); + while (iVer-- > 0) + { + HKEY hkeyVer; + rc = RegOpenKeyExW(hkeyTypeLibId, g_apwszTypelibVersions[iVer], 0, KEY_READ, &hkeyVer); + if (rc == ERROR_SUCCESS) + { + char szValue[128]; + DWORD cbValue = sizeof(szValue) - 1; + rc = RegQueryValueExA(hkeyVer, NULL, NULL, NULL, (PBYTE)&szValue[0], &cbValue); + vbpsCloseKey(pState, hkeyVer, __LINE__); + if (rc == 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). + */ + rc = RegDeleteKeyW(hkeyTypeLibs, g_apwszTypeLibIds[iTlb]); + Assert(rc == ERROR_SUCCESS); + } + } + } + else + Assert(rc == 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 rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, true /*fDelete*/, false /*fUpdate*/, fSamWow); + if (rc == 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 rc = RTUtf16CopyAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\""); + if (RT_SUCCESS(rc)) + rc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszVBoxDir); + if (RT_SUCCESS(rc)) + rc = RTUtf16Cat(wszFilePath, RT_ELEMENTS(wszFilePath), pwszModule); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszFilePath, RT_ELEMENTS(wszFilePath), "\""); + AssertLogRelRCReturnVoid(rc); + + /* + * 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->rc = GetLastError(); + LogRel(("Failed to not open service %ls for stop+delete: %u\n", pwszServiceName, pState->rc)); + 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->rc = GetLastError(); + AssertMsgFailed(("Failed to create service '%ls': %u\n", pwszServiceName, pState->rc)); + } + CloseServiceHandle(hSCM); + } + else + { + pState->rc = GetLastError(); + LogRel(("Failed to open service manager with create service access: %u\n", pState->rc)); + } + } + } + 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 rc; + 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. + */ + rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/, 0); + if (rc == 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); + rc = State.rc; + } + 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 (rc == ERROR_SUCCESS) + { + rc = vbpsRegInit(&State, HKEY_CLASSES_ROOT, NULL, false /*fDelete*/, true /*fUpdate*/, + !fIs32On64 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY); + if (rc == ERROR_SUCCESS && !vbpsIsUpToDate(&State)) + { + vbpsUpdateTypeLibRegistration(&State, wszVBoxDir, !fIs32On64); + vbpsUpdateProxyStubRegistration(&State, wszVBoxDir, !fIs32On64); + vbpsUpdateInterfaceRegistrations(&State); + RegisterXidlModulesAndClassesGenerated(&State, wszVBoxDir, !fIs32On64); + vbpsMarkUpToDate(&State); + rc = State.rc; + } + 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..f21ac77d --- /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-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; SPDX-License-Identifier: GPL-3.0-only +; + +LIBRARY 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..81e0d5da --- /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-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 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..34815f80 --- /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-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 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..96b95f41 --- /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-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 +--> + +<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('"',../@context,'"')"/> + <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('"',$tmodel,'"')"/> + <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..17340f77 --- /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-2022 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..c3460917 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <iprt/cdefs.h> +#include <VBox/cdefs.h> +#include <iprt/types.h> +#include <iprt/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 + |