/* $Id$ */
/** @file
* TestExecServ - Basic Remote Execution Service.
*/
/*
* Copyright (C) 2010-2014 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.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/critsect.h>
#include <iprt/initterm.h>
#ifndef RT_OS_WINDOWS
#endif
#include "TestExecServiceInternal.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Handle IDs used by txsDoExec for the poll set.
*/
typedef enum TXSEXECHNDID
{
TXSEXECHNDID_STDIN = 0,
} TXSEXECHNDID;
/**
* For buffering process input supplied by the client.
*/
typedef struct TXSEXECSTDINBUF
{
/** The mount of buffered data. */
/** The current data offset. */
/** The data buffer. */
char *pch;
/** The amount of allocated buffer space. */
/** Send further input into the bit bucket (stdin is dead). */
bool fBitBucket;
/** The CRC-32 for standard input (received part). */
/** Pointer to a standard input buffer. */
/**
* TXS child process info.
*/
typedef struct TXSEXEC
{
int rcReplySend;
/** @name For the setup phase
* @{ */
struct StdPipe
{
} StdIn,
/** @} */
/** 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. */
bool volatile fProcessAlive;
/** The end of the pipe that hThreadWaiter writes to. */
/** @} */
} TXSEXEC;
/** Pointer to a the TXS child process info. */
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* Transport layers.
*/
{
//&g_SerialTransport,
//&g_FileSysTransport,
//&g_GuestPropTransport,
//&g_TestDevTransport,
};
/** The select transport layer. */
/** The scratch path. */
/** The default scratch path. */
/** The operating system short name. */
/** The CPU architecture short 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. */
static bool g_fDisplayOutput = true;
/** Whether to terminate or not.
* @todo implement signals and stuff. */
static bool volatile g_fTerminate = false;
/**
* 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
* aligned buffer.
*/
{
if (RT_FAILURE(rc))
return 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
* code.
*
* @returns IPRT status code.
* @param ppPktHdr Where to return the packet on success. Free
* with RTMemFree.
* @param fAutoRetryOnFailure Whether to retry on error.
*/
{
for (;;)
{
if (RT_SUCCESS(rc))
{
/* validate the packet. */
{
Log2(("txsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n"
"%.*Rhxd\n",
: 0;
{
)
{
return rc;
}
}
else
{
Log(("txsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n",
rc = VERR_IO_CRC;
}
}
else
/* Send babble reply and disconnect the client if the transport is
connection oriented. */
if (rc == VERR_IO_BAD_LENGTH)
txsReplyBabble("BABBLE L");
else if (rc == VERR_IO_CRC)
txsReplyBabble("BABBLE C");
else if (rc == VERR_IO_BAD_COMMAND)
txsReplyBabble("BABBLE O");
else
txsReplyBabble("BABBLE ");
}
/* Try again or return failure? */
if ( g_fTerminate
|| rc != VERR_INTERRUPTED
)
{
return 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
* with space.
* @param cbExtra Bytes in addition to the header.
*/
{
/* copy the opcode, don't be too strict in case of a padding screw up. */
else
{
cchOpcode--;
AssertMsgReturn(cchOpcode < sizeof(pReply->achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4);
}
return txsSendPkt(pReply);
}
/**
* 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
* with space.
*/
{
}
/**
* 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
* with space.
* @param pszDetailsFmt Longer description of the problem (format
* string).
* @param va Format arguments.
*/
static int txsReplyFailureV(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, va_list va)
{
union
{
} uPkt;
pszDetailFmt, va);
}
/**
* 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
* with space.
* @param pszDetails Longer description of the problem (format
* string).
* @param ... Format arguments.
*/
static int txsReplyFailure(PCTXSPKTHDR pPktHdr, const char *pszOpcode, const char *pszDetailFmt, ...)
{
return rc;
}
/**
* 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.
*/
{
if (RT_SUCCESS(rcOperation))
return txsReplyAck(pPktHdr);
}
/**
* 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.
*/
{
return txsReplyFailure(pPktHdr, "NOT IMPL", "Opcode '%.8s' is not implemented", pPktHdr->achOpcode);
}
/**
* 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 offVar Variable offset.
* @param cchVar Variable length.
* @param pszValue The value.
* @param cchValue Value length.
*/
{
{
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/**
* 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
* on error.
* @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
* failure.
*/
static int txsReplaceStringVariables(PCTXSPKTHDR pPktHdr, const char *pszSrc, char **ppszNew, int *prcSend)
{
/* Lazy approach that employs memmove. */
{
{
if (pszEnd)
{
{ \
}
int rc;
else
{
return false;
}
if (RT_FAILURE(rc))
{
return false;
}
}
}
}
*prcSend = VINF_SUCCESS;
return true;
}
/**
* 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
* advertised length.
* @param prcSend Where to return the status code of the send on
* failure.
*/
{
if (ppszNext)
{
return false;
}
if (!pszEnd)
{
*prcSend = txsReplyFailure(pPktHdr, "STR TERM", "The string argument '%s' in '%.8s' is unterminated",
return false;
}
{
*prcSend = txsReplyFailure(pPktHdr, "STR SHRT", "The string argument '%s' in '%.8s' is shorter than advertised",
return false;
}
return false;
if (ppszNext)
return true;
}
/**
* 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
* failure.
*/
static bool txsIsStringPktValid(PCTXSPKTHDR pPktHdr, const char *pszArgName, char **ppszExp, int *prcSend)
{
{
return false;
}
}
/**
* 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.
*/
{
return false;
return false;
unsigned i = 2;
&& pszOpcode2[i] != '\0')
{
break;
i++;
}
&& pszOpcode2[i] == '\0')
{
i++;
}
}
/**
* 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).
*/
{
/** @todo timeout? */
if (RT_SUCCESS(rc))
{
rc = VINF_SUCCESS;
else
{
txsReplyBabble("BABBLE ");
}
}
return rc;
}
#ifndef RT_OS_WINDOWS
/**
* Unpacks a tar file.
*
* @returns IPRT status code from send.
* @param pPktHdr The unpack file packet.
*/
{
int rc;
/* Packet cursor. */
{
{
unsigned cArgs = 0;
if ( pszSuff
if (rcExit != RTEXITCODE_SUCCESS)
else
rc = VINF_SUCCESS;
}
}
return rc;
}
#endif
/**
* 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
* aborted.
*
* @returns IPRT status code from send.
* @param pPktHdr The get file packet.
*/
{
int rc;
char *pszPath;
return rc;
if (RT_SUCCESS(rc))
{
for (;;)
{
struct
{
} Pkt;
{
{
if (RT_SUCCESS(rc))
}
else
break;
}
if (RT_FAILURE(rc))
break;
if (RT_FAILURE(rc))
break;
}
}
else
return rc;
}
/**
* 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.
*/
{
int rc;
char *pszPath;
return rc;
if (RT_SUCCESS(rc))
{
bool fSuccess = false;
if (RT_SUCCESS(rc))
{
/*
* Read client command packets and process them.
*/
for (;;)
{
if (RT_FAILURE(rc))
break;
{
{
{
if (RT_SUCCESS(rc))
{
continue;
}
}
else
}
else
}
{
{
{
}
else
}
else
}
else
rc = txsReplyFailure(pDataPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during PUT FILE", pDataPktHdr->achOpcode);
break;
}
}
/*
* Delete the file on failure.
*/
if (!fSuccess)
}
else
return rc;
}
/**
* List the entries in the specified directory.
*
* @returns IPRT status code from send.
* @param pPktHdr The list packet.
*/
{
int rc;
char *pszPath;
return rc;
return rc;
}
/**
* 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.
*/
{
int rc;
char *pszPath;
return rc;
if (RT_SUCCESS(rc))
/** @todo figure out how to format the return buffer here. */
else
return rc;
}
/**
* Get info about a file system object, following all symbolic links.
*
* @returns IPRT status code from send.
* @param pPktHdr The stat packet.
*/
{
int rc;
char *pszPath;
return rc;
if (RT_SUCCESS(rc))
/** @todo figure out how to format the return buffer here. */
else
return rc;
}
/**
* Checks if the specified path is a symbolic link.
*
* @returns IPRT status code from send.
* @param pPktHdr The issymlnk packet.
*/
{
int rc;
char *pszPath;
return rc;
else
return rc;
}
/**
* 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
* FALSE.
*
* @returns IPRT status code from send.
* @param pPktHdr The isfile packet.
*/
{
int rc;
char *pszPath;
return rc;
else
return rc;
}
/**
* 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
* FALSE.
*
* @returns IPRT status code from send.
* @param pPktHdr The isdir packet.
*/
{
int rc;
char *pszPath;
return rc;
else
return rc;
}
/**
* Changes the group of a file, directory of symbolic link.
*
* @returns IPRT status code from send.
* @param pPktHdr The chmod packet.
*/
{
return txsReplyNotImplemented(pPktHdr);
}
/**
* Changes the owner of a file, directory of symbolic link.
*
* @returns IPRT status code from send.
* @param pPktHdr The chmod packet.
*/
{
return txsReplyNotImplemented(pPktHdr);
}
/**
* Changes the mode of a file or directory.
*
* @returns IPRT status code from send.
* @param pPktHdr The chmod packet.
*/
{
return txsReplyNotImplemented(pPktHdr);
}
/**
* Removes a directory tree.
*
* @returns IPRT status code from send.
* @param pPktHdr The rmtree packet.
*/
{
int rc;
char *pszPath;
return rc;
return rc;
}
/**
* Removes a symbolic link.
*
* @returns IPRT status code from send.
* @param pPktHdr The rmsymlink packet.
*/
{
int rc;
char *pszPath;
return rc;
return rc;
}
/**
* Removes a file.
*
* @returns IPRT status code from send.
* @param pPktHdr The rmfile packet.
*/
{
int rc;
char *pszPath;
return rc;
return rc;
}
/**
* Removes a directory.
*
* @returns IPRT status code from send.
* @param pPktHdr The rmdir packet.
*/
{
int rc;
char *pszPath;
return rc;
return rc;
}
/**
* Creates a symbolic link.
*
* @returns IPRT status code from send.
* @param pPktHdr The mksymlnk packet.
*/
{
return txsReplyNotImplemented(pPktHdr);
}
/**
* 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. */
int rc;
char *pszPath;
if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc))
return rc;
return rc;
}
/**
* Creates a directory.
*
* @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. */
int rc;
char *pszPath;
if (!txsIsStringValid(pPktHdr, "dir", (const char *)(pPktHdr + 1) + sizeof(RTFMODE), &pszPath, NULL, &rc))
return rc;
return rc;
}
/**
* Cleans up the scratch area.
*
* @returns IPRT status code from send.
* @param pPktHdr The shutdown packet.
*/
{
}
/**
*
* @returns IPRT status code from send.
* @param pPktHdr The eject packet.
*/
{
/* After the packet header follows a uint32_t ordinal. */
if (RT_FAILURE(rc))
}
/**
* 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.
*/
int rc;
g_pTransport->pfnTerm();
/*
* Do the job, if it fails we'll restart the transport layer.
*/
#if 0
rc = VINF_SUCCESS;
#else
"Test Execution Service");
#endif
if (RT_SUCCESS(rc))
{
g_fTerminate = true;
}
else
{
if (RT_FAILURE(rc2))
{
g_fTerminate = true;
}
}
return rc;
}
/**
* Shuts down the machine, powering it off if possible.
*
* @returns IPRT status code from send.
* @param pPktHdr The shutdown packet.
*/
{
}
/**
* Reboots the machine.
*
* @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.
*/
{
struct
{
} Pkt;
if (RT_FAILURE(rc))
}
/**
* Verifies and acknowledges a "BYE" request.
*
* @returns IPRT status code.
* @param pPktHdr The howdy packet.
*/
{
int rc;
else
return rc;
}
/**
* Verifies and acknowledges a "HOWDY" request.
*
* @returns IPRT status code.
* @param pPktHdr The howdy packet.
*/
{
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* 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')",
return rcOperation;
}
/**
* 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
* will).
* @param fProcessTimedOut Whether the process timed out.
* @param MsProcessKilled When the process was killed, UINT64_MAX if not.
*/
static int txsExecSendExitStatus(PTXSEXEC pTxsExec, bool fProcessAlive, bool fProcessTimedOut, uint64_t MsProcessKilled)
{
int rc;
{
if (g_fDisplayOutput)
RTPrintf("txs: Process timed out and was killed\n");
}
{
if (g_fDisplayOutput)
RTPrintf("txs: Process timed out and was not killed successfully\n");
}
else if (fProcessAlive)
{
AssertFailed();
}
else if (MsProcessKilled != UINT64_MAX)
{
rc = txsReplyFailure(pTxsExec->pPktHdr, "PROC DOO", "Doofus! process has been killed when it should not");
AssertFailed();
}
{
if (g_fDisplayOutput)
RTPrintf("txs: Process exited with status: 0\n");
}
{
if (g_fDisplayOutput)
}
{
if (g_fDisplayOutput)
}
{
if (g_fDisplayOutput)
RTPrintf("txs: Process exited with status: abend\n");
}
else
{
AssertMsgFailed(("enmReason=%d iStatus=%d", pTxsExec->ProcessStatus.enmReason, pTxsExec->ProcessStatus.iStatus));
}
return rc;
}
/**
* 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 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.
*/
struct
{
} Pkt;
{
if (g_fDisplayOutput)
{
if (enmHndId == TXSEXECHNDID_STDOUT)
else if (enmHndId == TXSEXECHNDID_STDERR)
}
/* Make sure we go another poll round in case there was too much data
for the buffer to hold. */
}
else if (RT_FAILURE(rc2))
{
}
/*
* If an error was raised signalled,
*/
if (fPollEvt & RTPOLL_EVT_ERROR)
{
*phPipeR = NIL_RTPIPE;
}
return rc;
}
/**
* 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.
*/
{
if (RT_SUCCESS(rc))
{
}
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 txsDoExecHlpHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
{
int rc2;
{
}
*phStdInW = NIL_RTPIPE;
pStdInBuf->cbAllocated = 0;
pStdInBuf->fBitBucket = true;
}
/**
* 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 txsDoExecHlpHandleStdInWritableEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW,
{
int rc;
if (!(fPollEvt & RTPOLL_EVT_ERROR))
{
{
/** @todo do we need to do something about this error condition? */
}
{
}
}
else
}
/**
* 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 txsDoExecHlpHandleTransportEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, uint32_t idPollHnd,
{
/* 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()
* is busted for sockets.
*
* Temporary workaround: Poll for input before trying to read it. */
if (!g_pTransport->pfnPollIn())
{
Log(("Bad transport event\n"));
return VINF_SUCCESS;
}
if (RT_FAILURE(rc))
return rc;
Log(("Bad transport event\n"));
/*
* The most common thing here would be a STDIN request with data
* for the child process.
*/
{
if ( !pStdInBuf->fBitBucket
{
/* Check the CRC */
{
/* Rewind the buffer if it's empty. */
if (fAddToSet)
/* Try and see if we can simply append the data. */
{
}
else
{
/* Try write a bit or two before we move+realloc the buffer. */
if (cbInBuf > 0)
/* Move any buffered data to the front. */
if (cbInBuf == 0)
else
{
}
/* Do we need to grow the buffer? */
{
if (pvNew)
{
}
}
/* Finally, copy the data. */
{
}
else
}
/*
* handle from the set.
*/
{
}
{
}
}
else
}
else
}
/*
* 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.
*/
{
if (RT_SUCCESS(rc))
}
{
if (RT_SUCCESS(rc))
}
{
if (RT_SUCCESS(rc))
}
else
rc = txsReplyFailure(pPktHdr, "UNKNOWN ", "Opcode '%.8s' is not known or not recognized during EXEC", pPktHdr->achOpcode);
return rc;
}
/**
* 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 rc2;
bool fProcessTimedOut = false;
? 5000 : 100;
/*
* 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.
*/
while ( RT_SUCCESS(rc)
&& RT_UNLIKELY(!g_fTerminate))
{
/*
*/
Log3(("Calling RTPollNoResume(,%u,)...\n"));
if (g_fTerminate)
continue;
cMsPollCur = 0; /* no rest until we've checked everything. */
if (RT_SUCCESS(rc2))
{
switch (idPollHnd)
{
case TXSEXECHNDID_STDOUT:
rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdOutR, &uStdOutCrc32,
TXSEXECHNDID_STDOUT, "STDOUT ");
break;
case TXSEXECHNDID_STDERR:
rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hStdErrR, &uStdErrCrc32,
TXSEXECHNDID_STDERR, "STDERR ");
break;
case TXSEXECHNDID_TESTPIPE:
rc = txsDoExecHlpHandleOutputEvent(pTxsExec->hPollSet, fPollEvt, &pTxsExec->hTestPipeR, &uTestPipeCrc32,
TXSEXECHNDID_TESTPIPE, "TESTPIPE");
break;
case TXSEXECHNDID_STDIN:
break;
break;
case TXSEXECHNDID_THREAD:
break;
default:
&StdInBuf);
break;
}
break; /* abort command, or client dead or something */
continue;
}
/*
* Check for incoming data.
*/
if (g_pTransport->pfnPollIn())
{
rc = txsDoExecHlpHandleTransportEvent(pTxsExec->hPollSet, 0, UINT32_MAX, &pTxsExec->hStdInW, &StdInBuf);
break; /* abort command, or client dead or something */
continue;
}
/*
* If the process has terminated, we're should head out.
*/
break;
/*
* Check for timed out, killing the process.
*/
{
{
fProcessTimedOut = true;
if ( MsProcessKilled == UINT64_MAX
{
break; /* give up after 20 mins */
if (pTxsExec->fProcessAlive)
continue;
}
cMilliesLeft = 10000;
}
else
}
/* 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 (RT_SUCCESS(rc))
{
break;
}
if (i == 0 || i == 10 || i == 15 || i == 18 || i > 20)
{
if (pTxsExec->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))
return rc;
}
/**
* Creates a poll set for the pipes and let the transport layer add stuff to it
* as well.
*
* @returns IPRT status code, reply to client made on error.
* @param pTxsExec The TXSEXEC instance.
*/
{
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
rc = RTPollSetAddPipe(pTxsExec->hPollSet, pTxsExec->hWakeUpPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
if (RT_FAILURE(rc))
if (g_pTransport->pfnPollSetAdd)
{
if (RT_FAILURE(rc))
}
return VINF_SUCCESS;
}
/**
* 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... */
for (;;)
{
/* If the pipe is NIL, the destructor wants us to get lost ASAP. */
break;
if (RT_FAILURE(rc))
{
if (rc == VERR_PROCESS_RUNNING)
continue;
if (RT_FAILURE(rc))
{
}
}
/* The process finished, signal the main thread over the pipe. */
break;
}
return VINF_SUCCESS;
}
/**
* 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.
*/
{
if (RT_FAILURE(rc))
{
}
RTTHREADFLAGS_WAITABLE, "TxsProcW");
if (RT_FAILURE(rc))
{
}
return VINF_SUCCESS;
}
/**
* Sets up the test pipe.
*
* @returns IPRT status code, reply to client made on error.
* @param pTxsExec The TXSEXEC instance.
* @param pszTestPipe How to set up the test pipe.
*/
{
return VINF_SUCCESS;
if (RT_FAILURE(rc))
{
}
if (RT_FAILURE(rc))
return VINF_SUCCESS;
}
/**
* 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 ==
* 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 txsExecSetupRedir(PTXSEXEC pTxsExec, const char *pszHowTo, const char *pszStdWhat, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe)
{
*phPipe = NIL_RTPIPE;
int rc;
{
/*
*/
if (fd == 0)
else
if (RT_FAILURE(rc))
}
{
/*
*/
if (RT_FAILURE(rc))
}
else if (*pszHowTo)
{
/*
*/
if (fd == 0)
else
{
else
{
/* append */
pszHowTo += 2;
}
}
if (RT_FAILURE(rc))
}
else
/* same as parent (us) */
rc = VINF_SUCCESS;
return rc;
}
/**
* 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.
*/
if (RT_FAILURE(rc))
{
if (RT_FAILURE(rc))
}
return VINF_SUCCESS;
}
/**
* Deletes the TXSEXEC structure and frees the memory backing it.
*
* @param pTxsExec The structure to destroy.
*/
{
int rc2;
/*
* 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
* the pid.
*/
&& pTxsExec->fProcessAlive)
if (RT_SUCCESS(rcThread))
{
}
/* 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.
*/
if (!pTxsExec)
if (RT_FAILURE(rc))
{
}
/*
* Initialize the member to NIL values.
*/
pTxsExec->fProcessAlive = false;
return VINF_SUCCESS;
}
/**
* 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.
*/
{
int rc2;
/*
* Input validation, filter out things we don't yet support..
*/
if (!*pszExecName)
if (!*pszStdIn)
if (!*pszStdOut)
if (!*pszStdErr)
if (!*pszTestPipe)
return txsReplyFailure(pPktHdr, "BAD TSTP", "Only \"|\" and \"/dev/null\" are allowed as testpipe howtos ('%s')",
if (*pszUsername)
return txsReplyFailure(pPktHdr, "NOT IMPL", "Executing as a specific user is not implemented ('%s')", pszUsername);
/*
* Prepare for process launch.
*/
return rc;
if (RT_SUCCESS(rc))
rc = txsExecSetupRedir(pTxsExec, pszStdIn, "StdIn", 0, &pTxsExec->StdIn.hChild, &pTxsExec->StdIn.phChild, &pTxsExec->hStdInW);
if (RT_SUCCESS(rc))
rc = txsExecSetupRedir(pTxsExec, pszStdOut, "StdOut", 1, &pTxsExec->StdOut.hChild, &pTxsExec->StdOut.phChild, &pTxsExec->hStdOutR);
if (RT_SUCCESS(rc))
rc = txsExecSetupRedir(pTxsExec, pszStdErr, "StdErr", 2, &pTxsExec->StdErr.hChild, &pTxsExec->StdErr.phChild, &pTxsExec->hStdErrR);
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/*
* Create the process.
*/
if (g_fDisplayOutput)
{
RTPrintf("\n");
}
if (RT_SUCCESS(rc))
{
/*
* 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.
*/
}
else
pszExecName, rc);
}
else
return rc;
}
/**
* Execute a program.
*
* @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,
*
* Packet content:
* 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) + 0 /* environ */
+ 4 * 1
+ sizeof(uint32_t) /* timeout */;
/* unpack the packet */
/* 1. flags */
if (fFlags != 0)
/* 2. exec name */
int rc;
return rc;
/* 3. argc */
else if (cArgs > 128)
else
{
if (papszArgs)
{
/* 4. argv */
bool fOk = true;
{
if (!fOk)
break;
}
if (fOk)
{
/* 5. cEnvVars */
uint32_t const cEnvVars = (size_t)(pchEnd - pch) > sizeof(uint32_t) ? *(uint32_t const *)pch : 0xfff;
rc = txsReplyFailure(pPktHdr, "BAD ENVC", "Bad or missing environment variable count (%#x)", cEnvVars);
else if (cEnvVars > 256)
else
{
if (papszEnv)
{
/* 6. environ */
{
if (!fOk) /* Bail out on error. */
break;
}
if (fOk)
{
/* 7. stdout */
char *pszStdIn;
{
/* 8. stdout */
char *pszStdOut;
{
/* 9. stderr */
char *pszStdErr;
{
/* 10. testpipe */
char *pszTestPipe;
{
/* 11. username */
char *pszUsername;
{
/** @todo No password value? */
/* 12. time limit */
: 0;
rc = txsReplyFailure(pPktHdr, "BAD END ", "Timeout argument not at end of packet (%#x)", (size_t)(pchEnd - pch));
else if (cMillies < 1000)
else
{
/*
* Time to employ a helper here before we go way beyond
* the right margin...
*/
}
}
}
}
}
}
}
}
else
rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes environ", sizeof(char *) * (cEnvVars + 1));
}
}
}
else
rc = txsReplyFailure(pPktHdr, "NO MEM ", "Failed to allocate %zu bytes for argv", sizeof(char *) * (cArgs + 1));
}
return rc;
}
/**
* The main loop.
*
* @returns exit code.
*/
{
while (!g_fTerminate)
{
/*
* Read client command packet and process it.
*/
if (RT_FAILURE(rc))
continue;
/*
* Do a string switch on the opcode bit.
*/
/* Connection: */
/* Process: */
/* Admin: */
/* File system: */
#ifndef RT_OS_WINDOWS
#endif
/* Misc: */
else
}
return enmExitCode;
}
/**
* Finalizes the scratch directory, making sure it exists.
*
* @returns exit code.
*/
{
if (!pszFilename)
int rc;
{
*pszFilename = ch;
if (RT_SUCCESS(rc))
}
else
{
if (RTDirExists(g_szScratchPath))
rc = VINF_SUCCESS;
else
}
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to create scratch directory: %Rrc (%s)\n", rc, g_szScratchPath);
return RTEXITCODE_SUCCESS;
}
/**
* 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.
*/
static RTEXITCODE txsAutoUpdateStage2(int argc, char **argv, bool *pfExit, const char *pszUpgrading)
{
/*
* Copy the current executable onto the original.
* Note that we're racing the original program on some platforms, thus the
* 60 sec sleep mess.
*/
{
RTMsgError("RTProcGetExecutablePath failed (step 2)\n");
return RTEXITCODE_SUCCESS;
}
void *pvUpgrade;
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
| (0755 << RTFILE_O_CREATE_MODE_SHIFT));
while ( RT_FAILURE(rc)
{
RTThreadSleep(1000);
| (0755 << RTFILE_O_CREATE_MODE_SHIFT));
}
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Relaunch the service with the original name, foricbly barring
* further upgrade cycles in case of bugs (and simplifying the code).
*/
if (papszArgs)
{
papszArgs[0] = pszUpgrading;
for (int i = 1; i < argc; i++)
if (RT_SUCCESS(rc))
*pfExit = true;
else
}
else
RTMsgError("RTMemAlloc failed during upgrade attempt (stage 2)\n");
}
else
}
else
return RTEXITCODE_SUCCESS;
}
/**
* 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("RTProcGetExecutablePath failed\n");
return RTEXITCODE_SUCCESS;
}
if (RT_SUCCESS(rc))
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
/*
* Query information about the two images and read the entire potential source file.
*/
if ( rc == VERR_FILE_NOT_FOUND
|| rc == VERR_PATH_NOT_FOUND
|| rc == VERR_MEDIA_NOT_PRESENT
|| rc == VERR_MEDIA_NOT_RECOGNIZED)
return RTEXITCODE_SUCCESS;
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
void *pvUpgrade;
rc = RTFileReadAllEx(szUpgradePath, 0, UpgradeInfo.cbObject, RTFILE_RDALL_O_DENY_NONE, &pvUpgrade, &cbUpgrade);
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
/*
* Compare and see if we've got a different service image or not.
*/
{
/* must compare bytes. */
void *pvOrg;
if (RT_FAILURE(rc))
{
return RTEXITCODE_SUCCESS;
}
if (fSame)
{
return RTEXITCODE_SUCCESS;
}
}
/*
* Should upgrade. Start by creating an executable copy of the update
* image in the scratch area.
*/
if (rcExit == RTEXITCODE_SUCCESS)
{
if (RT_SUCCESS(rc))
{
| (0755 << RTFILE_O_CREATE_MODE_SHIFT));
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Try execute the new image and quit if it works.
*/
if (papszArgs)
{
for (int i = 1; i < argc; i++)
if (RT_SUCCESS(rc))
*pfExit = true;
else
}
else
RTMsgError("RTMemAlloc failed during upgrade attempt (stage)\n");
}
else
}
else
}
else
}
return rcExit;
}
/**
* Determines the default configuration.
*/
static void txsSetDefaults(void)
{
/*
* OS and ARCH.
*/
AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName));
AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName));
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
#else
#endif
/*
*/
/** @todo do a better job here :-) */
#ifdef RT_OS_WINDOWS
#else
if (RTDirExists("/media"))
else
#endif
/*
* Temporary directory.
*/
if (RT_SUCCESS(rc))
#else
#endif
if (RT_FAILURE(rc))
{
}
/*
* The default transporter is the first one.
*/
g_pTransport = g_apTransports[0];
}
/**
* Prints the usage.
*
* @param pStrm Where to print it.
* @param pszArgv0 The program name (argv[0]).
*/
{
"Usage: %Rbn [options]\n"
"\n"
"Options:\n"
" --cdrom <path>\n"
" Default: %s\n"
" --scratch <path>\n"
" Where to put scratch files.\n"
" Default: %s \n"
,
" --transport <name>\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");
" --foreground\n"
" Don't daemonize, run in the foreground.\n");
" --help, -h, -?\n"
" Display this message and exit.\n"
" --version, -V\n"
" Display the version and exit.\n");
if (g_apTransports[i]->cOpts)
{
"\n"
}
}
/**
* 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.
*/
{
*pfExit = false;
/*
* Storage for locally handled options.
*/
bool fAutoUpgrade = true;
bool fDaemonize = true;
bool fDaemonized = false;
bool fTransportFixed = false;
/*
* Combine the base and transport layer option arrays.
*/
{
};
if (!paOptions)
{
memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF));
}
/*
* Parse the arguments.
*/
int ch;
{
switch (ch)
{
case 'a':
fAutoUpgrade = true;
break;
case 'A':
fAutoUpgrade = false;
break;
case 'c':
if (RT_FAILURE(rc))
break;
case 'd':
g_fDisplayOutput = true;
break;
case 'D':
g_fDisplayOutput = false;
break;
case 'f':
fDaemonize = false;
break;
case 'h':
*pfExit = true;
return RTEXITCODE_SUCCESS;
case 's':
if (RT_FAILURE(rc))
break;
case 't':
{
{
pTransport = g_apTransports[i];
break;
}
if (!pTransport)
break;
}
case 'U':
break;
case 'V':
RTPrintf("$Revision$\n");
*pfExit = true;
return RTEXITCODE_SUCCESS;
case 'Z':
fDaemonized = true;
fDaemonize = false;
break;
default:
{
rc = VERR_TRY_AGAIN;
if (g_apTransports[i]->cOpts)
{
if (RT_SUCCESS(rc))
break;
if (rc != VERR_TRY_AGAIN)
{
*pfExit = true;
return RTEXITCODE_SYNTAX;
}
}
if (rc == VERR_TRY_AGAIN)
{
*pfExit = true;
}
break;
}
}
}
/*
* Handle automatic upgrading of the service.
*/
if (fAutoUpgrade && !*pfExit)
{
if (pszUpgrading)
else
if ( *pfExit
|| rcExit != RTEXITCODE_SUCCESS)
return rcExit;
}
/*
* Daemonize ourselves if asked to.
*/
if (fDaemonize && !*pfExit)
{
if (RT_FAILURE(rc))
*pfExit = true;
}
return RTEXITCODE_SUCCESS;
}
{
/*
* Initialize the runtime.
*/
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
/*
* Determine defaults and parse the arguments.
*/
bool fExit;
return rcExit;
/*
* Generate a UUID for this TXS instance.
*/
if (RT_FAILURE(rc))
/*
* Finalize the scratch directory and initialize the transport layer.
*/
rcExit = txsFinalizeScratch();
if (rcExit != RTEXITCODE_SUCCESS)
return rcExit;
if (RT_FAILURE(rc))
return RTEXITCODE_FAILURE;
/*
* Ok, start working
*/
rcExit = txsMainLoop();
/*
* Cleanup.
*/
g_pTransport->pfnTerm();
return rcExit;
}