summaryrefslogtreecommitdiff
path: root/src/VBox/Main/src-client/GuestProcessImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-client/GuestProcessImpl.cpp')
-rw-r--r--src/VBox/Main/src-client/GuestProcessImpl.cpp2149
1 files changed, 1215 insertions, 934 deletions
diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp
index feffba96..4ccf1e44 100644
--- a/src/VBox/Main/src-client/GuestProcessImpl.cpp
+++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp
@@ -1,11 +1,10 @@
-
/* $Id: GuestProcessImpl.cpp $ */
/** @file
- * VirtualBox Main - XXX.
+ * VirtualBox Main - Guest process handling.
*/
/*
- * Copyright (C) 2012 Oracle Corporation
+ * 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;
@@ -32,16 +31,20 @@
#include "GuestSessionImpl.h"
#include "GuestCtrlImplPrivate.h"
#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
#include "Global.h"
#include "AutoCaller.h"
-#include "VMMDev.h"
+#include "VBoxEvents.h"
#include <memory> /* For auto_ptr. */
#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
#include <iprt/getopt.h>
-#include <VBox/VMMDev.h>
+
+#include <VBox/com/listeners.h>
+
#include <VBox/com/array.h>
#ifdef LOG_GROUP
@@ -79,6 +82,62 @@ public:
: GuestProcessTask(pProcess) { }
};
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestProcessListener
+{
+public:
+
+ GuestProcessListener(void)
+ {
+ }
+
+ HRESULT init(GuestProcess *pProcess)
+ {
+ AssertPtrReturn(pProcess, E_POINTER);
+ mProcess = pProcess;
+ return S_OK;
+ }
+
+ void uninit(void)
+ {
+ mProcess = NULL;
+ }
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnGuestProcessStateChanged:
+ case VBoxEventType_OnGuestProcessInputNotify:
+ case VBoxEventType_OnGuestProcessOutput:
+ {
+ AssertPtrReturn(mProcess, E_POINTER);
+ int rc2 = mProcess->signalWaitEvent(aType, aEvent);
+#ifdef DEBUG
+ LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in rc=%Rrc\n",
+ aType, &mProcess, rc2));
+#endif
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled event %RU32\n", aType));
+ break;
+ }
+
+ return S_OK;
+ }
+
+private:
+
+ GuestProcess *mProcess;
+};
+typedef ListenerImpl<GuestProcessListener, GuestProcess*> GuestProcessListenerImpl;
+
+VBOX_LISTENER_DECLARE(GuestProcessListenerImpl)
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
@@ -88,19 +147,7 @@ DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
HRESULT GuestProcess::FinalConstruct(void)
{
LogFlowThisFuncEnter();
-
- mData.mExitCode = 0;
- mData.mNextContextID = 0;
- mData.mPID = 0;
- mData.mProcessID = 0;
- mData.mRC = VINF_SUCCESS;
- mData.mStatus = ProcessStatus_Undefined;
-
- mData.mWaitCount = 0;
- mData.mWaitEvent = NULL;
-
- HRESULT hr = BaseFinalConstruct();
- return hr;
+ return BaseFinalConstruct();
}
void GuestProcess::FinalRelease(void)
@@ -114,72 +161,124 @@ void GuestProcess::FinalRelease(void)
// public initializer/uninitializer for internal purposes only
/////////////////////////////////////////////////////////////////////////////
-int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aProcessID, const GuestProcessStartupInfo &aProcInfo)
+int GuestProcess::init(Console *aConsole, GuestSession *aSession,
+ ULONG aProcessID, const GuestProcessStartupInfo &aProcInfo)
{
LogFlowThisFunc(("aConsole=%p, aSession=%p, aProcessID=%RU32\n",
aConsole, aSession, aProcessID));
+ AssertPtrReturn(aConsole, VERR_INVALID_POINTER);
AssertPtrReturn(aSession, VERR_INVALID_POINTER);
/* Enclose the state transition NotReady->InInit->Ready. */
AutoInitSpan autoInitSpan(this);
AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
- mData.mConsole = aConsole;
- mData.mParent = aSession;
- mData.mProcessID = aProcessID;
- mData.mProcess = aProcInfo;
- /* Everything else will be set by the actual starting routine. */
-
- /* Confirm a successful initialization when it's the case. */
+#ifndef VBOX_WITH_GUEST_CONTROL
autoInitSpan.setSucceeded();
-
return VINF_SUCCESS;
+#else
+ HRESULT hr;
+
+ int vrc = bindToSession(aConsole, aSession, aProcessID /* Object ID */);
+ if (RT_SUCCESS(vrc))
+ {
+ hr = unconst(mEventSource).createObject();
+ if (FAILED(hr))
+ vrc = VERR_NO_MEMORY;
+ else
+ {
+ hr = mEventSource->init();
+ if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ try
+ {
+ GuestProcessListener *pListener = new GuestProcessListener();
+ ComObjPtr<GuestProcessListenerImpl> thisListener;
+ hr = thisListener.createObject();
+ if (SUCCEEDED(hr))
+ hr = thisListener->init(pListener, this);
+
+ if (SUCCEEDED(hr))
+ {
+ com::SafeArray <VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+ hr = mEventSource->RegisterListener(thisListener,
+ ComSafeArrayAsInParam(eventTypes),
+ TRUE /* Active listener */);
+ if (SUCCEEDED(hr))
+ {
+ vrc = baseInit();
+ if (RT_SUCCESS(vrc))
+ {
+ mLocalListener = thisListener;
+ }
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ catch(std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ mData.mProcess = aProcInfo;
+ mData.mExitCode = 0;
+ mData.mPID = 0;
+ mData.mLastError = VINF_SUCCESS;
+ mData.mStatus = ProcessStatus_Undefined;
+ /* Everything else will be set by the actual starting routine. */
+
+ /* Confirm a successful initialization when it's the case. */
+ autoInitSpan.setSucceeded();
+
+ return vrc;
+ }
+
+ autoInitSpan.setFailed();
+ return vrc;
+#endif
}
/**
* Uninitializes the instance.
- * Called from FinalRelease().
+ * Called from FinalRelease() or IGuestSession::uninit().
*/
void GuestProcess::uninit(void)
{
- LogFlowThisFunc(("mCmd=%s, PID=%RU32\n",
- mData.mProcess.mCommand.c_str(), mData.mPID));
-
/* Enclose the state transition Ready->InUninit->NotReady. */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
- int vrc = VINF_SUCCESS;
-
#ifdef VBOX_WITH_GUEST_CONTROL
- /*
- * Cancel all callbacks + waiters.
- * Note: Deleting them is the job of the caller!
- */
- for (GuestCtrlCallbacks::iterator itCallbacks = mData.mCallbacks.begin();
- itCallbacks != mData.mCallbacks.end(); ++itCallbacks)
- {
- GuestCtrlCallback *pCallback = itCallbacks->second;
- AssertPtr(pCallback);
- int rc2 = pCallback->Cancel();
- if (RT_SUCCESS(vrc))
- vrc = rc2;
- }
- mData.mCallbacks.clear();
+ LogFlowThisFunc(("mCmd=%s, PID=%RU32\n",
+ mData.mProcess.mCommand.c_str(), mData.mPID));
- if (mData.mWaitEvent)
- {
- int rc2 = mData.mWaitEvent->Cancel();
- if (RT_SUCCESS(vrc))
- vrc = rc2;
- }
+ /* Terminate process if not already done yet. */
+ int guestRc = VINF_SUCCESS;
+ int vrc = terminateProcess(30 * 1000, &guestRc); /** @todo Make timeouts configurable. */
+ /* Note: Don't return here yet; first uninit all other stuff in
+ * case of failure. */
- mData.mStatus = ProcessStatus_Down; /** @todo Correct? */
-#endif
+ baseUninit();
- LogFlowFuncLeaveRC(vrc);
+ LogFlowThisFunc(("Returning rc=%Rrc, guestRc=%Rrc\n",
+ vrc, guestRc));
+#endif
}
// implementation of public getters/setters for attributes
@@ -241,6 +340,26 @@ STDMETHODIMP GuestProcess::COMGETTER(Environment)(ComSafeArrayOut(BSTR, aEnviron
#endif /* VBOX_WITH_GUEST_CONTROL */
}
+STDMETHODIMP GuestProcess::COMGETTER(EventSource)(IEventSource ** aEventSource)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else
+ LogFlowThisFuncEnter();
+
+ CheckComArgOutPointerValid(aEventSource);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource);
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
STDMETHODIMP GuestProcess::COMGETTER(ExecutablePath)(BSTR *aExecutablePath)
{
#ifndef VBOX_WITH_GUEST_CONTROL
@@ -342,143 +461,45 @@ STDMETHODIMP GuestProcess::COMGETTER(Status)(ProcessStatus_T *aStatus)
// private methods
/////////////////////////////////////////////////////////////////////////////
-inline int GuestProcess::callbackAdd(GuestCtrlCallback *pCallback, uint32_t *puContextID)
-{
- const ComObjPtr<GuestSession> pSession(mData.mParent);
- Assert(!pSession.isNull());
- ULONG uSessionID = 0;
- HRESULT hr = pSession->COMGETTER(Id)(&uSessionID);
- ComAssertComRC(hr);
-
- /* Create a new context ID and assign it. */
- int vrc = VERR_NOT_FOUND;
-
- ULONG uCount = mData.mNextContextID++;
- ULONG uNewContextID = 0;
- ULONG uTries = 0;
- for (;;)
- {
- if (uCount == VBOX_GUESTCTRL_MAX_CONTEXTS)
- uCount = 0;
-
- /* Create a new context ID ... */
- uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID,
- mData.mProcessID, uCount);
-
- /* Is the context ID already used? Try next ID ... */
- if (!callbackExists(uCount))
- {
- /* Callback with context ID was not found. This means
- * we can use this context ID for our new callback we want
- * to add below. */
- vrc = VINF_SUCCESS;
- break;
- }
-
- uCount++;
- if (++uTries == UINT32_MAX)
- break; /* Don't try too hard. */
- }
-
- if (RT_SUCCESS(vrc))
- {
- /* 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. */
- mData.mCallbacks[uCount] = pCallback;
- Assert(mData.mCallbacks.size());
-
- /* Report back new context ID. */
- if (puContextID)
- *puContextID = uNewContextID;
-
- LogFlowThisFunc(("Added new callback (Session: %RU32, Process: %RU32, Count=%RU32) CID=%RU32\n",
- uSessionID, mData.mProcessID, uCount, uNewContextID));
- }
-
- return vrc;
-}
-
-int GuestProcess::callbackDispatcher(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData)
+int GuestProcess::callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
#ifdef DEBUG
- LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uFunction=%RU32, pvData=%p, cbData=%RU32\n",
- mData.mPID, uContextID, uFunction, pvData, cbData));
+ LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
+ mData.mPID, pCbCtx->uContextID, pCbCtx->uFunction, pSvcCb));
#endif
- AssertPtrReturn(pvData, VERR_INVALID_POINTER);
- AssertReturn(cbData, VERR_INVALID_PARAMETER);
-
- AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
-
int vrc;
-
- /* Get the optional callback associated to this context ID.
- * The callback may not be around anymore if just kept locally by the caller when
- * doing the actual HGCM sending stuff. */
- GuestCtrlCallback *pCallback = NULL;
- GuestCtrlCallbacks::const_iterator it
- = mData.mCallbacks.find(VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID));
- if (it != mData.mCallbacks.end())
- {
- pCallback = it->second;
- AssertPtr(pCallback);
-#ifdef DEBUG
- LogFlowThisFunc(("pCallback=%p, CID=%RU32, Count=%RU32\n",
- pCallback, uContextID, VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID)));
-#endif
- }
-
- switch (uFunction)
+ switch (pCbCtx->uFunction)
{
case GUEST_DISCONNECTED:
{
- PCALLBACKDATACLIENTDISCONNECTED pCallbackData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvData);
- AssertPtr(pCallbackData);
- AssertReturn(sizeof(CALLBACKDATACLIENTDISCONNECTED) == cbData, VERR_INVALID_PARAMETER);
- AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
-
- vrc = onGuestDisconnected(pCallback, pCallbackData); /* Affects all callbacks. */
+ vrc = onGuestDisconnected(pCbCtx, pSvcCb);
break;
}
- case GUEST_EXEC_SEND_STATUS:
+ case GUEST_EXEC_STATUS:
{
- PCALLBACKDATAEXECSTATUS pCallbackData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvData);
- AssertPtr(pCallbackData);
- AssertReturn(sizeof(CALLBACKDATAEXECSTATUS) == cbData, VERR_INVALID_PARAMETER);
- AssertReturn(CALLBACKDATAMAGIC_EXEC_STATUS == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
-
- vrc = onProcessStatusChange(pCallback, pCallbackData);
+ vrc = onProcessStatusChange(pCbCtx, pSvcCb);
break;
}
- case GUEST_EXEC_SEND_OUTPUT:
+ case GUEST_EXEC_OUTPUT:
{
- PCALLBACKDATAEXECOUT pCallbackData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvData);
- AssertPtr(pCallbackData);
- AssertReturn(sizeof(CALLBACKDATAEXECOUT) == cbData, VERR_INVALID_PARAMETER);
- AssertReturn(CALLBACKDATAMAGIC_EXEC_OUT == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
-
- vrc = onProcessOutput(pCallback, pCallbackData);
+ vrc = onProcessOutput(pCbCtx, pSvcCb);
break;
}
- case GUEST_EXEC_SEND_INPUT_STATUS:
+ case GUEST_EXEC_INPUT_STATUS:
{
- PCALLBACKDATAEXECINSTATUS pCallbackData = reinterpret_cast<PCALLBACKDATAEXECINSTATUS>(pvData);
- AssertPtr(pCallbackData);
- AssertReturn(sizeof(CALLBACKDATAEXECINSTATUS) == cbData, VERR_INVALID_PARAMETER);
- AssertReturn(CALLBACKDATAMAGIC_EXEC_IN_STATUS == pCallbackData->hdr.u32Magic, VERR_INVALID_PARAMETER);
-
- vrc = onProcessInputStatus(pCallback, pCallbackData);
+ vrc = onProcessInputStatus(pCbCtx, pSvcCb);
break;
}
default:
/* Silently ignore not implemented functions. */
- vrc = VERR_NOT_IMPLEMENTED;
+ vrc = VERR_NOT_SUPPORTED;
break;
}
@@ -488,67 +509,38 @@ int GuestProcess::callbackDispatcher(uint32_t uContextID, uint32_t uFunction, vo
return vrc;
}
-inline bool GuestProcess::callbackExists(uint32_t uContextID)
-{
- GuestCtrlCallbacks::const_iterator it =
- mData.mCallbacks.find(VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID));
- return (it == mData.mCallbacks.end()) ? false : true;
-}
-
-inline int GuestProcess::callbackRemove(uint32_t uContextID)
-{
- LogFlowThisFunc(("Removing callback (Session: %RU32, Process: %RU32, Count=%RU32) CID=%RU32\n",
- VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID),
- VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(uContextID),
- VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID),
- uContextID));
-
- GuestCtrlCallbacks::iterator it =
- mData.mCallbacks.find(VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID));
- if (it != mData.mCallbacks.end())
- {
- delete it->second;
- mData.mCallbacks.erase(it);
-
- return VINF_SUCCESS;
- }
-
- return VERR_NOT_FOUND;
-}
-
/**
* Checks if the current assigned PID matches another PID (from a callback).
*
* In protocol v1 we don't have the possibility to terminate/kill
- * processes so it can happen that a formerly start process A
+ * 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.
*
+ * Note: This also can happen when restoring from a saved state which
+ * had a guest process running.
+ *
* @return IPRT status code.
* @param uPID PID to check.
*/
inline int GuestProcess::checkPID(uint32_t uPID)
{
+ int rc = VINF_SUCCESS;
+
/* Was there a PID assigned yet? */
if (mData.mPID)
{
- /*
-
- */
- if (mData.mParent->getProtocolVersion() < 2)
+ if (RT_UNLIKELY(mData.mPID != uPID))
{
- /* Simply ignore the stale requests. */
- return (mData.mPID == uPID)
- ? VINF_SUCCESS : VERR_NOT_FOUND;
+ LogFlowFunc(("Stale guest process (PID=%RU32) sent data to a newly started process (pProcesS=%p, PID=%RU32, status=%RU32)\n",
+ uPID, this, mData.mPID, mData.mStatus));
+ rc = VERR_NOT_FOUND;
}
- /* This should never happen! */
- AssertReleaseMsg(mData.mPID == uPID, ("Unterminated guest process (PID %RU32) sent data to a newly started process (PID %RU32)\n",
- uPID, mData.mPID));
}
- return VINF_SUCCESS;
+ return rc;
}
/* static */
@@ -600,7 +592,7 @@ Utf8Str GuestProcess::guestErrorToString(int guestRc)
break;
case VERR_MAX_PROCS_REACHED:
- strError += Utf8StrFmt(tr("Maximum number of parallel guest processes has been reached"));
+ strError += Utf8StrFmt(tr("Maximum number of concurrent guest processes has been reached"));
break;
case VERR_NOT_EQUAL: /** @todo Imprecise to the user; can mean anything and all. */
@@ -612,7 +604,7 @@ Utf8Str GuestProcess::guestErrorToString(int guestRc)
break;
default:
- strError += Utf8StrFmt(tr("%Rrc"), guestRc);
+ strError += Utf8StrFmt("%Rrc", guestRc);
break;
}
@@ -626,226 +618,205 @@ inline bool GuestProcess::isAlive(void)
|| mData.mStatus == ProcessStatus_Terminating);
}
-bool GuestProcess::isReady(void)
+inline bool GuestProcess::hasEnded(void)
{
- AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
-
- if (mData.mStatus == ProcessStatus_Started)
- {
- Assert(mData.mPID); /* PID must not be 0. */
- return true;
- }
-
- return false;
+ return ( mData.mStatus == ProcessStatus_TerminatedNormally
+ || mData.mStatus == ProcessStatus_TerminatedSignal
+ || mData.mStatus == ProcessStatus_TerminatedAbnormally
+ || mData.mStatus == ProcessStatus_TimedOutKilled
+ || mData.mStatus == ProcessStatus_TimedOutAbnormally
+ || mData.mStatus == ProcessStatus_Down
+ || mData.mStatus == ProcessStatus_Error);
}
-int GuestProcess::onGuestDisconnected(GuestCtrlCallback *pCallback, PCALLBACKDATACLIENTDISCONNECTED pData)
+int GuestProcess::onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
- /* pCallback is optional. */
- AssertPtrReturn(pData, VERR_INVALID_POINTER);
-
- LogFlowThisFunc(("uPID=%RU32, pCallback=%p, pData=%p\n", mData.mPID, pCallback, pData));
-
- mData.mStatus = ProcessStatus_Down;
-
- /* First, signal callback in every case. */
- if (pCallback)
- pCallback->Signal();
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
- /* Do we need to report a termination? */
- ProcessWaitResult_T waitRes;
- if (mData.mProcess.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses)
- waitRes = ProcessWaitResult_Status; /* No, just report a status. */
- else
- waitRes = ProcessWaitResult_Terminate;
-
- /* Signal in any case. */
- int vrc = signalWaiters(waitRes);
- AssertRC(vrc);
+ int vrc = setProcessStatus(ProcessStatus_Down, VINF_SUCCESS);
LogFlowFuncLeaveRC(vrc);
return vrc;
}
-int GuestProcess::onProcessInputStatus(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECINSTATUS pData)
+int GuestProcess::onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
/* pCallback is optional. */
- AssertPtrReturn(pData, VERR_INVALID_POINTER);
- LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32, cbProcessed=%RU32, pCallback=%p, pData=%p\n",
- mData.mPID, pData->u32Status, pData->u32Flags, pData->cbProcessed, pCallback, pData));
-
- int vrc = checkPID(pData->u32PID);
- if (RT_FAILURE(vrc))
- return vrc;
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
- /* First, signal callback in every case (if available). */
- if (pCallback)
+ CALLBACKDATA_PROC_INPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[4].getUInt32(&dataCb.uProcessed);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed));
+
+ vrc = checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
{
- vrc = pCallback->SetData(pData, sizeof(CALLBACKDATAEXECINSTATUS));
+ ProcessInputStatus_T inputStatus = ProcessInputStatus_Undefined;
+ switch (dataCb.uStatus)
+ {
+ case INPUT_STS_WRITTEN:
+ inputStatus = ProcessInputStatus_Written;
+ break;
+ case INPUT_STS_ERROR:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_TERMINATED:
+ inputStatus = ProcessInputStatus_Broken;
+ break;
+ case INPUT_STS_OVERFLOW:
+ inputStatus = ProcessInputStatus_Overflow;
+ break;
+ case INPUT_STS_UNDEFINED:
+ /* Fall through is intentional. */
+ default:
+ AssertMsg(!dataCb.uProcessed, ("Processed data is not 0 in undefined input state\n"));
+ break;
+ }
- int rc2 = pCallback->Signal();
- if (RT_SUCCESS(vrc))
- vrc = rc2;
- }
+ if (inputStatus != ProcessInputStatus_Undefined)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Then do the WaitFor signalling stuff. */
- uint32_t uWaitFlags = mData.mWaitEvent
- ? mData.mWaitEvent->GetWaitFlags() : 0;
- if (uWaitFlags & ProcessWaitForFlag_StdIn)
- {
- int rc2 = signalWaiters(ProcessWaitResult_StdIn);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ fireGuestProcessInputNotifyEvent(mEventSource, mSession, this,
+ uPID, 0 /* StdIn */, dataCb.uProcessed, inputStatus);
+ }
}
LogFlowFuncLeaveRC(vrc);
return vrc;
}
-int GuestProcess::onProcessNotifyIO(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECSTATUS pData)
+int GuestProcess::onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
- /* pCallback is optional. */
- AssertPtrReturn(pData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
- return 0;
+ return VERR_NOT_IMPLEMENTED;
}
-int GuestProcess::onProcessStatusChange(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECSTATUS pData)
+int GuestProcess::onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
- /* pCallback is optional. */
- AssertPtrReturn(pData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
- LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32, pCallback=%p, pData=%p\n",
- pData->u32PID, pData->u32Status, pData->u32Flags, pCallback, pData));
-
- int vrc = checkPID(pData->u32PID);
- if (RT_FAILURE(vrc))
- return vrc;
-
- ProcessStatus_T procStatus = ProcessStatus_Undefined;
- int procRc = VINF_SUCCESS;
-
- bool fSignalWaiters = false;
- ProcessWaitResult_T waitRes;
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
- uint32_t uWaitFlags = mData.mWaitEvent
- ? mData.mWaitEvent->GetWaitFlags() : 0;
- switch (pData->u32Status)
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[4].getPointer(&dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags));
+
+ vrc = checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
{
- case PROC_STS_STARTED:
- {
- fSignalWaiters = (uWaitFlags & ProcessWaitForFlag_Start);
- /* If the caller only wants to wait until the process has been started,
- * notify in any case. */
- if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
- fSignalWaiters = true;
- waitRes = ProcessWaitResult_Start;
-
- procStatus = ProcessStatus_Started;
- mData.mPID = pData->u32PID; /* Set the process PID. */
- break;
- }
-
- case PROC_STS_TEN:
- {
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Terminate;
-
- procStatus = ProcessStatus_TerminatedNormally;
- mData.mExitCode = pData->u32Flags; /* Contains the exit code. */
- break;
- }
-
- case PROC_STS_TES:
- {
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Terminate;
-
- procStatus = ProcessStatus_TerminatedSignal;
- mData.mExitCode = pData->u32Flags; /* Contains the signal. */
- break;
- }
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ int procRc = VINF_SUCCESS;
- case PROC_STS_TEA:
+ switch (dataCb.uStatus)
{
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Terminate;
+ case PROC_STS_STARTED:
+ {
+ procStatus = ProcessStatus_Started;
- procStatus = ProcessStatus_TerminatedAbnormally;
- break;
- }
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mPID = dataCb.uPID; /* Set the process PID. */
+ break;
+ }
- case PROC_STS_TOK:
- {
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Timeout;
+ case PROC_STS_TEN:
+ {
+ procStatus = ProcessStatus_TerminatedNormally;
- procStatus = ProcessStatus_TimedOutKilled;
- break;
- }
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the exit code. */
+ break;
+ }
- case PROC_STS_TOA:
- {
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Timeout;
+ case PROC_STS_TES:
+ {
+ procStatus = ProcessStatus_TerminatedSignal;
- procStatus = ProcessStatus_TimedOutAbnormally;
- break;
- }
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the signal. */
+ break;
+ }
- case PROC_STS_DWN:
- {
- fSignalWaiters = true; /* Signal in any case. */
- /* Do we need to report termination? */
- if (mData.mProcess.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses)
- waitRes = ProcessWaitResult_Status;
- else
- waitRes = ProcessWaitResult_Terminate;
+ case PROC_STS_TEA:
+ {
+ procStatus = ProcessStatus_TerminatedAbnormally;
+ break;
+ }
- procStatus = ProcessStatus_Down;
- break;
- }
+ case PROC_STS_TOK:
+ {
+ procStatus = ProcessStatus_TimedOutKilled;
+ break;
+ }
- case PROC_STS_ERROR:
- {
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Error;
+ case PROC_STS_TOA:
+ {
+ procStatus = ProcessStatus_TimedOutAbnormally;
+ break;
+ }
- procRc = pData->u32Flags; /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
- procStatus = ProcessStatus_Error;
- break;
- }
+ case PROC_STS_DWN:
+ {
+ procStatus = ProcessStatus_Down;
+ break;
+ }
- case PROC_STS_UNDEFINED:
- default:
- {
- /* Silently skip this request. */
- fSignalWaiters = true; /* Signal in any case. */
- waitRes = ProcessWaitResult_Status;
+ case PROC_STS_ERROR:
+ {
+ procRc = dataCb.uFlags; /* mFlags contains the IPRT error sent from the guest. */
+ procStatus = ProcessStatus_Error;
+ break;
+ }
- procStatus = ProcessStatus_Undefined;
- break;
+ case PROC_STS_UNDEFINED:
+ default:
+ {
+ /* Silently skip this request. */
+ procStatus = ProcessStatus_Undefined;
+ break;
+ }
}
- }
-
- LogFlowThisFunc(("Got rc=%Rrc, waitRes=%d, procSts=%ld, procRc=%Rrc, fSignalWaiters=%RTbool\n",
- vrc, waitRes, procStatus, procRc, fSignalWaiters));
- /* Set the process status. */
- int rc2 = setProcessStatus(procStatus, procRc);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ LogFlowThisFunc(("Got rc=%Rrc, procSts=%RU32, procRc=%Rrc\n",
+ vrc, procStatus, procRc));
- /*
- * Now do the signalling stuff.
- */
- if (pCallback)
- vrc = pCallback->Signal(procRc);
-
- if (fSignalWaiters)
- {
- rc2 = signalWaiters(waitRes, procRc);
+ /* Set the process status. */
+ int rc2 = setProcessStatus(procStatus, procRc);
if (RT_SUCCESS(vrc))
vrc = rc2;
}
@@ -854,58 +825,64 @@ int GuestProcess::onProcessStatusChange(GuestCtrlCallback *pCallback, PCALLBACKD
return vrc;
}
-int GuestProcess::onProcessOutput(GuestCtrlCallback *pCallback, PCALLBACKDATAEXECOUT pData)
+int GuestProcess::onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
{
- /* pCallback is optional. */
- AssertPtrReturn(pData, VERR_INVALID_POINTER);
-
- LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, pCallback=%p, pData=%p\n",
- mData.mPID, pData->u32HandleId, pData->u32Flags, pData->pvData, pData->cbData, pCallback, pData));
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
- int vrc = checkPID(pData->u32PID);
- if (RT_FAILURE(vrc))
- return vrc;
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
- /* First, signal callback in every case (if available). */
- if (pCallback)
+ CALLBACKDATA_PROC_OUTPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uHandle);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = pSvcCbData->mpaParms[4].getPointer(&dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n",
+ dataCb.uPID, dataCb.uHandle, dataCb.uFlags, dataCb.pvData, dataCb.cbData));
+
+ vrc = checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
{
- vrc = pCallback->SetData(pData, sizeof(CALLBACKDATAEXECOUT));
+ com::SafeArray<BYTE> data((size_t)dataCb.cbData);
+ if (dataCb.cbData)
+ data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData);
- int rc2 = pCallback->Signal();
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ fireGuestProcessOutputEvent(mEventSource, mSession, this,
+ mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data));
}
- /* Then do the WaitFor signalling stuff. */
- BOOL fSignal = FALSE;
- uint32_t uWaitFlags = mData.mWaitEvent
- ? mData.mWaitEvent->GetWaitFlags() : 0;
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
- if ( (uWaitFlags & ProcessWaitForFlag_StdOut)
- || (uWaitFlags & ProcessWaitForFlag_StdErr))
- {
- fSignal = TRUE;
- }
- else if ( (uWaitFlags & ProcessWaitForFlag_StdOut)
- && (pData->u32HandleId == OUTPUT_HANDLE_ID_STDOUT))
- {
- fSignal = TRUE;
- }
- else if ( (uWaitFlags & ProcessWaitForFlag_StdErr)
- && (pData->u32HandleId == OUTPUT_HANDLE_ID_STDERR))
- {
- fSignal = TRUE;
- }
+/**
+ * Called by IGuestSession right before this process gets
+ * removed from the public process list.
+ */
+int GuestProcess::onRemove(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
- if (fSignal)
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
{
- int rc2;
- if (pData->u32HandleId == OUTPUT_HANDLE_ID_STDOUT)
- rc2 = signalWaiters(ProcessWaitResult_StdOut);
- else
- rc2 = signalWaiters(ProcessWaitResult_StdErr);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
}
LogFlowFuncLeaveRC(vrc);
@@ -913,7 +890,7 @@ int GuestProcess::onProcessOutput(GuestCtrlCallback *pCallback, PCALLBACKDATAEXE
}
int GuestProcess::readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS,
- void *pvData, size_t cbData, size_t *pcbRead, int *pGuestRc)
+ void *pvData, size_t cbData, uint32_t *pcbRead, int *pGuestRc)
{
LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, pGuestRc=%p\n",
mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, pGuestRc));
@@ -924,7 +901,15 @@ int GuestProcess::readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- if (mData.mStatus != ProcessStatus_Started)
+ if ( mData.mStatus != ProcessStatus_Started
+ /* Skip reading if the process wasn't started with the appropriate
+ * flags. */
+ || ( ( uHandle == OUTPUT_HANDLE_ID_STDOUT
+ || uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED)
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut))
+ || ( uHandle == OUTPUT_HANDLE_ID_STDERR
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr))
+ )
{
if (pcbRead)
*pcbRead = 0;
@@ -933,138 +918,120 @@ int GuestProcess::readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS
return VINF_SUCCESS; /* Nothing to read anymore. */
}
- int vrc = VINF_SUCCESS;
+ int vrc;
- GuestCtrlCallback *pCallbackRead = NULL;
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
try
{
- pCallbackRead = new GuestCtrlCallback();
+ /*
+ * 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 (mSession->getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
}
- catch(std::bad_alloc &)
+ catch (std::bad_alloc)
{
vrc = VERR_NO_MEMORY;
}
- /* Create callback and add it to the map. */
- uint32_t uContextID = 0;
- if (RT_SUCCESS(vrc))
- {
- vrc = pCallbackRead->Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT);
- if (RT_SUCCESS(vrc))
- vrc = callbackAdd(pCallbackRead, &uContextID);
- }
-
- alock.release(); /* Drop the write lock again. */
+ if (RT_FAILURE(vrc))
+ return vrc;
if (RT_SUCCESS(vrc))
{
- VBOXHGCMSVCPARM paParms[5];
-
+ VBOXHGCMSVCPARM paParms[8];
int i = 0;
- paParms[i++].setUInt32(uContextID);
+ paParms[i++].setUInt32(pEvent->ContextID());
paParms[i++].setUInt32(mData.mPID);
paParms[i++].setUInt32(uHandle);
paParms[i++].setUInt32(0 /* Flags, none set yet. */);
+ alock.release(); /* Drop the write lock before sending. */
+
vrc = sendCommand(HOST_EXEC_GET_OUTPUT, i, paParms);
}
if (RT_SUCCESS(vrc))
- {
- /*
- * Let's wait for the process being started.
- * Note: Be sure not keeping a AutoRead/WriteLock here.
- */
- LogFlowThisFunc(("Waiting for callback (%RU32ms) ...\n", uTimeoutMS));
- vrc = pCallbackRead->Wait(uTimeoutMS);
- if (RT_SUCCESS(vrc)) /* Wait was successful, check for supplied information. */
- {
- int guestRc = pCallbackRead->GetResultCode();
- LogFlowThisFunc(("Callback returned rc=%Rrc, cbData=%RU32\n", guestRc, pCallbackRead->GetDataSize()));
-
- if (RT_SUCCESS(guestRc))
- {
- Assert(pCallbackRead->GetDataSize() == sizeof(CALLBACKDATAEXECOUT));
- PCALLBACKDATAEXECOUT pData = (PCALLBACKDATAEXECOUT)pCallbackRead->GetDataRaw();
- AssertPtr(pData);
-
- size_t cbRead = pData->cbData;
- if (cbRead)
- {
- Assert(cbData >= cbRead);
- memcpy(pvData, pData->pvData, cbRead);
- }
-
- LogFlowThisFunc(("cbRead=%RU32\n", cbRead));
-
- if (pcbRead)
- *pcbRead = cbRead;
- }
- else
- vrc = VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
-
- if (pGuestRc)
- *pGuestRc = guestRc;
- }
- }
-
- alock.acquire();
+ vrc = waitForOutput(pEvent, uHandle, uTimeoutMS,
+ pvData, cbData, pcbRead);
- AssertPtr(pCallbackRead);
- int rc2 = callbackRemove(uContextID);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ unregisterWaitEvent(pEvent);
LogFlowFuncLeaveRC(vrc);
return vrc;
}
-int GuestProcess::sendCommand(uint32_t uFunction,
- uint32_t uParms, PVBOXHGCMSVCPARM paParms)
+/* Does not do locking; caller is responsible for that! */
+int GuestProcess::setProcessStatus(ProcessStatus_T procStatus, int procRc)
{
LogFlowThisFuncEnter();
- ComObjPtr<Console> pConsole = mData.mConsole;
- Assert(!pConsole.isNull());
-
- /* Forward the information to the VMM device. */
- VMMDev *pVMMDev = pConsole->getVMMDev();
- AssertPtr(pVMMDev);
-
- LogFlowThisFunc(("uFunction=%RU32, uParms=%RU32\n", uFunction, uParms));
- int vrc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", uFunction, uParms, paParms);
- if (RT_FAILURE(vrc))
- {
- int rc2 = setProcessStatus(ProcessStatus_Error, vrc);
- AssertRC(rc2);
- }
-
- LogFlowFuncLeaveRC(vrc);
- return vrc;
-}
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
-/* Does not do locking; caller is responsible for that! */
-int GuestProcess::setProcessStatus(ProcessStatus_T procStatus, int procRc)
-{
- LogFlowThisFunc(("oldStatus=%ld, newStatus=%ld, procRc=%Rrc\n",
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, procRc=%Rrc\n",
mData.mStatus, procStatus, procRc));
-#ifdef DEBUG
if (procStatus == ProcessStatus_Error)
{
AssertMsg(RT_FAILURE(procRc), ("Guest rc must be an error (%Rrc)\n", procRc));
/* Do not allow overwriting an already set error. If this happens
* this means we forgot some error checking/locking somewhere. */
- AssertMsg(RT_SUCCESS(mData.mRC), ("Guest rc already set (to %Rrc)\n", mData.mRC));
+ AssertMsg(RT_SUCCESS(mData.mLastError), ("Guest rc already set (to %Rrc)\n", mData.mLastError));
}
else
AssertMsg(RT_SUCCESS(procRc), ("Guest rc must not be an error (%Rrc)\n", procRc));
-#endif
- mData.mStatus = procStatus;
- mData.mRC = procRc;
+ int rc = VINF_SUCCESS;
- return VINF_SUCCESS;
+ if (mData.mStatus != procStatus) /* Was there a process status change? */
+ {
+ mData.mStatus = procStatus;
+ mData.mLastError = procRc;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hr = errorInfo.createObject();
+ ComAssertComRC(hr);
+ if (RT_FAILURE(mData.mLastError))
+ {
+ hr = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError,
+ COM_IIDOF(IGuestProcess), getComponentName(),
+ guestErrorToString(mData.mLastError));
+ ComAssertComRC(hr);
+ }
+
+ /* Copy over necessary data before releasing lock again. */
+ uint32_t uPID = mData.mPID;
+ /** @todo Also handle mSession? */
+
+ alock.release(); /* Release lock before firing off event. */
+
+ fireGuestProcessStateChangedEvent(mEventSource, mSession, this,
+ uPID, procStatus, errorInfo);
+#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.
+ */
+ if ( mSession->getProtocolVersion() < 2
+ && hasEnded())
+ {
+ LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n"));
+ rc = cancelWaitEvents();
+ }
+#endif
+ }
+
+ return rc;
}
/* static */
@@ -1076,170 +1043,147 @@ HRESULT GuestProcess::setErrorExternal(VirtualBoxBase *pInterface, int guestRc)
return pInterface->setError(VBOX_E_IPRT_ERROR, GuestProcess::guestErrorToString(guestRc).c_str());
}
-int GuestProcess::signalWaiters(ProcessWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */)
+int GuestProcess::startProcess(uint32_t uTimeoutMS, int *pGuestRc)
{
- LogFlowThisFunc(("enmWaitResult=%d, rc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n",
- enmWaitResult, rc, mData.mWaitCount, mData.mWaitEvent));
-
- /* Note: No write locking here -- already done in the caller. */
-
- int vrc = VINF_SUCCESS;
- if (mData.mWaitEvent)
- vrc = mData.mWaitEvent->Signal(enmWaitResult, rc);
- LogFlowFuncLeaveRC(vrc);
- return vrc;
-}
-
-int GuestProcess::startProcess(int *pGuestRc)
-{
- LogFlowThisFunc(("aCmd=%s, aTimeoutMS=%RU32, fFlags=%x\n",
- mData.mProcess.mCommand.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags));
+ LogFlowThisFunc(("uTimeoutMS=%RU32, procCmd=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n",
+ uTimeoutMS, mData.mProcess.mCommand.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags,
+ mSession->getId()));
/* Wait until the caller function (if kicked off by a thread)
* has returned and continue operation. */
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- int vrc = VINF_SUCCESS;
- uint32_t uContextID = 0;
+ mData.mStatus = ProcessStatus_Starting;
- GuestCtrlCallback *pCallbackStart;
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
try
{
- pCallbackStart = new GuestCtrlCallback();
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
}
- catch(std::bad_alloc &)
+ catch (std::bad_alloc)
{
vrc = VERR_NO_MEMORY;
}
- if (RT_SUCCESS(vrc))
- {
- mData.mStatus = ProcessStatus_Starting;
+ if (RT_FAILURE(vrc))
+ return vrc;
- /* Create callback and add it to the map. */
- vrc = pCallbackStart->Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START);
- if (RT_SUCCESS(vrc))
- vrc = callbackAdd(pCallbackStart, &uContextID);
- }
+ GuestSession *pSession = mSession;
+ AssertPtr(pSession);
- if (RT_SUCCESS(vrc))
- {
- GuestSession *pSession = mData.mParent;
- AssertPtr(pSession);
+ const GuestCredentials &sessionCreds = pSession->getCredentials();
- const GuestCredentials &sessionCreds = pSession->getCredentials();
+ /* Prepare arguments. */
+ char *pszArgs = NULL;
+ size_t cArgs = mData.mProcess.mArguments.size();
+ if (cArgs >= UINT32_MAX)
+ vrc = VERR_BUFFER_OVERFLOW;
- /* Prepare arguments. */
- char *pszArgs = NULL;
- size_t cArgs = mData.mProcess.mArguments.size();
- if (cArgs >= UINT32_MAX)
- vrc = VERR_BUFFER_OVERFLOW;
+ if ( RT_SUCCESS(vrc)
+ && cArgs)
+ {
+ char **papszArgv = (char**)RTMemAlloc((cArgs + 1) * sizeof(char*));
+ AssertReturn(papszArgv, VERR_NO_MEMORY);
- if ( RT_SUCCESS(vrc)
- && cArgs)
+ for (size_t i = 0; i < cArgs && RT_SUCCESS(vrc); i++)
{
- char **papszArgv = (char**)RTMemAlloc((cArgs + 1) * sizeof(char*));
- AssertReturn(papszArgv, VERR_NO_MEMORY);
-
- for (size_t i = 0; i < cArgs && RT_SUCCESS(vrc); i++)
- {
- const char *pszCurArg = mData.mProcess.mArguments[i].c_str();
- AssertPtr(pszCurArg);
- vrc = RTStrDupEx(&papszArgv[i], pszCurArg);
- }
- papszArgv[cArgs] = NULL;
+ const char *pszCurArg = mData.mProcess.mArguments[i].c_str();
+ AssertPtr(pszCurArg);
+ vrc = RTStrDupEx(&papszArgv[i], pszCurArg);
+ }
+ papszArgv[cArgs] = NULL;
- if (RT_SUCCESS(vrc))
- vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
+ if (RT_SUCCESS(vrc))
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
- if (papszArgv)
- {
- size_t i = 0;
- while (papszArgv[i])
- RTStrFree(papszArgv[i++]);
- RTMemFree(papszArgv);
- }
+ if (papszArgv)
+ {
+ size_t i = 0;
+ while (papszArgv[i])
+ RTStrFree(papszArgv[i++]);
+ RTMemFree(papszArgv);
}
+ }
- /* Calculate arguments size (in bytes). */
- size_t cbArgs = 0;
- if (RT_SUCCESS(vrc))
- cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
+ /* Calculate arguments size (in bytes). */
+ size_t cbArgs = 0;
+ if (RT_SUCCESS(vrc))
+ cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
- /* Prepare environment. */
- void *pvEnv = NULL;
- size_t cbEnv = 0;
- if (RT_SUCCESS(vrc))
- vrc = mData.mProcess.mEnvironment.BuildEnvironmentBlock(&pvEnv, &cbEnv, NULL /* cEnv */);
+ /* Prepare environment. */
+ void *pvEnv = NULL;
+ size_t cbEnv = 0;
+ if (RT_SUCCESS(vrc))
+ vrc = mData.mProcess.mEnvironment.BuildEnvironmentBlock(&pvEnv, &cbEnv, NULL /* cEnv */);
- if (RT_SUCCESS(vrc))
+ if (RT_SUCCESS(vrc))
+ {
+ AssertPtr(mSession);
+ uint32_t uProtocol = mSession->getProtocolVersion();
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[16];
+ int i = 0;
+ paParms[i++].setUInt32(pEvent->ContextID());
+ paParms[i++].setPointer((void*)mData.mProcess.mCommand.c_str(),
+ (ULONG)mData.mProcess.mCommand.length() + 1);
+ paParms[i++].setUInt32(mData.mProcess.mFlags);
+ paParms[i++].setUInt32((uint32_t)mData.mProcess.mArguments.size());
+ paParms[i++].setPointer((void*)pszArgs, (uint32_t)cbArgs);
+ paParms[i++].setUInt32((uint32_t)mData.mProcess.mEnvironment.Size());
+ paParms[i++].setUInt32((uint32_t)cbEnv);
+ paParms[i++].setPointer((void*)pvEnv, (uint32_t)cbEnv);
+ if (uProtocol < 2)
{
- /* Prepare HGCM call. */
- VBOXHGCMSVCPARM paParms[15];
- int i = 0;
- paParms[i++].setUInt32(uContextID);
- paParms[i++].setPointer((void*)mData.mProcess.mCommand.c_str(),
- (ULONG)mData.mProcess.mCommand.length() + 1);
- paParms[i++].setUInt32(mData.mProcess.mFlags);
- paParms[i++].setUInt32(mData.mProcess.mArguments.size());
- paParms[i++].setPointer((void*)pszArgs, cbArgs);
- paParms[i++].setUInt32(mData.mProcess.mEnvironment.Size());
- paParms[i++].setUInt32(cbEnv);
- paParms[i++].setPointer((void*)pvEnv, cbEnv);
+ /* 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.mUser.c_str(), (ULONG)sessionCreds.mUser.length() + 1);
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.
- */
- if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
- paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);
- else
- paParms[i++].setUInt32(mData.mProcess.mTimeoutMS);
-
- /* Note: Don't hold the write lock in here, because setErrorInternal */
- vrc = sendCommand(HOST_EXEC_CMD, i, paParms);
+ }
+ /*
+ * 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 (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);
+ else
+ paParms[i++].setUInt32(mData.mProcess.mTimeoutMS);
+ if (uProtocol >= 2)
+ {
+ paParms[i++].setUInt32(mData.mProcess.mPriority);
+ /* 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. */
+ paParms[i++].setUInt32(1);
+ /* The actual CPU affinity blocks. */
+ paParms[i++].setPointer((void*)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));
}
- GuestEnvironment::FreeEnvironmentBlock(pvEnv);
- if (pszArgs)
- RTStrFree(pszArgs);
-
- uint32_t uTimeoutMS = mData.mProcess.mTimeoutMS;
-
- /* Drop the write lock again before waiting. */
- alock.release();
+ alock.release(); /* Drop the write lock before sending. */
- if (RT_SUCCESS(vrc))
+ vrc = sendCommand(HOST_EXEC_CMD, i, paParms);
+ if (RT_FAILURE(vrc))
{
- /*
- * Let's wait for the process being started.
- * Note: Be sure not keeping a AutoRead/WriteLock here.
- */
- LogFlowThisFunc(("Waiting for callback (%RU32ms) ...\n", uTimeoutMS));
- vrc = pCallbackStart->Wait(uTimeoutMS);
- if (RT_SUCCESS(vrc)) /* Wait was successful, check for supplied information. */
- {
- int guestRc = pCallbackStart->GetResultCode();
- if (pGuestRc)
- *pGuestRc = guestRc;
- LogFlowThisFunc(("Callback returned rc=%Rrc\n", guestRc));
- }
- else
- vrc = VERR_TIMEOUT;
+ int rc2 = setProcessStatus(ProcessStatus_Error, vrc);
+ AssertRC(rc2);
}
+ }
- AutoWriteLock awlock(this COMMA_LOCKVAL_SRC_POS);
+ GuestEnvironment::FreeEnvironmentBlock(pvEnv);
+ if (pszArgs)
+ RTStrFree(pszArgs);
- AssertPtr(pCallbackStart);
- int rc2 = callbackRemove(uContextID);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
- }
+ if (RT_SUCCESS(vrc))
+ vrc = waitForStatusChange(pEvent, uTimeoutMS,
+ NULL /* Process status */, pGuestRc);
+ unregisterWaitEvent(pEvent);
LogFlowFuncLeaveRC(vrc);
return vrc;
@@ -1291,208 +1235,477 @@ DECLCALLBACK(int) GuestProcess::startProcessThread(RTTHREAD Thread, void *pvUser
AutoCaller autoCaller(pProcess);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
- int vrc = pProcess->startProcess(NULL /* Guest rc, ignored */);
+ int vrc = pProcess->startProcess(30 * 1000 /* 30s timeout */,
+ NULL /* Guest rc, ignored */);
/* Nothing to do here anymore. */
- LogFlowFuncLeaveRC(vrc);
+ LogFlowFunc(("pProcess=%p returning rc=%Rrc\n", (GuestProcess *)pProcess, vrc));
return vrc;
}
-int GuestProcess::terminateProcess(void)
+int GuestProcess::terminateProcess(uint32_t uTimeoutMS, int *pGuestRc)
{
- LogFlowThisFuncEnter();
+ /* pGuestRc is optional. */
+ LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS));
AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
- if (mData.mParent->getProtocolVersion() < 2)
- return VERR_NOT_SUPPORTED;
+ int vrc = VINF_SUCCESS;
- LogFlowThisFuncLeave();
- return VERR_NOT_IMPLEMENTED;
-}
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n",
+ mData.mStatus));
+ }
+ else
+ {
+ AssertPtr(mSession);
+ /* Note: VBox < 4.3 (aka protocol version 1) does not
+ * support this, so just skip. */
+ if (mSession->getProtocolVersion() < 2)
+ vrc = VERR_NOT_SUPPORTED;
-int GuestProcess::waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, ProcessWaitResult_T &waitResult, int *pGuestRc)
-{
- LogFlowThisFuncEnter();
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
- AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
- LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, pGuestRc=%p\n",
- fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, pGuestRc));
+ if (RT_FAILURE(vrc))
+ return vrc;
- AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ paParms[i++].setUInt32(pEvent->ContextID());
+ paParms[i++].setUInt32(mData.mPID);
- /* Did some error occur before? Then skip waiting and return. */
- if (mData.mStatus == ProcessStatus_Error)
- {
- waitResult = ProcessWaitResult_Error;
- AssertMsg(RT_FAILURE(mData.mRC), ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mRC));
- if (pGuestRc)
- *pGuestRc = mData.mRC; /* Return last set error. */
- return VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendCommand(HOST_EXEC_TERMINATE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = waitForStatusChange(pEvent, uTimeoutMS,
+ NULL /* ProcessStatus */, pGuestRc);
+ unregisterWaitEvent(pEvent);
+ }
}
- waitResult = ProcessWaitResult_None;
- if ( (fWaitFlags & ProcessWaitForFlag_Terminate)
- || (fWaitFlags & ProcessWaitForFlag_StdIn)
- || (fWaitFlags & ProcessWaitForFlag_StdOut)
- || (fWaitFlags & ProcessWaitForFlag_StdErr))
- {
- switch (mData.mStatus)
- {
- case ProcessStatus_TerminatedNormally:
- case ProcessStatus_TerminatedSignal:
- case ProcessStatus_TerminatedAbnormally:
- case ProcessStatus_Down:
- waitResult = ProcessWaitResult_Terminate;
- break;
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
- case ProcessStatus_TimedOutKilled:
- case ProcessStatus_TimedOutAbnormally:
- waitResult = ProcessWaitResult_Timeout;
- break;
+/* static */
+ProcessWaitResult_T GuestProcess::waitFlagsToResultEx(uint32_t fWaitFlags,
+ ProcessStatus_T oldStatus, ProcessStatus_T newStatus,
+ uint32_t uProcFlags, uint32_t uProtocol)
+{
+ ProcessWaitResult_T waitResult = ProcessWaitResult_None;
- case ProcessStatus_Error:
- /* Handled above. */
- break;
+ switch (newStatus)
+ {
+ case ProcessStatus_TerminatedNormally:
+ case ProcessStatus_TerminatedSignal:
+ case ProcessStatus_TerminatedAbnormally:
+ case ProcessStatus_Down:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Terminate;
+ break;
- case ProcessStatus_Started:
+ case ProcessStatus_TimedOutKilled:
+ case ProcessStatus_TimedOutAbnormally:
+ /* Dito. */
+ waitResult = ProcessWaitResult_Timeout;
+ break;
+
+ case ProcessStatus_Started:
+ switch (oldStatus)
{
- /*
- * 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.
- */
- if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ /* Also wait for process start. */
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ 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.
+ */
+ if (uProcFlags & ProcessCreateFlag_WaitForProcessStartOnly)
+ waitResult = ProcessWaitResult_Start;
+ }
+ break;
+
+ case ProcessStatus_Started:
+ /* Only wait for process start. */
+ if (fWaitFlags == ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled old status %RU32 before new status 'started'\n",
+ oldStatus));
waitResult = ProcessWaitResult_Start;
- break;
+ break;
}
+ break;
- case ProcessStatus_Undefined:
- case ProcessStatus_Starting:
- /* Do the waiting below. */
- break;
+ case ProcessStatus_Error:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Error;
+ break;
- default:
- AssertMsgFailed(("Unhandled process status %ld\n", mData.mStatus));
- return VERR_NOT_IMPLEMENTED;
- }
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ /* No result available yet, leave wait
+ * flags untouched. */
+ break;
}
- else if (fWaitFlags & ProcessWaitForFlag_Start)
+
+ if (newStatus == ProcessStatus_Started)
{
- switch (mData.mStatus)
+ /* Filter out waits which are *not* supported using
+ * older guest control Guest Additions.
+ *
+ ** @todo ProcessWaitForFlag_Std* flags are not implemented yet.
+ */
+ if (uProtocol < 99) /* See @todo above. */
{
- case ProcessStatus_Started:
- case ProcessStatus_Paused:
- case ProcessStatus_Terminating:
- case ProcessStatus_TerminatedNormally:
- case ProcessStatus_TerminatedSignal:
- case ProcessStatus_TerminatedAbnormally:
- case ProcessStatus_Down:
- waitResult = ProcessWaitResult_Start;
- break;
+ if ( waitResult == ProcessWaitResult_None
+ /* We don't support waiting for stdin, out + err,
+ * just skip waiting then. */
+ && ( (fWaitFlags & ProcessWaitForFlag_StdIn)
+ || (fWaitFlags & ProcessWaitForFlag_StdOut)
+ || (fWaitFlags & ProcessWaitForFlag_StdErr)
+ )
+ )
+ {
+ /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
+ waitResult = ProcessWaitResult_WaitFlagNotSupported;
+ }
+ }
+ }
- case ProcessStatus_Error:
- waitResult = ProcessWaitResult_Error;
- break;
+#ifdef DEBUG
+ LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n",
+ oldStatus, newStatus, fWaitFlags, waitResult));
+#endif
+ return waitResult;
+}
- case ProcessStatus_TimedOutKilled:
- case ProcessStatus_TimedOutAbnormally:
- waitResult = ProcessWaitResult_Timeout;
- break;
+ProcessWaitResult_T GuestProcess::waitFlagsToResult(uint32_t fWaitFlags)
+{
+ AssertPtr(mSession);
+ return GuestProcess::waitFlagsToResultEx(fWaitFlags,
+ mData.mStatus /* curStatus */, mData.mStatus /* newStatus */,
+ mData.mProcess.mFlags, mSession->getProtocolVersion());
+}
- case ProcessStatus_Undefined:
- case ProcessStatus_Starting:
- /* Do the waiting below. */
- break;
+int GuestProcess::waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS,
+ ProcessWaitResult_T &waitResult, int *pGuestRc)
+{
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
- default:
- AssertMsgFailed(("Unhandled process status %ld\n", mData.mStatus));
- return VERR_NOT_IMPLEMENTED;
- }
- }
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
- /* Filter out waits which are *not* supported using
- * older guest control Guest Additions. */
- if (mData.mParent->getProtocolVersion() < 2)
+ LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, pGuestRc=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, pGuestRc));
+
+ /* Did some error occur before? Then skip waiting and return. */
+ ProcessStatus_T curStatus = mData.mStatus;
+ if (curStatus == ProcessStatus_Error)
{
- if ( waitResult == ProcessWaitResult_None
- /* We don't support waiting for stdin, out + err,
- * just skip waiting then. */
- && ( (fWaitFlags & ProcessWaitForFlag_StdIn)
- || (fWaitFlags & ProcessWaitForFlag_StdOut)
- || (fWaitFlags & ProcessWaitForFlag_StdErr)
- )
- )
- {
- /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
- waitResult = ProcessWaitResult_WaitFlagNotSupported;
- }
+ waitResult = ProcessWaitResult_Error;
+ AssertMsg(RT_FAILURE(mData.mLastError), ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
+ if (pGuestRc)
+ *pGuestRc = mData.mLastError; /* Return last set error. */
+ LogFlowThisFunc(("Process is in error state (guestRc=%Rrc)\n", mData.mLastError));
+ return VERR_GSTCTL_GUEST_ERROR;
}
- LogFlowThisFunc(("procStatus=%ld, procRc=%Rrc, waitResult=%ld\n",
- mData.mStatus, mData.mRC, waitResult));
+ waitResult = waitFlagsToResult(fWaitFlags);
/* No waiting needed? Return immediately using the last set error. */
if (waitResult != ProcessWaitResult_None)
{
if (pGuestRc)
- *pGuestRc = mData.mRC; /* Return last set error (if any). */
- return RT_SUCCESS(mData.mRC) ? VINF_SUCCESS : VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
+ *pGuestRc = mData.mLastError; /* Return last set error (if any). */
+ LogFlowThisFunc(("Nothing to wait for (guestRc=%Rrc)\n", mData.mLastError));
+ return RT_SUCCESS(mData.mLastError) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
}
- if (mData.mWaitCount > 0)
- return VERR_ALREADY_EXISTS;
- mData.mWaitCount++;
+ /* Adjust timeout. Passing 0 means RT_INDEFINITE_WAIT. */
+ if (!uTimeoutMS)
+ uTimeoutMS = RT_INDEFINITE_WAIT;
- int vrc = VINF_SUCCESS;
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
try
{
- Assert(mData.mWaitEvent == NULL);
- mData.mWaitEvent = new GuestProcessWaitEvent(fWaitFlags);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
}
- catch(std::bad_alloc &)
+ catch (std::bad_alloc)
{
vrc = VERR_NO_MEMORY;
}
- if (RT_SUCCESS(vrc))
- {
- GuestProcessWaitEvent *pEvent = mData.mWaitEvent;
- AssertPtr(pEvent);
+ if (RT_FAILURE(vrc))
+ return vrc;
- alock.release(); /* Release lock before waiting. */
+ alock.release(); /* Release lock before waiting. */
+
+ /*
+ * Do the actual waiting.
+ */
+ ProcessStatus_T newStatus = ProcessStatus_Undefined;
+ uint64_t u64StartMS = RTTimeMilliTS();
+ for (;;)
+ {
+ uint64_t u64ElapsedMS = RTTimeMilliTS() - u64StartMS;
+ if ( uTimeoutMS != RT_INDEFINITE_WAIT
+ && u64ElapsedMS >= uTimeoutMS)
+ {
+ vrc = VERR_TIMEOUT;
+ break;
+ }
- vrc = pEvent->Wait(uTimeoutMS);
- LogFlowThisFunc(("Waiting completed with rc=%Rrc\n", vrc));
+ vrc = waitForStatusChange(pEvent,
+ uTimeoutMS == RT_INDEFINITE_WAIT
+ ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS,
+ &newStatus, pGuestRc);
if (RT_SUCCESS(vrc))
{
- waitResult = pEvent->GetWaitResult();
- int waitRc = pEvent->GetWaitRc();
+ alock.acquire();
+
+ waitResult = waitFlagsToResultEx(fWaitFlags, curStatus, newStatus,
+ mData.mProcess.mFlags, mSession->getProtocolVersion());
+#ifdef DEBUG
+ LogFlowThisFunc(("Got new status change: fWaitFlags=0x%x, newStatus=%RU32, waitResult=%RU32\n",
+ fWaitFlags, newStatus, waitResult));
+#endif
+ if (ProcessWaitResult_None != waitResult) /* We got a waiting result. */
+ break;
+ }
+ else /* Waiting failed, bail out. */
+ break;
+
+ alock.release(); /* Don't hold lock in next waiting round. */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowThisFunc(("Returned waitResult=%RU32, newStatus=%RU32, rc=%Rrc\n",
+ waitResult, newStatus, vrc));
+ return vrc;
+}
+
+int GuestProcess::waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessInputNotify)
+ {
+ ComPtr<IGuestProcessInputNotifyEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
- LogFlowThisFunc(("Waiting event returned rc=%Rrc\n", waitRc));
+ if (pInputStatus)
+ {
+ HRESULT hr2 = pProcessEvent->COMGETTER(Status)(pInputStatus);
+ ComAssertComRC(hr2);
+ }
+ if (pcbProcessed)
+ {
+ HRESULT hr2 = pProcessEvent->COMGETTER(Processed)((ULONG*)pcbProcessed);
+ ComAssertComRC(hr2);
+ }
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
+ }
- if (pGuestRc)
- *pGuestRc = waitRc;
+ LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n",
+ pEvent, uHandle, vrc));
+ return vrc;
+}
- vrc = RT_SUCCESS(waitRc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
+int GuestProcess::waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* 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",
+ pEvent->TypeCount(), pEvent, uHandle, uTimeoutMS, pvData, cbData, pcbRead));
+
+ int vrc;
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ do
+ {
+ vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ if (evtType == VBoxEventType_OnGuestProcessOutput)
+ {
+ ComPtr<IGuestProcessOutputEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ ULONG uHandleEvent;
+ HRESULT hr = pProcessEvent->COMGETTER(Handle)(&uHandleEvent);
+ if ( SUCCEEDED(hr)
+ && uHandleEvent == uHandle)
+ {
+ if (pvData)
+ {
+ com::SafeArray <BYTE> data;
+ hr = pProcessEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
+ ComAssertComRC(hr);
+ size_t cbRead = data.size();
+ if (cbRead)
+ {
+ if (cbRead <= cbData)
+ {
+ /* Copy data from event into our buffer. */
+ memcpy(pvData, data.raw(), data.size());
+ }
+ else
+ vrc = VERR_BUFFER_OVERFLOW;
+
+ LogFlowThisFunc(("Read %zu bytes (uHandle=%RU32), rc=%Rrc\n",
+ cbRead, uHandleEvent, vrc));
+ }
+ }
+
+ if ( RT_SUCCESS(vrc)
+ && pcbRead)
+ {
+ ULONG cbRead;
+ hr = pProcessEvent->COMGETTER(Processed)(&cbRead);
+ ComAssertComRC(hr);
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ break;
+ }
+ else if (FAILED(hr))
+ vrc = VERR_COM_UNEXPECTED;
+ }
+ else
+ vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
}
- alock.acquire(); /* Get the lock again. */
+ } while (vrc == VINF_SUCCESS);
- /* Note: The caller always is responsible of deleting the
- * stuff it created before. See close() for more information. */
- delete mData.mWaitEvent;
- mData.mWaitEvent = NULL;
+ if ( vrc != VINF_SUCCESS
+ && pcbRead)
+ {
+ *pcbRead = 0;
}
- Assert(mData.mWaitCount);
- mData.mWaitCount--;
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+int GuestProcess::waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ ProcessStatus_T *pProcessStatus, int *pGuestRc)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pProcessStatus is optional. */
+ /* pGuestRc is optional. */
+
+ VBoxEventType_T evtType;
+ ComPtr<IEvent> pIEvent;
+ int vrc = waitForEvent(pEvent, uTimeoutMS,
+ &evtType, pIEvent.asOutParam());
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(evtType == VBoxEventType_OnGuestProcessStateChanged);
+ ComPtr<IGuestProcessStateChangedEvent> pProcessEvent = pIEvent;
+ Assert(!pProcessEvent.isNull());
+
+ ProcessStatus_T procStatus;
+ HRESULT hr = pProcessEvent->COMGETTER(Status)(&procStatus);
+ ComAssertComRC(hr);
+ if (pProcessStatus)
+ *pProcessStatus = procStatus;
+
+ ComPtr<IVirtualBoxErrorInfo> errorInfo;
+ hr = pProcessEvent->COMGETTER(Error)(errorInfo.asOutParam());
+ ComAssertComRC(hr);
+
+ LONG lGuestRc;
+ hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
+ ComAssertComRC(hr);
+
+ LogFlowThisFunc(("Got procStatus=%RU32, guestRc=%RI32 (%Rrc)\n",
+ procStatus, lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (pGuestRc)
+ *pGuestRc = (int)lGuestRc;
+ }
LogFlowFuncLeaveRC(vrc);
return vrc;
}
+/* static */
+bool GuestProcess::waitResultImpliesEx(ProcessWaitResult_T waitResult,
+ ProcessStatus_T procStatus, uint32_t uProcFlags,
+ uint32_t uProtocol)
+{
+ bool fImplies;
+
+ switch (waitResult)
+ {
+ case ProcessWaitResult_Start:
+ fImplies = procStatus == ProcessStatus_Started;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fImplies = ( procStatus == ProcessStatus_TerminatedNormally
+ || procStatus == ProcessStatus_TerminatedSignal
+ || procStatus == ProcessStatus_TerminatedAbnormally
+ || procStatus == ProcessStatus_TimedOutKilled
+ || procStatus == ProcessStatus_TimedOutAbnormally
+ || procStatus == ProcessStatus_Down
+ || procStatus == ProcessStatus_Error);
+ break;
+
+ default:
+ fImplies = false;
+ break;
+ }
+
+ return fImplies;
+}
+
int GuestProcess::writeData(uint32_t uHandle, uint32_t uFlags,
void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *pGuestRc)
{
@@ -1511,107 +1724,64 @@ int GuestProcess::writeData(uint32_t uHandle, uint32_t uFlags,
return VINF_SUCCESS; /* Not available for writing (anymore). */
}
- int vrc = VINF_SUCCESS;
+ int vrc;
- GuestCtrlCallback *pCallbackWrite = NULL;
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
try
{
- pCallbackWrite = new GuestCtrlCallback();
+ /*
+ * 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 (mSession->getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
}
- catch(std::bad_alloc &)
+ catch (std::bad_alloc)
{
vrc = VERR_NO_MEMORY;
}
- /* Create callback and add it to the map. */
- uint32_t uContextID = 0;
- if (RT_SUCCESS(vrc))
- {
- vrc = pCallbackWrite->Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_INPUT_STATUS);
- if (RT_SUCCESS(vrc))
- vrc = callbackAdd(pCallbackWrite, &uContextID);
- }
-
- alock.release(); /* Drop the write lock again. */
+ if (RT_FAILURE(vrc))
+ return vrc;
- if (RT_SUCCESS(vrc))
- {
- VBOXHGCMSVCPARM paParms[5];
+ VBOXHGCMSVCPARM paParms[5];
+ int i = 0;
+ paParms[i++].setUInt32(pEvent->ContextID());
+ paParms[i++].setUInt32(mData.mPID);
+ paParms[i++].setUInt32(uFlags);
+ paParms[i++].setPointer(pvData, (uint32_t)cbData);
+ paParms[i++].setUInt32((uint32_t)cbData);
- int i = 0;
- paParms[i++].setUInt32(uContextID);
- paParms[i++].setUInt32(mData.mPID);
- paParms[i++].setUInt32(uFlags);
- paParms[i++].setPointer(pvData, cbData);
- paParms[i++].setUInt32(cbData);
-
- vrc = sendCommand(HOST_EXEC_SET_INPUT, i, paParms);
- }
+ alock.release(); /* Drop the write lock before sending. */
+ uint32_t cbProcessed = 0;
+ vrc = sendCommand(HOST_EXEC_SET_INPUT, i, paParms);
if (RT_SUCCESS(vrc))
{
- /*
- * Let's wait for the process being started.
- * Note: Be sure not keeping a AutoRead/WriteLock here.
- */
- LogFlowThisFunc(("Waiting for callback (%RU32ms) ...\n", uTimeoutMS));
- vrc = pCallbackWrite->Wait(uTimeoutMS);
- if (RT_SUCCESS(vrc)) /* Wait was successful, check for supplied information. */
+ ProcessInputStatus_T inputStatus;
+ vrc = waitForInputNotify(pEvent, uHandle, uTimeoutMS,
+ &inputStatus, &cbProcessed);
+ if (RT_SUCCESS(vrc))
{
- int guestRc = pCallbackWrite->GetResultCode();
- LogFlowThisFunc(("Callback returned rc=%Rrc, cbData=%RU32\n", guestRc, pCallbackWrite->GetDataSize()));
+ /** @todo Set guestRc. */
- if (RT_SUCCESS(guestRc))
- {
- Assert(pCallbackWrite->GetDataSize() == sizeof(CALLBACKDATAEXECINSTATUS));
- PCALLBACKDATAEXECINSTATUS pData = (PCALLBACKDATAEXECINSTATUS)pCallbackWrite->GetDataRaw();
- AssertPtr(pData);
-
- uint32_t cbWritten = 0;
- switch (pData->u32Status)
- {
- case INPUT_STS_WRITTEN:
- cbWritten = pData->cbProcessed;
- break;
-
- case INPUT_STS_ERROR:
- vrc = pData->u32Flags; /** @todo Fix int vs. uint32_t! */
- break;
-
- case INPUT_STS_TERMINATED:
- vrc = VERR_CANCELLED;
- break;
-
- case INPUT_STS_OVERFLOW:
- vrc = VERR_BUFFER_OVERFLOW;
- break;
-
- default:
- /* Silently skip unknown errors. */
- break;
- }
-
- LogFlowThisFunc(("cbWritten=%RU32\n", cbWritten));
-
- if (pGuestRc)
- *pGuestRc = guestRc;
-
- if (puWritten)
- *puWritten = cbWritten;
-
- if (RT_FAILURE(guestRc))
- vrc = VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
- }
+ if (puWritten)
+ *puWritten = cbProcessed;
}
+ /** @todo Error handling. */
}
- alock.acquire();
-
- int rc2 = callbackRemove(uContextID);
- if (RT_SUCCESS(vrc))
- vrc = rc2;
+ unregisterWaitEvent(pEvent);
- LogFlowFuncLeaveRC(vrc);
+ LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n",
+ cbProcessed, vrc));
return vrc;
}
@@ -1623,6 +1793,8 @@ STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS,
#ifndef VBOX_WITH_GUEST_CONTROL
ReturnComNotImplemented();
#else
+ LogFlowThisFuncEnter();
+
if (aToRead == 0)
return setError(E_INVALIDARG, tr("The size to read is zero"));
CheckComArgOutSafeArrayPointerValid(aData);
@@ -1635,7 +1807,7 @@ STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS,
HRESULT hr = S_OK;
- size_t cbRead; int guestRc;
+ uint32_t cbRead; int guestRc;
int vrc = readData(aHandle, aToRead, aTimeoutMS, data.raw(), aToRead, &cbRead, &guestRc);
if (RT_SUCCESS(vrc))
{
@@ -1647,7 +1819,7 @@ STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS,
{
switch (vrc)
{
- case VERR_GENERAL_FAILURE: /** @todo Special guest control rc needed! */
+ case VERR_GSTCTL_GUEST_ERROR:
hr = GuestProcess::setErrorExternal(this, guestRc);
break;
@@ -1659,7 +1831,7 @@ STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS,
}
}
- LogFlowThisFunc(("rc=%Rrc, cbRead=%RU64\n", vrc, cbRead));
+ LogFlowThisFunc(("rc=%Rrc, cbRead=%RU32\n", vrc, cbRead));
LogFlowFuncLeaveRC(vrc);
return hr;
@@ -1678,14 +1850,16 @@ STDMETHODIMP GuestProcess::Terminate(void)
HRESULT hr = S_OK;
- int vrc = terminateProcess();
+ int guestRc;
+ int vrc = terminateProcess(30 * 1000 /* Timeout in ms */,
+ &guestRc);
if (RT_FAILURE(vrc))
{
switch (vrc)
{
- case VERR_NOT_IMPLEMENTED:
- ReturnComNotImplemented();
- break; /* Never reached. */
+ case VERR_GSTCTL_GUEST_ERROR:
+ hr = GuestProcess::setErrorExternal(this, guestRc);
+ break;
case VERR_NOT_SUPPORTED:
hr = setError(VBOX_E_IPRT_ERROR,
@@ -1701,15 +1875,12 @@ STDMETHODIMP GuestProcess::Terminate(void)
}
}
- AssertPtr(mData.mParent);
- mData.mParent->processRemoveFromList(this);
-
- /*
- * Release autocaller before calling uninit.
- */
- autoCaller.release();
-
- uninit();
+ /* Remove process from guest session list. Now only API clients
+ * still can hold references to it. */
+ AssertPtr(mSession);
+ int rc2 = mSession->processRemoveFromList(this);
+ if (RT_SUCCESS(vrc))
+ vrc = rc2;
LogFlowFuncLeaveRC(vrc);
return hr;
@@ -1743,7 +1914,7 @@ STDMETHODIMP GuestProcess::WaitFor(ULONG aWaitFlags, ULONG aTimeoutMS, ProcessWa
{
switch (vrc)
{
- case VERR_GENERAL_FAILURE: /** @todo Special guest control rc needed! */
+ case VERR_GSTCTL_GUEST_ERROR:
hr = GuestProcess::setErrorExternal(this, guestRc);
break;
@@ -1796,22 +1967,23 @@ STDMETHODIMP GuestProcess::Write(ULONG aHandle, ULONG aFlags,
#else
LogFlowThisFuncEnter();
+ CheckComArgSafeArrayNotNull(aData);
CheckComArgOutPointerValid(aWritten);
AutoCaller autoCaller(this);
if (FAILED(autoCaller.rc())) return autoCaller.rc();
- AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ com::SafeArray<BYTE> data(ComSafeArrayInArg(aData));
HRESULT hr = S_OK;
- com::SafeArray<BYTE> data(ComSafeArrayInArg(aData)); int guestRc;
- int vrc = writeData(aHandle, aFlags, data.raw(), data.size(), aTimeoutMS, (uint32_t*)aWritten, &guestRc);
+ uint32_t cbWritten; int guestRc;
+ int vrc = writeData(aHandle, aFlags, data.raw(), data.size(), aTimeoutMS, &cbWritten, &guestRc);
if (RT_FAILURE(vrc))
{
switch (vrc)
{
- case VERR_GENERAL_FAILURE: /** @todo Special guest control rc needed! */
+ case VERR_GSTCTL_GUEST_ERROR:
hr = GuestProcess::setErrorExternal(this, guestRc);
break;
@@ -1823,7 +1995,9 @@ STDMETHODIMP GuestProcess::Write(ULONG aHandle, ULONG aFlags,
}
}
- LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, aWritten));
+ LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, cbWritten));
+
+ *aWritten = (ULONG)cbWritten;
LogFlowFuncLeaveRC(vrc);
return hr;
@@ -1838,6 +2012,7 @@ STDMETHODIMP GuestProcess::WriteArray(ULONG aHandle, ComSafeArrayIn(ProcessInput
#else
LogFlowThisFuncEnter();
+ CheckComArgSafeArrayNotNull(aData);
CheckComArgOutPointerValid(aWritten);
AutoCaller autoCaller(this);
@@ -1858,13 +2033,14 @@ STDMETHODIMP GuestProcess::WriteArray(ULONG aHandle, ComSafeArrayIn(ProcessInput
///////////////////////////////////////////////////////////////////////////////
GuestProcessTool::GuestProcessTool(void)
- : pSession(NULL)
+ : pSession(NULL),
+ pProcess(NULL)
{
}
GuestProcessTool::~GuestProcessTool(void)
{
- Terminate();
+ Terminate(30 * 1000, NULL /* pGuestRc */);
}
int GuestProcessTool::Init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
@@ -1875,7 +2051,7 @@ int GuestProcessTool::Init(GuestSession *pGuestSession, const GuestProcessStartu
AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
- pSession = pGuestSession;
+ pSession = pGuestSession;
mStartupInfo = startupInfo;
/* Make sure the process is hidden. */
@@ -1883,15 +2059,18 @@ int GuestProcessTool::Init(GuestSession *pGuestSession, const GuestProcessStartu
int vrc = pSession->processCreateExInteral(mStartupInfo, pProcess);
if (RT_SUCCESS(vrc))
- vrc = fAsync ? pProcess->startProcessAsync() : pProcess->startProcess(pGuestRc);
+ vrc = fAsync
+ ? pProcess->startProcessAsync()
+ : pProcess->startProcess(30 * 1000 /* 30s timeout */, pGuestRc);
- if ( !fAsync
+ if ( RT_SUCCESS(vrc)
+ && !fAsync
&& ( pGuestRc
&& RT_FAILURE(*pGuestRc)
)
)
{
- vrc = VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
+ vrc = VERR_GSTCTL_GUEST_ERROR;
}
LogFlowFuncLeaveRC(vrc);
@@ -1925,20 +2104,78 @@ int GuestProcessTool::GetCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock
bool GuestProcessTool::IsRunning(void)
{
- AssertReturn(!pProcess.isNull(), true);
+ AssertReturn(!pProcess.isNull(), false);
ProcessStatus_T procStatus = ProcessStatus_Undefined;
HRESULT hr = pProcess->COMGETTER(Status(&procStatus));
Assert(SUCCEEDED(hr));
- if ( procStatus != ProcessStatus_Started
- && procStatus != ProcessStatus_Paused
- && procStatus != ProcessStatus_Terminating)
+ if ( procStatus == ProcessStatus_Started
+ || procStatus == ProcessStatus_Paused
+ || procStatus == ProcessStatus_Terminating)
{
- return false;
+ return true;
}
- return true;
+ return false;
+}
+
+/* static */
+int GuestProcessTool::Run( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ int *pGuestRc)
+{
+ return RunEx(pGuestSession, startupInfo,
+ NULL /* pStrmOutObjects */, 0 /* cStrmOutObjects */,
+ pGuestRc);
+}
+
+/* static */
+int GuestProcessTool::RunEx( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *pStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ int *pGuestRc)
+{
+ GuestProcessTool procTool; int guestRc;
+ int vrc = procTool.Init(pGuestSession, startupInfo, false /* Async */, &guestRc);
+ if (RT_SUCCESS(vrc))
+ {
+ while (cStrmOutObjects--)
+ {
+ try
+ {
+ GuestProcessStreamBlock strmBlk;
+ vrc = procTool.WaitEx( pStrmOutObjects
+ ? GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK
+ : GUESTPROCESSTOOL_FLAG_NONE, &strmBlk, &guestRc);
+ if (pStrmOutObjects)
+ pStrmOutObjects->push_back(strmBlk);
+ }
+ catch (std::bad_alloc)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure the process runs until completion. */
+ vrc = procTool.Wait(GUESTPROCESSTOOL_FLAG_NONE, &guestRc);
+ if (RT_SUCCESS(vrc))
+ {
+ guestRc = procTool.TerminatedOk(NULL /* Exit code */);
+ if (RT_FAILURE(guestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ if (pGuestRc)
+ *pGuestRc = guestRc;
+
+ LogFlowFunc(("Returned rc=%Rrc, guestRc=%Rrc\n", vrc, guestRc));
+ return vrc;
}
int GuestProcessTool::TerminatedOk(LONG *pExitCode)
@@ -1946,6 +2183,7 @@ int GuestProcessTool::TerminatedOk(LONG *pExitCode)
Assert(!pProcess.isNull());
/* pExitCode is optional. */
+ int vrc;
if (!IsRunning())
{
LONG exitCode;
@@ -1955,36 +2193,36 @@ int GuestProcessTool::TerminatedOk(LONG *pExitCode)
if (pExitCode)
*pExitCode = exitCode;
- if (exitCode != 0)
- return VERR_NOT_EQUAL; /** @todo Special guest control rc needed! */
- return VINF_SUCCESS;
+ vrc = (exitCode != 0)
+ /** @todo Special guest control rc needed! */
+ ? VERR_NOT_EQUAL : VINF_SUCCESS;
}
+ else
+ vrc = VERR_INVALID_STATE; /** @todo Special guest control rc needed! */
- return VERR_INVALID_STATE; /** @todo Special guest control rc needed! */
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
}
int GuestProcessTool::Wait(uint32_t fFlags, int *pGuestRc)
{
- return WaitEx(fFlags, NULL /* pStreamBlock */, pGuestRc);
+ return WaitEx(fFlags, NULL /* pStrmBlkOut */, pGuestRc);
}
-int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBlock, int *pGuestRc)
+int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStrmBlkOut, int *pGuestRc)
{
- LogFlowThisFunc(("pSession=%p, fFlags=0x%x, pStreamBlock=%p, pGuestRc=%p\n",
- pSession, fFlags, pStreamBlock, pGuestRc));
-
- AssertPtrReturn(pSession, VERR_INVALID_POINTER);
- Assert(!pProcess.isNull());
- /* Other parameters are optional. */
+ LogFlowThisFunc(("fFlags=0x%x, pStreamBlock=%p, pGuestRc=%p\n",
+ fFlags, pStrmBlkOut, pGuestRc));
/* Can we parse the next block without waiting? */
int vrc;
if (fFlags & GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK)
{
- AssertPtr(pStreamBlock);
- vrc = GetCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStreamBlock);
+ AssertPtr(pStrmBlkOut);
+ vrc = GetCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStrmBlkOut);
if (RT_SUCCESS(vrc))
return vrc;
+ /* else do the the waiting below. */
}
/* Do the waiting. */
@@ -1994,25 +2232,47 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
fWaitFlags |= ProcessWaitForFlag_StdErr;
- LogFlowFunc(("waitFlags=0x%x\n", fWaitFlags));
-
- /** @todo Decrease timeout. */
+ /** @todo Decrease timeout while running. */
+ uint64_t u64StartMS = RTTimeMilliTS();
uint32_t uTimeoutMS = mStartupInfo.mTimeoutMS;
int guestRc;
bool fDone = false;
BYTE byBuf[_64K];
- size_t cbRead;
+ uint32_t cbRead;
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() \
+ u64ElapsedMS = RTTimeMilliTS() - u64StartMS; \
+ if ( uTimeoutMS != RT_INDEFINITE_WAIT \
+ && u64ElapsedMS >= uTimeoutMS) \
+ { \
+ vrc = VERR_TIMEOUT; \
+ break; \
+ }
+
+ /**
+ * Returns the remaining time (in ms).
+ */
+#define GET_REMAINING_TIME \
+ uTimeoutMS == RT_INDEFINITE_WAIT \
+ ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS \
+
ProcessWaitResult_T waitRes;
do
{
- vrc = pProcess->waitFor(fWaitFlags,
- uTimeoutMS, waitRes, &guestRc);
+ uint64_t u64ElapsedMS;
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ vrc = pProcess->waitFor(fWaitFlags, GET_REMAINING_TIME,
+ waitRes, &guestRc);
if (RT_FAILURE(vrc))
break;
@@ -2041,7 +2301,7 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
break;
case ProcessWaitResult_Error:
- vrc = VERR_GENERAL_FAILURE; /** @todo Special guest control rc needed! */
+ vrc = VERR_GSTCTL_GUEST_ERROR;
break;
case ProcessWaitResult_Terminate:
@@ -2058,28 +2318,39 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
break;
default:
- AssertReleaseMsgFailed(("Unhandled process wait result %ld\n", waitRes));
+ AssertMsgFailed(("Unhandled process wait result %RU32\n", waitRes));
break;
}
+ if (RT_FAILURE(vrc))
+ break;
+
if (fHandleStdOut)
{
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
vrc = pProcess->readData(OUTPUT_HANDLE_ID_STDOUT, sizeof(byBuf),
- uTimeoutMS, byBuf, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
&cbRead, &guestRc);
- if (RT_FAILURE(vrc))
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
break;
if (cbRead)
{
- LogFlowThisFunc(("Received %RU64 bytes from stdout\n", cbRead));
+ LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead));
vrc = mStdOut.AddData(byBuf, cbRead);
if ( RT_SUCCESS(vrc)
&& (fFlags & GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK))
{
- AssertPtr(pStreamBlock);
- vrc = GetCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStreamBlock);
+ AssertPtr(pStrmBlkOut);
+ vrc = GetCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStrmBlkOut);
+
+ /* When successful, break out of the loop because we're done
+ * with reading the first stream block. */
if (RT_SUCCESS(vrc))
fDone = true;
}
@@ -2090,15 +2361,20 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
if (fHandleStdErr)
{
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
vrc = pProcess->readData(OUTPUT_HANDLE_ID_STDERR, sizeof(byBuf),
- uTimeoutMS, byBuf, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
&cbRead, &guestRc);
- if (RT_FAILURE(vrc))
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
break;
if (cbRead)
{
- LogFlowThisFunc(("Received %RU64 bytes from stderr\n", cbRead));
+ LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead));
vrc = mStdErr.AddData(byBuf, cbRead);
}
@@ -2107,7 +2383,13 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
} while (!fDone && RT_SUCCESS(vrc));
- LogFlowThisFunc(("Loop ended with rc=%Rrc, guestRc=%Rrc, waitRes=%ld\n",
+#undef UPDATE_AND_CHECK_ELAPSED_TIME
+#undef GET_REMAINING_TIME
+
+ if (RT_FAILURE(guestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ LogFlowThisFunc(("Loop ended with rc=%Rrc, guestRc=%Rrc, waitRes=%RU32\n",
vrc, guestRc, waitRes));
if (pGuestRc)
*pGuestRc = guestRc;
@@ -2116,21 +2398,20 @@ int GuestProcessTool::WaitEx(uint32_t fFlags, GuestProcessStreamBlock *pStreamBl
return vrc;
}
-void GuestProcessTool::Terminate(void)
+int GuestProcessTool::Terminate(uint32_t uTimeoutMS, int *pGuestRc)
{
LogFlowThisFuncEnter();
+ int rc = VINF_SUCCESS;
if (!pProcess.isNull())
{
- /** @todo Add pProcess.Terminate() here as soon as it's implemented. */
-
- Assert(pSession);
- int rc2 = pSession->processRemoveFromList(pProcess);
- AssertRC(rc2);
-
+ rc = pProcess->terminateProcess(uTimeoutMS, pGuestRc);
pProcess.setNull();
}
+ else
+ rc = VERR_NOT_FOUND;
- LogFlowThisFuncLeave();
+ LogFlowFuncLeaveRC(rc);
+ return rc;
}