~ubuntu-branches/ubuntu/trusty/virtualbox-lts-xenial/trusty-proposed

« back to all changes in this revision

Viewing changes to src/VBox/Installer/linux/install_service/generate_service_file.cpp

  • Committer: Package Import Robot
  • Author(s): Gianfranco Costamagna
  • Date: 2016-02-23 14:28:26 UTC
  • Revision ID: package-import@ubuntu.com-20160223142826-bdu69el2z6wa2a44
Tags: upstream-4.3.36-dfsg
ImportĀ upstreamĀ versionĀ 4.3.36-dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* $Id: generate_service_file.cpp $ */
 
2
/** @file
 
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.
 
6
 */
 
7
 
 
8
/*
 
9
 * Copyright (C) 2012-2013 Oracle Corporation
 
10
 *
 
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.
 
18
 */
 
19
 
 
20
/**
 
21
 * Description of the generation process.
 
22
 *
 
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
 
27
 * the command line.
 
28
 *
 
29
 * %COMMAND%          -> path to the service binary or script.
 
30
 * %ARGUMENTS%        -> the arguments to pass to the binary when starting the
 
31
 *                       service.
 
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
 
37
 *                       status.
 
38
 * %STATUS_ARGUMENTS% -> the arguments for the status command
 
39
 
 
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
 
43
 *                          will be removed.
 
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
 
47
 *                          command.
 
48
 * %HAVE_STATUS_COMMAND% -> Analogue to %HAVE_STOP_COMMAND% for the status
 
49
 *                          command.
 
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.
 
53
 *
 
54
 * %% will be replaced with a single %.
 
55
 */
 
56
 
 
57
#include <VBox/version.h>
 
58
 
 
59
#include <iprt/ctype.h>
 
60
#include <iprt/getopt.h>
 
61
#include <iprt/initterm.h>
 
62
#include <iprt/mem.h>
 
63
#include <iprt/message.h>
 
64
#include <iprt/path.h>
 
65
#include <iprt/stream.h>
 
66
#include <iprt/string.h>
 
67
 
 
68
#ifndef READ_SIZE
 
69
/** How much of the input we read at a time.  Override to something small for
 
70
 *  testing. */
 
71
# define READ_SIZE _1M
 
72
#endif
 
73
 
 
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%"
 
89
 
 
90
void showLogo(void)
 
91
{
 
92
    static bool s_fShown; /* show only once */
 
93
 
 
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"
 
98
             "\n");
 
99
}
 
100
 
 
101
static void showOptions(void);
 
102
 
 
103
void showUsage(const char *pcszArgv0)
 
104
{
 
105
    const char *pcszName = strrchr(pcszArgv0, '/');
 
106
    if (!pcszName)
 
107
        pcszName = pcszArgv0;
 
108
    RTPrintf(
 
109
"Usage:\n"
 
110
"\n"
 
111
"  %s --help|-h|-?|--version|-V|--format <format> <parameters...>\n\n",
 
112
             pcszArgv0);
 
113
    RTPrintf(
 
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");
 
120
 
 
121
    RTPrintf(
 
122
"  --help|-h|-?\n"
 
123
"      Print this help text and exit.\n\n"
 
124
"  --version|-V\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");
 
129
    RTPrintf(
 
130
"Parameters:\n"
 
131
"\n");
 
132
    RTPrintf(
 
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");
 
136
    RTPrintf(
 
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"
 
141
    );
 
142
    RTPrintf(
 
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");
 
148
    RTPrintf(
 
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");
 
153
    RTPrintf(
 
154
"  --one-shot\n"
 
155
"      The service command is expected to do some work and exit immediately with"
 
156
"      a status indicating success or failure.\n\n"
 
157
    );
 
158
    RTPrintf(
 
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"
 
162
"      used here.\n\n"
 
163
    );
 
164
    RTPrintf(
 
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"
 
168
    );
 
169
    RTPrintf(
 
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"
 
174
    );
 
175
    RTPrintf(
 
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"
 
179
    );
 
180
}
 
181
 
 
182
/** @name Template format.
 
183
 * @{
 
184
 */
 
185
enum ENMFORMAT
 
186
{
 
187
    /** No format selected. */
 
188
    FORMAT_NONE = 0,
 
189
    /** Shell script format. */
 
190
    FORMAT_SHELL
 
191
};
 
192
/** @} */
 
193
 
 
194
struct SERVICEPARAMETERS
 
195
{
 
196
    enum ENMFORMAT enmFormat;
 
197
    const char *pcszCommand;
 
198
    const char *pcszArguments;
 
199
    const char *pcszDescription;
 
200
    const char *pcszServiceName;
 
201
    bool fOneShot;
 
202
    const char *pcszStopCommand;
 
203
    const char *pcszStopArguments;
 
204
    const char *pcszStatusCommand;
 
205
    const char *pcszStatusArguments;
 
206
};
 
207
 
 
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);
 
214
 
 
215
int main(int cArgs, char **apszArgs)
 
216
{
 
217
     int rc = RTR3InitExe(cArgs, &apszArgs, 0);
 
218
     if (RT_FAILURE(rc))
 
219
         return RTMsgInitFailure(rc);
 
220
 
 
221
     enum
 
222
     {
 
223
         OPTION_FORMAT = 1,
 
224
         OPTION_COMMAND,
 
225
         OPTION_ARGUMENTS,
 
226
         OPTION_DESCRIPTION,
 
227
         OPTION_SERVICE_NAME,
 
228
         OPTION_ONE_SHOT,
 
229
         OPTION_STOP_COMMAND,
 
230
         OPTION_STOP_ARGUMENTS,
 
231
         OPTION_STATUS_COMMAND,
 
232
         OPTION_STATUS_ARGUMENTS
 
233
     };
 
234
 
 
235
     static const RTGETOPTDEF s_aOptions[] =
 
236
     {
 
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 }
 
257
     };
 
258
 
 
259
     int ch;
 
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)))
 
266
     {
 
267
         switch (ch)
 
268
         {
 
269
             case 'h':
 
270
                 showUsage(apszArgs[0]);
 
271
                 return RTEXITCODE_SUCCESS;
 
272
                 break;
 
273
 
 
274
             case 'V':
 
275
                 showLogo();
 
276
                 return RTEXITCODE_SUCCESS;
 
277
                 break;
 
278
 
 
279
             case OPTION_FORMAT:
 
280
                 if (errorIfSet("--format",
 
281
                                Parameters.enmFormat != FORMAT_NONE))
 
282
                     return(RTEXITCODE_SYNTAX);
 
283
                 Parameters.enmFormat
 
284
                     = getFormat("--format", ValueUnion.psz);
 
285
                 if (Parameters.enmFormat == FORMAT_NONE)
 
286
                     return(RTEXITCODE_SYNTAX);
 
287
                 break;
 
288
 
 
289
             case OPTION_COMMAND:
 
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);
 
296
                 break;
 
297
 
 
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;
 
304
                 break;
 
305
 
 
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);
 
314
                 break;
 
315
 
 
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);
 
324
                 break;
 
325
 
 
326
             case OPTION_ONE_SHOT:
 
327
                 Parameters.fOneShot = true;
 
328
                 break;
 
329
 
 
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);
 
338
                 break;
 
339
 
 
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;
 
346
                 break;
 
347
 
 
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);
 
356
                 break;
 
357
 
 
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;
 
364
                 break;
 
365
 
 
366
             default:
 
367
                 return RTGetOptPrintError(ch, &ValueUnion);
 
368
         }
 
369
     }
 
370
     if (Parameters.enmFormat == FORMAT_NONE)
 
371
     {
 
372
         RTStrmPrintf(g_pStdErr, "--format must be specified.\n");
 
373
         return(RTEXITCODE_SYNTAX);
 
374
     }
 
375
     if (Parameters.pcszArguments && !Parameters.pcszCommand)
 
376
     {
 
377
         RTStrmPrintf(g_pStdErr, "--arguments requires --command to be specified.\n");
 
378
         return(RTEXITCODE_SYNTAX);
 
379
     }
 
380
     if (Parameters.pcszStopArguments && !Parameters.pcszStopCommand)
 
381
     {
 
382
         RTStrmPrintf(g_pStdErr, "--stop-arguments requires --stop-command to be specified.\n");
 
383
         return(RTEXITCODE_SYNTAX);
 
384
     }
 
385
     if (Parameters.pcszStatusArguments && !Parameters.pcszStatusCommand)
 
386
     {
 
387
         RTStrmPrintf(g_pStdErr, "--status-arguments requires --status-command to be specified.\n");
 
388
         return(RTEXITCODE_SYNTAX);
 
389
     }
 
390
     return createServiceFile(&Parameters)
 
391
            ? RTEXITCODE_SUCCESS
 
392
            : RTEXITCODE_FAILURE;
 
393
}
 
394
 
 
395
/** Print an error and return true if an option is already set. */
 
396
bool errorIfSet(const char *pcszName, bool isSet)
 
397
{
 
398
    if (isSet)
 
399
        RTStrmPrintf(g_pStdErr, "%s may only be specified once.\n", pcszName);
 
400
    return isSet;
 
401
}
 
402
 
 
403
/** Match the string to a known format and return that (or "none" and print an
 
404
 * error). */
 
405
enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue)
 
406
{
 
407
    if (!strcmp(pcszValue, "shell"))
 
408
        return FORMAT_SHELL;
 
409
    RTStrmPrintf(g_pStdErr, "%s: unknown format %s.\n", pcszName, pcszValue);
 
410
    return FORMAT_NONE;
 
411
}
 
412
 
 
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)
 
415
{
 
416
    if (RTPathFilename(pcszValue) && RTPathStartsWithRoot(pcszValue))
 
417
        return true;
 
418
    RTStrmPrintf(g_pStdErr, "%s: %s must be an absolute path of a file.\n", pcszName, pcszValue);
 
419
    return false;
 
420
}
 
421
 
 
422
/** Check that the string does not contain any non-printable characters. */
 
423
bool checkPrintable(const char *pcszName, const char *pcszValue)
 
424
{
 
425
    const char *pcch = pcszValue;
 
426
    for (; *pcch; ++pcch)
 
427
    {
 
428
        if (!RT_C_IS_PRINT(*pcch))
 
429
        {
 
430
            RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n",
 
431
                         pcszName, pcch - pcszValue, pcszValue);
 
432
            return false;
 
433
        }
 
434
    }
 
435
    return true;
 
436
}
 
437
 
 
438
/** Check that the string does not contain any non-graphic characters. */
 
439
static bool checkGraphic(const char *pcszName, const char *pcszValue)
 
440
{
 
441
    const char *pcch = pcszValue;
 
442
    for (; *pcch; ++pcch)
 
443
    {
 
444
        if (!RT_C_IS_GRAPH(*pcch))
 
445
        {
 
446
            RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n",
 
447
                         pcszName, pcch - pcszValue, pcszValue);
 
448
            return false;
 
449
        }
 
450
    }
 
451
    return true;
 
452
}
 
453
 
 
454
static bool createServiceFileCore(char **ppachTemplate,
 
455
                                  struct SERVICEPARAMETERS
 
456
                                      *pParamters);
 
457
 
 
458
/**
 
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.
 
463
 */
 
464
bool createServiceFile(struct SERVICEPARAMETERS *pParameters)
 
465
{
 
466
    char *pachTemplate = NULL;
 
467
    bool rc = createServiceFileCore(&pachTemplate, pParameters);
 
468
    RTMemFree(pachTemplate);
 
469
    return rc;
 
470
}
 
471
 
 
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);
 
479
 
 
480
/** The actual implemenation code for @a createServiceFile. */
 
481
bool createServiceFileCore(char **ppachTemplate,
 
482
                           struct SERVICEPARAMETERS *pParameters)
 
483
{
 
484
    /* The size of the template data we have read. */
 
485
    size_t cchTemplate = 0;
 
486
    /* The size of the buffer we have allocated. */
 
487
    size_t cbBuffer = 0;
 
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)
 
493
    {
 
494
        size_t cchRead;
 
495
 
 
496
        if (cchTemplate == cbBuffer)
 
497
        {
 
498
            cbBuffer += READ_SIZE;
 
499
            *ppachTemplate = (char *)RTMemRealloc((void *)*ppachTemplate,
 
500
                                                  cbBuffer);
 
501
        }
 
502
        if (!*ppachTemplate)
 
503
        {
 
504
            RTStrmPrintf(g_pStdErr, "Out of memory.\n");
 
505
            return false;
 
506
        }
 
507
        rc = RTStrmReadEx(g_pStdIn, *ppachTemplate + cchTemplate,
 
508
                          cbBuffer - cchTemplate, &cchRead);
 
509
        if (RT_FAILURE(rc))
 
510
        {
 
511
            RTStrmPrintf(g_pStdErr, "Error reading input: %Rrc\n", rc);
 
512
            return false;
 
513
        }
 
514
        if (!cchRead)
 
515
            rc = VINF_EOF;
 
516
        cchTemplate += cchRead;
 
517
    }
 
518
    while (true)
 
519
    {
 
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);
 
528
        if (RT_FAILURE(rc))
 
529
        {
 
530
            RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
 
531
            return false;
 
532
        }
 
533
        cchWritten += cchToWrite;
 
534
        if (!pchNext)
 
535
            break;
 
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))
 
540
        {
 
541
            if (!pParameters->pcszCommand)
 
542
            {
 
543
                RTStrmPrintf(g_pStdErr, "--command not specified.\n");
 
544
                return false;
 
545
            }
 
546
            if (!writeCommand(pParameters->enmFormat,
 
547
                              pParameters->pcszCommand))
 
548
                return false;
 
549
        }
 
550
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
551
                             ARGUMENTS, sizeof(ARGUMENTS) - 1))
 
552
        {
 
553
            if (   pParameters->pcszArguments
 
554
                && !writeQuoted(pParameters->enmFormat,
 
555
                                pParameters->pcszArguments))
 
556
                return false;
 
557
        }
 
558
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
559
                             DESCRIPTION, sizeof(DESCRIPTION) - 1))
 
560
        {
 
561
            if (!pParameters->pcszDescription)
 
562
            {
 
563
                RTStrmPrintf(g_pStdErr, "--description not specified.\n");
 
564
                return false;
 
565
            }
 
566
            if (!writePrintableString(pParameters->enmFormat,
 
567
                                      pParameters->pcszDescription))
 
568
                return false;
 
569
        }
 
570
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
571
                             SERVICE_NAME, sizeof(SERVICE_NAME) - 1))
 
572
        {
 
573
            if (   !pParameters->pcszCommand
 
574
                && !pParameters->pcszServiceName)
 
575
            {
 
576
                RTStrmPrintf(g_pStdErr, "Neither --command nor --service-name specified.\n");
 
577
                return false;
 
578
            }
 
579
            if (pParameters->pcszServiceName)
 
580
            {
 
581
                if (!writePrintableString(pParameters->enmFormat,
 
582
                                          pParameters->pcszServiceName))
 
583
                    return false;
 
584
            }
 
585
            else
 
586
            {
 
587
                const char *pcszFileName =
 
588
                    RTPathFilename(pParameters->pcszCommand);
 
589
                const char *pcszExtension =
 
590
                    RTPathExt(pParameters->pcszCommand);
 
591
                char *pszName = RTStrDupN(pcszFileName,
 
592
                                            pcszExtension
 
593
                                          ? pcszExtension - pcszFileName
 
594
                                          : RTPATH_MAX);
 
595
                bool fRc;
 
596
                if (!pszName)
 
597
                {
 
598
                    RTStrmPrintf(g_pStdErr, "Out of memory.\n");
 
599
                    return false;
 
600
                }
 
601
                fRc = writePrintableString(pParameters->enmFormat,
 
602
                                           pszName);
 
603
                RTStrFree(pszName);
 
604
                if (!fRc)
 
605
                    return false;
 
606
            }
 
607
        }
 
608
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
609
                             HAVE_ONESHOT, sizeof(HAVE_ONESHOT) - 1))
 
610
        {
 
611
            if (!pParameters->fOneShot)
 
612
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
613
        }
 
614
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
615
                             HAVE_DAEMON, sizeof(HAVE_DAEMON) - 1))
 
616
        {
 
617
            if (pParameters->fOneShot)
 
618
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
619
        }
 
620
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
621
                             STOP_COMMAND, sizeof(STOP_COMMAND) - 1))
 
622
        {
 
623
            if (   pParameters->pcszStopCommand
 
624
                && !writeCommand(pParameters->enmFormat,
 
625
                                 pParameters->pcszStopCommand))
 
626
                return false;
 
627
        }
 
628
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
629
                             STOP_ARGUMENTS, sizeof(STOP_ARGUMENTS) - 1))
 
630
        {
 
631
            if (   pParameters->pcszStopArguments
 
632
                && !writeQuoted(pParameters->enmFormat,
 
633
                                pParameters->pcszStopArguments))
 
634
                return false;
 
635
        }
 
636
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
637
                             HAVE_STOP_COMMAND, sizeof(HAVE_STOP_COMMAND) - 1))
 
638
        {
 
639
            if (!pParameters->pcszStopCommand)
 
640
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
641
        }
 
642
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
643
                             NO_STOP_COMMAND, sizeof(NO_STOP_COMMAND) - 1))
 
644
        {
 
645
            if (pParameters->pcszStopCommand)
 
646
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
647
        }
 
648
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
649
                             STATUS_COMMAND, sizeof(STATUS_COMMAND) - 1))
 
650
        {
 
651
            if (   pParameters->pcszStatusCommand
 
652
                && !writeCommand(pParameters->enmFormat,
 
653
                                 pParameters->pcszStatusCommand))
 
654
                return false;
 
655
        }
 
656
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
657
                             STATUS_ARGUMENTS, sizeof(STATUS_ARGUMENTS) - 1))
 
658
        {
 
659
            if (   pParameters->pcszStatusArguments
 
660
                && !writeQuoted(pParameters->enmFormat,
 
661
                                pParameters->pcszStatusArguments))
 
662
                return false;
 
663
        }
 
664
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
665
                             HAVE_STATUS_COMMAND,
 
666
                             sizeof(HAVE_STATUS_COMMAND) - 1))
 
667
        {
 
668
            if (!pParameters->pcszStatusCommand)
 
669
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
670
        }
 
671
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
672
                             NO_STATUS_COMMAND, sizeof(NO_STATUS_COMMAND) - 1))
 
673
        {
 
674
            if (pParameters->pcszStatusCommand)
 
675
                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
 
676
        }
 
677
        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
 
678
                             "%%", 2))
 
679
        {
 
680
            rc = RTStrmPutCh(g_pStdOut, '%');
 
681
            if (RT_FAILURE(rc))
 
682
            {
 
683
                RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
 
684
                return false;
 
685
            }
 
686
        }
 
687
        else
 
688
        {
 
689
            RTStrmPrintf(g_pStdErr, "Unknown substitution sequence in input at \"%.*s\"\n",
 
690
                         RT_MIN(16, cchTemplate - cchWritten),
 
691
                         *ppachTemplate + cchWritten);
 
692
            return false;
 
693
        }
 
694
   }
 
695
    return true;
 
696
}
 
697
 
 
698
bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
 
699
                 const char *pcszSequence, size_t cchSequence)
 
700
{
 
701
    if (   cch - *pcchRead >= cchSequence
 
702
        && !RTStrNCmp(pach + *pcchRead, pcszSequence, cchSequence))
 
703
    {
 
704
        *pcchRead += cchSequence;
 
705
        return true;
 
706
    }
 
707
    return false;
 
708
}
 
709
 
 
710
/** Write a character to standard output and print an error and return false on
 
711
 * failure. */
 
712
bool outputCharacter(char ch)
 
713
{
 
714
    int rc = RTStrmWrite(g_pStdOut, &ch, 1);
 
715
    if (RT_FAILURE(rc))
 
716
    {
 
717
        RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
 
718
        return false;
 
719
    }
 
720
    return true;
 
721
}
 
722
 
 
723
/** Write a string to standard output and print an error and return false on
 
724
 * failure. */
 
725
bool outputString(const char *pcsz)
 
726
{
 
727
    int rc = RTStrmPutStr(g_pStdOut, pcsz);
 
728
    if (RT_FAILURE(rc))
 
729
    {
 
730
        RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc);
 
731
        return false;
 
732
    }
 
733
    return true;
 
734
}
 
735
 
 
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)
 
739
{
 
740
    if (enmFormat == FORMAT_SHELL)
 
741
    {
 
742
        if (ch == '\'')
 
743
            return outputString("\'\\\'\'");
 
744
        return outputCharacter(ch);
 
745
    }
 
746
    RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
 
747
    return false;
 
748
}
 
749
 
 
750
/** Write a character to standard output, adding any escaping needed for the
 
751
 * format being written. */
 
752
static bool outputArgumentSeparator(enum ENMFORMAT enmFormat)
 
753
{
 
754
    if (enmFormat == FORMAT_SHELL)
 
755
        return outputString("\' \'");
 
756
    RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
 
757
    return false;
 
758
}
 
759
 
 
760
bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand)
 
761
{
 
762
    if (enmFormat == FORMAT_SHELL)
 
763
        if (!outputCharacter('\''))
 
764
            return false;
 
765
    for (; *pcszCommand; ++pcszCommand)
 
766
        if (enmFormat == FORMAT_SHELL)
 
767
        {
 
768
            if (*pcszCommand == '\'')
 
769
            {
 
770
                if (!outputString("\'\\\'\'"))
 
771
                    return false;
 
772
            }
 
773
            else if (!outputCharacter(*pcszCommand))
 
774
                return false;
 
775
        }
 
776
    if (enmFormat == FORMAT_SHELL)
 
777
        if (!outputCharacter('\''))
 
778
            return false;
 
779
    return true;
 
780
}
 
781
 
 
782
const char aachEscapes[][2] =
 
783
{
 
784
    { 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' },
 
785
    { 't', '\t' }, { 'v', '\v' }, { 0, 0 }
 
786
};
 
787
 
 
788
bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted)
 
789
{
 
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)?
 
793
     */
 
794
    bool fNextArgument = false;
 
795
 
 
796
    if (enmFormat == FORMAT_SHELL)
 
797
        if (!outputCharacter('\''))
 
798
            return false;
 
799
    for (; *pcszQuoted; ++pcszQuoted)
 
800
    {
 
801
        if (fEscaped)
 
802
        {
 
803
            bool fRc = true;
 
804
            const char (*pachEscapes)[2];
 
805
            fEscaped = false;
 
806
            /* One-letter escapes. */
 
807
            for (pachEscapes = aachEscapes; (*pachEscapes)[0]; ++pachEscapes)
 
808
                if (*pcszQuoted == (*pachEscapes)[0])
 
809
                {
 
810
                    if (!escapeAndOutputCharacter(enmFormat, (*pachEscapes)[1]))
 
811
                        return false;
 
812
                    break;
 
813
                }
 
814
            if ((*pachEscapes)[0])
 
815
                continue;
 
816
            /* Octal. */
 
817
            if (*pcszQuoted >= '0' && *pcszQuoted <= '7')
 
818
            {
 
819
                uint8_t cNum;
 
820
                char *pchNext;
 
821
                char achDigits[4];
 
822
                int rc;
 
823
                RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted);
 
824
                rc = RTStrToUInt8Ex(achDigits, &pchNext, 8, &cNum);
 
825
                if (rc == VWRN_NUMBER_TOO_BIG)
 
826
                {
 
827
                    RTStrmPrintf(g_pStdErr, "Invalid octal sequence at \"%.16s\"\n",
 
828
                                 pcszQuoted - 1);
 
829
                    return false;
 
830
                }
 
831
                if (!escapeAndOutputCharacter(enmFormat, cNum))
 
832
                    return false;
 
833
                pcszQuoted += pchNext - achDigits - 1;
 
834
                continue;
 
835
            }
 
836
            /* Hexadecimal. */
 
837
            if (*pcszQuoted == 'x')
 
838
            {
 
839
                uint8_t cNum;
 
840
                char *pchNext;
 
841
                char achDigits[3];
 
842
                int rc;
 
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
 
847
                    || RT_FAILURE(rc))
 
848
                {
 
849
                    RTStrmPrintf(g_pStdErr, "Invalid hexadecimal sequence at \"%.16s\"\n",
 
850
                                 pcszQuoted - 1);
 
851
                    return false;
 
852
                }
 
853
                if (!escapeAndOutputCharacter(enmFormat, cNum))
 
854
                    return false;
 
855
                pcszQuoted += pchNext - achDigits;
 
856
                continue;
 
857
            }
 
858
            /* Output anything else non-zero as is. */
 
859
            if (*pcszQuoted)
 
860
            {
 
861
                if (!escapeAndOutputCharacter(enmFormat, *pcszQuoted))
 
862
                    return false;
 
863
                continue;
 
864
            }
 
865
            RTStrmPrintf(g_pStdErr, "Trailing back slash in argument.\n");
 
866
            return false;
 
867
        }
 
868
        /* Argument separator. */
 
869
        if (*pcszQuoted == ' ')
 
870
        {
 
871
            if (!fNextArgument && !outputArgumentSeparator(enmFormat))
 
872
                return false;
 
873
            fNextArgument = true;
 
874
            continue;
 
875
        }
 
876
        else
 
877
            fNextArgument = false;
 
878
        /* Start of escape sequence. */
 
879
        if (*pcszQuoted == '\\')
 
880
        {
 
881
            fEscaped = true;
 
882
            continue;
 
883
        }
 
884
        /* Anything else. */
 
885
        if (!outputCharacter(*pcszQuoted))
 
886
            return false;
 
887
    }
 
888
    if (enmFormat == FORMAT_SHELL)
 
889
        if (!outputCharacter('\''))
 
890
            return false;
 
891
    return true;
 
892
}
 
893
 
 
894
bool writePrintableString(enum ENMFORMAT enmFormat, const char *pcszString)
 
895
{
 
896
    if (enmFormat == FORMAT_SHELL)
 
897
        return outputString(pcszString);
 
898
    RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n");
 
899
    return false;
 
900
}
 
901
 
 
902
void skipLine(const char *pach, size_t cch, size_t *pcchRead)
 
903
{
 
904
    while (   *pcchRead < cch
 
905
           && (pach)[*pcchRead] != '\n'
 
906
           && (pach)[*pcchRead] != '\r')
 
907
        ++*pcchRead;
 
908
    while (   *pcchRead < cch
 
909
           && (   (pach)[*pcchRead] == '\n'
 
910
               || (pach)[*pcchRead] == '\r'))
 
911
        ++*pcchRead;
 
912
}