/* $Id: tstGuestControlSvc.cpp 33806 2010-11-05 17:20:15Z vboxsync $ */ /** @file * * Testcase for the guest control service. */ /* * 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; * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 : "", cMillies); for (uint32_t i=0; ifile 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; 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 */ paParms[6].getBuffer((void**)&pcData, &cbLen); AssertPtr(pcData); char **ppaEnv = NULL; 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; ipfnCall(pTable->pvService, &callHandle, 0, NULL, command, 3, paParms); int rc = VINF_SUCCESS; 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