VirtualBox

source: vbox/trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp@ 48286

Last change on this file since 48286 was 44529, checked in by vboxsync, 12 years ago

header (C) fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.9 KB
Line 
1/* $Id: generate_service_file.cpp 44529 2013-02-04 15:54:15Z vboxsync $ */
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
90void 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
101static void showOptions(void);
102
103void 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 */
185enum ENMFORMAT
186{
187 /** No format selected. */
188 FORMAT_NONE = 0,
189 /** Shell script format. */
190 FORMAT_SHELL
191};
192/** @} */
193
194struct 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
208static bool errorIfSet(const char *pcszName, bool isSet);
209static enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue);
210static bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue);
211static bool checkPrintable(const char *pcszName, const char *pcszValue);
212static bool checkGraphic(const char *pcszName, const char *pcszValue);
213static bool createServiceFile(struct SERVICEPARAMETERS *pParameters);
214
215int 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. */
396bool 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). */
405enum 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. */
414bool 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. */
423bool 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. */
439static 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
454static 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 */
464bool createServiceFile(struct SERVICEPARAMETERS *pParameters)
465{
466 char *pachTemplate = NULL;
467 bool rc = createServiceFileCore(&pachTemplate, pParameters);
468 RTMemFree(pachTemplate);
469 return rc;
470}
471
472static bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
473 const char *pcszSequence, size_t cchSequence);
474static bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand);
475static bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted);
476static bool writePrintableString(enum ENMFORMAT enmFormat,
477 const char *pcszString);
478static void skipLine(const char *pach, size_t cch, size_t *pcchRead);
479
480/** The actual implemenation code for @a createServiceFile. */
481bool 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
698bool 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. */
712bool 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. */
725bool 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. */
738static 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. */
752static 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
760bool 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
782const char aachEscapes[][2] =
783{
784 { 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' },
785 { 't', '\t' }, { 'v', '\v' }, { 0, 0 }
786};
787
788bool 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
894bool 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
902void 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}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette