VBoxServiceControlExecThread.cpp revision b47f1bf67de12aaf4f0827597196f26a5539c34b
/* $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;
* 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 "VBoxServiceControlExecThread.h"
using namespace guestControl;
/* Internal functions. */
/**
* Allocates and gives back a thread data struct which then can be used by the worker thread.
* Needs to be freed with VBoxServiceControlExecDestroyThreadData().
*
* @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 pszCmd Full qualified path of process to start (without arguments).
* @param uFlags Process execution flags.
* @param pszArgs String of arguments to pass to the process to start.
* @param uNumArgs Number of arguments specified in pszArgs.
* @param pszEnv String of environment variables ("FOO=BAR") to pass to the process
* to start.
* @param cbEnv Size (in bytes) of environment variables.
* @param uNumEnvVars Number of environment variables specified in pszEnv.
* @param pszUser User name (account) to start the process under.
* @param pszPassword Password of specified user name (account).
* @param uTimeLimitMS Time limit (in ms) of the process' life time.
*/
{
/* General stuff. */
/* ClientID will be assigned when thread is started! */
if (RT_FAILURE(rc))
return rc;
|| uTimeLimitMS == 0)
/* Prepare argument list. */
/* Did we get the same result? */
if (RT_SUCCESS(rc))
{
/* Prepare environment list. */
pThread->uNumEnvVars = 0;
if (uNumEnvVars)
{
uint32_t i = 0;
{
/* sanity check */
if (i >= uNumEnvVars)
{
break;
}
if (cbStr < 0)
{
break;
}
}
}
/* User management. */
}
return rc;
}
/**
* TODO
*
* @param pThread
*/
{
/* First, set the shutdown flag. */
if (RT_FAILURE(rc))
}
{
int rc = VINF_SUCCESS;
{
/* Wait a bit ... */
}
return rc;
}
/**
* Frees an allocated thread data structure along with all its allocated parameters.
*
* @param pThread Pointer to thread data to free.
*/
{
if (pThread)
{
if (pThread->uNumEnvVars)
{
}
}
}
/**
* Closes the stdin pipe of a guest process.
*
* @return IPRT status code.
* @param hPollSet The polling set.
* @param phStdInW The standard input pipe handle.
*/
{
if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND)
*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 VBoxServiceControlExecProcHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE 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.
*
*/
{
int rc = VINF_SUCCESS;
#if 0
if (fPollEvt & RTPOLL_EVT_READ)
{
/** @todo Later: Notify the host about the read operation! */
/* Make sure we go another poll round in case there was too much data
for the buffer to hold. */
/* Remove read event from poll set and just poll for errors from now on.
* This is necessary for doing a RTPipeRead. */
}
#endif
#ifdef DEBUG_andy
#endif
/*
* If an error was raised signalled
*/
if (fPollEvt & RTPOLL_EVT_ERROR)
{
*phPipeR = NIL_RTPIPE;
}
return rc;
}
{
#ifdef DEBUG_andy
#endif
/* Drain the notification pipe. */
if (RT_FAILURE(rc))
return rc;
}
{
#ifdef DEBUG_andy
#endif
int rc = VINF_SUCCESS;
{
case VBOXSERVICECTRLREQUEST_QUIT: /* Main control asked us to quit. */
/** @todo Check for some conditions to check to
* veto quitting. */
break;
/* Fall through is intentional. */
{
if (*phStdInW != NIL_RTPIPE)
{
}
else
/* If this is the last write we need to close the stdin pipe on our
* end and remove it from the poll set. */
/* Reqport back actual data written (if any). */
break;
}
/* Fall through is intentional. */
{
if (*pPipeR != NIL_RTPIPE)
{
if (rcReq == VERR_BROKEN_PIPE)
}
else
/* Report back actual data read (if any). */
break;
}
default:
break;
}
return rc;
}
/**
* Execution loop which runs in a dedicated per-started-process thread and
*
* @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.
*/
{
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.
* 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!
*/
if (RT_FAILURE(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.
*/
/*
* 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))
{
/*VBoxServiceVerbose(4, "ControlExec: [PID %u}: RTPollNoResume idPollHnd=%u\n",
pThread->uPID, idPollHnd);*/
switch (idPollHnd)
{
break;
break;
break;
/* Fall through is intentional. */
default:
/* Handle IPC requests. */
if (RT_SUCCESS(rc))
{
}
/* If we were asked to terminate do so ... */
/* In any case, regardless of the result, we notify
* the main guest control to unblock it. */
/* No access to pRequest here anymore -- could be out of scope
* or modified already! */
break;
}
break; /* Abort command, or client dead or something. */
/* Only notification pipe left? Then there's nothing to poll for anymore really.
* Bail out .. */
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 (cMsTimeout != RT_INDEFINITE_WAIT)
{
if (cMsElapsed >= cMsTimeout)
{
VBoxServiceVerbose(3, "ControlExec: [PID %u]: Timed out (%ums elapsed > %ums timeout), killing ...",
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))
{
/* Mark this thread as stopped and do some action required for stopping ... */
//VBoxServiceControlExecThreadCleanup(pThread);
{
}
{
}
{
VBoxServiceVerbose(3, "ControlExec: [PID %u]: Got terminated because system/service is about to shutdown\n",
}
else if (fProcessAlive)
{
VBoxServiceError("ControlExec: [PID %u]: Is alive when it should not!\n",
}
else if (MsProcessKilled != UINT64_MAX)
{
VBoxServiceError("ControlExec: [PID %u]: Has been killed when it should not!\n",
}
{
}
{
}
{
}
else
if (RT_FAILURE(rc))
VBoxServiceError("ControlExec: [PID %u]: Error reporting final status to host; rc=%Rrc\n",
}
else
VBoxServiceError("ControlExec: [PID %u]: Loop failed with rc=%Rrc\n",
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;
}
/**
* 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 VBoxServiceControlExecMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded)
{
int rc = VINF_SUCCESS;
#ifdef RT_OS_WINDOWS
#else
/* No expansion for non-Windows yet. */
#endif
#ifdef DEBUG
#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 VBoxServiceControlExecResolveExecutable(const char *pszFileName, char *pszResolved, size_t cbResolved)
{
int rc = VINF_SUCCESS;
/* Search the path of our executable. */
char szVBoxService[RTPATH_MAX];
{
char *pszExecResolved = NULL;
{
/* We just want to execute VBoxService (no toolbox). */
}
else /* Nothing to resolve, copy original. */
#ifdef DEBUG
#endif
}
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 VBoxServiceControlExecPrepareArgv(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)
if ( RT_SUCCESS(rc)
&& papszArgs)
{
char *pszArgs;
RTGETOPTARGV_CNV_QUOTE_MS_CRT); /* RTGETOPTARGV_CNV_QUOTE_BOURNE_SH */
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
}
}
if (RT_SUCCESS(rc))
{
int iNumArgsIgnored;
}
if (pszNewArgs)
return rc;
}
/**
*
* @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 VBoxServiceControlExecCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
{
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).
*/
{
/* Use a predefined sysprep path as default. */
/*
* On Windows Vista (and up) sysprep is located in "system32\\sysprep\\sysprep.exe",
* so detect the OS and use a different path.
*/
{
if (RT_SUCCESS(rc))
}
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
if (RT_SUCCESS(rc))
{
}
}
return rc;
}
#endif /* RT_OS_WINDOWS */
#ifdef VBOXSERVICE_TOOLBOX
{
/* We want to use the internal toolbox (all internal
* tools are starting with "vbox_" (e.g. "vbox_cat"). */
}
else
{
#endif
/*
* Do the environment variables expansion on executable and arguments.
*/
#ifdef VBOXSERVICE_TOOLBOX
}
#endif
if (RT_SUCCESS(rc))
{
char **papszArgsExp;
rc = VBoxServiceControlExecPrepareArgv(pszExec /* Always use the unmodified executable name as argv0. */,
if (RT_SUCCESS(rc))
{
uint32_t uProcFlags = 0;
if (fFlags)
{
/* Process Main flag "ExecuteProcessFlag_Hidden". */
/* Process Main flag "ExecuteProcessFlag_NoProfile". */
}
/* If no user name specified run with current credentials (e.g.
*
* 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)
#ifdef DEBUG
for (size_t i = 0; papszArgsExp[i]; i++)
#endif
/* Do normal execution. */
}
}
return rc;
}
/**
* The actual worker routine (lopp) for a started guest process.
*
* @return IPRT status code.
* @param PVBOXSERVICECTRLTHREAD Thread data associated with a started process.
*/
{
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))
{
/* Stdin. */
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pThread->pipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN);
/* Stdout. */
if (RT_SUCCESS(rc))
/* Stderr. */
if (RT_SUCCESS(rc))
/* IPC notification pipe. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, pThread->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY);
if (RT_SUCCESS(rc))
{
rc = VBoxServiceControlExecCreateProcess(pThread->pszCmd, pThread->papszArgs, hEnv, pThread->uFlags,
&hProcess);
if (RT_FAILURE(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).
*/
if (RT_FAILURE(rc2))
fSignalled = true;
if (RT_SUCCESS(rc))
{
/*
* Close the child ends of any pipes and redirected files.
*/
/* Enter the process loop. */
/* Before cleaning up everything else, remove the thread from our thread list. */
/*
* 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",
}
}
}
}
}
}
}
}
/* Disconnect from guest control service. */
if (RT_SUCCESS(rc2))
/*
* If something went wrong signal the user event so that others don't wait
* forever on this thread.
*/
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.
*
*/
{
}
/**
* 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 uClientID Client ID for accessing host service.
* @param uContextID Context ID to associate the process to start with.
* @param pszCmd Full qualified path of process to start (without arguments).
* @param uFlags Process execution flags.
* @param pszArgs String of arguments to pass to the process to start.
* @param uNumArgs Number of arguments specified in pszArgs.
* @param pszEnv String of environment variables ("FOO=BAR") to pass to the process
* to start.
* @param cbEnv Size (in bytes) of environment variables.
* @param uNumEnvVars Number of environment variables specified in pszEnv.
* @param pszUser User name (account) to start the process under.
* @param pszPassword Password of specified user name (account).
* @param uTimeLimitMS Time limit (in ms) of the process' life time.
* @param ppNode The thread's list node to insert into the global thread list
* on success.
*/
{
/*
* Allocate new thread data and assign it to our thread list.
*/
if (!pThread)
return VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
static uint32_t uCtrlExecThread = 0;
char szThreadName[32];
AssertMsgFailed(("Unable to create unique control exec thread name!\n"));
(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
{
/* Return the thread's node. */
}
}
}
if (RT_FAILURE(rc))
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 pData Pointer to guest control execution thread data.
* @param uPID PID to assign to the specified guest control execution thread.
*/
{
int rc = VINF_SUCCESS;
/* Search an old thread using the desired PID and shut it down completely -- it's
* not used anymore. */
if ( pOldThread
&& pOldThread != pThread)
{
uPID);
if (RT_FAILURE(rc))
{
/* Keep going. */
}
}
/* Assign PID to current thread. */
return rc;
}
/**
* TODO
*
* @return IPRT status code.
* @return int
* @param uPID
* @param pRequest
*/
{
/* Rest in pRequest is optional (based on the request type). */
int rc;
if (pThread)
{
if (RT_SUCCESS(rc))
{
/* Set request structure pointer. */
/** @todo To speed up simultaneous guest process handling we could add a worker threads 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. */
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/* Give back overall request result. */
/* Reset the semaphore. */
if (RT_FAILURE(rc2))
}
}
}
}
else /* PID not found! */
rc = VERR_NOT_FOUND;
VBoxServiceVerbose(4, "ControlExec: [PID %u]: Performed enmType=%u, pvData=0x%p, cbData=%u with rc=%Rrc\n",
return rc;
}
/**
* Removes the guest thread from the global guest thread list and finally
* destroys it. Does not do locking, must be done by the caller!
*
* @param pThread Thread to destroy.
*/
{
if (!pThread)
return;
/* Destroy thread structure as final step. */
}
/**
* Shuts down a guest thread.
* Does not do locking, must be done by the caller!
*
* @return IPRT status code.
* @param pThread Thread to shut down.
*/
{
return VINF_SUCCESS;
/* First, signal shut down. */
/*
* Wait for thread to shutdown.
*/
int rc;
for (int i = 0; i < 3; i++)
{
if (RT_SUCCESS(rc))
break;
VBoxServiceError("ControlExec: [PID %u]: Attempt #%d: Waiting for shutdown failed with rc=%Rrc ...\n",
}
/* Do not destroy critical section here! */
/*
* Destroy thread-specific data.
*/
return rc;
}