GuestProcessImpl.cpp revision ccbdc11833996cb9f3be7868f1ebaefcacafb94d
/* $Id$ */
/** @file
* VirtualBox Main - XXX.
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "GuestProcessImpl.h"
#include "GuestSessionImpl.h"
#include "ConsoleImpl.h"
#include "Global.h"
#include "AutoCaller.h"
#include "Logging.h"
#include "VMMDev.h"
#include <memory> /* For auto_ptr. */
struct GuestProcessTask
{
mRC(VINF_SUCCESS) { }
virtual ~GuestProcessTask(void) { }
private:
int mRC;
};
struct GuestProcessStartTask : public GuestProcessTask
{
: GuestProcessTask(pProcess) { }
};
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
mData.mNextContextID = 0;
mData.mProcessID = 0;
mData.mWaitCount = 0;
return hr;
}
void GuestProcess::FinalRelease(void)
{
uninit();
}
// public initializer/uninitializer for internal purposes only
/////////////////////////////////////////////////////////////////////////////
int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aProcessID, const GuestProcessInfo &aProcInfo)
{
LogFlowThisFunc(("aConsole=%p, aSession=%p, aProcessID=%RU32\n",
/* Enclose the state transition NotReady->InInit->Ready. */
AutoInitSpan autoInitSpan(this);
/* Everything else will be set by the actual starting routine. */
/* Confirm a successful initialization when it's the case. */
return VINF_SUCCESS;
}
/**
* Uninitializes the instance.
* Called from FinalRelease().
*/
void GuestProcess::uninit(void)
{
LogFlowThisFunc(("\n"));
/* Enclose the state transition Ready->InUninit->NotReady. */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
#ifndef VBOX_WITH_GUEST_CONTROL
close();
#endif
}
/////////////////////////////////////////////////////////////////////////////
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
size_t s = 0;
it++, s++)
{
}
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
{
}
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
return S_OK;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
// private methods
/////////////////////////////////////////////////////////////////////////////
/*
SYNC TO ASK:
either can be called synchronously or asynchronously by running in a Main worker
thread.
Rules:
- Only one async operation per process a time can be around.
*/
{
ULONG uSessionID = 0;
/* Create a new context ID and assign it. */
int rc = VERR_NOT_FOUND;
ULONG uNewContextID = 0;
for (;;)
{
/* Create a new context ID ... */
mData.mNextContextID = 0;
/* 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 (++uTries == UINT32_MAX)
break; /* Don't try too hard. */
}
if (RT_SUCCESS(rc))
{
/* Add callback with new context ID to our callback map.
* Note: This is *not* uNewContextID (which also includes
* the session + process ID), just the context count
* will be used here. */
/* Report back new context ID. */
if (puContextID)
LogFlowFunc(("Added new callback (Session: %RU32, Process: %RU32, Count=%RU32) CID=%RU32\n",
}
return rc;
}
int GuestProcess::callbackDispatcher(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData)
{
#ifdef DEBUG
LogFlowFunc(("uPID=%RU32, uContextID=%RU32, uFunction=%RU32, pvData=%p, cbData=%RU32\n",
#endif
#ifdef DEBUG
#endif
int rc;
{
switch (uFunction)
{
case GUEST_DISCONNECTED:
{
PCALLBACKDATACLIENTDISCONNECTED pCallbackData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvData);
AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
case GUEST_EXEC_SEND_STATUS:
{
break;
}
case GUEST_EXEC_SEND_OUTPUT:
{
break;
}
{
AssertReturn(CALLBACKDATAMAGIC_EXEC_IN_STATUS == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
default:
/* Silently ignore not implemented functions. */
break;
}
}
else
rc = VERR_NOT_FOUND;
#ifdef DEBUG
#endif
return rc;
}
{
}
{
{
return VINF_SUCCESS;
}
return VERR_NOT_FOUND;
}
inline bool GuestProcess::isAlive(void)
{
}
void GuestProcess::close(void)
{
/*
* Cancel all callbacks + waiters.
* Note: Deleting them is the job of the caller!
*/
{
}
if (mData.mWaitEvent)
{
}
}
bool GuestProcess::isReady(void)
{
{
return true;
}
return false;
}
int GuestProcess::onGuestDisconnected(GuestCtrlCallback *pCallback, PCALLBACKDATACLIENTDISCONNECTED pData)
{
/* First, signal callback in every case. */
/* Do we need to report a termination? */
else
/* Signal in any case. */
return rc;
}
int GuestProcess::onProcessInputStatus(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECINSTATUS pData)
{
LogFlowFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32, cbProcessed=%RU32, pCallback=%p, pData=%p\n",
int rc = VINF_SUCCESS;
/** @todo Fill data into callback. */
/* First, signal callback in every case. */
/* Then do the WaitFor signalling stuff. */
return rc;
}
int GuestProcess::onProcessStatusChange(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECSTATUS pData)
{
LogFlowFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32, pCallback=%p, pData=%p\n",
int rc = VINF_SUCCESS;
/* Get data from the callback payload. */
{
case PROC_STS_STARTED:
{
break;
}
case PROC_STS_TEN:
{
break;
}
case PROC_STS_TES:
{
break;
}
case PROC_STS_TEA:
{
break;
}
case PROC_STS_TOK:
{
break;
}
case PROC_STS_TOA:
{
break;
}
case PROC_STS_DWN:
{
break;
}
case PROC_STS_ERROR:
{
Utf8Str strError = Utf8StrFmt(tr("Guest process \"%s\" could not be started: ", mData.mProcess.mCommand.c_str()));
/* Note: It's not required that the process has been started before. */
{
}
else
{
/** @todo pData->u32Flags; /** @todo int vs. uint32 -- IPRT errors are *negative* !!! */
{
case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
break;
case VERR_PATH_NOT_FOUND:
break;
case VERR_BAD_EXE_FORMAT:
break;
break;
case VERR_INVALID_NAME:
break;
case VERR_TIMEOUT:
break;
case VERR_CANCELLED:
break;
case VERR_PERMISSION_DENIED:
break;
case VERR_MAX_PROCS_REACHED:
break;
default:
break;
}
}
break;
}
case PROC_STS_UNDEFINED:
default:
{
/* Silently skip this request. */
break;
}
}
LogFlowFunc(("Got rc=%Rrc, waitResult=%d\n",
/*
* Now do the signalling stuff.
*/
if (fSignal)
{
if (RT_SUCCESS(rc))
}
return rc;
}
{
LogFlowFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, pCallback=%p, pData=%p\n",
/* Copy data into callback. */
/* First, signal callback in every case. */
if (RT_SUCCESS(rc))
/* Then do the WaitFor signalling stuff. */
if ( (uWaitFlags & ProcessWaitForFlag_StdOut)
|| (uWaitFlags & ProcessWaitForFlag_StdErr))
{
}
else if ( (uWaitFlags & ProcessWaitForFlag_StdOut)
{
}
else if ( (uWaitFlags & ProcessWaitForFlag_StdErr)
{
}
if (fSignal)
{
if (RT_SUCCESS(rc))
}
return rc;
}
int GuestProcess::readData(ULONG uHandle, ULONG uSize, ULONG uTimeoutMS, BYTE *pbData, size_t cbData)
{
LogFlowFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pbData=%p, cbData=%z\n",
return 0;
}
{
/* Forward the information to the VMM device. */
if (RT_FAILURE(rc))
{
}
return rc;
}
{
#ifdef DEBUG
/* Do not allow overwriting an already set error. If this happens
#endif
return rc2;
}
int GuestProcess::setErrorExternal(void)
{
}
{
LogFlowFunc(("enmWaitResult=%d, mWaitCount=%RU32, mWaitEvent=%p\n",
/* Note: No write locking here -- already done in the caller. */
int rc = VINF_SUCCESS;
if (mData.mWaitEvent)
return rc;
}
int GuestProcess::startProcess(void)
{
LogFlowFunc(("aCmd=%s, aTimeoutMS=%RU32, fFlags=%x\n",
/* Wait until the caller function (if kicked off by a thread)
* has returned and continue operation. */
int rc;
ULONG uContextID = 0;
if (!pCallbackStart)
return VERR_NO_MEMORY;
/* Create callback and add it to the map. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
// AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
/* Prepare arguments. */
if (cArgs)
{
if (RT_SUCCESS(rc))
}
/* Prepare environment. */
if (RT_SUCCESS(rc))
{
/* Prepare HGCM call. */
int i = 0;
paParms[i++].setPointer((void*)sessionCreds.mPassword.c_str(), (ULONG)sessionCreds.mPassword.length() + 1);
/** @todo New command needs the domain as well! */
/*
* 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
if (RT_FAILURE(rc))
{
int rc2;
if (rc == VERR_INVALID_VM_HANDLE)
else if (rc == VERR_NOT_FOUND)
else if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
else
}
}
if (pszArgs)
if (RT_SUCCESS(rc))
{
/*
* Let's wait for the process being started.
*/
{
}
else
rc = VERR_TIMEOUT;
}
if (RT_SUCCESS(rc))
}
return rc;
}
int GuestProcess::startProcessAsync(void)
{
/* Asynchronously start the process on the guest by kicking off a
* worker thread. */
"gctlPrcStart");
if (RT_SUCCESS(rc))
{
/* pTask is now owned by startProcessThread(), so release it. */
}
return rc;
}
{
if (RT_FAILURE(rc))
{
/** @todo What now? */
}
return VINF_SUCCESS;
}
int GuestProcess::terminateProcess(void)
{
return VERR_NOT_IMPLEMENTED;
}
{
LogFlowFunc(("fWaitFlags=%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p\n",
/* Did some error occur before? Then skip waiting and return. */
if (curStatus == ProcessStatus_Error)
{
return VINF_SUCCESS;
}
if ( (fWaitFlags & ProcessWaitForFlag_Terminate)
|| (fWaitFlags & ProcessWaitForFlag_StdErr))
{
{
case ProcessStatus_Down:
break;
break;
case ProcessStatus_Error:
break;
case ProcessStatus_Undefined:
case ProcessStatus_Starting:
/* Do the waiting below. */
break;
default:
return VERR_NOT_IMPLEMENTED;
}
}
else if (fWaitFlags & ProcessWaitForFlag_Start)
{
{
case ProcessStatus_Started:
case ProcessStatus_Paused:
case ProcessStatus_Down:
break;
case ProcessStatus_Error:
break;
case ProcessStatus_Undefined:
case ProcessStatus_Starting:
/* Do the waiting below. */
break;
default:
return VERR_NOT_IMPLEMENTED;
}
}
/* No waiting needed? Return immediately. */
return VINF_SUCCESS;
if (mData.mWaitCount > 0)
return VERR_ALREADY_EXISTS;
mData.mWaitCount++;
if (RT_SUCCESS(rc))
/* Note: The caller always is responsible of deleting the
* stuff it created before. See close() for more information. */
delete mData.mWaitEvent;
mData.mWaitCount--;
return rc;
}
{
switch (procStatus)
{
case ProcessStatus_Started:
break;
break;
{
break;
}
{
break;
}
{
break;
}
{
break;
}
case ProcessStatus_Down:
{
strMsg = Utf8StrFmt(tr("Guest process \"%s\" (PID %RU32) was killed because guest OS is shutting down\n"),
break;
}
case ProcessStatus_Error:
{
strMsg = Utf8StrFmt(tr("Guest process \"%s\" could not be started: ", mData.mProcess.mCommand.c_str()));
/* Note: It's not required that the process has been started before. */
{
}
else
{
switch (rc) /* rc contains the IPRT error code from guest side. */
{
case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
break;
case VERR_PATH_NOT_FOUND:
break;
case VERR_BAD_EXE_FORMAT:
break;
break;
case VERR_INVALID_NAME:
break;
case VERR_TIMEOUT:
break;
case VERR_CANCELLED:
break;
case VERR_PERMISSION_DENIED:
break;
case VERR_MAX_PROCS_REACHED:
break;
default:
break;
}
}
break;
}
case ProcessStatus_Undefined:
default:
/* Silently skip this request. */
break;
}
if (RT_FAILURE(rc))
{
}
if (fLog)
{
}
return hr;
}
int GuestProcess::writeData(ULONG uHandle, const BYTE *pbData, size_t cbData, ULONG uTimeoutMS, ULONG *puWritten)
{
LogFlowFunc(("uPID=%RU32, uHandle=%RU32, pbData=%p, cbData=%z, uTimeoutMS=%RU32, puWritten=%p\n",
/* Rest is optional. */
return 0;
}
// implementation of public methods
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aSize, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData))
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
if (aSize == 0)
AutoCaller autoCaller(this);
if (RT_SUCCESS(rc))
/** @todo Do setError() here. */
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
int rc = terminateProcess();
/** @todo Do setError() here. */
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP GuestProcess::WaitFor(ULONG aWaitFlags, ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
/*
* Note: Do not hold any locks here while waiting!
*/
if (RT_SUCCESS(rc))
{
hr = setErrorExternal();
}
else
{
if (rc == VERR_TIMEOUT)
tr("Process \"%s\" (PID %RU32) did not respond within time (%RU32ms)"),
else
tr("Waiting for process \"%s\" (PID %RU32) failed with rc=%Rrc"),
}
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP GuestProcess::WaitForArray(ComSafeArrayIn(ProcessWaitForFlag_T, aFlags), ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
/*
* Note: Do not hold any locks here while waiting!
*/
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
STDMETHODIMP GuestProcess::Write(ULONG aHandle, ComSafeArrayIn(BYTE, aData), ULONG aTimeoutMS, ULONG *aWritten)
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
/** @todo Do setError() here. */
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}