1
/* $Id: generate_service_file.cpp $ */
3
* Read a service file template from standard input and output a service file
4
* to standard output generated from the template based on arguments passed to
5
* the utility. See the usage text for more information.
9
* Copyright (C) 2012-2013 Oracle Corporation
11
* This file is part of VirtualBox Open Source Edition (OSE), as
12
* available from http://www.virtualbox.org. This file is free software;
13
* you can redistribute it and/or modify it under the terms of the GNU
14
* General Public License (GPL) as published by the Free Software
15
* Foundation, in version 2 as it comes in the "COPYING" file of the
16
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
21
* Description of the generation process.
23
* A template for the service file to be generated is fed into standard input
24
* and the service file is sent to standard output. The following
25
* substitutions are performed based on the command line parameters supplied,
26
* with all quoting appropriate to the format of the template as specified on
29
* %COMMAND% -> path to the service binary or script.
30
* %ARGUMENTS% -> the arguments to pass to the binary when starting the
32
* %SERVICE_NAME% -> the name of the service.
33
* %DESCRIPTION% -> the short description of the service.
34
* %STOP_COMMAND% -> path to the command used to stop the service.
35
* %STOP_ARGUMENTS% -> the arguments for the stop command
36
* %STATUS_COMMAND% -> path to the command used to determine the service
38
* %STATUS_ARGUMENTS% -> the arguments for the status command
40
* %NO_STOP_COMMAND% -> if no stop command was specified, this and all text
41
* following it on the line (including the end-of-
42
* line) will be removed, otherwise only the marker
44
* %HAVE_STOP_COMMAND% -> like above, but text on the line will be removed
45
* if a stop command was supplied.
46
* %NO_STATUS_COMMAND% -> Analogue to %NO_STOP_COMMAND% for the status
48
* %HAVE_STATUS_COMMAND% -> Analogue to %HAVE_STOP_COMMAND% for the status
50
* %HAVE_ONESHOT% -> like above, text on the line will be removed unless
51
* --one-shot was specified on the command line.
52
* %HAVE_DAEMON% -> the same if --one-shot was not specified.
54
* %% will be replaced with a single %.
57
#include <VBox/version.h>
59
#include <iprt/ctype.h>
60
#include <iprt/getopt.h>
61
#include <iprt/initterm.h>
63
#include <iprt/message.h>
64
#include <iprt/path.h>
65
#include <iprt/stream.h>
66
#include <iprt/string.h>
69
/** How much of the input we read at a time. Override to something small for
71
# define READ_SIZE _1M
74
/* Macros for the template substitution sequences to guard against mis-types. */
75
#define COMMAND "%COMMAND%"
76
#define ARGUMENTS "%ARGUMENTS%"
77
#define DESCRIPTION "%DESCRIPTION%"
78
#define SERVICE_NAME "%SERVICE_NAME%"
79
#define HAVE_ONESHOT "%HAVE_ONESHOT%"
80
#define HAVE_DAEMON "%HAVE_DAEMON%"
81
#define STOP_COMMAND "%STOP_COMMAND%"
82
#define STOP_ARGUMENTS "%STOP_ARGUMENTS%"
83
#define HAVE_STOP_COMMAND "%HAVE_STOP_COMMAND%"
84
#define NO_STOP_COMMAND "%NO_STOP_COMMAND%"
85
#define STATUS_COMMAND "%STATUS_COMMAND%"
86
#define STATUS_ARGUMENTS "%STATUS_ARGUMENTS%"
87
#define HAVE_STATUS_COMMAND "%HAVE_STATUS_COMMAND%"
88
#define NO_STATUS_COMMAND "%NO_STATUS_COMMAND%"
92
static bool s_fShown; /* show only once */
94
RTPrintf(VBOX_PRODUCT " Service File Generator Version "
95
VBOX_VERSION_STRING "\n"
96
"(C) 2012" /* "-" VBOX_C_YEAR */ " " VBOX_VENDOR "\n"
97
"All rights reserved.\n"
101
static void showOptions(void);
103
void showUsage(const char *pcszArgv0)
105
const char *pcszName = strrchr(pcszArgv0, '/');
107
pcszName = pcszArgv0;
111
" %s --help|-h|-?|--version|-V|--format <format> <parameters...>\n\n",
114
"Read a service file template from standard input and output a service file to\n"
115
"standard output which was generated from the template based on parameters\n"
116
"passed on the utility's command line. Generation is done by replacing well-\n"
117
"known text sequences in the template with strings based on the parameters.\n"
118
"All strings should be in UTF-8 format. Processing will stop if a sequence is\n"
119
"read which cannot be replace based on the parameters supplied.\n\n");
123
" Print this help text and exit.\n\n"
125
" Print version information and exit.\n\n"
126
" --format <shell>\n"
127
" The format of the template. Currently only \"shell\" for shell script\n"
128
" is supported. This affects escaping of strings substituted.\n\n");
133
" --command <command>\n"
134
" The absolute path of the executable file to be started by the service.\n"
135
" No form of quoting should be used here.\n\n");
137
" --description <description>\n"
138
" A short description of the service which can also be used in sentences\n"
139
" like \"<description> failed to start.\", as a single parameter. Characters\n"
140
" 0 to 31 and 127 should not be used.\n\n"
143
" --arguments <arguments>\n"
144
" The arguments to pass to the executable file when it is started, as a\n"
145
" single parameter. Characters \" \", \"\\\" and \"%%\" must be escaped with\n"
146
" back-slashes and C string-style back-slash escapes are recognised. Some\n"
147
" systemd-style \"%%\" sequences may be added at a future time.\n\n");
149
" --service-name <name>\n"
150
" Specify the name of the service. By default the base name without the\n"
151
" extension of the command binary is used. Only ASCII characters 33 to 126\n"
152
" should be used.\n\n");
155
" The service command is expected to do some work and exit immediately with"
156
" a status indicating success or failure.\n\n"
159
" --stop-command <command>\n"
160
" The command which should be used to stop the service before sending the\n"
161
" termination signal to the main process. No form of quoting should be\n"
165
" --stop-arguments <arguments>\n"
166
" Arguments for the stop command. This may only be used in combination\n"
167
" with \"--stop-command\". Quoting is the same as for \"--arguments\".\n\n"
170
" --status-command <command>\n"
171
" The command which should be used to determine the status of the service.\n"
172
" This may not be respected by all service management systems. The command\n"
173
" should return an LSB status code. No form of quoting should be used.\n\n"
176
" --stop-arguments <arguments>\n"
177
" Arguments for the status command. This may only be used in combination\n"
178
" with \"--status-command\". Quoting is the same as for \"--arguments\".\n\n"
182
/** @name Template format.
187
/** No format selected. */
189
/** Shell script format. */
194
struct SERVICEPARAMETERS
196
enum ENMFORMAT enmFormat;
197
const char *pcszCommand;
198
const char *pcszArguments;
199
const char *pcszDescription;
200
const char *pcszServiceName;
202
const char *pcszStopCommand;
203
const char *pcszStopArguments;
204
const char *pcszStatusCommand;
205
const char *pcszStatusArguments;
208
static bool errorIfSet(const char *pcszName, bool isSet);
209
static enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue);
210
static bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue);
211
static bool checkPrintable(const char *pcszName, const char *pcszValue);
212
static bool checkGraphic(const char *pcszName, const char *pcszValue);
213
static bool createServiceFile(struct SERVICEPARAMETERS *pParameters);
215
int main(int cArgs, char **apszArgs)
217
int rc = RTR3InitExe(cArgs, &apszArgs, 0);
219
return RTMsgInitFailure(rc);
230
OPTION_STOP_ARGUMENTS,
231
OPTION_STATUS_COMMAND,
232
OPTION_STATUS_ARGUMENTS
235
static const RTGETOPTDEF s_aOptions[] =
237
{ "--format", OPTION_FORMAT,
238
RTGETOPT_REQ_STRING },
239
{ "--command", OPTION_COMMAND,
240
RTGETOPT_REQ_STRING },
241
{ "--arguments", OPTION_ARGUMENTS,
242
RTGETOPT_REQ_STRING },
243
{ "--description", OPTION_DESCRIPTION,
244
RTGETOPT_REQ_STRING },
245
{ "--service-name", OPTION_SERVICE_NAME,
246
RTGETOPT_REQ_STRING },
247
{ "--one-shot", OPTION_ONE_SHOT,
248
RTGETOPT_REQ_NOTHING },
249
{ "--stop-command", OPTION_STOP_COMMAND,
250
RTGETOPT_REQ_STRING },
251
{ "--stop-arguments", OPTION_STOP_ARGUMENTS,
252
RTGETOPT_REQ_STRING },
253
{ "--status-command", OPTION_STATUS_COMMAND,
254
RTGETOPT_REQ_STRING },
255
{ "--status-arguments", OPTION_STATUS_ARGUMENTS,
256
RTGETOPT_REQ_STRING }
260
struct SERVICEPARAMETERS Parameters = { FORMAT_NONE };
261
RTGETOPTUNION ValueUnion;
262
RTGETOPTSTATE GetState;
263
RTGetOptInit(&GetState, cArgs, apszArgs, s_aOptions,
264
RT_ELEMENTS(s_aOptions), 1, 0);
265
while ((ch = RTGetOpt(&GetState, &ValueUnion)))
270
showUsage(apszArgs[0]);
271
return RTEXITCODE_SUCCESS;
276
return RTEXITCODE_SUCCESS;
280
if (errorIfSet("--format",
281
Parameters.enmFormat != FORMAT_NONE))
282
return(RTEXITCODE_SYNTAX);
284
= getFormat("--format", ValueUnion.psz);
285
if (Parameters.enmFormat == FORMAT_NONE)
286
return(RTEXITCODE_SYNTAX);
290
if (errorIfSet("--command", Parameters.pcszCommand))
291
return(RTEXITCODE_SYNTAX);
292
Parameters.pcszCommand = ValueUnion.psz;
293
if (!checkAbsoluteFilePath("--command",
294
Parameters.pcszCommand))
295
return(RTEXITCODE_SYNTAX);
298
case OPTION_ARGUMENTS:
299
if (errorIfSet("--arguments",
300
Parameters.pcszArguments))
301
return(RTEXITCODE_SYNTAX);
302
/* Quoting will be checked while writing out the string. */
303
Parameters.pcszArguments = ValueUnion.psz;
306
case OPTION_DESCRIPTION:
307
if (errorIfSet("--description",
308
Parameters.pcszDescription))
309
return(RTEXITCODE_SYNTAX);
310
Parameters.pcszDescription = ValueUnion.psz;
311
if (!checkPrintable("--description",
312
Parameters.pcszDescription))
313
return(RTEXITCODE_SYNTAX);
316
case OPTION_SERVICE_NAME:
317
if (errorIfSet("--service-name",
318
Parameters.pcszServiceName))
319
return(RTEXITCODE_SYNTAX);
320
Parameters.pcszServiceName = ValueUnion.psz;
321
if (!checkGraphic("--service-name",
322
Parameters.pcszServiceName))
323
return(RTEXITCODE_SYNTAX);
326
case OPTION_ONE_SHOT:
327
Parameters.fOneShot = true;
330
case OPTION_STOP_COMMAND:
331
if (errorIfSet("--stop-command",
332
Parameters.pcszStopCommand))
333
return(RTEXITCODE_SYNTAX);
334
Parameters.pcszStopCommand = ValueUnion.psz;
335
if (!checkAbsoluteFilePath("--stop-command",
336
Parameters.pcszStopCommand))
337
return(RTEXITCODE_SYNTAX);
340
case OPTION_STOP_ARGUMENTS:
341
if (errorIfSet("--stop-arguments",
342
Parameters.pcszStopArguments))
343
return(RTEXITCODE_SYNTAX);
344
/* Quoting will be checked while writing out the string. */
345
Parameters.pcszStopArguments = ValueUnion.psz;
348
case OPTION_STATUS_COMMAND:
349
if (errorIfSet("--status-command",
350
Parameters.pcszStatusCommand))
351
return(RTEXITCODE_SYNTAX);
352
Parameters.pcszStatusCommand = ValueUnion.psz;
353
if (!checkAbsoluteFilePath("--status-command",
354
Parameters.pcszStatusCommand))
355
return(RTEXITCODE_SYNTAX);
358
case OPTION_STATUS_ARGUMENTS:
359
if (errorIfSet("--status-arguments",
360
Parameters.pcszStatusArguments))
361
return(RTEXITCODE_SYNTAX);
362
/* Quoting will be checked while writing out the string. */
363
Parameters.pcszStatusArguments = ValueUnion.psz;
367
return RTGetOptPrintError(ch, &ValueUnion);
370
if (Parameters.enmFormat == FORMAT_NONE)
372
RTStrmPrintf(g_pStdErr, "--format must be specified.\n");
373
return(RTEXITCODE_SYNTAX);
375
if (Parameters.pcszArguments && !Parameters.pcszCommand)
377
RTStrmPrintf(g_pStdErr, "--arguments requires --command to be specified.\n");
378
return(RTEXITCODE_SYNTAX);
380
if (Parameters.pcszStopArguments && !Parameters.pcszStopCommand)
382
RTStrmPrintf(g_pStdErr, "--stop-arguments requires --stop-command to be specified.\n");
383
return(RTEXITCODE_SYNTAX);
385
if (Parameters.pcszStatusArguments && !Parameters.pcszStatusCommand)
387
RTStrmPrintf(g_pStdErr, "--status-arguments requires --status-command to be specified.\n");
388
return(RTEXITCODE_SYNTAX);
390
return createServiceFile(&Parameters)
392
: RTEXITCODE_FAILURE;
395
/** Print an error and return true if an option is already set. */
396
bool errorIfSet(const char *pcszName, bool isSet)
399
RTStrmPrintf(g_pStdErr, "%s may only be specified once.\n", pcszName);
403
/** Match the string to a known format and return that (or "none" and print an
405
enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue)
407
if (!strcmp(pcszValue, "shell"))
409
RTStrmPrintf(g_pStdErr, "%s: unknown format %s.\n", pcszName, pcszValue);
413
/** Check that the string is an absolute path to a file or print an error. */
414
bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue)
416
if (RTPathFilename(pcszValue) && RTPathStartsWithRoot(pcszValue))
418
RTStrmPrintf(g_pStdErr, "%s: %s must be an absolute path of a file.\n", pcszName, pcszValue);
422
/** Check that the string does not contain any non-printable characters. */
423
bool checkPrintable(const char *pcszName, const char *pcszValue)
425
const char *pcch = pcszValue;
426
for (; *pcch; ++pcch)
428
if (!RT_C_IS_PRINT(*pcch))
430
RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n",
431
pcszName, pcch - pcszValue, pcszValue);
438
/** Check that the string does not contain any non-graphic characters. */
439
static bool checkGraphic(const char *pcszName, const char *pcszValue)
441
const char *pcch = pcszValue;
442
for (; *pcch; ++pcch)
444
if (!RT_C_IS_GRAPH(*pcch))
446
RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n",
447
pcszName, pcch - pcszValue, pcszValue);
454
static bool createServiceFileCore(char **ppachTemplate,
455
struct SERVICEPARAMETERS
459
* Read standard input and write it to standard output, doing all substitutions
460
* as per the usage documentation.
461
* @note This is a wrapper around the actual function to simplify resource
462
* allocation without requiring a single point of exit.
464
bool createServiceFile(struct SERVICEPARAMETERS *pParameters)
466
char *pachTemplate = NULL;
467
bool rc = createServiceFileCore(&pachTemplate, pParameters);
468
RTMemFree(pachTemplate);
472
static bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
473
const char *pcszSequence, size_t cchSequence);
474
static bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand);
475
static bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted);
476
static bool writePrintableString(enum ENMFORMAT enmFormat,
477
const char *pcszString);
478
static void skipLine(const char *pach, size_t cch, size_t *pcchRead);
480
/** The actual implemenation code for @a createServiceFile. */
481
bool createServiceFileCore(char **ppachTemplate,
482
struct SERVICEPARAMETERS *pParameters)
484
/* The size of the template data we have read. */
485
size_t cchTemplate = 0;
486
/* The size of the buffer we have allocated. */
488
/* How much of the template data we have written out. */
489
size_t cchWritten = 0;
490
int rc = VINF_SUCCESS;
491
/* First of all read in the file. */
492
while (rc != VINF_EOF)
496
if (cchTemplate == cbBuffer)
498
cbBuffer += READ_SIZE;
499
*ppachTemplate = (char *)RTMemRealloc((void *)*ppachTemplate,
504
RTStrmPrintf(g_pStdErr, "Out of memory.\n");
507
rc = RTStrmReadEx(g_pStdIn, *ppachTemplate + cchTemplate,
508
cbBuffer - cchTemplate, &cchRead);
511
RTStrmPrintf(g_pStdErr, "Error reading input: %Rrc\n", rc);
516
cchTemplate += cchRead;
520
/* Find the next '%' character if any and write out up to there (or the
521
* end if there is no '%'). */
522
char *pchNext = (char *) memchr((void *)(*ppachTemplate + cchWritten),
523
'%', cchTemplate - cchWritten);
524
size_t cchToWrite = pchNext
525
? pchNext - *ppachTemplate - cchWritten
526
: cchTemplate - cchWritten;
527
rc = RTStrmWrite(g_pStdOut, *ppachTemplate + cchWritten, cchToWrite);
530
RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
533
cchWritten += cchToWrite;
536
/* And substitute any of our well-known strings. We favour code
537
* readability over efficiency here. */
538
if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
539
COMMAND, sizeof(COMMAND) - 1))
541
if (!pParameters->pcszCommand)
543
RTStrmPrintf(g_pStdErr, "--command not specified.\n");
546
if (!writeCommand(pParameters->enmFormat,
547
pParameters->pcszCommand))
550
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
551
ARGUMENTS, sizeof(ARGUMENTS) - 1))
553
if ( pParameters->pcszArguments
554
&& !writeQuoted(pParameters->enmFormat,
555
pParameters->pcszArguments))
558
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
559
DESCRIPTION, sizeof(DESCRIPTION) - 1))
561
if (!pParameters->pcszDescription)
563
RTStrmPrintf(g_pStdErr, "--description not specified.\n");
566
if (!writePrintableString(pParameters->enmFormat,
567
pParameters->pcszDescription))
570
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
571
SERVICE_NAME, sizeof(SERVICE_NAME) - 1))
573
if ( !pParameters->pcszCommand
574
&& !pParameters->pcszServiceName)
576
RTStrmPrintf(g_pStdErr, "Neither --command nor --service-name specified.\n");
579
if (pParameters->pcszServiceName)
581
if (!writePrintableString(pParameters->enmFormat,
582
pParameters->pcszServiceName))
587
const char *pcszFileName =
588
RTPathFilename(pParameters->pcszCommand);
589
const char *pcszExtension =
590
RTPathExt(pParameters->pcszCommand);
591
char *pszName = RTStrDupN(pcszFileName,
593
? pcszExtension - pcszFileName
598
RTStrmPrintf(g_pStdErr, "Out of memory.\n");
601
fRc = writePrintableString(pParameters->enmFormat,
608
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
609
HAVE_ONESHOT, sizeof(HAVE_ONESHOT) - 1))
611
if (!pParameters->fOneShot)
612
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
614
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
615
HAVE_DAEMON, sizeof(HAVE_DAEMON) - 1))
617
if (pParameters->fOneShot)
618
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
620
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
621
STOP_COMMAND, sizeof(STOP_COMMAND) - 1))
623
if ( pParameters->pcszStopCommand
624
&& !writeCommand(pParameters->enmFormat,
625
pParameters->pcszStopCommand))
628
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
629
STOP_ARGUMENTS, sizeof(STOP_ARGUMENTS) - 1))
631
if ( pParameters->pcszStopArguments
632
&& !writeQuoted(pParameters->enmFormat,
633
pParameters->pcszStopArguments))
636
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
637
HAVE_STOP_COMMAND, sizeof(HAVE_STOP_COMMAND) - 1))
639
if (!pParameters->pcszStopCommand)
640
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
642
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
643
NO_STOP_COMMAND, sizeof(NO_STOP_COMMAND) - 1))
645
if (pParameters->pcszStopCommand)
646
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
648
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
649
STATUS_COMMAND, sizeof(STATUS_COMMAND) - 1))
651
if ( pParameters->pcszStatusCommand
652
&& !writeCommand(pParameters->enmFormat,
653
pParameters->pcszStatusCommand))
656
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
657
STATUS_ARGUMENTS, sizeof(STATUS_ARGUMENTS) - 1))
659
if ( pParameters->pcszStatusArguments
660
&& !writeQuoted(pParameters->enmFormat,
661
pParameters->pcszStatusArguments))
664
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
666
sizeof(HAVE_STATUS_COMMAND) - 1))
668
if (!pParameters->pcszStatusCommand)
669
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
671
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
672
NO_STATUS_COMMAND, sizeof(NO_STATUS_COMMAND) - 1))
674
if (pParameters->pcszStatusCommand)
675
skipLine(*ppachTemplate, cchTemplate, &cchWritten);
677
else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
680
rc = RTStrmPutCh(g_pStdOut, '%');
683
RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
689
RTStrmPrintf(g_pStdErr, "Unknown substitution sequence in input at \"%.*s\"\n",
690
RT_MIN(16, cchTemplate - cchWritten),
691
*ppachTemplate + cchWritten);
698
bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
699
const char *pcszSequence, size_t cchSequence)
701
if ( cch - *pcchRead >= cchSequence
702
&& !RTStrNCmp(pach + *pcchRead, pcszSequence, cchSequence))
704
*pcchRead += cchSequence;
710
/** Write a character to standard output and print an error and return false on
712
bool outputCharacter(char ch)
714
int rc = RTStrmWrite(g_pStdOut, &ch, 1);
717
RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
723
/** Write a string to standard output and print an error and return false on
725
bool outputString(const char *pcsz)
727
int rc = RTStrmPutStr(g_pStdOut, pcsz);
730
RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
736
/** Write a character to standard output, adding any escaping needed for the
737
* format being written. */
738
static bool escapeAndOutputCharacter(enum ENMFORMAT enmFormat, char ch)
740
if (enmFormat == FORMAT_SHELL)
743
return outputString("\'\\\'\'");
744
return outputCharacter(ch);
746
RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
750
/** Write a character to standard output, adding any escaping needed for the
751
* format being written. */
752
static bool outputArgumentSeparator(enum ENMFORMAT enmFormat)
754
if (enmFormat == FORMAT_SHELL)
755
return outputString("\' \'");
756
RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
760
bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand)
762
if (enmFormat == FORMAT_SHELL)
763
if (!outputCharacter('\''))
765
for (; *pcszCommand; ++pcszCommand)
766
if (enmFormat == FORMAT_SHELL)
768
if (*pcszCommand == '\'')
770
if (!outputString("\'\\\'\'"))
773
else if (!outputCharacter(*pcszCommand))
776
if (enmFormat == FORMAT_SHELL)
777
if (!outputCharacter('\''))
782
const char aachEscapes[][2] =
784
{ 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' },
785
{ 't', '\t' }, { 'v', '\v' }, { 0, 0 }
788
bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted)
790
/* Was the last character seen a back slash? */
791
bool fEscaped = false;
792
/* Was the last character seen an argument separator (an unescaped space)?
794
bool fNextArgument = false;
796
if (enmFormat == FORMAT_SHELL)
797
if (!outputCharacter('\''))
799
for (; *pcszQuoted; ++pcszQuoted)
804
const char (*pachEscapes)[2];
806
/* One-letter escapes. */
807
for (pachEscapes = aachEscapes; (*pachEscapes)[0]; ++pachEscapes)
808
if (*pcszQuoted == (*pachEscapes)[0])
810
if (!escapeAndOutputCharacter(enmFormat, (*pachEscapes)[1]))
814
if ((*pachEscapes)[0])
817
if (*pcszQuoted >= '0' && *pcszQuoted <= '7')
823
RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted);
824
rc = RTStrToUInt8Ex(achDigits, &pchNext, 8, &cNum);
825
if (rc == VWRN_NUMBER_TOO_BIG)
827
RTStrmPrintf(g_pStdErr, "Invalid octal sequence at \"%.16s\"\n",
831
if (!escapeAndOutputCharacter(enmFormat, cNum))
833
pcszQuoted += pchNext - achDigits - 1;
837
if (*pcszQuoted == 'x')
843
RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted + 1);
844
rc = RTStrToUInt8Ex(achDigits, &pchNext, 16, &cNum);
845
if ( rc == VWRN_NUMBER_TOO_BIG
846
|| rc == VWRN_NEGATIVE_UNSIGNED
849
RTStrmPrintf(g_pStdErr, "Invalid hexadecimal sequence at \"%.16s\"\n",
853
if (!escapeAndOutputCharacter(enmFormat, cNum))
855
pcszQuoted += pchNext - achDigits;
858
/* Output anything else non-zero as is. */
861
if (!escapeAndOutputCharacter(enmFormat, *pcszQuoted))
865
RTStrmPrintf(g_pStdErr, "Trailing back slash in argument.\n");
868
/* Argument separator. */
869
if (*pcszQuoted == ' ')
871
if (!fNextArgument && !outputArgumentSeparator(enmFormat))
873
fNextArgument = true;
877
fNextArgument = false;
878
/* Start of escape sequence. */
879
if (*pcszQuoted == '\\')
885
if (!outputCharacter(*pcszQuoted))
888
if (enmFormat == FORMAT_SHELL)
889
if (!outputCharacter('\''))
894
bool writePrintableString(enum ENMFORMAT enmFormat, const char *pcszString)
896
if (enmFormat == FORMAT_SHELL)
897
return outputString(pcszString);
898
RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
902
void skipLine(const char *pach, size_t cch, size_t *pcchRead)
904
while ( *pcchRead < cch
905
&& (pach)[*pcchRead] != '\n'
906
&& (pach)[*pcchRead] != '\r')
908
while ( *pcchRead < cch
909
&& ( (pach)[*pcchRead] == '\n'
910
|| (pach)[*pcchRead] == '\r'))