VBoxServiceControlExec.cpp revision 9eea21d61089fe62b80ef3f4549600091c2b1967
/* $Id$ */
/** @file
* VBoxServiceControlExec - Utility functions for process execution.
*/
/*
* Copyright (C) 2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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/semaphore.h>
#include <VBox/VBoxGuestLib.h>
#include "VBoxServiceInternal.h"
#include "VBoxServiceUtils.h"
using namespace guestControl;
extern RTLISTNODE g_GuestControlExecThreads;
/**
* Handle an error event on standard input.
*
* @returns IPRT status code.
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phStdInW The standard input pipe handle.
* @param pStdInBuf The standard input buffer.
*/
static int VBoxServiceControlExecProcHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
{
if (RT_SUCCESS(rc))
{
int rc2;
/* If no data is to be processed anymore, remove the writable pipe from
* the poll set. */
{
*phStdInW = NIL_RTPIPE;
/* Mark the stdin buffer as dead; we're not using it anymore. */
}
if (RT_SUCCESS(rc))
}
return rc;
}
/**
* Try write some more data to the standard input of the child.
*
* @returns IPRT status code.
* @param pStdInBuf The standard input buffer.
* @param hStdInW The standard input pipe.
*/
static int VBoxServiceControlExecProcWriteStdIn(PVBOXSERVICECTRLEXECPIPEBUF pStdInBuf, RTPIPE hStdInW, size_t *pcbWritten)
{
if (RT_SUCCESS(rc))
{
int rc = VINF_SUCCESS;
{
if (RT_SUCCESS(rc))
{
}
else
*pcbWritten = 0;
}
else
rc = VERR_BAD_PIPE;
if (RT_SUCCESS(rc))
}
return rc;
}
/**
* Handle an event indicating we can write to the standard input pipe of the
* child process.
*
* @returns IPRT status code.
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phStdInW The standard input pipe.
* @param pStdInBuf The standard input buffer.
*/
static int VBoxServiceControlExecProcHandleStdInWritableEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
{
int rc = VINF_SUCCESS;
if (!(fPollEvt & RTPOLL_EVT_ERROR))
{
if ( RT_FAILURE(rc)
&& rc != VERR_BAD_PIPE)
{
/** @todo Do we need to do something about this error condition? */
}
/* No more data to write to stdin? Then remove stdin from poll set. */
if ( cbWritten <= 0
|| rc == VERR_BAD_PIPE)
{
}
}
else
return rc;
}
static int VBoxServiceControlExecProcHandleInputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
{
int rc = VINF_SUCCESS;
if (fPollEvt & RTPOLL_EVT_ERROR)
return rc;
}
/**
* Handle pending output data or error on standard out, standard error or the
* test pipe.
*
* @returns IPRT status code from client send.
* @param pThread The thread specific data.
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param phPipeR The pipe handle.
* @param uHandleId The handle ID.
*
* @todo Put the last 4 parameters into a struct!
*/
static int VBoxServiceControlExecProcHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phPipeR,
{
/*
* Try drain the pipe before acting on any errors.
*/
int rc = VINF_SUCCESS;
{
#if 0
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlExec: Error while sending real-time output data, rc=%Rrc, cbRead=%u, CID=%u, PID=%u\n",
}
else
{
#endif
if (RT_SUCCESS(rc))
{
/* Make sure we go another poll round in case there was too much data
for the buffer to hold. */
}
#if 0
}
#endif
}
else if (RT_FAILURE(rc2))
{
}
/*
* If an error was raised signalled,
*/
if (fPollEvt & RTPOLL_EVT_ERROR)
{
*phPipeR = NIL_RTPIPE;
}
return rc;
}
/**
* Handle a transport event or successful pfnPollIn() call.
*
* @returns IPRT status code from client send.
* @retval VINF_EOF indicates ABORT command.
*
* @param hPollSet The polling set.
* @param fPollEvt The event mask returned by RTPollNoResume.
* @param idPollHnd The handle ID.
* @param hStdInW The standard input pipe.
* @param pStdInBuf The standard input buffer.
*/
static int VBoxServiceControlExecProcHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd,
{
return 0; //RTPollSetAddPipe(hPollSet, *phStdInW, RTPOLL_EVT_WRITE, 4 /*TXSEXECHNDID_STDIN_WRITABLE*/);
}
{
int rc;
int rc2;
bool fProcessAlive = true;
bool fProcessTimedOut = false;
? 100 /* Need to poll for input. */
: 1000; /* Need only poll for process exit and aborts. */
RTMSINTERVAL cMsPollCur = 0;
/* Assign PID to thread data. */
/*
* 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.
*/
/*
* Process input, output, the test pipe and client requests.
*/
while ( RT_SUCCESS(rc)
{
/*
*/
continue;
cMsPollCur = 0; /* No rest until we've checked everything. */
if (RT_SUCCESS(rc2))
{
switch (idPollHnd)
{
break;
rc = VBoxServiceControlExecProcHandleStdInWritableEvent(hPollSet, fPollEvt, &hStdInW, &pData->stdIn);
break;
break;
break;
default:
//rc = VBoxServiceControlExecProcHandleTransportEvent(hPollSet, fPollEvt, idPollHnd, &hStdInW, &pData->stdIn);
break;
}
break; /* Abort command, or client dead or something. */
continue;
}
/*
* Check for process death.
*/
if (fProcessAlive)
{
if (RT_SUCCESS_NP(rc2))
{
fProcessAlive = false;
continue;
}
continue;
{
fProcessAlive = false;
AssertFailed();
}
else
}
/*
* If the process has terminated, we're should head out.
*/
if (!fProcessAlive)
break;
/*
* Check for timed out, killing the process.
*/
if (cMillies != RT_INDEFINITE_WAIT)
{
if (cMsElapsed >= cMillies)
{
VBoxServiceVerbose(3, "ControlExec: Process timed out (%ums elapsed > %ums timeout), killing ...", cMsElapsed, cMillies);
fProcessTimedOut = true;
if ( MsProcessKilled == UINT64_MAX
{
break; /* give up after 20 mins */
continue;
}
cMilliesLeft = 10000;
}
else
}
/* Reset the polling interval since we've done all pending work. */
/*
* Need to exit?
*/
break;
}
/*
* Try kill the process if it's still alive at this point.
*/
if (fProcessAlive)
{
if (MsProcessKilled == UINT64_MAX)
{
RTThreadSleep(500);
}
for (size_t i = 0; i < 10; i++)
{
if (RT_SUCCESS(rc2))
{
fProcessAlive = false;
break;
}
if (i >= 5)
{
}
}
if (fProcessAlive)
}
/*
* If we don't have a client problem (RT_FAILURE(rc) we'll reply to the
* clients exec packet now.
*/
if (RT_SUCCESS(rc))
{
{
}
{
}
{
VBoxServiceVerbose(3, "ControlExec: Process got terminated because system/service is about to shutdown\n");
}
else if (fProcessAlive)
{
VBoxServiceError("ControlExec: Process is alive when it should not!\n");
}
else if (MsProcessKilled != UINT64_MAX)
{
VBoxServiceError("ControlExec: Process has been killed when it should not!\n");
}
{
}
{
}
{
}
else
{
VBoxServiceError("ControlExec: Process has reached an undefined status!\n");
}
}
else
return rc;
}
/**
* Sets up the redirection / pipe / nothing for one of the standard handles.
*
* @returns IPRT status code. No client replies made.
* @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. Always set.
*/
{
*phPipe = NIL_RTPIPE;
int rc;
/*
* to represent the "other" end to phPipe.
*/
if (fd == 0) /* stdin? */
{
/* Connect a wrtie pipe specified by phPipe to stdin. */
}
else /* stdout or stderr? */
{
/* Connect a read pipe specified by phPipe to stdout or stderr. */
}
if (RT_FAILURE(rc))
return rc;
return rc;
}
{
/** @todo Add allocation size as function parameter! */
pBuf->cbProcessed = 0;
}
{
if (pBuf)
{
pBuf->cbAllocated = 0;
pBuf->cbProcessed = 0;
}
}
{
if (RT_SUCCESS(rc))
{
if (*pcbToRead > 0)
{
}
else
{
*pcbToRead = 0;
}
}
return rc;
}
{
if (RT_SUCCESS(rc))
{
{
/** @todo Buffer size limit! 4MB */
/* Resize buffer if not enough space for storing the read in data left. */
{
break;
}
rc = VINF_SUCCESS;
{
/** @todo Add offset clamping! */
}
else
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
}
return rc;
}
/** Allocates and gives back a thread data struct which then can be used by the worker thread. */
{
/* General stuff. */
/* ClientID will be assigned when thread is started! */
/* Specific stuff. */
PVBOXSERVICECTRLTHREADDATAEXEC pData = (PVBOXSERVICECTRLTHREADDATAEXEC)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREADDATAEXEC));
return VERR_NO_MEMORY;
pData->uNumEnvVars = 0;
/* Prepare argument list. */
/* Did we get the same result? */
if (RT_SUCCESS(rc))
{
/* Prepare environment list. */
if (uNumEnvVars)
{
uint32_t i = 0;
{
/* sanity check */
if (i >= uNumEnvVars)
{
break;
}
if (cbStr < 0)
{
rc = VERR_NO_MEMORY;
break;
}
}
}
/* Adjust time limit value. */
|| (uTimeLimitMS == 0)) ?
/* Init buffers. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
}
if (RT_FAILURE(rc))
{
}
else
{
}
return rc;
}
/** Frees an allocated thread data structure along with all its allocated parameters. */
{
if (pData)
{
if (pData->uNumEnvVars)
{
}
}
}
int VBoxServiceControlExecCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
{
int rc = VINF_SUCCESS;
#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).
*/
{
/* Get the predefined path of sysprep.exe (depending on Windows OS). */
{
if (RT_SUCCESS(rc))
}
}
else
{
#endif
/* Do normal execution. */
#ifdef RT_OS_WINDOWS
}
#endif
return rc;
}
{
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlExec: Thread failed to connect to the guest control service, aborted! Error: %Rrc\n", rc);
return rc;
}
bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */
/*
* Create the environment.
*/
if (RT_SUCCESS(rc))
{
size_t 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. */
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Create a poll set for the pipes and let the
* transport layer add stuff to it as well.
*/
if (RT_SUCCESS(rc))
{
rc = RTPollSetAddPipe(hPollSet, pData->pipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN_ERROR);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDOUT);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDERR);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pData->pipeStdInW, RTPOLL_EVT_WRITE, VBOXSERVICECTRLPIPEID_STDIN_WRITABLE);
if (RT_SUCCESS(rc))
{
rc = VBoxServiceControlExecCreateProcess(pData->pszCmd, pData->papszArgs, hEnv, RTPROC_FLAGS_SERVICE,
&hProcess);
/*
* 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).
*/
if (RT_FAILURE(rc2))
fSignalled = true;
if (RT_SUCCESS(rc))
{
/*
* Close the child ends of any pipes and redirected files.
*/
/* Enter the process loop. */
/*
* 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.
*/
}
else /* Something went wrong; report error! */
{
VBoxServiceError("ControlExec: Could not start process '%s' (CID: %u)! Error: %Rrc\n",
if (RT_FAILURE(rc2))
VBoxServiceError("ControlExec: Could not report process start error! Error: %Rrc (process error %Rrc)\n",
}
}
}
}
}
}
}
}
/*
* If something went wrong signal the user event so that others don't wait
* forever on this thread.
*/
return rc;
}
{
bool fFound = false;
{
{
{
return pNode;
}
}
}
return NULL;
}
{
}
{
int rc;
if (pThread)
{
if (RT_SUCCESS(rc))
{
(void *)(PVBOXSERVICECTRLTHREAD*)pThread, 0,
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlExec: RTThreadCreate failed, rc=%Rrc\n, pThread=%p\n",
}
else
{
/* Wait for the thread to initialize. */
{
}
else
{
}
}
if (RT_FAILURE(rc))
}
if (RT_FAILURE(rc))
}
else
rc = VERR_NO_MEMORY;
return rc;
}
/**
*
*
* @return int
*
* @param u32ClientId
* @param uNumParms
*/
{
char szUser[128];
char szPassword[128];
#if 0 /* for valgrind */
#endif
if (uNumParms != 11)
return VERR_INVALID_PARAMETER;
/* Command */
/* Flags */
&uFlags,
/* Arguments */
/* Environment */
/* Credentials */
szPassword, sizeof(szPassword),
/* Timelimit */
&uTimeLimitMS);
if (RT_FAILURE(rc))
{
}
else
{
}
VBoxServiceVerbose(3, "ControlExec: VBoxServiceControlExecHandleCmdStartProcess returned with %Rrc\n", rc);
return rc;
}
/**
* Handles input for the started process by copying the received data into its
* stdin pipe.
*
* @return int
*
* @param u32ClientId
* @param uNumParms
*/
{
if (uNumParms != 5)
return VERR_INVALID_PARAMETER;
if (RT_FAILURE(rc))
{
}
else
{
if (pNode)
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
}
}
else
}
VBoxServiceVerbose(3, "ControlExec: VBoxServiceControlExecHandleCmdSetInput returned with %Rrc\n", rc);
return rc;
}
/**
*
*
* @return int
*
* @param u32ClientId
* @param uNumParms
*/
{
if (RT_FAILURE(rc))
{
}
else
{
if (pNode)
{
if (pBuf)
{
if (RT_SUCCESS(rc))
{
/* cbRead now contains actual size. */
}
}
else
rc = VERR_NO_MEMORY;
}
else
}
VBoxServiceVerbose(3, "ControlExec: VBoxServiceControlExecHandleCmdGetOutput returned with %Rrc\n", rc);
return rc;
}