diff options
Diffstat (limited to 'src/VBox/Main/src-client/GuestProcessImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-client/GuestProcessImpl.cpp | 2149 |
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; } |