diff options
Diffstat (limited to 'src/VBox/Main/glue/initterm.cpp')
-rw-r--r-- | src/VBox/Main/glue/initterm.cpp | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/src/VBox/Main/glue/initterm.cpp b/src/VBox/Main/glue/initterm.cpp new file mode 100644 index 00000000..e2ca0ada --- /dev/null +++ b/src/VBox/Main/glue/initterm.cpp @@ -0,0 +1,641 @@ +/* $Id: initterm.cpp $ */ +/** @file + * MS COM / XPCOM Abstraction Layer - Initialization and Termination. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN +#if !defined(VBOX_WITH_XPCOM) + +# include <iprt/nt/nt-and-windows.h> +# include <iprt/win/objbase.h> + +#else /* !defined(VBOX_WITH_XPCOM) */ + +# include <stdlib.h> + +# include <nsIComponentRegistrar.h> +# include <nsIServiceManager.h> +# include <nsCOMPtr.h> +# include <nsEventQueueUtils.h> +# include <nsEmbedString.h> + +# include <nsILocalFile.h> +# include <nsIDirectoryService.h> +# include <nsDirectoryServiceDefs.h> + +#endif /* !defined(VBOX_WITH_XPCOM) */ + +#include "VBox/com/com.h" +#include "VBox/com/assert.h" +#include "VBox/com/NativeEventQueue.h" +#include "VBox/com/AutoLock.h" + +#include "../include/LoggingNew.h" + +#include <iprt/asm.h> +#include <iprt/env.h> +#include <iprt/ldr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include <VBox/err.h> + +namespace com +{ + +#if defined(VBOX_WITH_XPCOM) + +class DirectoryServiceProvider : public nsIDirectoryServiceProvider +{ +public: + + NS_DECL_ISUPPORTS + + DirectoryServiceProvider() + : mCompRegLocation(NULL), mXPTIDatLocation(NULL) + , mComponentDirLocation(NULL), mCurrProcDirLocation(NULL) + {} + + virtual ~DirectoryServiceProvider(); + + HRESULT init(const char *aCompRegLocation, + const char *aXPTIDatLocation, + const char *aComponentDirLocation, + const char *aCurrProcDirLocation); + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + +private: + /** @remarks This is not a UTF-8 string. */ + char *mCompRegLocation; + /** @remarks This is not a UTF-8 string. */ + char *mXPTIDatLocation; + /** @remarks This is not a UTF-8 string. */ + char *mComponentDirLocation; + /** @remarks This is not a UTF-8 string. */ + char *mCurrProcDirLocation; +}; + +NS_IMPL_ISUPPORTS1(DirectoryServiceProvider, nsIDirectoryServiceProvider) + +DirectoryServiceProvider::~DirectoryServiceProvider() +{ + if (mCompRegLocation) + { + RTStrFree(mCompRegLocation); + mCompRegLocation = NULL; + } + if (mXPTIDatLocation) + { + RTStrFree(mXPTIDatLocation); + mXPTIDatLocation = NULL; + } + if (mComponentDirLocation) + { + RTStrFree(mComponentDirLocation); + mComponentDirLocation = NULL; + } + if (mCurrProcDirLocation) + { + RTStrFree(mCurrProcDirLocation); + mCurrProcDirLocation = NULL; + } +} + +/** + * @param aCompRegLocation Path to compreg.dat, in Utf8. + * @param aXPTIDatLocation Path to xpti.data, in Utf8. + */ +HRESULT +DirectoryServiceProvider::init(const char *aCompRegLocation, + const char *aXPTIDatLocation, + const char *aComponentDirLocation, + const char *aCurrProcDirLocation) +{ + AssertReturn(aCompRegLocation, NS_ERROR_INVALID_ARG); + AssertReturn(aXPTIDatLocation, NS_ERROR_INVALID_ARG); + +/** @todo r=bird: Gotta check how this encoding stuff plays out on darwin! + * We get down to [VBoxNsxp]NS_NewNativeLocalFile and that file isn't + * nsLocalFileUnix.cpp on 32-bit darwin. On 64-bit darwin it's a question + * of what we're doing in IPRT and such... We should probably add a + * RTPathConvertToNative for use here. */ + int vrc = RTStrUtf8ToCurrentCP(&mCompRegLocation, aCompRegLocation); + if (RT_SUCCESS(vrc)) + vrc = RTStrUtf8ToCurrentCP(&mXPTIDatLocation, aXPTIDatLocation); + if (RT_SUCCESS(vrc) && aComponentDirLocation) + vrc = RTStrUtf8ToCurrentCP(&mComponentDirLocation, aComponentDirLocation); + if (RT_SUCCESS(vrc) && aCurrProcDirLocation) + vrc = RTStrUtf8ToCurrentCP(&mCurrProcDirLocation, aCurrProcDirLocation); + + return RT_SUCCESS(vrc) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +DirectoryServiceProvider::GetFile(const char *aProp, + PRBool *aPersistent, + nsIFile **aRetval) +{ + nsCOMPtr <nsILocalFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aRetval = nsnull; + *aPersistent = PR_TRUE; + + const char *fileLocation = NULL; + + if (strcmp(aProp, NS_XPCOM_COMPONENT_REGISTRY_FILE) == 0) + fileLocation = mCompRegLocation; + else if (strcmp(aProp, NS_XPCOM_XPTI_REGISTRY_FILE) == 0) + fileLocation = mXPTIDatLocation; + else if (mComponentDirLocation && strcmp(aProp, NS_XPCOM_COMPONENT_DIR) == 0) + fileLocation = mComponentDirLocation; + else if (mCurrProcDirLocation && strcmp(aProp, NS_XPCOM_CURRENT_PROCESS_DIR) == 0) + fileLocation = mCurrProcDirLocation; + else + return NS_ERROR_FAILURE; + + rv = NS_NewNativeLocalFile(nsEmbedCString(fileLocation), + PR_TRUE, getter_AddRefs(localFile)); + if (NS_FAILED(rv)) + return rv; + + return localFile->QueryInterface(NS_GET_IID(nsIFile), (void **)aRetval); +} + +/** + * Global XPCOM initialization flag (we maintain it ourselves since XPCOM + * doesn't provide such functionality) + */ +static bool volatile gIsXPCOMInitialized = false; + +/** + * Number of Initialize() calls on the main thread. + */ +static unsigned int gXPCOMInitCount = 0; + +#else /* !defined(VBOX_WITH_XPCOM) */ + +/** + * The COM main thread handle. (The first caller of com::Initialize().) + */ +static RTTHREAD volatile gCOMMainThread = NIL_RTTHREAD; + +/** + * Number of Initialize() calls on the main thread. + */ +static uint32_t gCOMMainInitCount = 0; + +#endif /* !defined(VBOX_WITH_XPCOM) */ + + +/** + * Initializes the COM runtime. + * + * This method must be called on each thread of the client application that + * wants to access COM facilities. The initialization must be performed before + * calling any other COM method or attempting to instantiate COM objects. + * + * On platforms using XPCOM, this method uses the following scheme to search for + * XPCOM runtime: + * + * 1. If the VBOX_APP_HOME environment variable is set, the path it specifies + * is used to search XPCOM libraries and components. If this method fails to + * initialize XPCOM runtime using this path, it will immediately return a + * failure and will NOT check for other paths as described below. + * + * 2. If VBOX_APP_HOME is not set, this methods tries the following paths in the + * given order: + * + * a) Compiled-in application data directory (as returned by + * RTPathAppPrivateArch()) + * b) "/usr/lib/virtualbox" (Linux only) + * c) "/opt/VirtualBox" (Linux only) + * + * The first path for which the initialization succeeds will be used. + * + * On MS COM platforms, the COM runtime is provided by the system and does not + * need to be searched for. + * + * Once the COM subsystem is no longer necessary on a given thread, Shutdown() + * must be called to free resources allocated for it. Note that a thread may + * call Initialize() several times but for each of tese calls there must be a + * corresponding Shutdown() call. + * + * @return S_OK on success and a COM result code in case of failure. + */ +HRESULT Initialize(bool fGui /*= false*/, bool fAutoRegUpdate /*= true*/) +{ + HRESULT rc = E_FAIL; + NOREF(fAutoRegUpdate); + +#if !defined(VBOX_WITH_XPCOM) + +# ifdef VBOX_WITH_AUTO_COM_REG_UPDATE + /* + * First time we're called in a process, we refresh the VBox COM + * registrations. Use a global mutex to prevent updating when there are + * API users already active, as that could lead to a bit of a mess. + */ + if ( fAutoRegUpdate + && gCOMMainThread == NIL_RTTHREAD) + { + SetLastError(ERROR_SUCCESS); + HANDLE hLeakIt = CreateMutexW(NULL/*pSecAttr*/, FALSE, L"Global\\VirtualBoxComLazyRegistrationMutant"); + DWORD dwErr = GetLastError(); + AssertMsg(dwErr == ERROR_SUCCESS || dwErr == ERROR_ALREADY_EXISTS || dwErr == ERROR_ACCESS_DENIED, ("%u\n", dwErr)); + if (dwErr == ERROR_SUCCESS) + { + char szPath[RTPATH_MAX]; + int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath)); + if (RT_SUCCESS(vrc)) +# ifndef VBOX_IN_32_ON_64_MAIN_API + vrc = RTPathAppend(szPath, sizeof(szPath), + 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"); +# else + vrc = RTPathAppend(szPath, sizeof(szPath), "x86\\VBoxProxyStub-x86.dll"); +# endif + if (RT_SUCCESS(vrc)) + { + RTLDRMOD hMod; + vrc = RTLdrLoad(szPath, &hMod); + if (RT_SUCCESS(vrc)) + { + union + { + void *pv; + DECLCALLBACKMEMBER(uint32_t, pfnRegUpdate)(void); + } u; + vrc = RTLdrGetSymbol(hMod, "VbpsUpdateRegistrations", &u.pv); + if (RT_SUCCESS(vrc)) + u.pfnRegUpdate(); + /* Just keep it loaded. */ + } + } + Assert(hLeakIt != NULL); NOREF(hLeakIt); + } + } +# endif + + /* + * We initialize COM in GUI thread in STA, to be compliant with QT and + * OLE requirments (for example to allow D&D), while other threads + * initialized in regular MTA. To allow fast proxyless access from + * GUI thread to COM objects, we explicitly provide our COM objects + * with free threaded marshaller. + * !!!!! Please think twice before touching this code !!!!! + */ + DWORD flags = fGui ? + COINIT_APARTMENTTHREADED + | COINIT_SPEED_OVER_MEMORY + : + COINIT_MULTITHREADED + | COINIT_DISABLE_OLE1DDE + | COINIT_SPEED_OVER_MEMORY; + + rc = CoInitializeEx(NULL, flags); + + /* the overall result must be either S_OK or S_FALSE (S_FALSE means + * "already initialized using the same apartment model") */ + AssertMsg(rc == S_OK || rc == S_FALSE, ("rc=%08X\n", rc)); + + /* To be flow compatible with the XPCOM case, we return here if this isn't + * the main thread or if it isn't its first initialization call. + * Note! CoInitializeEx and CoUninitialize does it's own reference + * counting, so this exercise is entirely for the EventQueue init. */ + bool fRc; + RTTHREAD hSelf = RTThreadSelf(); + if (hSelf != NIL_RTTHREAD) + ASMAtomicCmpXchgHandle(&gCOMMainThread, hSelf, NIL_RTTHREAD, fRc); + else + fRc = false; + + if (fGui) + Assert(RTThreadIsMain(hSelf)); + + if (!fRc) + { + if ( gCOMMainThread == hSelf + && SUCCEEDED(rc)) + gCOMMainInitCount++; + + AssertComRC(rc); + return rc; + } + Assert(RTThreadIsMain(hSelf)); + + /* this is the first main thread initialization */ + Assert(gCOMMainInitCount == 0); + if (SUCCEEDED(rc)) + gCOMMainInitCount = 1; + +#else /* !defined(VBOX_WITH_XPCOM) */ + + /* Unused here */ + NOREF(fGui); + + if (ASMAtomicXchgBool(&gIsXPCOMInitialized, true) == true) + { + /* XPCOM is already initialized on the main thread, no special + * initialization is necessary on additional threads. Just increase + * the init counter if it's a main thread again (to correctly support + * nested calls to Initialize()/Shutdown() for compatibility with + * Win32). */ + + nsCOMPtr<nsIEventQueue> eventQ; + rc = NS_GetMainEventQ(getter_AddRefs(eventQ)); + + if (NS_SUCCEEDED(rc)) + { + PRBool isOnMainThread = PR_FALSE; + rc = eventQ->IsOnCurrentThread(&isOnMainThread); + if (NS_SUCCEEDED(rc) && isOnMainThread) + ++gXPCOMInitCount; + } + + AssertComRC(rc); + return rc; + } + Assert(RTThreadIsMain(RTThreadSelf())); + + /* this is the first initialization */ + gXPCOMInitCount = 1; + + /* prepare paths for registry files */ + char szCompReg[RTPATH_MAX]; + char szXptiDat[RTPATH_MAX]; + + int vrc = GetVBoxUserHomeDirectory(szCompReg, sizeof(szCompReg)); + if (vrc == VERR_ACCESS_DENIED) + return NS_ERROR_FILE_ACCESS_DENIED; + AssertRCReturn(vrc, NS_ERROR_FAILURE); + vrc = RTStrCopy(szXptiDat, sizeof(szXptiDat), szCompReg); + AssertRCReturn(vrc, NS_ERROR_FAILURE); +# ifdef VBOX_IN_32_ON_64_MAIN_API + vrc = RTPathAppend(szCompReg, sizeof(szCompReg), "compreg-x86.dat"); + AssertRCReturn(vrc, NS_ERROR_FAILURE); + vrc = RTPathAppend(szXptiDat, sizeof(szXptiDat), "xpti-x86.dat"); + AssertRCReturn(vrc, NS_ERROR_FAILURE); +# else + vrc = RTPathAppend(szCompReg, sizeof(szCompReg), "compreg.dat"); + AssertRCReturn(vrc, NS_ERROR_FAILURE); + vrc = RTPathAppend(szXptiDat, sizeof(szXptiDat), "xpti.dat"); + AssertRCReturn(vrc, NS_ERROR_FAILURE); +# endif + + LogFlowFunc(("component registry : \"%s\"\n", szCompReg)); + LogFlowFunc(("XPTI data file : \"%s\"\n", szXptiDat)); + + static const char *kAppPathsToProbe[] = + { + NULL, /* 0: will use VBOX_APP_HOME */ + NULL, /* 1: will try RTPathAppPrivateArch(), correctly installed release builds will never go further */ + NULL, /* 2: will try parent directory of RTPathAppPrivateArch(), only for testcases in non-hardened builds */ + /* There used to be hard coded paths, but they only caused trouble + * because they often led to mixing of builds or even versions. + * If you feel tempted to add anything here, think again. They would + * only be used if option 1 would not work, which is a sign of a big + * problem, as it returns a fixed location defined at compile time. + * It is better to fail than blindly trying to cover the problem. */ + }; + + /* Find out the directory where VirtualBox binaries are located */ + for (size_t i = 0; i < RT_ELEMENTS(kAppPathsToProbe); ++ i) + { + char szAppHomeDir[RTPATH_MAX]; + + if (i == 0) + { + /* Use VBOX_APP_HOME if present */ + vrc = RTEnvGetEx(RTENV_DEFAULT, "VBOX_APP_HOME", szAppHomeDir, sizeof(szAppHomeDir), NULL); + if (vrc == VERR_ENV_VAR_NOT_FOUND) + continue; + AssertRC(vrc); + } + else if (i == 1) + { + /* Use RTPathAppPrivateArch() first */ + vrc = RTPathAppPrivateArch(szAppHomeDir, sizeof(szAppHomeDir)); + AssertRC(vrc); + } + else if (i == 2) + { +# ifdef VBOX_WITH_HARDENING + continue; +# else /* !VBOX_WITH_HARDENING */ + /* Use parent of RTPathAppPrivateArch() if ends with "testcase" */ + vrc = RTPathAppPrivateArch(szAppHomeDir, sizeof(szAppHomeDir)); + AssertRC(vrc); + vrc = RTPathStripTrailingSlash(szAppHomeDir); + AssertRC(vrc); + char *filename = RTPathFilename(szAppHomeDir); + if (!filename || strcmp(filename, "testcase")) + continue; + RTPathStripFilename(szAppHomeDir); +# endif /* !VBOX_WITH_HARDENING */ + } + else + { + /* Iterate over all other paths */ + RTStrCopy(szAppHomeDir, sizeof(szAppHomeDir), kAppPathsToProbe[i]); + vrc = VINF_SUCCESS; + } + if (RT_FAILURE(vrc)) + { + rc = NS_ERROR_FAILURE; + continue; + } + char szCompDir[RTPATH_MAX]; + vrc = RTStrCopy(szCompDir, sizeof(szCompDir), szAppHomeDir); + if (RT_FAILURE(vrc)) + { + rc = NS_ERROR_FAILURE; + continue; + } + vrc = RTPathAppend(szCompDir, sizeof(szCompDir), "components"); + if (RT_FAILURE(vrc)) + { + rc = NS_ERROR_FAILURE; + continue; + } + LogFlowFunc(("component directory : \"%s\"\n", szCompDir)); + + nsCOMPtr<DirectoryServiceProvider> dsProv; + dsProv = new DirectoryServiceProvider(); + if (dsProv) + rc = dsProv->init(szCompReg, szXptiDat, szCompDir, szAppHomeDir); + else + rc = NS_ERROR_OUT_OF_MEMORY; + if (NS_FAILED(rc)) + break; + + /* Setup the application path for NS_InitXPCOM2. Note that we properly + * answer the NS_XPCOM_CURRENT_PROCESS_DIR query in our directory + * service provider but it seems to be activated after the directory + * service is used for the first time (see the source NS_InitXPCOM2). So + * use the same value here to be on the safe side. */ + nsCOMPtr <nsIFile> appDir; + { + char *appDirCP = NULL; + vrc = RTStrUtf8ToCurrentCP(&appDirCP, szAppHomeDir); + if (RT_SUCCESS(vrc)) + { + nsCOMPtr<nsILocalFile> file; + rc = NS_NewNativeLocalFile(nsEmbedCString(appDirCP), + PR_FALSE, getter_AddRefs(file)); + if (NS_SUCCEEDED(rc)) + appDir = do_QueryInterface(file, &rc); + + RTStrFree(appDirCP); + } + else + rc = NS_ERROR_FAILURE; + } + if (NS_FAILED(rc)) + break; + + /* Set VBOX_XPCOM_HOME to the same app path to make XPCOM sources that + * still use it instead of the directory service happy */ + vrc = RTEnvSetEx(RTENV_DEFAULT, "VBOX_XPCOM_HOME", szAppHomeDir); + AssertRC(vrc); + + /* Finally, initialize XPCOM */ + { + nsCOMPtr<nsIServiceManager> serviceManager; + rc = NS_InitXPCOM2(getter_AddRefs(serviceManager), appDir, dsProv); + if (NS_SUCCEEDED(rc)) + { + nsCOMPtr<nsIComponentRegistrar> registrar = + do_QueryInterface(serviceManager, &rc); + if (NS_SUCCEEDED(rc)) + { + rc = registrar->AutoRegister(nsnull); + if (NS_SUCCEEDED(rc)) + { + /* We succeeded, stop probing paths */ + LogFlowFunc(("Succeeded.\n")); + break; + } + } + } + } + + /* clean up before the new try */ + HRESULT rc2 = NS_ShutdownXPCOM(nsnull); + if (SUCCEEDED(rc)) + rc = rc2; + + if (i == 0) + { + /* We failed with VBOX_APP_HOME, don't probe other paths */ + break; + } + } + +#endif /* !defined(VBOX_WITH_XPCOM) */ + + AssertComRCReturnRC(rc); + + // for both COM and XPCOM, we only get here if this is the main thread; + // only then initialize the autolock system (AutoLock.cpp) + Assert(RTThreadIsMain(RTThreadSelf())); + util::InitAutoLockSystem(); + + /* + * Init the main event queue (ASSUMES it cannot fail). + */ + if (SUCCEEDED(rc)) + NativeEventQueue::init(); + + return rc; +} + +HRESULT Shutdown() +{ + HRESULT rc = S_OK; + +#if !defined(VBOX_WITH_XPCOM) + + /* EventQueue::uninit reference counting fun. */ + RTTHREAD hSelf = RTThreadSelf(); + if ( hSelf == gCOMMainThread + && hSelf != NIL_RTTHREAD) + { + if (-- gCOMMainInitCount == 0) + { + NativeEventQueue::uninit(); + ASMAtomicWriteHandle(&gCOMMainThread, NIL_RTTHREAD); + } + } + + CoUninitialize(); + +#else /* !defined(VBOX_WITH_XPCOM) */ + + nsCOMPtr<nsIEventQueue> eventQ; + rc = NS_GetMainEventQ(getter_AddRefs(eventQ)); + + if (NS_SUCCEEDED(rc) || rc == NS_ERROR_NOT_AVAILABLE) + { + /* NS_ERROR_NOT_AVAILABLE seems to mean that + * nsIEventQueue::StopAcceptingEvents() has been called (see + * nsEventQueueService.cpp). We hope that this error code always means + * just that in this case and assume that we're on the main thread + * (it's a kind of unexpected behavior if a non-main thread ever calls + * StopAcceptingEvents() on the main event queue). */ + + PRBool isOnMainThread = PR_FALSE; + if (NS_SUCCEEDED(rc)) + { + rc = eventQ->IsOnCurrentThread(&isOnMainThread); + eventQ = nsnull; /* early release before shutdown */ + } + else + { + isOnMainThread = RTThreadIsMain(RTThreadSelf()); + rc = NS_OK; + } + + if (NS_SUCCEEDED(rc) && isOnMainThread) + { + /* only the main thread needs to uninitialize XPCOM and only if + * init counter drops to zero */ + if (--gXPCOMInitCount == 0) + { + NativeEventQueue::uninit(); + rc = NS_ShutdownXPCOM(nsnull); + + /* This is a thread initialized XPCOM and set gIsXPCOMInitialized to + * true. Reset it back to false. */ + bool wasInited = ASMAtomicXchgBool(&gIsXPCOMInitialized, false); + Assert(wasInited == true); + NOREF(wasInited); + } + } + } + +#endif /* !defined(VBOX_WITH_XPCOM) */ + + AssertComRC(rc); + + return rc; +} + +} /* namespace com */ |