VBoxServiceControlThread.cpp revision 862ba4401c9fa66fbb0264053634d47d0718ed74
/* $Id$ */
/** @file
* VBoxServiceControlExecThread - Thread for every started guest process.
*/
/*
* Copyright (C) 2011 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"
using namespace guestControl;
/* Internal functions. */
static int vboxServiceControlThreadRequestCancel(PVBOXSERVICECTRLREQUEST pThread);
/**
* Initialies the passed in thread data structure with the parameters given.
*
* @return IPRT status code.
* @param pThread The thread's handle to allocate the data for.
* @param u32ContextID The context ID bound to this request / command.
@ @param pProcess Process information.
*/
static int gstsvcCntlExecThreadInit(PVBOXSERVICECTRLTHREAD pThread,
PVBOXSERVICECTRLPROCESS pProcess,
uint32_t u32ContextID)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/* General stuff. */
pThread->pAnchor = NULL;
pThread->Node.pPrev = NULL;
pThread->Node.pNext = NULL;
pThread->fShutdown = false;
pThread->fStarted = false;
pThread->fStopped = false;
pThread->uContextID = u32ContextID;
/* 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(&pThread->CritSect);
if (RT_FAILURE(rc))
return rc;
pThread->uPID = 0; /* Don't have a PID yet. */
pThread->pRequest = NULL; /* No request assigned yet. */
pThread->uFlags = pProcess->uFlags;
pThread->uTimeLimitMS = ( pProcess->uTimeLimitMS == UINT32_MAX
|| pProcess->uTimeLimitMS == 0)
? RT_INDEFINITE_WAIT : pProcess->uTimeLimitMS;
/* Prepare argument list. */
pThread->uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */
rc = RTGetOptArgvFromString(&pThread->papszArgs, (int*)&pThread->uNumArgs,
(pProcess->uNumArgs > 0) ? pProcess->szArgs : "", NULL);
/* Did we get the same result? */
Assert(pProcess->uNumArgs == pThread->uNumArgs);
if (RT_SUCCESS(rc))
{
/* Prepare environment list. */
pThread->uNumEnvVars = 0;
if (pProcess->uNumEnvVars)
{
pThread->papszEnv = (char **)RTMemAlloc(pProcess->uNumEnvVars * sizeof(char*));
AssertPtr(pThread->papszEnv);
pThread->uNumEnvVars = pProcess->uNumEnvVars;
const char *pszCur = pProcess->szEnv;
uint32_t i = 0;
uint32_t cbLen = 0;
while (cbLen < pProcess->cbEnv)
{
/* sanity check */
if (i >= pProcess->uNumEnvVars)
{
rc = VERR_INVALID_PARAMETER;
break;
}
int cbStr = RTStrAPrintf(&pThread->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 == pThread->uNumEnvVars);
}
/* The actual command to execute. */
pThread->pszCmd = RTStrDup(pProcess->szCmd);
AssertPtr(pThread->pszCmd);
/* User management. */
pThread->pszUser = RTStrDup(pProcess->szUser);
AssertPtr(pThread->pszUser);
pThread->pszPassword = RTStrDup(pProcess->szPassword);
AssertPtr(pThread->pszPassword);
}
if (RT_FAILURE(rc)) /* Clean up on failure. */
VBoxServiceControlThreadFree(pThread);
return rc;
}
/**
* Frees a guest thread.
*
* @return IPRT status code.
* @param pThread Thread to shut down.
*/
int VBoxServiceControlThreadFree(PVBOXSERVICECTRLTHREAD pThread)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Freeing ...\n",
pThread->uPID);
int rc = RTCritSectEnter(&pThread->CritSect);
if (RT_SUCCESS(rc))
{
if (pThread->uNumEnvVars)
{
for (uint32_t i = 0; i < pThread->uNumEnvVars; i++)
RTStrFree(pThread->papszEnv[i]);
RTMemFree(pThread->papszEnv);
}
RTGetOptArgvFree(pThread->papszArgs);
RTStrFree(pThread->pszCmd);
RTStrFree(pThread->pszUser);
RTStrFree(pThread->pszPassword);
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Setting stopped state\n",
pThread->uPID);
rc = RTCritSectLeave(&pThread->CritSect);
AssertRC(rc);
}
/*
* Destroy other thread data.
*/
if (RTCritSectIsInitialized(&pThread->CritSect))
RTCritSectDelete(&pThread->CritSect);
/*
* Destroy thread structure as final step.
*/
RTMemFree(pThread);
pThread = NULL;
return rc;
}
/**
* Signals a guest process thread that we want it to shut down in
* a gentle way.
*
* @return IPRT status code.
* @param pThread Thread to shut down.
*/
int VBoxServiceControlThreadStop(const PVBOXSERVICECTRLTHREAD pThread)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Stopping ...\n",
pThread->uPID);
int rc = vboxServiceControlThreadRequestCancel(pThread->pRequest);
if (RT_FAILURE(rc))
VBoxServiceError("ControlThread: [PID %u]: Signalling request event failed, rc=%Rrc\n",
pThread->uPID, rc);
/* Do *not* set pThread->fShutdown or other stuff here!
* The guest thread loop will do that as soon as it processes the quit message. */
PVBOXSERVICECTRLREQUEST pRequest;
rc = VBoxServiceControlThreadRequestAlloc(&pRequest, VBOXSERVICECTRLREQUEST_QUIT);
if (RT_SUCCESS(rc))
{
rc = VBoxServiceControlThreadPerform(pThread->uPID, pRequest);
if (RT_FAILURE(rc))
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Sending quit request failed with rc=%Rrc\n",
pThread->uPID, rc);
VBoxServiceControlThreadRequestFree(pRequest);
}
return rc;
}
/**
* Wait for a guest process thread to shut down.
*
* @return IPRT status code.
* @param pThread Thread to wait shutting down for.
* @param RTMSINTERVAL Timeout in ms to wait for shutdown.
*/
int VBoxServiceControlThreadWait(const PVBOXSERVICECTRLTHREAD pThread,
RTMSINTERVAL msTimeout)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
int rc = VINF_SUCCESS;
if ( pThread->Thread != NIL_RTTHREAD
&& ASMAtomicReadBool(&pThread->fStarted))
{
VBoxServiceVerbose(2, "ControlThread: [PID %u]: Waiting for shutdown ...\n",
pThread->uPID);
/* Wait a bit ... */
int rcThread;
rc = RTThreadWait(pThread->Thread, msTimeout, &rcThread);
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlThread: [PID %u]: Waiting for shutting down thread returned error rc=%Rrc\n",
pThread->uPID, rc);
}
else
{
if (RT_FAILURE(rcThread))
{
VBoxServiceError("ControlThread: [PID %u]: Shutdown returned error rc=%Rrc\n",
pThread->uPID, rcThread);
rc = rcThread;
}
}
}
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 VBoxServiceControlThreadCloseStdIn(RTPOLLSET hPollSet, PRTPIPE phStdInW)
{
AssertPtrReturn(phStdInW, VERR_INVALID_POINTER);
int rc = RTPollSetRemove(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;
}
/**
* Handle an error event on standard input.
*
* @return IPRT status code.
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phStdInW The standard input pipe handle.
*/
static int VBoxServiceControlThreadHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW)
{
NOREF(fPollEvt);
return VBoxServiceControlThreadCloseStdIn(hPollSet, phStdInW);
}
/**
* Handle pending output data or error on standard out or standard error.
*
* @returns IPRT status code from client send.
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phPipeR The pipe handle.
* @param idPollHnd The pipe ID to handle.
*
*/
static int VBoxServiceControlThreadHandleOutputError(RTPOLLSET hPollSet, uint32_t fPollEvt,
PRTPIPE phPipeR, uint32_t idPollHnd)
{
AssertPtrReturn(phPipeR, VERR_INVALID_POINTER);
#ifdef DEBUG
VBoxServiceVerbose(4, "ControlThread: VBoxServiceControlThreadHandleOutputError: fPollEvt=0x%x, idPollHnd=%u\n",
fPollEvt, idPollHnd);
#endif
/* Remove pipe from poll set. */
int rc2 = RTPollSetRemove(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. */
size_t cbReadable;
rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable);
if ( RT_SUCCESS(rc2)
&& cbReadable)
{
VBoxServiceVerbose(3, "ControlThread: VBoxServiceControlThreadHandleOutputError: idPollHnd=%u has %ld bytes left, vetoing close\n",
idPollHnd, cbReadable);
/* 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;
}
else
VBoxServiceVerbose(3, "ControlThread: VBoxServiceControlThreadHandleOutputError: idPollHnd=%u will be closed\n",
idPollHnd);
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 hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phPipeR The pipe handle.
* @param idPollHnd The pipe ID to handle.
*
*/
static int VBoxServiceControlThreadHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt,
PRTPIPE phPipeR, uint32_t idPollHnd)
{
#if 0
VBoxServiceVerbose(4, "ControlThread: VBoxServiceControlThreadHandleOutputEvent: fPollEvt=0x%x, idPollHnd=%u\n",
fPollEvt, idPollHnd);
#endif
int rc = VINF_SUCCESS;
#ifdef DEBUG
size_t cbReadable;
rc = RTPipeQueryReadable(*phPipeR, &cbReadable);
if ( RT_SUCCESS(rc)
&& cbReadable)
{
VBoxServiceVerbose(4, "ControlThread: VBoxServiceControlThreadHandleOutputEvent: cbReadable=%ld\n",
cbReadable);
}
#endif
#if 0
//if (fPollEvt & RTPOLL_EVT_READ)
{
size_t cbRead = 0;
uint8_t byData[_64K];
rc = RTPipeRead(*phPipeR,
byData, sizeof(byData), &cbRead);
VBoxServiceVerbose(4, "ControlThread: VBoxServiceControlThreadHandleOutputEvent 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 = VBoxServiceControlThreadHandleOutputError(hPollSet, fPollEvt,
phPipeR, idPollHnd);
return rc;
}
static int VBoxServiceControlThreadHandleRequest(RTPOLLSET hPollSet, uint32_t fPollEvt,
PRTPIPE phStdInW, PRTPIPE phStdOutR, PRTPIPE phStdErrR,
PVBOXSERVICECTRLTHREAD pThread)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
AssertPtrReturn(phStdInW, VERR_INVALID_POINTER);
AssertPtrReturn(phStdOutR, VERR_INVALID_POINTER);
AssertPtrReturn(phStdErrR, VERR_INVALID_POINTER);
/* Drain the notification pipe. */
uint8_t abBuf[8];
size_t cbIgnore;
int rc = RTPipeRead(pThread->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore);
if (RT_FAILURE(rc))
VBoxServiceError("ControlThread: Draining IPC notification pipe failed with rc=%Rrc\n", rc);
int rcReq = VINF_SUCCESS; /* Actual request result. */
PVBOXSERVICECTRLREQUEST pRequest = pThread->pRequest;
if (!pRequest)
{
VBoxServiceError("ControlThread: IPC request is invalid\n");
return VERR_INVALID_POINTER;
}
switch (pRequest->enmType)
{
case VBOXSERVICECTRLREQUEST_QUIT: /* Main control asked us to quit. */
{
/** @todo Check for some conditions to check to
* veto quitting. */
ASMAtomicXchgBool(&pThread->fShutdown, true);
rcReq = VERR_CANCELLED;
break;
}
case VBOXSERVICECTRLREQUEST_STDIN_WRITE:
case VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF:
{
size_t cbWritten = 0;
if (pRequest->cbData)
{
AssertPtrReturn(pRequest->pvData, VERR_INVALID_POINTER);
if (*phStdInW != NIL_RTPIPE)
{
rcReq = RTPipeWrite(*phStdInW,
pRequest->pvData, pRequest->cbData, &cbWritten);
}
else
rcReq = VINF_EOF;
}
/*
* 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 ( pRequest->enmType == VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF
&& pRequest->cbData == cbWritten)
{
rc = VBoxServiceControlThreadCloseStdIn(hPollSet, phStdInW);
}
/* Reqport back actual data written (if any). */
pRequest->cbData = cbWritten;
break;
}
case VBOXSERVICECTRLREQUEST_STDOUT_READ:
case VBOXSERVICECTRLREQUEST_STDERR_READ:
{
AssertPtrReturn(pRequest->pvData, VERR_INVALID_POINTER);
AssertReturn(pRequest->cbData, VERR_INVALID_PARAMETER);
PRTPIPE pPipeR = pRequest->enmType == VBOXSERVICECTRLREQUEST_STDERR_READ
? phStdErrR : phStdOutR;
AssertPtr(pPipeR);
size_t cbRead = 0;
if (*pPipeR != NIL_RTPIPE)
{
rcReq = RTPipeRead(*pPipeR,
pRequest->pvData, pRequest->cbData, &cbRead);
if (RT_FAILURE(rcReq))
{
RTPollSetRemove(hPollSet, pRequest->enmType == VBOXSERVICECTRLREQUEST_STDERR_READ
? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT);
RTPipeClose(*pPipeR);
*pPipeR = NIL_RTPIPE;
if (rcReq == VERR_BROKEN_PIPE)
rcReq = VINF_EOF;
}
}
else
rcReq = VINF_EOF;
/* Report back actual data read (if any). */
pRequest->cbData = cbRead;
break;
}
default:
rcReq = VERR_NOT_IMPLEMENTED;
break;
}
/* Assign overall result. */
pRequest->rc = RT_SUCCESS(rc)
? rcReq : rc;
VBoxServiceVerbose(2, "ControlThread: [PID %u]: Handled req=%u, CID=%u, rc=%Rrc, cbData=%u\n",
pThread->uPID, pRequest->enmType, pRequest->uCID, pRequest->rc, pRequest->cbData);
/* In any case, regardless of the result, we notify
* the main guest control to unblock it. */
int rc2 = RTSemEventMultiSignal(pRequest->Event);
AssertRC(rc2);
/* No access to pRequest here anymore -- could be out of scope
* or modified already! */
pThread->pRequest = pRequest = NULL;
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 pThread The process' thread handle.
* @param hProcess The actual process handle.
* @param cMsTimeout Time limit (in ms) of the process' life time.
* @param hPollSet The poll set to use.
* @param hStdInW Handle to the process' stdin write end.
* @param hStdOutR Handle to the process' stdout read end.
* @param hStdErrR Handle to the process' stderr read end.
*/
static int VBoxServiceControlThreadProcLoop(PVBOXSERVICECTRLTHREAD pThread,
RTPROCESS hProcess, RTMSINTERVAL cMsTimeout, RTPOLLSET hPollSet,
PRTPIPE phStdInW, PRTPIPE phStdOutR, PRTPIPE phStdErrR)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
AssertPtrReturn(phStdInW, VERR_INVALID_PARAMETER);
/* Rest is optional. */
int rc;
int rc2;
uint64_t const MsStart = RTTimeMilliTS();
RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND };
bool fProcessAlive = true;
bool fProcessTimedOut = false;
uint64_t MsProcessKilled = UINT64_MAX;
RTMSINTERVAL const cMsPollBase = *phStdInW != 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 = VBoxServiceControlAssignPID(pThread, hProcess);
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlThread: Unable to assign PID=%u, to new thread, rc=%Rrc\n",
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, "ControlThread: [PID %u]: Process \"%s\" started, CID=%u, User=%s\n",
pThread->uPID, pThread->pszCmd, pThread->uContextID, pThread->pszUser);
rc = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID,
pThread->uPID, PROC_STS_STARTED, 0 /* u32Flags */,
NULL /* pvData */, 0 /* cbData */);
/*
* Process input, output, the test pipe and client requests.
*/
while ( RT_SUCCESS(rc)
&& RT_UNLIKELY(!pThread->fShutdown))
{
/*
* Wait/Process all pending events.
*/
uint32_t idPollHnd;
uint32_t fPollEvt;
rc2 = RTPollNoResume(hPollSet, cMsPollCur, &fPollEvt, &idPollHnd);
if (pThread->fShutdown)
continue;
cMsPollCur = 0; /* No rest until we've checked everything. */
if (RT_SUCCESS(rc2))
{
/*VBoxServiceVerbose(4, "ControlThread: [PID %u}: RTPollNoResume idPollHnd=%u\n",
pThread->uPID, idPollHnd);*/
switch (idPollHnd)
{
case VBOXSERVICECTRLPIPEID_STDIN:
rc = VBoxServiceControlThreadHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW);
break;
case VBOXSERVICECTRLPIPEID_STDOUT:
rc = VBoxServiceControlThreadHandleOutputEvent(hPollSet, fPollEvt,
phStdOutR, idPollHnd);
break;
case VBOXSERVICECTRLPIPEID_STDERR:
rc = VBoxServiceControlThreadHandleOutputEvent(hPollSet, fPollEvt,
phStdErrR, idPollHnd);
break;
case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
rc = VBoxServiceControlThreadHandleRequest(hPollSet, fPollEvt,
phStdInW, phStdOutR, phStdErrR, pThread);
break;
}
if (RT_FAILURE(rc) || rc == VINF_EOF)
break; /* Abort command, or client dead or something. */
if (RT_UNLIKELY(pThread->fShutdown))
break; /* We were asked to shutdown. */
continue;
}
#if 0
VBoxServiceVerbose(4, "ControlThread: [PID %u]: Polling done, pollRC=%Rrc, pollCnt=%u, rc=%Rrc, fShutdown=%RTbool\n",
pThread->uPID, rc2, RTPollSetGetCount(hPollSet), rc, pThread->fShutdown);
#endif
/*
* Check for process death.
*/
if (fProcessAlive)
{
rc2 = RTProcWaitNoResume(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
if (RT_SUCCESS_NP(rc2))
{
fProcessAlive = false;
continue;
}
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
&& *phStdOutR == NIL_RTPIPE
&& *phStdErrR == NIL_RTPIPE)
break;
/*
* Check for timed out, killing the process.
*/
uint32_t cMilliesLeft = RT_INDEFINITE_WAIT;
if (cMsTimeout != RT_INDEFINITE_WAIT)
{
uint64_t u64Now = RTTimeMilliTS();
uint64_t cMsElapsed = u64Now - MsStart;
if (cMsElapsed >= cMsTimeout)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Timed out (%ums elapsed > %ums timeout), killing ...",
pThread->uPID, cMsElapsed, cMsTimeout);
fProcessTimedOut = true;
if ( MsProcessKilled == UINT64_MAX
|| u64Now - MsProcessKilled > 1000)
{
if (u64Now - MsProcessKilled > 20*60*1000)
break; /* Give up after 20 mins. */
RTProcTerminate(hProcess);
MsProcessKilled = u64Now;
continue;
}
cMilliesLeft = 10000;
}
else
cMilliesLeft = cMsTimeout - (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;
/*
* Need to exit?
*/
if (pThread->fShutdown)
break;
}
rc2 = RTCritSectEnter(&pThread->CritSect);
if (RT_SUCCESS(rc2))
{
ASMAtomicXchgBool(&pThread->fShutdown, true);
rc2 = RTCritSectLeave(&pThread->CritSect);
AssertRC(rc2);
}
/*
* Try kill the process if it's still alive at this point.
*/
if (fProcessAlive)
{
if (MsProcessKilled == UINT64_MAX)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Is still alive and not killed yet\n",
pThread->uPID);
MsProcessKilled = RTTimeMilliTS();
RTProcTerminate(hProcess);
RTThreadSleep(500);
}
for (size_t i = 0; i < 10; i++)
{
VBoxServiceVerbose(4, "ControlThread: [PID %u]: Kill attempt %d/10: Waiting to exit ...\n",
pThread->uPID, i + 1);
rc2 = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
if (RT_SUCCESS(rc2))
{
VBoxServiceVerbose(4, "ControlThread: [PID %u]: Kill attempt %d/10: Exited\n",
pThread->uPID, i + 1);
fProcessAlive = false;
break;
}
if (i >= 5)
{
VBoxServiceVerbose(4, "ControlThread: [PID %u]: Kill attempt %d/10: Trying to terminate ...\n",
pThread->uPID, i + 1);
RTProcTerminate(hProcess);
}
RTThreadSleep(i >= 5 ? 2000 : 500);
}
if (fProcessAlive)
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Could not be killed\n", pThread->uPID);
}
/*
* 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, "ControlThread: [PID %u]: Timed out and got killed\n",
pThread->uPID);
uStatus = PROC_STS_TOK;
}
else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Timed out and did *not* get killed\n",
pThread->uPID);
uStatus = PROC_STS_TOA;
}
else if (pThread->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX))
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Got terminated because system/service is about to shutdown\n",
pThread->uPID);
uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */
uFlags = pThread->uFlags; /* Return handed-in execution flags back to the host. */
}
else if (fProcessAlive)
{
VBoxServiceError("ControlThread: [PID %u]: Is alive when it should not!\n",
pThread->uPID);
}
else if (MsProcessKilled != UINT64_MAX)
{
VBoxServiceError("ControlThread: [PID %u]: Has been killed when it should not!\n",
pThread->uPID);
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %u)\n",
pThread->uPID, ProcessStatus.iStatus);
uStatus = PROC_STS_TEN;
uFlags = ProcessStatus.iStatus;
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n",
pThread->uPID, ProcessStatus.iStatus);
uStatus = PROC_STS_TES;
uFlags = ProcessStatus.iStatus;
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND)
{
/* ProcessStatus.iStatus will be undefined. */
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Ended with RTPROCEXITREASON_ABEND\n",
pThread->uPID);
uStatus = PROC_STS_TEA;
uFlags = ProcessStatus.iStatus;
}
else
VBoxServiceVerbose(1, "ControlThread: [PID %u]: Handling process status %u not implemented\n",
pThread->uPID, ProcessStatus.enmReason);
VBoxServiceVerbose(2, "ControlThread: [PID %u]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n",
pThread->uPID, pThread->uClientID, pThread->uContextID, uStatus, uFlags);
rc = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID,
pThread->uPID, uStatus, uFlags,
NULL /* pvData */, 0 /* cbData */);
if (RT_FAILURE(rc))
VBoxServiceError("ControlThread: [PID %u]: Error reporting final status to host; rc=%Rrc\n",
pThread->uPID, rc);
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Process loop ended with rc=%Rrc\n",
pThread->uPID, rc);
}
else
VBoxServiceError("ControlThread: [PID %u]: Loop failed with rc=%Rrc\n",
pThread->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 vboxServiceControlThreadInitPipe(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;
}
/**
* Allocates a guest thread request with the specified request data.
*
* @return IPRT status code.
* @param ppReq Pointer that will receive the newly allocated request.
* Must be freed later with VBoxServiceControlThreadRequestFree().
* @param enmType Request type.
* @param pbData Payload data, based on request type.
* @param cbData Size of payload data (in bytes).
* @param uCID Context ID to which this request belongs to.
*/
int VBoxServiceControlThreadRequestAllocEx(PVBOXSERVICECTRLREQUEST *ppReq,
VBOXSERVICECTRLREQUESTTYPE enmType,
void* pbData,
size_t cbData,
uint32_t uCID)
{
AssertPtrReturn(ppReq, VERR_INVALID_POINTER);
PVBOXSERVICECTRLREQUEST pReq = (PVBOXSERVICECTRLREQUEST)
RTMemAlloc(sizeof(VBOXSERVICECTRLREQUEST));
AssertPtrReturn(pReq, VERR_NO_MEMORY);
RT_ZERO(*pReq);
pReq->enmType = enmType;
pReq->uCID = uCID;
pReq->cbData = cbData;
pReq->pvData = (uint8_t*)pbData;
/* Set request result to some defined state in case
* it got cancelled. */
pReq->rc = VERR_CANCELLED;
int rc = RTSemEventMultiCreate(&pReq->Event);
AssertRC(rc);
if (RT_SUCCESS(rc))
{
*ppReq = pReq;
return VINF_SUCCESS;
}
RTMemFree(pReq);
return rc;
}
/**
* Allocates a guest thread request with the specified request data.
*
* @return IPRT status code.
* @param ppReq Pointer that will receive the newly allocated request.
* Must be freed later with VBoxServiceControlThreadRequestFree().
* @param enmType Request type.
*/
int VBoxServiceControlThreadRequestAlloc(PVBOXSERVICECTRLREQUEST *ppReq,
VBOXSERVICECTRLREQUESTTYPE enmType)
{
return VBoxServiceControlThreadRequestAllocEx(ppReq, enmType,
NULL /* pvData */, 0 /* cbData */,
0 /* ContextID */);
}
/**
* Cancels a previously fired off guest thread request.
*
* Note: Does *not* do locking since VBoxServiceControlThreadRequestWait()
* holds the lock (critsect); so only trigger the signal; the owner
* needs to clean up afterwards.
*
* @return IPRT status code.
* @param pReq Request to cancel.
*/
static int vboxServiceControlThreadRequestCancel(PVBOXSERVICECTRLREQUEST pReq)
{
if (!pReq) /* Silently skip non-initialized requests. */
return VINF_SUCCESS;
VBoxServiceVerbose(4, "ControlThread: Cancelling request=0x%p\n", pReq);
return RTSemEventMultiSignal(pReq->Event);
}
/**
* Frees a formerly allocated guest thread request.
*
* @return IPRT status code.
* @param pReq Request to free.
*/
void VBoxServiceControlThreadRequestFree(PVBOXSERVICECTRLREQUEST pReq)
{
AssertPtrReturnVoid(pReq);
VBoxServiceVerbose(4, "ControlThread: Freeing request=0x%p (event=%RTsem)\n",
pReq, &pReq->Event);
int rc = RTSemEventMultiDestroy(pReq->Event);
AssertRC(rc);
RTMemFree(pReq);
pReq = NULL;
}
/**
* Waits for a guest thread's event to get triggered.
*
* @return IPRT status code.
* @param pReq Request to wait for.
*/
int VBoxServiceControlThreadRequestWait(PVBOXSERVICECTRLREQUEST pReq)
{
AssertPtrReturn(pReq, VERR_INVALID_POINTER);
/* Wait on the request to get completed (or we are asked to abort/shutdown). */
int rc = RTSemEventMultiWait(pReq->Event, RT_INDEFINITE_WAIT);
if (RT_SUCCESS(rc))
{
VBoxServiceVerbose(4, "ControlThread: Performed request with rc=%Rrc, cbData=%u\n",
pReq->rc, pReq->cbData);
/* Give back overall request result. */
rc = pReq->rc;
}
else
VBoxServiceError("ControlThread: Waiting for request failed, rc=%Rrc\n", rc);
return rc;
}
/**
* 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 VBoxServiceControlThreadSetupPipe(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 VBoxServiceControlThreadMakeFullPath(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, "ControlThread: 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.
*
* @return IPRT status code.
* @param pszFileName File name to resovle.
* @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 VBoxServiceControlThreadResolveExecutable(const char *pszFileName,
char *pszResolved, size_t cbResolved)
{
int rc = VINF_SUCCESS;
/* Search the path of our executable. */
char szVBoxService[RTPATH_MAX];
if (RTProcGetExecutablePath(szVBoxService, sizeof(szVBoxService)))
{
char *pszExecResolved = NULL;
if ( (g_pszProgName && RTStrICmp(pszFileName, g_pszProgName) == 0)
|| !RTStrICmp(pszFileName, VBOXSERVICE_NAME))
{
/* We just want to execute VBoxService (no toolbox). */
pszExecResolved = RTStrDup(szVBoxService);
}
else /* Nothing to resolve, copy original. */
pszExecResolved = RTStrDup(pszFileName);
AssertPtr(pszExecResolved);
rc = VBoxServiceControlThreadMakeFullPath(pszExecResolved, pszResolved, cbResolved);
#ifdef DEBUG
VBoxServiceVerbose(3, "ControlThread: VBoxServiceControlExecResolveExecutable: %s -> %s\n",
pszFileName, pszResolved);
#endif
RTStrFree(pszExecResolved);
}
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.
* @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 VBoxServiceControlThreadPrepareArgv(const char *pszArgv0,
const char * const *papszArgs, char ***ppapszArgv)
{
/** @todo RTGetOptArgvToString converts to MSC quoted string, while
* RTGetOptArgvFromString takes bourne shell according to the docs...
* Actually, converting to and from here is a very roundabout way of prepending
* an entry (pszFilename) to an array (*ppapszArgv). */
int rc = VINF_SUCCESS;
char *pszNewArgs = NULL;
if (pszArgv0)
rc = RTStrAAppend(&pszNewArgs, pszArgv0);
if ( RT_SUCCESS(rc)
&& papszArgs)
{
char *pszArgs;
rc = RTGetOptArgvToString(&pszArgs, papszArgs,
RTGETOPTARGV_CNV_QUOTE_MS_CRT); /* RTGETOPTARGV_CNV_QUOTE_BOURNE_SH */
if (RT_SUCCESS(rc))
{
rc = RTStrAAppend(&pszNewArgs, " ");
if (RT_SUCCESS(rc))
rc = RTStrAAppend(&pszNewArgs, pszArgs);
RTStrFree(pszArgs);
}
}
if (RT_SUCCESS(rc))
{
int iNumArgsIgnored;
rc = RTGetOptArgvFromString(ppapszArgv, &iNumArgsIgnored,
pszNewArgs ? pszNewArgs : "", NULL /* Use standard separators. */);
}
if (pszNewArgs)
RTStrFree(pszNewArgs);
return rc;
}
/**
* 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 VBoxServiceControlThreadCreateProcess(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];
#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") == 0)
{
/* Use a predefined sysprep path as default. */
char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
/*
* 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);
if ( GetVersionEx((LPOSVERSIONINFO) &OSInfoEx)
&& OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT
&& OSInfoEx.dwMajorVersion >= 6 /* Vista or later */)
{
rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
if (RT_SUCCESS(rc))
rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\sysprep\\sysprep.exe");
}
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
rc = VBoxServiceControlThreadPrepareArgv(szSysprepCmd /* argv0 */, papszArgs, &papszArgsExp);
if (RT_SUCCESS(rc))
{
rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */,
phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */,
NULL /* pszPassword */, phProcess);
RTGetOptArgvFree(papszArgsExp);
}
}
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 = VBoxServiceControlThreadResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp));
}
else
{
#endif
/*
* Do the environment variables expansion on executable and arguments.
*/
rc = VBoxServiceControlThreadResolveExecutable(pszExec, szExecExp, sizeof(szExecExp));
#ifdef VBOXSERVICE_TOOLBOX
}
#endif
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
rc = VBoxServiceControlThreadPrepareArgv(pszExec /* Always use the unmodified executable name as argv0. */,
papszArgs /* Append the rest of the argument vector (if any). */, &papszArgsExp);
if (RT_SUCCESS(rc))
{
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)
uProcFlags |= RTPROC_FLAGS_SERVICE;
#ifdef DEBUG
VBoxServiceVerbose(3, "ControlThread: Command: %s\n", szExecExp);
for (size_t i = 0; papszArgsExp[i]; i++)
VBoxServiceVerbose(3, "ControlThread:\targv[%ld]: %s\n", i, papszArgsExp[i]);
#endif
/* Do normal execution. */
rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags,
phStdIn, phStdOut, phStdErr,
*pszAsUser ? pszAsUser : NULL,
*pszPassword ? pszPassword : NULL,
phProcess);
RTGetOptArgvFree(papszArgsExp);
}
}
return rc;
}
/**
* The actual worker routine (loop) for a started guest process.
*
* @return IPRT status code.
* @param PVBOXSERVICECTRLTHREAD Thread data associated with a started process.
*/
static int VBoxServiceControlThreadProcessWorker(PVBOXSERVICECTRLTHREAD pThread)
{
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
VBoxServiceVerbose(3, "ControlThread: Thread of process \"%s\" started\n", pThread->pszCmd);
int rc = VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLIST_RUNNING, pThread);
AssertRC(rc);
rc = VbglR3GuestCtrlConnect(&pThread->uClientID);
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlThread: Thread failed to connect to the guest control service, aborted! Error: %Rrc\n", rc);
RTThreadUserSignal(RTThreadSelf());
return rc;
}
VBoxServiceVerbose(3, "ControlThread: Guest process \"%s\" got client ID=%u, flags=0x%x\n",
pThread->pszCmd, pThread->uClientID, pThread->uFlags);
bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */
/*
* Create the environment.
*/
RTENV hEnv;
rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
if (RT_SUCCESS(rc))
{
size_t i;
for (i = 0; i < pThread->uNumEnvVars && pThread->papszEnv; i++)
{
rc = RTEnvPutEx(hEnv, pThread->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 = VBoxServiceControlThreadSetupPipe("|", 0 /*STDIN_FILENO*/,
&hStdIn, &phStdIn, &pThread->pipeStdInW);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdOut;
PRTHANDLE phStdOut;
RTPIPE pipeStdOutR;
rc = VBoxServiceControlThreadSetupPipe( (pThread->uFlags & EXECUTEPROCESSFLAG_WAIT_STDOUT)
? "|" : "/dev/null",
1 /*STDOUT_FILENO*/,
&hStdOut, &phStdOut, &pipeStdOutR);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdErr;
PRTHANDLE phStdErr;
RTPIPE pipeStdErrR;
rc = VBoxServiceControlThreadSetupPipe( (pThread->uFlags & EXECUTEPROCESSFLAG_WAIT_STDERR)
? "|" : "/dev/null",
2 /*STDERR_FILENO*/,
&hStdErr, &phStdErr, &pipeStdErrR);
if (RT_SUCCESS(rc))
{
/*
* Create a poll set for the pipes and let the
* transport layer add stuff to it as well.
*/
RTPOLLSET hPollSet;
rc = RTPollSetCreate(&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(hPollSet, pThread->pipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN);
/* Stdout. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT);
/* Stderr. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR);
/* IPC notification pipe. */
if (RT_SUCCESS(rc))
rc = RTPipeCreate(&pThread->hNotificationPipeR, &pThread->hNotificationPipeW, 0 /* Flags */);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pThread->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY);
if (RT_SUCCESS(rc))
{
RTPROCESS hProcess;
rc = VBoxServiceControlThreadCreateProcess(pThread->pszCmd, pThread->papszArgs, hEnv, pThread->uFlags,
phStdIn, phStdOut, phStdErr,
pThread->pszUser, pThread->pszPassword,
&hProcess);
if (RT_FAILURE(rc))
VBoxServiceError("ControlThread: Error starting process, rc=%Rrc\n", rc);
/*
* Tell the control thread that it can continue
* spawning services. This needs to be done after the new
* process has been started because otherwise signal handling
* on (Open) Solaris does not work correctly (see #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 loop. */
rc = VBoxServiceControlThreadProcLoop(pThread,
hProcess, pThread->uTimeLimitMS, hPollSet,
&pThread->pipeStdInW, &pipeStdOutR, &pipeStdErrR);
/*
* 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(hPollSet, VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL)))
{
pThread->hNotificationPipeR = NIL_RTPIPE;
pThread->hNotificationPipeW = NIL_RTPIPE;
}
if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDERR, NULL)))
pipeStdErrR = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDOUT, NULL)))
pipeStdOutR = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDIN, NULL)))
pThread->pipeStdInW = NIL_RTPIPE;
}
}
RTPollSetDestroy(hPollSet);
RTPipeClose(pThread->hNotificationPipeR);
pThread->hNotificationPipeR = NIL_RTPIPE;
RTPipeClose(pThread->hNotificationPipeW);
pThread->hNotificationPipeW = NIL_RTPIPE;
}
RTPipeClose(pipeStdErrR);
pipeStdErrR = NIL_RTPIPE;
RTHandleClose(phStdErr);
if (phStdErr)
RTHandleClose(phStdErr);
}
RTPipeClose(pipeStdOutR);
pipeStdOutR = NIL_RTPIPE;
RTHandleClose(&hStdOut);
if (phStdOut)
RTHandleClose(phStdOut);
}
RTPipeClose(pThread->pipeStdInW);
pThread->pipeStdInW = NIL_RTPIPE;
RTHandleClose(phStdIn);
}
}
RTEnvDestroy(hEnv);
}
/* Move thread to stopped thread list. */
int rc2 = VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLIST_STOPPED, pThread);
AssertRC(rc2);
if (pThread->uClientID)
{
if (RT_FAILURE(rc))
{
rc2 = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID, pThread->uPID,
PROC_STS_ERROR, rc,
NULL /* pvData */, 0 /* cbData */);
if (RT_FAILURE(rc2))
VBoxServiceError("ControlThread: Could not report process failure error; rc=%Rrc (process error %Rrc)\n",
rc2, rc);
}
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Cancelling pending host requests (client ID=%u)\n",
pThread->uPID, pThread->uClientID);
rc2 = VbglR3GuestCtrlCancelPendingWaits(pThread->uClientID);
if (RT_FAILURE(rc2))
{
VBoxServiceError("ControlThread: [PID %u]: Cancelling pending host requests failed; rc=%Rrc\n",
pThread->uPID, rc2);
if (RT_SUCCESS(rc))
rc = rc2;
}
/* Disconnect from guest control service. */
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Disconnecting (client ID=%u) ...\n",
pThread->uPID, pThread->uClientID);
VbglR3GuestCtrlDisconnect(pThread->uClientID);
pThread->uClientID = 0;
}
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Thread of process \"%s\" ended with rc=%Rrc\n",
pThread->uPID, pThread->pszCmd, rc);
/* Update started/stopped status. */
ASMAtomicXchgBool(&pThread->fStopped, true);
ASMAtomicXchgBool(&pThread->fStarted, false);
/*
* 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());
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) VBoxServiceControlThread(RTTHREAD ThreadSelf, void *pvUser)
{
PVBOXSERVICECTRLTHREAD pThread = (VBOXSERVICECTRLTHREAD*)pvUser;
AssertPtrReturn(pThread, VERR_INVALID_POINTER);
return VBoxServiceControlThreadProcessWorker(pThread);
}
/**
* 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 uContextID Context ID to associate the process to start with.
* @param pProcess Process info.
*/
int VBoxServiceControlThreadStart(uint32_t uContextID,
PVBOXSERVICECTRLPROCESS pProcess)
{
AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
/*
* Allocate new thread data and assign it to our thread list.
*/
PVBOXSERVICECTRLTHREAD pThread = (PVBOXSERVICECTRLTHREAD)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREAD));
if (!pThread)
return VERR_NO_MEMORY;
int rc = gstsvcCntlExecThreadInit(pThread, pProcess, 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(&pThread->Thread, VBoxServiceControlThread,
pThread /*pvUser*/, 0 /*cbStack*/,
RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%u", s_uCtrlExecThread);
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlThread: RTThreadCreate failed, rc=%Rrc\n, pThread=%p\n",
rc, pThread);
}
else
{
VBoxServiceVerbose(4, "ControlThread: Waiting for thread to initialize ...\n");
/* Wait for the thread to initialize. */
rc = RTThreadUserWait(pThread->Thread, 60 * 1000 /* 60 seconds max. */);
AssertRC(rc);
if ( ASMAtomicReadBool(&pThread->fShutdown)
|| RT_FAILURE(rc))
{
VBoxServiceError("ControlThread: Thread for process \"%s\" failed to start, rc=%Rrc\n",
pProcess->szCmd, rc);
}
else
{
ASMAtomicXchgBool(&pThread->fStarted, true);
}
}
}
if (RT_FAILURE(rc))
RTMemFree(pThread);
return rc;
}
/**
* Performs a request to a specific (formerly started) guest process and waits
* for its response.
*
* @return IPRT status code.
* @param uPID PID of guest process to perform a request to.
* @param pRequest Pointer to request to perform.
*/
int VBoxServiceControlThreadPerform(uint32_t uPID, PVBOXSERVICECTRLREQUEST pRequest)
{
AssertPtrReturn(pRequest, VERR_INVALID_POINTER);
AssertReturn(pRequest->enmType > VBOXSERVICECTRLREQUEST_UNKNOWN, VERR_INVALID_PARAMETER);
/* Rest in pRequest is optional (based on the request type). */
int rc = VINF_SUCCESS;
PVBOXSERVICECTRLTHREAD pThread = VBoxServiceControlLockThread(uPID);
if (pThread)
{
if (ASMAtomicReadBool(&pThread->fShutdown))
{
rc = VERR_CANCELLED;
}
else
{
/* Set request structure pointer. */
pThread->pRequest = pRequest;
/** @todo To speed up simultaneous guest process handling we could add a worker threads
* or queue in order to wait for the request to happen. Later. */
/* Wake up guest thrad by sending a wakeup byte to the notification pipe so
* that RTPoll unblocks (returns) and we then can do our requested operation. */
Assert(pThread->hNotificationPipeW != NIL_RTPIPE);
size_t cbWritten;
if (RT_SUCCESS(rc))
rc = RTPipeWrite(pThread->hNotificationPipeW, "i", 1, &cbWritten);
if ( RT_SUCCESS(rc)
&& cbWritten)
{
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Waiting for response on enmType=%u, pvData=0x%p, cbData=%u\n",
uPID, pRequest->enmType, pRequest->pvData, pRequest->cbData);
rc = VBoxServiceControlThreadRequestWait(pRequest);
}
}
VBoxServiceControlUnlockThread(pThread);
}
else /* PID not found! */
rc = VERR_NOT_FOUND;
VBoxServiceVerbose(3, "ControlThread: [PID %u]: Performed enmType=%u, uCID=%u, pvData=0x%p, cbData=%u, rc=%Rrc\n",
uPID, pRequest->enmType, pRequest->uCID, pRequest->pvData, pRequest->cbData, rc);
return rc;
}