GuestCtrlImpl.cpp revision 7f1968b754f2c734452aad469522b5c76f559129
/* $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;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include "GuestImpl.h"
#include "Global.h"
#include "ConsoleImpl.h"
#include "ProgressImpl.h"
#include "VMMDev.h"
#include "AutoCaller.h"
#include "Logging.h"
#include <VBox/VMMDev.h>
#ifdef VBOX_WITH_GUEST_CONTROL
# include <VBox/com/array.h>
# include <VBox/com/ErrorInfo.h>
#endif
#include <iprt/cpp/utils.h>
#include <iprt/file.h>
#include <iprt/getopt.h>
#include <iprt/isofs.h>
#include <iprt/list.h>
#include <iprt/path.h>
#include <VBox/vmm/pgm.h>
#include <memory>
struct Guest::TaskGuest
{
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
};
TaskGuest(TaskType aTaskType, Guest *aThat, Progress *aProgress)
: taskType(aTaskType),
pGuest(aThat),
progress(aProgress),
rc(S_OK)
{}
virtual ~TaskGuest() {}
int startThread();
static int taskThread(RTTHREAD aThread, void *pvUser);
static int uploadProgress(unsigned uPercent, void *pvUser);
static HRESULT setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, const char * pszText, ...);
static HRESULT setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, ComObjPtr<Guest> pGuest);
TaskType taskType;
Guest *pGuest;
ComObjPtr<Progress> progress;
HRESULT rc;
/* Task data. */
Utf8Str strSource;
Utf8Str strDest;
Utf8Str strUserName;
Utf8Str strPassword;
ULONG uFlags;
};
int Guest::TaskGuest::startThread()
{
int vrc = RTThreadCreate(NULL, Guest::TaskGuest::taskThread, this,
0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0,
"Guest::Task");
if (RT_FAILURE(vrc))
return Guest::setErrorStatic(E_FAIL, Utf8StrFmt("Could not create taskThreadGuest (%Rrc)\n", vrc));
return vrc;
}
/* static */
DECLCALLBACK(int) Guest::TaskGuest::taskThread(RTTHREAD /* aThread */, void *pvUser)
{
std::auto_ptr<TaskGuest> task(static_cast<TaskGuest*>(pvUser));
AssertReturn(task.get(), VERR_GENERAL_FAILURE);
Guest *pGuest = task->pGuest;
LogFlowFuncEnter();
LogFlowFunc(("Guest %p\n", pGuest));
HRESULT rc = S_OK;
switch (task->taskType)
{
#ifdef VBOX_WITH_GUEST_CONTROL
case TaskGuest::CopyFileToGuest:
{
rc = pGuest->taskCopyFileToGuest(task.get());
break;
}
case TaskGuest::CopyFileFromGuest:
{
rc = pGuest->taskCopyFileFromGuest(task.get());
break;
}
case TaskGuest::UpdateGuestAdditions:
{
rc = pGuest->taskUpdateGuestAdditions(task.get());
break;
}
#endif
default:
AssertMsgFailed(("Invalid task type %u specified!\n", task->taskType));
break;
}
LogFlowFunc(("rc=%Rhrc\n", rc));
LogFlowFuncLeave();
return VINF_SUCCESS;
}
/* static */
int Guest::TaskGuest::uploadProgress(unsigned uPercent, void *pvUser)
{
Guest::TaskGuest *pTask = *(Guest::TaskGuest**)pvUser;
if (pTask &&
!pTask->progress.isNull())
{
BOOL fCanceled;
pTask->progress->COMGETTER(Canceled)(&fCanceled);
if (fCanceled)
return -1;
pTask->progress->SetCurrentOperationProgress(uPercent);
}
return VINF_SUCCESS;
}
/* static */
HRESULT Guest::TaskGuest::setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, const char *pszText, ...)
{
BOOL fCanceled;
BOOL fCompleted;
if ( SUCCEEDED(pProgress->COMGETTER(Canceled(&fCanceled)))
&& !fCanceled
&& SUCCEEDED(pProgress->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
{
va_list va;
va_start(va, pszText);
HRESULT hr2 = pProgress->notifyCompleteV(hr,
COM_IIDOF(IGuest),
Guest::getStaticComponentName(),
pszText,
va);
va_end(va);
if (hr2 == S_OK) /* If unable to retrieve error, return input error. */
hr2 = hr;
return hr2;
}
return S_OK;
}
/* static */
HRESULT Guest::TaskGuest::setProgressErrorInfo(HRESULT hr, ComObjPtr<Progress> pProgress, ComObjPtr<Guest> pGuest)
{
return setProgressErrorInfo(hr, pProgress,
Utf8Str(com::ErrorInfo((IGuest*)pGuest, COM_IIDOF(IGuest)).getText()).c_str());
}
#ifdef VBOX_WITH_GUEST_CONTROL
HRESULT Guest::taskCopyFileToGuest(TaskGuest *aTask)
{
LogFlowFuncEnter();
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/*
* 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.
*/
HRESULT rc = S_OK;
try
{
Guest *pGuest = aTask->pGuest;
AssertPtr(pGuest);
/* Does our source file exist? */
if (!RTFileExists(aTask->strSource.c_str()))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Source file \"%s\" does not exist, or is not a file"),
aTask->strSource.c_str());
}
else
{
RTFILE fileSource;
int vrc = RTFileOpen(&fileSource, aTask->strSource.c_str(),
RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not open source file \"%s\" for reading (%Rrc)"),
aTask->strSource.c_str(), vrc);
}
else
{
uint64_t cbSize;
vrc = RTFileGetSize(fileSource, &cbSize);
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not query file size of \"%s\" (%Rrc)"),
aTask->strSource.c_str(), vrc);
}
else
{
com::SafeArray<IN_BSTR> args;
com::SafeArray<IN_BSTR> env;
/*
* 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.
*/
Utf8Str osType = mData.mOSTypeId;
if ( osType.contains("Microsoft", Utf8Str::CaseInsensitive)
|| osType.contains("Windows", Utf8Str::CaseInsensitive))
{
/* We have a Windows guest. */
RTPathChangeToDosSlashes(szOutput, true /* Force conversion. */);
}
else /* ... or something which isn't from Redmond ... */
{
RTPathChangeToUnixSlashes(szOutput, true /* Force conversion. */);
}
args.push_back(Bstr(szOutput).raw()); /* We want to write a file ... */
}
else
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Error preparing command line"));
}
ComPtr<IProgress> execProgress;
ULONG uPID;
if (SUCCEEDED(rc))
{
LogRel(("Copying file \"%s\" to guest \"%s\" (%u bytes) ...\n",
aTask->strSource.c_str(), aTask->strDest.c_str(), cbSize));
/*
* Okay, since we gathered all stuff we need until now to start the
* actual copying, start the guest part now.
*/
rc = pGuest->ExecuteProcess(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
ExecuteProcessFlag_Hidden
| ExecuteProcessFlag_WaitForProcessStartOnly,
ComSafeArrayAsInParam(args),
ComSafeArrayAsInParam(env),
Bstr(aTask->strUserName).raw(),
Bstr(aTask->strPassword).raw(),
5 * 1000 /* Wait 5s for getting the process started. */,
&uPID, execProgress.asOutParam());
if (FAILED(rc))
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
if (SUCCEEDED(rc))
{
BOOL fCompleted = FALSE;
BOOL fCanceled = FALSE;
size_t cbToRead = cbSize;
size_t cbTransfered = 0;
size_t cbRead;
SafeArray<BYTE> aInputData(_64K);
while ( SUCCEEDED(execProgress->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
{
if (!cbToRead)
cbRead = 0;
else
{
vrc = RTFileRead(fileSource, (uint8_t*)aInputData.raw(),
RT_MIN(cbToRead, _64K), &cbRead);
/*
* Some other error occured? There might be a chance that RTFileRead
* could not resolve/map the native error code to an IPRT code, so just
* print a generic error.
*/
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not read from file \"%s\" (%Rrc)"),
aTask->strSource.c_str(), vrc);
break;
}
}
/* Resize buffer to reflect amount we just have read.
* Size 0 is allowed! */
aInputData.resize(cbRead);
ULONG uFlags = ProcessInputFlag_None;
/* Did we reach the end of the content we want to transfer (last chunk)? */
if ( (cbRead < _64K)
/* Did we reach the last block which is exactly _64K? */
|| (cbToRead - cbRead == 0)
/* ... or does the user want to cancel? */
|| ( SUCCEEDED(aTask->progress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
)
{
uFlags |= ProcessInputFlag_EndOfFile;
}
/* Transfer the current chunk ... */
ULONG uBytesWritten;
rc = pGuest->SetProcessInput(uPID, uFlags,
10 * 1000 /* Wait 10s for getting the input data transfered. */,
ComSafeArrayAsInParam(aInputData), &uBytesWritten);
if (FAILED(rc))
{
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
break;
}
Assert(cbRead <= cbToRead);
Assert(cbToRead >= cbRead);
cbToRead -= cbRead;
cbTransfered += uBytesWritten;
Assert(cbTransfered <= cbSize);
aTask->progress->SetCurrentOperationProgress(cbTransfered / (cbSize / 100.0));
/* End of file reached? */
if (cbToRead == 0)
break;
/* Did the user cancel the operation above? */
if (fCanceled)
break;
/* Progress canceled by Main API? */
if ( SUCCEEDED(execProgress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Copy operation of file \"%s\" was canceled on guest side"),
aTask->strSource.c_str());
break;
}
}
if (SUCCEEDED(rc))
{
/*
* If we got here this means the started process either was completed,
* canceled or we simply got all stuff transferred.
*/
ExecuteProcessStatus_T retStatus;
ULONG uRetExitCode;
rc = pGuest->waitForProcessStatusChange(uPID, &retStatus, &uRetExitCode, 10 * 1000 /* 10s timeout. */);
if (FAILED(rc))
{
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
else
{
if ( uRetExitCode != 0
|| retStatus != ExecuteProcessStatus_TerminatedNormally)
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Guest reported error %u while copying file \"%s\" to \"%s\""),
uRetExitCode, aTask->strSource.c_str(), aTask->strDest.c_str());
}
}
}
if (SUCCEEDED(rc))
{
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.
*/
aTask->progress->notifyComplete(VBOX_E_IPRT_ERROR,
COM_IIDOF(IGuest),
Guest::getStaticComponentName(),
Guest::tr("Copying file \"%s\" canceled"), aTask->strSource.c_str());
}
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. */
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Access denied when copying file \"%s\" to \"%s\""),
aTask->strSource.c_str(), aTask->strDest.c_str());
}
else if (cbTransfered < cbSize)
{
/* If we did not copy all let the user know. */
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Copying file \"%s\" failed (%u/%u bytes transfered)"),
aTask->strSource.c_str(), cbTransfered, cbSize);
}
else /* Yay, all went fine! */
aTask->progress->notifyComplete(S_OK);
}
}
}
}
RTFileClose(fileSource);
}
}
}
catch (HRESULT aRC)
{
rc = aRC;
}
/* Clean up */
aTask->rc = rc;
LogFlowFunc(("rc=%Rhrc\n", rc));
LogFlowFuncLeave();
return VINF_SUCCESS;
}
HRESULT Guest::taskCopyFileFromGuest(TaskGuest *aTask)
{
LogFlowFuncEnter();
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/*
* 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.
*/
HRESULT rc = S_OK;
try
{
Guest *pGuest = aTask->pGuest;
AssertPtr(pGuest);
#if 0
/* Does our source file exist? */
if (!RTFileExists(aTask->strSource.c_str()))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Source file \"%s\" does not exist, or is not a file"),
aTask->strSource.c_str());
}
else
{
RTFILE fileSource;
int vrc = RTFileOpen(&fileSource, aTask->strSource.c_str(),
RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not open source file \"%s\" for reading (%Rrc)"),
aTask->strSource.c_str(), vrc);
}
else
{
uint64_t cbSize;
vrc = RTFileGetSize(fileSource, &cbSize);
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not query file size of \"%s\" (%Rrc)"),
aTask->strSource.c_str(), vrc);
}
else
{
com::SafeArray<IN_BSTR> args;
com::SafeArray<IN_BSTR> env;
/*
* 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.
*/
Utf8Str osType = mData.mOSTypeId;
if ( osType.contains("Microsoft", Utf8Str::CaseInsensitive)
|| osType.contains("Windows", Utf8Str::CaseInsensitive))
{
/* We have a Windows guest. */
RTPathChangeToDosSlashes(szOutput, true /* Force conversion. */);
}
else /* ... or something which isn't from Redmond ... */
{
RTPathChangeToUnixSlashes(szOutput, true /* Force conversion. */);
}
args.push_back(Bstr(szOutput).raw()); /* We want to write a file ... */
}
else
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Error preparing command line"));
}
ComPtr<IProgress> execProgress;
ULONG uPID;
if (SUCCEEDED(rc))
{
LogRel(("Copying file \"%s\" to guest \"%s\" (%u bytes) ...\n",
aTask->strSource.c_str(), aTask->strDest.c_str(), cbSize));
/*
* Okay, since we gathered all stuff we need until now to start the
* actual copying, start the guest part now.
*/
rc = pGuest->ExecuteProcess(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
ExecuteProcessFlag_Hidden
| ExecuteProcessFlag_WaitForProcessStartOnly,
ComSafeArrayAsInParam(args),
ComSafeArrayAsInParam(env),
Bstr(aTask->strUserName).raw(),
Bstr(aTask->strPassword).raw(),
5 * 1000 /* Wait 5s for getting the process started. */,
&uPID, execProgress.asOutParam());
if (FAILED(rc))
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
if (SUCCEEDED(rc))
{
BOOL fCompleted = FALSE;
BOOL fCanceled = FALSE;
size_t cbToRead = cbSize;
size_t cbTransfered = 0;
size_t cbRead;
SafeArray<BYTE> aInputData(_64K);
while ( SUCCEEDED(execProgress->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
{
if (!cbToRead)
cbRead = 0;
else
{
vrc = RTFileRead(fileSource, (uint8_t*)aInputData.raw(),
RT_MIN(cbToRead, _64K), &cbRead);
/*
* Some other error occured? There might be a chance that RTFileRead
* could not resolve/map the native error code to an IPRT code, so just
* print a generic error.
*/
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not read from file \"%s\" (%Rrc)"),
aTask->strSource.c_str(), vrc);
break;
}
}
/* Resize buffer to reflect amount we just have read.
* Size 0 is allowed! */
aInputData.resize(cbRead);
ULONG uFlags = ProcessInputFlag_None;
/* Did we reach the end of the content we want to transfer (last chunk)? */
if ( (cbRead < _64K)
/* Did we reach the last block which is exactly _64K? */
|| (cbToRead - cbRead == 0)
/* ... or does the user want to cancel? */
|| ( SUCCEEDED(aTask->progress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
)
{
uFlags |= ProcessInputFlag_EndOfFile;
}
/* Transfer the current chunk ... */
ULONG uBytesWritten;
rc = pGuest->SetProcessInput(uPID, uFlags,
10 * 1000 /* Wait 10s for getting the input data transfered. */,
ComSafeArrayAsInParam(aInputData), &uBytesWritten);
if (FAILED(rc))
{
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
break;
}
Assert(cbRead <= cbToRead);
Assert(cbToRead >= cbRead);
cbToRead -= cbRead;
cbTransfered += uBytesWritten;
Assert(cbTransfered <= cbSize);
aTask->progress->SetCurrentOperationProgress(cbTransfered / (cbSize / 100.0));
/* End of file reached? */
if (cbToRead == 0)
break;
/* Did the user cancel the operation above? */
if (fCanceled)
break;
/* Progress canceled by Main API? */
if ( SUCCEEDED(execProgress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Copy operation of file \"%s\" was canceled on guest side"),
aTask->strSource.c_str());
break;
}
}
if (SUCCEEDED(rc))
{
/*
* If we got here this means the started process either was completed,
* canceled or we simply got all stuff transferred.
*/
ExecuteProcessStatus_T retStatus;
ULONG uRetExitCode;
rc = pGuest->waitForProcessStatusChange(uPID, &retStatus, &uRetExitCode, 10 * 1000 /* 10s timeout. */);
if (FAILED(rc))
{
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
else
{
if ( uRetExitCode != 0
|| retStatus != ExecuteProcessStatus_TerminatedNormally)
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Guest reported error %u while copying file \"%s\" to \"%s\""),
uRetExitCode, aTask->strSource.c_str(), aTask->strDest.c_str());
}
}
}
if (SUCCEEDED(rc))
{
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.
*/
aTask->progress->notifyComplete(VBOX_E_IPRT_ERROR,
COM_IIDOF(IGuest),
Guest::getStaticComponentName(),
Guest::tr("Copying file \"%s\" canceled"), aTask->strSource.c_str());
}
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. */
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Access denied when copying file \"%s\" to \"%s\""),
aTask->strSource.c_str(), aTask->strDest.c_str());
}
else if (cbTransfered < cbSize)
{
/* If we did not copy all let the user know. */
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Copying file \"%s\" failed (%u/%u bytes transfered)"),
aTask->strSource.c_str(), cbTransfered, cbSize);
}
else /* Yay, all went fine! */
aTask->progress->notifyComplete(S_OK);
}
}
}
}
RTFileClose(fileSource);
}
}
#endif
}
catch (HRESULT aRC)
{
rc = aRC;
}
/* Clean up */
aTask->rc = rc;
LogFlowFunc(("rc=%Rhrc\n", rc));
LogFlowFuncLeave();
return VINF_SUCCESS;
}
HRESULT Guest::taskUpdateGuestAdditions(TaskGuest *aTask)
{
LogFlowFuncEnter();
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/*
* 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.
*/
HRESULT rc = S_OK;
BOOL fCompleted;
BOOL fCanceled;
try
{
Guest *pGuest = aTask->pGuest;
AssertPtr(pGuest);
aTask->progress->SetCurrentOperationProgress(10);
/*
* Determine guest OS type and the required installer image.
* At the moment only Windows guests are supported.
*/
Utf8Str installerImage;
Bstr osTypeId;
if ( SUCCEEDED(pGuest->COMGETTER(OSTypeId(osTypeId.asOutParam())))
&& !osTypeId.isEmpty())
{
Utf8Str osTypeIdUtf8(osTypeId); /* Needed for .contains(). */
if ( osTypeIdUtf8.contains("Microsoft", Utf8Str::CaseInsensitive)
|| osTypeIdUtf8.contains("Windows", Utf8Str::CaseInsensitive))
{
if (osTypeIdUtf8.contains("64", Utf8Str::CaseInsensitive))
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). */
throw TaskGuest::setProgressErrorInfo(VBOX_E_NOT_SUPPORTED, aTask->progress,
Guest::tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"),
osTypeIdUtf8.c_str());
}
else
throw TaskGuest::setProgressErrorInfo(VBOX_E_NOT_SUPPORTED, aTask->progress,
Guest::tr("Could not detected guest OS type/version, please update manually"));
Assert(!installerImage.isEmpty());
/*
* Try to open the .ISO file and locate the specified installer.
*/
RTISOFSFILE iso;
int vrc = RTIsoFsOpen(&iso, aTask->strSource.c_str());
if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_FILE_ERROR, aTask->progress,
Guest::tr("Invalid installation medium detected: \"%s\""),
aTask->strSource.c_str());
}
else
{
uint32_t cbOffset;
size_t cbLength;
vrc = RTIsoFsGetFileInfo(&iso, installerImage.c_str(), &cbOffset, &cbLength);
if ( RT_SUCCESS(vrc)
&& cbOffset
&& cbLength)
{
vrc = RTFileSeek(iso.file, cbOffset, RTFILE_SEEK_BEGIN, NULL);
if (RT_FAILURE(vrc))
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Could not seek to setup file on installation medium \"%s\" (%Rrc)"),
aTask->strSource.c_str(), vrc);
}
else
{
switch (vrc)
{
case VERR_FILE_NOT_FOUND:
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Setup file was not found on installation medium \"%s\""),
aTask->strSource.c_str());
break;
default:
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("An unknown error (%Rrc) occured while retrieving information of setup file on installation medium \"%s\""),
vrc, aTask->strSource.c_str());
break;
}
}
/* Specify the ouput path on the guest side. */
Utf8Str strInstallerPath = "%TEMP%\\VBoxWindowsAdditions.exe";
if (RT_SUCCESS(vrc))
{
/* Okay, we're ready to start our copy routine on the guest! */
aTask->progress->SetCurrentOperationProgress(15);
/* Prepare command line args. */
com::SafeArray<IN_BSTR> args;
com::SafeArray<IN_BSTR> env;
args.push_back(Bstr("--output").raw()); /* We want to write a file ... */
args.push_back(Bstr(strInstallerPath.c_str()).raw()); /* ... with this path. */
if (SUCCEEDED(rc))
{
ComPtr<IProgress> progressCat;
ULONG uPID;
/*
* Start built-in "vbox_cat" tool (inside VBoxService) to
* copy over/pipe the data into a file on the guest (with
* system rights, no username/password specified).
*/
rc = pGuest->executeProcessInternal(Bstr(VBOXSERVICE_TOOL_CAT).raw(),
ExecuteProcessFlag_Hidden
| ExecuteProcessFlag_WaitForProcessStartOnly,
ComSafeArrayAsInParam(args),
ComSafeArrayAsInParam(env),
Bstr("").raw() /* Username. */,
Bstr("").raw() /* Password */,
5 * 1000 /* Wait 5s for getting the process started. */,
&uPID, progressCat.asOutParam(), &vrc);
if (FAILED(rc))
{
/* 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"));
rc = TaskGuest::setProgressErrorInfo(VBOX_E_NOT_SUPPORTED, aTask->progress,
Guest::tr("Guest Additions seem not to be installed or are not ready to update yet"));
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"));
rc = TaskGuest::setProgressErrorInfo(VBOX_E_NOT_SUPPORTED, aTask->progress,
Guest::tr("Installed Guest Additions do not support automatic updating"));
break;
case VERR_TIMEOUT:
LogRel(("Guest was unable to start copying the Guest Additions setup within time\n"));
rc = TaskGuest::setProgressErrorInfo(E_FAIL, aTask->progress,
Guest::tr("Guest was unable to start copying the Guest Additions setup within time"));
break;
default:
rc = TaskGuest::setProgressErrorInfo(E_FAIL, aTask->progress,
Guest::tr("Error copying Guest Additions setup file to guest path \"%s\" (%Rrc)"),
strInstallerPath.c_str(), vrc);
break;
}
}
else
{
LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", aTask->strSource.c_str()));
LogRel(("Copying Guest Additions installer \"%s\" to \"%s\" on guest ...\n",
installerImage.c_str(), strInstallerPath.c_str()));
aTask->progress->SetCurrentOperationProgress(20);
/* Wait for process to exit ... */
SafeArray<BYTE> aInputData(_64K);
while ( SUCCEEDED(progressCat->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
{
size_t cbRead;
/* cbLength contains remaining bytes of our installer file
* opened above to read. */
size_t cbToRead = RT_MIN(cbLength, _64K);
if (cbToRead)
{
vrc = RTFileRead(iso.file, (uint8_t*)aInputData.raw(), cbToRead, &cbRead);
if ( cbRead
&& RT_SUCCESS(vrc))
{
/* Resize buffer to reflect amount we just have read. */
if (cbRead > 0)
aInputData.resize(cbRead);
/* Did we reach the end of the content we want to transfer (last chunk)? */
ULONG uFlags = ProcessInputFlag_None;
if ( (cbRead < _64K)
/* Did we reach the last block which is exactly _64K? */
|| (cbToRead - cbRead == 0)
/* ... or does the user want to cancel? */
|| ( SUCCEEDED(aTask->progress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
)
{
uFlags |= ProcessInputFlag_EndOfFile;
}
/* Transfer the current chunk ... */
#ifdef DEBUG_andy
LogRel(("Copying Guest Additions (%u bytes left) ...\n", cbLength));
#endif
ULONG uBytesWritten;
rc = pGuest->SetProcessInput(uPID, uFlags,
10 * 1000 /* Wait 10s for getting the input data transfered. */,
ComSafeArrayAsInParam(aInputData), &uBytesWritten);
if (FAILED(rc))
{
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
break;
}
/* If task was canceled above also cancel the process execution. */
if (fCanceled)
progressCat->Cancel();
#ifdef DEBUG_andy
LogRel(("Copying Guest Additions (%u bytes written) ...\n", uBytesWritten));
#endif
Assert(cbLength >= uBytesWritten);
cbLength -= uBytesWritten;
}
else if (RT_FAILURE(vrc))
{
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Error while reading setup file \"%s\" (To read: %u, Size: %u) from installation medium (%Rrc)"),
installerImage.c_str(), cbToRead, cbLength, vrc);
}
}
/* Internal progress canceled? */
if ( SUCCEEDED(progressCat->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
aTask->progress->Cancel();
break;
}
}
}
}
}
RTIsoFsClose(&iso);
if ( SUCCEEDED(rc)
&& ( SUCCEEDED(aTask->progress->COMGETTER(Canceled(&fCanceled)))
&& !fCanceled
)
)
{
/*
* Installer was transferred successfully, so let's start it
* (with system rights).
*/
LogRel(("Preparing to execute Guest Additions update ...\n"));
aTask->progress->SetCurrentOperationProgress(66);
/* Prepare command line args for installer. */
com::SafeArray<IN_BSTR> installerArgs;
com::SafeArray<IN_BSTR> installerEnv;
/** @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:
* This means that if we run an application with the profile/security context
* of VBoxService (system rights!) we're not able to show any UI. */
installerArgs.push_back(Bstr("/S").raw()); /* We want to install in silent mode. */
installerArgs.push_back(Bstr("/l").raw()); /* ... and logging enabled. */
/* Don't quit VBoxService during upgrade because it still is used for this
* piece of code we're in right now (that is, here!) ... */
installerArgs.push_back(Bstr("/no_vboxservice_exit").raw());
/* Tell the installer to report its current installation status
* using a running VBoxTray instance via balloon messages in the
* Windows taskbar. */
installerArgs.push_back(Bstr("/post_installstatus").raw());
/*
* 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.
*/
ComPtr<IProgress> progressInstaller;
ULONG uPID;
rc = pGuest->executeProcessInternal(Bstr(strInstallerPath).raw(),
ExecuteProcessFlag_WaitForProcessStartOnly,
ComSafeArrayAsInParam(installerArgs),
ComSafeArrayAsInParam(installerEnv),
Bstr("").raw() /* Username */,
Bstr("").raw() /* Password */,
10 * 1000 /* Wait 10s for getting the process started */,
&uPID, progressInstaller.asOutParam(), &vrc);
if (SUCCEEDED(rc))
{
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. */
if (aTask->uFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)
aTask->progress->notifyComplete(S_OK);
else
aTask->progress->SetCurrentOperationProgress(70);
/* Wait until the Guest Additions installer finishes ... */
while ( SUCCEEDED(progressInstaller->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
{
if ( SUCCEEDED(aTask->progress->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
progressInstaller->Cancel();
break;
}
/* Progress canceled by Main API? */
if ( SUCCEEDED(progressInstaller->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
break;
}
RTThreadSleep(100);
}
ExecuteProcessStatus_T retStatus;
ULONG uRetExitCode, uRetFlags;
rc = pGuest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &retStatus);
if (SUCCEEDED(rc))
{
if (fCompleted)
{
if (uRetExitCode == 0)
{
LogRel(("Guest Additions update successful!\n"));
if ( SUCCEEDED(aTask->progress->COMGETTER(Completed(&fCompleted)))
&& !fCompleted)
aTask->progress->notifyComplete(S_OK);
}
else
{
LogRel(("Guest Additions update failed (Exit code=%u, Status=%u, Flags=%u)\n",
uRetExitCode, retStatus, uRetFlags));
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Guest Additions update failed with exit code=%u (status=%u, flags=%u)"),
uRetExitCode, retStatus, uRetFlags);
}
}
else if ( SUCCEEDED(progressInstaller->COMGETTER(Canceled(&fCanceled)))
&& fCanceled)
{
LogRel(("Guest Additions update was canceled\n"));
rc = TaskGuest::setProgressErrorInfo(VBOX_E_IPRT_ERROR, aTask->progress,
Guest::tr("Guest Additions update was canceled by the guest with exit code=%u (status=%u, flags=%u)"),
uRetExitCode, retStatus, uRetFlags);
}
else
{
LogRel(("Guest Additions update was canceled by the user\n"));
}
}
else
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
else
rc = TaskGuest::setProgressErrorInfo(rc, aTask->progress, pGuest);
}
}
}
catch (HRESULT aRC)
{
rc = aRC;
}
/* Clean up */
aTask->rc = rc;
LogFlowFunc(("rc=%Rhrc\n", rc));
LogFlowFuncLeave();
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;
uint32_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2);
if (*ppvList)
{
uint32_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */
char *pvTmp = (char *)RTMemRealloc(*ppvList, cbNewLen);
if (pvTmp == NULL)
rc = VERR_NO_MEMORY;
else
{
memcpy(pvTmp + *pcbList, pszEnv, cchEnv);
pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */
*ppvList = (void **)pvTmp;
}
}
else
{
char *pszTmp;
if (RTStrAPrintf(&pszTmp, "%s", pszEnv) >= 0)
{
*ppvList = (void **)pszTmp;
/* Reset counters. */
*pcEnvVars = 0;
*pcbList = 0;
}
}
if (RT_SUCCESS(rc))
{
*pcbList += cchEnv + 1; /* Include zero termination. */
*pcEnvVars += 1; /* Increase env variable count. */
}
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
*
*/
DECLCALLBACK(int) Guest::doGuestCtrlNotification(void *pvExtension,
uint32_t u32Function,
void *pvParms,
uint32_t cbParms)
{
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",
pvExtension, u32Function, pvParms, cbParms));
#endif
ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
int rc = VINF_SUCCESS;
switch (u32Function)
{
case GUEST_DISCONNECTED:
{
//LogFlowFunc(("GUEST_DISCONNECTED\n"));
PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);
AssertPtr(pCBData);
AssertReturn(sizeof(CALLBACKDATACLIENTDISCONNECTED) == cbParms, VERR_INVALID_PARAMETER);
AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
rc = pGuest->notifyCtrlClientDisconnected(u32Function, pCBData);
break;
}
case GUEST_EXEC_SEND_STATUS:
{
//LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
PCALLBACKDATAEXECSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvParms);
AssertPtr(pCBData);
AssertReturn(sizeof(CALLBACKDATAEXECSTATUS) == cbParms, VERR_INVALID_PARAMETER);
AssertReturn(CALLBACKDATAMAGIC_EXEC_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
rc = pGuest->notifyCtrlExecStatus(u32Function, pCBData);
break;
}
case GUEST_EXEC_SEND_OUTPUT:
{
//LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
PCALLBACKDATAEXECOUT pCBData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvParms);
AssertPtr(pCBData);
AssertReturn(sizeof(CALLBACKDATAEXECOUT) == cbParms, VERR_INVALID_PARAMETER);
AssertReturn(CALLBACKDATAMAGIC_EXEC_OUT == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
rc = pGuest->notifyCtrlExecOut(u32Function, pCBData);
break;
}
case GUEST_EXEC_SEND_INPUT_STATUS:
{
//LogFlowFunc(("GUEST_EXEC_SEND_INPUT_STATUS\n"));
PCALLBACKDATAEXECINSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECINSTATUS>(pvParms);
AssertPtr(pCBData);
AssertReturn(sizeof(CALLBACKDATAEXECINSTATUS) == cbParms, VERR_INVALID_PARAMETER);
AssertReturn(CALLBACKDATAMAGIC_EXEC_IN_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
rc = pGuest->notifyCtrlExecInStatus(u32Function, pCBData);
break;
}
default:
AssertMsgFailed(("Unknown guest control notification received, u32Function=%u\n", u32Function));
rc = VERR_INVALID_PARAMETER;
break;
}
return rc;
}
/* Function for handling the execution start/termination notification. */
int Guest::notifyCtrlExecStatus(uint32_t u32Function,
PCALLBACKDATAEXECSTATUS pData)
{
int vrc = VINF_SUCCESS;
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
AssertPtr(pData);
CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
/* Callback can be called several times. */
if (it != mCallbackMap.end())
{
PCALLBACKDATAEXECSTATUS pCBData = (PCALLBACKDATAEXECSTATUS)it->second.pvData;
AssertPtr(pCBData);
pCBData->u32PID = pData->u32PID;
pCBData->u32Status = pData->u32Status;
pCBData->u32Flags = pData->u32Flags;
/** @todo Copy void* buffer contents! */
Utf8Str errMsg;
/* Was progress canceled before? */
BOOL fCanceled;
ComAssert(!it->second.pProgress.isNull());
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Canceled)(&fCanceled))
&& !fCanceled)
{
/* Do progress handling. */
HRESULT hr;
switch (pData->u32Status)
{
case PROC_STS_STARTED:
LogRel(("Guest process (PID %u) started\n", pCBData->u32PID)); /** @todo Add process name */
hr = it->second.pProgress->SetNextOperation(Bstr(tr("Waiting for process to exit ...")).raw(), 1 /* Weight */);
AssertComRC(hr);
break;
case PROC_STS_TEN: /* Terminated normally. */
LogRel(("Guest process (PID %u) exited normally\n", pCBData->u32PID)); /** @todo Add process name */
if (!it->second.pProgress->getCompleted())
{
hr = it->second.pProgress->notifyComplete(S_OK);
AssertComRC(hr);
LogFlowFunc(("Process (CID=%u, status=%u) terminated successfully\n",
pData->hdr.u32ContextID, pData->u32Status));
}
break;
case PROC_STS_TEA: /* Terminated abnormally. */
LogRel(("Guest process (PID %u) terminated abnormally with exit code = %u\n",
pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
errMsg = Utf8StrFmt(Guest::tr("Process terminated abnormally with status '%u'"),
pCBData->u32Flags);
break;
case PROC_STS_TES: /* Terminated through signal. */
LogRel(("Guest process (PID %u) terminated through signal with exit code = %u\n",
pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
errMsg = Utf8StrFmt(Guest::tr("Process terminated via signal with status '%u'"),
pCBData->u32Flags);
break;
case PROC_STS_TOK:
LogRel(("Guest process (PID %u) timed out and was killed\n", pCBData->u32PID)); /** @todo Add process name */
errMsg = Utf8StrFmt(Guest::tr("Process timed out and was killed"));
break;
case PROC_STS_TOA:
LogRel(("Guest process (PID %u) timed out and could not be killed\n", pCBData->u32PID)); /** @todo Add process name */
errMsg = Utf8StrFmt(Guest::tr("Process timed out and could not be killed"));
break;
case PROC_STS_DWN:
LogRel(("Guest process (PID %u) killed because system is shutting down\n", pCBData->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().
*/
if (pData->u32Flags & ExecuteProcessFlag_IgnoreOrphanedProcesses)
{
if (!it->second.pProgress->getCompleted())
{
hr = it->second.pProgress->notifyComplete(S_OK);
AssertComRC(hr);
}
}
else
{
errMsg = Utf8StrFmt(Guest::tr("Process killed because system is shutting down"));
}
break;
case PROC_STS_ERROR:
LogRel(("Guest process (PID %u) could not be started because of rc=%Rrc\n",
pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
errMsg = Utf8StrFmt(Guest::tr("Process execution failed with rc=%Rrc"), pCBData->u32Flags);
break;
default:
vrc = VERR_INVALID_PARAMETER;
break;
}
/* Handle process map. */
/** @todo What happens on/deal with PID reuse? */
/** @todo How to deal with multiple updates at once? */
if (pCBData->u32PID > 0)
{
GuestProcessMapIter it_proc = getProcessByPID(pCBData->u32PID);
if (it_proc == mGuestProcessMap.end())
{
/* Not found, add to map. */
GuestProcess newProcess;
newProcess.mStatus = (ExecuteProcessStatus_T)pCBData->u32Status;
newProcess.mExitCode = pCBData->u32Flags; /* Contains exit code. */
newProcess.mFlags = 0;
mGuestProcessMap[pCBData->u32PID] = newProcess;
}
else /* Update map. */
{
it_proc->second.mStatus = (ExecuteProcessStatus_T)pCBData->u32Status;
it_proc->second.mExitCode = pCBData->u32Flags; /* Contains exit code. */
it_proc->second.mFlags = 0;
}
}
}
else
errMsg = Utf8StrFmt(Guest::tr("Process execution canceled"));
if (!it->second.pProgress->getCompleted())
{
if ( errMsg.length()
|| fCanceled) /* If canceled we have to report E_FAIL! */
{
/* Destroy all callbacks which are still waiting on something
* which is related to the current PID. */
CallbackMapIter it2;
for (it2 = mCallbackMap.begin(); it2 != mCallbackMap.end(); it2++)
{
switch (it2->second.mType)
{
case VBOXGUESTCTRLCALLBACKTYPE_EXEC_START:
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. */
case VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT:
{
PCALLBACKDATAEXECOUT pItData = (PCALLBACKDATAEXECOUT)it2->second.pvData;
AssertPtr(pItData);
if (pItData->u32PID == pCBData->u32PID)
notifyCtrlCallbackContext(it2, errMsg.c_str());
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. */
case VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS:
{
PCALLBACKDATAEXECINSTATUS pItData = (PCALLBACKDATAEXECINSTATUS)it2->second.pvData;
AssertPtr(pItData);
if (pItData->u32PID == pCBData->u32PID)
notifyCtrlCallbackContext(it2, errMsg.c_str());
break;
}
default:
AssertMsgFailed(("Unknown callback type %d\n", it2->second.mType));
break;
}
}
/* Let the caller know what went wrong ... */
notifyCtrlCallbackContext(it, errMsg.c_str());
LogFlowFunc(("Process (CID=%u, status=%u) reported error: %s\n",
pData->hdr.u32ContextID, pData->u32Status, errMsg.c_str()));
}
}
}
else
LogFlowFunc(("Unexpected callback (magic=%u, CID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
LogFlowFunc(("Returned with rc=%Rrc\n", vrc));
return vrc;
}
/* Function for handling the execution output notification. */
int Guest::notifyCtrlExecOut(uint32_t u32Function,
PCALLBACKDATAEXECOUT pData)
{
int rc = VINF_SUCCESS;
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
AssertPtr(pData);
CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
if (it != mCallbackMap.end())
{
PCALLBACKDATAEXECOUT pCBData = (PCALLBACKDATAEXECOUT)it->second.pvData;
AssertPtr(pCBData);
pCBData->u32PID = pData->u32PID;
pCBData->u32HandleId = pData->u32HandleId;
pCBData->u32Flags = pData->u32Flags;
/* Make sure we really got something! */
if ( pData->cbData
&& pData->pvData)
{
/* Allocate data buffer and copy it */
pCBData->pvData = RTMemAlloc(pData->cbData);
pCBData->cbData = pData->cbData;
AssertReturn(pCBData->pvData, VERR_NO_MEMORY);
memcpy(pCBData->pvData, pData->pvData, pData->cbData);
}
else
{
pCBData->pvData = NULL;
pCBData->cbData = 0;
}
/* Was progress canceled before? */
BOOL fCanceled;
ComAssert(!it->second.pProgress.isNull());
if (SUCCEEDED(it->second.pProgress->COMGETTER(Canceled)(&fCanceled)) && fCanceled)
{
it->second.pProgress->notifyComplete(VBOX_E_IPRT_ERROR,
COM_IIDOF(IGuest),
Guest::getStaticComponentName(),
Guest::tr("The output operation was canceled"));
}
else
{
BOOL fCompleted;
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
&& !fCompleted)
{
/* If we previously got completed notification, don't trigger again. */
it->second.pProgress->notifyComplete(S_OK);
}
}
}
else
LogFlowFunc(("Unexpected callback (magic=%u, CID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
return rc;
}
/* Function for handling the execution input status notification. */
int Guest::notifyCtrlExecInStatus(uint32_t u32Function,
PCALLBACKDATAEXECINSTATUS pData)
{
int rc = VINF_SUCCESS;
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
AssertPtr(pData);
CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
if (it != mCallbackMap.end())
{
PCALLBACKDATAEXECINSTATUS pCBData = (PCALLBACKDATAEXECINSTATUS)it->second.pvData;
AssertPtr(pCBData);
/* Save bytes processed. */
pCBData->cbProcessed = pData->cbProcessed;
pCBData->u32Status = pData->u32Status;
pCBData->u32Flags = pData->u32Flags;
pCBData->u32PID = pData->u32PID;
/* Only trigger completion once. */
BOOL fCompleted;
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
&& !fCompleted)
{
it->second.pProgress->notifyComplete(S_OK);
}
}
else
LogFlowFunc(("Unexpected callback (magic=%u, CID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
return rc;
}
int Guest::notifyCtrlClientDisconnected(uint32_t u32Function,
PCALLBACKDATACLIENTDISCONNECTED pData)
{
int rc = VINF_SUCCESS;
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
if (it != mCallbackMap.end())
{
LogFlowFunc(("Client with CID=%u disconnected\n", it->first));
notifyCtrlCallbackContext(it, Guest::tr("Client disconnected"));
}
return rc;
}
Guest::CallbackMapIter Guest::getCtrlCallbackContextByID(uint32_t u32ContextID)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
return mCallbackMap.find(u32ContextID);
}
Guest::GuestProcessMapIter Guest::getProcessByPID(uint32_t u32PID)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
return mGuestProcessMap.find(u32PID);
}
/* No locking here; */
void Guest::destroyCtrlCallbackContext(Guest::CallbackMapIter it)
{
LogFlowFunc(("Destroying callback with CID=%u ...\n", it->first));
if (it->second.pvData)
{
RTMemFree(it->second.pvData);
it->second.pvData = NULL;
it->second.cbData = 0;
}
/* Remove callback context (not used anymore). */
mCallbackMap.erase(it);
}
/* No locking here; */
void Guest::notifyCtrlCallbackContext(Guest::CallbackMapIter it, const char *pszText)
{
AssertPtr(pszText);
LogFlowFunc(("Handling callback with CID=%u ...\n", it->first));
/* Notify outstanding waits for progress ... */
if ( it->second.pProgress
&& !it->second.pProgress.isNull())
{
LogFlowFunc(("Notifying progress for CID=%u (Reason: %s) ...\n",
it->first, pszText));
/*
* Assume we didn't complete to make sure we clean up even if the
* following call fails.
*/
BOOL fCompleted = FALSE;
it->second.pProgress->COMGETTER(Completed)(&fCompleted);
if (!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
* have to abort here to make sure the host never hangs/gets stuck while waiting for the
* progress object to become signalled.
*/
it->second.pProgress->notifyComplete(VBOX_E_IPRT_ERROR,
COM_IIDOF(IGuest),
Guest::getStaticComponentName(),
pszText);
}
/*
* 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)
{
AssertPtr(pProgress);
/** @todo Put this stuff into a constructor! */
CallbackContext context;
context.mType = enmType;
context.pvData = pvData;
context.cbData = cbData;
context.pProgress = pProgress;
/* Create a new context ID and assign it. */
CallbackMapIter it;
uint32_t uNewContext = 0;
do
{
/* Create a new context ID ... */
uNewContext = ASMAtomicIncU32(&mNextContextID);
if (uNewContext == UINT32_MAX)
ASMAtomicUoWriteU32(&mNextContextID, 1000);
/* Is the context ID already used? */
it = getCtrlCallbackContextByID(uNewContext);
} while(it != mCallbackMap.end());
uint32_t nCallbacks = 0;
if ( it == mCallbackMap.end()
&& uNewContext > 0)
{
/* We apparently got an unused context ID, let's use it! */
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
mCallbackMap[uNewContext] = context;
nCallbacks = mCallbackMap.size();
}
else
{
/* Should never happen ... */
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
nCallbacks = mCallbackMap.size();
}
AssertReleaseMsg(uNewContext, ("No free context ID found! uNewContext=%u, nCallbacks=%u", uNewContext, nCallbacks));
}
#if 0
if (nCallbacks > 256) /* Don't let the container size get too big! */
{
Guest::CallbackListIter it = mCallbackList.begin();
destroyCtrlCallbackContext(it);
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
mCallbackList.erase(it);
}
}
#endif
return uNewContext;
}
HRESULT Guest::waitForProcessStatusChange(ULONG uPID, ExecuteProcessStatus_T *pRetStatus, ULONG *puRetExitCode, ULONG uTimeoutMS)
{
AssertPtr(pRetStatus);
AssertPtr(puRetExitCode);
if (uTimeoutMS == 0)
uTimeoutMS = UINT32_MAX;
uint64_t u64StartMS = RTTimeMilliTS();
HRESULT hRC;
ULONG uRetFlagsIgnored;
do
{
/*
* Do some busy waiting within the specified time period (if any).
*/
if ( uTimeoutMS != UINT32_MAX
&& RTTimeMilliTS() - u64StartMS > uTimeoutMS)
{
hRC = setError(VBOX_E_IPRT_ERROR,
tr("The process (PID %u) did not change its status within time (%ums)"),
uPID, uTimeoutMS);
break;
}
hRC = GetProcessStatus(uPID, puRetExitCode, &uRetFlagsIgnored, pRetStatus);
if (FAILED(hRC))
break;
RTThreadSleep(100);
} while(*pRetStatus == ExecuteProcessStatus_Started && SUCCEEDED(hRC));
return hRC;
}
#endif /* VBOX_WITH_GUEST_CONTROL */
STDMETHODIMP Guest::ExecuteProcess(IN_BSTR aCommand, ULONG aFlags,
ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress)
{
/** @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
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgStrNotEmptyOrNull(aCommand);
CheckComArgOutPointerValid(aPID);
CheckComArgOutPointerValid(aProgress);
/* Do not allow anonymous executions (with system rights). */
if (RT_UNLIKELY((aUserName) == NULL || *(aUserName) == '\0'))
return setError(E_INVALIDARG, tr("No user name specified"));
LogRel(("Executing guest process \"%s\" as user \"%s\" ...\n",
Utf8Str(aCommand).c_str(), Utf8Str(aUserName).c_str()));
return executeProcessInternal(aCommand, aFlags, ComSafeArrayInArg(aArguments),
ComSafeArrayInArg(aEnvironment),
aUserName, aPassword, aTimeoutMS, aPID, aProgress, NULL /* rc */);
#endif
}
HRESULT Guest::executeProcessInternal(IN_BSTR aCommand, ULONG aFlags,
ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress, int *pRC)
{
/** @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
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags != ExecuteProcessFlag_None)
{
if ( !(aFlags & ExecuteProcessFlag_IgnoreOrphanedProcesses)
&& !(aFlags & ExecuteProcessFlag_WaitForProcessStartOnly)
&& !(aFlags & ExecuteProcessFlag_Hidden)
&& !(aFlags & ExecuteProcessFlag_NoProfile))
{
if (pRC)
*pRC = VERR_INVALID_PARAMETER;
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
}
HRESULT rc = S_OK;
try
{
/*
* Create progress object. Note that this is a multi operation
* object to perform the following steps:
* - Operation 1 (0): Create/start process.
* - Operation 2 (1): Wait for process to exit.
* If this progress completed successfully (S_OK), the process
* started and exited normally. In any other case an error/exception
* occurred.
*/
ComObjPtr <Progress> progress;
rc = progress.createObject();
if (SUCCEEDED(rc))
{
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Executing process")).raw(),
TRUE,
2, /* Number of operations. */
Bstr(tr("Starting process ...")).raw()); /* Description of first stage. */
}
ComAssertComRC(rc);
/*
* Prepare process execution.
*/
int vrc = VINF_SUCCESS;
Utf8Str Utf8Command(aCommand);
/* Adjust timeout. If set to 0, we define
* an infinite timeout. */
if (aTimeoutMS == 0)
aTimeoutMS = UINT32_MAX;
/* Prepare arguments. */
char **papszArgv = NULL;
uint32_t uNumArgs = 0;
if (aArguments)
{
com::SafeArray<IN_BSTR> args(ComSafeArrayInArg(aArguments));
uNumArgs = args.size();
papszArgv = (char**)RTMemAlloc(sizeof(char*) * (uNumArgs + 1));
AssertReturn(papszArgv, E_OUTOFMEMORY);
for (unsigned i = 0; RT_SUCCESS(vrc) && i < uNumArgs; i++)
vrc = RTUtf16ToUtf8(args[i], &papszArgv[i]);
papszArgv[uNumArgs] = NULL;
}
Utf8Str Utf8UserName(aUserName);
Utf8Str Utf8Password(aPassword);
if (RT_SUCCESS(vrc))
{
uint32_t uContextID = 0;
char *pszArgs = NULL;
if (uNumArgs > 0)
vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
if (RT_SUCCESS(vrc))
{
uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
/* Prepare environment. */
void *pvEnv = NULL;
uint32_t uNumEnv = 0;
uint32_t cbEnv = 0;
if (aEnvironment)
{
com::SafeArray<IN_BSTR> env(ComSafeArrayInArg(aEnvironment));
for (unsigned i = 0; i < env.size(); i++)
{
vrc = prepareExecuteEnv(Utf8Str(env[i]).c_str(), &pvEnv, &cbEnv, &uNumEnv);
if (RT_FAILURE(vrc))
break;
}
}
if (RT_SUCCESS(vrc))
{
PCALLBACKDATAEXECSTATUS pData = (PCALLBACKDATAEXECSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECSTATUS));
AssertReturn(pData, VBOX_E_IPRT_ERROR);
RT_ZERO(*pData);
uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START,
pData, sizeof(CALLBACKDATAEXECSTATUS), progress);
Assert(uContextID > 0);
VBOXHGCMSVCPARM paParms[15];
int i = 0;
paParms[i++].setUInt32(uContextID);
paParms[i++].setPointer((void*)Utf8Command.c_str(), (uint32_t)Utf8Command.length() + 1);
paParms[i++].setUInt32(aFlags);
paParms[i++].setUInt32(uNumArgs);
paParms[i++].setPointer((void*)pszArgs, cbArgs);
paParms[i++].setUInt32(uNumEnv);
paParms[i++].setUInt32(cbEnv);
paParms[i++].setPointer((void*)pvEnv, cbEnv);
paParms[i++].setPointer((void*)Utf8UserName.c_str(), (uint32_t)Utf8UserName.length() + 1);
paParms[i++].setPointer((void*)Utf8Password.c_str(), (uint32_t)Utf8Password.length() + 1);
/*
* 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.
*/
if (aFlags & ExecuteProcessFlag_WaitForProcessStartOnly)
paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);
else
paParms[i++].setUInt32(aTimeoutMS);
VMMDev *pVMMDev = NULL;
{
/* 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 ... */
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Forward the information to the VMM device. */
AssertPtr(mParent);
pVMMDev = mParent->getVMMDev();
}
if (pVMMDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,
i, paParms);
}
else
vrc = VERR_INVALID_VM_HANDLE;
RTMemFree(pvEnv);
}
RTStrFree(pszArgs);
}
if (RT_SUCCESS(vrc))
{
LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
/*
* Wait for the HGCM low level callback until the process
* has been started (or something went wrong). This is necessary to
* get the PID.
*/
CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
BOOL fCanceled = FALSE;
if (it != mCallbackMap.end())
{
ComAssert(!it->second.pProgress.isNull());
/*
* Wait for the first stage (=0) to complete (that is starting the process).
*/
PCALLBACKDATAEXECSTATUS pData = NULL;
rc = it->second.pProgress->WaitForOperationCompletion(0, aTimeoutMS);
if (SUCCEEDED(rc))
{
/* Was the operation canceled by one of the parties? */
rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
if (FAILED(rc)) throw rc;
if (!fCanceled)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
pData = (PCALLBACKDATAEXECSTATUS)it->second.pvData;
Assert(it->second.cbData == sizeof(CALLBACKDATAEXECSTATUS));
AssertPtr(pData);
/* Did we get some status? */
switch (pData->u32Status)
{
case PROC_STS_STARTED:
/* Process is (still) running; get PID. */
*aPID = pData->u32PID;
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.
*/
*aPID = pData->u32PID;
break;
case PROC_STS_ERROR:
vrc = pData->u32Flags; /* u32Flags member contains IPRT error code. */
break;
case PROC_STS_UNDEFINED:
vrc = VERR_TIMEOUT; /* Operation did not complete within time. */
break;
default:
vrc = VERR_INVALID_PARAMETER; /* Unknown status, should never happen! */
break;
}
}
else /* Operation was canceled. */
vrc = VERR_CANCELLED;
}
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))
{
if (vrc == VERR_FILE_NOT_FOUND) /* This is the most likely error. */
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The file '%s' was not found on guest"), Utf8Command.c_str());
else if (vrc == VERR_PATH_NOT_FOUND)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The path to file '%s' was not found on guest"), Utf8Command.c_str());
else if (vrc == VERR_BAD_EXE_FORMAT)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The file '%s' is not an executable format on guest"), Utf8Command.c_str());
else if (vrc == VERR_AUTHENTICATION_FAILURE)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The specified user '%s' was not able to logon on guest"), Utf8UserName.c_str());
else if (vrc == VERR_TIMEOUT)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The guest did not respond within time (%ums)"), aTimeoutMS);
else if (vrc == VERR_CANCELLED)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The execution operation was canceled"));
else if (vrc == VERR_PERMISSION_DENIED)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("Invalid user/password credentials"));
else
{
if (pData && pData->u32Status == PROC_STS_ERROR)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("Process could not be started: %Rrc"), pData->u32Flags);
else
rc = setErrorNoLog(E_UNEXPECTED,
tr("The service call failed with error %Rrc"), vrc);
}
}
else /* Execution went fine. */
{
/* Return the progress to the caller. */
progress.queryInterfaceTo(aProgress);
}
}
else /* Callback context not found; should never happen! */
AssertMsg(it != mCallbackMap.end(), ("Callback context with ID %u not found!", uContextID));
}
else /* HGCM related error codes .*/
{
if (vrc == VERR_INVALID_VM_HANDLE)
rc = setErrorNoLog(VBOX_E_VM_ERROR,
tr("VMM device is not available (is the VM running?)"));
else if (vrc == VERR_NOT_FOUND)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The guest execution service is not ready"));
else if (vrc == VERR_HGCM_SERVICE_NOT_FOUND)
rc = setErrorNoLog(VBOX_E_IPRT_ERROR,
tr("The guest execution service is not available"));
else /* HGCM call went wrong. */
rc = setErrorNoLog(E_UNEXPECTED,
tr("The HGCM call failed with error %Rrc"), vrc);
}
for (unsigned i = 0; i < uNumArgs; i++)
RTMemFree(papszArgv[i]);
RTMemFree(papszArgv);
}
if (RT_FAILURE(vrc))
{
if (!pRC) /* Skip logging internal calls. */
LogRel(("Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",
Utf8Command.c_str(), Utf8UserName.c_str(), vrc));
}
if (pRC)
*pRC = vrc;
}
catch (std::bad_alloc &)
{
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
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgExpr(aPID, aPID > 0);
CheckComArgOutPointerValid(aBytesWritten);
/* Validate flags. */
if (aFlags)
{
if (!(aFlags & ProcessInputFlag_EndOfFile))
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
HRESULT rc = S_OK;
try
{
/* Init. */
*aBytesWritten = 0;
{
/* Take read lock to prevent races. */
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Search for existing PID. */
GuestProcessMapIterConst itProc = getProcessByPID(aPID);
if (itProc != mGuestProcessMap.end())
{
/* PID exists; check if process is still running. */
if (itProc->second.mStatus != ExecuteProcessStatus_Started)
rc = setError(VBOX_E_IPRT_ERROR,
Guest::tr("Cannot inject input to not running process (PID %u)"), aPID);
}
else
rc = setError(VBOX_E_IPRT_ERROR,
Guest::tr("Cannot inject input to non-existent process (PID %u)"), aPID);
}
if (SUCCEEDED(rc))
{
/*
* 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.
*/
ComObjPtr <Progress> pProgress;
rc = pProgress.createObject();
if (SUCCEEDED(rc))
{
rc = pProgress->init(static_cast<IGuest*>(this),
Bstr(tr("Setting input for process")).raw(),
TRUE /* Cancelable */);
}
if (FAILED(rc)) throw rc;
ComAssert(!pProgress.isNull());
/* Adjust timeout. */
if (aTimeoutMS == 0)
aTimeoutMS = UINT32_MAX;
PCALLBACKDATAEXECINSTATUS pData = (PCALLBACKDATAEXECINSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECINSTATUS));
if (NULL == pData) throw rc;
AssertReturn(pData, VBOX_E_IPRT_ERROR);
RT_ZERO(*pData);
/* Save PID + output flags for later use. */
pData->u32PID = aPID;
pData->u32Flags = aFlags;
/* Add job to callback contexts. */
uint32_t uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS,
pData, sizeof(CALLBACKDATAEXECINSTATUS), pProgress);
Assert(uContextID > 0);
com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(aData));
uint32_t cbSize = sfaData.size();
VBOXHGCMSVCPARM paParms[6];
int i = 0;
paParms[i++].setUInt32(uContextID);
paParms[i++].setUInt32(aPID);
paParms[i++].setUInt32(aFlags);
paParms[i++].setPointer(sfaData.raw(), cbSize);
paParms[i++].setUInt32(cbSize);
int vrc = VINF_SUCCESS;
{
VMMDev *pVMMDev = NULL;
{
/* 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 ... */
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Forward the information to the VMM device. */
AssertPtr(mParent);
pVMMDev = mParent->getVMMDev();
}
if (pVMMDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_SET_INPUT,
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.
*/
CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
BOOL fCanceled = FALSE;
if (it != mCallbackMap.end())
{
ComAssert(!it->second.pProgress.isNull());
/* Wait until operation completed. */
rc = it->second.pProgress->WaitForCompletion(aTimeoutMS);
if (FAILED(rc)) throw rc;
/* Was the operation canceled by one of the parties? */
rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
if (FAILED(rc)) throw rc;
if (!fCanceled)
{
BOOL fCompleted;
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
&& fCompleted)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
PCALLBACKDATAEXECINSTATUS pStatusData = (PCALLBACKDATAEXECINSTATUS)it->second.pvData;
AssertPtr(pStatusData);
Assert(it->second.cbData == sizeof(CALLBACKDATAEXECINSTATUS));
switch (pStatusData->u32Status)
{
case INPUT_STS_WRITTEN:
*aBytesWritten = pStatusData->cbProcessed;
break;
default:
rc = setError(VBOX_E_IPRT_ERROR,
tr("Client error %u while processing input data"), pStatusData->u32Status);
break;
}
}
else
rc = setError(VBOX_E_IPRT_ERROR,
tr("The input operation was not acknowledged from guest within time (%ums)"), aTimeoutMS);
}
else
rc = setError(VBOX_E_IPRT_ERROR,
tr("The input operation was canceled by the guest"));
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Destroy locally used progress object. */
destroyCtrlCallbackContext(it);
}
}
else /* PID lookup failed. */
rc = setError(VBOX_E_IPRT_ERROR,
tr("Process (PID %u) not found"), aPID);
}
else /* HGCM operation failed. */
rc = setError(E_UNEXPECTED,
tr("The HGCM call failed (%Rrc)"), vrc);
/* Cleanup. */
if (!pProgress.isNull())
pProgress->uninit();
pProgress.setNull();
}
}
catch (std::bad_alloc &)
{
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
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgExpr(aPID, aPID > 0);
if (aSize < 0)
return setError(E_INVALIDARG, tr("The size argument (%lld) is negative"), aSize);
if (aFlags)
{
if (!(aFlags & ProcessOutputFlag_StdErr))
{
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
}
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
HRESULT rc = S_OK;
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.
*/
ComObjPtr <Progress> progress;
rc = progress.createObject();
if (SUCCEEDED(rc))
{
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Getting output of process")).raw(),
TRUE /* Cancelable */);
}
if (FAILED(rc)) return rc;
/* Adjust timeout. */
if (aTimeoutMS == 0)
aTimeoutMS = UINT32_MAX;
/* Set handle ID. */
uint32_t uHandleID = OUTPUT_HANDLE_ID_STDOUT; /* Default */
if (aFlags & ProcessOutputFlag_StdErr)
uHandleID = OUTPUT_HANDLE_ID_STDERR;
/* Search for existing PID. */
PCALLBACKDATAEXECOUT pData = (CALLBACKDATAEXECOUT*)RTMemAlloc(sizeof(CALLBACKDATAEXECOUT));
AssertReturn(pData, VBOX_E_IPRT_ERROR);
RT_ZERO(*pData);
/* Save PID + output flags for later use. */
pData->u32PID = aPID;
pData->u32Flags = aFlags;
/* Add job to callback contexts. */
uint32_t uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT,
pData, sizeof(CALLBACKDATAEXECOUT), progress);
Assert(uContextID > 0);
VBOXHGCMSVCPARM paParms[5];
int i = 0;
paParms[i++].setUInt32(uContextID);
paParms[i++].setUInt32(aPID);
paParms[i++].setUInt32(uHandleID);
paParms[i++].setUInt32(0 /* Flags, none set yet */);
com::SafeArray<BYTE> outputData((size_t)aSize);
int vrc = VINF_SUCCESS;
{
VMMDev *pVMMDev = NULL;
{
/* 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 ... */
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Forward the information to the VMM device. */
AssertPtr(mParent);
pVMMDev = mParent->getVMMDev();
}
if (pVMMDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_GET_OUTPUT,
i, paParms);
}
}
if (RT_SUCCESS(vrc))
{
LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
/*
* Wait for the HGCM low level callback until the process
* has been started (or something went wrong). This is necessary to
* get the PID.
*/
CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
BOOL fCanceled = FALSE;
if (it != mCallbackMap.end())
{
ComAssert(!it->second.pProgress.isNull());
/* Wait until operation completed. */
rc = it->second.pProgress->WaitForCompletion(aTimeoutMS);
if (FAILED(rc)) throw rc;
/* Was the operation canceled by one of the parties? */
rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
if (FAILED(rc)) throw rc;
if (!fCanceled)
{
BOOL fCompleted;
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
&& fCompleted)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Did we get some output? */
pData = (PCALLBACKDATAEXECOUT)it->second.pvData;
Assert(it->second.cbData == sizeof(CALLBACKDATAEXECOUT));
AssertPtr(pData);
if (pData->cbData)
{
/* Do we need to resize the array? */
if (pData->cbData > aSize)
outputData.resize(pData->cbData);
/* Fill output in supplied out buffer. */
memcpy(outputData.raw(), pData->pvData, pData->cbData);
outputData.resize(pData->cbData); /* Shrink to fit actual buffer size. */
}
else
{
/* No data within specified timeout available. Use a special
* error so that we can gently handle that case a bit below. */
vrc = VERR_NO_DATA;
}
}
else /* If callback not called within time ... well, that's a timeout! */
vrc = VERR_TIMEOUT;
}
else /* Operation was canceled. */
{
vrc = VERR_CANCELLED;
}
if (RT_FAILURE(vrc))
{
if (vrc == VERR_NO_DATA)
{
/* If there was no output data then this is no error we want
* to report to COM. The caller just gets back a size of 0 (zero). */
rc = S_OK;
}
else if (vrc == VERR_TIMEOUT)
{
rc = setError(VBOX_E_IPRT_ERROR,
tr("The guest did not output within time (%ums)"), aTimeoutMS);
}
else if (vrc == VERR_CANCELLED)
{
rc = setError(VBOX_E_IPRT_ERROR,
tr("The output operation was canceled"));
}
else
{
rc = setError(E_UNEXPECTED,
tr("The service call failed with error %Rrc"), vrc);
}
}
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Destroy locally used progress object. */
destroyCtrlCallbackContext(it);
}
}
else /* PID lookup failed. */
rc = setError(VBOX_E_IPRT_ERROR,
tr("Process (PID %u) not found!"), aPID);
}
else /* HGCM operation failed. */
rc = setError(E_UNEXPECTED,
tr("The HGCM call failed with error %Rrc"), vrc);
/* Cleanup. */
progress->uninit();
progress.setNull();
/* 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. */
if (RT_FAILURE(vrc) || FAILED(rc))
outputData.resize(0);
outputData.detachTo(ComSafeArrayOutArg(aData));
}
catch (std::bad_alloc &)
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
STDMETHODIMP Guest::GetProcessStatus(ULONG aPID, ULONG *aExitCode, ULONG *aFlags, ExecuteProcessStatus_T *aStatus)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
CheckComArgNotNull(aExitCode);
CheckComArgNotNull(aFlags);
CheckComArgNotNull(aStatus);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
HRESULT rc = S_OK;
try
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
GuestProcessMapIterConst it = getProcessByPID(aPID);
if (it != mGuestProcessMap.end())
{
*aExitCode = it->second.mExitCode;
*aFlags = it->second.mFlags;
*aStatus = it->second.mStatus;
}
else
rc = setError(VBOX_E_IPRT_ERROR,
tr("Process (PID %u) not found!"), aPID);
}
catch (std::bad_alloc &)
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
STDMETHODIMP Guest::CopyFromGuest(IN_BSTR aSource, IN_BSTR aDest,
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aFlags, IProgress **aProgress)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
CheckComArgStrNotEmptyOrNull(aSource);
CheckComArgStrNotEmptyOrNull(aDest);
CheckComArgStrNotEmptyOrNull(aUserName);
CheckComArgStrNotEmptyOrNull(aPassword);
CheckComArgOutPointerValid(aProgress);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags != CopyFileFlag_None)
{
if ( !(aFlags & CopyFileFlag_Recursive)
&& !(aFlags & CopyFileFlag_Update)
&& !(aFlags & CopyFileFlag_FollowLinks))
{
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
}
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
HRESULT rc = S_OK;
ComObjPtr<Progress> progress;
try
{
/* Create the progress object. */
progress.createObject();
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Copying file from guest to host")).raw(),
TRUE /* aCancelable */);
if (FAILED(rc)) throw rc;
/* Initialize our worker task. */
TaskGuest *pTask = new TaskGuest(TaskGuest::CopyFileFromGuest, this, progress);
AssertPtr(pTask);
std::auto_ptr<TaskGuest> task(pTask);
/* Assign data - aSource is the source file on the guest,
* aDest reflects the full path on the host. */
task->strSource = (Utf8Str(aSource));
task->strDest = (Utf8Str(aDest));
task->strUserName = (Utf8Str(aUserName));
task->strPassword = (Utf8Str(aPassword));
task->uFlags = aFlags;
rc = task->startThread();
if (FAILED(rc)) throw rc;
/* Don't destruct on success. */
task.release();
}
catch (HRESULT aRC)
{
rc = aRC;
}
if (SUCCEEDED(rc))
{
/* Return progress to the caller. */
progress.queryInterfaceTo(aProgress);
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP Guest::CopyToGuest(IN_BSTR aSource, IN_BSTR aDest,
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aFlags, IProgress **aProgress)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
CheckComArgStrNotEmptyOrNull(aSource);
CheckComArgStrNotEmptyOrNull(aDest);
CheckComArgStrNotEmptyOrNull(aUserName);
CheckComArgStrNotEmptyOrNull(aPassword);
CheckComArgOutPointerValid(aProgress);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags != CopyFileFlag_None)
{
if ( !(aFlags & CopyFileFlag_Recursive)
&& !(aFlags & CopyFileFlag_Update)
&& !(aFlags & CopyFileFlag_FollowLinks))
{
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
}
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
HRESULT rc = S_OK;
ComObjPtr<Progress> progress;
try
{
/* Create the progress object. */
progress.createObject();
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Copying file from host to guest")).raw(),
TRUE /* aCancelable */);
if (FAILED(rc)) throw rc;
/* Initialize our worker task. */
TaskGuest *pTask = new TaskGuest(TaskGuest::CopyFileToGuest, this, progress);
AssertPtr(pTask);
std::auto_ptr<TaskGuest> task(pTask);
/* Assign data - aSource is the source file on the host,
* aDest reflects the full path on the guest. */
task->strSource = (Utf8Str(aSource));
task->strDest = (Utf8Str(aDest));
task->strUserName = (Utf8Str(aUserName));
task->strPassword = (Utf8Str(aPassword));
task->uFlags = aFlags;
rc = task->startThread();
if (FAILED(rc)) throw rc;
/* Don't destruct on success. */
task.release();
}
catch (HRESULT aRC)
{
rc = aRC;
}
if (SUCCEEDED(rc))
{
/* Return progress to the caller. */
progress.queryInterfaceTo(aProgress);
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP Guest::DirectoryClose(ULONG aHandle)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
return VBOX_E_NOT_SUPPORTED;
#endif
}
STDMETHODIMP Guest::DirectoryCreate(IN_BSTR aDirectory,
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aMode, ULONG aFlags)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgStrNotEmptyOrNull(aDirectory);
/* Do not allow anonymous executions (with system rights). */
if (RT_UNLIKELY((aUserName) == NULL || *(aUserName) == '\0'))
return setError(E_INVALIDARG, tr("No user name specified"));
LogRel(("Creating guest directory \"%s\" as user \"%s\" ...\n",
Utf8Str(aDirectory).c_str(), Utf8Str(aUserName).c_str()));
return directoryCreateInternal(aDirectory,
aUserName, aPassword,
aMode, aFlags, NULL /* rc */);
#endif
}
HRESULT Guest::directoryCreateInternal(IN_BSTR aDirectory,
IN_BSTR aUserName, IN_BSTR aPassword,
ULONG aMode, ULONG aFlags, int *pRC)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgStrNotEmptyOrNull(aDirectory);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags != DirectoryCreateFlag_None)
{
if (!(aFlags & DirectoryCreateFlag_Parents))
{
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
}
HRESULT rc = S_OK;
try
{
Utf8Str Utf8Directory(aDirectory);
Utf8Str Utf8UserName(aUserName);
Utf8Str Utf8Password(aPassword);
com::SafeArray<IN_BSTR> args;
com::SafeArray<IN_BSTR> env;
/*
* Prepare tool command line.
*/
if (aFlags & DirectoryCreateFlag_Parents)
args.push_back(Bstr("--parents").raw()); /* We also want to create the parent directories. */
if (aMode > 0)
{
args.push_back(Bstr("--mode").raw()); /* Set the creation mode. */
char szMode[16];
RTStrPrintf(szMode, sizeof(szMode), "%o", aMode);
args.push_back(Bstr(szMode).raw());
}
args.push_back(Bstr(Utf8Directory).raw()); /* The directory we want to create. */
/*
* Execute guest process.
*/
ComPtr<IProgress> progressExec;
ULONG uPID;
if (SUCCEEDED(rc))
{
rc = ExecuteProcess(Bstr(VBOXSERVICE_TOOL_MKDIR).raw(),
ExecuteProcessFlag_Hidden,
ComSafeArrayAsInParam(args),
ComSafeArrayAsInParam(env),
Bstr(Utf8UserName).raw(),
Bstr(Utf8Password).raw(),
5 * 1000 /* Wait 5s for getting the process started. */,
&uPID, progressExec.asOutParam());
}
if (SUCCEEDED(rc))
{
/* Wait for process to exit ... */
rc = progressExec->WaitForCompletion(-1);
if (FAILED(rc)) return rc;
BOOL fCompleted = FALSE;
BOOL fCanceled = FALSE;
progressExec->COMGETTER(Completed)(&fCompleted);
if (!fCompleted)
progressExec->COMGETTER(Canceled)(&fCanceled);
if (fCompleted)
{
ExecuteProcessStatus_T retStatus;
ULONG uRetExitCode, uRetFlags;
if (SUCCEEDED(rc))
{
rc = GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &retStatus);
if (SUCCEEDED(rc) && uRetExitCode != 0)
{
rc = setError(VBOX_E_IPRT_ERROR,
tr("Error %u while creating guest directory"), uRetExitCode);
}
}
}
else if (fCanceled)
rc = setError(VBOX_E_IPRT_ERROR,
tr("Guest directory creation was aborted"));
else
AssertReleaseMsgFailed(("Guest directory creation neither completed nor canceled!?"));
}
}
catch (std::bad_alloc &)
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP Guest::DirectoryOpen(IN_BSTR aDirectory, IN_BSTR aFilter,
ULONG aFlags, IN_BSTR aUserName, IN_BSTR aPassword,
ULONG *aHandle)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
CheckComArgStrNotEmptyOrNull(aDirectory);
CheckComArgStrNotEmptyOrNull(aUserName);
CheckComArgStrNotEmptyOrNull(aPassword);
CheckComArgNotNull(aHandle);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags != DirectoryOpenFlag_None)
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
HRESULT rc = S_OK;
try
{
Utf8Str Utf8Dir(aDirectory);
Utf8Str UtfFilter(aFilter);
Utf8Str Utf8UserName(aUserName);
Utf8Str Utf8Password(aPassword);
/*
* 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.
*/
ComObjPtr <Progress> progress;
rc = progress.createObject();
if (SUCCEEDED(rc))
{
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Getting output of process")).raw(),
TRUE /* Cancelable */);
}
if (FAILED(rc)) return rc;
PCALLBACKDATADIROPEN pData = (PCALLBACKDATADIROPEN)RTMemAlloc(sizeof(CALLBACKDATADIROPEN));
AssertReturn(pData, VBOX_E_IPRT_ERROR);
RT_ZERO(*pData);
uint32_t uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START,
pData, sizeof(CALLBACKDATADIROPEN), progress);
Assert(uContextID > 0);
VBOXHGCMSVCPARM paParms[8];
int i = 0;
paParms[i++].setUInt32(uContextID);
paParms[i++].setPointer((void*)Utf8Dir.c_str(), (uint32_t)Utf8Dir.length() + 1);
paParms[i++].setPointer((void*)UtfFilter.c_str(), (uint32_t)UtfFilter.length() + 1);
paParms[i++].setUInt32(0 /* Flags, none set yet */);
paParms[i++].setPointer((void*)Utf8UserName.c_str(), (uint32_t)Utf8UserName.length() + 1);
paParms[i++].setPointer((void*)Utf8Password.c_str(), (uint32_t)Utf8Password.length() + 1);
int vrc = VINF_SUCCESS;
{
VMMDev *pVMMDev = NULL;
{
/* 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 ... */
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Forward the information to the VMM device. */
AssertPtr(mParent);
pVMMDev = mParent->getVMMDev();
}
if (pVMMDev)
{
LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_DIR_OPEN,
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.
*/
CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
Assert(it != mCallbackMap.end());
ComAssert(!it->second.pProgress.isNull());
BOOL fCanceled = FALSE;
/* Wait until operation completed. */
rc = it->second.pProgress->WaitForCompletion(10 * 1000);
if (FAILED(rc)) throw rc;
/* Was the operation canceled by one of the parties? */
rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
if (FAILED(rc)) throw rc;
if (!fCanceled)
{
BOOL fCompleted;
if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
&& fCompleted)
{
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Did we get some output? */
pData = (PCALLBACKDATADIROPEN)it->second.pvData;
Assert(it->second.cbData == sizeof(PCALLBACKDATADIROPEN));
AssertPtr(pData);
}
else /* If callback not called within time ... well, that's a timeout! */
vrc = VERR_TIMEOUT;
}
else /* Operation was canceled. */
{
vrc = VERR_CANCELLED;
}
if (RT_FAILURE(vrc))
{
if (vrc == VERR_NO_DATA)
{
/* If there was no output data then this is no error we want
* to report to COM. The caller just gets back a size of 0 (zero). */
rc = S_OK;
}
else if (vrc == VERR_TIMEOUT)
{
rc = setError(VBOX_E_IPRT_ERROR,
tr("The guest did not output within time"));
}
else if (vrc == VERR_CANCELLED)
{
rc = setError(VBOX_E_IPRT_ERROR,
tr("The output operation was canceled"));
}
else
{
rc = setError(E_UNEXPECTED,
tr("The service call failed with error %Rrc"), vrc);
}
}
{
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Destroy locally used progress object. */
destroyCtrlCallbackContext(it);
}
}
else /* HGCM operation failed. */
rc = setError(E_UNEXPECTED,
tr("The HGCM call failed with error %Rrc"), vrc);
/* Cleanup. */
progress->uninit();
progress.setNull();
}
catch (std::bad_alloc &)
{
rc = E_OUTOFMEMORY;
}
return rc;
#endif
}
STDMETHODIMP Guest::DirectoryRead(ULONG aHandle, IGuestDirEntry **aDirEntry)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
return VBOX_E_NOT_SUPPORTED;
#endif
}
STDMETHODIMP Guest::FileExists(IN_BSTR aFile, IN_BSTR aUserName, IN_BSTR aPassword, BOOL *aExists)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
using namespace guestControl;
return VBOX_E_NOT_SUPPORTED;
#endif
}
STDMETHODIMP Guest::UpdateGuestAdditions(IN_BSTR aSource, ULONG aFlags, IProgress **aProgress)
{
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else /* VBOX_WITH_GUEST_CONTROL */
CheckComArgStrNotEmptyOrNull(aSource);
CheckComArgOutPointerValid(aProgress);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
/* Validate flags. */
if (aFlags)
{
if (!(aFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly))
return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
}
AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
HRESULT rc = S_OK;
ComObjPtr<Progress> progress;
try
{
/* Create the progress object. */
progress.createObject();
rc = progress->init(static_cast<IGuest*>(this),
Bstr(tr("Updating Guest Additions")).raw(),
TRUE /* aCancelable */);
if (FAILED(rc)) throw rc;
/* Initialize our worker task. */
TaskGuest *pTask = new TaskGuest(TaskGuest::UpdateGuestAdditions, this, progress);
AssertPtr(pTask);
std::auto_ptr<TaskGuest> task(pTask);
/* Assign data - in that case aSource is the full path
* to the Guest Additions .ISO we want to mount. */
task->strSource = (Utf8Str(aSource));
task->uFlags = aFlags;
rc = task->startThread();
if (FAILED(rc)) throw rc;
/* Don't destruct on success. */
task.release();
}
catch (HRESULT aRC)
{
rc = aRC;
}
if (SUCCEEDED(rc))
{
/* Return progress to the caller. */
progress.queryInterfaceTo(aProgress);
}
return rc;
#endif /* VBOX_WITH_GUEST_CONTROL */
}