diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2014-03-26 19:21:20 +0000 |
---|---|---|
committer | <> | 2014-05-08 15:03:54 +0000 |
commit | fb123f93f9f5ce42c8e5785d2f8e0edaf951740e (patch) | |
tree | c2103d76aec5f1f10892cd1d3a38e24f665ae5db /src/VBox/Installer/linux/install_service/generate_service_file.cpp | |
parent | 58ed4748338f9466599adfc8a9171280ed99e23f (diff) | |
download | VirtualBox-master.tar.gz |
Imported from /home/lorry/working-area/delta_VirtualBox/VirtualBox-4.3.10.tar.bz2.HEADVirtualBox-4.3.10master
Diffstat (limited to 'src/VBox/Installer/linux/install_service/generate_service_file.cpp')
-rw-r--r-- | src/VBox/Installer/linux/install_service/generate_service_file.cpp | 912 |
1 files changed, 912 insertions, 0 deletions
diff --git a/src/VBox/Installer/linux/install_service/generate_service_file.cpp b/src/VBox/Installer/linux/install_service/generate_service_file.cpp new file mode 100644 index 00000000..2ff27573 --- /dev/null +++ b/src/VBox/Installer/linux/install_service/generate_service_file.cpp @@ -0,0 +1,912 @@ +/* $Id: generate_service_file.cpp $ */ +/** @file + * Read a service file template from standard input and output a service file + * to standard output generated from the template based on arguments passed to + * the utility. See the usage text for more information. + */ + +/* + * Copyright (C) 2012-2013 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. + */ + +/** + * Description of the generation process. + * + * A template for the service file to be generated is fed into standard input + * and the service file is sent to standard output. The following + * substitutions are performed based on the command line parameters supplied, + * with all quoting appropriate to the format of the template as specified on + * the command line. + * + * %COMMAND% -> path to the service binary or script. + * %ARGUMENTS% -> the arguments to pass to the binary when starting the + * service. + * %SERVICE_NAME% -> the name of the service. + * %DESCRIPTION% -> the short description of the service. + * %STOP_COMMAND% -> path to the command used to stop the service. + * %STOP_ARGUMENTS% -> the arguments for the stop command + * %STATUS_COMMAND% -> path to the command used to determine the service + * status. + * %STATUS_ARGUMENTS% -> the arguments for the status command + + * %NO_STOP_COMMAND% -> if no stop command was specified, this and all text + * following it on the line (including the end-of- + * line) will be removed, otherwise only the marker + * will be removed. + * %HAVE_STOP_COMMAND% -> like above, but text on the line will be removed + * if a stop command was supplied. + * %NO_STATUS_COMMAND% -> Analogue to %NO_STOP_COMMAND% for the status + * command. + * %HAVE_STATUS_COMMAND% -> Analogue to %HAVE_STOP_COMMAND% for the status + * command. + * %HAVE_ONESHOT% -> like above, text on the line will be removed unless + * --one-shot was specified on the command line. + * %HAVE_DAEMON% -> the same if --one-shot was not specified. + * + * %% will be replaced with a single %. + */ + +#include <VBox/version.h> + +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#ifndef READ_SIZE +/** How much of the input we read at a time. Override to something small for + * testing. */ +# define READ_SIZE _1M +#endif + +/* Macros for the template substitution sequences to guard against mis-types. */ +#define COMMAND "%COMMAND%" +#define ARGUMENTS "%ARGUMENTS%" +#define DESCRIPTION "%DESCRIPTION%" +#define SERVICE_NAME "%SERVICE_NAME%" +#define HAVE_ONESHOT "%HAVE_ONESHOT%" +#define HAVE_DAEMON "%HAVE_DAEMON%" +#define STOP_COMMAND "%STOP_COMMAND%" +#define STOP_ARGUMENTS "%STOP_ARGUMENTS%" +#define HAVE_STOP_COMMAND "%HAVE_STOP_COMMAND%" +#define NO_STOP_COMMAND "%NO_STOP_COMMAND%" +#define STATUS_COMMAND "%STATUS_COMMAND%" +#define STATUS_ARGUMENTS "%STATUS_ARGUMENTS%" +#define HAVE_STATUS_COMMAND "%HAVE_STATUS_COMMAND%" +#define NO_STATUS_COMMAND "%NO_STATUS_COMMAND%" + +void showLogo(void) +{ + static bool s_fShown; /* show only once */ + + RTPrintf(VBOX_PRODUCT " Service File Generator Version " + VBOX_VERSION_STRING "\n" + "(C) 2012" /* "-" VBOX_C_YEAR */ " " VBOX_VENDOR "\n" + "All rights reserved.\n" + "\n"); +} + +static void showOptions(void); + +void showUsage(const char *pcszArgv0) +{ + const char *pcszName = strrchr(pcszArgv0, '/'); + if (!pcszName) + pcszName = pcszArgv0; + RTPrintf( +"Usage:\n" +"\n" +" %s --help|-h|-?|--version|-V|--format <format> <parameters...>\n\n", + pcszArgv0); + RTPrintf( +"Read a service file template from standard input and output a service file to\n" +"standard output which was generated from the template based on parameters\n" +"passed on the utility's command line. Generation is done by replacing well-\n" +"known text sequences in the template with strings based on the parameters.\n" +"All strings should be in UTF-8 format. Processing will stop if a sequence is\n" +"read which cannot be replace based on the parameters supplied.\n\n"); + + RTPrintf( +" --help|-h|-?\n" +" Print this help text and exit.\n\n" +" --version|-V\n" +" Print version information and exit.\n\n" +" --format <shell>\n" +" The format of the template. Currently only \"shell\" for shell script\n" +" is supported. This affects escaping of strings substituted.\n\n"); + RTPrintf( +"Parameters:\n" +"\n"); + RTPrintf( +" --command <command>\n" +" The absolute path of the executable file to be started by the service.\n" +" No form of quoting should be used here.\n\n"); + RTPrintf( +" --description <description>\n" +" A short description of the service which can also be used in sentences\n" +" like \"<description> failed to start.\", as a single parameter. Characters\n" +" 0 to 31 and 127 should not be used.\n\n" + ); + RTPrintf( +" --arguments <arguments>\n" +" The arguments to pass to the executable file when it is started, as a\n" +" single parameter. Characters \" \", \"\\\" and \"%%\" must be escaped with\n" +" back-slashes and C string-style back-slash escapes are recognised. Some\n" +" systemd-style \"%%\" sequences may be added at a future time.\n\n"); + RTPrintf( +" --service-name <name>\n" +" Specify the name of the service. By default the base name without the\n" +" extension of the command binary is used. Only ASCII characters 33 to 126\n" +" should be used.\n\n"); + RTPrintf( +" --one-shot\n" +" The service command is expected to do some work and exit immediately with" +" a status indicating success or failure.\n\n" + ); + RTPrintf( +" --stop-command <command>\n" +" The command which should be used to stop the service before sending the\n" +" termination signal to the main process. No form of quoting should be\n" +" used here.\n\n" + ); + RTPrintf( +" --stop-arguments <arguments>\n" +" Arguments for the stop command. This may only be used in combination\n" +" with \"--stop-command\". Quoting is the same as for \"--arguments\".\n\n" + ); + RTPrintf( +" --status-command <command>\n" +" The command which should be used to determine the status of the service.\n" +" This may not be respected by all service management systems. The command\n" +" should return an LSB status code. No form of quoting should be used.\n\n" + ); + RTPrintf( +" --stop-arguments <arguments>\n" +" Arguments for the status command. This may only be used in combination\n" +" with \"--status-command\". Quoting is the same as for \"--arguments\".\n\n" + ); +} + +/** @name Template format. + * @{ + */ +enum ENMFORMAT +{ + /** No format selected. */ + FORMAT_NONE = 0, + /** Shell script format. */ + FORMAT_SHELL +}; +/** @} */ + +struct SERVICEPARAMETERS +{ + enum ENMFORMAT enmFormat; + const char *pcszCommand; + const char *pcszArguments; + const char *pcszDescription; + const char *pcszServiceName; + bool fOneShot; + const char *pcszStopCommand; + const char *pcszStopArguments; + const char *pcszStatusCommand; + const char *pcszStatusArguments; +}; + +static bool errorIfSet(const char *pcszName, bool isSet); +static enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue); +static bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue); +static bool checkPrintable(const char *pcszName, const char *pcszValue); +static bool checkGraphic(const char *pcszName, const char *pcszValue); +static bool createServiceFile(struct SERVICEPARAMETERS *pParameters); + +int main(int cArgs, char **apszArgs) +{ + int rc = RTR3InitExe(cArgs, &apszArgs, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + enum + { + OPTION_FORMAT = 1, + OPTION_COMMAND, + OPTION_ARGUMENTS, + OPTION_DESCRIPTION, + OPTION_SERVICE_NAME, + OPTION_ONE_SHOT, + OPTION_STOP_COMMAND, + OPTION_STOP_ARGUMENTS, + OPTION_STATUS_COMMAND, + OPTION_STATUS_ARGUMENTS + }; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--format", OPTION_FORMAT, + RTGETOPT_REQ_STRING }, + { "--command", OPTION_COMMAND, + RTGETOPT_REQ_STRING }, + { "--arguments", OPTION_ARGUMENTS, + RTGETOPT_REQ_STRING }, + { "--description", OPTION_DESCRIPTION, + RTGETOPT_REQ_STRING }, + { "--service-name", OPTION_SERVICE_NAME, + RTGETOPT_REQ_STRING }, + { "--one-shot", OPTION_ONE_SHOT, + RTGETOPT_REQ_NOTHING }, + { "--stop-command", OPTION_STOP_COMMAND, + RTGETOPT_REQ_STRING }, + { "--stop-arguments", OPTION_STOP_ARGUMENTS, + RTGETOPT_REQ_STRING }, + { "--status-command", OPTION_STATUS_COMMAND, + RTGETOPT_REQ_STRING }, + { "--status-arguments", OPTION_STATUS_ARGUMENTS, + RTGETOPT_REQ_STRING } + }; + + int ch; + struct SERVICEPARAMETERS Parameters = { FORMAT_NONE }; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, cArgs, apszArgs, s_aOptions, + RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'h': + showUsage(apszArgs[0]); + return RTEXITCODE_SUCCESS; + break; + + case 'V': + showLogo(); + return RTEXITCODE_SUCCESS; + break; + + case OPTION_FORMAT: + if (errorIfSet("--format", + Parameters.enmFormat != FORMAT_NONE)) + return(RTEXITCODE_SYNTAX); + Parameters.enmFormat + = getFormat("--format", ValueUnion.psz); + if (Parameters.enmFormat == FORMAT_NONE) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_COMMAND: + if (errorIfSet("--command", Parameters.pcszCommand)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszCommand = ValueUnion.psz; + if (!checkAbsoluteFilePath("--command", + Parameters.pcszCommand)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_ARGUMENTS: + if (errorIfSet("--arguments", + Parameters.pcszArguments)) + return(RTEXITCODE_SYNTAX); + /* Quoting will be checked while writing out the string. */ + Parameters.pcszArguments = ValueUnion.psz; + break; + + case OPTION_DESCRIPTION: + if (errorIfSet("--description", + Parameters.pcszDescription)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszDescription = ValueUnion.psz; + if (!checkPrintable("--description", + Parameters.pcszDescription)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_SERVICE_NAME: + if (errorIfSet("--service-name", + Parameters.pcszServiceName)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszServiceName = ValueUnion.psz; + if (!checkGraphic("--service-name", + Parameters.pcszServiceName)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_ONE_SHOT: + Parameters.fOneShot = true; + break; + + case OPTION_STOP_COMMAND: + if (errorIfSet("--stop-command", + Parameters.pcszStopCommand)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszStopCommand = ValueUnion.psz; + if (!checkAbsoluteFilePath("--stop-command", + Parameters.pcszStopCommand)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_STOP_ARGUMENTS: + if (errorIfSet("--stop-arguments", + Parameters.pcszStopArguments)) + return(RTEXITCODE_SYNTAX); + /* Quoting will be checked while writing out the string. */ + Parameters.pcszStopArguments = ValueUnion.psz; + break; + + case OPTION_STATUS_COMMAND: + if (errorIfSet("--status-command", + Parameters.pcszStatusCommand)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszStatusCommand = ValueUnion.psz; + if (!checkAbsoluteFilePath("--status-command", + Parameters.pcszStatusCommand)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_STATUS_ARGUMENTS: + if (errorIfSet("--status-arguments", + Parameters.pcszStatusArguments)) + return(RTEXITCODE_SYNTAX); + /* Quoting will be checked while writing out the string. */ + Parameters.pcszStatusArguments = ValueUnion.psz; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (Parameters.enmFormat == FORMAT_NONE) + { + RTStrmPrintf(g_pStdErr, "--format must be specified.\n"); + return(RTEXITCODE_SYNTAX); + } + if (Parameters.pcszArguments && !Parameters.pcszCommand) + { + RTStrmPrintf(g_pStdErr, "--arguments requires --command to be specified.\n"); + return(RTEXITCODE_SYNTAX); + } + if (Parameters.pcszStopArguments && !Parameters.pcszStopCommand) + { + RTStrmPrintf(g_pStdErr, "--stop-arguments requires --stop-command to be specified.\n"); + return(RTEXITCODE_SYNTAX); + } + if (Parameters.pcszStatusArguments && !Parameters.pcszStatusCommand) + { + RTStrmPrintf(g_pStdErr, "--status-arguments requires --status-command to be specified.\n"); + return(RTEXITCODE_SYNTAX); + } + return createServiceFile(&Parameters) + ? RTEXITCODE_SUCCESS + : RTEXITCODE_FAILURE; +} + +/** Print an error and return true if an option is already set. */ +bool errorIfSet(const char *pcszName, bool isSet) +{ + if (isSet) + RTStrmPrintf(g_pStdErr, "%s may only be specified once.\n", pcszName); + return isSet; +} + +/** Match the string to a known format and return that (or "none" and print an + * error). */ +enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue) +{ + if (!strcmp(pcszValue, "shell")) + return FORMAT_SHELL; + RTStrmPrintf(g_pStdErr, "%s: unknown format %s.\n", pcszName, pcszValue); + return FORMAT_NONE; +} + +/** Check that the string is an absolute path to a file or print an error. */ +bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue) +{ + if (RTPathFilename(pcszValue) && RTPathStartsWithRoot(pcszValue)) + return true; + RTStrmPrintf(g_pStdErr, "%s: %s must be an absolute path of a file.\n", pcszName, pcszValue); + return false; +} + +/** Check that the string does not contain any non-printable characters. */ +bool checkPrintable(const char *pcszName, const char *pcszValue) +{ + const char *pcch = pcszValue; + for (; *pcch; ++pcch) + { + if (!RT_C_IS_PRINT(*pcch)) + { + RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n", + pcszName, pcch - pcszValue, pcszValue); + return false; + } + } + return true; +} + +/** Check that the string does not contain any non-graphic characters. */ +static bool checkGraphic(const char *pcszName, const char *pcszValue) +{ + const char *pcch = pcszValue; + for (; *pcch; ++pcch) + { + if (!RT_C_IS_GRAPH(*pcch)) + { + RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n", + pcszName, pcch - pcszValue, pcszValue); + return false; + } + } + return true; +} + +static bool createServiceFileCore(char **ppachTemplate, + struct SERVICEPARAMETERS + *pParamters); + +/** + * Read standard input and write it to standard output, doing all substitutions + * as per the usage documentation. + * @note This is a wrapper around the actual function to simplify resource + * allocation without requiring a single point of exit. + */ +bool createServiceFile(struct SERVICEPARAMETERS *pParameters) +{ + char *pachTemplate = NULL; + bool rc = createServiceFileCore(&pachTemplate, pParameters); + RTMemFree(pachTemplate); + return rc; +} + +static bool getSequence(const char *pach, size_t cch, size_t *pcchRead, + const char *pcszSequence, size_t cchSequence); +static bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand); +static bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted); +static bool writePrintableString(enum ENMFORMAT enmFormat, + const char *pcszString); +static void skipLine(const char *pach, size_t cch, size_t *pcchRead); + +/** The actual implemenation code for @a createServiceFile. */ +bool createServiceFileCore(char **ppachTemplate, + struct SERVICEPARAMETERS *pParameters) +{ + /* The size of the template data we have read. */ + size_t cchTemplate = 0; + /* The size of the buffer we have allocated. */ + size_t cbBuffer = 0; + /* How much of the template data we have written out. */ + size_t cchWritten = 0; + int rc = VINF_SUCCESS; + /* First of all read in the file. */ + while (rc != VINF_EOF) + { + size_t cchRead; + + if (cchTemplate == cbBuffer) + { + cbBuffer += READ_SIZE; + *ppachTemplate = (char *)RTMemRealloc((void *)*ppachTemplate, + cbBuffer); + } + if (!*ppachTemplate) + { + RTStrmPrintf(g_pStdErr, "Out of memory.\n"); + return false; + } + rc = RTStrmReadEx(g_pStdIn, *ppachTemplate + cchTemplate, + cbBuffer - cchTemplate, &cchRead); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error reading input: %Rrc\n", rc); + return false; + } + if (!cchRead) + rc = VINF_EOF; + cchTemplate += cchRead; + } + while (true) + { + /* Find the next '%' character if any and write out up to there (or the + * end if there is no '%'). */ + char *pchNext = (char *) memchr((void *)(*ppachTemplate + cchWritten), + '%', cchTemplate - cchWritten); + size_t cchToWrite = pchNext + ? pchNext - *ppachTemplate - cchWritten + : cchTemplate - cchWritten; + rc = RTStrmWrite(g_pStdOut, *ppachTemplate + cchWritten, cchToWrite); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + cchWritten += cchToWrite; + if (!pchNext) + break; + /* And substitute any of our well-known strings. We favour code + * readability over efficiency here. */ + if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + COMMAND, sizeof(COMMAND) - 1)) + { + if (!pParameters->pcszCommand) + { + RTStrmPrintf(g_pStdErr, "--command not specified.\n"); + return false; + } + if (!writeCommand(pParameters->enmFormat, + pParameters->pcszCommand)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + ARGUMENTS, sizeof(ARGUMENTS) - 1)) + { + if ( pParameters->pcszArguments + && !writeQuoted(pParameters->enmFormat, + pParameters->pcszArguments)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + DESCRIPTION, sizeof(DESCRIPTION) - 1)) + { + if (!pParameters->pcszDescription) + { + RTStrmPrintf(g_pStdErr, "--description not specified.\n"); + return false; + } + if (!writePrintableString(pParameters->enmFormat, + pParameters->pcszDescription)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + SERVICE_NAME, sizeof(SERVICE_NAME) - 1)) + { + if ( !pParameters->pcszCommand + && !pParameters->pcszServiceName) + { + RTStrmPrintf(g_pStdErr, "Neither --command nor --service-name specified.\n"); + return false; + } + if (pParameters->pcszServiceName) + { + if (!writePrintableString(pParameters->enmFormat, + pParameters->pcszServiceName)) + return false; + } + else + { + const char *pcszFileName = + RTPathFilename(pParameters->pcszCommand); + const char *pcszExtension = + RTPathExt(pParameters->pcszCommand); + char *pszName = RTStrDupN(pcszFileName, + pcszExtension + ? pcszExtension - pcszFileName + : RTPATH_MAX); + bool fRc; + if (!pszName) + { + RTStrmPrintf(g_pStdErr, "Out of memory.\n"); + return false; + } + fRc = writePrintableString(pParameters->enmFormat, + pszName); + RTStrFree(pszName); + if (!fRc) + return false; + } + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + HAVE_ONESHOT, sizeof(HAVE_ONESHOT) - 1)) + { + if (!pParameters->fOneShot) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + HAVE_DAEMON, sizeof(HAVE_DAEMON) - 1)) + { + if (pParameters->fOneShot) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + STOP_COMMAND, sizeof(STOP_COMMAND) - 1)) + { + if ( pParameters->pcszStopCommand + && !writeCommand(pParameters->enmFormat, + pParameters->pcszStopCommand)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + STOP_ARGUMENTS, sizeof(STOP_ARGUMENTS) - 1)) + { + if ( pParameters->pcszStopArguments + && !writeQuoted(pParameters->enmFormat, + pParameters->pcszStopArguments)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + HAVE_STOP_COMMAND, sizeof(HAVE_STOP_COMMAND) - 1)) + { + if (!pParameters->pcszStopCommand) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + NO_STOP_COMMAND, sizeof(NO_STOP_COMMAND) - 1)) + { + if (pParameters->pcszStopCommand) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + STATUS_COMMAND, sizeof(STATUS_COMMAND) - 1)) + { + if ( pParameters->pcszStatusCommand + && !writeCommand(pParameters->enmFormat, + pParameters->pcszStatusCommand)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + STATUS_ARGUMENTS, sizeof(STATUS_ARGUMENTS) - 1)) + { + if ( pParameters->pcszStatusArguments + && !writeQuoted(pParameters->enmFormat, + pParameters->pcszStatusArguments)) + return false; + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + HAVE_STATUS_COMMAND, + sizeof(HAVE_STATUS_COMMAND) - 1)) + { + if (!pParameters->pcszStatusCommand) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + NO_STATUS_COMMAND, sizeof(NO_STATUS_COMMAND) - 1)) + { + if (pParameters->pcszStatusCommand) + skipLine(*ppachTemplate, cchTemplate, &cchWritten); + } + else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + "%%", 2)) + { + rc = RTStrmPutCh(g_pStdOut, '%'); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + } + else + { + RTStrmPrintf(g_pStdErr, "Unknown substitution sequence in input at \"%.*s\"\n", + RT_MIN(16, cchTemplate - cchWritten), + *ppachTemplate + cchWritten); + return false; + } + } + return true; +} + +bool getSequence(const char *pach, size_t cch, size_t *pcchRead, + const char *pcszSequence, size_t cchSequence) +{ + if ( cch - *pcchRead >= cchSequence + && !RTStrNCmp(pach + *pcchRead, pcszSequence, cchSequence)) + { + *pcchRead += cchSequence; + return true; + } + return false; +} + +/** Write a character to standard output and print an error and return false on + * failure. */ +bool outputCharacter(char ch) +{ + int rc = RTStrmWrite(g_pStdOut, &ch, 1); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + return true; +} + +/** Write a string to standard output and print an error and return false on + * failure. */ +bool outputString(const char *pcsz) +{ + int rc = RTStrmPutStr(g_pStdOut, pcsz); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + return true; +} + +/** Write a character to standard output, adding any escaping needed for the + * format being written. */ +static bool escapeAndOutputCharacter(enum ENMFORMAT enmFormat, char ch) +{ + if (enmFormat == FORMAT_SHELL) + { + if (ch == '\'') + return outputString("\'\\\'\'"); + return outputCharacter(ch); + } + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + return false; +} + +/** Write a character to standard output, adding any escaping needed for the + * format being written. */ +static bool outputArgumentSeparator(enum ENMFORMAT enmFormat) +{ + if (enmFormat == FORMAT_SHELL) + return outputString("\' \'"); + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + return false; +} + +bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand) +{ + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + for (; *pcszCommand; ++pcszCommand) + if (enmFormat == FORMAT_SHELL) + { + if (*pcszCommand == '\'') + { + if (!outputString("\'\\\'\'")) + return false; + } + else if (!outputCharacter(*pcszCommand)) + return false; + } + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + return true; +} + +const char aachEscapes[][2] = +{ + { 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' }, + { 't', '\t' }, { 'v', '\v' }, { 0, 0 } +}; + +bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted) +{ + /* Was the last character seen a back slash? */ + bool fEscaped = false; + /* Was the last character seen an argument separator (an unescaped space)? + */ + bool fNextArgument = false; + + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + for (; *pcszQuoted; ++pcszQuoted) + { + if (fEscaped) + { + bool fRc = true; + const char (*pachEscapes)[2]; + fEscaped = false; + /* One-letter escapes. */ + for (pachEscapes = aachEscapes; (*pachEscapes)[0]; ++pachEscapes) + if (*pcszQuoted == (*pachEscapes)[0]) + { + if (!escapeAndOutputCharacter(enmFormat, (*pachEscapes)[1])) + return false; + break; + } + if ((*pachEscapes)[0]) + continue; + /* Octal. */ + if (*pcszQuoted >= '0' && *pcszQuoted <= '7') + { + uint8_t cNum; + char *pchNext; + char achDigits[4]; + int rc; + RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted); + rc = RTStrToUInt8Ex(achDigits, &pchNext, 8, &cNum); + if (rc == VWRN_NUMBER_TOO_BIG) + { + RTStrmPrintf(g_pStdErr, "Invalid octal sequence at \"%.16s\"\n", + pcszQuoted - 1); + return false; + } + if (!escapeAndOutputCharacter(enmFormat, cNum)) + return false; + pcszQuoted += pchNext - achDigits - 1; + continue; + } + /* Hexadecimal. */ + if (*pcszQuoted == 'x') + { + uint8_t cNum; + char *pchNext; + char achDigits[3]; + int rc; + RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted + 1); + rc = RTStrToUInt8Ex(achDigits, &pchNext, 16, &cNum); + if ( rc == VWRN_NUMBER_TOO_BIG + || rc == VWRN_NEGATIVE_UNSIGNED + || RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Invalid hexadecimal sequence at \"%.16s\"\n", + pcszQuoted - 1); + return false; + } + if (!escapeAndOutputCharacter(enmFormat, cNum)) + return false; + pcszQuoted += pchNext - achDigits; + continue; + } + /* Output anything else non-zero as is. */ + if (*pcszQuoted) + { + if (!escapeAndOutputCharacter(enmFormat, *pcszQuoted)) + return false; + continue; + } + RTStrmPrintf(g_pStdErr, "Trailing back slash in argument.\n"); + return false; + } + /* Argument separator. */ + if (*pcszQuoted == ' ') + { + if (!fNextArgument && !outputArgumentSeparator(enmFormat)) + return false; + fNextArgument = true; + continue; + } + else + fNextArgument = false; + /* Start of escape sequence. */ + if (*pcszQuoted == '\\') + { + fEscaped = true; + continue; + } + /* Anything else. */ + if (!outputCharacter(*pcszQuoted)) + return false; + } + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + return true; +} + +bool writePrintableString(enum ENMFORMAT enmFormat, const char *pcszString) +{ + if (enmFormat == FORMAT_SHELL) + return outputString(pcszString); + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + return false; +} + +void skipLine(const char *pach, size_t cch, size_t *pcchRead) +{ + while ( *pcchRead < cch + && (pach)[*pcchRead] != '\n' + && (pach)[*pcchRead] != '\r') + ++*pcchRead; + while ( *pcchRead < cch + && ( (pach)[*pcchRead] == '\n' + || (pach)[*pcchRead] == '\r')) + ++*pcchRead; +} |