GuestCtrlImpl.cpp revision 301777b8631007fd378f6e36d415a1a5a860e551
/* $Id$ */
/** @file
* VirtualBox COM class implementation: Guest
*/
/*
* Copyright (C) 2006-2011 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
#include <memory>
{
enum TaskType
{
/** Copies a file from host to the guest. */
CopyFileToGuest = 50,
/** Copies a file from guest to the host. */
CopyFileFromGuest = 55,
/** Update Guest Additions by directly copying the required installer
* off the .ISO file, transfer it to the guest and execute the installer
* with system privileges. */
UpdateGuestAdditions = 100
};
{}
virtual ~TaskGuest() {}
int startThread();
static HRESULT setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, const char * pszText, ...);
static HRESULT setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, ComObjPtr<Guest> pGuest);
/* Task data. */
};
{
"Guest::Task");
if (RT_FAILURE(vrc))
return vrc;
}
/* static */
{
{
#ifdef VBOX_WITH_GUEST_CONTROL
case TaskGuest::CopyFileToGuest:
{
break;
}
case TaskGuest::CopyFileFromGuest:
{
break;
}
case TaskGuest::UpdateGuestAdditions:
{
break;
}
#endif
default:
break;
}
return VINF_SUCCESS;
}
/* static */
{
if (pTask &&
{
if (fCanceled)
return -1;
}
return VINF_SUCCESS;
}
/* static */
HRESULT Guest::TaskGuest::setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, const char *pszText, ...)
{
&& !fCanceled
&& !fCompleted)
{
va);
return hr2;
}
return S_OK;
}
/* static */
HRESULT Guest::TaskGuest::setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, ComObjPtr<Guest> pGuest)
{
}
#ifdef VBOX_WITH_GUEST_CONTROL
{
AutoCaller autoCaller(this);
/*
* Do *not* take a write lock here since we don't (and won't)
* touch any class-specific data (of IGuest) here - only the member functions
* which get called here can do that.
*/
try
{
/* Does our source file exist? */
{
}
else
{
if (RT_FAILURE(vrc))
{
}
else
{
if (RT_FAILURE(vrc))
{
}
else
{
/*
* Prepare tool command line.
*/
char szOutput[RTPATH_MAX];
if (RTStrPrintf(szOutput, sizeof(szOutput), "--output=%s", aTask->strDest.c_str()) <= sizeof(szOutput) - 1)
{
/*
* Normalize path slashes, based on the detected guest.
*/
{
/* We have a Windows guest. */
}
else /* ... or something which isn't from Redmond ... */
{
}
}
else
{
}
{
LogRel(("Copying file \"%s\" to guest \"%s\" (%u bytes) ...\n",
/*
* Okay, since we gathered all stuff we need until now to start the
* actual copying, start the guest part now.
*/
5 * 1000 /* Wait 5s for getting the process started. */,
}
{
size_t cbTransfered = 0;
&& !fCompleted)
{
if (!cbToRead)
cbRead = 0;
else
{
/*
* Some other error occured? There might be a chance that RTFileRead
* print a generic error.
*/
if (RT_FAILURE(vrc))
{
break;
}
}
/* Resize buffer to reflect amount we just have read.
* Size 0 is allowed! */
/* Did we reach the end of the content we want to transfer (last chunk)? */
/* Did we reach the last block which is exactly _64K? */
/* ... or does the user want to cancel? */
&& fCanceled)
)
{
}
/* Transfer the current chunk ... */
10 * 1000 /* Wait 10s for getting the input data transfered. */,
{
break;
}
/* End of file reached? */
if (cbToRead == 0)
break;
/* Did the user cancel the operation above? */
if (fCanceled)
break;
/* Progress canceled by Main API? */
&& fCanceled)
{
break;
}
}
{
/*
* If we got here this means the started process either was completed,
* canceled or we simply got all stuff transferred.
*/
rc = pGuest->waitForProcessStatusChange(uPID, &retStatus, &uRetExitCode, 10 * 1000 /* 10s timeout. */);
{
}
else
{
if ( uRetExitCode != 0
{
}
}
}
{
if (fCanceled)
{
/*
* In order to make the progress object to behave nicely, we also have to
* notify the object with a complete event when it's canceled.
*/
}
else
{
/*
* Even if we succeeded until here make sure to check whether we really transfered
* everything.
*/
if ( cbSize > 0
&& cbTransfered == 0)
{
/* If nothing was transfered but the file size was > 0 then "vbox_cat" wasn't able to write
* to the destination -> access denied. */
}
else if (cbTransfered < cbSize)
{
/* If we did not copy all let the user know. */
}
else /* Yay, all went fine! */
}
}
}
}
}
}
}
{
}
/* Clean up */
return VINF_SUCCESS;
}
{
AutoCaller autoCaller(this);
/*
* Do *not* take a write lock here since we don't (and won't)
* touch any class-specific data (of IGuest) here - only the member functions
* which get called here can do that.
*/
try
{
#if 0
/* Does our source file exist? */
{
}
else
{
if (RT_FAILURE(vrc))
{
}
else
{
if (RT_FAILURE(vrc))
{
}
else
{
/*
* Prepare tool command line.
*/
char szOutput[RTPATH_MAX];
if (RTStrPrintf(szOutput, sizeof(szOutput), "--output=%s", aTask->strDest.c_str()) <= sizeof(szOutput) - 1)
{
/*
* Normalize path slashes, based on the detected guest.
*/
{
/* We have a Windows guest. */
}
else /* ... or something which isn't from Redmond ... */
{
}
}
else
{
}
{
LogRel(("Copying file \"%s\" to guest \"%s\" (%u bytes) ...\n",
/*
* Okay, since we gathered all stuff we need until now to start the
* actual copying, start the guest part now.
*/
5 * 1000 /* Wait 5s for getting the process started. */,
}
{
size_t cbTransfered = 0;
&& !fCompleted)
{
if (!cbToRead)
cbRead = 0;
else
{
/*
* Some other error occured? There might be a chance that RTFileRead
* print a generic error.
*/
if (RT_FAILURE(vrc))
{
break;
}
}
/* Resize buffer to reflect amount we just have read.
* Size 0 is allowed! */
/* Did we reach the end of the content we want to transfer (last chunk)? */
/* Did we reach the last block which is exactly _64K? */
/* ... or does the user want to cancel? */
&& fCanceled)
)
{
}
/* Transfer the current chunk ... */
10 * 1000 /* Wait 10s for getting the input data transfered. */,
{
break;
}
/* End of file reached? */
if (cbToRead == 0)
break;
/* Did the user cancel the operation above? */
if (fCanceled)
break;
/* Progress canceled by Main API? */
&& fCanceled)
{
break;
}
}
{
/*
* If we got here this means the started process either was completed,
* canceled or we simply got all stuff transferred.
*/
rc = pGuest->waitForProcessStatusChange(uPID, &retStatus, &uRetExitCode, 10 * 1000 /* 10s timeout. */);
{
}
else
{
if ( uRetExitCode != 0
{
}
}
}
{
if (fCanceled)
{
/*
* In order to make the progress object to behave nicely, we also have to
* notify the object with a complete event when it's canceled.
*/
}
else
{
/*
* Even if we succeeded until here make sure to check whether we really transfered
* everything.
*/
if ( cbSize > 0
&& cbTransfered == 0)
{
/* If nothing was transfered but the file size was > 0 then "vbox_cat" wasn't able to write
* to the destination -> access denied. */
}
else if (cbTransfered < cbSize)
{
/* If we did not copy all let the user know. */
}
else /* Yay, all went fine! */
}
}
}
}
}
}
#endif
}
{
}
/* Clean up */
return VINF_SUCCESS;
}
{
AutoCaller autoCaller(this);
/*
* Do *not* take a write lock here since we don't (and won't)
* touch any class-specific data (of IGuest) here - only the member functions
* which get called here can do that.
*/
try
{
/*
* Determine guest OS type and the required installer image.
* At the moment only Windows guests are supported.
*/
{
{
installerImage = "VBOXWINDOWSADDITIONS_AMD64.EXE";
else
installerImage = "VBOXWINDOWSADDITIONS_X86.EXE";
/* Since the installers are located in the root directory,
* no further path processing needs to be done (yet). */
}
else /* Everything else is not supported (yet). */
Guest::tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
osTypeIdUtf8.c_str());
}
else
/*
* Try to open the .ISO file and locate the specified installer.
*/
if (RT_FAILURE(vrc))
{
}
else
{
if ( RT_SUCCESS(vrc)
&& cbOffset
&& cbLength)
{
if (RT_FAILURE(vrc))
}
else
{
switch (vrc)
{
case VERR_FILE_NOT_FOUND:
break;
default:
Guest::tr("An unknown error (%Rrc) occured while retrieving information of setup file on installation medium \"%s\""),
break;
}
}
/* Specify the ouput path on the guest side. */
if (RT_SUCCESS(vrc))
{
/* Okay, we're ready to start our copy routine on the guest! */
/* Prepare command line args. */
{
/*
* Start built-in "vbox_cat" tool (inside VBoxService) to
*/
5 * 1000 /* Wait 5s for getting the process started. */,
{
/* Errors which return VBOX_E_NOT_SUPPORTED can be safely skipped by the caller
* to silently fall back to "normal" (old) .ISO mounting. */
/* Due to a very limited COM error range we use vrc for a more detailed error
* lookup to figure out what went wrong. */
switch (vrc)
{
/* Guest execution service is not (yet) ready. This basically means that either VBoxService
* is not running (yet) or that the Guest Additions are too old (because VBoxService does not
* support the guest execution feature in this version). */
case VERR_NOT_FOUND:
LogRel(("Guest Additions seem not to be installed yet\n"));
break;
/* Getting back a VERR_INVALID_PARAMETER indicates that the installed Guest Additions are supporting the guest
* execution but not the built-in "vbox_cat" tool of VBoxService (< 4.0). */
case VERR_INVALID_PARAMETER:
LogRel(("Guest Additions are installed but don't supported automatic updating\n"));
break;
case VERR_TIMEOUT:
LogRel(("Guest was unable to start copying the Guest Additions setup within time\n"));
break;
default:
break;
}
}
else
{
LogRel(("Copying Guest Additions installer \"%s\" to \"%s\" on guest ...\n",
/* Wait for process to exit ... */
&& !fCompleted)
{
/* cbLength contains remaining bytes of our installer file
* opened above to read. */
if (cbToRead)
{
if ( cbRead
&& RT_SUCCESS(vrc))
{
/* Resize buffer to reflect amount we just have read. */
if (cbRead > 0)
/* Did we reach the end of the content we want to transfer (last chunk)? */
/* Did we reach the last block which is exactly _64K? */
/* ... or does the user want to cancel? */
&& fCanceled)
)
{
}
/* Transfer the current chunk ... */
#ifdef DEBUG_andy
#endif
10 * 1000 /* Wait 10s for getting the input data transfered. */,
{
break;
}
/* If task was canceled above also cancel the process execution. */
if (fCanceled)
progressCat->Cancel();
#ifdef DEBUG_andy
#endif
}
else if (RT_FAILURE(vrc))
{
Guest::tr("Error while reading setup file \"%s\" (To read: %u, Size: %u) from installation medium (%Rrc)"),
}
}
/* Internal progress canceled? */
&& fCanceled)
{
break;
}
}
}
}
}
RTIsoFsClose(&iso);
&& !fCanceled
)
)
{
/*
* Installer was transferred successfully, so let's start it
* (with system rights).
*/
LogRel(("Preparing to execute Guest Additions update ...\n"));
/* Prepare command line args for installer. */
/** @todo Only Windows! */
installerArgs.push_back(Bstr(strInstallerPath).raw()); /* The actual (internal) installer image (as argv[0]). */
/* Note that starting at Windows Vista the lovely session 0 separation applies:
* of VBoxService (system rights!) we're not able to show any UI. */
/* Don't quit VBoxService during upgrade because it still is used for this
* piece of code we're in right now (that is, here!) ... */
/* Tell the installer to report its current installation status
* using a running VBoxTray instance via balloon messages in the
* Windows taskbar. */
/*
* Start the just copied over installer with system rights
* in silent mode on the guest. Don't use the hidden flag since there
* may be pop ups the user has to process.
*/
10 * 1000 /* Wait 10s for getting the process started */,
{
LogRel(("Guest Additions update is running ...\n"));
/* If the caller does not want to wait for out guest update process to end,
* complete the progress object now so that the caller can do other work. */
else
/* Wait until the Guest Additions installer finishes ... */
&& !fCompleted)
{
&& fCanceled)
{
break;
}
/* Progress canceled by Main API? */
&& fCanceled)
{
break;
}
RTThreadSleep(100);
}
{
if (fCompleted)
{
if (uRetExitCode == 0)
{
LogRel(("Guest Additions update successful!\n"));
&& !fCompleted)
}
else
{
LogRel(("Guest Additions update failed (Exit code=%u, Status=%u, Flags=%u)\n",
}
}
&& fCanceled)
{
LogRel(("Guest Additions update was canceled\n"));
Guest::tr("Guest Additions update was canceled by the guest with exit code=%u (status=%u, flags=%u)"),
}
else
{
LogRel(("Guest Additions update was canceled by the user\n"));
}
}
else
}
else
}
}
}
{
}
/* Clean up */
return VINF_SUCCESS;
}
#endif
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
#ifdef VBOX_WITH_GUEST_CONTROL
/**
* Appends environment variables to the environment block.
*
* Each var=value pair is separated by the null character ('\\0'). 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.
*
* @param pszEnvVar The environment variable=value to append to the
* environment block.
* @param ppvList This is actually a pointer to a char pointer
* variable which keeps track of the environment block
* that we're constructing.
* @param pcbList Pointer to the variable holding the current size of
* the environment block. (List is a misnomer, go
* ahead a be confused.)
* @param pcEnvVars Pointer to the variable holding count of variables
* stored in the environment block.
*/
int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars)
{
int rc = VINF_SUCCESS;
if (*ppvList)
{
rc = VERR_NO_MEMORY;
else
{
}
}
else
{
char *pszTmp;
{
/* Reset counters. */
*pcEnvVars = 0;
*pcbList = 0;
}
}
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* 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.
*
* @return IPRT status code.
* @param puContextID
* @param pCallbackData
*/
{
int rc;
/* Create a new context ID and assign it. */
uint32_t uNewContextID = 0;
for (;;)
{
/* Create a new context ID ... */
if (uNewContextID == UINT32_MAX)
/* Is the context ID already used? Try next ID ... */
if (!callbackExists(uNewContextID))
{
/* Callback with context ID was not found. This means
* we can use this context ID for our new callback we want
* to add below. */
rc = VINF_SUCCESS;
break;
}
}
if (RT_SUCCESS(rc))
{
/* Add callback with new context ID to our callback map. */
/* Report back new context ID. */
}
return rc;
}
/**
* Does not do locking!
*
* @param uContextID
*/
{
/* Notify callback (if necessary). */
{
{
}
/* Remove callback context (not used anymore). */
}
}
{
AssertReturn(uContextID, false);
}
{
if (pvData)
{
}
}
{
/* pEnmType is optional. */
/* pcbData is optional. */
{
if (pEnmType)
if (pcbData)
return VINF_SUCCESS;
}
return VERR_NOT_FOUND;
}
/* Does not do locking! Caller has to take care of it because the caller needs to
* modify the data ...*/
{
/* pcbData is optional. */
{
if (pcbData)
}
return NULL;
}
{
AssertReturn(uContextID, true);
{
}
if (pProgress)
{
&& !fCanceled)
{
return false;
}
}
return true; /* No progress / error means canceled. */
}
{
AssertReturn(uContextID, true);
{
}
if (pProgress)
{
&& fCompleted)
{
return true;
}
}
return false;
}
{
{
}
if (pProgress)
{
return VERR_CANCELLED;
return VINF_SUCCESS;
}
return VERR_NOT_FOUND;
}
/**
* TODO
*
* @return IPRT status code.
* @param uContextID
* @param iRC
* @param pszMessage
*/
{
LogFlowFunc(("Notifying callback with CID=%u, iRC=%d, pszMsg=%s ...\n",
{
}
#if 0
&& fCanceled)
{
/* If progress already canceled do nothing here. */
return VINF_SUCCESS;
}
#endif
if (pProgress)
{
/*
* Assume we didn't complete to make sure we clean up even if the
* following call fails.
*/
&& !fCompleted)
{
/*
* To get waitForCompletion completed (unblocked) we have to notify it if necessary (only
* cancel 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.
*/
if ( RT_SUCCESS(iRC)
&& !pszMessage)
{
}
else
{
}
}
/*
* Do *not* NULL pProgress here, because waiting function like executeProcess()
* will still rely on this object for checking whether they have to give up!
*/
}
/* If pProgress is not found (anymore) that's fine.
* Might be destroyed already. */
return S_OK;
}
/**
* TODO
*
* @return IPRT status code.
* @param uPID
* @param iRC
* @param pszMessage
*/
{
int vrc = VINF_SUCCESS;
{
{
break;
/* When waiting for process output while the process is destroyed,
* make sure we also destroy the actual waiting operation (internal progress object)
* in order to not block the caller. */
{
break;
}
/* When waiting for injecting process input while the process is destroyed,
* make sure we also destroy the actual waiting operation (internal progress object)
* in order to not block the caller. */
{
break;
}
default:
AssertMsgFailed(("Unknown callback type %d, iRC=%d, message=%s\n",
break;
}
if (RT_FAILURE(vrc))
break;
}
return 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.
*/
int vrc = VINF_SUCCESS;
{
else
}
if (RT_SUCCESS(vrc))
{
if (lStage < 0)
else
{
if (!callbackIsComplete(uContextID))
}
else
vrc = VERR_TIMEOUT;
}
return vrc;
}
/**
* 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;
switch (u32Function)
{
case GUEST_DISCONNECTED:
{
//LogFlowFunc(("GUEST_DISCONNECTED\n"));
PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);
AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
case GUEST_EXEC_SEND_STATUS:
{
//LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
break;
}
case GUEST_EXEC_SEND_OUTPUT:
{
//LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
break;
}
{
//LogFlowFunc(("GUEST_EXEC_SEND_INPUT_STATUS\n"));
break;
}
default:
break;
}
return rc;
}
/* Function for handling the execution start/termination notification. */
/* Callback can be called several times. */
{
/* Scope write locks as much as possible. */
{
if (pCallbackData)
{
/** @todo Copy void* buffer contents? */
}
else
AssertReleaseMsgFailed(("Process status (PID=%u) does not have allocated callback data!\n",
}
int vrc = VINF_SUCCESS;
/* Was progress canceled before? */
if (!fCbCanceled)
{
/* Do progress handling. */
{
case PROC_STS_STARTED:
break;
case PROC_STS_TEN: /* Terminated normally. */
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", pData->u32PID)); /** @todo Add process name */
break;
case PROC_STS_TOA:
LogRel(("Guest process (PID %u) timed out and could not be killed\n", pData->u32PID)); /** @todo Add process name */
break;
case PROC_STS_DWN:
LogRel(("Guest process (PID %u) killed because system is shutting down\n", pData->u32PID)); /** @todo Add process name */
/*
* If u32Flags has ExecuteProcessFlag_IgnoreOrphanedProcesses set, we don't report an error to
* our progress object. This is helpful for waiters which rely on the success of our progress object
* even if the executed process was killed because the system/VBoxService is shutting down.
*
* In this case u32Flags contains the actual execution flags reached in via Guest::ExecuteProcess().
*/
{
}
else
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? */
{
if (vrc == VERR_NOT_FOUND)
{
/* Not found, add to map. */
0 /*Flags. */);
}
else if (RT_SUCCESS(vrc))
{
/* Process found, update process map. */
0 /*Flags. */);
}
else
AssertReleaseMsgFailed(("Process was neither found nor absent!?\n"));
}
}
else
if (!callbackIsComplete(uContextID))
{
|| fCbCanceled) /* If canceled we have to report E_FAIL! */
{
/* Notify all callbacks which are still waiting on something
* which is related to the current PID. */
{
if (RT_FAILURE(vrc))
LogFlowFunc(("Failed to notify other callbacks for PID=%u\n",
}
/* Let the caller know what went wrong ... */
if (RT_FAILURE(rc2))
{
LogFlowFunc(("Failed to notify callback CID=%u for PID=%u\n",
if (RT_SUCCESS(vrc))
}
LogFlowFunc(("Process (CID=%u, status=%u) reported error: %s\n",
}
}
return vrc;
}
/* Function for handling the execution output notification. */
{
/* Scope write locks as much as possible. */
{
if (pCallbackData)
{
/* Make sure we really got something! */
{
/* Allocate data buffer and copy it */
}
else /* Nothing received ... */
{
pCallbackData->cbData = 0;
}
}
else
AssertReleaseMsgFailed(("Process output status (PID=%u) does not have allocated callback data!\n",
}
int vrc;
{
}
else
return vrc;
}
/* Function for handling the execution input status notification. */
{
/* Scope write locks as much as possible. */
{
if (pCallbackData)
{
/* Save bytes processed. */
}
else
AssertReleaseMsgFailed(("Process input status (PID=%u) does not have allocated callback data!\n",
}
return callbackNotifyComplete(uContextID);
}
{
/* u32Function is 0. */
}
{
{
return VINF_SUCCESS;
}
return VERR_ALREADY_EXISTS;
}
{
{
return VINF_SUCCESS;
}
return VERR_NOT_FOUND;
}
int Guest::processSetStatus(uint32_t u32PID, ExecuteProcessStatus_T enmStatus, uint32_t uExitCode, uint32_t uFlags)
{
{
return VINF_SUCCESS;
}
return VERR_NOT_FOUND;
}
{
if (rc == VERR_NOT_FOUND)
tr("VMM device is not available (is the VM running?)"));
else if (rc == VERR_CANCELLED)
tr("Process execution has been canceled"));
else if (rc == VERR_TIMEOUT)
tr("The guest did not respond within time"));
else
return hRC;
}
{
if (rc == VERR_INVALID_VM_HANDLE)
tr("VMM device is not available (is the VM running?)"));
else if (rc == VERR_NOT_FOUND)
tr("The guest execution service is not ready (yet)"));
else if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
tr("The guest execution service is not available"));
else /* HGCM call went wrong. */
return hRC;
}
HRESULT Guest::waitForProcessStatusChange(ULONG uPID, ExecuteProcessStatus_T *pRetStatus, ULONG *puRetExitCode, ULONG uTimeoutMS)
{
if (uTimeoutMS == 0)
do
{
/*
* Do some busy waiting within the specified time period (if any).
*/
if ( uTimeoutMS != UINT32_MAX
{
tr("The process (PID %u) did not change its status within time (%ums)"),
uPID, uTimeoutMS);
break;
}
break;
RTThreadSleep(100);
return hRC;
}
{
/* Did we get some status? */
switch (pExecStatus->u32Status)
{
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:
{
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
}
}
break;
case PROC_STS_UNDEFINED: /* . */
tr("The operation did not complete within time"));
break;
default:
AssertReleaseMsgFailed(("Process (PID %u) reported back an undefined state!\n",
pExecStatus->u32PID));
rc = E_UNEXPECTED;
break;
}
return rc;
}
#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). */
LogRel(("Executing guest process \"%s\" as user \"%s\" ...\n",
#endif
}
{
/** @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;
AutoCaller autoCaller(this);
/* Validate flags. */
if (aFlags != ExecuteProcessFlag_None)
{
&& !(aFlags & ExecuteProcessFlag_Hidden)
&& !(aFlags & ExecuteProcessFlag_NoProfile))
{
if (pRC)
}
}
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
* occurred.
*/
{
TRUE,
2, /* Number of operations. */
}
/*
* Prepare process execution.
*/
int vrc = VINF_SUCCESS;
/* Adjust timeout. If set to 0, we define
* an infinite timeout. */
if (aTimeoutMS == 0)
/* Prepare arguments. */
if (aArguments)
{
}
if (RT_SUCCESS(vrc))
{
uint32_t uContextID = 0;
if (uNumArgs > 0)
if (RT_SUCCESS(vrc))
{
/* Prepare environment. */
if (aEnvironment)
{
{
if (RT_FAILURE(vrc))
break;
}
}
if (RT_SUCCESS(vrc))
{
/* Allocate payload. */
PCALLBACKDATAEXECSTATUS pStatus = (PCALLBACKDATAEXECSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECSTATUS));
/* Create callback. */
if (RT_SUCCESS(vrc))
{
int i = 0;
/*
* If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout
* until the process was started - the process itself then gets an infinite timeout for execution.
* This is handy when we want to start a process inside a worker thread within a certain timeout
* but let the started process perform lengthly operations then.
*/
else
{
/* 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 (pVMMDev)
{
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).
*/
if (RT_SUCCESS(vrc))
{
if (RT_SUCCESS(vrc))
{
pExecStatus, aPID);
}
else
{
tr("Unable to retrieve process execution status data"));
}
}
else
/*
* Do *not* remove the callback yet - we might wait with the IProgress object on something
* else (like end of process) ...
*/
}
else
for (unsigned i = 0; i < uNumArgs; i++)
}
{
/* Return the progress to the caller. */
}
else
{
if (!pRC) /* Skip logging internal calls. */
LogRel(("Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",
}
if (pRC)
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP Guest::SetProcessInput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, ComSafeArrayIn(BYTE, aData), ULONG *aBytesWritten)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
/* Validate flags. */
if (aFlags)
{
if (!(aFlags & ProcessInputFlag_EndOfFile))
}
AutoCaller autoCaller(this);
try
{
if (RT_SUCCESS(vrc))
{
/* PID exists; check if process is still running. */
}
else
{
uint32_t uContextID = 0;
/*
* Create progress object.
* This progress object, compared to the one in executeProgress() above,
* is only single-stage local and is used to determine whether the operation
* finished or got canceled.
*/
{
TRUE /* Cancelable */);
}
/* Adjust timeout. */
if (aTimeoutMS == 0)
/* Construct callback data. */
/* Save PID + output flags for later use. */
/* Add the callback. */
if (RT_SUCCESS(vrc))
{
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 (pVMMDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
i, paParms);
}
}
}
if (RT_SUCCESS(vrc))
{
LogFlowFunc(("Waiting for HGCM callback ...\n"));
/*
* 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).
*/
if (RT_SUCCESS(vrc))
{
if (RT_SUCCESS(vrc))
{
switch (pExecStatusIn->u32Status)
{
case INPUT_STS_WRITTEN:
break;
default:
break;
}
}
else
{
tr("Unable to retrieve process input status data"));
}
}
else
}
else
{
/* Nothing to do here yet. */
}
/* The callback isn't needed anymore -- just was kept locally. */
/* Cleanup. */
}
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
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 (aSize == 0)
if (aFlags)
{
if (!(aFlags & ProcessOutputFlag_StdErr))
{
}
}
AutoCaller autoCaller(this);
try
{
if (RT_FAILURE(vrc))
{
uint32_t uContextID = 0;
/*
* Create progress object.
* This progress object, compared to the one in executeProgress() above,
* is only single-stage local and is used to determine whether the operation
* finished or got canceled.
*/
{
TRUE /* Cancelable */);
}
/* Adjust timeout. */
if (aTimeoutMS == 0)
/* Set handle ID. */
if (aFlags & ProcessOutputFlag_StdErr)
/* Construct callback data. */
/* Save PID + output flags for later use. */
/* Add the callback. */
if (RT_SUCCESS(vrc))
{
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 (pVMMDev)
{
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 for the first stage (=0) to complete (that is starting the process).
*/
if (RT_SUCCESS(vrc))
{
if (RT_SUCCESS(vrc))
{
{
/* Do we need to resize the array? */
/* Fill output in supplied out buffer. */
}
else
{
/* No data within specified timeout available. */
outputData.resize(0);
}
/* Detach output buffer to output argument. */
}
else
{
tr("Unable to retrieve process output data"));
}
}
else
}
else
{
}
/* The callback isn't needed anymore -- just was kept locally. */
/* Cleanup. */
}
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
STDMETHODIMP Guest::GetProcessStatus(ULONG aPID, ULONG *aExitCode, ULONG *aFlags, ExecuteProcessStatus_T *aStatus)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
AutoCaller autoCaller(this);
try
{
if (RT_SUCCESS(vrc))
{
}
else
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
{
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
AutoCaller autoCaller(this);
/* Validate flags. */
if (aFlags != CopyFileFlag_None)
{
if ( !(aFlags & CopyFileFlag_Recursive)
&& !(aFlags & CopyFileFlag_Update)
&& !(aFlags & CopyFileFlag_FollowLinks))
{
}
}
try
{
/* Create the progress object. */
TRUE /* aCancelable */);
/* Initialize our worker task. */
/* Assign data - aSource is the source file on the host,
* aDest reflects the full path on the guest. */
/* Don't destruct on success. */
}
{
}
{
/* Return progress to the caller. */
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
/* Do not allow anonymous executions (with system rights). */
LogRel(("Creating guest directory \"%s\" as user \"%s\" ...\n",
return directoryCreateInternal(aDirectory,
#endif
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
AutoCaller autoCaller(this);
/* Validate flags. */
if (aFlags != DirectoryCreateFlag_None)
{
if (!(aFlags & DirectoryCreateFlag_Parents))
{
}
}
try
{
/*
* Prepare tool command line.
*/
if (aMode > 0)
{
char szMode[16];
}
/*
* Execute guest process.
*/
{
5 * 1000 /* Wait 5s for getting the process started. */,
}
{
/* Wait for process to exit ... */
if (!fCompleted)
if (fCompleted)
{
{
{
}
}
}
else if (fCanceled)
tr("Guest directory creation was aborted"));
else
AssertReleaseMsgFailed(("Guest directory creation neither completed nor canceled!?\n"));
}
}
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
}
{
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
return VBOX_E_NOT_SUPPORTED;
#endif
}
STDMETHODIMP Guest::FileQuerySize(IN_BSTR aFile, IN_BSTR aUserName, IN_BSTR aPassword, LONG64 *aSize)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
return VBOX_E_NOT_SUPPORTED;
#endif
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else /* VBOX_WITH_GUEST_CONTROL */
AutoCaller autoCaller(this);
/* Validate flags. */
if (aFlags)
{
}
try
{
/* Create the progress object. */
TRUE /* aCancelable */);
/* Initialize our worker task. */
/* Assign data - in that case aSource is the full path
* to the Guest Additions .ISO we want to mount. */
/* Don't destruct on success. */
}
{
}
{
/* Return progress to the caller. */
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}