generate_service_file.cpp revision 376e7522b2055ec5f2834c8e52dfefbee1c5ea8b
/* $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 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.
*/
#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%"
void showLogo(void)
{
static bool s_fShown; /* show only once */
VBOX_VERSION_STRING "\n"
"All rights reserved.\n"
"\n");
}
{
if (!pcszName)
"Usage:\n"
"\n"
" %s --help|-h|-?|--version|-V|<options>\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. The\n",
"exact strings substituted will depend on the format of the template, for\n"
"example shell script or systemd unit file. The sequence \"%%%%\" in the template\n"
"will be replaced by a single \"%%\" character. The description of the options\n"
"also describes the sequences which will be replaced in the template. All\n"
"arguments should be in Utf-8 encoding.\n"
"\n"
"\n");
" --help|-h|-?\n"
" Print this help text and exit.\n"
"\n"
" --version|-V\n"
" Print version information and exit.\n"
"\n");
"Required options:\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"
" --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. Substituted for the sequence\n"
" \"%%COMMAND%%\" in the template.\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. ASCII\n"
" characters 0 to 31 and 127 should not be used. Substituted for the\n"
" sequence \"%%DESCRIPTION%%\" in the template.\n"
"\n"
"Other options:\n"
"\n");
" --arguments <arguments>\n"
" The arguments to pass to the executable file when it is started, as a\n"
" single parameter. ASCII characters 0 to 31, \"\'\" and 127 should be escaped\n"
" in C string-style and spaces inside words should be preceeded by a back\n"
" slash. Some systemd-style \"%%\" sequences may be added at a future time.\n"
" Substituted for the sequence \"%%ARGUMENTS%%\" in the template.\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. Substituted for the sequence \"%%SERVICE_NAME%%\" in the\n"
" template.\n"
"\n");
}
/** @name Template format.
* @{
*/
enum ENMFORMAT
{
/** No format selected. */
FORMAT_NONE = 0,
/** Shell script format. */
};
/** @} */
const char *pcszCommand,
const char *pcszArguments,
const char *pcszDescription,
const char *pcszServiceName);
{
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,
};
int ch;
const char *pcszCommand = NULL;
const char *pcszArguments = NULL;
const char *pcszDescription = NULL;
const char *pcszServiceName = NULL;
{
switch (ch)
{
case 'h':
return RTEXITCODE_SUCCESS;
break;
case 'V':
showLogo();
return RTEXITCODE_SUCCESS;
break;
case OPTION_FORMAT:
return(RTEXITCODE_SYNTAX);
if (enmFormat == FORMAT_NONE)
return(RTEXITCODE_SYNTAX);
break;
case OPTION_COMMAND:
return(RTEXITCODE_SYNTAX);
return(RTEXITCODE_SYNTAX);
break;
case OPTION_ARGUMENTS:
return(RTEXITCODE_SYNTAX);
return(RTEXITCODE_SYNTAX);
break;
case OPTION_DESCRIPTION:
return(RTEXITCODE_SYNTAX);
return(RTEXITCODE_SYNTAX);
break;
case OPTION_SERVICE_NAME:
return(RTEXITCODE_SYNTAX);
return(RTEXITCODE_SYNTAX);
break;
default:
}
}
return(RTEXITCODE_SYNTAX);
}
/** Print an error and return true if an option is already set. */
{
if (isSet)
return isSet;
}
/** Print an error and return true if an option is not 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 and
* that the quoting and escaping are balanced. */
{
bool fEscaped = false;
{
if (!RT_C_IS_PRINT(*pcch))
{
return false;
}
if (fEscaped)
fEscaped = false;
else
{
if (*pcch == '\\')
fEscaped == true;
if (*pcch == '\'')
++cQuotes;
}
}
if (cQuotes % 2)
else if (fEscaped)
pcszName);
else
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,
const char *pcszCommand,
const char *pcszArguments,
const char *pcszDescription,
const char *pcszServiceName);
/**
* 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.
*/
const char *pcszCommand,
const char *pcszArguments,
const char *pcszDescription,
const char *pcszServiceName)
{
char *pachTemplate = NULL;
return rc;
}
const char *pcszString);
/** The actual implemenation code for @a createServiceFile. */
bool createServiceFileCore(char **ppachTemplate,
const char *pcszCommand,
const char *pcszArguments,
const char *pcszDescription,
const char *pcszServiceName)
{
/* 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. */
sizeof(COMMAND) - 1))
{
return false;
}
sizeof(ARGUMENTS) - 1))
{
return false;
}
sizeof(DESCRIPTION) - 1))
{
return false;
}
sizeof(SERVICE_NAME) - 1))
{
if (pcszServiceName)
{
return false;
}
else
{
: RTPATH_MAX);
bool fRc;
if (!pszName)
{
return false;
}
if (!fRc)
return false;
}
}
{
if (RT_FAILURE(rc))
{
return false;
}
cchWritten += 2;
}
else
{
*ppachTemplate + cchWritten);
return false;
}
}
return true;
}
/** 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 (; *pcszArguments; ++pcszArguments)
{
if (fEscaped)
{
bool fRc = true;
const char (*pachEscapes)[2];
fEscaped = false;
/* One-letter escapes. */
if (*pcszArguments == (*pachEscapes)[0])
{
return false;
break;
}
if ((*pachEscapes)[0])
continue;
/* Octal. */
{
char *pchNext;
char achDigits[4];
int rc;
if (rc == VWRN_NUMBER_TOO_BIG)
{
pcszArguments - 1);
return false;
}
return false;
continue;
}
/* Hexadecimal. */
if (*pcszArguments == 'x')
{
char *pchNext;
char achDigits[3];
int rc;
if ( rc == VWRN_NUMBER_TOO_BIG
|| rc == VWRN_NEGATIVE_UNSIGNED
|| RT_FAILURE(rc))
{
pcszArguments - 1);
return false;
}
return false;
continue;
}
/* Reject anything else. */
return false;
}
/* Argument separator. */
if (*pcszArguments == ' ')
{
return false;
fNextArgument = true;
continue;
}
else
fNextArgument = false;
/* Start of escape sequence. */
if (*pcszArguments == '\\')
{
fEscaped = true;
continue;
}
/* Anything else. */
if (!outputCharacter(*pcszArguments))
return false;
}
if (enmFormat == FORMAT_SHELL)
if (!outputCharacter('\''))
return false;
return true;
}
{
if (enmFormat == FORMAT_SHELL)
return outputString(pcszString);
return false;
}