generate_service_file.cpp revision c7814cf6e1240a519cbec0441e033d0e2470ed00
/* $Id$ */
/** @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;
* 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 <iprt/initterm.h>
#ifndef READ_SIZE
/** How much of the input we read at a time. Override to something small for
* testing. */
#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 */
VBOX_VERSION_STRING "\n"
"All rights reserved.\n"
"\n");
}
static void showOptions(void);
{
if (!pcszName)
"Usage:\n"
"\n"
" %s --help|-h|-?|--version|-V|--format <format> <parameters...>\n\n",
"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");
" --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");
"Parameters:\n"
"\n");
" --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");
" --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"
);
" --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");
" --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");
" --one-shot\n"
" The service command is expected to do some work and exit immediately with"
" a status indicating success or failure.\n\n"
);
" --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"
);
" --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"
);
" --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"
);
" --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. */
};
/** @} */
struct SERVICEPARAMETERS
{
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;
};
{
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
enum
{
OPTION_FORMAT = 1,
};
static const RTGETOPTDEF s_aOptions[] =
{
{ "--format", OPTION_FORMAT,
{ "--command", OPTION_COMMAND,
{ "--arguments", OPTION_ARGUMENTS,
{ "--description", OPTION_DESCRIPTION,
{ "--service-name", OPTION_SERVICE_NAME,
{ "--one-shot", OPTION_ONE_SHOT,
{ "--stop-command", OPTION_STOP_COMMAND,
{ "--stop-arguments", OPTION_STOP_ARGUMENTS,
{ "--status-command", OPTION_STATUS_COMMAND,
{ "--status-arguments", OPTION_STATUS_ARGUMENTS,
};
int ch;
{
switch (ch)
{
case 'h':
return RTEXITCODE_SUCCESS;
break;
case 'V':
showLogo();
return RTEXITCODE_SUCCESS;
break;
case OPTION_FORMAT:
if (errorIfSet("--format",
return(RTEXITCODE_SYNTAX);
return(RTEXITCODE_SYNTAX);
break;
case OPTION_COMMAND:
return(RTEXITCODE_SYNTAX);
if (!checkAbsoluteFilePath("--command",
return(RTEXITCODE_SYNTAX);
break;
case OPTION_ARGUMENTS:
if (errorIfSet("--arguments",
return(RTEXITCODE_SYNTAX);
/* Quoting will be checked while writing out the string. */
break;
case OPTION_DESCRIPTION:
if (errorIfSet("--description",
return(RTEXITCODE_SYNTAX);
if (!checkPrintable("--description",
return(RTEXITCODE_SYNTAX);
break;
case OPTION_SERVICE_NAME:
if (errorIfSet("--service-name",
return(RTEXITCODE_SYNTAX);
if (!checkGraphic("--service-name",
return(RTEXITCODE_SYNTAX);
break;
case OPTION_ONE_SHOT:
Parameters.fOneShot = true;
break;
case OPTION_STOP_COMMAND:
if (errorIfSet("--stop-command",
return(RTEXITCODE_SYNTAX);
if (!checkAbsoluteFilePath("--stop-command",
return(RTEXITCODE_SYNTAX);
break;
case OPTION_STOP_ARGUMENTS:
if (errorIfSet("--stop-arguments",
return(RTEXITCODE_SYNTAX);
/* Quoting will be checked while writing out the string. */
break;
case OPTION_STATUS_COMMAND:
if (errorIfSet("--status-command",
return(RTEXITCODE_SYNTAX);
if (!checkAbsoluteFilePath("--status-command",
return(RTEXITCODE_SYNTAX);
break;
case OPTION_STATUS_ARGUMENTS:
if (errorIfSet("--status-arguments",
return(RTEXITCODE_SYNTAX);
/* Quoting will be checked while writing out the string. */
break;
default:
}
}
{
return(RTEXITCODE_SYNTAX);
}
{
return(RTEXITCODE_SYNTAX);
}
{
return(RTEXITCODE_SYNTAX);
}
{
return(RTEXITCODE_SYNTAX);
}
return createServiceFile(&Parameters)
}
/** Print an error and return true if an option is already set. */
{
if (isSet)
return isSet;
}
/** Match the string to a known format and return that (or "none" and print an
* error). */
{
return FORMAT_SHELL;
return FORMAT_NONE;
}
/** Check that the string is an absolute path to a file or print an error. */
{
return true;
return false;
}
/** Check that the string does not contain any non-printable characters. */
{
{
if (!RT_C_IS_PRINT(*pcch))
{
return false;
}
}
return true;
}
/** Check that the string does not contain any non-graphic characters. */
{
{
if (!RT_C_IS_GRAPH(*pcch))
{
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.
*/
{
char *pachTemplate = NULL;
return rc;
}
const char *pcszString);
/** 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. */
/* 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. */
{
if (cchTemplate == cbBuffer)
{
cbBuffer);
}
if (!*ppachTemplate)
{
return false;
}
if (RT_FAILURE(rc))
{
return false;
}
if (!cchRead)
cchTemplate += cchRead;
}
while (true)
{
/* Find the next '%' character if any and write out up to there (or the
* end if there is no '%'). */
: cchTemplate - cchWritten;
if (RT_FAILURE(rc))
{
return false;
}
cchWritten += cchToWrite;
if (!pchNext)
break;
/* And substitute any of our well-known strings. We favour code
* readability over efficiency here. */
{
if (!pParameters->pcszCommand)
{
return false;
}
return false;
}
{
if ( pParameters->pcszArguments
return false;
}
{
if (!pParameters->pcszDescription)
{
return false;
}
return false;
}
{
if ( !pParameters->pcszCommand
&& !pParameters->pcszServiceName)
{
return false;
}
if (pParameters->pcszServiceName)
{
return false;
}
else
{
const char *pcszFileName =
const char *pcszExtension =
: RTPATH_MAX);
bool fRc;
if (!pszName)
{
return false;
}
pszName);
if (!fRc)
return false;
}
}
{
if (!pParameters->fOneShot)
}
{
if (pParameters->fOneShot)
}
{
if ( pParameters->pcszStopCommand
return false;
}
{
if ( pParameters->pcszStopArguments
return false;
}
{
if (!pParameters->pcszStopCommand)
}
{
if (pParameters->pcszStopCommand)
}
{
if ( pParameters->pcszStatusCommand
return false;
}
{
return false;
}
sizeof(HAVE_STATUS_COMMAND) - 1))
{
if (!pParameters->pcszStatusCommand)
}
{
if (pParameters->pcszStatusCommand)
}
"%%", 2))
{
if (RT_FAILURE(rc))
{
return false;
}
}
else
{
*ppachTemplate + cchWritten);
return false;
}
}
return true;
}
{
{
*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)
{
if (RT_FAILURE(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)
{
if (RT_FAILURE(rc))
{
return false;
}
return true;
}
/** Write a character to standard output, adding any escaping needed for the
* format being written. */
{
if (enmFormat == FORMAT_SHELL)
{
if (ch == '\'')
return outputString("\'\\\'\'");
return outputCharacter(ch);
}
return false;
}
/** Write a character to standard output, adding any escaping needed for the
* format being written. */
{
if (enmFormat == FORMAT_SHELL)
return outputString("\' \'");
return false;
}
{
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 }
};
{
/* 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. */
if (*pcszQuoted == (*pachEscapes)[0])
{
return false;
break;
}
if ((*pachEscapes)[0])
continue;
/* Octal. */
{
char *pchNext;
char achDigits[4];
int rc;
if (rc == VWRN_NUMBER_TOO_BIG)
{
pcszQuoted - 1);
return false;
}
return false;
continue;
}
/* Hexadecimal. */
if (*pcszQuoted == 'x')
{
char *pchNext;
char achDigits[3];
int rc;
if ( rc == VWRN_NUMBER_TOO_BIG
|| rc == VWRN_NEGATIVE_UNSIGNED
|| RT_FAILURE(rc))
{
pcszQuoted - 1);
return false;
}
return false;
continue;
}
/* Output anything else non-zero as is. */
if (*pcszQuoted)
{
return false;
continue;
}
return false;
}
/* Argument separator. */
if (*pcszQuoted == ' ')
{
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;
}
{
if (enmFormat == FORMAT_SHELL)
return outputString(pcszString);
return false;
}
{
++*pcchRead;
++*pcchRead;
}