ExtPackManagerImpl.cpp revision 766eace665f330e29fa1793e4533d09e63d75321
/* $Id$ */
/** @file
* VirtualBox Main - interface for Extension Packs, VBoxSVC & VBoxC.
*/
/*
* Copyright (C) 2010-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "ExtPackManagerImpl.h"
#include "ExtPackUtil.h"
#include <iprt/buildconfig.h>
#include <iprt/manifest.h>
#include "AutoCaller.h"
#include "Global.h"
#include "ProgressImpl.h"
#if defined(VBOX_COM_INPROC)
# include "ConsoleImpl.h"
#else
# include "VirtualBoxImpl.h"
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** @name 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). */
/** The file system object info of the XML file.
* This is for detecting changes and save time in refresh(). */
/** Whether it's usable or not. */
bool fUsable;
/** Why it is unusable. */
};
#if !defined(VBOX_COM_INPROC)
/**
* Private extension pack data.
*/
{
public:
/** The path to the tarball. */
/** The SHA-256 hash of the file (as string). */
/** The file handle of the extension pack file. */
/** Our manifest for the tarball. */
/** Pointer to the extension pack manager. */
/** Pointer to the VirtualBox object so we can create a progress object. */
};
#endif
/**
* Private extension pack data.
*/
{
public:
/** Where the extension pack is located. */
/** The file system object info of the extension pack directory.
* This is for detecting changes and save time in refresh(). */
/** The full path to the main module. */
/** The file system object info of the main module.
* This is used to determin whether to bother try reload it. */
/** The module handle of the main extension pack module. */
/** The helper callbacks for the extension pack. */
/** Pointer back to the extension pack object (for Hlp methods). */
/** The extension pack registration structure. */
/** The current context. */
/** Set if we've made the pfnVirtualBoxReady or pfnConsoleReady call. */
bool fMadeReadyCall;
};
/** List of extension packs. */
/**
* Private extension pack manager data.
*/
struct ExtPackManager::Data
{
/** The directory where the extension packs are installed. */
/** The directory where the certificates this installation recognizes are
* stored. */
/** The list of installed extension packs. */
#if !defined(VBOX_COM_INPROC)
/** Pointer to the VirtualBox object, our parent. */
#endif
/** The current context. */
#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
/** File handle for the VBoxVMM libary which we slurp because ExtPacks depend on it. */
#endif
};
#if !defined(VBOX_COM_INPROC)
/**
* Extension pack installation job.
*/
typedef struct EXTPACKINSTALLJOB
{
/** Smart pointer to the extension pack file. */
/** The replace argument. */
bool fReplace;
/** The display info argument. */
/** Smart pointer to the extension manager. */
/** Smart pointer to the progress object for this job. */
/** Pointer to an extension pack installation job. */
typedef EXTPACKINSTALLJOB *PEXTPACKINSTALLJOB;
/**
* Extension pack uninstallation job.
*/
typedef struct EXTPACKUNINSTALLJOB
{
/** Smart pointer to the extension manager. */
/** The name of the extension pack. */
/** The replace argument. */
bool fForcedRemoval;
/** The display info argument. */
/** Smart pointer to the progress object for this job. */
/** Pointer to an extension pack uninstallation job. */
typedef EXTPACKUNINSTALLJOB *PEXTPACKUNINSTALLJOB;
/**
* Called by ComObjPtr::createObject when creating the object.
*
* Just initialize the basic object state, do the rest in initWithDir().
*
* @returns S_OK.
*/
{
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);
/*
* Allocate + initialize our private data.
*/
m = new ExtPackFile::Data;
VBoxExtPackInitDesc(&m->Desc);
RT_ZERO(m->ObjInfoDesc);
m->fUsable = false;
m->strExtPackFile = a_pszFile;
m->strDigest = a_pszDigest;
m->hExtPackFile = NIL_RTFILE;
m->hOurManifest = NIL_RTMANIFEST;
m->ptrExtPackMgr = a_pExtPackMgr;
m->pVirtualBox = a_pVirtualBox;
if (pstrTarName)
{
delete pstrTarName;
pstrTarName = NULL;
}
/*
* Try open the extension pack and check that it is a regular file.
*/
if (RT_FAILURE(vrc))
{
}
if (RT_FAILURE(vrc))
/*
* Validate the tarball and extract the XML file.
*/
char szError[8192];
if (RT_FAILURE(vrc))
/*
* Parse the XML.
*/
if (pStrLoadErr != NULL)
{
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. */
return initFailed(tr("Extension pack name mismatch between the downloaded file and the XML inside it (xml='%s' file='%s')"),
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.
*/
{
return S_OK;
}
/**
* COM cruft.
*/
void ExtPackFile::FinalRelease()
{
uninit();
}
/**
* Do the actual cleanup.
*/
void ExtPackFile::uninit()
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
{
VBoxExtPackFreeDesc(&m->Desc);
RTFileClose(m->hExtPackFile);
m->hExtPackFile = NIL_RTFILE;
m->hOurManifest = NIL_RTMANIFEST;
delete m;
m = NULL;
}
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
/** @todo implement plug-ins. */
#ifdef VBOX_WITH_XPCOM
#endif
}
{
AutoCaller autoCaller(this);
*a_pfUsable = m->fUsable;
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
}
/* Same as ExtPack::QueryLicense, should really explore the subject of base classes here... */
STDMETHODIMP ExtPackFile::QueryLicense(IN_BSTR a_bstrPreferredLocale, IN_BSTR a_bstrPreferredLanguage, IN_BSTR a_bstrFormat,
{
/*
* Validate input.
*/
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.
*/
else if (strPreferredLocale.isNotEmpty())
RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s", strPreferredLocale.c_str(), strFormat.c_str());
else if (strPreferredLanguage.isNotEmpty())
RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s", strPreferredLocale.c_str(), strFormat.c_str());
else
/*
* Lock the extension pack. We need a write lock here as there must not be
* concurrent accesses to the tar file handle.
*/
AutoCaller autoCaller(this);
{
/*
* 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)
else
{
/*
* Look it up in the manifest before scanning the tarball for it
*/
{
char szError[8192];
if (RT_SUCCESS(vrc))
{
for (;;)
{
char *pszName;
if (RT_FAILURE(vrc))
{
else
break;
}
/* Is this it? */
&& ( enmType == RTVFSOBJTYPE_IO_STREAM
|| enmType == RTVFSOBJTYPE_FILE))
{
/* Load the file into memory. */
if (RT_SUCCESS(vrc))
{
if (pvFile)
{
if (RT_SUCCESS(vrc))
{
/* try translate it into a string we can return. */
if (bstrLicense.isNotEmpty())
{
}
else
tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
szName);
}
else
}
else
}
else
break;
}
/* Release current. */
}
}
else
}
else
}
}
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
STDMETHODIMP ExtPackFile::Install(BOOL a_fReplace, IN_BSTR a_bstrDisplayInfo, IProgress **a_ppProgress)
{
if (a_ppProgress)
*a_ppProgress = NULL;
AutoCaller autoCaller(this);
{
if (m->fUsable)
{
try
{
pJob = new EXTPACKINSTALLJOB;
pJob->ptrExtPackFile = this;
{
#ifndef VBOX_COM_INPROC
m->pVirtualBox,
#endif
static_cast<IExtPackFile *>(this),
FALSE /*aCancelable*/,
NULL /*aId*/);
}
{
if (RT_SUCCESS(vrc))
{
}
else
}
}
{
hrc = E_OUTOFMEMORY;
}
if (pJob)
delete pJob;
}
else
}
return hrc;
}
#endif
/**
* Called by ComObjPtr::createObject when creating the object.
*
* Just initialize the basic object state, do the rest in initWithDir().
*
* @returns S_OK.
*/
{
m = NULL;
return S_OK;
}
/**
* Initializes the extension pack by reading its file.
*
* @returns COM status code.
* @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(VBOXEXTPACKCTX a_enmContext, const char *a_pszName, const char *a_pszDir)
{
AutoInitSpan autoInitSpan(this);
static const VBOXEXTPACKHLP s_HlpTmpl =
{
/* u32Version = */ VBOXEXTPACKHLP_VERSION,
/* uVBoxFullVersion = */ VBOX_FULL_VERSION,
/* uVBoxVersionRevision = */ 0,
/* u32Padding = */ 0,
/* pszVBoxVersion = */ "",
/* u32EndMarker = */ VBOXEXTPACKHLP_VERSION
};
/*
* Allocate + initialize our private data.
*/
m = new Data;
VBoxExtPackInitDesc(&m->Desc);
RT_ZERO(m->ObjInfoDesc);
m->fUsable = false;
m->strExtPackPath = a_pszDir;
RT_ZERO(m->ObjInfoExtPack);
m->strMainModPath.setNull();
RT_ZERO(m->ObjInfoMainMod);
m->hMainMod = NIL_RTLDRMOD;
m->pThis = this;
m->enmContext = a_enmContext;
m->fMadeReadyCall = false;
/*
* Probe the extension pack (this code is shared with refresh()).
*/
probeAndLoad();
return S_OK;
}
/**
* COM cruft.
*/
void ExtPack::FinalRelease()
{
uninit();
}
/**
* Do the actual cleanup.
*/
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
{
if (m->hMainMod != NIL_RTLDRMOD)
{
RTLdrClose(m->hMainMod);
m->hMainMod = NIL_RTLDRMOD;
}
VBoxExtPackFreeDesc(&m->Desc);
delete m;
m = NULL;
}
}
/**
* 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::callInstalledHook(IVirtualBox *a_pVirtualBox, AutoWriteLock *a_pLock, PRTERRINFO pErrInfo)
{
if ( m != NULL
&& m->hMainMod != NIL_RTLDRMOD)
{
if (m->pReg->pfnInstalled)
{
return true;
}
}
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.
*/
{
if ( m != NULL
&& m->hMainMod != NIL_RTLDRMOD)
{
{
if (RT_FAILURE(vrc))
{
if (!a_fForcedRemoval)
}
}
{
RTLdrClose(m->hMainMod);
m->hMainMod = NIL_RTLDRMOD;
}
}
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.
*/
{
if ( m != NULL
&& m->fUsable
&& !m->fMadeReadyCall)
{
m->fMadeReadyCall = true;
if (m->pReg->pfnVirtualBoxReady)
{
return true;
}
}
return false;
}
/**
* 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.
*/
{
if ( m != NULL
&& m->fUsable
&& !m->fMadeReadyCall)
{
m->fMadeReadyCall = true;
if (m->pReg->pfnConsoleReady)
{
return true;
}
}
return false;
}
/**
* 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::callVmCreatedHook(IVirtualBox *a_pVirtualBox, IMachine *a_pMachine, AutoWriteLock *a_pLock)
{
if ( m != NULL
&& m->fUsable)
{
if (m->pReg->pfnVMCreated)
{
return true;
}
}
return false;
}
/**
* 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_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::callVmConfigureVmmHook(IConsole *a_pConsole, PVM a_pVM, AutoWriteLock *a_pLock, int *a_pvrc)
{
*a_pvrc = VINF_SUCCESS;
if ( m != NULL
&& m->fUsable)
{
if (m->pReg->pfnVMConfigureVMM)
{
if (RT_FAILURE(vrc))
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_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::callVmPowerOnHook(IConsole *a_pConsole, PVM a_pVM, AutoWriteLock *a_pLock, int *a_pvrc)
{
*a_pvrc = VINF_SUCCESS;
if ( m != NULL
&& m->fUsable)
{
if (m->pReg->pfnVMPowerOn)
{
if (RT_FAILURE(vrc))
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_pLock The write lock held by the caller.
*/
{
if ( m != NULL
&& m->fUsable)
{
if (m->pReg->pfnVMPowerOff)
{
return true;
}
}
return false;
}
/**
* 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.
*/
{
if ( m != NULL
&& m->fUsable)
{
else
hrc = setError(E_FAIL, tr("The extension pack '%s' does not include a VRDE module"), m->Desc.strName.c_str());
}
else
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.
*/
{
{
else
}
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.
*/
{
else
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::wantsToBeDefaultVrde(void) const
{
return m->fUsable
}
/**
* 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.
*/
{
if (a_pfCanDelete)
*a_pfCanDelete = false;
/*
* Has the module been deleted?
*/
int vrc = RTPathQueryInfoEx(m->strExtPackPath.c_str(), &ObjInfoExtPack, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
if ( RT_FAILURE(vrc)
{
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.
*/
char szDescFilePath[RTPATH_MAX];
vrc = RTPathJoin(szDescFilePath, sizeof(szDescFilePath), m->strExtPackPath.c_str(), VBOX_EXTPACK_DESCRIPTION_NAME);
if (RT_SUCCESS(vrc))
if (RT_FAILURE(vrc))
if (m->strMainModPath.isNotEmpty())
vrc = RTPathQueryInfoEx(m->strMainModPath.c_str(), &ObjInfoMainMod, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
/*
* 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)
probeAndLoad();
{
/** @todo not important, so it can wait. */
}
}
/*
* Ok, it is currently not usable. If anything has changed since last time
* reprobe the extension pack.
*/
probeAndLoad();
return S_OK;
}
/**
* 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::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;
}
{
m->strWhyUnusable.printf(tr("'%s' is a symbolic link, this is not allowed"), m->strExtPackPath.c_str(), vrc);
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;
}
vrc = SUPR3HardenedVerifyDir(m->strExtPackPath.c_str(), true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core);
if (RT_FAILURE(vrc))
{
return;
}
/*
* Read the description file.
*/
if (pStrLoadErr != NULL)
{
delete pStrLoadErr;
return;
}
/*
* Make sure the XML name and directory matches.
*/
{
m->strWhyUnusable.printf(tr("The description name ('%s') and directory name ('%s') does not match"),
return;
}
/*
* Load the main DLL and call the predefined entry point.
*/
bool fIsNative;
{
m->strWhyUnusable.printf(tr("Failed to locate the main module ('%s')"), m->Desc.strMainModule.c_str());
return;
}
if (RT_FAILURE(vrc))
{
return;
}
if (fIsNative)
{
if (RT_FAILURE(vrc))
{
m->hMainMod = NIL_RTLDRMOD;
return;
}
}
else
{
return;
}
/*
* Resolve the predefined entry point.
*/
if (RT_SUCCESS(vrc))
{
if ( RT_SUCCESS(vrc)
{
{
)
{
/*
* We're good!
*/
m->fUsable = true;
m->strWhyUnusable.setNull();
return;
}
}
else
}
else
}
else
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.
*/
{
/*
* Try the native path first.
*/
char szPath[RTPATH_MAX];
AssertLogRelRCReturn(vrc, false);
AssertLogRelRCReturn(vrc, false);
if (!a_pszExt)
{
const char *pszDefExt;
switch (a_enmKind)
{
default:
AssertFailedReturn(false);
}
AssertLogRelRCReturn(vrc, false);
}
if (!a_pObjInfo)
a_pObjInfo = &ObjInfo;
{
if (a_pfNative)
*a_pfNative = true;
*a_pStrFound = szPath;
return true;
}
/*
* Try the platform agnostic modules.
*/
/* gcc.x86/module.rel */
char szSubDir[32];
AssertLogRelRCReturn(vrc, false);
AssertLogRelRCReturn(vrc, false);
if (!a_pszExt)
{
AssertLogRelRCReturn(vrc, false);
}
{
if (a_pfNative)
*a_pfNative = false;
*a_pStrFound = szPath;
return true;
}
/* x86/module.rel */
AssertLogRelRCReturn(vrc, false);
AssertLogRelRCReturn(vrc, false);
if (!a_pszExt)
{
AssertLogRelRCReturn(vrc, false);
}
{
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.
*/
{
return false;
return false;
return false;
return false;
return false;
{
{
case RTFSOBJATTRADD_UNIX:
return false;
return false;
return false;
return false;
return false;
break;
default:
break;
}
}
return true;
}
/**
* @interface_method_impl{VBOXEXTPACKHLP,pfnFindModule}
*/
/*static*/ DECLCALLBACK(int)
ExtPack::hlpFindModule(PCVBOXEXTPACKHLP pHlp, const char *pszName, const char *pszExt, VBOXEXTPACKMODKIND enmKind,
{
/*
* Validate the input and get our bearings.
*/
AssertReturn(enmKind > VBOXEXTPACKMODKIND_INVALID && enmKind < VBOXEXTPACKMODKIND_END, VERR_INVALID_PARAMETER);
/*
* This is just a wrapper around findModule.
*/
return VERR_FILE_NOT_FOUND;
}
/*static*/ DECLCALLBACK(int)
ExtPack::hlpGetFilePath(PCVBOXEXTPACKHLP pHlp, const char *pszFilename, char *pszPath, size_t cbPath)
{
/*
* Validate the input and get our bearings.
*/
/*
* This is a simple RTPathJoin, no checking if things exists or anything.
*/
if (RT_FAILURE(vrc))
return vrc;
}
{
/*
* Validate the input and get our bearings.
*/
return pThis->m->enmContext;
}
/*static*/ DECLCALLBACK(int)
const char *pszServiceLibrary, const char *pszServiceName)
{
#ifdef VBOX_COM_INPROC
/*
* Validate the input and get our bearings.
*/
#else
#endif
return VERR_INVALID_STATE;
}
/*static*/ DECLCALLBACK(int)
{
/*
* Validate the input and get our bearings.
*/
return VERR_NOT_IMPLEMENTED;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
/** @todo implement plug-ins. */
#ifdef VBOX_WITH_XPCOM
#endif
}
{
AutoCaller autoCaller(this);
*a_pfUsable = m->fUsable;
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
AutoCaller autoCaller(this);
return hrc;
}
{
}
STDMETHODIMP ExtPack::QueryLicense(IN_BSTR a_bstrPreferredLocale, IN_BSTR a_bstrPreferredLanguage, IN_BSTR a_bstrFormat,
{
/*
* Validate input.
*/
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.
*/
else if (strPreferredLocale.isNotEmpty())
RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-%s.%s", strPreferredLocale.c_str(), strFormat.c_str());
else if (strPreferredLanguage.isNotEmpty())
RTStrPrintf(szName, sizeof(szName), VBOX_EXTPACK_LICENSE_NAME_PREFIX "-_%s.%s", strPreferredLocale.c_str(), strFormat.c_str());
else
/*
* Effectuate the query.
*/
AutoCaller autoCaller(this);
{
if (!m->fUsable)
else
{
char szPath[RTPATH_MAX];
if (RT_SUCCESS(vrc))
{
void *pvFile;
if (RT_SUCCESS(vrc))
{
if (bstrLicense.isNotEmpty())
{
}
else
hrc = setError(VBOX_E_IPRT_ERROR, tr("The license file '%s' is empty or contains invalid UTF-8 encoding"),
szPath);
}
hrc = setError(VBOX_E_OBJECT_NOT_FOUND, tr("The license file '%s' was not found in extension pack '%s'"),
else
}
else
}
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
if ( m->pReg
&& m->pReg->pfnQueryObject)
{
if (pvUnknown)
else
hrc = E_NOINTERFACE;
}
else
hrc = E_NOINTERFACE;
}
return hrc;
}
/**
* Called by ComObjPtr::createObject when creating the object.
*
* Just initialize the basic object state, do the rest in init().
*
* @returns S_OK.
*/
{
m = NULL;
return S_OK;
}
/**
* 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.
*/
{
AutoInitSpan autoInitSpan(this);
/*
* Figure some stuff out before creating the instance data.
*/
char szBaseDir[RTPATH_MAX];
char szCertificatDir[RTPATH_MAX];
/*
* Allocate and initialize the instance data.
*/
m = new Data;
m->strBaseDir = szBaseDir;
#if !defined(VBOX_COM_INPROC)
m->pVirtualBox = a_pVirtualBox;
#endif
m->enmContext = a_enmContext;
/*
* Slurp in VBoxVMM which is used by VBoxPuelMain.
*/
#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
{
if (RT_FAILURE(vrc))
m->hVBoxVMM = NIL_RTLDRMOD;
/* cleanup in ::uninit()? */
}
#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.
*/
if (RT_SUCCESS(vrc))
{
for (;;)
{
if (RT_FAILURE(vrc))
{
break;
}
{
/*
* All directories are extensions, the shall be nothing but
* extensions in this subdirectory.
*/
char szExtPackDir[RTPATH_MAX];
if (RT_SUCCESS(vrc))
{
if (pstrName)
{
delete pstrName;
}
else
hrc = E_UNEXPECTED;
}
else
hrc = E_UNEXPECTED;
}
}
}
/* else: ignore, the directory probably does not exist or something. */
return hrc;
}
/**
* COM cruft.
*/
void ExtPackManager::FinalRelease()
{
uninit();
}
/**
* Do the actual cleanup.
*/
void ExtPackManager::uninit()
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
{
delete m;
m = NULL;
}
}
STDMETHODIMP ExtPackManager::COMGETTER(InstalledExtPacks)(ComSafeArrayOut(IExtPack *, a_paExtPacks))
{
AutoCaller autoCaller(this);
{
}
return hrc;
}
{
AutoCaller autoCaller(this);
{
if (!ptrExtPack.isNull())
else
}
return hrc;
}
STDMETHODIMP ExtPackManager::OpenExtPackFile(IN_BSTR a_bstrTarballAndDigest, IExtPackFile **a_ppExtPackFile)
{
#if !defined(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. */
else
{
}
return hrc;
#else
return E_NOTIMPL;
#endif
}
STDMETHODIMP ExtPackManager::Uninstall(IN_BSTR a_bstrName, BOOL a_fForcedRemoval, IN_BSTR a_bstrDisplayInfo,
{
if (a_ppProgress)
*a_ppProgress = NULL;
#if !defined(VBOX_COM_INPROC)
AutoCaller autoCaller(this);
{
try
{
pJob = new EXTPACKUNINSTALLJOB;
pJob->ptrExtPackMgr = this;
{
#ifndef VBOX_COM_INPROC
m->pVirtualBox,
#endif
static_cast<IExtPackManager *>(this),
FALSE /*aCancelable*/,
NULL /*aId*/);
}
{
if (RT_SUCCESS(vrc))
{
}
else
}
}
{
hrc = E_OUTOFMEMORY;
}
if (pJob)
delete pJob;
}
return hrc;
#else
return E_NOTIMPL;
#endif
}
{
AutoCaller autoCaller(this);
{
/*
* 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.
*/
"cleanup",
(const char *)NULL);
}
return hrc;
}
STDMETHODIMP ExtPackManager::QueryAllPlugInsForFrontend(IN_BSTR a_bstrFrontend, ComSafeArrayOut(BSTR, a_pabstrPlugInModules))
{
AutoCaller autoCaller(this);
{
/** @todo implement plug-ins */
}
return hrc;
}
{
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 const char s_szSuccessInd[] = "rcExit=RTEXITCODE_SUCCESS";
return NULL;
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::runSetUidToRootHelper(Utf8Str const *a_pstrDisplayInfo, const char *a_pszCommand, ...)
{
/*
* Calculate the path to the helper application.
*/
char szExecName[RTPATH_MAX];
/*
* Convert the variable argument list to a RTProcCreate argument vector.
*/
const char *apszArgs[20];
unsigned cArgs = 0;
if ( a_pstrDisplayInfo
&& a_pstrDisplayInfo->isNotEmpty())
{
}
const char *pszLastArg;
for (;;)
{
if (!pszLastArg)
break;
};
LogRel(("\n"));
/*
* 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.
*/
/*
* Spawn the process.
*/
0 /*fFlags*/,
NULL /*phStdIn*/,
NULL /*phStdOut*/,
NULL /*pszAsUser*/,
NULL /*pszPassword*/,
&hProcess);
if (RT_SUCCESS(vrc))
{
/*
* Read the pipe output until the process completes.
*/
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];
if (RT_SUCCESS(vrc))
{
/* grow the buffer? */
if ( cbBufReq > cbStdErrBuf
{
if (pvNew)
{
pszStdErrBuf = (char *)pvNew;
cbStdErrBuf = cbNew;
}
}
/* append if we've got room. */
if (cbBufReq <= cbStdErrBuf)
{
}
}
else
{
hPipeR = NIL_RTPIPE;
}
}
/*
* Service the process. Block if we have no pipe.
*/
if (hProcess != NIL_RTPROCESS)
{
&ProcStatus);
if (RT_SUCCESS(vrc))
else
}
} while ( hPipeR != NIL_RTPIPE
|| hProcess != NIL_RTPROCESS);
LogRel(("ExtPack: enmReason=%d iStatus=%d stderr='%s'\n",
/*
* 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)
{
}
if (pszSuccessInd)
{
*pszSuccessInd = '\0';
}
&& ProcStatus.iStatus == 0)
/*
* Compose the status code and, on failure, error message.
*/
&& ProcStatus.iStatus == 0)
{
}
else
}
else
hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to launch the helper application '%s' (%Rrc)"), szExecName, vrc);
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.
*/
{
it++)
{
if ( pExtPackData
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.
*/
{
it++)
{
if ( pExtPackData
{
return;
}
}
}
#if !defined(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::refreshExtPack(const char *a_pszName, bool a_fUnusableIsError, ExtPack **a_ppExtPack)
{
if (pExtPack)
{
/*
* Refresh existing object.
*/
bool fCanDelete;
{
if (fCanDelete)
{
}
}
}
else
{
/*
* Do this check here, otherwise VBoxExtPackCalcDir() will fail with a strange
* error.
*/
if (!fValid)
/*
* 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];
if (!fExists)
{
if (RT_SUCCESS(vrc))
{
for (;;)
{
if (RT_FAILURE(vrc))
{
break;
}
{
/*
* 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 */
fExists = true;
break;
}
}
}
}
if (fExists)
{
/*
* We've got something, create a new extension pack object for it.
*/
{
if (ptrNewExtPack->m->fUsable)
else
LogRel(("ExtPackManager: Found bad extension pack '%s': %s\n",
}
}
else
}
/*
* Report error if not usable, if that is desired.
*/
&& pExtPack
if (a_ppExtPack)
*a_ppExtPack = pExtPack;
return hrc;
}
/**
* Thread wrapper around doInstall.
*
* @returns VINF_SUCCESS (ignored)
* @param hThread The thread handle (ignored).
* @param pvJob The job structure.
*/
{
HRESULT hrc = pJob->ptrExtPackMgr->doInstall(pJob->ptrExtPackFile, pJob->fReplace, &pJob->strDisplayInfo);
delete pJob;
return VINF_SUCCESS;
}
/**
* 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.
* @param a_ppProgress Where to return a progress object some day. Can
* be NULL.
*/
HRESULT ExtPackManager::doInstall(ExtPackFile *a_pExtPackFile, bool a_fReplace, Utf8Str const *a_pstrDisplayInfo)
{
AutoCaller autoCaller(this);
{
/*
* Refresh the data we have on the extension pack as it
* may be made stale by direct meddling or some other user.
*/
{
if (pExtPack && a_fReplace)
else if (pExtPack)
tr("Extension pack '%s' is already installed."
" In case of a reinstallation, please uninstall it first"),
}
{
/*
* 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).
*/
"install",
(const char *)NULL);
{
{
else
{
LogRel(("ExtPackManager: Installed hook for '%s' failed: %Rrc - %s\n",
/*
* Uninstall the extpack if the error indicates that.
*/
"uninstall",
"--forced",
(const char *)NULL);
}
}
}
else
{
}
}
/*
* Do VirtualBoxReady callbacks now for any freshly installed
* extension pack (old ones will not be called).
*/
if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
{
}
}
return hrc;
}
/**
* Thread wrapper around doUninstall.
*
* @returns VINF_SUCCESS (ignored)
* @param hThread The thread handle (ignored).
* @param pvJob The job structure.
*/
{
HRESULT hrc = pJob->ptrExtPackMgr->doUninstall(&pJob->strName, pJob->fForcedRemoval, &pJob->strDisplayInfo);
delete pJob;
return VINF_SUCCESS;
}
/**
* 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::doUninstall(Utf8Str const *a_pstrName, bool a_fForcedRemoval, Utf8Str const *a_pstrDisplayInfo)
{
AutoCaller autoCaller(this);
{
/*
* Refresh the data we have on the extension pack as it may be made
* stale by direct meddling or some other user.
*/
{
if (!pExtPack)
{
LogRel(("ExtPackManager: Extension pack '%s' is not installed, so nothing to uninstall.\n", a_pstrName->c_str()));
}
else
{
/*
* Call the uninstall hook and unload the main dll.
*/
{
/*
* 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.
*/
"uninstall",
pszForcedOpt, /* Last as it may be NULL. */
(const char *)NULL);
{
{
if (!pExtPack)
else
tr("Uninstall extension pack '%s' failed under mysterious circumstances"),
a_pstrName->c_str());
}
}
else
{
}
}
}
}
/*
* Do VirtualBoxReady callbacks now for any freshly installed
* extension pack (old ones will not be called).
*/
if (m->enmContext == VBOXEXTPACKCTX_PER_USER_DAEMON)
{
}
}
return hrc;
}
/**
* Calls the pfnVirtualBoxReady hook for all working extension packs.
*
* @remarks The caller must not hold any locks.
*/
void ExtPackManager::callAllVirtualBoxReadyHooks(void)
{
AutoCaller autoCaller(this);
return;
/* advancing below */)
{
else
it++;
}
}
#endif
/**
* Calls the pfnConsoleReady hook for all working extension packs.
*
* @param a_pConsole The console interface.
* @remarks The caller must not hold any locks.
*/
{
AutoCaller autoCaller(this);
return;
/* advancing below */)
{
else
it++;
}
}
#if !defined(VBOX_COM_INPROC)
/**
* Calls the pfnVMCreated hook for all working extension packs.
*
* @param a_pMachine The machine interface of the new VM.
*/
{
AutoCaller autoCaller(this);
return;
}
#endif
/**
* 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.
*/
{
AutoCaller autoCaller(this);
{
int 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.
*/
{
AutoCaller autoCaller(this);
{
int 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.
*/
{
AutoCaller autoCaller(this);
return;
}
/**
* 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.
*/
{
AutoCaller autoCaller(this);
{
if (pExtPack)
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 S_OK if a path is returned, COM error status and message return if
* not.
* @param a_pstrExtPack The extension pack.
* @param a_pstrVrdeLibrary Where to return the path.
*/
int ExtPackManager::getVrdeLibraryPathForExtPack(Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrVrdeLibrary)
{
AutoCaller autoCaller(this);
{
if (pExtPack)
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 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_pstrExtPack The extension pack.
* @param a_pstrVrdeLibrary Where to return the path.
*/
HRESULT ExtPackManager::getLibraryPathForExtPack(const char *a_pszModuleName, Utf8Str const *a_pstrExtPack, Utf8Str *a_pstrLibrary)
{
AutoCaller autoCaller(this);
{
if (pExtPack)
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 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.
*/
{
a_pstrExtPack->setNull();
AutoCaller autoCaller(this);
{
it++)
{
if ((*it)->wantsToBeDefaultVrde())
{
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.
*/
{
AutoCaller autoCaller(this);
return false;
}
/**
* Dumps all extension packs to the release log.
*/
void ExtPackManager::dumpAllToReleaseLog(void)
{
AutoCaller autoCaller(this);
return;
LogRel(("Installed Extension Packs:\n"));
it++)
{
if (pExtPackData)
{
if (pExtPackData->fUsable)
LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s)\n",
else
LogRel((" %s (Version: %s r%u%s%s; VRDE Module: %s unusable because of '%s')\n",
}
else
LogRel((" pExtPackData is NULL\n"));
}
if (!m->llInstalledExtPacks.size())
LogRel((" None installed!\n"));
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */