GuestImpl.cpp revision 525cfe310305abf78e4fd4cb98bd0fa603694a65
/* $Id$ */
/** @file
*
* VirtualBox COM class implementation
*/
/*
* Copyright (C) 2006-2010 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.
*/
#include "GuestImpl.h"
#include "Global.h"
#include "ConsoleImpl.h"
#include "ProgressImpl.h"
#include "VMMDev.h"
#include "AutoCaller.h"
#include "Logging.h"
#ifdef VBOX_WITH_GUEST_CONTROL
#endif
// defines
/////////////////////////////////////////////////////////////////////////////
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
return S_OK;
}
void Guest::FinalRelease()
{
uninit ();
}
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
/**
* Initializes the guest object.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
/* Confirm a successful initialization when it's the case */
else
mMemoryBalloonSize = 0; /* Default is no ballooning */
else
mfPageFusionEnabled = false; /* Default is no page fusion*/
mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
/* Clear statistics. */
for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
mCurrentGuestStat[i] = 0;
#ifdef VBOX_WITH_GUEST_CONTROL
/* Init the context ID counter at 1000. */
mNextContextID = 1000;
#endif
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.
*/
{
LogFlowThisFunc(("\n"));
#ifdef VBOX_WITH_GUEST_CONTROL
/* Scope write lock as much as possible. */
{
/*
* Cleanup must be done *before* AutoUninitSpan to cancel all
* all outstanding waits in API functions (which hold AutoCaller
* ref counts).
*/
/* Clean up callback data. */
/* Clear process map. */
}
#endif
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
}
// IGuest properties
/////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
// redirect the call to IMachine if no additions are installed
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
/* Only try alternative way if GA are active! */
{
/*
* If we got back an empty string from GetAdditionsVersion() we either
* really don't have the Guest Additions version yet or the guest is running
* older Guest Additions (< 3.2.0) which don't provide VMMDevReq_ReportGuestInfo2,
* so get the version + revision from the (hopefully) provided guest properties
* instead.
*/
{
&& !addVersion.isEmpty()
&& !addRevision.isEmpty())
{
/* Some Guest Additions versions had interchanged version + revision values,
* so check if the version value at least has a dot to identify it and change
* both values to reflect the right content. */
{
}
}
/** @todo r=bird: else: Should not return failure! */
}
else
{
/* If getting the version + revision above fails or they simply aren't there
* because of *really* old Guest Additions we only can report the interface
* version to at least have something. */
/** @todo r=bird: hr is still indicating failure! */
}
}
else
return hr;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return mfPageFusionEnabled;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
/* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
* does not call us back in any way! */
{
/* forward the information to the VMM device */
/* MUST release all locks before calling VMM device as its critsect
* has higher lock order than anything in Main. */
if (pVMMDev)
{
if (pVMMDevPort)
}
}
return ret;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
/* forward the information to the VMM device */
/* MUST release all locks before calling VMM device as its critsect
* has higher lock order than anything in Main. */
if (pVMMDev)
{
if (pVMMDevPort)
}
return S_OK;
}
{
AutoCaller autoCaller(this);
*aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
/* MUST release all locks before calling any PGM statistics queries,
* as they are executed by EMT and that might deadlock us by VMM device
* activity which waits for the Guest object lock. */
{
*aMemFreeTotal = 0;
int rc = PGMR3QueryVMMMemoryStats(pVM.raw(), &uAllocTotal, &uFreeTotal, &uBalloonedTotal, &uSharedTotal);
if (rc == VINF_SUCCESS)
{
}
/* Query the missing per-VM memory statistics. */
*aMemShared = 0;
if (rc == VINF_SUCCESS)
{
}
}
else
{
*aMemFreeTotal = 0;
*aMemShared = 0;
}
return S_OK;
}
{
AutoCaller autoCaller(this);
if (enmType >= GUESTSTATTYPE_MAX)
return E_INVALIDARG;
return S_OK;
}
{
AutoCaller autoCaller(this);
switch (aLevel)
{
break;
break;
break;
default:
break;
}
return rc;
}
{
AutoCaller autoCaller(this);
/* forward the information to the VMM device */
if (pVMMDev)
{
if (pVMMDevPort)
{
if (!aAllowInteractiveLogon)
u32Flags);
return S_OK;
}
}
return setError(VBOX_E_VM_ERROR,
tr("VMM device is not available (is the VM running?)"));
}
#ifdef VBOX_WITH_GUEST_CONTROL
/**
* Appends environment variables to the environment block. Each var=value pair is separated
* by NULL (\0) sequence. The whole block will be stored in one blob and disassembled on the
* guest side later to fit into the HGCM param structure.
*
* @returns VBox status code.
*
* @todo
*
*/
int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnv)
{
int rc = VINF_SUCCESS;
if (*ppvList)
{
{
rc = VERR_NO_MEMORY;
}
else
{
}
}
else
{
char *pcTmp;
{
/* Reset counters. */
*pcEnv = 0;
*pcbList = 0;
}
}
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* Static callback function for receiving updates on guest control commands
* from the guest. Acts as a dispatcher for the actual class instance.
*
* @returns VBox status code.
*
* @todo
*
*/
void *pvParms,
{
using namespace guestControl;
/*
* No locking, as this is purely a notification which does not make any
* changes to the object state.
*/
#ifdef DEBUG_andy
LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n",
#endif
int rc = VINF_SUCCESS;
if (u32Function == GUEST_DISCONNECTED)
{
//LogFlowFunc(("GUEST_DISCONNECTED\n"));
PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);
}
else if (u32Function == GUEST_EXEC_SEND_STATUS)
{
//LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
}
else if (u32Function == GUEST_EXEC_SEND_OUTPUT)
{
//LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
}
else
return rc;
}
/* Function for handling the execution start/termination notification. */
{
int vrc = VINF_SUCCESS;
/* Callback can be called several times. */
{
/** @todo Copy void* buffer contents! */
/* Was progress canceled before? */
&& !fCanceled)
{
/* Do progress handling. */
{
case PROC_STS_STARTED:
hr = it->second.pProgress->SetNextOperation(Bstr(tr("Waiting for process to exit ...")).raw(), 1 /* Weight */);
break;
case PROC_STS_TEN: /* Terminated normally. */
LogRel(("Guest process (PID %u) exited normally\n", pCBData->u32PID)); /** @todo Add process name */
LogFlowFunc(("Proccess (context ID=%u, status=%u) terminated successfully\n",
break;
case PROC_STS_TEA: /* Terminated abnormally. */
LogRel(("Guest process (PID %u) terminated abnormally with exit code = %u\n",
break;
case PROC_STS_TES: /* Terminated through signal. */
LogRel(("Guest process (PID %u) terminated through signal with exit code = %u\n",
break;
case PROC_STS_TOK:
LogRel(("Guest process (PID %u) timed out and was killed\n", pCBData->u32PID)); /** @todo Add process name */
break;
case PROC_STS_TOA:
LogRel(("Guest process (PID %u) timed out and could not be killed\n", pCBData->u32PID)); /** @todo Add process name */
break;
case PROC_STS_DWN:
LogRel(("Guest process (PID %u) exited because system is shutting down\n", pCBData->u32PID)); /** @todo Add process name */
break;
case PROC_STS_ERROR:
LogRel(("Guest process (PID %u) could not be started because of rc=%Rrc\n",
break;
default:
break;
}
/* Handle process map. */
/** @todo How to deal with multiple updates at once? */
{
{
/* Not found, add to map. */
newProcess.mFlags = 0;
}
else /* Update map. */
{
}
}
}
else
{
|| fCanceled) /* If canceled we have to report E_FAIL! */
{
LogFlowFunc(("Process (context ID=%u, status=%u) reported error: %s\n",
}
}
}
else
LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
return vrc;
}
/* Function for handling the execution output notification. */
{
int rc = VINF_SUCCESS;
{
/* Make sure we really got something! */
{
/* Allocate data buffer and copy it */
}
else
{
}
/* Was progress canceled before? */
{
}
else
}
else
LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
return rc;
}
{
int rc = VINF_SUCCESS;
{
}
return rc;
}
{
}
{
}
/* No locking here; */
{
{
}
/* Notify outstanding waits for progress ... */
{
/*
* Assume we didn't complete to make sure we clean up even if the
* following call fails.
*/
if (!fCompleted)
{
/* Only cancel if not canceled before! */
/*
* To get waitForCompletion completed (unblocked) we have to notify it if necessary (only
* cancle won't work!). This could happen if the client thread (e.g. VBoxService, thread of a spawned process)
* is disconnecting without having the chance to sending a status message before, so we
* progress object to become signalled.
*/
}
/*
* Do *not* NULL pProgress here, because waiting function like executeProcess()
* will still rely on this object for checking whether they have to give up!
*/
}
}
/* Adds a callback with a user provided data block and an optional progress object
* to the callback map. A callback is identified by a unique context ID which is used
* to identify a callback from the guest side. */
uint32_t Guest::addCtrlCallbackContext(eVBoxGuestCtrlCallbackType enmType, void *pvData, uint32_t cbData, Progress *pProgress)
{
/** @todo Put this stuff into a constructor! */
/* Create a new context ID and assign it. */
uint32_t uNewContext = 0;
do
{
/* Create a new context ID ... */
if (uNewContext == UINT32_MAX)
/* Is the context ID already used? */
uint32_t nCallbacks = 0;
&& uNewContext > 0)
{
/* We apparently got an unused context ID, let's use it! */
}
else
{
/* Should never happen ... */
{
}
AssertReleaseMsg(uNewContext, ("No free context ID found! uNewContext=%u, nCallbacks=%u", uNewContext, nCallbacks));
}
#if 0
{
{
}
}
#endif
return uNewContext;
}
#endif /* VBOX_WITH_GUEST_CONTROL */
{
/** @todo r=bird: Eventually we should clean up all the timeout parameters
* in the API and have the same way of specifying infinite waits! */
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
/* Do not allow anonymous executions (with system rights). */
AutoCaller autoCaller(this);
if (aFlags != 0) /* Flags are not supported at the moment. */
return E_INVALIDARG;
try
{
/*
* Create progress object. Note that this is a multi operation
* object to perform the following steps:
* - Operation 2 (1): Wait for process to exit.
* If this progress completed successfully (S_OK), the process
* occured.
*/
{
TRUE,
2, /* Number of operations. */
}
/*
* Prepare process execution.
*/
int vrc = VINF_SUCCESS;
/* Adjust timeout */
if (aTimeoutMS == 0)
/* Prepare arguments. */
if (aArguments > 0)
{
}
if (RT_SUCCESS(vrc))
{
uint32_t uContextID = 0;
if (uNumArgs > 0)
if (RT_SUCCESS(vrc))
{
/* Prepare environment. */
if (aEnvironment > 0)
{
{
if (RT_FAILURE(vrc))
break;
}
}
LogRel(("Executing guest process \"%s\" as user \"%s\" ...\n",
if (RT_SUCCESS(vrc))
{
PCALLBACKDATAEXECSTATUS pData = (PCALLBACKDATAEXECSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECSTATUS));
Assert(uContextID > 0);
int i = 0;
{
/* Make sure mParent is valid, so set the read lock while using.
* Do not keep this lock while doing the actual call, because in the meanwhile
* another thread could request a write lock which would be a bad idea ... */
/* Forward the information to the VMM device. */
}
if (vmmDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
i, paParms);
}
else
}
}
if (RT_SUCCESS(vrc))
{
/*
* Wait for the HGCM low level callback until the process
* has been started (or something went wrong). This is necessary to
* get the PID.
*/
{
/*
* Wait for the first stage (=0) to complete (that is starting the process).
*/
{
/* Was the operation canceled by one of the parties? */
if (!fCanceled)
{
/* Did we get some status? */
{
case PROC_STS_STARTED:
/* Process is (still) running; get PID. */
break;
/* In any other case the process either already
* terminated or something else went wrong, so no PID ... */
case PROC_STS_TEN: /* Terminated normally. */
case PROC_STS_TEA: /* Terminated abnormally. */
case PROC_STS_TES: /* Terminated through signal. */
case PROC_STS_TOK:
case PROC_STS_TOA:
case PROC_STS_DWN:
/*
* Process (already) ended, but we want to get the
* PID anyway to retrieve the output in a later call.
*/
break;
case PROC_STS_ERROR:
break;
case PROC_STS_UNDEFINED:
break;
default:
break;
}
}
else /* Operation was canceled. */
}
else /* Operation did not complete within time. */
vrc = VERR_TIMEOUT;
/*
* Do *not* remove the callback yet - we might wait with the IProgress object on something
* else (like end of process) ...
*/
if (RT_FAILURE(vrc))
{
else if (vrc == VERR_PATH_NOT_FOUND)
else if (vrc == VERR_BAD_EXE_FORMAT)
else if (vrc == VERR_AUTHENTICATION_FAILURE)
else if (vrc == VERR_TIMEOUT)
else if (vrc == VERR_CANCELLED)
tr("The execution operation was canceled"));
else if (vrc == VERR_PERMISSION_DENIED)
else
{
else
}
}
else /* Execution went fine. */
{
/* Return the progress to the caller. */
}
}
else /* Callback context not found; should never happen! */
}
else /* HGCM related error codes .*/
{
if (vrc == VERR_INVALID_VM_HANDLE)
tr("VMM device is not available (is the VM running?)"));
else if (vrc == VERR_TIMEOUT)
tr("The guest execution service is not ready"));
else if (vrc == VERR_HGCM_SERVICE_NOT_FOUND)
tr("The guest execution service is not available"));
else /* HGCM call went wrong. */
}
for (unsigned i = 0; i < uNumArgs; i++)
}
if (RT_FAILURE(vrc))
LogRel(("Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP Guest::GetProcessOutput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, LONG64 aSize, ComSafeArrayOut(BYTE, aData))
{
/** @todo r=bird: Eventually we should clean up all the timeout parameters
* in the API and have the same way of specifying infinite waits! */
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
if (aSize < 0)
if (aFlags != 0) /* Flags are not supported at the moment. */
AutoCaller autoCaller(this);
try
{
/*
* Create progress object.
* This progress object, compared to the one in executeProgress() above,
* is only local and is used to determine whether the operation finished
* or got canceled.
*/
{
TRUE);
}
/* Adjust timeout */
if (aTimeoutMS == 0)
/* Search for existing PID. */
Assert(uContextID > 0);
int i = 0;
int vrc = VINF_SUCCESS;
{
{
/* Make sure mParent is valid, so set the read lock while using.
* Do not keep this lock while doing the actual call, because in the meanwhile
* another thread could request a write lock which would be a bad idea ... */
/* Forward the information to the VMM device. */
}
if (vmmDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
i, paParms);
}
}
if (RT_SUCCESS(vrc))
{
/*
* Wait for the HGCM low level callback until the process
* has been started (or something went wrong). This is necessary to
* get the PID.
*/
{
/* Wait until operation completed. */
/* Was the operation canceled by one of the parties? */
if (!fCanceled)
{
&& fCompleted)
{
/* Did we get some output? */
{
/* Do we need to resize the array? */
/* Fill output in supplied out buffer. */
}
else
}
else /* If callback not called within time ... well, that's a timeout! */
vrc = VERR_TIMEOUT;
}
else /* Operation was canceled. */
{
}
if (RT_FAILURE(vrc))
{
if (vrc == VERR_NO_DATA)
{
/* This is not an error we want to report to COM. */
}
else if (vrc == VERR_TIMEOUT)
{
}
else if (vrc == VERR_CANCELLED)
{
tr("The output operation was canceled"));
}
else
{
}
}
{
/*
* Destroy locally used progress object.
*/
}
/* Remove callback context (not used anymore). */
}
else /* PID lookup failed. */
}
else /* HGCM operation failed. */
/* Cleanup. */
/* If something failed (or there simply was no data, indicated by VERR_NO_DATA,
* we return an empty array so that the frontend knows when to give up. */
outputData.resize(0);
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
AutoCaller autoCaller(this);
try
{
{
}
else
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
/**
* Sets the general Guest Additions information like
* API (interface) version and OS type. Gets called by
* vmmdevUpdateGuestInfo.
*
* @param aInterfaceVersion
* @param aOsType
*/
{
AutoCaller autoCaller(this);
/*
* Note: The Guest Additions API (interface) version is deprecated
* and will not be used anymore! We might need it to at least report
* something as version number if *really* ancient Guest Additions are
* installed (without the guest version + revision properties having set).
*/
/*
* Older Additions rely on the Additions API version whether they
* are assumed to be active or not. Since newer Additions do report
* the Additions version *before* calling this function (by calling
* VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
* in that order) we can tell apart old and new Additions here. Old
* Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
* so they just rely on the aInterfaceVersion string (which gets set by
* VMMDevReportGuestInfo).
*
* So only mark the Additions as being active (run level = system) when we
* don't have the Additions version set.
*/
{
if (aInterfaceVersion.isEmpty())
else
}
/*
* Older Additions didn't have this finer grained capability bit,
* so enable it by default. Newer Additions will not enable this here
* and use the setSupportedFeatures function instead.
*/
/*
* Note! There is a race going on between setting mAdditionsRunLevel and
* its real status when using new(er) Guest Additions.
*/
}
/**
* Sets the Guest Additions version information details.
* Gets called by vmmdevUpdateGuestInfo2.
*
* @param aAdditionsVersion
* @param aVersionName
*/
{
AutoCaller autoCaller(this);
if (!aVersionName.isEmpty())
/*
* aVersionName could be "x.y.z_BETA1_FOOBAR", so append revision manually to
* become "x.y.z_BETA1_FOOBARr12345".
*/
else /* aAdditionsVersion is in x.y.zr12345 format. */
}
/**
* Sets the status of a certain Guest Additions facility.
* Gets called by vmmdevUpdateGuestStatus.
*
* @param Facility
* @param Status
* @param ulFlags
*/
void Guest::setAdditionsStatus(VBoxGuestStatusFacility Facility, VBoxGuestStatusCurrent Status, ULONG ulFlags)
{
AutoCaller autoCaller(this);
/* First check for disabled status. */
|| ( Facility == VBoxGuestStatusFacility_All
)
)
)
{
}
else if (uCurFacility >= VBoxGuestStatusFacility_VBoxTray)
{
}
else if (uCurFacility >= VBoxGuestStatusFacility_VBoxService)
{
}
else if (uCurFacility >= VBoxGuestStatusFacility_VBoxGuestDriver)
{
}
else /* Should never happen! */
}
/**
* Sets the supported features (and whether they are active or not).
*
* @param fCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
* @param fActive No idea what this is supposed to be, it's always 0 and
* not references by this method.
*/
{
AutoCaller autoCaller(this);
/** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */