summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client/GuestProcessImpl.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Main/src-client/GuestProcessImpl.cpp
parentInitial commit. (diff)
downloadvirtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz
virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-client/GuestProcessImpl.cpp')
-rw-r--r--src/VBox/Main/src-client/GuestProcessImpl.cpp3031
1 files changed, 3031 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/GuestProcessImpl.cpp b/src/VBox/Main/src-client/GuestProcessImpl.cpp
new file mode 100644
index 00000000..e2befb3f
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestProcessImpl.cpp
@@ -0,0 +1,3031 @@
+/* $Id: GuestProcessImpl.cpp $ */
+/** @file
+ * VirtualBox Main - Guest process handling.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * Locking rules:
+ * - When the main dispatcher (callbackDispatcher) is called it takes the
+ * WriteLock while dispatching to the various on* methods.
+ * - All other outer functions (accessible by Main) must not own a lock
+ * while waiting for a callback or for an event.
+ * - Only keep Read/WriteLocks as short as possible and only when necessary.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_GUESTPROCESS
+#include "LoggingNew.h"
+
+#ifndef VBOX_WITH_GUEST_CONTROL
+# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
+#endif
+#include "GuestImpl.h"
+#include "GuestProcessImpl.h"
+#include "GuestSessionImpl.h"
+#include "GuestCtrlImplPrivate.h"
+#include "ConsoleImpl.h"
+#include "VirtualBoxErrorInfoImpl.h"
+
+#include "Global.h"
+#include "AutoCaller.h"
+#include "VBoxEvents.h"
+#include "ThreadTask.h"
+
+#include <memory> /* For auto_ptr. */
+
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h> /* For unconst(). */
+#include <iprt/getopt.h>
+
+#include <VBox/com/listeners.h>
+
+#include <VBox/com/array.h>
+
+
+/**
+ * Base class for all guest process tasks.
+ */
+class GuestProcessTask : public ThreadTask
+{
+public:
+
+ GuestProcessTask(GuestProcess *pProcess)
+ : ThreadTask("GenericGuestProcessTask")
+ , mProcess(pProcess)
+ , mRC(VINF_SUCCESS) { }
+
+ virtual ~GuestProcessTask(void) { }
+
+ /** Returns the last set result code. */
+ int i_rc(void) const { return mRC; }
+ /** Returns whether the last set result is okay (successful) or not. */
+ bool i_isOk(void) const { return RT_SUCCESS(mRC); }
+ /** Returns the reference of the belonging progress object. */
+ const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; }
+
+protected:
+
+ /** Progress object this process belongs to. */
+ const ComObjPtr<GuestProcess> mProcess;
+ /** Last set result code. */
+ int mRC;
+};
+
+/**
+ * Task to start a process on the guest.
+ */
+class GuestProcessStartTask : public GuestProcessTask
+{
+public:
+
+ GuestProcessStartTask(GuestProcess *pProcess)
+ : GuestProcessTask(pProcess)
+ {
+ m_strTaskName = "gctlPrcStart";
+ }
+
+ void handler()
+ {
+ GuestProcess::i_startProcessThreadTask(this);
+ }
+};
+
+/**
+ * Internal listener class to serve events in an
+ * active manner, e.g. without polling delays.
+ */
+class GuestProcessListener
+{
+public:
+
+ GuestProcessListener(void)
+ {
+ }
+
+ virtual ~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 vrc2 = mProcess->signalWaitEvent(aType, aEvent);
+ RT_NOREF(vrc2);
+#ifdef LOG_ENABLED
+ LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in vrc=%Rrc\n",
+ aType, &mProcess, vrc2));
+#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
+/////////////////////////////////////////////////////////////////////////////
+
+DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
+
+HRESULT GuestProcess::FinalConstruct(void)
+{
+ LogFlowThisFuncEnter();
+ return BaseFinalConstruct();
+}
+
+void GuestProcess::FinalRelease(void)
+{
+ LogFlowThisFuncEnter();
+ uninit();
+ BaseFinalRelease();
+ LogFlowThisFuncLeave();
+}
+
+// public initializer/uninitializer for internal purposes only
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initialies a guest process object.
+ *
+ * @returns VBox status code.
+ * @param aConsole Console this process is bound to.
+ * @param aSession Guest session this process is bound to.
+ * @param aObjectID Object ID to use for this process object.
+ * @param aProcInfo Process startup information to use.
+ * @param pBaseEnv Guest environment to apply when starting the process on the guest.
+ */
+int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aObjectID,
+ const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv)
+{
+ LogFlowThisFunc(("aConsole=%p, aSession=%p, aObjectID=%RU32, pBaseEnv=%p\n",
+ aConsole, aSession, aObjectID, pBaseEnv));
+
+ 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);
+
+ HRESULT hr;
+
+ int vrc = bindToSession(aConsole, aSession, aObjectID);
+ 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.mpSessionBaseEnv = pBaseEnv;
+ if (pBaseEnv)
+ pBaseEnv->retainConst();
+ 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;
+}
+
+/**
+ * Uninitializes the instance.
+ * Called from FinalRelease() or IGuestSession::uninit().
+ */
+void GuestProcess::uninit(void)
+{
+ /* Enclose the state transition Ready->InUninit->NotReady. */
+ AutoUninitSpan autoUninitSpan(this);
+ if (autoUninitSpan.uninitDone())
+ return;
+
+ LogFlowThisFunc(("mExe=%s, PID=%RU32\n", mData.mProcess.mExecutable.c_str(), mData.mPID));
+
+ if (mData.mpSessionBaseEnv)
+ {
+ mData.mpSessionBaseEnv->releaseConst();
+ mData.mpSessionBaseEnv = NULL;
+ }
+
+ baseUninit();
+
+ LogFlowFuncLeave();
+}
+
+// implementation of public getters/setters for attributes
+/////////////////////////////////////////////////////////////////////////////
+HRESULT GuestProcess::getArguments(std::vector<com::Utf8Str> &aArguments)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+ aArguments = mData.mProcess.mArguments;
+ return S_OK;
+}
+
+HRESULT GuestProcess::getEnvironment(std::vector<com::Utf8Str> &aEnvironment)
+{
+#ifndef VBOX_WITH_GUEST_CONTROL
+ ReturnComNotImplemented();
+#else
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* (Paranoia since both environment objects are immutable.) */
+ HRESULT hrc;
+ if (mData.mpSessionBaseEnv)
+ {
+ int vrc;
+ if (mData.mProcess.mEnvironmentChanges.count() == 0)
+ vrc = mData.mpSessionBaseEnv->queryPutEnvArray(&aEnvironment);
+ else
+ {
+ GuestEnvironment TmpEnv;
+ vrc = TmpEnv.copy(*mData.mpSessionBaseEnv);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = TmpEnv.applyChanges(mData.mProcess.mEnvironmentChanges);
+ if (RT_SUCCESS(vrc))
+ vrc = TmpEnv.queryPutEnvArray(&aEnvironment);
+ }
+ }
+ hrc = Global::vboxStatusCodeToCOM(vrc);
+ }
+ else
+ hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by installed Guest Additions"));
+ LogFlowThisFuncLeave();
+ return hrc;
+#endif
+}
+
+HRESULT GuestProcess::getEventSource(ComPtr<IEventSource> &aEventSource)
+{
+ LogFlowThisFuncEnter();
+
+ // no need to lock - lifetime constant
+ mEventSource.queryInterfaceTo(aEventSource.asOutParam());
+
+ LogFlowThisFuncLeave();
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExecutablePath(com::Utf8Str &aExecutablePath)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aExecutablePath = mData.mProcess.mExecutable;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getExitCode(LONG *aExitCode)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aExitCode = mData.mExitCode;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getName(com::Utf8Str &aName)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ aName = mData.mProcess.mName;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getPID(ULONG *aPID)
+{
+ LogFlowThisFuncEnter();
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ *aPID = mData.mPID;
+
+ return S_OK;
+}
+
+HRESULT GuestProcess::getStatus(ProcessStatus_T *aStatus)
+{
+ LogFlowThisFuncEnter();
+
+ *aStatus = i_getStatus();
+
+ return S_OK;
+}
+
+// private methods
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Entry point for guest side process callbacks.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCb Host callback data.
+ */
+int GuestProcess::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
+#ifdef DEBUG
+ LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
+ mData.mPID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
+#endif
+
+ int vrc;
+ switch (pCbCtx->uMessage)
+ {
+ case GUEST_MSG_DISCONNECTED:
+ {
+ vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_STATUS:
+ {
+ vrc = i_onProcessStatusChange(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_OUTPUT:
+ {
+ vrc = i_onProcessOutput(pCbCtx, pSvcCb);
+ break;
+ }
+
+ case GUEST_MSG_EXEC_INPUT_STATUS:
+ {
+ vrc = i_onProcessInputStatus(pCbCtx, pSvcCb);
+ break;
+ }
+
+ default:
+ /* Silently ignore not implemented functions. */
+ vrc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+#ifdef DEBUG
+ LogFlowFuncLeaveRC(vrc);
+#endif
+ return vrc;
+}
+
+/**
+ * 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 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::i_checkPID(uint32_t uPID)
+{
+ int vrc = VINF_SUCCESS;
+
+ /* Was there a PID assigned yet? */
+ if (mData.mPID)
+ {
+ if (RT_UNLIKELY(mData.mPID != uPID))
+ {
+ 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));
+ vrc = VERR_NOT_FOUND;
+ }
+ }
+
+ return vrc;
+}
+
+/**
+ * Returns the current process status.
+ *
+ * @returns Current process status.
+ *
+ * @note Takes the read lock.
+ */
+ProcessStatus_T GuestProcess::i_getStatus(void)
+{
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ return mData.mStatus;
+}
+
+/**
+ * Converts a given guest process error to a string.
+ *
+ * @returns Error as a string.
+ * @param rcGuest Guest process error to return string for.
+ * @param pcszWhat Hint of what was involved when the error occurred.
+ */
+/* static */
+Utf8Str GuestProcess::i_guestErrorToString(int rcGuest, const char *pcszWhat)
+{
+ AssertPtrReturn(pcszWhat, "");
+
+ Utf8Str strErr;
+ switch (rcGuest)
+ {
+#define CASE_MSG(a_iRc, ...) \
+ case a_iRc: strErr.printf(__VA_ARGS__); break;
+
+ CASE_MSG(VERR_FILE_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat); /* This is the most likely error. */
+ CASE_MSG(VERR_PATH_NOT_FOUND, tr("No such file or directory \"%s\" on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_VM_HANDLE, tr("VMM device is not available (is the VM running?)"));
+ CASE_MSG(VERR_HGCM_SERVICE_NOT_FOUND, tr("The guest execution service is not available"));
+ CASE_MSG(VERR_BAD_EXE_FORMAT, tr("The file \"%s\" is not an executable format on guest"), pcszWhat);
+ CASE_MSG(VERR_AUTHENTICATION_FAILURE, tr("The user \"%s\" was not able to logon on guest"), pcszWhat);
+ CASE_MSG(VERR_INVALID_NAME, tr("The file \"%s\" is an invalid name"), pcszWhat);
+ CASE_MSG(VERR_TIMEOUT, tr("The guest did not respond within time"));
+ CASE_MSG(VERR_CANCELLED, tr("The execution operation for \"%s\" was canceled"), pcszWhat);
+ CASE_MSG(VERR_GSTCTL_MAX_CID_OBJECTS_REACHED, tr("Maximum number of concurrent guest processes has been reached"));
+ CASE_MSG(VERR_NOT_FOUND, tr("The guest execution service is not ready (yet)"));
+ default:
+ strErr.printf(tr("Error %Rrc for guest process \"%s\" occurred\n"), rcGuest, pcszWhat);
+ break;
+#undef CASE_MSG
+ }
+
+ return strErr;
+}
+
+/**
+ * Translates a process status to a human readable string.
+ *
+ * @returns Process status as a string.
+ * @param enmStatus Guest process status to return string for.
+ */
+/* static */
+Utf8Str GuestProcess::i_statusToString(ProcessStatus_T enmStatus)
+{
+ switch (enmStatus)
+ {
+ case ProcessStatus_Starting:
+ return "starting";
+ case ProcessStatus_Started:
+ return "started";
+ case ProcessStatus_Paused:
+ return "paused";
+ case ProcessStatus_Terminating:
+ return "terminating";
+ case ProcessStatus_TerminatedNormally:
+ return "successfully terminated";
+ case ProcessStatus_TerminatedSignal:
+ return "terminated by signal";
+ case ProcessStatus_TerminatedAbnormally:
+ return "abnormally aborted";
+ case ProcessStatus_TimedOutKilled:
+ return "timed out";
+ case ProcessStatus_TimedOutAbnormally:
+ return "timed out, hanging";
+ case ProcessStatus_Down:
+ return "killed";
+ case ProcessStatus_Error:
+ return "error";
+ default:
+ break;
+ }
+
+ AssertFailed(); /* Should never happen! */
+ return "unknown";
+}
+
+/**
+ * Returns @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ *
+ * @return bool @c true if the passed in error code indicates an error which came
+ * from the guest side, or @c false if not.
+ * @param rc Error code to check.
+ */
+/* static */
+bool GuestProcess::i_isGuestError(int rc)
+{
+ return ( rc == VERR_GSTCTL_GUEST_ERROR
+ || rc == VERR_GSTCTL_PROCESS_EXIT_CODE);
+}
+
+/**
+ * Returns whether the guest process is alive (i.e. running) or not.
+ *
+ * @returns \c true if alive and running, or \c false if not.
+ */
+inline bool GuestProcess::i_isAlive(void)
+{
+ return ( mData.mStatus == ProcessStatus_Started
+ || mData.mStatus == ProcessStatus_Paused
+ || mData.mStatus == ProcessStatus_Terminating);
+}
+
+/**
+ * Returns whether the guest process has ended (i.e. terminated) or not.
+ *
+ * @returns \c true if ended, or \c false if not.
+ */
+inline bool GuestProcess::i_hasEnded(void)
+{
+ 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);
+}
+
+/**
+ * Called when the guest side of the process has been disconnected (closed, terminated, +++).
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ int vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current input status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+ /* pCallback is optional. */
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_INPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[4], &dataCb.uProcessed);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ 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;
+ }
+
+ if (inputStatus != ProcessInputStatus_Undefined)
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ /* 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;
+}
+
+/**
+ * Notifies of an I/O operation of the guest process.
+ *
+ * @returns VERR_NOT_IMPLEMENTED -- not implemented yet.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * Sets (reports) the current running status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_STATUS dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uStatus);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &dataCb.pvData, &dataCb.cbData);
+ AssertRCReturn(vrc, vrc);
+
+ LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
+ dataCb.uPID, dataCb.uStatus, dataCb.uFlags));
+
+ vrc = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ int procRc = VINF_SUCCESS;
+
+ switch (dataCb.uStatus)
+ {
+ case PROC_STS_STARTED:
+ {
+ procStatus = ProcessStatus_Started;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mPID = dataCb.uPID; /* Set the process PID. */
+ break;
+ }
+
+ case PROC_STS_TEN:
+ {
+ procStatus = ProcessStatus_TerminatedNormally;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the exit code. */
+ break;
+ }
+
+ case PROC_STS_TES:
+ {
+ procStatus = ProcessStatus_TerminatedSignal;
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+ mData.mExitCode = dataCb.uFlags; /* Contains the signal. */
+ break;
+ }
+
+ case PROC_STS_TEA:
+ {
+ procStatus = ProcessStatus_TerminatedAbnormally;
+ break;
+ }
+
+ case PROC_STS_TOK:
+ {
+ procStatus = ProcessStatus_TimedOutKilled;
+ break;
+ }
+
+ case PROC_STS_TOA:
+ {
+ procStatus = ProcessStatus_TimedOutAbnormally;
+ break;
+ }
+
+ case PROC_STS_DWN:
+ {
+ procStatus = ProcessStatus_Down;
+ break;
+ }
+
+ case PROC_STS_ERROR:
+ {
+ procRc = dataCb.uFlags; /* mFlags contains the IPRT error sent from the guest. */
+ procStatus = ProcessStatus_Error;
+ break;
+ }
+
+ case PROC_STS_UNDEFINED:
+ default:
+ {
+ /* Silently skip this request. */
+ procStatus = ProcessStatus_Undefined;
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Got rc=%Rrc, procSts=%RU32, procRc=%Rrc\n",
+ vrc, procStatus, procRc));
+
+ /* Set the process status. */
+ int vrc2 = i_setProcessStatus(procStatus, procRc);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current output status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param pCbCtx Host callback context.
+ * @param pSvcCbData Host callback data.
+ */
+int GuestProcess::i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
+{
+ RT_NOREF(pCbCtx);
+ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
+
+ if (pSvcCbData->mParms < 5)
+ return VERR_INVALID_PARAMETER;
+
+ CALLBACKDATA_PROC_OUTPUT dataCb;
+ /* pSvcCb->mpaParms[0] always contains the context ID. */
+ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uPID);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uHandle);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[3], &dataCb.uFlags);
+ AssertRCReturn(vrc, vrc);
+ vrc = HGCMSvcGetPv(&pSvcCbData->mpaParms[4], &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 = i_checkPID(dataCb.uPID);
+ if (RT_SUCCESS(vrc))
+ {
+ com::SafeArray<BYTE> data((size_t)dataCb.cbData);
+ if (dataCb.cbData)
+ data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData);
+
+ ::FireGuestProcessOutputEvent(mEventSource, mSession, this,
+ mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data));
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onUnregister
+ */
+int GuestProcess::i_onUnregister(void)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ /*
+ * Note: The event source stuff holds references to this object,
+ * so make sure that this is cleaned up *before* calling uninit().
+ */
+ if (!mEventSource.isNull())
+ {
+ mEventSource->UnregisterListener(mLocalListener);
+
+ mLocalListener.setNull();
+ unconst(mEventSource).setNull();
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * @copydoc GuestObject::i_onSessionStatusChange
+ */
+int GuestProcess::i_onSessionStatusChange(GuestSessionStatus_T enmSessionStatus)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc = VINF_SUCCESS;
+
+ /* If the session now is in a terminated state, set the process status
+ * to "down", as there is not much else we can do now. */
+ if (GuestSession::i_isTerminated(enmSessionStatus))
+ {
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ vrc = i_setProcessStatus(ProcessStatus_Down, 0 /* rc, ignored */);
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Reads data from a guest file.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Internal file handle to use for reading.
+ * @param uSize Size (in bytes) to read.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the read data on success.
+ * @param cbData Size (in bytes) of \a pvData on input.
+ * @param pcbRead Where to return to size (in bytes) read on success.
+ * Optional.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS,
+ void *pvData, size_t cbData, uint32_t *pcbRead, int *prcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, prcGuest=%p\n",
+ mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, prcGuest));
+ AssertReturn(uSize, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if ( mData.mStatus != ProcessStatus_Started
+ /* Skip reading if the process wasn't started with the appropriate
+ * flags. */
+ || ( ( uHandle == GUEST_PROC_OUT_H_STDOUT
+ || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED)
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut))
+ || ( uHandle == GUEST_PROC_OUT_H_STDERR
+ && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr))
+ )
+ {
+ if (pcbRead)
+ *pcbRead = 0;
+ if (prcGuest)
+ *prcGuest = VINF_SUCCESS;
+ return VINF_SUCCESS; /* Nothing to read anymore. */
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that the process status
+ * change arrives *after* the output event, e.g. if this was the last output
+ * block being read and the process will report status "terminate".
+ * So just skip checking for process status change and only wait for the
+ * output event.
+ */
+ if (mSession->i_getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ if (RT_SUCCESS(vrc))
+ {
+ VBOXHGCMSVCPARM paParms[8];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+ HGCMSvcSetU32(&paParms[i++], uHandle);
+ HGCMSvcSetU32(&paParms[i++], 0 /* Flags, none set yet. */);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_GET_OUTPUT, i, paParms);
+ }
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForOutput(pEvent, uHandle, uTimeoutMS,
+ pvData, cbData, pcbRead);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Sets (reports) the current (overall) status of the guest process.
+ *
+ * @returns VBox status code.
+ * @param procStatus Guest process status to set.
+ * @param procRc Guest process result code to set.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_setProcessStatus(ProcessStatus_T procStatus, int procRc)
+{
+ LogFlowThisFuncEnter();
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, procRc=%Rrc\n",
+ mData.mStatus, procStatus, procRc));
+
+ 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.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));
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != procStatus) /* Was there a process status change? */
+ {
+ mData.mStatus = procStatus;
+ mData.mLastError = procRc;
+
+ ComObjPtr<VirtualBoxErrorInfo> errorInfo;
+ HRESULT hrc = errorInfo.createObject();
+ ComAssertComRC(hrc);
+ if (RT_FAILURE(mData.mLastError))
+ {
+ hrc = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError,
+ COM_IIDOF(IGuestProcess), getComponentName(),
+ i_guestErrorToString(mData.mLastError, mData.mProcess.mExecutable.c_str()));
+ ComAssertComRC(hrc);
+ }
+
+ /* 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"));
+ vrc = cancelWaitEvents();
+ }
+#endif
+ }
+
+ return vrc;
+}
+
+/**
+ * Starts the process on the guest.
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_startProcess(uint32_t cMsTimeout, int *prcGuest)
+{
+ LogFlowThisFunc(("cMsTimeout=%RU32, procExe=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n",
+ cMsTimeout, mData.mProcess.mExecutable.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags,
+ mSession->i_getId()));
+
+ /* Wait until the caller function (if kicked off by a thread)
+ * has returned and continue operation. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ mData.mStatus = ProcessStatus_Starting;
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ vrc = i_startProcessInner(cMsTimeout, alock, pEvent, prcGuest);
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Helper function to start a process on the guest. Do not call directly!
+ *
+ * @returns VBox status code.
+ * @param cMsTimeout Timeout (in ms) to wait for starting the process.
+ * @param rLock Write lock to use for serialization.
+ * @param pEvent Event to use for notifying waiters.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcess::i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *prcGuest)
+{
+ GuestSession *pSession = mSession;
+ AssertPtr(pSession);
+ uint32_t const uProtocol = pSession->i_getProtocolVersion();
+
+ const GuestCredentials &sessionCreds = pSession->i_getCredentials();
+
+ /* Prepare arguments. */
+ size_t cArgs = mData.mProcess.mArguments.size();
+ if (cArgs >= 128*1024)
+ return VERR_BUFFER_OVERFLOW;
+
+ size_t cbArgs = 0;
+ char *pszArgs = NULL;
+ int vrc = VINF_SUCCESS;
+ if (cArgs)
+ {
+ char const **papszArgv = (char const **)RTMemAlloc((cArgs + 1) * sizeof(papszArgv[0]));
+ AssertReturn(papszArgv, VERR_NO_MEMORY);
+
+ for (size_t i = 0; i < cArgs; i++)
+ {
+ papszArgv[i] = mData.mProcess.mArguments[i].c_str();
+ AssertPtr(papszArgv[i]);
+ }
+ papszArgv[cArgs] = NULL;
+
+ Guest *pGuest = mSession->i_getParent();
+ AssertPtr(pGuest);
+
+ const uint64_t fGuestControlFeatures0 = pGuest->i_getGuestControlFeatures0();
+
+ /* If the Guest Additions don't support using argv[0] correctly (< 6.1.x), don't supply it. */
+ if (!(fGuestControlFeatures0 & VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0))
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ else /* ... else send the whole argv, including argv[0]. */
+ vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+
+ RTMemFree(papszArgv);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ /* Note! No direct returns after this. */
+ }
+
+ /* Calculate arguments size (in bytes). */
+ AssertPtr(pszArgs);
+ cbArgs = strlen(pszArgs) + 1; /* Include terminating zero. */
+
+ /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */
+ size_t cbEnvBlock = 0; /* Shut up MSVC. */
+ char *pszzEnvBlock = NULL; /* Ditto. */
+ vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock);
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(cbEnvBlock > 0);
+ cbEnvBlock--;
+ AssertPtr(pszzEnvBlock);
+
+ /* Prepare HGCM call. */
+ VBOXHGCMSVCPARM paParms[16];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetRTCStr(&paParms[i++], mData.mProcess.mExecutable);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mFlags);
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)mData.mProcess.mArguments.size());
+ HGCMSvcSetPv(&paParms[i++], pszArgs, (uint32_t)cbArgs);
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mEnvironmentChanges.count());
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)cbEnvBlock);
+ HGCMSvcSetPv(&paParms[i++], pszzEnvBlock, (uint32_t)cbEnvBlock);
+ if (uProtocol < 2)
+ {
+ /* In protocol v1 (VBox < 4.3) the credentials were part of the execution
+ * call. In newer protocols these credentials are part of the opened guest
+ * session, so not needed anymore here. */
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mUser);
+ HGCMSvcSetRTCStr(&paParms[i++], sessionCreds.mPassword);
+ }
+ /*
+ * 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)
+ HGCMSvcSetU32(&paParms[i++], UINT32_MAX /* Infinite timeout */);
+ else
+ HGCMSvcSetU32(&paParms[i++], mData.mProcess.mTimeoutMS);
+ if (uProtocol >= 2)
+ {
+ HGCMSvcSetU32(&paParms[i++], 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. */
+ HGCMSvcSetU32(&paParms[i++], 1);
+ /* The actual CPU affinity blocks. */
+ HGCMSvcSetPv(&paParms[i++], (void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));
+ }
+
+ rLock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_CMD, i, paParms);
+ if (RT_FAILURE(vrc))
+ {
+ int vrc2 = i_setProcessStatus(ProcessStatus_Error, vrc);
+ AssertRC(vrc2);
+ }
+
+ mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock);
+ }
+
+ RTStrFree(pszArgs);
+
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, cMsTimeout,
+ NULL /* Process status */, prcGuest);
+ return vrc;
+}
+
+/**
+ * Starts the process asynchronously (via worker thread) on the guest.
+ *
+ * @returns VBox status code.
+ */
+int GuestProcess::i_startProcessAsync(void)
+{
+ LogFlowThisFuncEnter();
+
+ /* Create the task: */
+ GuestProcessStartTask *pTask = NULL;
+ try
+ {
+ pTask = new GuestProcessStartTask(this);
+ }
+ catch (std::bad_alloc &)
+ {
+ LogFlowThisFunc(("out of memory\n"));
+ return VERR_NO_MEMORY;
+ }
+ AssertReturnStmt(pTask->i_isOk(), delete pTask, E_FAIL); /* cannot fail for GuestProcessStartTask. */
+ LogFlowThisFunc(("Successfully created GuestProcessStartTask object\n"));
+
+ /* Start the thread (always consumes the task): */
+ HRESULT hrc = pTask->createThread();
+ pTask = NULL;
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+ LogFlowThisFunc(("Failed to create thread for GuestProcessStartTask\n"));
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Thread task which does the asynchronous starting of a guest process.
+ *
+ * @returns VBox status code.
+ * @param pTask Process start task (context) to process.
+ */
+/* static */
+int GuestProcess::i_startProcessThreadTask(GuestProcessStartTask *pTask)
+{
+ LogFlowFunc(("pTask=%p\n", pTask));
+
+ const ComObjPtr<GuestProcess> pProcess(pTask->i_process());
+ Assert(!pProcess.isNull());
+
+ AutoCaller autoCaller(pProcess);
+ if (FAILED(autoCaller.rc()))
+ return VERR_COM_UNEXPECTED;
+
+ int vrc = pProcess->i_startProcess(30 * 1000 /* 30s timeout */, NULL /* Guest rc, ignored */);
+ /* Nothing to do here anymore. */
+
+ LogFlowFunc(("pProcess=%p, vrc=%Rrc\n", (GuestProcess *)pProcess, vrc));
+ return vrc;
+}
+
+/**
+ * Terminates a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VWRN_INVALID_STATE if process not in running state (anymore).
+ * @retval VERR_NOT_SUPPORTED if process termination is not supported on the guest.
+ * @param uTimeoutMS Timeout (in ms) to wait for process termination.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_terminateProcess(uint32_t uTimeoutMS, int *prcGuest)
+{
+ /* prcGuest is optional. */
+ LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS));
+
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ int vrc = VINF_SUCCESS;
+
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n",
+ mData.mStatus));
+ vrc = VWRN_INVALID_STATE;
+ }
+ else
+ {
+ AssertPtr(mSession);
+ /* Note: VBox < 4.3 (aka protocol version 1) does not
+ * support this, so just skip. */
+ if (mSession->i_getProtocolVersion() < 2)
+ vrc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(vrc))
+ {
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ VBOXHGCMSVCPARM paParms[4];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ vrc = sendMessage(HOST_MSG_EXEC_TERMINATE, i, paParms);
+ if (RT_SUCCESS(vrc))
+ vrc = i_waitForStatusChange(pEvent, uTimeoutMS,
+ NULL /* ProcessStatus */, prcGuest);
+ unregisterWaitEvent(pEvent);
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts given process status / flags and wait flag combination
+ * to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ * @param oldStatus Old process status to use for conversion.
+ * @param newStatus New process status to use for conversion.
+ * @param uProcFlags Process flags to use for conversion.
+ * @param uProtocol Guest Control protocol version to use for conversion.
+ */
+/* static */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResultEx(uint32_t fWaitFlags,
+ ProcessStatus_T oldStatus, ProcessStatus_T newStatus,
+ uint32_t uProcFlags, uint32_t uProtocol)
+{
+ ProcessWaitResult_T waitResult = ProcessWaitResult_None;
+
+ 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_TimedOutKilled:
+ case ProcessStatus_TimedOutAbnormally:
+ /* Dito. */
+ waitResult = ProcessWaitResult_Timeout;
+ break;
+
+ case ProcessStatus_Started:
+ switch (oldStatus)
+ {
+ 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));
+ if (fWaitFlags & ProcessWaitForFlag_Start)
+ waitResult = ProcessWaitResult_Start;
+ break;
+ }
+ break;
+
+ case ProcessStatus_Error:
+ /* Nothing to wait for anymore. */
+ waitResult = ProcessWaitResult_Error;
+ break;
+
+ case ProcessStatus_Undefined:
+ case ProcessStatus_Starting:
+ case ProcessStatus_Terminating:
+ case ProcessStatus_Paused:
+ /* No result available yet, leave wait
+ * flags untouched. */
+ break;
+#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
+ case ProcessStatus_32BitHack: AssertFailedBreak(); /* (compiler warnings) */
+#endif
+ }
+
+ if (newStatus == ProcessStatus_Started)
+ {
+ /*
+ * Filter out waits which are *not* supported using
+ * older guest control Guest Additions.
+ *
+ */
+ /** @todo ProcessWaitForFlag_Std* flags are not implemented yet. */
+ if (uProtocol < 99) /* See @todo above. */
+ {
+ 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;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n",
+ oldStatus, newStatus, fWaitFlags, waitResult));
+#endif
+ return waitResult;
+}
+
+/**
+ * Converts given wait flags to an overall process wait result.
+ *
+ * @returns Overall process wait result.
+ * @param fWaitFlags Process wait flags to use for conversion.
+ */
+ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags)
+{
+ AssertPtr(mSession);
+ return GuestProcess::i_waitFlagsToResultEx(fWaitFlags,
+ mData.mStatus /* oldStatus */, mData.mStatus /* newStatus */,
+ mData.mProcess.mFlags, mSession->i_getProtocolVersion());
+}
+
+/**
+ * Waits for certain events of the guest process.
+ *
+ * @returns VBox status code.
+ * @param fWaitFlags Process wait flags to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param waitResult Where to return the process wait result on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ * @note Takes the read lock.
+ */
+int GuestProcess::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS,
+ ProcessWaitResult_T &waitResult, int *prcGuest)
+{
+ AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, prcGuest=%p\n",
+ fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, prcGuest));
+
+ /* Did some error occur before? Then skip waiting and return. */
+ ProcessStatus_T curStatus = mData.mStatus;
+ if (curStatus == ProcessStatus_Error)
+ {
+ waitResult = ProcessWaitResult_Error;
+ AssertMsg(RT_FAILURE(mData.mLastError),
+ ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
+ if (prcGuest)
+ *prcGuest = mData.mLastError; /* Return last set error. */
+ LogFlowThisFunc(("Process is in error state (rcGuest=%Rrc)\n", mData.mLastError));
+ return VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ waitResult = i_waitFlagsToResult(fWaitFlags);
+
+ /* No waiting needed? Return immediately using the last set error. */
+ if (waitResult != ProcessWaitResult_None)
+ {
+ if (prcGuest)
+ *prcGuest = mData.mLastError; /* Return last set error (if any). */
+ LogFlowThisFunc(("Nothing to wait for (rcGuest=%Rrc)\n", mData.mLastError));
+ return RT_SUCCESS(mData.mLastError) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ /* Adjust timeout. Passing 0 means RT_INDEFINITE_WAIT. */
+ if (!uTimeoutMS)
+ uTimeoutMS = RT_INDEFINITE_WAIT;
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ 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 = i_waitForStatusChange(pEvent,
+ uTimeoutMS == RT_INDEFINITE_WAIT
+ ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS,
+ &newStatus, prcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ alock.acquire();
+
+ waitResult = i_waitFlagsToResultEx(fWaitFlags, curStatus, newStatus,
+ mData.mProcess.mFlags, mSession->i_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;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pInputStatus Where to return the process input status on success.
+ * @param pcbProcessed Where to return the processed input (in bytes) on success.
+ */
+int GuestProcess::i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
+ ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed)
+{
+ RT_NOREF(uHandle);
+ 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());
+
+ 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;
+ }
+
+ LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n",
+ pEvent, uHandle, vrc));
+ return vrc;
+}
+
+/**
+ * Waits for a guest process input notification.
+ *
+ * @returns VBox status code.
+ * @param pEvent Wait event to use for waiting.
+ * @param uHandle Guest process file handle to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pvData Where to store the guest process output on success.
+ * @param cbData Size (in bytes) of \a pvData.
+ * @param pcbRead Where to return the size (in bytes) read.
+ */
+int GuestProcess::i_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;
+ }
+
+ } while (vrc == VINF_SUCCESS);
+
+ if ( vrc != VINF_SUCCESS
+ && pcbRead)
+ {
+ *pcbRead = 0;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process status change.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param pEvent Guest wait event to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param pProcessStatus Where to return the process status on success.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned.
+ */
+int GuestProcess::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
+ ProcessStatus_T *pProcessStatus, int *prcGuest)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ /* pProcessStatus is optional. */
+ /* prcGuest 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, rcGuest=%RI32 (%Rrc)\n",
+ procStatus, lGuestRc, lGuestRc));
+
+ if (RT_FAILURE((int)lGuestRc))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ if (prcGuest)
+ *prcGuest = (int)lGuestRc;
+ }
+ /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */
+ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest)
+ *prcGuest = pEvent->GuestResult();
+ Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc);
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+#if 0 /* Unused */
+/* static */
+bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult, ProcessStatus_T procStatus, uint32_t uProtocol)
+{
+ RT_NOREF(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;
+}
+#endif /* unused */
+
+/**
+ * Writes input data to a guest process.
+ *
+ * @returns VBox status code.
+ * @retval VERR_GSTCTL_GUEST_ERROR when an error from the guest side has been received.
+ * @param uHandle Guest process file handle to write to.
+ * @param uFlags Input flags of type PRocessInputFlag_XXX.
+ * @param pvData Data to write to the guest process.
+ * @param cbData Size (in bytes) of \a pvData to write.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param puWritten Where to return the size (in bytes) written. Optional.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ *
+ * @note Takes the write lock.
+ */
+int GuestProcess::i_writeData(uint32_t uHandle, uint32_t uFlags,
+ void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *prcGuest)
+{
+ LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, prcGuest=%p\n",
+ mData.mPID, uHandle, uFlags, pvData, cbData, uTimeoutMS, puWritten, prcGuest));
+ /* All is optional. There can be 0 byte writes. */
+ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+ if (mData.mStatus != ProcessStatus_Started)
+ {
+ if (puWritten)
+ *puWritten = 0;
+ if (prcGuest)
+ *prcGuest = VINF_SUCCESS;
+ return VINF_SUCCESS; /* Not available for writing (anymore). */
+ }
+
+ int vrc;
+
+ GuestWaitEvent *pEvent = NULL;
+ GuestEventTypes eventTypes;
+ try
+ {
+ /*
+ * On Guest Additions < 4.3 there is no guarantee that the process status
+ * change arrives *after* the input event, e.g. if this was the last input
+ * block being written and the process will report status "terminate".
+ * So just skip checking for process status change and only wait for the
+ * input event.
+ */
+ if (mSession->i_getProtocolVersion() >= 2)
+ eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
+ eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
+
+ vrc = registerWaitEvent(eventTypes, &pEvent);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ VBOXHGCMSVCPARM paParms[5];
+ int i = 0;
+ HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
+ HGCMSvcSetU32(&paParms[i++], mData.mPID);
+ HGCMSvcSetU32(&paParms[i++], uFlags);
+ HGCMSvcSetPv(&paParms[i++], pvData, (uint32_t)cbData);
+ HGCMSvcSetU32(&paParms[i++], (uint32_t)cbData);
+
+ alock.release(); /* Drop the write lock before sending. */
+
+ uint32_t cbProcessed = 0;
+ vrc = sendMessage(HOST_MSG_EXEC_SET_INPUT, i, paParms);
+ if (RT_SUCCESS(vrc))
+ {
+ ProcessInputStatus_T inputStatus;
+ vrc = i_waitForInputNotify(pEvent, uHandle, uTimeoutMS,
+ &inputStatus, &cbProcessed);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo Set rcGuest. */
+
+ if (puWritten)
+ *puWritten = cbProcessed;
+ }
+ /** @todo Error handling. */
+ }
+
+ unregisterWaitEvent(pEvent);
+
+ LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n",
+ cbProcessed, vrc));
+ return vrc;
+}
+
+// implementation of public methods
+/////////////////////////////////////////////////////////////////////////////
+
+HRESULT GuestProcess::read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ if (aToRead == 0)
+ return setError(E_INVALIDARG, tr("The size to read is zero"));
+
+ LogFlowThisFuncEnter();
+
+ aData.resize(aToRead);
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbRead;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ if (aData.size() != cbRead)
+ aData.resize(cbRead);
+ }
+ else
+ {
+ aData.resize(0);
+
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Reading %RU32 bytes from guest process handle %RU32 failed: %s", "", aToRead),
+ aToRead, aHandle, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Reading from guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("rc=%Rrc, cbRead=%RU32\n", vrc, cbRead));
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::terminate()
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */, &vrcGuest);
+
+ switch (vrc)
+ {
+ case VINF_SUCCESS:
+ /* Nothing to do here, all good. */
+ break;
+
+ case VWRN_INVALID_STATE:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, VWRN_INVALID_STATE,
+ tr("Guest process is not in '%s' state anymore (current is in '%s')"),
+ GuestProcess::i_statusToString(ProcessStatus_Started).c_str(),
+ GuestProcess::i_statusToString(i_getStatus()).c_str());
+ break;
+ }
+
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Terminating guest process failed: %s"),
+ GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+
+ case VERR_NOT_SUPPORTED:
+ {
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
+ tr("Terminating guest process \"%s\" (PID %RU32) not supported by installed Guest Additions"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID);
+ break;
+ }
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Terminating guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+
+ /* Note: Also could be VWRN_INVALID_STATE from i_terminateProcess().
+ * In such a case we have to keep the process in our list in order to fullfill any upcoming responses / requests. */
+ if (vrc == VINF_SUCCESS)
+ {
+ /* Remove process from guest session list. Now only API clients
+ * still can hold references to it. */
+ AssertPtr(mSession);
+ int vrc2 = mSession->i_processUnregister(this);
+ if (RT_SUCCESS(vrc))
+ vrc = vrc2;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ /* Validate flags: */
+ static ULONG const s_fValidFlags = ProcessWaitForFlag_None | ProcessWaitForFlag_Start | ProcessWaitForFlag_Terminate
+ | ProcessWaitForFlag_StdIn | ProcessWaitForFlag_StdOut | ProcessWaitForFlag_StdErr;
+ if (aWaitFor & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aWaitFor, aWaitFor & ~s_fValidFlags);
+
+ /*
+ * Note: Do not hold any locks here while waiting!
+ */
+ HRESULT hrc = S_OK;
+
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ ProcessWaitResult_T waitResult;
+ int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ *aReason = waitResult;
+ }
+ else
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest, tr("Waiting for guest process (flags %#x) failed: %s"),
+ aWaitFor, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ case VERR_TIMEOUT:
+ *aReason = ProcessWaitResult_Timeout;
+ break;
+
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor,
+ ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
+{
+ uint32_t fWaitFor = ProcessWaitForFlag_None;
+ for (size_t i = 0; i < aWaitFor.size(); i++)
+ fWaitFor |= aWaitFor[i];
+
+ return WaitFor(fWaitFor, aTimeoutMS, aReason);
+}
+
+HRESULT GuestProcess::write(ULONG aHandle, ULONG aFlags, const std::vector<BYTE> &aData,
+ ULONG aTimeoutMS, ULONG *aWritten)
+{
+ static ULONG const s_fValidFlags = ProcessInputFlag_None | ProcessInputFlag_EndOfFile;
+ if (aFlags & ~s_fValidFlags)
+ return setErrorBoth(E_INVALIDARG, VERR_INVALID_FLAGS, tr("Flags value %#x, invalid: %#x"),
+ aFlags, aFlags & ~s_fValidFlags);
+
+ AutoCaller autoCaller(this);
+ if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+ LogFlowThisFuncEnter();
+
+ HRESULT hrc = S_OK;
+
+ uint32_t cbWritten;
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+ uint32_t cbData = (uint32_t)aData.size();
+ void *pvData = cbData > 0 ? (void *)&aData.front() : NULL;
+ int vrc = i_writeData(aHandle, aFlags, pvData, cbData, aTimeoutMS, &cbWritten, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ {
+ switch (vrc)
+ {
+ case VERR_GSTCTL_GUEST_ERROR:
+ {
+ GuestErrorInfo ge(GuestErrorInfo::Type_Process, vrcGuest, mData.mProcess.mExecutable.c_str());
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrcGuest,
+ tr("Writing %RU32 bytes (flags %#x) to guest process failed: %s", "", cbData),
+ cbData, aFlags, GuestBase::getErrorAsString(ge).c_str());
+ break;
+ }
+ default:
+ hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Writing to guest process \"%s\" (PID %RU32) failed: %Rrc"),
+ mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, cbWritten));
+
+ *aWritten = (ULONG)cbWritten;
+
+ LogFlowFuncLeaveRC(vrc);
+ return hrc;
+}
+
+HRESULT GuestProcess::writeArray(ULONG aHandle, const std::vector<ProcessInputFlag_T> &aFlags,
+ const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
+{
+ LogFlowThisFuncEnter();
+
+ ULONG fWrite = ProcessInputFlag_None;
+ for (size_t i = 0; i < aFlags.size(); i++)
+ fWrite |= aFlags[i];
+
+ return write(aHandle, fWrite, aData, aTimeoutMS, aWritten);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GuestProcessTool::GuestProcessTool(void)
+ : pSession(NULL),
+ pProcess(NULL)
+{
+}
+
+GuestProcessTool::~GuestProcessTool(void)
+{
+ uninit();
+}
+
+/**
+ * Initializes and starts a process tool on the guest.
+ *
+ * @returns VBox status code.
+ * @param pGuestSession Guest session the process tools should be started in.
+ * @param startupInfo Guest process startup info to use for starting.
+ * @param fAsync Whether to start asynchronously or not.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
+ bool fAsync, int *prcGuest)
+{
+ LogFlowThisFunc(("pGuestSession=%p, exe=%s, fAsync=%RTbool\n",
+ pGuestSession, startupInfo.mExecutable.c_str(), fAsync));
+
+ AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
+ Assert(startupInfo.mArguments[0] == startupInfo.mExecutable);
+
+ pSession = pGuestSession;
+ mStartupInfo = startupInfo;
+
+ /* Make sure the process is hidden. */
+ mStartupInfo.mFlags |= ProcessCreateFlag_Hidden;
+
+ int vrc = pSession->i_processCreateEx(mStartupInfo, pProcess);
+ if (RT_SUCCESS(vrc))
+ {
+ int vrcGuest = VINF_SUCCESS;
+ vrc = fAsync
+ ? pProcess->i_startProcessAsync()
+ : pProcess->i_startProcess(30 * 1000 /* 30s timeout */, &vrcGuest);
+
+ if ( RT_SUCCESS(vrc)
+ && !fAsync
+ && RT_FAILURE(vrcGuest)
+ )
+ {
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+ }
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Unitializes a guest process tool by terminating it on the guest.
+ */
+void GuestProcessTool::uninit(void)
+{
+ /* Make sure the process is terminated and unregistered from the guest session. */
+ int vrcGuestIgnored;
+ terminate(30 * 1000 /* 30s timeout */, &vrcGuestIgnored);
+
+ /* Unregister the process from the process (and the session's object) list. */
+ if ( pSession
+ && pProcess)
+ pSession->i_processUnregister(pProcess);
+
+ /* Release references. */
+ pProcess.setNull();
+ pSession.setNull();
+}
+
+/**
+ * Gets the current guest process stream block.
+ *
+ * @returns VBox status code.
+ * @param uHandle Guest process file handle to get current block for.
+ * @param strmBlock Where to return the stream block on success.
+ */
+int GuestProcessTool::getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock)
+{
+ const GuestProcessStream *pStream = NULL;
+ if (uHandle == GUEST_PROC_OUT_H_STDOUT)
+ pStream = &mStdOut;
+ else if (uHandle == GUEST_PROC_OUT_H_STDERR)
+ pStream = &mStdErr;
+
+ if (!pStream)
+ return VERR_INVALID_PARAMETER;
+
+ /** @todo Why not using pStream down below and hardcode to mStdOut? */
+
+ int vrc;
+ do
+ {
+ /* Try parsing the data to see if the current block is complete. */
+ vrc = mStdOut.ParseBlock(strmBlock);
+ if (strmBlock.GetCount())
+ break;
+ } while (RT_SUCCESS(vrc));
+
+ LogFlowThisFunc(("rc=%Rrc, %RU64 pairs\n",
+ vrc, strmBlock.GetCount()));
+ return vrc;
+}
+
+/**
+ * Returns the result code from an ended guest process tool.
+ *
+ * @returns Result code from guest process tool.
+ */
+int GuestProcessTool::getRc(void) const
+{
+ LONG exitCode = -1;
+ HRESULT hr = pProcess->COMGETTER(ExitCode(&exitCode));
+ AssertComRC(hr);
+
+ return GuestProcessTool::exitCodeToRc(mStartupInfo, exitCode);
+}
+
+/**
+ * Returns whether a guest process tool is still running or not.
+ *
+ * @returns \c true if running, or \c false if not.
+ */
+bool GuestProcessTool::isRunning(void)
+{
+ AssertReturn(!pProcess.isNull(), false);
+
+ ProcessStatus_T procStatus = ProcessStatus_Undefined;
+ HRESULT hr = pProcess->COMGETTER(Status(&procStatus));
+ AssertComRC(hr);
+
+ if ( procStatus == ProcessStatus_Started
+ || procStatus == ProcessStatus_Paused
+ || procStatus == ProcessStatus_Terminating)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the tool has been run correctly or not, based on it's internal process
+ * status and reported exit status.
+ *
+ * @return @c true if the tool has been run correctly (exit status 0), or @c false if some error
+ * occurred (exit status <> 0 or wrong process state).
+ */
+bool GuestProcessTool::isTerminatedOk(void)
+{
+ return getTerminationStatus() == VINF_SUCCESS ? true : false;
+}
+
+/**
+ * Static helper function to start and wait for a certain toolbox tool.
+ *
+ * This function most likely is the one you want to use in the first place if you
+ * want to just use a toolbox tool and wait for its result. See runEx() if you also
+ * needs its output.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param pvrcGuest Where to store the toolbox tool's specific error code in case
+ * VERR_GSTCTL_GUEST_ERROR is returned.
+ */
+/* static */
+int GuestProcessTool::run( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = runErrorInfo(pGuestSession, startupInfo, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.rcGuest))
+ {
+ if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.rcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Static helper function to start and wait for a certain toolbox tool, returning
+ * extended error information from the guest.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param errorInfo Error information returned for error handling.
+ */
+/* static */
+int GuestProcessTool::runErrorInfo( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestProcessToolErrorInfo &errorInfo)
+{
+ return runExErrorInfo(pGuestSession, startupInfo,
+ NULL /* paStrmOutObjects */, 0 /* cStrmOutObjects */, errorInfo);
+}
+
+/**
+ * Static helper function to start and wait for output of a certain toolbox tool.
+ *
+ * @return IPRT status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
+ * Optional.
+ * @param cStrmOutObjects Number of stream objects passed in. Optional.
+ * @param pvrcGuest Error code returned from the guest side if VERR_GSTCTL_GUEST_ERROR is returned. Optional.
+ */
+/* static */
+int GuestProcessTool::runEx( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *paStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ int *pvrcGuest /* = NULL */)
+{
+ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
+
+ GuestProcessToolErrorInfo errorInfo = { VERR_IPE_UNINITIALIZED_STATUS, INT32_MAX };
+ int vrc = GuestProcessTool::runExErrorInfo(pGuestSession, startupInfo, paStrmOutObjects, cStrmOutObjects, errorInfo);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure to check the error information we got from the guest tool. */
+ if (GuestProcess::i_isGuestError(errorInfo.rcGuest))
+ {
+ if (errorInfo.rcGuest == VERR_GSTCTL_PROCESS_EXIT_CODE) /* Translate exit code to a meaningful error code. */
+ vrcGuest = GuestProcessTool::exitCodeToRc(startupInfo, errorInfo.iExitCode);
+ else /* At least return something. */
+ vrcGuest = errorInfo.rcGuest;
+
+ if (pvrcGuest)
+ *pvrcGuest = vrcGuest;
+
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ }
+ }
+
+ LogFlowFunc(("Returned vrc=%Rrc, vrcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Static helper function to start and wait for output of a certain toolbox tool.
+ *
+ * This is the extended version, which addds the possibility of retrieving parsable so-called guest stream
+ * objects. Those objects are issued on the guest side as part of VBoxService's toolbox tools (think of a BusyBox-like approach)
+ * on stdout and can be used on the host side to retrieve more information about the actual command issued on the guest side.
+ *
+ * @return VBox status code.
+ * @param pGuestSession Guest control session to use for starting the toolbox tool in.
+ * @param startupInfo Startup information about the toolbox tool.
+ * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
+ * Optional.
+ * @param cStrmOutObjects Number of stream objects passed in. Optional.
+ * @param errorInfo Error information returned for error handling.
+ */
+/* static */
+int GuestProcessTool::runExErrorInfo( GuestSession *pGuestSession,
+ const GuestProcessStartupInfo &startupInfo,
+ GuestCtrlStreamObjects *paStrmOutObjects,
+ uint32_t cStrmOutObjects,
+ GuestProcessToolErrorInfo &errorInfo)
+{
+ AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
+ /* paStrmOutObjects is optional. */
+
+ /** @todo Check if this is a valid toolbox. */
+
+ GuestProcessTool procTool;
+ int vrc = procTool.init(pGuestSession, startupInfo, false /* Async */, &errorInfo.rcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ while (cStrmOutObjects--)
+ {
+ try
+ {
+ GuestProcessStreamBlock strmBlk;
+ vrc = procTool.waitEx( paStrmOutObjects
+ ? GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK
+ : GUESTPROCESSTOOL_WAIT_FLAG_NONE, &strmBlk, &errorInfo.rcGuest);
+ if (paStrmOutObjects)
+ paStrmOutObjects->push_back(strmBlk);
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(vrc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure the process runs until completion. */
+ vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &errorInfo.rcGuest);
+ if (RT_SUCCESS(vrc))
+ errorInfo.rcGuest = procTool.getTerminationStatus(&errorInfo.iExitCode);
+ }
+
+ LogFlowFunc(("Returned rc=%Rrc, rcGuest=%Rrc, iExitCode=%d\n", vrc, errorInfo.rcGuest, errorInfo.iExitCode));
+ return vrc;
+}
+
+/**
+ * Reports if the tool has been run correctly.
+ *
+ * @return Will return VERR_GSTCTL_PROCESS_EXIT_CODE if the tool process returned an exit code <> 0,
+ * VERR_GSTCTL_PROCESS_WRONG_STATE if the tool process is in a wrong state (e.g. still running),
+ * or VINF_SUCCESS otherwise.
+ *
+ * @param piExitCode Exit code of the tool. Optional.
+ */
+int GuestProcessTool::getTerminationStatus(int32_t *piExitCode /* = NULL */)
+{
+ Assert(!pProcess.isNull());
+ /* pExitCode is optional. */
+
+ int vrc;
+ if (!isRunning())
+ {
+ LONG iExitCode = -1;
+ HRESULT hr = pProcess->COMGETTER(ExitCode(&iExitCode));
+ AssertComRC(hr);
+
+ if (piExitCode)
+ *piExitCode = iExitCode;
+
+ vrc = iExitCode != 0 ? VERR_GSTCTL_PROCESS_EXIT_CODE : VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_GSTCTL_PROCESS_WRONG_STATE;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Waits for a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::wait(uint32_t fToolWaitFlags, int *prcGuest)
+{
+ return waitEx(fToolWaitFlags, NULL /* pStrmBlkOut */, prcGuest);
+}
+
+/**
+ * Waits for a guest process tool, also returning process output.
+ *
+ * @returns VBox status code.
+ * @param fToolWaitFlags Guest process tool wait flags to use for waiting.
+ * @param pStrmBlkOut Where to store the guest process output.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::waitEx(uint32_t fToolWaitFlags, GuestProcessStreamBlock *pStrmBlkOut, int *prcGuest)
+{
+ LogFlowThisFunc(("fToolWaitFlags=0x%x, pStreamBlock=%p, prcGuest=%p\n", fToolWaitFlags, pStrmBlkOut, prcGuest));
+
+ /* Can we parse the next block without waiting? */
+ int vrc;
+ if (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK)
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_STDOUT, *pStrmBlkOut);
+ if (RT_SUCCESS(vrc))
+ return vrc;
+ /* else do the waiting below. */
+ }
+
+ /* Do the waiting. */
+ uint32_t fProcWaitForFlags = ProcessWaitForFlag_Terminate;
+ if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
+ fProcWaitForFlags |= ProcessWaitForFlag_StdOut;
+ if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
+ fProcWaitForFlags |= ProcessWaitForFlag_StdErr;
+
+ /** @todo Decrease timeout while running. */
+ uint64_t u64StartMS = RTTimeMilliTS();
+ uint32_t uTimeoutMS = mStartupInfo.mTimeoutMS;
+
+ int vrcGuest = VINF_SUCCESS;
+ bool fDone = false;
+
+ BYTE byBuf[_64K];
+ 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 = ProcessWaitResult_None;
+ do
+ {
+ uint64_t u64ElapsedMS;
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ vrc = pProcess->i_waitFor(fProcWaitForFlags, GET_REMAINING_TIME, waitRes, &vrcGuest);
+ if (RT_FAILURE(vrc))
+ break;
+
+ switch (waitRes)
+ {
+ case ProcessWaitResult_StdIn:
+ vrc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ case ProcessWaitResult_StdOut:
+ fHandleStdOut = true;
+ break;
+
+ case ProcessWaitResult_StdErr:
+ fHandleStdErr = true;
+ break;
+
+ case ProcessWaitResult_WaitFlagNotSupported:
+ if (fProcWaitForFlags & ProcessWaitForFlag_StdOut)
+ fHandleStdOut = true;
+ if (fProcWaitForFlags & ProcessWaitForFlag_StdErr)
+ fHandleStdErr = true;
+ /* Since waiting for stdout / stderr is not supported by the guest,
+ * wait a bit to not hog the CPU too much when polling for data. */
+ RTThreadSleep(1); /* Optional, don't check rc. */
+ break;
+
+ case ProcessWaitResult_Error:
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+ break;
+
+ case ProcessWaitResult_Terminate:
+ fDone = true;
+ break;
+
+ case ProcessWaitResult_Timeout:
+ vrc = VERR_TIMEOUT;
+ break;
+
+ case ProcessWaitResult_Start:
+ case ProcessWaitResult_Status:
+ /* Not used here, just skip. */
+ break;
+
+ default:
+ 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->i_readData(GUEST_PROC_OUT_H_STDOUT, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead));
+ vrc = mStdOut.AddData(byBuf, cbRead);
+
+ if ( RT_SUCCESS(vrc)
+ && (fToolWaitFlags & GUESTPROCESSTOOL_WAIT_FLAG_STDOUT_BLOCK))
+ {
+ AssertPtr(pStrmBlkOut);
+ vrc = getCurrentBlock(GUEST_PROC_OUT_H_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;
+ }
+ }
+
+ fHandleStdOut = false;
+ }
+
+ if (fHandleStdErr)
+ {
+ UPDATE_AND_CHECK_ELAPSED_TIME();
+
+ cbRead = 0;
+ vrc = pProcess->i_readData(GUEST_PROC_OUT_H_STDERR, sizeof(byBuf),
+ GET_REMAINING_TIME,
+ byBuf, sizeof(byBuf),
+ &cbRead, &vrcGuest);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
+ break;
+
+ if (cbRead)
+ {
+ LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead));
+ vrc = mStdErr.AddData(byBuf, cbRead);
+ }
+
+ fHandleStdErr = false;
+ }
+
+ } while (!fDone && RT_SUCCESS(vrc));
+
+#undef UPDATE_AND_CHECK_ELAPSED_TIME
+#undef GET_REMAINING_TIME
+
+ if (RT_FAILURE(vrcGuest))
+ vrc = VERR_GSTCTL_GUEST_ERROR;
+
+ LogFlowThisFunc(("Loop ended with rc=%Rrc, vrcGuest=%Rrc, waitRes=%RU32\n",
+ vrc, vrcGuest, waitRes));
+ if (prcGuest)
+ *prcGuest = vrcGuest;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Terminates a guest process tool.
+ *
+ * @returns VBox status code.
+ * @param uTimeoutMS Timeout (in ms) to wait.
+ * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR
+ * was returned. Optional.
+ */
+int GuestProcessTool::terminate(uint32_t uTimeoutMS, int *prcGuest)
+{
+ LogFlowThisFuncEnter();
+
+ int vrc;
+ if (!pProcess.isNull())
+ vrc = pProcess->i_terminateProcess(uTimeoutMS, prcGuest);
+ else
+ vrc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(vrc);
+ return vrc;
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param startupInfo Startup info of the toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const GuestProcessStartupInfo &startupInfo, int32_t iExitCode)
+{
+ if (startupInfo.mArguments.size() == 0)
+ {
+ AssertFailed();
+ return VERR_GENERAL_FAILURE; /* Should not happen. */
+ }
+
+ return exitCodeToRc(startupInfo.mArguments[0].c_str(), iExitCode);
+}
+
+/**
+ * Converts a toolbox tool's exit code to an IPRT error code.
+ *
+ * @returns VBox status code.
+ * @param pszTool Name of toolbox tool to lookup error code for.
+ * @param iExitCode The toolbox tool's exit code to lookup IPRT error for.
+ */
+/* static */
+int GuestProcessTool::exitCodeToRc(const char *pszTool, int32_t iExitCode)
+{
+ AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("%s: %d\n", pszTool, iExitCode));
+
+ if (iExitCode == 0) /* No error? Bail out early. */
+ return VINF_SUCCESS;
+
+ if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_CAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION: return VERR_SHARING_VIOLATION;
+ case VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY: return VERR_IS_A_DIRECTORY;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_LS))
+ {
+ switch (iExitCode)
+ {
+ /** @todo Handle access denied? */
+ case RTEXITCODE_FAILURE: return VERR_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_STAT))
+ {
+ switch (iExitCode)
+ {
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
+ case VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND: return VERR_NET_PATH_NOT_FOUND;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKDIR))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_MKTEMP))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_CANT_CREATE;
+ default: break;
+ }
+ }
+ else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_RM))
+ {
+ switch (iExitCode)
+ {
+ case RTEXITCODE_FAILURE: return VERR_FILE_NOT_FOUND;
+ /** @todo RTPathRmCmd does not yet distinguish between not found and access denied yet. */
+ default: break;
+ }
+ }
+
+ LogFunc(("Warning: Exit code %d not handled for tool '%s', returning VERR_GENERAL_FAILURE\n", iExitCode, pszTool));
+
+ if (iExitCode == RTEXITCODE_SYNTAX)
+ return VERR_INTERNAL_ERROR_5;
+ return VERR_GENERAL_FAILURE;
+}
+
+/**
+ * Returns a stringyfied error of a guest process tool error.
+ *
+ * @returns Stringyfied error.
+ * @param pszTool Toolbox tool name to get stringyfied error for.
+ * @param guestErrorInfo Guest error info to get stringyfied error for.
+ */
+/* static */
+Utf8Str GuestProcessTool::guestErrorToString(const char *pszTool, const GuestErrorInfo &guestErrorInfo)
+{
+ Utf8Str strErr;
+
+ /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
+ switch (guestErrorInfo.getRc())
+ {
+ case VERR_ACCESS_DENIED:
+ strErr.printf(tr("Access to \"%s\" denied"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
+ RT_FALL_THROUGH();
+ case VERR_PATH_NOT_FOUND:
+ strErr.printf(tr("No such file or directory \"%s\""), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_VM_HANDLE:
+ strErr.printf(tr("VMM device is not available (is the VM running?)"));
+ break;
+
+ case VERR_HGCM_SERVICE_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not available"));
+ break;
+
+ case VERR_BAD_EXE_FORMAT:
+ strErr.printf(tr("The file \"%s\" is not an executable format"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_AUTHENTICATION_FAILURE:
+ strErr.printf(tr("The user \"%s\" was not able to logon"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_INVALID_NAME:
+ strErr.printf(tr("The file \"%s\" is an invalid name"), guestErrorInfo.getWhat().c_str());
+ break;
+
+ case VERR_TIMEOUT:
+ strErr.printf(tr("The guest did not respond within time"));
+ break;
+
+ case VERR_CANCELLED:
+ strErr.printf(tr("The execution operation was canceled"));
+ break;
+
+ case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
+ strErr.printf(tr("Maximum number of concurrent guest processes has been reached"));
+ break;
+
+ case VERR_NOT_FOUND:
+ strErr.printf(tr("The guest execution service is not ready (yet)"));
+ break;
+
+ default:
+ strErr.printf(tr("Unhandled error %Rrc for \"%s\" occurred for tool \"%s\" on guest -- please file a bug report"),
+ guestErrorInfo.getRc(), guestErrorInfo.getWhat().c_str(), pszTool);
+ break;
+ }
+
+ return strErr;
+}
+