VBoxServiceControl.cpp revision 68a95b86122c4c00bce18f0905444b1d15ff5935
/* $Id$ */
/** @file
* VBoxServiceControl - Host-driven Guest Control.
*/
/*
* 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 "VBoxServiceUtils.h"
using namespace guestControl;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** The control interval (milliseconds). */
/** The semaphore we're blocking our main control thread on. */
/** The guest control service client ID. */
static uint32_t g_GuestControlSvcClientID = 0;
/** How many started guest processes are kept into memory for supplying
* information to the host. Default is 25 processes. If 0 is specified,
* the maximum number of processes is unlimited. */
/** List of guest control threads. */
/** Critical section protecting g_GuestControlExecThreads. */
static int VBoxServiceControlStartAllowed(bool *pbAllowed);
/** @copydoc VBOXSERVICE::pfnPreInit */
static DECLCALLBACK(int) VBoxServiceControlPreInit(void)
{
#ifdef VBOX_WITH_GUEST_PROPS
/*
* Read the service options from the VM's guest properties.
* Note that these options can be overridden by the command line options later.
*/
if (RT_FAILURE(rc))
{
{
VBoxServiceVerbose(0, "Control: Guest property service is not available, skipping\n");
rc = VINF_SUCCESS;
}
else
}
else
{
rc = VBoxServiceReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--control-procs-max-kept",
}
rc = VINF_SUCCESS;
return rc;
#else
/* Nothing to do here yet. */
return VINF_SUCCESS;
#endif
}
/** @copydoc VBOXSERVICE::pfnOption */
static DECLCALLBACK(int) VBoxServiceControlOption(const char **ppszShort, int argc, char **argv, int *pi)
{
int rc = -1;
if (ppszShort)
/* no short options */;
return rc;
}
/** @copydoc VBOXSERVICE::pfnInit */
static DECLCALLBACK(int) VBoxServiceControlInit(void)
{
/*
* If not specified, find the right interval default.
* Then create the event sem to block on.
*/
if (!g_ControlInterval)
g_ControlInterval = 1000;
if (RT_SUCCESS(rc))
{
/* Init thread list. */
}
else
{
/* If the service was not found, we disable this service without
causing VBoxService to fail. */
{
VBoxServiceVerbose(0, "Control: Guest control service is not available\n");
}
else
}
return rc;
}
/** @copydoc VBOXSERVICE::pfnWorker */
{
/*
* Tell the control thread that it can continue
* spawning services.
*/
int rc = VINF_SUCCESS;
/*
* Execution loop.
*
* @todo
*/
for (;;)
{
if (RT_FAILURE(rc))
{
if (rc == VERR_TOO_MUCH_DATA)
{
VBoxServiceVerbose(4, "Control: Message requires %ld parameters, but only 2 supplied -- retrying request (no error!)...\n", uNumParms);
}
else
VBoxServiceVerbose(3, "Control: Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
}
if (RT_SUCCESS(rc))
{
switch(uMsg)
{
break;
case HOST_EXEC_CMD:
break;
case HOST_EXEC_SET_INPUT:
/** @todo Make buffer size configurable via guest properties/argv! */
rc = VBoxServiceControlHandleCmdSetInput(g_GuestControlSvcClientID, uNumParms, _1M /* Buffer size */);
break;
case HOST_EXEC_GET_OUTPUT:
break;
default:
/* Don't terminate here; just wait for the next message. */
break;
}
}
/* Do we need to shutdown? */
if ( *pfShutdown
|| uMsg == HOST_CANCEL_PENDING_WAITS)
{
rc = VINF_SUCCESS;
break;
}
/* Let's sleep for a bit and let others run ... */
}
return rc;
}
/**
* Handles starting processes on the guest.
*
* @returns IPRT status code.
* @param u32ClientId The HGCM client session ID.
* @param uNumParms The number of parameters the host is offering.
*/
{
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_SUCCESS(rc))
{
#ifdef DEBUG
VBoxServiceVerbose(3, "ControlExec: Start process szCmd=%s, uFlags=%u, szArgs=%s, szEnv=%s, szUser=%s, szPW=%s, uTimeout=%u\n",
szCmd, uFlags, uNumArgs ? szArgs : "<None>", uNumEnvVars ? szEnv : "<None>", szUser, szPassword, uTimeLimitMS);
#endif
bool fAllowed = false;
if (RT_FAILURE(rc))
VBoxServiceError("ControlExec: Error determining whether process can be started or not, rc=%Rrc\n", rc);
if ( RT_SUCCESS(rc)
&& fAllowed)
{
if (RT_SUCCESS(rc))
{
/** @todo Put the following params into a struct! */
&pThreadNode);
if (RT_SUCCESS(rc))
{
/* Insert thread node into thread list. */
}
if (RT_SUCCESS(rc))
}
}
else /* Process start is not allowed due to policy settings. */
{
/* Tell the host. */
}
}
else
return rc;
}
/**
*
* @return IPRT status code.
* @param uPID PID of process to retrieve the output from.
* @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from.
* @param uTimeout Timeout (in ms) to wait for output becoming available.
* @param pvBuf Pointer to a pre-allocated buffer to store the output.
* @param cbBuf Size (in bytes) of the pre-allocated buffer.
* @param pcbRead Pointer to number of bytes read. Optional.
*/
{
/* pcbRead is optional. */
int rc = VINF_SUCCESS;
switch (uHandleId)
{
case OUTPUT_HANDLE_ID_STDERR:
break;
case OUTPUT_HANDLE_ID_STDOUT:
break;
default:
break;
}
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
if (pcbRead)
}
else /* Something went wrong, nothing read. */
*pcbRead = 0;
return rc;
}
/**
* Injects input to a specified running process.
*
* @return IPRT status code.
* @param uPID PID of process to set the input for.
* @param fPendingClose Flag indicating whether this is the last input block sent to the process.
* @param pvBuf Pointer to a buffer containing the actual input data.
* @param cbBuf Size (in bytes) of the input buffer data.
* @param pcbWritten Pointer to number of bytes written to the process. Optional.
*/
{
/* pcbWritten is optional. */
int rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
if (pcbWritten)
}
return rc;
}
/**
* Handles input for a started process by copying the received data into its
* stdin pipe.
*
* @returns IPRT status code.
* @param u32ClientId The HGCM client session ID.
* @param uNumParms The number of parameters the host is offering.
* @param cMaxBufSize The maximum buffer size for retrieving the input data.
*/
int VBoxServiceControlHandleCmdSetInput(uint32_t u32ClientId, uint32_t uNumParms, size_t cbMaxBufSize)
{
/*
* Ask the host for the input data.
*/
if (RT_FAILURE(rc))
{
VBoxServiceError("ControlExec: [PID %u]: Failed to retrieve exec input command! Error: %Rrc\n",
}
else if (cbSize > cbMaxBufSize)
{
VBoxServiceError("ControlExec: [PID %u]: Too much input received! cbSize=%u, cbMaxBufSize=%u\n",
}
else
{
/*
* Is this the last input block we need to deliver? Then let the pipe know ...
*/
bool fPendingClose = false;
if (uFlags & INPUT_FLAG_EOF)
{
fPendingClose = true;
}
VBoxServiceVerbose(4, "ControlExec: [PID %u]: Written input, rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n",
if (RT_SUCCESS(rc))
{
{
uFlags = 0;
}
}
else
{
if (rc == VERR_BAD_PIPE)
else if (rc == VERR_BUFFER_OVERFLOW)
}
}
/*
* 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)
{
}
VBoxServiceVerbose(3, "ControlExec: [PID %u]: Input processed, CID=%u, uStatus=%u, uFlags=0x%x, cbWritten=%u\n",
/* Note: Since the context ID is unique the request *has* to be completed here,
* regardless whether we got data or not! Otherwise the progress object
* on the host never will get completed! */
if (RT_FAILURE(rc))
VBoxServiceError("ControlExec: [PID %u]: Failed to report input status! Error: %Rrc\n",
return rc;
}
/**
* Handles the guest control output command.
*
* @return IPRT status code.
* @param u32ClientId idClient The HGCM client session ID.
* @param uNumParms cParms The number of parameters the host is
* offering.
*/
{
if (RT_SUCCESS(rc))
{
if (pBuf)
{
if (RT_SUCCESS(rc))
VBoxServiceVerbose(3, "ControlExec: [PID %u]: Got output, CID=%u, cbRead=%u, uHandle=%u, uFlags=%u\n",
else
VBoxServiceError("ControlExec: [PID %u]: Failed to retrieve output, CID=%u, uHandle=%u, rc=%Rrc\n",
/* Note: Since the context ID is unique the request *has* to be completed here,
* regardless whether we got data or not! Otherwise the progress object
* on the host never will get completed! */
/* cbRead now contains actual size. */
if (RT_SUCCESS(rc))
}
else
rc = VERR_NO_MEMORY;
}
if (RT_FAILURE(rc))
VBoxServiceError("ControlExec: [PID %u]: Error handling output command! Error: %Rrc\n",
return rc;
}
/** @copydoc VBOXSERVICE::pfnStop */
static DECLCALLBACK(void) VBoxServiceControlStop(void)
{
/** @todo Later, figure what to do if we're in RTProcWait(). It's a very
* annoying call since doesn't support timeouts in the posix world. */
if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
/*
* Ask the host service to cancel all pending requests so that we can
* shutdown properly here.
*/
{
if (RT_FAILURE(rc))
}
}
static void VBoxServiceControlDestroyThreads(void)
{
/* Signal all threads that we want to shutdown. */
/* Wait for threads to shutdown and destroy thread list. */
while (pThread)
{
30 * 1000 /* Wait 30 seconds max. */);
if (RT_FAILURE(rc2))
if (fLast)
break;
}
("Guest process thread list still contains children when it should not\n"));
/* Destroy critical section. */
}
/** @copydoc VBOXSERVICE::pfnTerm */
static DECLCALLBACK(void) VBoxServiceControlTerm(void)
{
if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
{
}
}
/**
* Determines whether starting a new guest process according to the
* maximum number of concurrent guest processes defined is allowed or not.
*
* @return IPRT status code.
* @param pbAllowed True if starting (another) guest process
* is allowed, false if not.
*/
static int VBoxServiceControlStartAllowed(bool *pbAllowed)
{
if (RT_SUCCESS(rc))
{
/*
* Check if we're respecting our memory policy by checking
* how many guest processes are started and served already.
*/
bool fLimitReached = false;
if (g_GuestControlProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
{
uint32_t uProcsRunning = 0;
uint32_t uProcsStopped = 0;
{
// THREAD LOCKING!!
else
AssertMsgFailed(("ControlExec: Guest process neither started nor stopped!?\n"));
}
VBoxServiceVerbose(2, "ControlExec: Maximum served guest processes set to %u, running=%u, stopped=%u\n",
if (iProcsLeft < 0)
{
fLimitReached = true;
}
}
*pbAllowed = !fLimitReached;
if (RT_SUCCESS(rc))
}
return rc;
}
/**
* Finds a (formerly) started process given by its PID.
*
* @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL.
* @param uPID PID to search for.
*/
{
if (RT_SUCCESS(rc))
{
{
{
break;
}
}
if (RT_SUCCESS(rc))
}
return pThread;
}
/**
* Removes the specified guest process thread from the global thread
* list.
*
* @return IPRT status code.
* @param pThread Thread to remove.
*/
{
if (!pThread)
return;
if (RT_SUCCESS(rc))
{
}
}
/**
* The 'vminfo' service description.
*/
{
/* pszName. */
"control",
/* pszDescription. */
"Host-driven Guest Control",
/* pszUsage. */
" [--control-interval <ms>] [--control-procs-max-kept <x>]\n"
" [--control-procs-mem-std[in|out|err] <KB>]"
,
/* pszOptions. */
" --control-interval Specifies the interval at which to check for\n"
" new control commands. The default is 1000 ms.\n"
" --control-procs-max-kept\n"
" Specifies how many started guest processes are\n"
" kept into memory to work with. Default is 25.\n"
,
/* methods */
};