GuestProcessImpl.cpp revision cb56d80f0d157315e2d20463de15ec4ba231d876
/* $Id$ */
/** @file
* VirtualBox Main - Guest process handling.
*/
/*
* Copyright (C) 2012-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/**
* Locking rules:
* - When the main dispatcher (callbackDispatcher) is called it takes the
* WriteLock while dispatching to the various on* methods.
* - All other outer functions (accessible by Main) must not own a lock
* while waiting for a callback or for an event.
* - Only keep Read/WriteLocks as short as possible and only when necessary.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "GuestProcessImpl.h"
#include "GuestSessionImpl.h"
#include "GuestCtrlImplPrivate.h"
#include "ConsoleImpl.h"
#include "VirtualBoxErrorInfoImpl.h"
#include "Global.h"
#include "AutoCaller.h"
#include "VBoxEvents.h"
#include <memory> /* For auto_ptr. */
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
class GuestProcessTask
{
public:
mRC(VINF_SUCCESS) { }
virtual ~GuestProcessTask(void) { }
protected:
int mRC;
};
class GuestProcessStartTask : public GuestProcessTask
{
public:
: GuestProcessTask(pProcess) { }
};
/**
* Internal listener class to serve events in an
* active manner, e.g. without polling delays.
*/
class GuestProcessListener
{
public:
GuestProcessListener(void)
{
}
{
return S_OK;
}
void uninit(void)
{
}
{
switch (aType)
{
{
#ifdef DEBUG
LogFlowThisFunc(("Signalling events of type=%ld, process=%p resulted in rc=%Rrc\n",
#endif
break;
}
default:
break;
}
return S_OK;
}
private:
};
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
return BaseFinalConstruct();
}
void GuestProcess::FinalRelease(void)
{
uninit();
}
// public initializer/uninitializer for internal purposes only
/////////////////////////////////////////////////////////////////////////////
{
LogFlowThisFunc(("aConsole=%p, aSession=%p, aProcessID=%RU32\n",
/* Enclose the state transition NotReady->InInit->Ready. */
AutoInitSpan autoInitSpan(this);
#ifndef VBOX_WITH_GUEST_CONTROL
return VINF_SUCCESS;
#else
if (RT_SUCCESS(vrc))
{
else
{
}
}
if (RT_SUCCESS(vrc))
{
try
{
{
TRUE /* Active listener */);
{
if (RT_SUCCESS(vrc))
{
}
}
else
}
else
}
{
}
}
if (RT_SUCCESS(vrc))
{
/* Everything else will be set by the actual starting routine. */
/* Confirm a successful initialization when it's the case. */
return vrc;
}
return vrc;
#endif
}
/**
* Uninitializes the instance.
* Called from FinalRelease().
*/
void GuestProcess::uninit(void)
{
/* Enclose the state transition Ready->InUninit->NotReady. */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
LogFlowThisFunc(("mCmd=%s, PID=%RU32\n",
/* Terminate process if not already done yet. */
int guestRc = VINF_SUCCESS;
/* Note: Don't return here yet; first uninit all other stuff in
* case of failure. */
#ifdef VBOX_WITH_GUEST_CONTROL
baseUninit();
#endif
LogFlowThisFunc(("Returning rc=%Rrc, guestRc=%Rrc\n",
}
/////////////////////////////////////////////////////////////////////////////
{
#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);
// no need to lock - lifetime constant
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
/////////////////////////////////////////////////////////////////////////////
int GuestProcess::callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
{
#ifdef DEBUG
LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
#endif
int vrc;
{
case GUEST_DISCONNECTED:
{
break;
}
case GUEST_EXEC_STATUS:
{
break;
}
case GUEST_EXEC_OUTPUT:
{
break;
}
case GUEST_EXEC_INPUT_STATUS:
{
break;
}
default:
/* Silently ignore not implemented functions. */
break;
}
#ifdef DEBUG
#endif
return vrc;
}
/**
* Checks if the current assigned PID matches another PID (from a callback).
*
* processes so it can happen that a formerly started process A
* (which has the context ID 0 (session=0, process=0, count=0) will
* send a delayed message to the host if this process has already
* been discarded there and the same context ID was reused by
* a process B. Process B in turn then has a different guest PID.
*
* @return IPRT status code.
* @param uPID PID to check.
*/
{
/* Was there a PID assigned yet? */
{
/*
*/
{
/* Simply ignore the stale requests. */
}
#ifndef DEBUG_andy
/* This should never happen! */
AssertReleaseMsg(mData.mPID == uPID, ("Unterminated guest process (guest PID %RU32) sent data to a newly started process (host PID %RU32)\n",
#endif
}
return VINF_SUCCESS;
}
/* static */
{
/** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
switch (guestRc)
{
case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
break;
case VERR_INVALID_VM_HANDLE:
break;
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;
case VERR_NOT_EQUAL: /** @todo Imprecise to the user; can mean anything and all. */
break;
case VERR_NOT_FOUND:
break;
default:
break;
}
return strError;
}
inline bool GuestProcess::isAlive(void)
{
}
inline bool GuestProcess::hasEnded(void)
{
}
int GuestProcess::onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
return vrc;
}
int GuestProcess::onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
/* pCallback is optional. */
return VERR_INVALID_PARAMETER;
/* pSvcCb->mpaParms[0] always contains the context ID. */
LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
if (RT_SUCCESS(vrc))
{
{
case INPUT_STS_WRITTEN:
break;
case INPUT_STS_ERROR:
break;
case INPUT_STS_TERMINATED:
break;
case INPUT_STS_OVERFLOW:
break;
case INPUT_STS_UNDEFINED:
/* Fall through is intentional. */
default:
break;
}
{
/* Copy over necessary data before releasing lock again. */
/** @todo Also handle mSession? */
}
}
return vrc;
}
int GuestProcess::onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
return VERR_NOT_IMPLEMENTED;
}
int GuestProcess::onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
return VERR_INVALID_PARAMETER;
/* pSvcCb->mpaParms[0] always contains the context ID. */
LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
if (RT_SUCCESS(vrc))
{
int procRc = VINF_SUCCESS;
{
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:
{
break;
}
case PROC_STS_UNDEFINED:
default:
{
/* Silently skip this request. */
break;
}
}
LogFlowThisFunc(("Got rc=%Rrc, procSts=%ld, procRc=%Rrc\n",
/* Set the process status. */
if (RT_SUCCESS(vrc))
}
return vrc;
}
int GuestProcess::onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
return VERR_INVALID_PARAMETER;
/* pSvcCb->mpaParms[0] always contains the context ID. */
LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n",
if (RT_SUCCESS(vrc))
{
}
return vrc;
}
{
LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, pGuestRc=%p\n",
/* pcbRead is optional. */
/* Skip reading if the process wasn't started with the appropriate
* flags. */
|| ( ( uHandle == OUTPUT_HANDLE_ID_STDOUT
|| ( uHandle == OUTPUT_HANDLE_ID_STDERR
)
{
if (pcbRead)
*pcbRead = 0;
if (pGuestRc)
*pGuestRc = VINF_SUCCESS;
return VINF_SUCCESS; /* Nothing to read anymore. */
}
int vrc;
try
{
/*
* On Guest Additions < 4.3 there is no guarantee that the process status
* change arrives *after* the output event, e.g. if this was the last output
* block being read and the process will report status "terminate".
* So just skip checking for process status change and only wait for the
* output event.
*/
}
{
}
if (RT_FAILURE(vrc))
return vrc;
if (RT_SUCCESS(vrc))
{
int i = 0;
}
if (RT_SUCCESS(vrc))
return vrc;
}
/* Does not do locking; caller is responsible for that! */
{
LogFlowThisFunc(("oldStatus=%ld, newStatus=%ld, procRc=%Rrc\n",
if (procStatus == ProcessStatus_Error)
{
/* Do not allow overwriting an already set error. If this happens
}
else
int rc = VINF_SUCCESS;
{
{
}
/* Copy over necessary data before releasing lock again. */
/** @todo Also handle mSession? */
#if 0
/*
* On Guest Additions < 4.3 there is no guarantee that outstanding
* requests will be delivered to the host after the process has ended,
* so just cancel all waiting events here to not let clients run
* into timeouts.
*/
&& hasEnded())
{
LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n"));
rc = cancelWaitEvents();
}
#endif
}
return rc;
}
/* static */
{
}
{
LogFlowThisFunc(("uTimeoutMS=%RU32, procCmd=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n",
/* Wait until the caller function (if kicked off by a thread)
* has returned and continue operation. */
int vrc;
try
{
}
{
}
if (RT_FAILURE(vrc))
return vrc;
/* Prepare arguments. */
if (cArgs >= UINT32_MAX)
if ( RT_SUCCESS(vrc)
&& cArgs)
{
{
}
if (RT_SUCCESS(vrc))
if (papszArgv)
{
size_t i = 0;
while (papszArgv[i])
}
}
/* Calculate arguments size (in bytes). */
if (RT_SUCCESS(vrc))
/* Prepare environment. */
if (RT_SUCCESS(vrc))
if (RT_SUCCESS(vrc))
{
/* Prepare HGCM call. */
int i = 0;
if (uProtocol < 2)
{
/* In protocol v1 (VBox < 4.3) the credentials were part of the execution
* call. In newer protocols these credentials are part of the opened guest
* session, so not needed anymore here. */
paParms[i++].setPointer((void*)sessionCreds.mPassword.c_str(), (ULONG)sessionCreds.mPassword.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.
*/
else
if (uProtocol >= 2)
{
/* CPU affinity: We only support one CPU affinity block at the moment,
* so that makes up to 64 CPUs total. This can be more in the future. */
/* The actual CPU affinity blocks. */
}
if (RT_FAILURE(vrc))
{
}
}
if (pszArgs)
if (RT_SUCCESS(vrc))
return vrc;
}
int GuestProcess::startProcessAsync(void)
{
int vrc;
try
{
/* Asynchronously start the process on the guest by kicking off a
* worker thread. */
"gctlPrcStart");
if (RT_SUCCESS(vrc))
{
/* pTask is now owned by startProcessThread(), so release it. */
}
}
{
}
return vrc;
}
/* static */
{
NULL /* Guest rc, ignored */);
/* Nothing to do here anymore. */
return vrc;
}
{
/* pGuestRc is optional. */
{
LogFlowThisFunc(("Process not started (yet), nothing to terminate\n"));
return VINF_SUCCESS; /* Nothing to do (anymore). */
}
int vrc = VINF_SUCCESS;
/* Note: VBox < 4.3 (aka protocol version 1) does not
* support this, so just skip. */
if (RT_SUCCESS(vrc))
{
try
{
}
{
}
if (RT_FAILURE(vrc))
return vrc;
int i = 0;
if (RT_SUCCESS(vrc))
}
return vrc;
}
/* static */
{
switch (newStatus)
{
break;
case ProcessStatus_Down:
break;
/* Fall through is intentional. */
break;
case ProcessStatus_Error:
break;
case ProcessStatus_Started:
{
switch (oldStatus)
{
case ProcessStatus_Starting:
{
}
else
{
/*
* If ProcessCreateFlag_WaitForProcessStartOnly was specified on process creation the
* caller is not interested in getting further process statuses -- so just don't notify
* anything here anymore and return.
*/
}
break;
default:
/* No result available (yet). */
break;
}
break;
}
case ProcessStatus_Undefined:
case ProcessStatus_Starting:
/* No result available yet. */
break;
default:
break;
}
if (newStatus == ProcessStatus_Started)
{
/* Filter out waits which are *not* supported using
* older guest control Guest Additions.
*
** @todo ProcessWaitForFlag_Std* flags are not implemented yet.
*/
{
if ( waitResult == ProcessWaitResult_None
/* We don't support waiting for stdin, out + err,
* just skip waiting then. */
&& ( (fWaitFlags & ProcessWaitForFlag_StdIn)
)
)
{
/* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
}
}
}
#ifdef DEBUG
LogFlowFunc(("oldStatus=%ld, newStatus=%ld, fWaitFlags=0x%x, waitResult=%ld\n",
#endif
return waitResult;
}
{
}
{
LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, pGuestRc=%p\n",
/* Did some error occur before? Then skip waiting and return. */
if (curStatus == ProcessStatus_Error)
{
AssertMsg(RT_FAILURE(mData.mLastError), ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
if (pGuestRc)
return VERR_GSTCTL_GUEST_ERROR;
}
/* No waiting needed? Return immediately using the last set error. */
if (waitResult != ProcessWaitResult_None)
{
if (pGuestRc)
}
int vrc;
try
{
}
{
}
if (RT_FAILURE(vrc))
return vrc;
/*
* Do the actual waiting.
*/
for (;;)
{
if ( uTimeoutMS != RT_INDEFINITE_WAIT
&& u64ElapsedMS >= uTimeoutMS)
{
vrc = VERR_TIMEOUT;
break;
}
if (RT_SUCCESS(vrc))
{
#ifdef DEBUG
LogFlowThisFunc(("Got new status change: fWaitFlags=0x%x, newStatus=%ld, waitResult=%ld\n",
#endif
break;
}
else /* Waiting failed, bail out. */
break;
}
LogFlowThisFunc(("Returned waitResult=%ld, newStatus=%ld, rc=%Rrc\n",
return vrc;
}
{
if (RT_SUCCESS(vrc))
{
{
if (pInputStatus)
{
}
if (pcbProcessed)
{
}
}
else
}
LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n",
return vrc;
}
{
/* pvData is optional. */
/* cbData is optional. */
/* pcbRead is optional. */
LogFlowThisFunc(("cEventTypes=%zu, pEvent=%p, uHandle=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu, pcbRead=%p\n",
int vrc;
do
{
if (RT_SUCCESS(vrc))
{
{
&& uHandleEvent == uHandle)
{
if (pvData)
{
if (cbRead)
{
{
/* Copy data from event into our buffer. */
}
else
LogFlowThisFunc(("Read %zu bytes (uHandle=%RU32), rc=%Rrc\n",
}
}
if ( RT_SUCCESS(vrc)
&& pcbRead)
{
}
break;
}
}
else
}
} while (vrc == VINF_SUCCESS);
if ( vrc != VINF_SUCCESS
&& pcbRead)
{
*pcbRead = 0;
}
return vrc;
}
{
/* pProcessStatus is optional. */
/* pGuestRc is optional. */
if (RT_SUCCESS(vrc))
{
if (pProcessStatus)
LogFlowThisFunc(("procStatus=%RU32, resultDetail=%RI32 (rc=%Rrc)\n",
if (RT_FAILURE((int)lGuestRc))
if (pGuestRc)
}
return vrc;
}
/* static */
{
bool fImplies;
switch (waitResult)
{
case ProcessWaitResult_Start:
break;
|| procStatus == ProcessStatus_Down
|| procStatus == ProcessStatus_Error);
break;
default:
fImplies = false;
break;
}
return fImplies;
}
{
LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, pGuestRc=%p\n",
/* All is optional. There can be 0 byte writes. */
{
if (puWritten)
*puWritten = 0;
if (pGuestRc)
*pGuestRc = VINF_SUCCESS;
return VINF_SUCCESS; /* Not available for writing (anymore). */
}
int vrc;
try
{
/*
* On Guest Additions < 4.3 there is no guarantee that the process status
* change arrives *after* the input event, e.g. if this was the last input
* block being written and the process will report status "terminate".
* So just skip checking for process status change and only wait for the
* input event.
*/
}
{
}
if (RT_FAILURE(vrc))
return vrc;
int i = 0;
uint32_t cbProcessed = 0;
if (RT_SUCCESS(vrc))
{
&inputStatus, &cbProcessed);
if (RT_SUCCESS(vrc))
{
/** @todo Set guestRc. */
if (puWritten)
*puWritten = cbProcessed;
}
/** @todo Error handling. */
}
LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n",
cbProcessed, vrc));
return vrc;
}
// implementation of public methods
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData))
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
if (aToRead == 0)
AutoCaller autoCaller(this);
if (RT_SUCCESS(vrc))
{
}
else
{
switch (vrc)
{
case VERR_GSTCTL_GUEST_ERROR:
break;
default:
tr("Reading from process \"%s\" (PID %RU32) failed: %Rrc"),
break;
}
}
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
int guestRc;
&guestRc);
if (RT_FAILURE(vrc))
{
switch (vrc)
{
case VERR_GSTCTL_GUEST_ERROR:
break;
case VERR_NOT_SUPPORTED:
tr("Terminating process \"%s\" (PID %RU32) not supported by installed Guest Additions"),
break;
default:
tr("Terminating process \"%s\" (PID %RU32) failed: %Rrc"),
break;
}
}
/* Remove the process from our internal session list. Only an API client
* now may hold references to it. */
mSession->processRemoveFromList(this);
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(vrc))
{
*aReason = waitResult;
}
else
{
switch (vrc)
{
case VERR_GSTCTL_GUEST_ERROR:
break;
case VERR_TIMEOUT:
break;
default:
tr("Waiting for process \"%s\" (PID %RU32) failed: %Rrc"),
break;
}
}
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!
*/
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
if (RT_FAILURE(vrc))
{
switch (vrc)
{
case VERR_GSTCTL_GUEST_ERROR:
break;
default:
tr("Writing to process \"%s\" (PID %RU32) failed: %Rrc"),
break;
}
}
return hr;
#endif /* VBOX_WITH_GUEST_CONTROL */
}
{
#ifndef VBOX_WITH_GUEST_CONTROL
#else
AutoCaller autoCaller(this);
/*
* Note: Do not hold any locks here while writing!
*/
#endif /* VBOX_WITH_GUEST_CONTROL */
}
///////////////////////////////////////////////////////////////////////////////
GuestProcessTool::GuestProcessTool(void)
{
}
GuestProcessTool::~GuestProcessTool(void)
{
}
{
LogFlowThisFunc(("pGuestSession=%p, szCmd=%s, fAsync=%RTbool\n",
/* Make sure the process is hidden. */
if (RT_SUCCESS(vrc))
pGuestRc);
if ( RT_SUCCESS(vrc)
&& !fAsync
&& ( pGuestRc
&& RT_FAILURE(*pGuestRc)
)
)
{
}
return vrc;
}
{
if (uHandle == OUTPUT_HANDLE_ID_STDOUT)
else if (uHandle == OUTPUT_HANDLE_ID_STDERR)
if (!pStream)
return VERR_INVALID_PARAMETER;
int vrc;
do
{
/* Try parsing the data to see if the current block is complete. */
break;
} while (RT_SUCCESS(vrc));
LogFlowThisFunc(("rc=%Rrc, %RU64 pairs\n",
return vrc;
}
bool GuestProcessTool::IsRunning(void)
{
if ( procStatus != ProcessStatus_Started
{
return false;
}
return true;
}
/* static */
const GuestProcessStartupInfo &startupInfo,
int *pGuestRc)
{
pGuestRc);
}
/* static */
const GuestProcessStartupInfo &startupInfo,
int *pGuestRc)
{
if (RT_SUCCESS(vrc))
{
while (cStrmOutObjects--)
{
try
{
if (pStrmOutObjects)
}
{
}
}
}
if (RT_SUCCESS(vrc))
{
/* Make sure the process runs until completion. */
if (pGuestRc)
}
else if (vrc == VERR_GSTCTL_GUEST_ERROR)
{
if (pGuestRc)
}
return vrc;
}
{
/* pExitCode is optional. */
int vrc;
if (!IsRunning())
{
if (pExitCode)
/** @todo Special guest control rc needed! */
}
else
return vrc;
}
{
}
{
LogFlowThisFunc(("fFlags=0x%x, pStreamBlock=%p, pGuestRc=%p\n",
/* Can we parse the next block without waiting? */
int vrc;
{
if (RT_SUCCESS(vrc))
return vrc;
/* else do the the waiting below. */
}
/* Do the waiting. */
/** @todo Decrease timeout while running. */
int guestRc;
bool fDone = false;
bool fHandleStdOut = false;
bool fHandleStdErr = false;
/**
* Updates the elapsed time and checks if a
* timeout happened, then breaking out of the loop.
*/
#define UPDATE_AND_CHECK_ELAPSED_TIME() \
if ( uTimeoutMS != RT_INDEFINITE_WAIT \
&& u64ElapsedMS >= uTimeoutMS) \
{ \
vrc = VERR_TIMEOUT; \
break; \
}
/**
* Returns the remaining time (in ms).
*/
#define GET_REMAINING_TIME \
do
{
if (RT_FAILURE(vrc))
break;
switch (waitRes)
{
case ProcessWaitResult_StdIn:
break;
case ProcessWaitResult_StdOut:
fHandleStdOut = true;
break;
case ProcessWaitResult_StdErr:
fHandleStdErr = true;
break;
fHandleStdOut = true;
fHandleStdErr = true;
/* Since waiting for stdout / stderr is not supported by the guest,
* wait a bit to not hog the CPU too much when polling for data. */
break;
case ProcessWaitResult_Error:
break;
fDone = true;
break;
vrc = VERR_TIMEOUT;
break;
case ProcessWaitResult_Start:
case ProcessWaitResult_Status:
/* Not used here, just skip. */
break;
default:
break;
}
if (RT_FAILURE(vrc))
break;
if (fHandleStdOut)
{
cbRead = 0;
if ( RT_FAILURE(vrc)
break;
if (cbRead)
{
if ( RT_SUCCESS(vrc)
{
/* When successful, break out of the loop because we're done
* with reading the first stream block. */
if (RT_SUCCESS(vrc))
fDone = true;
}
}
fHandleStdOut = false;
}
if (fHandleStdErr)
{
cbRead = 0;
if ( RT_FAILURE(vrc)
break;
if (cbRead)
{
}
fHandleStdErr = false;
}
LogFlowThisFunc(("Loop ended with rc=%Rrc, guestRc=%Rrc, waitRes=%ld\n",
if (pGuestRc)
return vrc;
}
{
int rc = VINF_SUCCESS;
{
}
else
rc = VERR_NOT_FOUND;
return rc;
}