TestExecService.cpp revision cf22150eaeeb72431bf1cf65c309a431454fb22b
251N/A * TestExecServ - Basic Remote Execution Service. 251N/A * Copyright (C) 2010-2014 Oracle Corporation 251N/A * This file is part of VirtualBox Open Source Edition (OSE), as 251N/A * you can redistribute it and/or modify it under the terms of the GNU 251N/A * General Public License (GPL) as published by the Free Software 251N/A * Foundation, in version 2 as it comes in the "COPYING" file of the 251N/A * VirtualBox OSE distribution. VirtualBox OSE is distributed in the 251N/A * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. 251N/A * The contents of this file may alternatively be used under the terms 251N/A * of the Common Development and Distribution License Version 1.0 251N/A * (CDDL) only, as it comes in the "COPYING.CDDL" file of the 251N/A * VirtualBox OSE distribution, in which case the provisions of the 251N/A * CDDL are applicable instead of those of the GPL. 251N/A * You may elect to license modified versions of this file under the 251N/A * terms and conditions of either the GPL or the CDDL or both. 251N/A/******************************************************************************* 251N/A*******************************************************************************/ /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ * Handle IDs used by txsDoExec for the poll set. * For buffering process input supplied by the client. /** The mount of buffered data. */ /** The current data offset. */ /** The amount of allocated buffer space. */ /** Send further input into the bit bucket (stdin is dead). */ /** The CRC-32 for standard input (received part). */ /** Pointer to a standard input buffer. */ * TXS child process info. /** @name For the setup phase /** For serializating some access. */ /** @name Members protected by the critical section. /** The process status. Only valid when fProcessAlive is cleared. */ /** Set when the process is alive, clear when dead. */ /** The end of the pipe that hThreadWaiter writes to. */ /** Pointer to a the TXS child process info. */ /******************************************************************************* *******************************************************************************/ /** The select transport layer. */ /** The default scratch path. */ /** The operating system short name. */ /** The CPU architecture short name. */ /** The combined "OS.arch" name. */ /** The combined "OS/arch" name. */ /** The executable suffix. */ /** The shell script suffix. */ /** UUID identifying this TXS instance. This can be used to see if TXS * has been restarted or not. */ /** Whether to display the output of the child process or not. */ /** Whether to terminate or not. * @todo implement signals and stuff. */ * Calculates the checksum value, zero any padding space and send the packet. * @returns IPRT status code. * @param pPkt The packet to send. Must point to a correctly Log((
"txsSendPkt: rc=%Rrc\n",
rc));
* Sends a babble reply and disconnects the client (if applicable). * @param pszOpcode The BABBLE opcode. * Receive and validate a packet. * Will send bable responses to malformed packets that results in a error status * @returns IPRT status code. * @param ppPktHdr Where to return the packet on success. Free * @param fAutoRetryOnFailure Whether to retry on error. /* validate the packet. */ Log2((
"txsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" Log((
"txsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n",
/* Send babble reply and disconnect the client if the transport is /* Try again or return failure? */ Log((
"txsRecvPkt: rc=%Rrc\n",
rc));
* Make a simple reply, only status opcode. * @returns IPRT status code of the send. * @param pReply The reply packet. * @param pszOpcode The status opcode. Exactly 8 chars long, padd * @param cbExtra Bytes in addition to the header. /* copy the opcode, don't be too strict in case of a padding screw up. */ * Make a simple reply, only status opcode. * @returns IPRT status code of the send. * @param pPktHdr The original packet (for future use). * @param pszOpcode The status opcode. Exactly 8 chars long, padd * Acknowledges a packet with success. * @returns IPRT status code of the send. * @param pPktHdr The original packet (for future use). * Replies with a failure. * @returns IPRT status code of the send. * @param pPktHdr The original packet (for future use). * @param pszOpcode The status opcode. Exactly 8 chars long, padd * @param pszDetailsFmt Longer description of the problem (format * @param va Format arguments. * Replies with a failure. * @returns IPRT status code of the send. * @param pPktHdr The original packet (for future use). * @param pszOpcode The status opcode. Exactly 8 chars long, padd * @param pszDetails Longer description of the problem (format * @param ... Format arguments. * Replies according to the return code. * @returns IPRT status code of the send. * @param pPktHdr The packet to reply to. * @param rcOperation The status code to report. * @param pszOperationFmt The operation that failed. Typically giving the * function call with important arguments. * @param ... Arguments to the format string. * Signal a bad packet minum size. * @returns IPRT status code of the send. * @param pPktHdr The packet to reply to. * @param cbMin The minimum size. * Signal a bad packet exact size. * @returns IPRT status code of the send. * @param pPktHdr The packet to reply to. * @param cb The wanted size. * Deals with a command that isn't implemented yet. * @returns IPRT status code of the send. * @param pPktHdr The packet which opcode isn't implemented. * Deals with a unknown command. * @returns IPRT status code of the send. * @param pPktHdr The packet to reply to. * Replaces a variable with its value. * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. * @param pcchNew In/Out. (Messed up on failure.) * @param offVar Variable offset. * @param cchVar Variable length. * @param pszValue The value. * @param cchValue Value length. * Replace the variables found in the source string, returning a new string that * lives on the string heap. * @returns Boolean success indicator. Will reply to the client with all the * gory detail on failure. * @param pPktHdr The packet the string relates to. For replying * @param pszSrc The source string. * @param ppszNew Where to return the new string. * @param prcSend Where to return the status code of the send on /* Lazy approach that employs memmove. */ * Checks if the string is valid and returns the expanded version. * @returns true if valid, false if invalid. * @param pPktHdr The packet being unpacked. * @param pszArgName The argument name. * @param psz Pointer to the string within pPktHdr. * @param ppszExp Where to return the expanded string. Must be * freed by calling RTStrFree(). * @param ppszNext Where to return the pointer to the next field. * If NULL, then we assume this string is at the * end of the packet and will make sure it has the * @param prcSend Where to return the status code of the send on * Validates a packet with a single string after the header. * @returns true if valid, false if invalid. * @param pPktHdr The packet. * @param pszArgName The argument name. * @param ppszExp Where to return the string pointer. Variables * will be replaced and it must therefore be freed * by calling RTStrFree(). * @param prcSend Where to return the status code of the send on * Checks if the two opcodes match. * @returns true on match, false on mismatch. * @param pPktHdr The packet header. * @param pszOpcode2 The opcode we're comparing with. Does not have * to be the whole 8 chars long. * Used by txsDoGetFile to wait for a reply ACK from the client. * @returns VINF_SUCCESS on ACK, VERR_GENERAL_FAILURE on NACK, * VERR_NET_NOT_CONNECTED on unknown response (sending a bable reply), * or whatever txsRecvPkt returns. * @param pPktHdr The original packet (for future use). * @returns IPRT status code from send. * @param pPktHdr The unpack file packet. * Downloads a file to the client. * The transfer sends a stream of DATA packets (0 or more) and ends it all with * a ACK packet. If an error occurs, a FAILURE packet is sent and the transfer * @returns IPRT status code from send. * @param pPktHdr The get file packet. * Uploads a file from the client. * The transfer sends a stream of DATA packets (0 or more) and ends it all with * a DATA EOF packet. We ACK each of these, so that if a write error occurs we * can abort the transfer straight away. * @returns IPRT status code from send. * @param pPktHdr The put file packet. * Read client command packets and process them. * Delete the file on failure. * List the entries in the specified directory. * @returns IPRT status code from send. * @param pPktHdr The list packet. * Get info about a file system object, following all but the symbolic links * except in the final path component. * @returns IPRT status code from send. * @param pPktHdr The lstat packet. /** @todo figure out how to format the return buffer here. */ * Get info about a file system object, following all symbolic links. * @returns IPRT status code from send. * @param pPktHdr The stat packet. /** @todo figure out how to format the return buffer here. */ * Checks if the specified path is a symbolic link. * @returns IPRT status code from send. * @param pPktHdr The issymlnk packet. * Checks if the specified path is a file or not. * If the final path element is a symbolic link to a file, we'll return * @returns IPRT status code from send. * @param pPktHdr The isfile packet. * Checks if the specified path is a directory or not. * If the final path element is a symbolic link to a directory, we'll return * @returns IPRT status code from send. * @param pPktHdr The isdir packet. * Changes the group of a file, directory of symbolic link. * @returns IPRT status code from send. * @param pPktHdr The chmod packet. * Changes the owner of a file, directory of symbolic link. * @returns IPRT status code from send. * @param pPktHdr The chmod packet. * Changes the mode of a file or directory. * @returns IPRT status code from send. * @param pPktHdr The chmod packet. * Removes a directory tree. * @returns IPRT status code from send. * @param pPktHdr The rmtree packet. * Removes a symbolic link. * @returns IPRT status code from send. * @param pPktHdr The rmsymlink packet. * @returns IPRT status code from send. * @param pPktHdr The rmfile packet. * @returns IPRT status code from send. * @param pPktHdr The rmdir packet. * Creates a symbolic link. * @returns IPRT status code from send. * @param pPktHdr The mksymlnk packet. * Creates a directory and all its parents. * @returns IPRT status code from send. * @param pPktHdr The mkdir -p packet. /* The same format as the MKDIR command. */ * @returns IPRT status code from send. * @param pPktHdr The mkdir packet. /* After the packet header follows a mode mask and the remainder of the packet is the zero terminated directory name. */ * Cleans up the scratch area. * @returns IPRT status code from send. * @param pPktHdr The shutdown packet. * Ejects the specified DVD/CD drive. * @returns IPRT status code from send. * @param pPktHdr The eject packet. /* After the packet header follows a uint32_t ordinal. */ * Common worker for txsDoShutdown and txsDoReboot. * @returns IPRT status code from send. * @param pPktHdr The reboot packet. * @param fAction Which action to take. * We ACK the reboot & shutdown before actually performing them, then we * terminate the transport layer. * This is to make sure the client isn't stuck with a dead connection. The * transport layer termination also make sure we won't accept new * connections in case the client is too eager to reconnect to a rebooted * test victim. On the down side, we cannot easily report RTSystemShutdown * failures failures this way. But the client can kind of figure it out by * reconnecting and seeing that our UUID was unchanged. * Do the job, if it fails we'll restart the transport layer. "Test Execution Service");
* Shuts down the machine, powering it off if possible. * @returns IPRT status code from send. * @param pPktHdr The shutdown packet. * @returns IPRT status code from send. * @param pPktHdr The reboot packet. * Verifies and acknowledges a "UUID" request. * @returns IPRT status code. * @param pPktHdr The howdy packet. * Verifies and acknowledges a "BYE" request. * @returns IPRT status code. * @param pPktHdr The howdy packet. * Verifies and acknowledges a "HOWDY" request. * @returns IPRT status code. * @param pPktHdr The howdy packet. * Replies according to the return code. * @returns rcOperation and pTxsExec->rcReplySend. * @param pTxsExec The TXSEXEC instance. * @param rcOperation The status code to report. * @param pszOperationFmt The operation that failed. Typically giving the * function call with important arguments. * @param ... Arguments to the format string. "%s failed with rc=%Rrc (opcode '%.8s')",
* Sends the process exit status reply to the TXS client. * @returns IPRT status code of the send. * @param pTxsExec The TXSEXEC instance. * @param fProcessAlive Whether the process is still alive (against our * @param fProcessTimedOut Whether the process timed out. * @param MsProcessKilled When the process was killed, UINT64_MAX if not. RTPrintf(
"txs: Process timed out and was killed\n");
RTPrintf(
"txs: Process timed out and was not killed successfully\n");
RTPrintf(
"txs: Process exited with status: 0\n");
RTPrintf(
"txs: Process exited with status: abend\n");
* Handle pending output data or error on standard out, standard error or the * @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 enmHndId The handle ID. * @param pszOpcode The opcode for the data upload. * @todo Put the last 4 parameters into a struct! * Try drain the pipe before acting on any errors. /* Make sure we go another poll round in case there was too much data for the buffer to hold. */ * If an error was raised signalled, * 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. * 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. * Handle an event indicating we can write to the standard input pipe of the * @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. /** @todo do we need to do something about this error condition? */ * 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. /* ASSUMES the transport layer will detect or clear any error condition. */ Log((
"txsDoExecHlpHandleTransportEvent\n"));
/** @todo Use a callback for this case? */ * Read client command packet and process it. /** @todo Sometimes this hangs on windows because there isn't any data pending. * We probably get woken up at the wrong time or in the wrong way, i.e. RTPoll() * Temporary workaround: Poll for input before trying to read it. */ Log((
"Bad transport event\n"));
Log((
"Bad transport event\n"));
* The most common thing here would be a STDIN request with data /* Rewind the buffer if it's empty. */ /* Try and see if we can simply append the data. */ /* Try write a bit or two before we move+realloc the buffer. */ /* Move any buffered data to the front. */ /* Do we need to grow the buffer? */ /* Finally, copy the data. */ * Flush the buffered data and add/remove the standard input * The only other two requests are connection oriented and we return a error * code so that we unwind the whole EXEC shebang and start afresh. * Handles the output and input of the process, waits for it finish up. * @returns IPRT status code from reply send. * @param pTxsExec The TXSEXEC instance. int rc;
/* client send. */ * Before entering the loop, tell the client that we've started the guest * and that it's now OK to send input to the process. (This is not the * final ACK, so the packet header is NULL ... kind of bogus.) * Process input, output, the test pipe and client requests. Log3((
"Calling RTPollNoResume(,%u,)...\n"));
cMsPollCur = 0;
/* no rest until we've checked everything. */ break;
/* abort command, or client dead or something */ * Check for incoming data. break;
/* abort command, or client dead or something */ * If the process has terminated, we're should head out. * Check for timed out, killing the process. break;
/* give up after 20 mins */ /* Reset the polling interval since we've done all pending work. */ * At this point we should hopefully only have to wait 0 ms on the thread * to release the handle... But if for instance the process refuses to die, * we'll have to try kill it again. Bothersome. for (
size_t i = 0; i <
22; i++)
if (i == 0 || i ==
10 || i ==
15 || i ==
18 || i >
20)
* If we don't have a client problem (RT_FAILURE(rc) we'll reply to the * clients exec packet now. * Creates a poll set for the pipes and let the transport layer add stuff to it * @returns IPRT status code, reply to client made on error. * @param pTxsExec The TXSEXEC instance. * Thread that calls RTProcWait and signals the main thread when it returns. * The thread is created before the process is started and is waiting for a user * signal from the main thread before it calls RTProcWait. * @returns VINF_SUCCESS (ignored). * @param hThreadSelf The thread handle. * @param pvUser The TXEEXEC structure. /* Wait for the go ahead... */ /* If the pipe is NIL, the destructor wants us to get lost ASAP. */ /* The process finished, signal the main thread over the pipe. */ * Sets up the thread that waits for the process to complete. * @returns IPRT status code, reply to client made on error. * @param pTxsExec The TXSEXEC instance. * @returns IPRT status code, reply to client made on error. * @param pTxsExec The TXSEXEC instance. * @param pszTestPipe How to set up the test pipe. * Sets up the redirection / pipe / nothing for one of the standard handles. * @returns IPRT status code, reply to client made on error. * @param pTxsExec The TXSEXEC instance. * @param pszHowTo How to set up this standard handle. * @param fd Which standard handle it is (0 == stdin, 1 == * @param ph The generic handle that @a pph may be set * pointing to. Always set. * @param pph Pointer to the RTProcCreateExec argument. * @param phPipe Where to return the end of the pipe that we * should service. Always set. * Setup a pipe for forwarding to/from the client. /* same as parent (us) */ * Create the environment. * @returns IPRT status code, reply to client made on error. * @param pTxsExec The TXSEXEC instance. * @param cEnvVars The number of environment variables. * @param papszEnv The environment variables (var=value). * Create the environment. * Deletes the TXSEXEC structure and frees the memory backing it. * @param pTxsExec The structure to destroy. * If the process is still running we're in a bit of a fix... Try kill it, * although that's potentially racing process termination and reusage of /* else: leak it or RTThreadWait may cause heap corruption later. */ * Initializes the TXSEXEC structure. * @returns VINF_SUCCESS and non-NULL *ppTxsExec on success, reply send status * and *ppTxsExec set to NULL on failure. * @param pPktHdr The exec packet. * @param cMsTimeout The time parameter. * @param ppTxsExec Where to return the structure. * Allocate the basic resources. * Initialize the member to NIL values. * txsDoExec helper that takes over when txsDoExec has expanded the packet. * @returns IPRT status code from send. * @param pPktHdr The exec packet. * @param fFlags Flags, reserved for future use. * @param pszExecName The executable name. * @param cArgs The argument count. * @param papszArgs The arguments. * @param cEnvVars The environment variable count. * @param papszEnv The environment variables. * @param pszStdIn How to deal with standard in. * @param pszStdOut How to deal with standard out. * @param pszStdErr How to deal with standard err. * @param pszTestPipe How to deal with the test pipe. * @param pszUsername The user to run the program as. * @param cMillies The process time limit in milliseconds. * Input validation, filter out things we don't yet support.. * Prepare for process launch. * Close the child ends of any pipes and redirected files. * Let another worker function funnel output and input to the * client as well as the process exit code. * @returns IPRT status code from send. * @param pPktHdr The exec packet. * This packet has a lot of parameters, most of which are zero terminated * strings. The strings used in items 7 thru 10 are either file names, * 1. Flags reserved for future use (32-bit unsigned). * 2. The executable name (string). * 3. The argument count given as a 32-bit unsigned integer. * 4. The arguments strings. * 5. The number of environment strings (32-bit unsigned). * 6. The environment strings (var=val) to apply the environment. * 7. What to do about standard in (string). * 8. What to do about standard out (string). * 9. What to do about standard err (string). * 10. What to do about the test pipe (string). * 11. The name of the user to run the program as (string). Empty string * means running it as the current user. * 12. Process time limit in milliseconds (32-bit unsigned). Max == no limit. +
sizeof(
uint32_t)
/* argc */ +
2 /* argv */ char const *
pch = (
char const *)(
pPktHdr +
1);
/* cursor */ if (!
fOk)
/* Bail out on error. */ /** @todo No password value? */ * Time to employ a helper here before we go way beyond * Read client command packet and process it. * Do a string switch on the opcode bit. * Finalizes the scratch directory, making sure it exists. * Attempts to complete an upgrade by updating the original and relaunching * ourselves from there again. * On failure, we'll continue running as the temporary copy. * @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. * Copy the current executable onto the original. * Note that we're racing the original program on some platforms, thus the RTMsgError(
"RTProcGetExecutablePath failed (step 2)\n");
* Relaunch the service with the original name, foricbly barring * further upgrade cycles in case of bugs (and simplifying the code). for (
int i =
1; i <
argc; i++)
RTMsgError(
"RTMemAlloc failed during upgrade attempt (stage 2)\n");
* Checks for an upgrade and respawns if there is. * @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. * Figure names of the current service image and the potential upgrade. RTMsgError(
"Failed to construct path to potential service upgrade: %Rrc\n",
rc);
* Query information about the two images and read the entire potential source file. * Compare and see if we've got a different service image or not. /* must compare bytes. */ * Should upgrade. Start by creating an executable copy of the update * image in the scratch area. * Try execute the new image and quit if it works. for (
int i =
1; i <
argc; i++)
RTMsgError(
"RTMemAlloc failed during upgrade attempt (stage)\n");
RTMsgError(
"Failed to construct path to temporary upgrade image: %Rrc\n",
rc);
* Determines the default configuration. /** @todo do a better job here :-) */ * The default transporter is the first one. * @param pStrm Where to print it. * @param pszArgv0 The program name (argv[0]). "Usage: %Rbn [options]\n" " Where to put scratch files.\n" " Use the specified transport layer, one of the following:\n");
" --auto-upgrade, --no-auto-upgrade\n" " To enable or disable the automatic upgrade mechanism where any different\n" " version found on the CD-ROM on startup will replace the initial copy.\n" " Default: --auto-upgrade\n" " --upgrading <org-path>\n" " Internal use only.\n");
" --display-output, --no-display-output\n" " Display the output and the result of all child processes.\n");
" Don't daemonize, run in the foreground.\n");
" Display this message and exit.\n" " Display the version and exit.\n");
* @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. * Storage for locally handled options. * Combine the base and transport layer option arrays. * Handle automatic upgrading of the service. * Daemonize ourselves if asked to. * Initialize the runtime. * Determine defaults and parse the arguments. * Generate a UUID for this TXS instance. * Finalize the scratch directory and initialize the transport layer.