/* $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 #include #include #include #include #include #include #include #include #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 \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 \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 \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 \n" " A short description of the service which can also be used in sentences\n" " like \" failed to start.\", as a single parameter. Characters\n" " 0 to 31 and 127 should not be used.\n\n" ); RTPrintf( " --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 \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 \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 \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 \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 \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; }