/* $Id$ */
/** @file
* VBoxServiceControlThread - Guest process handling.
*/
/*
* Copyright (C) 2012-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/env.h>
#include <iprt/file.h>
#include <iprt/getopt.h>
#include <iprt/handle.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/pipe.h>
#include <iprt/poll.h>
#include <iprt/process.h>
#include <iprt/semaphore.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include <VBox/VBoxGuestLib.h>
#include <VBox/HostServices/GuestControlSvc.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceControl.h"
using namespace guestControl;
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int gstcntlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID);
static int gstcntlProcessLock(PVBOXSERVICECTRLPROCESS pProcess);
static int gstcntlProcessRequest(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, PFNRT pfnFunction, unsigned cArgs, ...);
static int gstcntlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe);
static int gstcntlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess);
/* Request handlers. */
static DECLCALLBACK(int) gstcntlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf);
static DECLCALLBACK(int) gstcntlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags);
static DECLCALLBACK(int) gstcntlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis);
/**
* Initialies the passed in thread data structure with the parameters given.
*
* @return IPRT status code.
* @param pProcess Process to initialize.
* @param pSession Guest session the process is bound to.
* @param pStartupInfo Startup information.
* @param u32ContextID The context ID bound to this request / command.
*/
static int gstcntlProcessInit(PVBOXSERVICECTRLPROCESS pProcess,
const PVBOXSERVICECTRLSESSION pSession,
const PVBOXSERVICECTRLPROCSTARTUPINFO pStartupInfo,
uint32_t u32ContextID)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
AssertPtrReturn(pSession, VERR_INVALID_POINTER);
AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
/* General stuff. */
pProcess->hProcess = NIL_RTPROCESS;
pProcess->pSession = pSession;
pProcess->Node.pPrev = NULL;
pProcess->Node.pNext = NULL;
pProcess->fShutdown = false;
pProcess->fStarted = false;
pProcess->fStopped = false;
pProcess->uPID = 0; /* Don't have a PID yet. */
pProcess->cRefs = 0;
/*
* Use the initial context ID we got for starting
* the process to report back its status with the
* same context ID.
*/
pProcess->uContextID = u32ContextID;
/*
* Note: pProcess->ClientID will be assigned when thread is started;
* every guest process has its own client ID to detect crashes on
* a per-guest-process level.
*/
int rc = RTCritSectInit(&pProcess->CritSect);
if (RT_FAILURE(rc))
return rc;
pProcess->hPollSet = NIL_RTPOLLSET;
pProcess->hPipeStdInW = NIL_RTPIPE;
pProcess->hPipeStdOutR = NIL_RTPIPE;
pProcess->hPipeStdErrR = NIL_RTPIPE;
pProcess->hNotificationPipeW = NIL_RTPIPE;
pProcess->hNotificationPipeR = NIL_RTPIPE;
rc = RTReqQueueCreate(&pProcess->hReqQueue);
AssertReleaseRC(rc);
/* Copy over startup info. */
memcpy(&pProcess->StartupInfo, pStartupInfo, sizeof(VBOXSERVICECTRLPROCSTARTUPINFO));
/* Adjust timeout value. */
if ( pProcess->StartupInfo.uTimeLimitMS == UINT32_MAX
|| pProcess->StartupInfo.uTimeLimitMS == 0)
pProcess->StartupInfo.uTimeLimitMS = RT_INDEFINITE_WAIT;
if (RT_FAILURE(rc)) /* Clean up on failure. */
GstCntlProcessFree(pProcess);
return rc;
}
/**
* Frees a guest process. On success, pProcess will be
* free'd and thus won't be available anymore.
*
* @return IPRT status code.
* @param pProcess Guest process to free.
*/
int GstCntlProcessFree(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n",
pProcess->uPID, pProcess->cRefs);
Assert(pProcess->cRefs == 0);
/*
* Destroy other thread data.
*/
if (RTCritSectIsInitialized(&pProcess->CritSect))
RTCritSectDelete(&pProcess->CritSect);
int rc = RTReqQueueDestroy(pProcess->hReqQueue);
AssertRC(rc);
/*
* Remove from list.
*/
AssertPtr(pProcess->pSession);
rc = GstCntlSessionProcessRemove(pProcess->pSession, pProcess);
AssertRC(rc);
/*
* Destroy thread structure as final step.
*/
RTMemFree(pProcess);
pProcess = NULL;
return VINF_SUCCESS;
}
/**
* Signals a guest process thread that we want it to shut down in
* a gentle way.
*
* @return IPRT status code.
* @param pProcess Process to stop.
*/
int GstCntlProcessStop(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "[PID %RU32]: Stopping ...\n",
pProcess->uPID);
/* Do *not* set pThread->fShutdown or other stuff here!
* The guest thread loop will clean up itself. */
return GstCntlProcessHandleTerm(pProcess);
}
/**
* Releases a previously acquired guest process (decreases the refcount).
*
* @param pProcess Process to unlock.
*/
void GstCntlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturnVoid(pProcess);
bool fShutdown = false;
int rc = RTCritSectEnter(&pProcess->CritSect);
if (RT_SUCCESS(rc))
{
Assert(pProcess->cRefs);
pProcess->cRefs--;
fShutdown = pProcess->fStopped; /* Has the process' thread been stopped? */
rc = RTCritSectLeave(&pProcess->CritSect);
AssertRC(rc);
}
if (fShutdown)
GstCntlProcessFree(pProcess);
}
/**
* Wait for a guest process thread to shut down.
*
* @return IPRT status code.
* @param pProcess Process to wait shutting down for.
* @param RTMSINTERVAL Timeout in ms to wait for shutdown.
* @param pRc Where to store the thread's return code. Optional.
*/
int GstCntlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess,
RTMSINTERVAL msTimeout, int *pRc)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/* pRc is optional. */
int rc = gstcntlProcessLock(pProcess);
if (RT_SUCCESS(rc))
{
VBoxServiceVerbose(2, "[PID %RU32]: Waiting for shutdown (%RU32ms) ...\n",
pProcess->uPID, msTimeout);
AssertMsgReturn(pProcess->fStarted,
("Tried to wait on guest process=%p (PID %RU32) which has not been started yet\n",
pProcess, pProcess->uPID), VERR_INVALID_PARAMETER);
/* Guest process already has been stopped, no need to wait. */
if (!pProcess->fStopped)
{
/* Unlock process before waiting. */
rc = gstcntlProcessUnlock(pProcess);
AssertRC(rc);
/* Do the actual waiting. */
int rcThread;
Assert(pProcess->Thread != NIL_RTTHREAD);
rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread);
if (RT_FAILURE(rc))
{
VBoxServiceError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n",
pProcess->uPID, rc);
}
else
{
VBoxServiceVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n",
pProcess->uPID, rcThread);
if (pRc)
*pRc = rcThread;
}
}
else
{
VBoxServiceVerbose(3, "[PID %RU32]: Thread already shut down, no waiting needed\n",
pProcess->uPID);
int rc2 = gstcntlProcessUnlock(pProcess);
AssertRC(rc2);
}
}
VBoxServiceVerbose(3, "[PID %RU32]: Waiting resulted in rc=%Rrc\n",
pProcess->uPID, rc);
return rc;
}
/**
* Closes the stdin pipe of a guest process.
*
* @return IPRT status code.
* @param hPollSet The polling set.
* @param phStdInW The standard input pipe handle.
*/
static int gstcntlProcessPollsetCloseInput(PVBOXSERVICECTRLPROCESS pProcess,
PRTPIPE phStdInW)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
AssertPtrReturn(phStdInW, VERR_INVALID_POINTER);
int rc = RTPollSetRemove(pProcess->hPollSet, VBOXSERVICECTRLPIPEID_STDIN);
if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND)
AssertRC(rc);
if (*phStdInW != NIL_RTPIPE)
{
rc = RTPipeClose(*phStdInW);
AssertRC(rc);
*phStdInW = NIL_RTPIPE;
}
return rc;
}
static const char* gstcntlProcessPollHandleToString(uint32_t idPollHnd)
{
switch (idPollHnd)
{
case VBOXSERVICECTRLPIPEID_UNKNOWN:
return "unknown";
case VBOXSERVICECTRLPIPEID_STDIN:
return "stdin";
case VBOXSERVICECTRLPIPEID_STDIN_WRITABLE:
return "stdin_writable";
case VBOXSERVICECTRLPIPEID_STDOUT:
return "stdout";
case VBOXSERVICECTRLPIPEID_STDERR:
return "stderr";
case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
return "ipc_notify";
default:
break;
}
return "unknown";
}
/**
* Handle an error event on standard input.
*
* @return IPRT status code.
* @param pProcess Process to handle pollset for.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phStdInW The standard input pipe handle.
*/
static int gstcntlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess,
uint32_t fPollEvt, PRTPIPE phStdInW)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
NOREF(fPollEvt);
return gstcntlProcessPollsetCloseInput(pProcess, phStdInW);
}
/**
* Handle pending output data or error on standard out or standard error.
*
* @returns IPRT status code from client send.
* @param pProcess Process to handle pollset for.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phPipeR The pipe handle.
* @param idPollHnd The pipe ID to handle.
*
*/
static int gstcntlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess,
uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
if (!phPipeR)
return VINF_SUCCESS;
#ifdef DEBUG
VBoxServiceVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n",
pProcess->uPID, gstcntlProcessPollHandleToString(idPollHnd), fPollEvt);
#endif
/* Remove pipe from poll set. */
int rc2 = RTPollSetRemove(pProcess->hPollSet, idPollHnd);
AssertMsg(RT_SUCCESS(rc2) || rc2 == VERR_POLL_HANDLE_ID_NOT_FOUND, ("%Rrc\n", rc2));
bool fClosePipe = true; /* By default close the pipe. */
/* Check if there's remaining data to read from the pipe. */
if (*phPipeR != NIL_RTPIPE)
{
size_t cbReadable;
rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable);
if ( RT_SUCCESS(rc2)
&& cbReadable)
{
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n",
pProcess->uPID, gstcntlProcessPollHandleToString(idPollHnd), cbReadable);
#endif
/* Veto closing the pipe yet because there's still stuff to read
* from the pipe. This can happen on UNIX-y systems where on
* error/hangup there still can be data to be read out. */
fClosePipe = false;
}
}
#ifdef DEBUG
else
VBoxServiceVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n",
pProcess->uPID, gstcntlProcessPollHandleToString(idPollHnd));
#endif
if ( *phPipeR != NIL_RTPIPE
&& fClosePipe)
{
rc2 = RTPipeClose(*phPipeR);
AssertRC(rc2);
*phPipeR = NIL_RTPIPE;
}
return VINF_SUCCESS;
}
/**
* Handle pending output data or error on standard out or standard error.
*
* @returns IPRT status code from client send.
* @param pProcess Process to handle pollset for.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phPipeR The pipe handle.
* @param idPollHnd The pipe ID to handle.
*
*/
static int gstcntlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess,
uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
#ifdef DEBUG
VBoxServiceVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n",
pProcess->uPID, phPipeR, gstcntlProcessPollHandleToString(idPollHnd), fPollEvt);
#endif
if (!phPipeR)
return VINF_SUCCESS;
int rc = VINF_SUCCESS;
#ifdef DEBUG
if (*phPipeR != NIL_RTPIPE)
{
size_t cbReadable;
rc = RTPipeQueryReadable(*phPipeR, &cbReadable);
if ( RT_SUCCESS(rc)
&& cbReadable)
{
VBoxServiceVerbose(4, "[PID %RU32]: Output event cbReadable=%zu\n",
pProcess->uPID, cbReadable);
}
}
#endif
#if 0
/* Push output to the host. */
if (fPollEvt & RTPOLL_EVT_READ)
{
size_t cbRead = 0;
uint8_t byData[_64K];
rc = RTPipeRead(*phPipeR,
byData, sizeof(byData), &cbRead);
VBoxServiceVerbose(4, "GstCntlProcessHandleOutputEvent cbRead=%u, rc=%Rrc\n",
cbRead, rc);
/* Make sure we go another poll round in case there was too much data
for the buffer to hold. */
fPollEvt &= RTPOLL_EVT_ERROR;
}
#endif
if (fPollEvt & RTPOLL_EVT_ERROR)
rc = gstcntlProcessHandleOutputError(pProcess,
fPollEvt, phPipeR, idPollHnd);
return rc;
}
/**
* Execution loop which runs in a dedicated per-started-process thread and
* handles all pipe input/output and signalling stuff.
*
* @return IPRT status code.
* @param pProcess The guest process to handle.
*/
static int gstcntlProcessProcLoop(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
int rc;
int rc2;
uint64_t const uMsStart = RTTimeMilliTS();
RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND };
bool fProcessAlive = true;
bool fProcessTimedOut = false;
uint64_t MsProcessKilled = UINT64_MAX;
RTMSINTERVAL const cMsPollBase = pProcess->hPipeStdInW != NIL_RTPIPE
? 100 /* Need to poll for input. */
: 1000; /* Need only poll for process exit and aborts. */
RTMSINTERVAL cMsPollCur = 0;
/*
* Assign PID to thread data.
* Also check if there already was a thread with the same PID and shut it down -- otherwise
* the first (stale) entry will be found and we get really weird results!
*/
rc = gstcntlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */);
if (RT_FAILURE(rc))
{
VBoxServiceError("Unable to assign PID=%u, to new thread, rc=%Rrc\n",
pProcess->hProcess, rc);
return rc;
}
/*
* Before entering the loop, tell the host that we've started the guest
* and that it's now OK to send input to the process.
*/
VBoxServiceVerbose(2, "[PID %RU32]: Process \"%s\" started, CID=%u, User=%s, cMsTimeout=%RU32\n",
pProcess->uPID, pProcess->StartupInfo.szCmd, pProcess->uContextID,
pProcess->StartupInfo.szUser, pProcess->StartupInfo.uTimeLimitMS);
VBGLR3GUESTCTRLCMDCTX ctxStart = { pProcess->uClientID, pProcess->uContextID };
rc = VbglR3GuestCtrlProcCbStatus(&ctxStart,
pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */,
NULL /* pvData */, 0 /* cbData */);
if (rc == VERR_INTERRUPTED)
rc = VINF_SUCCESS; /* SIGCHLD send by quick childs! */
if (RT_FAILURE(rc))
VBoxServiceError("[PID %RU32]: Error reporting starting status to host, rc=%Rrc\n",
pProcess->uPID, rc);
/*
* Process input, output, the test pipe and client requests.
*/
while ( RT_SUCCESS(rc)
&& RT_UNLIKELY(!pProcess->fShutdown))
{
/*
* Wait/Process all pending events.
*/
uint32_t idPollHnd;
uint32_t fPollEvt;
rc2 = RTPollNoResume(pProcess->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd);
if (pProcess->fShutdown)
continue;
cMsPollCur = 0; /* No rest until we've checked everything. */
if (RT_SUCCESS(rc2))
{
switch (idPollHnd)
{
case VBOXSERVICECTRLPIPEID_STDIN:
rc = gstcntlProcessPollsetOnInput(pProcess, fPollEvt,
&pProcess->hPipeStdInW);
break;
case VBOXSERVICECTRLPIPEID_STDOUT:
rc = gstcntlProcessPollsetOnOutput(pProcess, fPollEvt,
&pProcess->hPipeStdOutR, idPollHnd);
break;
case VBOXSERVICECTRLPIPEID_STDERR:
rc = gstcntlProcessPollsetOnOutput(pProcess, fPollEvt,
&pProcess->hPipeStdOutR, idPollHnd);
break;
case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
#ifdef DEBUG_andy
VBoxServiceVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID);
#endif
rc2 = gstcntlProcessLock(pProcess);
if (RT_SUCCESS(rc2))
{
/* Drain the notification pipe. */
uint8_t abBuf[8];
size_t cbIgnore;
rc2 = RTPipeRead(pProcess->hNotificationPipeR,
abBuf, sizeof(abBuf), &cbIgnore);
if (RT_FAILURE(rc2))
VBoxServiceError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2);
/* Process all pending requests. */
VBoxServiceVerbose(4, "[PID %RU32]: Processing pending requests ...\n",
pProcess->uPID);
Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
rc2 = RTReqQueueProcess(pProcess->hReqQueue,
0 /* Only process all pending requests, don't wait for new ones */);
if ( RT_FAILURE(rc2)
&& rc2 != VERR_TIMEOUT)
VBoxServiceError("Processing requests failed with with rc=%Rrc\n", rc2);
int rc3 = gstcntlProcessUnlock(pProcess);
AssertRC(rc3);
#ifdef DEBUG
VBoxServiceVerbose(4, "[PID %RU32]: Processing pending requests done, rc=%Rrc\n",
pProcess->uPID, rc2);
#endif
}
break;
default:
AssertMsgFailed(("Unknown idPollHnd=%RU32\n", idPollHnd));
break;
}
if (RT_FAILURE(rc) || rc == VINF_EOF)
break; /* Abort command, or client dead or something. */
}
#if 0
VBoxServiceVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n",
pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), gstcntlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown);
VBoxServiceVerbose(4, "[PID %RU32]: stdOut=%s, stdErrR=%s\n",
pProcess->uPID,
*phStdOutR == NIL_RTPIPE ? "closed" : "open",
*phStdErrR == NIL_RTPIPE ? "closed" : "open");
#endif
if (RT_UNLIKELY(pProcess->fShutdown))
break; /* We were asked to shutdown. */
/*
* Check for process death.
*/
if (fProcessAlive)
{
rc2 = RTProcWaitNoResume(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
#if 0
VBoxServiceVerbose(4, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n",
pProcess->uPID, rc2);
#endif
if (RT_SUCCESS_NP(rc2))
{
fProcessAlive = false;
/* Note: Don't bail out here yet. First check in the next block below
* if all needed pipe outputs have been consumed. */
}
else
{
if (RT_UNLIKELY(rc2 == VERR_INTERRUPTED))
continue;
if (RT_UNLIKELY(rc2 == VERR_PROCESS_NOT_FOUND))
{
fProcessAlive = false;
ProcessStatus.enmReason = RTPROCEXITREASON_ABEND;
ProcessStatus.iStatus = 255;
AssertFailed();
}
else
AssertMsg(rc2 == VERR_PROCESS_RUNNING, ("%Rrc\n", rc2));
}
}
/*
* If the process has terminated and all output has been consumed,
* we should be heading out.
*/
if (!fProcessAlive)
{
if ( fProcessTimedOut
|| ( pProcess->hPipeStdOutR == NIL_RTPIPE
&& pProcess->hPipeStdErrR == NIL_RTPIPE)
)
{
break;
}
}
/*
* Check for timed out, killing the process.
*/
uint32_t cMilliesLeft = RT_INDEFINITE_WAIT;
if ( pProcess->StartupInfo.uTimeLimitMS != RT_INDEFINITE_WAIT
&& pProcess->StartupInfo.uTimeLimitMS != 0)
{
uint64_t u64Now = RTTimeMilliTS();
uint64_t cMsElapsed = u64Now - uMsStart;
if (cMsElapsed >= pProcess->StartupInfo.uTimeLimitMS)
{
fProcessTimedOut = true;
if ( MsProcessKilled == UINT64_MAX
|| u64Now - MsProcessKilled > 1000)
{
if (u64Now - MsProcessKilled > 20*60*1000)
break; /* Give up after 20 mins. */
VBoxServiceVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n",
pProcess->uPID, cMsElapsed, pProcess->StartupInfo.uTimeLimitMS);
rc2 = RTProcTerminate(pProcess->hProcess);
VBoxServiceVerbose(3, "[PID %RU32]: Killing process resulted in rc=%Rrc\n",
pProcess->uPID, rc2);
MsProcessKilled = u64Now;
continue;
}
cMilliesLeft = 10000;
}
else
cMilliesLeft = pProcess->StartupInfo.uTimeLimitMS - (uint32_t)cMsElapsed;
}
/* Reset the polling interval since we've done all pending work. */
cMsPollCur = fProcessAlive
? cMsPollBase
: RT_MS_1MIN;
if (cMilliesLeft < cMsPollCur)
cMsPollCur = cMilliesLeft;
}
VBoxServiceVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64\n",
pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled);
VBoxServiceVerbose(3, "[PID %RU32]: *phStdOutR=%s, *phStdErrR=%s\n",
pProcess->uPID,
pProcess->hPipeStdOutR == NIL_RTPIPE ? "closed" : "open",
pProcess->hPipeStdErrR == NIL_RTPIPE ? "closed" : "open");
/* Signal that this thread is in progress of shutting down. */
ASMAtomicXchgBool(&pProcess->fShutdown, true);
/*
* Try killing the process if it's still alive at this point.
*/
if (fProcessAlive)
{
if (MsProcessKilled == UINT64_MAX)
{
VBoxServiceVerbose(2, "[PID %RU32]: Is still alive and not killed yet\n",
pProcess->uPID);
MsProcessKilled = RTTimeMilliTS();
rc2 = RTProcTerminate(pProcess->hProcess);
if (rc2 == VERR_NOT_FOUND)
{
fProcessAlive = false;
}
else if (RT_FAILURE(rc2))
VBoxServiceError("PID %RU32]: Killing process failed with rc=%Rrc\n",
pProcess->uPID, rc2);
RTThreadSleep(500);
}
for (int i = 0; i < 10 && fProcessAlive; i++)
{
VBoxServiceVerbose(4, "[PID %RU32]: Kill attempt %d/10: Waiting to exit ...\n",
pProcess->uPID, i + 1);
rc2 = RTProcWait(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
if (RT_SUCCESS(rc2))
{
VBoxServiceVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n",
pProcess->uPID, i + 1);
fProcessAlive = false;
break;
}
if (i >= 5)
{
VBoxServiceVerbose(4, "[PID %RU32]: Kill attempt %d/10: Trying to terminate ...\n",
pProcess->uPID, i + 1);
rc2 = RTProcTerminate(pProcess->hProcess);
if ( RT_FAILURE(rc)
&& rc2 != VERR_NOT_FOUND)
VBoxServiceError("PID %RU32]: Killing process failed with rc=%Rrc\n",
pProcess->uPID, rc2);
}
RTThreadSleep(i >= 5 ? 2000 : 500);
}
if (fProcessAlive)
VBoxServiceError("[PID %RU32]: Could not be killed\n", pProcess->uPID);
}
/*
* Shutdown procedure:
* - Set the pProcess->fShutdown indicator to let others know we're
* not accepting any new requests anymore.
* - After setting the indicator, try to process all outstanding
* requests to make sure they're getting delivered.
*
* Note: After removing the process from the session's list it's not
* even possible for the session anymore to control what's
* happening to this thread, so be careful and don't mess it up.
*/
rc2 = gstcntlProcessLock(pProcess);
if (RT_SUCCESS(rc2))
{
VBoxServiceVerbose(3, "[PID %RU32]: Processing outstanding requests ...\n",
pProcess->uPID);
/* Process all pending requests (but don't wait for new ones). */
Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
rc2 = RTReqQueueProcess(pProcess->hReqQueue, 0 /* No timeout */);
if ( RT_FAILURE(rc2)
&& rc2 != VERR_TIMEOUT)
VBoxServiceError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n",
pProcess->uPID, rc2);
VBoxServiceVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n",
pProcess->uPID, rc2);
rc2 = gstcntlProcessUnlock(pProcess);
AssertRC(rc2);
}
/*
* If we don't have a client problem (RT_FAILURE(rc)) we'll reply to the
* clients exec packet now.
*/
if (RT_SUCCESS(rc))
{
uint32_t uStatus = PROC_STS_UNDEFINED;
uint32_t uFlags = 0;
if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX)
{
VBoxServiceVerbose(3, "[PID %RU32]: Timed out and got killed\n",
pProcess->uPID);
uStatus = PROC_STS_TOK;
}
else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX)
{
VBoxServiceVerbose(3, "[PID %RU32]: Timed out and did *not* get killed\n",
pProcess->uPID);
uStatus = PROC_STS_TOA;
}
else if (pProcess->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX))
{
VBoxServiceVerbose(3, "[PID %RU32]: Got terminated because system/service is about to shutdown\n",
pProcess->uPID);
uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */
uFlags = pProcess->StartupInfo.uFlags; /* Return handed-in execution flags back to the host. */
}
else if (fProcessAlive)
{
VBoxServiceError("[PID %RU32]: Is alive when it should not!\n",
pProcess->uPID);
}
else if (MsProcessKilled != UINT64_MAX)
{
VBoxServiceError("[PID %RU32]: Has been killed when it should not!\n",
pProcess->uPID);
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL)
{
VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n",
pProcess->uPID, ProcessStatus.iStatus);
uStatus = PROC_STS_TEN;
uFlags = ProcessStatus.iStatus;
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL)
{
VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n",
pProcess->uPID, ProcessStatus.iStatus);
uStatus = PROC_STS_TES;
uFlags = ProcessStatus.iStatus;
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND)
{
/* ProcessStatus.iStatus will be undefined. */
VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n",
pProcess->uPID);
uStatus = PROC_STS_TEA;
uFlags = ProcessStatus.iStatus;
}
else
VBoxServiceVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n",
pProcess->uPID, ProcessStatus.enmReason);
VBoxServiceVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n",
pProcess->uPID, pProcess->uClientID, pProcess->uContextID, uStatus, uFlags);
VBGLR3GUESTCTRLCMDCTX ctxEnd = { pProcess->uClientID, pProcess->uContextID };
rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd,
pProcess->uPID, uStatus, uFlags,
NULL /* pvData */, 0 /* cbData */);
if ( RT_FAILURE(rc2)
&& rc2 == VERR_NOT_FOUND)
VBoxServiceError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n",
pProcess->uPID, rc2);
}
VBoxServiceVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n",
pProcess->uPID, rc);
return rc;
}
/**
* Initializes a pipe's handle and pipe object.
*
* @return IPRT status code.
* @param ph The pipe's handle to initialize.
* @param phPipe The pipe's object to initialize.
*/
static int gstcntlProcessInitPipe(PRTHANDLE ph, PRTPIPE phPipe)
{
AssertPtrReturn(ph, VERR_INVALID_PARAMETER);
AssertPtrReturn(phPipe, VERR_INVALID_PARAMETER);
ph->enmType = RTHANDLETYPE_PIPE;
ph->u.hPipe = NIL_RTPIPE;
*phPipe = NIL_RTPIPE;
return VINF_SUCCESS;
}
/**
* Sets up the redirection / pipe / nothing for one of the standard handles.
*
* @returns IPRT status code. No client replies made.
* @param pszHowTo How to set up this standard handle.
* @param fd Which standard handle it is (0 == stdin, 1 ==
* stdout, 2 == stderr).
* @param ph The generic handle that @a pph may be set
* pointing to. Always set.
* @param pph Pointer to the RTProcCreateExec argument.
* Always set.
* @param phPipe Where to return the end of the pipe that we
* should service.
*/
static int gstcntlProcessSetupPipe(const char *pszHowTo, int fd,
PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe)
{
AssertPtrReturn(ph, VERR_INVALID_POINTER);
AssertPtrReturn(pph, VERR_INVALID_POINTER);
AssertPtrReturn(phPipe, VERR_INVALID_POINTER);
int rc;
ph->enmType = RTHANDLETYPE_PIPE;
ph->u.hPipe = NIL_RTPIPE;
*pph = NULL;
*phPipe = NIL_RTPIPE;
if (!strcmp(pszHowTo, "|"))
{
/*
* Setup a pipe for forwarding to/from the client.
* The ph union struct will be filled with a pipe read/write handle
* to represent the "other" end to phPipe.
*/
if (fd == 0) /* stdin? */
{
/* Connect a wrtie pipe specified by phPipe to stdin. */
rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ);
}
else /* stdout or stderr? */
{
/* Connect a read pipe specified by phPipe to stdout or stderr. */
rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE);
}
if (RT_FAILURE(rc))
return rc;
ph->enmType = RTHANDLETYPE_PIPE;
*pph = ph;
}
else if (!strcmp(pszHowTo, "/dev/null"))
{
/*
* Redirect to/from /dev/null.
*/
RTFILE hFile;
rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE);
if (RT_FAILURE(rc))
return rc;
ph->enmType = RTHANDLETYPE_FILE;
ph->u.hFile = hFile;
*pph = ph;
}
else /* Add other piping stuff here. */
rc = VINF_SUCCESS; /* Same as parent (us). */
return rc;
}
/**
* Expands a file name / path to its real content. This only works on Windows
* for now (e.g. translating "%TEMP%\foo.exe" to "C:\Windows\Temp" when starting
* with system / administrative rights).
*
* @return IPRT status code.
* @param pszPath Path to resolve.
* @param pszExpanded Pointer to string to store the resolved path in.
* @param cbExpanded Size (in bytes) of string to store the resolved path.
*/
static int gstcntlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded)
{
int rc = VINF_SUCCESS;
#ifdef RT_OS_WINDOWS
if (!ExpandEnvironmentStrings(pszPath, pszExpanded, cbExpanded))
rc = RTErrConvertFromWin32(GetLastError());
#else
/* No expansion for non-Windows yet. */
rc = RTStrCopy(pszExpanded, cbExpanded, pszPath);
#endif
#ifdef DEBUG
VBoxServiceVerbose(3, "VBoxServiceControlExecMakeFullPath: %s -> %s\n",
pszPath, pszExpanded);
#endif
return rc;
}
/**
* Resolves the full path of a specified executable name. This function also
* resolves internal VBoxService tools to its appropriate executable path + name if
* VBOXSERVICE_NAME is specified as pszFileName.
*
* @return IPRT status code.
* @param pszFileName File name to resolve.
* @param pszResolved Pointer to a string where the resolved file name will be stored.
* @param cbResolved Size (in bytes) of resolved file name string.
*/
static int gstcntlProcessResolveExecutable(const char *pszFileName,
char *pszResolved, size_t cbResolved)
{
AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
AssertPtrReturn(pszResolved, VERR_INVALID_POINTER);
AssertReturn(cbResolved, VERR_INVALID_PARAMETER);
int rc = VINF_SUCCESS;
char szPathToResolve[RTPATH_MAX];
if ( (g_pszProgName && (RTStrICmp(pszFileName, g_pszProgName) == 0))
|| !RTStrICmp(pszFileName, VBOXSERVICE_NAME))
{
/* Resolve executable name of this process. */
if (!RTProcGetExecutablePath(szPathToResolve, sizeof(szPathToResolve)))
rc = VERR_FILE_NOT_FOUND;
}
else
{
/* Take the raw argument to resolve. */
rc = RTStrCopy(szPathToResolve, sizeof(szPathToResolve), pszFileName);
}
if (RT_SUCCESS(rc))
{
rc = gstcntlProcessMakeFullPath(szPathToResolve, pszResolved, cbResolved);
if (RT_SUCCESS(rc))
VBoxServiceVerbose(3, "Looked up executable: %s -> %s\n",
pszFileName, pszResolved);
}
if (RT_FAILURE(rc))
VBoxServiceError("Failed to lookup executable \"%s\" with rc=%Rrc\n",
pszFileName, rc);
return rc;
}
/**
* Constructs the argv command line by resolving environment variables
* and relative paths.
*
* @return IPRT status code.
* @param pszArgv0 First argument (argv0), either original or modified version. Optional.
* @param papszArgs Original argv command line from the host, starting at argv[1].
* @param ppapszArgv Pointer to a pointer with the new argv command line.
* Needs to be freed with RTGetOptArgvFree.
*/
static int gstcntlProcessAllocateArgv(const char *pszArgv0,
const char * const *papszArgs,
bool fExpandArgs, char ***ppapszArgv)
{
AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "GstCntlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fExpandArgs=%RTbool, ppapszArgv=%p\n",
pszArgv0, papszArgs, fExpandArgs, ppapszArgv);
int rc = VINF_SUCCESS;
uint32_t cArgs;
for (cArgs = 0; papszArgs[cArgs]; cArgs++)
{
if (cArgs >= UINT32_MAX - 2)
return VERR_BUFFER_OVERFLOW;
}
/* Allocate new argv vector (adding + 2 for argv0 + termination). */
size_t cbSize = (cArgs + 2) * sizeof(char*);
char **papszNewArgv = (char**)RTMemAlloc(cbSize);
if (!papszNewArgv)
return VERR_NO_MEMORY;
#ifdef DEBUG
VBoxServiceVerbose(3, "GstCntlProcessAllocateArgv: cbSize=%RU32, cArgs=%RU32\n",
cbSize, cArgs);
#endif
size_t i = 0; /* Keep the argument counter in scope for cleaning up on failure. */
rc = RTStrDupEx(&papszNewArgv[0], pszArgv0);
if (RT_SUCCESS(rc))
{
for (; i < cArgs; i++)
{
char *pszArg;
#if 0 /* Arguments expansion -- untested. */
if (fExpandArgs)
{
/* According to MSDN the limit on older Windows version is 32K, whereas
* Vista+ there are no limits anymore. We still stick to 4K. */
char szExpanded[_4K];
# ifdef RT_OS_WINDOWS
if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded)))
rc = RTErrConvertFromWin32(GetLastError());
# else
/* No expansion for non-Windows yet. */
rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded);
# endif
if (RT_SUCCESS(rc))
rc = RTStrDupEx(&pszArg, szExpanded);
}
else
#endif
rc = RTStrDupEx(&pszArg, papszArgs[i]);
if (RT_FAILURE(rc))
break;
papszNewArgv[i + 1] = pszArg;
}
if (RT_SUCCESS(rc))
{
/* Terminate array. */
papszNewArgv[cArgs + 1] = NULL;
*ppapszArgv = papszNewArgv;
}
}
if (RT_FAILURE(rc))
{
for (i; i > 0; i--)
RTStrFree(papszNewArgv[i]);
RTMemFree(papszNewArgv);
}
return rc;
}
/**
* Assigns a valid PID to a guest control thread and also checks if there already was
* another (stale) guest process which was using that PID before and destroys it.
*
* @return IPRT status code.
* @param pProcess Process to assign PID to.
* @param uPID PID to assign to the specified guest control execution thread.
*/
int gstcntlProcessAssignPID(PVBOXSERVICECTRLPROCESS pProcess, uint32_t uPID)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
AssertReturn(uPID, VERR_INVALID_PARAMETER);
AssertPtr(pProcess->pSession);
int rc = RTCritSectEnter(&pProcess->pSession->CritSect);
if (RT_SUCCESS(rc))
{
/* Search old threads using the desired PID and shut them down completely -- it's
* not used anymore. */
PVBOXSERVICECTRLPROCESS pProcessCur;
bool fTryAgain;
do
{
fTryAgain = false;
RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node)
{
if (pProcessCur->uPID == uPID)
{
Assert(pProcessCur != pProcess); /* can't happen */
uint32_t uTriedPID = uPID;
uPID += 391939;
VBoxServiceVerbose(2, "PID %RU32 was used before (process %p), trying again with %RU32 ...\n",
uTriedPID, pProcessCur, uPID);
fTryAgain = true;
break;
}
}
} while (fTryAgain);
/* Assign PID to current thread. */
pProcess->uPID = uPID;
rc = RTCritSectLeave(&pProcess->pSession->CritSect);
AssertRC(rc);
}
return rc;
}
void gstcntlProcessFreeArgv(char **papszArgv)
{
if (papszArgv)
{
size_t i = 0;
while (papszArgv[i])
RTStrFree(papszArgv[i++]);
RTMemFree(papszArgv);
}
}
/**
* Helper function to create/start a process on the guest.
*
* @return IPRT status code.
* @param pszExec Full qualified path of process to start (without arguments).
* @param papszArgs Pointer to array of command line arguments.
* @param hEnv Handle to environment block to use.
* @param fFlags Process execution flags.
* @param phStdIn Handle for the process' stdin pipe.
* @param phStdOut Handle for the process' stdout pipe.
* @param phStdErr Handle for the process' stderr pipe.
* @param pszAsUser User name (account) to start the process under.
* @param pszPassword Password of the specified user.
* @param phProcess Pointer which will receive the process handle after
* successful process start.
*/
static int gstcntlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
const char *pszPassword, PRTPROCESS phProcess)
{
AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER);
AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER);
int rc = VINF_SUCCESS;
char szExecExp[RTPATH_MAX];
/* Do we need to expand environment variables in arguments? */
bool fExpandArgs = (fFlags & EXECUTEPROCESSFLAG_EXPAND_ARGUMENTS) ? true : false;
#ifdef RT_OS_WINDOWS
/*
* If sysprep should be executed do this in the context of VBoxService, which
* (usually, if started by SCM) has administrator rights. Because of that a UI
* won't be shown (doesn't have a desktop).
*/
if (!RTStrICmp(pszExec, "sysprep"))
{
/* Use a predefined sysprep path as default. */
char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
/** @todo Check digital signature of file above before executing it? */
/*
* On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe",
* so detect the OS and use a different path.
*/
OSVERSIONINFOEX OSInfoEx;
RT_ZERO(OSInfoEx);
OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
BOOL fRet = GetVersionEx((LPOSVERSIONINFO) &OSInfoEx);
if ( fRet
&& OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT
&& OSInfoEx.dwMajorVersion >= 6 /* Vista or later */)
{
rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
#ifndef RT_ARCH_AMD64
/* Don't execute 64-bit sysprep from a 32-bit service host! */
char szSysWow64[RTPATH_MAX];
if (RTStrPrintf(szSysWow64, sizeof(szSysWow64), "%s", szSysprepCmd))
{
rc = RTPathAppend(szSysWow64, sizeof(szSysWow64), "SysWow64");
AssertRC(rc);
}
if ( RT_SUCCESS(rc)
&& RTPathExists(szSysWow64))
VBoxServiceVerbose(0, "Warning: This service is 32-bit; could not execute sysprep on 64-bit OS!\n");
#endif
if (RT_SUCCESS(rc))
rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\Sysprep\\sysprep.exe");
if (RT_SUCCESS(rc))
RTPathChangeToDosSlashes(szSysprepCmd, false /* No forcing necessary */);
if (RT_FAILURE(rc))
VBoxServiceError("Failed to detect sysrep location, rc=%Rrc\n", rc);
}
else if (!fRet)
VBoxServiceError("Failed to retrieve OS information, last error=%ld\n", GetLastError());
VBoxServiceVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd);
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
rc = gstcntlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs,
fExpandArgs, &papszArgsExp);
if (RT_SUCCESS(rc))
{
/* As we don't specify credentials for the sysprep process, it will
* run under behalf of the account VBoxService was started under, most
* likely local system. */
rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */,
phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */,
NULL /* pszPassword */, phProcess);
gstcntlProcessFreeArgv(papszArgsExp);
}
}
if (RT_FAILURE(rc))
VBoxServiceVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc);
return rc;
}
#endif /* RT_OS_WINDOWS */
#ifdef VBOXSERVICE_TOOLBOX
if (RTStrStr(pszExec, "vbox_") == pszExec)
{
/* We want to use the internal toolbox (all internal
* tools are starting with "vbox_" (e.g. "vbox_cat"). */
rc = gstcntlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp));
}
else
{
#endif
/*
* Do the environment variables expansion on executable and arguments.
*/
rc = gstcntlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp));
#ifdef VBOXSERVICE_TOOLBOX
}
#endif
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
rc = gstcntlProcessAllocateArgv(pszExec /* Always use the unmodified executable name as argv0. */,
papszArgs /* Append the rest of the argument vector (if any). */,
fExpandArgs, &papszArgsExp);
if (RT_FAILURE(rc))
{
/* Don't print any arguments -- may contain passwords or other sensible data! */
VBoxServiceError("Could not prepare arguments, rc=%Rrc\n", rc);
}
else
{
uint32_t uProcFlags = 0;
if (fFlags)
{
if (fFlags & EXECUTEPROCESSFLAG_HIDDEN)
uProcFlags |= RTPROC_FLAGS_HIDDEN;
if (fFlags & EXECUTEPROCESSFLAG_NO_PROFILE)
uProcFlags |= RTPROC_FLAGS_NO_PROFILE;
}
/* If no user name specified run with current credentials (e.g.
* full service/system rights). This is prohibited via official Main API!
*
* Otherwise use the RTPROC_FLAGS_SERVICE to use some special authentication
* code (at least on Windows) for running processes as different users
* started from our system service. */
if (pszAsUser && *pszAsUser)
uProcFlags |= RTPROC_FLAGS_SERVICE;
#ifdef DEBUG
VBoxServiceVerbose(3, "Command: %s\n", szExecExp);
for (size_t i = 0; papszArgsExp[i]; i++)
VBoxServiceVerbose(3, "\targv[%ld]: %s\n", i, papszArgsExp[i]);
#endif
VBoxServiceVerbose(3, "Starting process \"%s\" ...\n", szExecExp);
/* Do normal execution. */
rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags,
phStdIn, phStdOut, phStdErr,
pszAsUser && *pszAsUser ? pszAsUser : NULL,
pszPassword && *pszPassword ? pszPassword : NULL,
phProcess);
VBoxServiceVerbose(3, "Starting process \"%s\" returned rc=%Rrc\n",
szExecExp, rc);
gstcntlProcessFreeArgv(papszArgsExp);
}
}
return rc;
}
#ifdef DEBUG
static int gstcntlProcessDumpToFile(const char *pszFileName, void *pvBuf, size_t cbBuf)
{
AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
if (!cbBuf)
return VINF_SUCCESS;
char szFile[RTPATH_MAX];
int rc = RTPathTemp(szFile, sizeof(szFile));
if (RT_SUCCESS(rc))
rc = RTPathAppend(szFile, sizeof(szFile), pszFileName);
if (RT_SUCCESS(rc))
{
VBoxServiceVerbose(4, "Dumping %ld bytes to \"%s\"\n", cbBuf, szFile);
RTFILE fh;
rc = RTFileOpen(&fh, szFile, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
if (RT_SUCCESS(rc))
{
rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */);
RTFileClose(fh);
}
}
return rc;
}
#endif
/**
* The actual worker routine (loop) for a started guest process.
*
* @return IPRT status code.
* @param PVBOXSERVICECTRLPROCESS Guest process.
*/
static int gstcntlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "Thread of process pThread=0x%p = \"%s\" started\n",
pProcess, pProcess->StartupInfo.szCmd);
int rc = VbglR3GuestCtrlConnect(&pProcess->uClientID);
if (RT_FAILURE(rc))
{
VBoxServiceError("Process thread \"%s\" (%p) failed to connect to the guest control service, rc=%Rrc\n",
pProcess->StartupInfo.szCmd, pProcess, rc);
RTThreadUserSignal(RTThreadSelf());
return rc;
}
VBoxServiceVerbose(3, "Guest process \"%s\" got client ID=%u, flags=0x%x\n",
pProcess->StartupInfo.szCmd, pProcess->uClientID, pProcess->StartupInfo.uFlags);
/* The process thread is not interested in receiving any commands;
* tell the host service. */
rc = VbglR3GuestCtrlMsgFilterSet(pProcess->uClientID, 0 /* Skip all */,
0 /* Filter mask to add */, 0 /* Filter mask to remove */);
if (RT_FAILURE(rc))
{
VBoxServiceError("Unable to set message filter, rc=%Rrc\n", rc);
/* Non-critical. */
}
rc = GstCntlSessionProcessAdd(pProcess->pSession, pProcess);
if (RT_FAILURE(rc))
{
VBoxServiceError("Errorwhile adding guest process \"%s\" (%p) to session process list, rc=%Rrc\n",
pProcess->StartupInfo.szCmd, pProcess, rc);
RTThreadUserSignal(RTThreadSelf());
return rc;
}
bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */
/*
* Prepare argument list.
*/
char **papszArgs;
uint32_t uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */
rc = RTGetOptArgvFromString(&papszArgs, (int*)&uNumArgs,
(pProcess->StartupInfo.uNumArgs > 0) ? pProcess->StartupInfo.szArgs : "", NULL);
/* Did we get the same result? */
Assert(pProcess->StartupInfo.uNumArgs == uNumArgs);
/*
* Prepare environment variables list.
*/
char **papszEnv = NULL;
uint32_t uNumEnvVars = 0; /* Initialize in case of failing ... */
if (RT_SUCCESS(rc))
{
/* Prepare environment list. */
if (pProcess->StartupInfo.uNumEnvVars)
{
papszEnv = (char **)RTMemAlloc(pProcess->StartupInfo.uNumEnvVars * sizeof(char*));
AssertPtr(papszEnv);
uNumEnvVars = pProcess->StartupInfo.uNumEnvVars;
const char *pszCur = pProcess->StartupInfo.szEnv;
uint32_t i = 0;
uint32_t cbLen = 0;
while (cbLen < pProcess->StartupInfo.cbEnv)
{
/* sanity check */
if (i >= pProcess->StartupInfo.uNumEnvVars)
{
rc = VERR_INVALID_PARAMETER;
break;
}
int cbStr = RTStrAPrintf(&papszEnv[i++], "%s", pszCur);
if (cbStr < 0)
{
rc = VERR_NO_STR_MEMORY;
break;
}
pszCur += cbStr + 1; /* Skip terminating '\0' */
cbLen += cbStr + 1; /* Skip terminating '\0' */
}
Assert(i == pProcess->StartupInfo.uNumEnvVars);
}
}
/*
* Create the environment.
*/
RTENV hEnv;
if (RT_SUCCESS(rc))
rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
if (RT_SUCCESS(rc))
{
size_t i;
for (i = 0; i < uNumEnvVars && papszEnv; i++)
{
rc = RTEnvPutEx(hEnv, papszEnv[i]);
if (RT_FAILURE(rc))
break;
}
if (RT_SUCCESS(rc))
{
/*
* Setup the redirection of the standard stuff.
*/
/** @todo consider supporting: gcc stuff.c >file 2>&1. */
RTHANDLE hStdIn;
PRTHANDLE phStdIn;
rc = gstcntlProcessSetupPipe("|", 0 /*STDIN_FILENO*/,
&hStdIn, &phStdIn, &pProcess->hPipeStdInW);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdOut;
PRTHANDLE phStdOut;
rc = gstcntlProcessSetupPipe( (pProcess->StartupInfo.uFlags & EXECUTEPROCESSFLAG_WAIT_STDOUT)
? "|" : "/dev/null",
1 /*STDOUT_FILENO*/,
&hStdOut, &phStdOut, &pProcess->hPipeStdOutR);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdErr;
PRTHANDLE phStdErr;
rc = gstcntlProcessSetupPipe( (pProcess->StartupInfo.uFlags & EXECUTEPROCESSFLAG_WAIT_STDERR)
? "|" : "/dev/null",
2 /*STDERR_FILENO*/,
&hStdErr, &phStdErr, &pProcess->hPipeStdErrR);
if (RT_SUCCESS(rc))
{
/*
* Create a poll set for the pipes and let the
* transport layer add stuff to it as well.
*/
rc = RTPollSetCreate(&pProcess->hPollSet);
if (RT_SUCCESS(rc))
{
uint32_t uFlags = RTPOLL_EVT_ERROR;
#if 0
/* Add reading event to pollset to get some more information. */
uFlags |= RTPOLL_EVT_READ;
#endif
/* Stdin. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(pProcess->hPollSet,
pProcess->hPipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN);
/* Stdout. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(pProcess->hPollSet,
pProcess->hPipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT);
/* Stderr. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(pProcess->hPollSet,
pProcess->hPipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR);
/* IPC notification pipe. */
if (RT_SUCCESS(rc))
rc = RTPipeCreate(&pProcess->hNotificationPipeR, &pProcess->hNotificationPipeW, 0 /* Flags */);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(pProcess->hPollSet,
pProcess->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY);
if (RT_SUCCESS(rc))
{
AssertPtr(pProcess->pSession);
bool fNeedsImpersonation = !(pProcess->pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK);
rc = gstcntlProcessCreateProcess(pProcess->StartupInfo.szCmd, papszArgs, hEnv, pProcess->StartupInfo.uFlags,
phStdIn, phStdOut, phStdErr,
fNeedsImpersonation ? pProcess->StartupInfo.szUser : NULL,
fNeedsImpersonation ? pProcess->StartupInfo.szPassword : NULL,
&pProcess->hProcess);
if (RT_FAILURE(rc))
VBoxServiceError("Error starting process, rc=%Rrc\n", rc);
/*
* Tell the session thread that it can continue
* spawning guest processes. This needs to be done after the new
* process has been started because otherwise signal handling
* on (Open) Solaris does not work correctly (see @bugref{5068}).
*/
int rc2 = RTThreadUserSignal(RTThreadSelf());
if (RT_SUCCESS(rc))
rc = rc2;
fSignalled = true;
if (RT_SUCCESS(rc))
{
/*
* Close the child ends of any pipes and redirected files.
*/
rc2 = RTHandleClose(phStdIn); AssertRC(rc2);
phStdIn = NULL;
rc2 = RTHandleClose(phStdOut); AssertRC(rc2);
phStdOut = NULL;
rc2 = RTHandleClose(phStdErr); AssertRC(rc2);
phStdErr = NULL;
/* Enter the process main loop. */
rc = gstcntlProcessProcLoop(pProcess);
/*
* The handles that are no longer in the set have
* been closed by the above call in order to prevent
* the guest from getting stuck accessing them.
* So, NIL the handles to avoid closing them again.
*/
if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL)))
{
pProcess->hNotificationPipeR = NIL_RTPIPE;
pProcess->hNotificationPipeW = NIL_RTPIPE;
}
if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
VBOXSERVICECTRLPIPEID_STDERR, NULL)))
pProcess->hPipeStdErrR = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
VBOXSERVICECTRLPIPEID_STDOUT, NULL)))
pProcess->hPipeStdOutR = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
VBOXSERVICECTRLPIPEID_STDIN, NULL)))
pProcess->hPipeStdInW = NIL_RTPIPE;
}
}
RTPollSetDestroy(pProcess->hPollSet);
RTPipeClose(pProcess->hNotificationPipeR);
pProcess->hNotificationPipeR = NIL_RTPIPE;
RTPipeClose(pProcess->hNotificationPipeW);
pProcess->hNotificationPipeW = NIL_RTPIPE;
}
RTPipeClose(pProcess->hPipeStdErrR);
pProcess->hPipeStdErrR = NIL_RTPIPE;
RTHandleClose(phStdErr);
if (phStdErr)
RTHandleClose(phStdErr);
}
RTPipeClose(pProcess->hPipeStdOutR);
pProcess->hPipeStdOutR = NIL_RTPIPE;
RTHandleClose(&hStdOut);
if (phStdOut)
RTHandleClose(phStdOut);
}
RTPipeClose(pProcess->hPipeStdInW);
pProcess->hPipeStdInW = NIL_RTPIPE;
RTHandleClose(phStdIn);
}
}
RTEnvDestroy(hEnv);
}
if (pProcess->uClientID)
{
if (RT_FAILURE(rc))
{
VBGLR3GUESTCTRLCMDCTX ctx = { pProcess->uClientID, pProcess->uContextID };
int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx,
pProcess->uPID, PROC_STS_ERROR, rc,
NULL /* pvData */, 0 /* cbData */);
if ( RT_FAILURE(rc2)
&& rc2 != VERR_NOT_FOUND)
VBoxServiceError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n",
pProcess->uPID, rc2, rc);
}
/* Disconnect this client from the guest control service. This also cancels all
* outstanding host requests. */
VBoxServiceVerbose(3, "[PID %RU32]: Disconnecting (client ID=%u) ...\n",
pProcess->uPID, pProcess->uClientID);
VbglR3GuestCtrlDisconnect(pProcess->uClientID);
pProcess->uClientID = 0;
}
/* Free argument + environment variable lists. */
if (uNumEnvVars)
{
for (uint32_t i = 0; i < uNumEnvVars; i++)
RTStrFree(papszEnv[i]);
RTMemFree(papszEnv);
}
if (uNumArgs)
RTGetOptArgvFree(papszArgs);
/*
* If something went wrong signal the user event so that others don't wait
* forever on this thread.
*/
if (RT_FAILURE(rc) && !fSignalled)
RTThreadUserSignal(RTThreadSelf());
VBoxServiceVerbose(3, "[PID %RU32]: Thread of process \"%s\" ended with rc=%Rrc\n",
pProcess->uPID, pProcess->StartupInfo.szCmd, rc);
/* Finally, update stopped status. */
ASMAtomicXchgBool(&pProcess->fStopped, true);
return rc;
}
static int gstcntlProcessLock(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
int rc = RTCritSectEnter(&pProcess->CritSect);
AssertRC(rc);
return rc;
}
/**
* Thread main routine for a started process.
*
* @return IPRT status code.
* @param RTTHREAD Pointer to the thread's data.
* @param void* User-supplied argument pointer.
*
*/
static DECLCALLBACK(int) gstcntlProcessThread(RTTHREAD ThreadSelf, void *pvUser)
{
PVBOXSERVICECTRLPROCESS pProcess = (VBOXSERVICECTRLPROCESS*)pvUser;
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
return gstcntlProcessProcessWorker(pProcess);
}
static int gstcntlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
int rc = RTCritSectLeave(&pProcess->CritSect);
AssertRC(rc);
return rc;
}
/**
* Executes (starts) a process on the guest. This causes a new thread to be created
* so that this function will not block the overall program execution.
*
* @return IPRT status code.
* @param pSession Guest session.
* @param pStartupInfo Startup info.
* @param uContextID Context ID to associate the process to start with.
*/
int GstCntlProcessStart(const PVBOXSERVICECTRLSESSION pSession,
const PVBOXSERVICECTRLPROCSTARTUPINFO pStartupInfo,
uint32_t uContextID)
{
AssertPtrReturn(pSession, VERR_INVALID_POINTER);
AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
/*
* Allocate new thread data and assign it to our thread list.
*/
PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)RTMemAlloc(sizeof(VBOXSERVICECTRLPROCESS));
if (!pProcess)
return VERR_NO_MEMORY;
int rc = gstcntlProcessInit(pProcess, pSession, pStartupInfo, uContextID);
if (RT_SUCCESS(rc))
{
static uint32_t s_uCtrlExecThread = 0;
if (s_uCtrlExecThread++ == UINT32_MAX)
s_uCtrlExecThread = 0; /* Wrap around to not let IPRT freak out. */
rc = RTThreadCreateF(&pProcess->Thread, gstcntlProcessThread,
pProcess /*pvUser*/, 0 /*cbStack*/,
RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%u", s_uCtrlExecThread);
if (RT_FAILURE(rc))
{
VBoxServiceError("Creating thread for guest process \"%s\" failed: rc=%Rrc, pProcess=%p\n",
pStartupInfo->szCmd, rc, pProcess);
GstCntlProcessFree(pProcess);
}
else
{
VBoxServiceVerbose(4, "Waiting for thread to initialize ...\n");
/* Wait for the thread to initialize. */
rc = RTThreadUserWait(pProcess->Thread, 60 * 1000 /* 60 seconds max. */);
AssertRC(rc);
if ( ASMAtomicReadBool(&pProcess->fShutdown)
|| RT_FAILURE(rc))
{
VBoxServiceError("Thread for process \"%s\" failed to start, rc=%Rrc\n",
pStartupInfo->szCmd, rc);
GstCntlProcessFree(pProcess);
}
else
{
ASMAtomicXchgBool(&pProcess->fStarted, true);
}
}
}
return rc;
}
static DECLCALLBACK(int) gstcntlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis,
const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
bool fPendingClose, void *pvBuf, uint32_t cbBuf)
{
AssertPtrReturn(pThis, VERR_INVALID_POINTER);
AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
int rc;
size_t cbWritten = 0;
if (pvBuf && cbBuf)
{
if (pThis->hPipeStdInW != NIL_RTPIPE)
{
rc = RTPipeWrite(pThis->hPipeStdInW,
pvBuf, cbBuf, &cbWritten);
}
else
rc = VINF_EOF;
}
else
rc = VERR_INVALID_PARAMETER;
/*
* If this is the last write + we have really have written all data
* we need to close the stdin pipe on our end and remove it from
* the poll set.
*/
if ( fPendingClose
&& (cbBuf == cbWritten))
{
int rc2 = gstcntlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW);
if (RT_SUCCESS(rc))
rc = rc2;
}
uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */
uint32_t uFlags = 0; /* No flags at the moment. */
if (RT_SUCCESS(rc))
{
VBoxServiceVerbose(4, "[PID %RU32]: Written %RU32 bytes input, CID=%RU32, fPendingClose=%RTbool\n",
pThis->uPID, cbWritten, pHostCtx->uContextID, fPendingClose);
uStatus = INPUT_STS_WRITTEN;
}
else
{
if (rc == VERR_BAD_PIPE)
uStatus = INPUT_STS_TERMINATED;
else if (rc == VERR_BUFFER_OVERFLOW)
uStatus = INPUT_STS_OVERFLOW;
/* else undefined */
}
/*
* If there was an error and we did not set the host status
* yet, then do it now.
*/
if ( RT_FAILURE(rc)
&& uStatus == INPUT_STS_UNDEFINED)
{
uStatus = INPUT_STS_ERROR;
uFlags = rc;
}
Assert(uStatus > INPUT_STS_UNDEFINED);
#ifdef DEBUG
#endif
int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID,
uStatus, uFlags, (uint32_t)cbWritten);
if (RT_SUCCESS(rc))
rc = rc2;
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessOnInput returned with rc=%Rrc\n",
pThis->uPID, rc);
#endif
return VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */
}
static DECLCALLBACK(int) gstcntlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis,
const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags)
{
AssertPtrReturn(pThis, VERR_INVALID_POINTER);
AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
const PVBOXSERVICECTRLSESSION pSession = pThis->pSession;
AssertPtrReturn(pSession, VERR_INVALID_POINTER);
int rc;
uint32_t cbBuf = cbToRead;
uint8_t *pvBuf = (uint8_t *)RTMemAlloc(cbBuf);
if (pvBuf)
{
PRTPIPE phPipe = uHandle == OUTPUT_HANDLE_ID_STDOUT
? &pThis->hPipeStdOutR
: &pThis->hPipeStdErrR;
AssertPtr(phPipe);
size_t cbRead = 0;
if (*phPipe != NIL_RTPIPE)
{
rc = RTPipeRead(*phPipe, pvBuf, cbBuf, &cbRead);
if (RT_FAILURE(rc))
{
RTPollSetRemove(pThis->hPollSet, uHandle == OUTPUT_HANDLE_ID_STDERR
? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT);
RTPipeClose(*phPipe);
*phPipe = NIL_RTPIPE;
if (rc == VERR_BROKEN_PIPE)
rc = VINF_EOF;
}
}
else
rc = VINF_EOF;
#ifdef DEBUG
if (RT_SUCCESS(rc))
{
if ( pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT
&& ( uHandle == OUTPUT_HANDLE_ID_STDOUT
|| uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED)
)
{
char szDumpFile[RTPATH_MAX];
if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdOut.txt",
pSession->StartupInfo.uSessionID, pThis->uPID)) rc = VERR_BUFFER_UNDERFLOW;
if (RT_SUCCESS(rc))
rc = gstcntlProcessDumpToFile(szDumpFile, pvBuf, cbRead);
AssertRC(rc);
}
else if ( pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR
&& uHandle == OUTPUT_HANDLE_ID_STDERR)
{
char szDumpFile[RTPATH_MAX];
if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdErr.txt",
pSession->StartupInfo.uSessionID, pThis->uPID))
rc = VERR_BUFFER_UNDERFLOW;
if (RT_SUCCESS(rc))
rc = gstcntlProcessDumpToFile(szDumpFile, pvBuf, cbRead);
AssertRC(rc);
}
}
#endif
if (RT_SUCCESS(rc))
{
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, uFlags=%x\n",
pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, uFlags);
#endif
/** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
* data which the host needs to work with -- so just pass through all data unfiltered! */
/* Note: Since the context ID is unique the request *has* to be completed here,
* regardless whether we got data or not! Otherwise the waiting events
* on the host never will get completed! */
rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, uFlags,
pvBuf, cbRead);
if ( RT_FAILURE(rc)
&& rc == VERR_NOT_FOUND) /* Not critical if guest PID is not found on the host (anymore). */
rc = VINF_SUCCESS;
}
RTMemFree(pvBuf);
}
else
rc = VERR_NO_MEMORY;
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: Reading output returned with rc=%Rrc\n",
pThis->uPID, rc);
#endif
return VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */
}
static DECLCALLBACK(int) gstcntlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis)
{
AssertPtrReturn(pThis, VERR_INVALID_POINTER);
if (!ASMAtomicXchgBool(&pThis->fShutdown, true))
{
VBoxServiceVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n",
pThis->uPID);
}
return VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */
}
int gstcntlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
bool fAsync, RTMSINTERVAL uTimeoutMS, PRTREQ pReq, PFNRT pfnFunction,
unsigned cArgs, va_list Args)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/* pHostCtx is optional. */
AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
if (!fAsync)
AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
int rc = gstcntlProcessLock(pProcess);
if (RT_SUCCESS(rc))
{
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n",
pProcess->uPID, fAsync, uTimeoutMS, cArgs);
#endif
uint32_t uFlags = RTREQFLAGS_IPRT_STATUS;
if (fAsync)
{
Assert(uTimeoutMS == 0);
uFlags |= RTREQFLAGS_NO_WAIT;
}
rc = RTReqQueueCallV(pProcess->hReqQueue, &pReq, uTimeoutMS, uFlags,
pfnFunction, cArgs, Args);
if (RT_SUCCESS(rc))
{
/* Wake up the process' notification pipe to get
* the request being processed. */
Assert(pProcess->hNotificationPipeW != NIL_RTPIPE);
size_t cbWritten = 0;
rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten);
if ( RT_SUCCESS(rc)
&& cbWritten != 1)
{
VBoxServiceError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n",
pProcess->uPID, cbWritten);
}
else if (RT_UNLIKELY(RT_FAILURE(rc)))
VBoxServiceError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n",
pProcess->uPID, rc);
}
else
VBoxServiceError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n",
pProcess->uPID, rc);
int rc2 = gstcntlProcessUnlock(pProcess);
if (RT_SUCCESS(rc))
rc = rc2;
}
#ifdef DEBUG
VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessRequestExV returned rc=%Rrc\n",
pProcess->uPID, rc);
#endif
return rc;
}
int gstcntlProcessRequestAsync(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
PFNRT pfnFunction, unsigned cArgs, ...)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/* pHostCtx is optional. */
AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
va_list va;
va_start(va, cArgs);
int rc = gstcntlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */,
NULL /* pReq */, pfnFunction, cArgs, va);
va_end(va);
return rc;
}
int gstcntlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
RTMSINTERVAL uTimeoutMS, PRTREQ pReq, PFNRT pfnFunction, unsigned cArgs, ...)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/* pHostCtx is optional. */
AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
va_list va;
va_start(va, cArgs);
int rc = gstcntlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS,
pReq, pfnFunction, cArgs, va);
va_end(va);
return rc;
}
int GstCntlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
bool fPendingClose, void *pvBuf, uint32_t cbBuf)
{
if (!ASMAtomicReadBool(&pProcess->fShutdown))
return gstcntlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)gstcntlProcessOnInput,
5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
return gstcntlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
}
int GstCntlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags)
{
if (!ASMAtomicReadBool(&pProcess->fShutdown))
return gstcntlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)gstcntlProcessOnOutput,
5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, uFlags);
return gstcntlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, uFlags);
}
int GstCntlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess)
{
if (!ASMAtomicReadBool(&pProcess->fShutdown))
return gstcntlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)gstcntlProcessOnTerm,
1 /* cArgs */, pProcess);
return gstcntlProcessOnTerm(pProcess);
}