tstGuestControlSvc.cpp revision 468c2bcb36eb9a032f5dd0fcb34db10bd58e9996
/* $Id$ */
/** @file
*
* Testcase for the guest control service.
*/
/*
* Copyright (C) 2010 Sun Microsystems, Inc.
*
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <VBox/HostServices/GuestControlSvc.h>
#include <iprt/alloca.h>
#include <iprt/initterm.h>
#include <iprt/crc32.h>
#include <iprt/ctype.h>
#include <iprt/env.h>
#include <iprt/file.h>
#include <iprt/getopt.h>
#include <iprt/handle.h>
#include <iprt/mem.h>
#include <iprt/message.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/pipe.h>
#include <iprt/poll.h>
#include <iprt/process.h>
#include <iprt/stream.h>
#include <iprt/thread.h>
#include "../gctrl.h"
using namespace guestControl;
extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *pTable);
char g_szImageName[RTPATH_MAX];
/** Prototypes. */
int guestExecSendStdOut(VBOXHGCMSVCFNTABLE *pTable,
const char *pszStdOut, uint32_t cbStdOut);
/** Simple call handle structure for the guest call completion callback */
struct VBOXHGCMCALLHANDLE_TYPEDEF
{
/** Where to store the result code */
int32_t rc;
};
/**
* For buffering process input supplied by the client.
*/
typedef struct TXSEXECSTDINBUF
{
/** The mount of buffered data. */
size_t cb;
/** The current data offset. */
size_t off;
/** The data buffer. */
char *pch;
/** The amount of allocated buffer space. */
size_t cbAllocated;
/** Send further input into the bit bucket (stdin is dead). */
bool fBitBucket;
/** The CRC-32 for standard input (received part). */
uint32_t uCrc32;
} TXSEXECSTDINBUF;
/** Pointer to a standard input buffer. */
typedef TXSEXECSTDINBUF *PTXSEXECSTDINBUF;
/** Call completion callback for guest calls. */
static void callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
{
callHandle->rc = rc;
}
/**
* Initialise the HGCM service table as much as we need to start the
* service
* @param pTable the table to initialise
*/
void initTable(VBOXHGCMSVCFNTABLE *pTable, VBOXHGCMSVCHELPERS *pHelpers)
{
pTable->cbSize = sizeof (VBOXHGCMSVCFNTABLE);
pTable->u32Version = VBOX_HGCM_SVC_VERSION;
pHelpers->pfnCallComplete = callComplete;
pTable->pHelpers = pHelpers;
}
/**
* 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. Always set.
*/
static int guestExecSetupPipe(int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe)
{
AssertPtr(ph);
AssertPtr(pph);
AssertPtr(phPipe);
ph->enmType = RTHANDLETYPE_PIPE;
ph->u.hPipe = NIL_RTPIPE;
*pph = NULL;
*phPipe = NIL_RTPIPE;
int rc;
/*
* 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;
return rc;
}
/**
* Handle an error event on standard input.
*
* @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 void guestExecProcHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
PTXSEXECSTDINBUF pStdInBuf)
{
int rc2;
if (pStdInBuf->off < pStdInBuf->cb)
{
rc2 = RTPollSetRemove(hPollSet, 4 /*TXSEXECHNDID_STDIN_WRITABLE*/);
AssertRC(rc2);
}
rc2 = RTPollSetRemove(hPollSet, 0 /*TXSEXECHNDID_STDIN*/);
AssertRC(rc2);
rc2 = RTPipeClose(*phStdInW);
AssertRC(rc2);
*phStdInW = NIL_RTPIPE;
RTMemFree(pStdInBuf->pch);
pStdInBuf->pch = NULL;
pStdInBuf->off = 0;
pStdInBuf->cb = 0;
pStdInBuf->cbAllocated = 0;
pStdInBuf->fBitBucket = true;
}
/**
* 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 guestExecProcWriteStdIn(PTXSEXECSTDINBUF pStdInBuf, RTPIPE hStdInW)
{
size_t cbToWrite = pStdInBuf->cb - pStdInBuf->off;
size_t cbWritten;
int rc = RTPipeWrite(hStdInW, &pStdInBuf->pch[pStdInBuf->off], cbToWrite, &cbWritten);
if (RT_SUCCESS(rc))
{
Assert(cbWritten == cbToWrite);
pStdInBuf->off += cbWritten;
}
return rc;
}
/**
* Handle an event indicating we can write to the standard input pipe of the
* child process.
*
* @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 void guestExecProcHandleStdInWritableEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
PTXSEXECSTDINBUF pStdInBuf)
{
int rc;
if (!(fPollEvt & RTPOLL_EVT_ERROR))
{
rc = guestExecProcWriteStdIn(pStdInBuf, *phStdInW);
if (RT_FAILURE(rc) && rc != VERR_BAD_PIPE)
{
/** @todo do we need to do something about this error condition? */
AssertRC(rc);
}
if (pStdInBuf->off < pStdInBuf->cb)
{
rc = RTPollSetRemove(hPollSet, 4 /*TXSEXECHNDID_STDIN_WRITABLE*/);
AssertRC(rc);
}
}
else
guestExecProcHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW, pStdInBuf);
}
/**
* Handle pending output data or error on standard out, standard error or the
* test pipe.
*
* @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 pu32Crc The current CRC-32 of the stream. (In/Out)
* @param uHandleId The handle ID.
* @param pszOpcode The opcode for the data upload.
*
* @todo Put the last 4 parameters into a struct!
*/
static int guestExecProcHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phPipeR,
uint32_t *puCrc32 , uint32_t uHandleId)
{
Log(("guestExecProcHandleOutputEvent: fPollEvt=%#x\n", fPollEvt));
/*
* Try drain the pipe before acting on any errors.
*/
int rc = VINF_SUCCESS;
char abBuf[_64K];
size_t cbRead;
int rc2 = RTPipeRead(*phPipeR, abBuf, sizeof(abBuf), &cbRead);
if (RT_SUCCESS(rc2) && cbRead)
{
Log(("Crc32=%#x ", *puCrc32));
#if 1
abBuf[cbRead] = '\0';
RTPrintf("%s: %s\n", uHandleId == 1 ? "StdOut: " : "StdErr: ", abBuf);
#endif
/**puCrc32 = RTCrc32Process(*puCrc32, abBuf, cbRead);
Log(("cbRead=%#x Crc32=%#x \n", cbRead, *puCrc32));
Pkt.uCrc32 = RTCrc32Finish(*puCrc32);*/
/* if (g_fDisplayOutput)
{
if (enmHndId == TXSEXECHNDID_STDOUT)
RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf);
else if (enmHndId == TXSEXECHNDID_STDERR)
RTStrmPrintf(g_pStdErr, "%.*s", cbRead, Pkt.abBuf);
}
rc = txsReplyInternal(&Pkt.Hdr, pszOpcode, cbRead + sizeof(uint32_t));*/
/* Make sure we go another poll round in case there was too much data
for the buffer to hold. */
fPollEvt &= RTPOLL_EVT_ERROR;
}
else if (RT_FAILURE(rc2))
{
fPollEvt |= RTPOLL_EVT_ERROR;
AssertMsg(rc2 == VERR_BROKEN_PIPE, ("%Rrc\n", rc));
}
/*
* If an error was raised signalled,
*/
if (fPollEvt & RTPOLL_EVT_ERROR)
{
rc2 = RTPollSetRemove(hPollSet, uHandleId);
AssertRC(rc2);
rc2 = RTPipeClose(*phPipeR);
AssertRC(rc2);
*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 guestExecProcHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd,
PRTPIPE phStdInW, PTXSEXECSTDINBUF pStdInBuf)
{
int rc = RTPollSetAddPipe(hPollSet, *phStdInW, RTPOLL_EVT_WRITE, 4 /*TXSEXECHNDID_STDIN_WRITABLE*/);
return rc;
}
static int guestExecProcLoop(VBOXHGCMSVCFNTABLE *pTable,
RTPROCESS hProcess, RTMSINTERVAL cMillies, RTPOLLSET hPollSet,
RTPIPE hStdInW, RTPIPE hStdOutR, RTPIPE hStdErrR)
{
int rc;
int rc2;
TXSEXECSTDINBUF StdInBuf = { 0, 0, NULL, 0, hStdInW == NIL_RTPIPE, RTCrc32Start() };
uint32_t uStdOutCrc32 = RTCrc32Start();
uint32_t uStdErrCrc32 = uStdOutCrc32;
uint64_t const MsStart = RTTimeMilliTS();
RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND };
bool fProcessAlive = true;
bool fProcessTimedOut = false;
uint64_t MsProcessKilled = UINT64_MAX;
bool const fHavePipes = hStdInW != NIL_RTPIPE
|| hStdOutR != NIL_RTPIPE
|| hStdErrR != NIL_RTPIPE;
RTMSINTERVAL const cMsPollBase = hStdInW != NIL_RTPIPE
? 100 /* need to poll for input */
: 1000; /* need only poll for process exit and aborts */
RTMSINTERVAL cMsPollCur = 0;
/*
* 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.
*/
rc = VINF_SUCCESS;
/*
* Process input, output, the test pipe and client requests.
*/
while (RT_SUCCESS(rc))
{
/*
* Wait/Process all pending events.
*/
uint32_t idPollHnd;
uint32_t fPollEvt;
rc2 = RTPollNoResume(hPollSet, cMsPollCur, &fPollEvt, &idPollHnd);
cMsPollCur = 0; /* no rest until we've checked everything. */
if (RT_SUCCESS(rc2))
{
switch (idPollHnd)
{
case 0 /* TXSEXECHNDID_STDIN */:
guestExecProcHandleStdInErrorEvent(hPollSet, fPollEvt, &hStdInW, &StdInBuf);
break;
case 1 /* TXSEXECHNDID_STDOUT */:
rc = guestExecProcHandleOutputEvent(hPollSet, fPollEvt, &hStdOutR, &uStdOutCrc32, 1 /* TXSEXECHNDID_STDOUT */);
break;
case 2 /*TXSEXECHNDID_STDERR */:
rc = guestExecProcHandleOutputEvent(hPollSet, fPollEvt, &hStdErrR, &uStdErrCrc32, 2 /*TXSEXECHNDID_STDERR */);
break;
case 4 /* TXSEXECHNDID_STDIN_WRITABLE */:
guestExecProcHandleStdInWritableEvent(hPollSet, fPollEvt, &hStdInW, &StdInBuf);
break;
default:
rc = guestExecProcHandleTransportEvent(hPollSet, fPollEvt, idPollHnd, &hStdInW, &StdInBuf);
break;
}
if (RT_FAILURE(rc) || rc == VINF_EOF)
break; /* abort command, or client dead or something */
continue;
}
/*
* Check for incoming data.
*/
/*
* 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, we're should head out.
*/
if (!fProcessAlive)
break;
/*
* Check for timed out, killing the process.
*/
uint32_t cMilliesLeft = RT_INDEFINITE_WAIT;
if (cMillies != RT_INDEFINITE_WAIT)
{
uint64_t u64Now = RTTimeMilliTS();
uint64_t cMsElapsed = u64Now - MsStart;
if (cMsElapsed >= cMillies)
{
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 = cMillies - (uint32_t)cMsElapsed;
}
/* Reset the polling interval since we've done all pending work. */
cMsPollCur = cMilliesLeft >= cMsPollBase ? cMsPollBase : cMilliesLeft;
}
/*
* Try kill the process if it's still alive at this point.
*/
if (fProcessAlive)
{
if (MsProcessKilled == UINT64_MAX)
{
MsProcessKilled = RTTimeMilliTS();
RTProcTerminate(hProcess);
RTThreadSleep(500);
}
for (size_t i = 0; i < 10; i++)
{
rc2 = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
if (RT_SUCCESS(rc2))
{
fProcessAlive = false;
break;
}
if (i >= 5)
RTProcTerminate(hProcess);
RTThreadSleep(i >= 5 ? 2000 : 500);
}
}
/*
* If we don't have a client problem (RT_FAILURE(rc) we'll reply to the
* clients exec packet now.
*/
if (RT_SUCCESS(rc))
{
if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX)
{
}
else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX)
{
}
/*else if (g_fTerminate && (fProcessAlive || MsProcessKilled != UINT64_MAX))
{
}*/
else if (fProcessAlive)
{
}
else if (MsProcessKilled != UINT64_MAX)
{
}
else if ( ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL
&& ProcessStatus.iStatus == 0)
{
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL)
{
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL)
{
}
else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND)
{
}
else
{
}
}
RTMemFree(StdInBuf.pch);
return rc;
}
int guestExecProcess(VBOXHGCMSVCFNTABLE *pTable,
uint32_t fFlags, const char *pszExecName,
uint32_t cArgs, const char * const *papszArgs,
uint32_t cEnvVars, const char * const *papszEnv,
const char *pszStdIn, const char *pszStdOut, const char *pszStdErr,
const char *pszUsername, const char *pszPassword, RTMSINTERVAL cMillies)
{
#if 0
/* Print some info */
RTPrintf("Flags: %u\n"
"# Args: %u\n"
"# Env: %u\n"
"StdIn: %s, StdOut: %s, StdErr: %s\n"
"User: %s, Timelimit: %u\n",
fFlags, cArgs, cEnvVars,
pszStdIn, pszStdOut, pszStdErr,
pszUsername ? pszUsername : "<None>", cMillies);
for (uint32_t i=0; i<cArgs; i++)
RTPrintf("Arg %u: %s\n", i, papszArgs[i]);
for (uint32_t i=0; i<cEnvVars; i++)
RTPrintf("Env %u: %s\n", i, papszEnv[i]);
#endif
/*
* Create the environment.
*/
RTENV hEnv;
int rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
if (RT_SUCCESS(rc))
{
size_t i;
for (i = 0; i < cEnvVars; i++)
{
rc = RTEnvPutEx(hEnv, papszEnv[i]);
if (RT_FAILURE(rc))
break;
}
if (RT_SUCCESS(rc))
{
/*
* Setup the redirection of the standard stuff.
*/
/** @todo consider supporting: gcc stuff.c >file 2>&1. */
RTHANDLE hStdIn;
PRTHANDLE phStdIn;
RTPIPE hStdInW;
rc = guestExecSetupPipe(0 /* stdin */, &hStdIn, &phStdIn, &hStdInW);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdOut;
PRTHANDLE phStdOut;
RTPIPE hStdOutR;
rc = guestExecSetupPipe(1 /* stdout */, &hStdOut, &phStdOut, &hStdOutR);
if (RT_SUCCESS(rc))
{
RTHANDLE hStdErr;
PRTHANDLE phStdErr;
RTPIPE hStdErrR;
rc = guestExecSetupPipe(2 /* stderr */, &hStdErr, &phStdErr, &hStdErrR);
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))
{
rc = RTPollSetAddPipe(hPollSet, hStdInW, RTPOLL_EVT_ERROR, 0 /* TXSEXECHNDID_STDIN */);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1 /* TXSEXECHNDID_STDOUT */);
if (RT_SUCCESS(rc))
rc = RTPollSetAddPipe(hPollSet, hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2 /* TXSEXECHNDID_TESTPIPE */);
if (RT_SUCCESS(rc))
{
RTPROCESS hProcess;
rc = RTProcCreateEx(pszExecName, papszArgs, hEnv, 0 /*fFlags*/,
phStdIn, phStdOut, phStdErr,
/*pszUsername, pszPassword,*/ NULL, NULL,
&hProcess);
if (RT_SUCCESS(rc))
{
/*
* Close the child ends of any pipes and redirected files.
*/
int rc2 = RTHandleClose(phStdIn); AssertRC(rc2);
phStdIn = NULL;
rc2 = RTHandleClose(phStdOut); AssertRC(rc2);
phStdOut = NULL;
rc2 = RTHandleClose(phStdErr); AssertRC(rc2);
phStdErr = NULL;
rc = guestExecProcLoop(pTable,
hProcess, cMillies, hPollSet,
hStdInW, hStdOutR, hStdErrR);
/*
* 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, 0 /* stdin */, NULL)))
hStdInW = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, 1 /* stdout */, NULL)))
hStdOutR = NIL_RTPIPE;
if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, 2 /* stderr */, NULL)))
hStdErrR = NIL_RTPIPE;
}
}
}
RTPipeClose(hStdErrR);
RTHandleClose(phStdErr);
}
RTPipeClose(hStdOutR);
RTHandleClose(phStdOut);
}
RTPipeClose(hStdInW);
RTHandleClose(phStdIn);
}
}
RTEnvDestroy(hEnv);
}
return rc;
}
int guestExecHandleCmdExecute(VBOXHGCMSVCFNTABLE *pTable, PVBOXHGCMSVCPARM paParms, uint32_t cParms)
{
char *pcBuf;
uint32_t cbLen;
if(cParms < 13)
return VERR_INVALID_PARAMETER;
/* flags */
uint32_t uFlags;
int rc = paParms[1].getUInt32(&uFlags);
if (RT_SUCCESS(rc))
{
/* cmd */
rc = paParms[2].getString(&pcBuf, &cbLen);
char *pszExecName = RTStrDup(pcBuf);
/* arguments */
if ( pszExecName
&& RT_SUCCESS(rc))
{
/* argc */
uint32_t uArgc;
paParms[3].getUInt32(&uArgc);
/* argv */
char *pcData;
uint32_t cbLen;
paParms[4].getBuffer((void**)&pcData, &cbLen);
AssertPtr(pcData);
char **ppaArg;
int iArgs;
rc = RTGetOptArgvFromString(&ppaArg, &iArgs, pcData, NULL);
Assert(uArgc == iArgs);
/* environment */
if (RT_SUCCESS(rc))
{
/* envc */
uint32_t uEnvc;
paParms[5].getUInt32(&uEnvc);
/* envv */
char *pcData;
uint32_t cbLen;
paParms[6].getBuffer((void**)&pcData, &cbLen);
AssertPtr(pcData);
char **ppaEnv;
if (uEnvc)
{
ppaEnv = (char**)RTMemAlloc(uEnvc * sizeof(char*));
AssertPtr(ppaEnv);
char *pcCur = pcData;
uint32_t i = 0;
while (pcCur < pcData + cbLen)
{
if (RTStrAPrintf(&ppaEnv[i++], "%s", pcCur) < 0)
{
rc = VERR_NO_MEMORY;
break;
}
pcCur += strlen(pcCur) + 1; /* Skip terminating zero. */
}
}
if (RT_SUCCESS(rc))
{
/* stdin */
rc = paParms[7].getString(&pcBuf, &cbLen);
char *pszStdIn = RTStrDup(pcBuf);
if ( pszStdIn
&& RT_SUCCESS(rc))
{
/* stdout */
rc = paParms[8].getString(&pcBuf, &cbLen);
char *pszStdOut = RTStrDup(pcBuf);
if ( pszStdOut
&& RT_SUCCESS(rc))
{
/* stderr */
rc = paParms[9].getString(&pcBuf, &cbLen);
char *pszStdErr = RTStrDup(pcBuf);
if ( pszStdErr
&& RT_SUCCESS(rc))
{
/* user */
rc = paParms[10].getString(&pcBuf, &cbLen);
char *pszUser = RTStrDup(pcBuf);
if ( pszUser
&& RT_SUCCESS(rc))
{
/* password */
rc = paParms[11].getString(&pcBuf, &cbLen);
char *pszPassword = RTStrDup(pcBuf);
if (RT_SUCCESS(rc))
{
/* time to wait (0 = max, no time limit) */
RTMSINTERVAL msTimeLimit;
rc = paParms[12].getUInt32(&msTimeLimit);
if (RT_SUCCESS(rc))
{
rc = guestExecProcess(pTable,
uFlags, pszExecName,
uArgc, ppaArg,
uEnvc, ppaEnv,
pszStdIn, pszStdOut, pszStdIn,
pszUser, pszPassword,
msTimeLimit == UINT32_MAX ? RT_INDEFINITE_WAIT : msTimeLimit);
}
}
RTStrFree(pszUser);
}
RTStrFree(pszStdErr);
}
RTStrFree(pszStdOut);
}
RTStrFree(pszStdIn);
}
for (uint32_t i=0; i<uEnvc; i++)
RTStrFree(ppaEnv[i]);
RTMemFree(ppaEnv);
}
RTGetOptArgvFree(ppaArg);
}
RTStrFree(pszExecName);
}
}
return rc;
}
/** Sends the current stdout (stderr later?) data to the host. */
int guestExecSendStdOut(VBOXHGCMSVCFNTABLE *pTable,
const char *pszStdOut, uint32_t cbStdOut)
{
VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS };
int command = GUEST_EXEC_SEND_STDOUT;
VBOXHGCMSVCPARM paParms[3];
if ( pszStdOut == NULL
|| cbStdOut == 0)
{
return VERR_INVALID_PARAMETER;
}
paParms[0].setUInt32(123 /* @todo PID. */);
paParms[1].setUInt32(1 /* @todo Pipe ID. */);
paParms[2].setPointer((void*)pszStdOut, cbStdOut + 1);
pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, command,
3, paParms);
int rc;
if (RT_FAILURE(callHandle.rc))
{
RTPrintf("guestSendStdOut failed with callHandle.rc=%Rrc\n", callHandle.rc);
rc = callHandle.rc;
}
return rc;
}
/** Gets a new message (command) from the host and does the appropriate action. */
int guestGetHostMsg(VBOXHGCMSVCFNTABLE *pTable)
{
VBOXHGCMCALLHANDLE_TYPEDEF callHandle = { VINF_SUCCESS };
int command = GUEST_GET_HOST_MSG;
VBOXHGCMSVCPARM paParms[13];
/* Work around silly constant issues - we ought to allow passing
* constant strings in the hgcm parameters. */
pTable->pfnCall(pTable->pvService, &callHandle, 0, NULL, command,
13, paParms);
int rc;
if (RT_FAILURE(callHandle.rc))
{
RTPrintf("guestGetHostMsg failed with callHandle.rc=%Rrc\n", callHandle.rc);
rc = callHandle.rc;
}
else
{
uint32_t uCmd;
/*
* Parameter 0 *always* specifies the actual command the host wants
* to execute.
*/
rc = paParms[0].getUInt32(&uCmd);
if (RT_SUCCESS(rc))
{
switch(uCmd)
{
case GETHOSTMSG_EXEC_CMD:
rc = guestExecHandleCmdExecute(pTable, paParms, 13);
break;
case GETHOSTMSG_EXEC_STDIN:
//rc = guestExecHandleCmdStdIn(pTable, paParms, 12);
break;
default:
RTPrintf("guestGetHostMsg: Invalid host command: %u\n", uCmd);
rc = VERR_INVALID_PARAMETER;
break;
}
}
}
return rc;
}
/** This represents the execution service thread in VBoxService. */
static DECLCALLBACK(int) guestThread(RTTHREAD Thread, void *pvUser)
{
VBOXHGCMSVCFNTABLE *pTable = (VBOXHGCMSVCFNTABLE*)pvUser;
AssertPtr(pTable);
int rc = guestGetHostMsg(pTable);
if(RT_FAILURE(rc))
return rc;
/* We don't remove the current request from the host queue yet,
* so don't try to get a new host message more than once atm. */
for(;;) /* Run forever atm. */
{
RTThreadSleep(1);
/** @tdo Add graceful shutdown here. */
}
return rc;
}
int hostExecCmd(VBOXHGCMSVCFNTABLE *pTable,
uint32_t u32Flags,
const char *pszCmd,
uint32_t u32Argc,
const void *pvArgs,
uint32_t cbArgs,
uint32_t u32Envc,
const void *pvEnv,
uint32_t cbEnv,
const char *pszStdin,
const char *pszStdOut,
const char *pszStdErr,
const char *pszUser,
const char *pszPassword,
uint32_t u32TimeLimit)
{
int command = HOST_EXEC_CMD;
VBOXHGCMSVCPARM paParms[13];
/** @todo Put some assert macros here! */
paParms[0].setUInt32(HOST_EXEC_CMD);
paParms[1].setUInt32(u32Flags);
paParms[2].setPointer((void*)pszCmd, (uint32_t)strlen(pszCmd) + 1);
paParms[3].setUInt32(u32Argc);
paParms[4].setPointer((void*)pvArgs, cbArgs);
paParms[5].setUInt32(u32Envc);
paParms[6].setPointer((void*)pvEnv, cbEnv);
paParms[7].setPointer((void*)pszStdin, (uint32_t)strlen(pszStdin) + 1);
paParms[8].setPointer((void*)pszStdOut, (uint32_t)strlen(pszStdOut) + 1);
paParms[9].setPointer((void*)pszStdErr, (uint32_t)strlen(pszStdErr) + 1);
paParms[10].setPointer((void*)pszUser, (uint32_t)strlen(pszUser) + 1);
paParms[11].setPointer((void*)pszPassword, (uint32_t)strlen(pszPassword) + 1);
paParms[12].setUInt32(u32TimeLimit);
int rc = pTable->pfnHostCall(pTable->pvService, command,
13, paParms);
if (RT_FAILURE(rc))
{
RTPrintf("hostDoExecute() call failed with rc=%Rrc\n", rc);
}
return rc;
}
int hostExecGetStatus(VBOXHGCMSVCFNTABLE *pTable)
{
int command = HOST_EXEC_GET_STATUS;
VBOXHGCMSVCPARM paParms[13];
/** @todo Put some assert macros here! */
paParms[0].setUInt32(0 /* @todo 0=Execute */);
return 0;
}
/**
* Test the EXECUTE function.
* @returns iprt status value to indicate whether the test went as expected.
* @note prints its own diagnostic information to stdout.
*/
int testExecute(VBOXHGCMSVCFNTABLE *pTable)
{
RTPrintf("Testing the EXECUTE call.\n");
if (!VALID_PTR(pTable->pfnHostCall))
{
RTPrintf("Invalid pfnHostCall() pointer\n");
return VERR_INVALID_POINTER;
}
int rc = VINF_SUCCESS;
/*
* The host code (= later Main?) will run in this thread,
* while the client (guest) code will run in another one (= VBoxService in guest).
*/
RTTHREAD threadGuest;
rc = RTThreadCreate(&threadGuest, guestThread, pTable /* Save call table. */,
0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "GUEST");
/* This is the host code. */
if(RT_SUCCESS(rc))
{
void *pvArgs;
uint32_t cArgs;
uint32_t cbArgs;
void *pvEnv = NULL;
uint32_t cEnv;
uint32_t cbEnv;
char szCmdLine[_1K];
/* Test stdout*/
RTStrPrintf(szCmdLine, sizeof(szCmdLine),
"%s --test-stdout", g_szImageName); /* Include image name as argv[0]. */
rc = gctrlPrepareExecArgv(szCmdLine, &pvArgs, &cbArgs, &cArgs);
if (RT_SUCCESS(rc))
{
rc = gctrlAddToExecEnvv("VBOX_LOG=all", &pvEnv, &cbEnv, &cEnv);
if (RT_SUCCESS(rc))
rc = gctrlAddToExecEnvv("VBOX_LOG_DEST=file=c:\\baz\\barfoo.txt", &pvEnv, &cbEnv, &cEnv);
if (RT_SUCCESS(rc))
rc = gctrlAddToExecEnvv("HOME=iN-WoNdeRlAnd", &pvEnv, &cbEnv, &cEnv);
if (RT_SUCCESS(rc))
{
rc = hostExecCmd(pTable,
456123,
g_szImageName,
cArgs,
pvArgs,
cbArgs,
cEnv,
pvEnv,
cbEnv,
"|",
"|",
"|",
"vbox",
"password",
UINT32_MAX);
RTMemFree(pvEnv);
}
RTMemFree(pvArgs);
}
if (RT_SUCCESS(rc))
{
for(;;)
{
RTThreadSleep(1);
}
}
/* Wait for guest thread to finish. */
int rc2;
rc = RTThreadWait(threadGuest, RT_INDEFINITE_WAIT, &rc2);
if (RT_FAILURE(rc))
RTPrintf("RTThreadWait failed, rc=%Rrc\n", rc);
else if (RT_FAILURE(rc2))
RTPrintf("Thread failed, rc2=%Rrc\n", rc2);
}
return rc;
}
int selfTestExecStdOut(int h, int argc, char **argv)
{
RTStrmPrintf(g_pStdOut, "This is the first text to StdOut!\n");
RTStrmPrintf(g_pStdErr, "Hum, this really should be ignored because it's stderr! :-/\n");
/*for (int i=0; i<10; i++)
{
RTStrmPrintf(g_pStdOut, "This is the i=%d to StdOut! Waiting ...\n", i+1);
RTStrmPrintf(g_pStdErr, "This is the i=%d to StdErr!\n", i+1);
RTThreadSleep(1000);
}*/
RTThreadSleep(5000);
return VINF_SUCCESS;
}
RTEXITCODE selfTestExecStd(int h, int argc, char **argv)
{
int rc = VINF_SUCCESS;
#if 0
/* Dump arguments and environment. */
RTENV env;
rc = RTEnvCreate(&env);
if (RT_SUCCESS(rc))
{
const char *const *pcEnv = RTEnvGetExecEnvP(env);
RTPrintf("Environment: %s\n" , pcEnv);
rc = RTEnvDestroy(env);
}
#endif
/* Do the test(s) based on the handle number(s). */
switch (h)
{
case 0: /* stdin */
break;
case 1: /* stdout */
rc = selfTestExecStdOut(h, argc, argv);
break;
case 2: /* stderr */
break;
case 3: /* all std* */
break;
}
return RTEXITCODE_SUCCESS;
}
/**
* Parses the arguments.
*
* @returns Exit code. Exit if this is non-zero or @a *pfExit is set.
* @param argc The number of arguments.
* @param argv The argument vector.
* @param pfExit For indicating exit when the exit code is zero.
*/
static RTEXITCODE parseArgv(int argc, char **argv, bool *pfExit)
{
#if 1
RTPrintf("ArgC: %d\n", argc);
for (int i=0; i<argc; i++)
RTPrintf("ArgV: %s\n", argv[i]);
#endif
if (argc == 2)
{
*pfExit = true;
if (!strcmp(argv[1], "--test-stdin"))
return selfTestExecStd(0 /* stdin */, argc, argv);
else if (!strcmp(argv[1], "--test-stdout"))
return selfTestExecStd(1 /* stdout */, argc, argv);
else if (!strcmp(argv[1], "--test-stderr"))
return selfTestExecStd(2 /* stderr */, argc, argv);
else if (!strcmp(argv[1], "--test-all"))
return selfTestExecStd(3 /* all */, argc, argv);
RTPrintf("Unknown test! Exit.\n");
return RTEXITCODE_FAILURE;
}
return RTEXITCODE_SUCCESS;
}
int main(int argc, char **argv)
{
int rc = RTR3Init();
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
/* Save image name for later use. */
if (!RTProcGetExecutableName(g_szImageName, sizeof(g_szImageName)))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutableName failed\n");
VBOXHGCMSVCFNTABLE svcTable;
VBOXHGCMSVCHELPERS svcHelpers;
initTable(&svcTable, &svcHelpers);
RTPrintf("PID: %u\n", RTProcSelf ());
bool fExit = false;
rc = parseArgv(argc, argv, &fExit);
if (rc || fExit)
return rc;
/* The function is inside the service, not HGCM. */
if (RT_FAILURE(VBoxHGCMSvcLoad(&svcTable)))
{
RTPrintf("Failed to start the HGCM service.\n");
return 1;
}
if (RT_FAILURE(testExecute(&svcTable)))
return 1;
RTPrintf("tstGuestControlSvc: SUCCEEDED.\n");
return 0;
}