Index: trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp
===================================================================
--- trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp	(revision 44119)
+++ trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp	(revision 44437)
@@ -18,4 +18,41 @@
  */
 
+/**
+ * Description of the generation process.
+ *
+ * A template for the service file to be generated is fed into standard input
+ * and the service file is sent to standard output.  The following
+ * substitutions are performed based on the command line parameters supplied,
+ * with all quoting appropriate to the format of the template as specified on
+ * the command line.
+ *
+ * %COMMAND%          -> path to the service binary or script.
+ * %ARGUMENTS%        -> the arguments to pass to the binary when starting the
+ *                       service.
+ * %SERVICE_NAME%     -> the name of the service.
+ * %DESCRIPTION%      -> the short description of the service.
+ * %STOP_COMMAND%     -> path to the command used to stop the service.
+ * %STOP_ARGUMENTS%   -> the arguments for the stop command
+ * %STATUS_COMMAND%   -> path to the command used to determine the service
+ *                       status.
+ * %STATUS_ARGUMENTS% -> the arguments for the status command
+
+ * %NO_STOP_COMMAND%     -> if no stop command was specified, this and all text
+ *                          following it on the line (including the end-of-
+ *                          line) will be removed, otherwise only the marker
+ *                          will be removed.
+ * %HAVE_STOP_COMMAND%   -> like above, but text on the line will be removed
+ *                          if a stop command was supplied.
+ * %NO_STATUS_COMMAND%   -> Analogue to %NO_STOP_COMMAND% for the status
+ *                          command.
+ * %HAVE_STATUS_COMMAND% -> Analogue to %HAVE_STOP_COMMAND% for the status
+ *                          command.
+ * %HAVE_ONESHOT%        -> like above, text on the line will be removed unless
+ *                          --one-shot was specified on the command line.
+ * %HAVE_DAEMON%         -> the same if --one-shot was not specified.
+ *
+ * %% will be replaced with a single %.
+ */
+
 #include <VBox/version.h>
 
@@ -40,4 +77,14 @@
 #define DESCRIPTION "%DESCRIPTION%"
 #define SERVICE_NAME "%SERVICE_NAME%"
+#define HAVE_ONESHOT "%HAVE_ONESHOT%"
+#define HAVE_DAEMON "%HAVE_DAEMON%"
+#define STOP_COMMAND "%STOP_COMMAND%"
+#define STOP_ARGUMENTS "%STOP_ARGUMENTS%"
+#define HAVE_STOP_COMMAND "%HAVE_STOP_COMMAND%"
+#define NO_STOP_COMMAND "%NO_STOP_COMMAND%"
+#define STATUS_COMMAND "%STATUS_COMMAND%"
+#define STATUS_ARGUMENTS "%STATUS_ARGUMENTS%"
+#define HAVE_STATUS_COMMAND "%HAVE_STATUS_COMMAND%"
+#define NO_STATUS_COMMAND "%NO_STATUS_COMMAND%"
 
 void showLogo(void)
@@ -62,69 +109,73 @@
 "Usage:\n"
 "\n"
-"  %s --help|-h|-?|--version|-V|<options>\n"
-"\n"
+"  %s --help|-h|-?|--version|-V|--format <format> <parameters...>\n\n",
+             pcszArgv0);
+    RTPrintf(
 "Read a service file template from standard input and output a service file to\n"
 "standard output which was generated from the template based on parameters\n"
 "passed on the utility's command line.  Generation is done by replacing well-\n"
-"known text sequences in the template with strings based on the parameters.  The\n",
-             pcszArgv0);
-    RTPrintf(
-"exact strings substituted will depend on the format of the template, for\n"
-"example shell script or systemd unit file.  The sequence \"%%%%\" in the template\n"
-"will be replaced by a single \"%%\" character.  The description of the options\n"
-"also describes the sequences which will be replaced in the template.  All\n"
-"arguments should be in Utf-8 encoding.\n"
-"\n"
-"\n");
+"known text sequences in the template with strings based on the parameters.\n"
+"All strings should be in UTF-8 format.  Processing will stop if a sequence is\n"
+"read which cannot be replace based on the parameters supplied.\n\n");
+
     RTPrintf(
 "  --help|-h|-?\n"
-"      Print this help text and exit.\n"
-"\n"
+"      Print this help text and exit.\n\n"
 "  --version|-V\n"
-"      Print version information and exit.\n"
-"\n");
-    RTPrintf(
-"Required options:\n"
-"\n"
+"      Print version information and exit.\n\n"
 "  --format <shell>\n"
 "      The format of the template.  Currently only \"shell\" for shell script\n"
-"      is supported.  This affects escaping of strings substituted.\n"
+"      is supported.  This affects escaping of strings substituted.\n\n");
+    RTPrintf(
+"Parameters:\n"
 "\n");
-    showOptions();
-}
-
-/** List the options which make sense to pass through from a wrapper script. */
-void showOptions(void)
-{
     RTPrintf(
 "  --command <command>\n"
 "      The absolute path of the executable file to be started by the service.\n"
-"      No form of quoting should be used here.  Substituted for the sequence\n"
-"      \"%%COMMAND%%\" in the template.\n");
-    RTPrintf(
-"\n"
+"      No form of quoting should be used here.\n\n");
+    RTPrintf(
 "  --description <description>\n"
 "      A short description of the service which can also be used in sentences\n"
-"      like \"<description> failed to start.\", as a single parameter.  ASCII\n"
-"      characters 0 to 31 and 127 should not be used.  Substituted for the\n"
-"      sequence \"%%DESCRIPTION%%\" in the template.\n"
-"\n"
-"Other options:\n"
-"\n");
+"      like \"<description> failed to start.\", as a single parameter.  Characters\n"
+"      0 to 31 and 127 should not be used.\n\n"
+    );
     RTPrintf(
 "  --arguments <arguments>\n"
 "      The arguments to pass to the executable file when it is started, as a\n"
-"      single parameter.  ASCII characters \" \", \"\\\" and \"%%\" must be escaped\n"
-"      with back-slashes and C string-style back-slash escapes are recognised.\n"
-"      Some systemd-style \"%%\" sequences may be added at a future time.\n"
-"      Substituted for the sequence \"%%ARGUMENTS%%\" in the template.\n"
-"\n");
+"      single parameter.  Characters \" \", \"\\\" and \"%%\" must be escaped with\n"
+"      back-slashes and C string-style back-slash escapes are recognised.  Some\n"
+"      systemd-style \"%%\" sequences may be added at a future time.\n\n");
     RTPrintf(
 "  --service-name <name>\n"
 "      Specify the name of the service.  By default the base name without the\n"
 "      extension of the command binary is used.  Only ASCII characters 33 to 126\n"
-"      should be used.  Substituted for the sequence \"%%SERVICE_NAME%%\" in the\n"
-"      template.\n"
-"\n");
+"      should be used.\n\n");
+    RTPrintf(
+"  --one-shot\n"
+"      The service command is expected to do some work and exit immediately with"
+"      a status indicating success or failure.\n\n"
+    );
+    RTPrintf(
+"  --stop-command <command>\n"
+"      The command which should be used to stop the service before sending the\n"
+"      termination signal to the main process.  No form of quoting should be\n"
+"      used here.\n\n"
+    );
+    RTPrintf(
+"  --stop-arguments <arguments>\n"
+"      Arguments for the stop command.  This may only be used in combination\n"
+"      with \"--stop-command\".  Quoting is the same as for \"--arguments\".\n\n"
+    );
+    RTPrintf(
+"  --status-command <command>\n"
+"      The command which should be used to determine the status of the service.\n"
+"      This may not be respected by all service management systems.  The command\n"
+"      should return an LSB status code.  No form of quoting should be used.\n\n"
+    );
+    RTPrintf(
+"  --stop-arguments <arguments>\n"
+"      Arguments for the status command.  This may only be used in combination\n"
+"      with \"--status-command\".  Quoting is the same as for \"--arguments\".\n\n"
+    );
 }
 
@@ -141,15 +192,24 @@
 /** @} */
 
+struct SERVICEPARAMETERS
+{
+    enum ENMFORMAT enmFormat;
+    const char *pcszCommand;
+    const char *pcszArguments;
+    const char *pcszDescription;
+    const char *pcszServiceName;
+    bool fOneShot;
+    const char *pcszStopCommand;
+    const char *pcszStopArguments;
+    const char *pcszStatusCommand;
+    const char *pcszStatusArguments;
+};
+
 static bool errorIfSet(const char *pcszName, bool isSet);
-static bool errorIfUnset(const char *pcszName, bool isSet);
 static enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue);
 static bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue);
 static bool checkPrintable(const char *pcszName, const char *pcszValue);
 static bool checkGraphic(const char *pcszName, const char *pcszValue);
-static bool createServiceFile(enum ENMFORMAT enmFormat,
-                              const char *pcszCommand,
-                              const char *pcszArguments,
-                              const char *pcszDescription,
-                              const char *pcszServiceName);
+static bool createServiceFile(struct SERVICEPARAMETERS *pParameters);
 
 int main(int cArgs, char **apszArgs)
@@ -161,16 +221,18 @@
      enum
      {
-         OPTION_LIST_OPTIONS = 1,
-         OPTION_FORMAT,
+         OPTION_FORMAT = 1,
          OPTION_COMMAND,
          OPTION_ARGUMENTS,
          OPTION_DESCRIPTION,
-         OPTION_SERVICE_NAME
+         OPTION_SERVICE_NAME,
+         OPTION_ONE_SHOT,
+         OPTION_STOP_COMMAND,
+         OPTION_STOP_ARGUMENTS,
+         OPTION_STATUS_COMMAND,
+         OPTION_STATUS_ARGUMENTS
      };
 
      static const RTGETOPTDEF s_aOptions[] =
      {
-         { "--list-options",       OPTION_LIST_OPTIONS,
-           RTGETOPT_REQ_NOTHING },
          { "--format",             OPTION_FORMAT,
            RTGETOPT_REQ_STRING },
@@ -182,13 +244,19 @@
            RTGETOPT_REQ_STRING },
          { "--service-name",       OPTION_SERVICE_NAME,
+           RTGETOPT_REQ_STRING },
+         { "--one-shot",           OPTION_ONE_SHOT,
+           RTGETOPT_REQ_NOTHING },
+         { "--stop-command",       OPTION_STOP_COMMAND,
+           RTGETOPT_REQ_STRING },
+         { "--stop-arguments",     OPTION_STOP_ARGUMENTS,
+           RTGETOPT_REQ_STRING },
+         { "--status-command",     OPTION_STATUS_COMMAND,
+           RTGETOPT_REQ_STRING },
+         { "--status-arguments",   OPTION_STATUS_ARGUMENTS,
            RTGETOPT_REQ_STRING }
      };
 
      int ch;
-     enum ENMFORMAT enmFormat = FORMAT_NONE;
-     const char *pcszCommand = NULL;
-     const char *pcszArguments = NULL;
-     const char *pcszDescription = NULL;
-     const char *pcszServiceName = NULL;
+     struct SERVICEPARAMETERS Parameters = { FORMAT_NONE };
      RTGETOPTUNION ValueUnion;
      RTGETOPTSTATE GetState;
@@ -209,46 +277,89 @@
                  break;
 
-             case OPTION_LIST_OPTIONS:
-                 showOptions();
-                 return RTEXITCODE_SUCCESS;
-                 break;
-
              case OPTION_FORMAT:
-                 if (errorIfSet("--format", enmFormat != FORMAT_NONE))
-                     return(RTEXITCODE_SYNTAX);
-                 enmFormat = getFormat("--format", ValueUnion.psz);
-                 if (enmFormat == FORMAT_NONE)
+                 if (errorIfSet("--format",
+                                Parameters.enmFormat != FORMAT_NONE))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.enmFormat
+                     = getFormat("--format", ValueUnion.psz);
+                 if (Parameters.enmFormat == FORMAT_NONE)
                      return(RTEXITCODE_SYNTAX);
                  break;
 
              case OPTION_COMMAND:
-                 if (errorIfSet("--command", pcszCommand))
-                     return(RTEXITCODE_SYNTAX);
-                 pcszCommand = ValueUnion.psz;
-                 if (!checkAbsoluteFilePath("--command", pcszCommand))
+                 if (errorIfSet("--command", Parameters.pcszCommand))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.pcszCommand = ValueUnion.psz;
+                 if (!checkAbsoluteFilePath("--command",
+                                            Parameters.pcszCommand))
                      return(RTEXITCODE_SYNTAX);
                  break;
 
              case OPTION_ARGUMENTS:
-                 if (errorIfSet("--arguments", pcszArguments))
-                     return(RTEXITCODE_SYNTAX);
-                 /* Arguments will be checked while writing them out. */
-                 pcszArguments = ValueUnion.psz;
+                 if (errorIfSet("--arguments",
+                                Parameters.pcszArguments))
+                     return(RTEXITCODE_SYNTAX);
+                 /* Quoting will be checked while writing out the string. */
+                 Parameters.pcszArguments = ValueUnion.psz;
                  break;
 
              case OPTION_DESCRIPTION:
-                 if (errorIfSet("--description", pcszDescription))
-                     return(RTEXITCODE_SYNTAX);
-                 pcszDescription = ValueUnion.psz;
-                 if (!checkPrintable("--description", pcszDescription))
+                 if (errorIfSet("--description",
+                                Parameters.pcszDescription))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.pcszDescription = ValueUnion.psz;
+                 if (!checkPrintable("--description",
+                                     Parameters.pcszDescription))
                      return(RTEXITCODE_SYNTAX);
                  break;
 
              case OPTION_SERVICE_NAME:
-                 if (errorIfSet("--service-name", pcszServiceName))
-                     return(RTEXITCODE_SYNTAX);
-                 pcszServiceName = ValueUnion.psz;
-                 if (!checkGraphic("--service-name", pcszServiceName))
-                     return(RTEXITCODE_SYNTAX);
+                 if (errorIfSet("--service-name",
+                                Parameters.pcszServiceName))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.pcszServiceName = ValueUnion.psz;
+                 if (!checkGraphic("--service-name",
+                                   Parameters.pcszServiceName))
+                     return(RTEXITCODE_SYNTAX);
+                 break;
+
+             case OPTION_ONE_SHOT:
+                 Parameters.fOneShot = true;
+                 break;
+
+             case OPTION_STOP_COMMAND:
+                 if (errorIfSet("--stop-command",
+                                Parameters.pcszStopCommand))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.pcszStopCommand = ValueUnion.psz;
+                 if (!checkAbsoluteFilePath("--stop-command",
+                         Parameters.pcszStopCommand))
+                     return(RTEXITCODE_SYNTAX);
+                 break;
+
+             case OPTION_STOP_ARGUMENTS:
+                 if (errorIfSet("--stop-arguments",
+                                Parameters.pcszStopArguments))
+                     return(RTEXITCODE_SYNTAX);
+                 /* Quoting will be checked while writing out the string. */
+                 Parameters.pcszStopArguments = ValueUnion.psz;
+                 break;
+
+             case OPTION_STATUS_COMMAND:
+                 if (errorIfSet("--status-command",
+                                Parameters.pcszStatusCommand))
+                     return(RTEXITCODE_SYNTAX);
+                 Parameters.pcszStatusCommand = ValueUnion.psz;
+                 if (!checkAbsoluteFilePath("--status-command",
+                         Parameters.pcszStatusCommand))
+                     return(RTEXITCODE_SYNTAX);
+                 break;
+
+             case OPTION_STATUS_ARGUMENTS:
+                 if (errorIfSet("--status-arguments",
+                                Parameters.pcszStatusArguments))
+                     return(RTEXITCODE_SYNTAX);
+                 /* Quoting will be checked while writing out the string. */
+                 Parameters.pcszStatusArguments = ValueUnion.psz;
                  break;
 
@@ -257,10 +368,25 @@
          }
      }
-     if (   errorIfUnset("--format", enmFormat != FORMAT_NONE)
-         || errorIfUnset("--command", pcszCommand)
-         || errorIfUnset("--description", pcszDescription))
+     if (Parameters.enmFormat == FORMAT_NONE)
+     {
+         RTStrmPrintf(g_pStdErr, "--format must be specified.\n");
          return(RTEXITCODE_SYNTAX);
-     return   createServiceFile(enmFormat, pcszCommand, pcszArguments,
-                                pcszDescription, pcszServiceName)
+     }
+     if (Parameters.pcszArguments && !Parameters.pcszCommand)
+     {
+         RTStrmPrintf(g_pStdErr, "--arguments requires --command to be specified.\n");
+         return(RTEXITCODE_SYNTAX);
+     }
+     if (Parameters.pcszStopArguments && !Parameters.pcszStopCommand)
+     {
+         RTStrmPrintf(g_pStdErr, "--stop-arguments requires --stop-command to be specified.\n");
+         return(RTEXITCODE_SYNTAX);
+     }
+     if (Parameters.pcszStatusArguments && !Parameters.pcszStatusCommand)
+     {
+         RTStrmPrintf(g_pStdErr, "--status-arguments requires --status-command to be specified.\n");
+         return(RTEXITCODE_SYNTAX);
+     }
+     return createServiceFile(&Parameters)
             ? RTEXITCODE_SUCCESS
             : RTEXITCODE_FAILURE;
@@ -273,12 +399,4 @@
         RTStrmPrintf(g_pStdErr, "%s may only be specified once.\n", pcszName);
     return isSet;
-}
-
-/** Print an error and return true if an option is not set. */
-bool errorIfUnset(const char *pcszName, bool isSet)
-{
-    if (!isSet)
-        RTStrmPrintf(g_pStdErr, "%s must be specified.\n", pcszName);
-    return !isSet;
 }
 
@@ -335,9 +453,6 @@
 
 static bool createServiceFileCore(char **ppachTemplate,
-                                  enum ENMFORMAT enmFormat,
-                                  const char *pcszCommand,
-                                  const char *pcszArguments,
-                                  const char *pcszDescription,
-                                  const char *pcszServiceName);
+                                  struct SERVICEPARAMETERS
+                                      *pParamters);
 
 /**
@@ -347,30 +462,23 @@
  *       allocation without requiring a single point of exit.
  */
-bool createServiceFile(enum ENMFORMAT enmFormat,
-                       const char *pcszCommand,
-                       const char *pcszArguments,
-                       const char *pcszDescription,
-                       const char *pcszServiceName)
+bool createServiceFile(struct SERVICEPARAMETERS *pParameters)
 {
     char *pachTemplate = NULL;
-    bool rc = createServiceFileCore(&pachTemplate, enmFormat, pcszCommand,
-                                    pcszArguments, pcszDescription,
-                                    pcszServiceName);
+    bool rc = createServiceFileCore(&pachTemplate, pParameters);
     RTMemFree(pachTemplate);
     return rc;
 }
 
+static bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
+                        const char *pcszSequence, size_t cchSequence);
 static bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand);
-static bool writeArguments(enum ENMFORMAT enmFormat, const char *pcszArguments);
+static bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted);
 static bool writePrintableString(enum ENMFORMAT enmFormat,
                                  const char *pcszString);
+static void skipLine(const char *pach, size_t cch, size_t *pcchRead);
 
 /** The actual implemenation code for @a createServiceFile. */
 bool createServiceFileCore(char **ppachTemplate,
-                           enum ENMFORMAT enmFormat,
-                           const char *pcszCommand,
-                           const char *pcszArguments,
-                           const char *pcszDescription,
-                           const char *pcszServiceName)
+                           struct SERVICEPARAMETERS *pParameters)
 {
     /* The size of the template data we have read. */
@@ -428,41 +536,57 @@
         /* And substitute any of our well-known strings.  We favour code
          * readability over efficiency here. */
-        if (   cchTemplate - cchWritten >= sizeof(COMMAND) - 1
-            && !RTStrNCmp(*ppachTemplate + cchWritten, COMMAND,
-                          sizeof(COMMAND) - 1))
-        {
-            if (!writeCommand(enmFormat, pcszCommand))
-                return false;
-            cchWritten += sizeof(COMMAND) - 1;
-        }
-        else if (   cchTemplate - cchWritten >= sizeof(ARGUMENTS) - 1
-                 && !RTStrNCmp(*ppachTemplate + cchWritten, ARGUMENTS,
-                               sizeof(ARGUMENTS) - 1))
-        {
-            if (pcszArguments && !writeArguments(enmFormat, pcszArguments))
-                return false;
-            cchWritten += sizeof(ARGUMENTS) - 1;
-        }
-        else if (   cchTemplate - cchWritten >= sizeof(DESCRIPTION) - 1
-                 && !RTStrNCmp(*ppachTemplate + cchWritten, DESCRIPTION,
-                               sizeof(DESCRIPTION) - 1))
-        {
-            if (!writePrintableString(enmFormat, pcszDescription))
-                return false;
-            cchWritten += sizeof(DESCRIPTION) - 1;
-        }
-        else if (   cchTemplate - cchWritten >= sizeof(SERVICE_NAME) - 1
-                 && !RTStrNCmp(*ppachTemplate + cchWritten, SERVICE_NAME,
-                               sizeof(SERVICE_NAME) - 1))
-        {
-            if (pcszServiceName)
+        if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                        COMMAND, sizeof(COMMAND) - 1))
+        {
+            if (!pParameters->pcszCommand)
             {
-                if (!writePrintableString(enmFormat, pcszServiceName))
+                RTStrmPrintf(g_pStdErr, "--command not specified.\n");
+                return false;
+            }
+            if (!writeCommand(pParameters->enmFormat,
+                              pParameters->pcszCommand))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             ARGUMENTS, sizeof(ARGUMENTS) - 1))
+        {
+            if (   pParameters->pcszArguments
+                && !writeQuoted(pParameters->enmFormat,
+                                pParameters->pcszArguments))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             DESCRIPTION, sizeof(DESCRIPTION) - 1))
+        {
+            if (!pParameters->pcszDescription)
+            {
+                RTStrmPrintf(g_pStdErr, "--description not specified.\n");
+                return false;
+            }
+            if (!writePrintableString(pParameters->enmFormat,
+                                      pParameters->pcszDescription))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             SERVICE_NAME, sizeof(SERVICE_NAME) - 1))
+        {
+            if (   !pParameters->pcszCommand
+                && !pParameters->pcszServiceName)
+            {
+                RTStrmPrintf(g_pStdErr, "Neither --command nor --service-name specified.\n");
+                return false;
+            }
+            if (pParameters->pcszServiceName)
+            {
+                if (!writePrintableString(pParameters->enmFormat,
+                                          pParameters->pcszServiceName))
                     return false;
             }
             else
             {
-                const char *pcszFileName = RTPathFilename(pcszCommand);
-                const char *pcszExtension = RTPathExt(pcszCommand);
+                const char *pcszFileName =
+                    RTPathFilename(pParameters->pcszCommand);
+                const char *pcszExtension =
+                    RTPathExt(pParameters->pcszCommand);
                 char *pszName = RTStrDupN(pcszFileName,
                                             pcszExtension
@@ -475,13 +599,82 @@
                     return false;
                 }
-                fRc = writePrintableString(enmFormat, pszName);
+                fRc = writePrintableString(pParameters->enmFormat,
+                                           pszName);
                 RTStrFree(pszName);
                 if (!fRc)
                     return false;
             }
-            cchWritten += sizeof(SERVICE_NAME) - 1;
-        }
-        else if (   cchTemplate - cchWritten > 1
-                 && *(*ppachTemplate + cchWritten + 1) == '%')
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             HAVE_ONESHOT, sizeof(HAVE_ONESHOT) - 1))
+        {
+            if (!pParameters->fOneShot)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             HAVE_DAEMON, sizeof(HAVE_DAEMON) - 1))
+        {
+            if (pParameters->fOneShot)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             STOP_COMMAND, sizeof(STOP_COMMAND) - 1))
+        {
+            if (   pParameters->pcszStopCommand
+                && !writeCommand(pParameters->enmFormat,
+                                 pParameters->pcszStopCommand))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             STOP_ARGUMENTS, sizeof(STOP_ARGUMENTS) - 1))
+        {
+            if (   pParameters->pcszStopArguments
+                && !writeQuoted(pParameters->enmFormat,
+                                pParameters->pcszStopArguments))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             HAVE_STOP_COMMAND, sizeof(HAVE_STOP_COMMAND) - 1))
+        {
+            if (!pParameters->pcszStopCommand)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             NO_STOP_COMMAND, sizeof(NO_STOP_COMMAND) - 1))
+        {
+            if (pParameters->pcszStopCommand)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             STATUS_COMMAND, sizeof(STATUS_COMMAND) - 1))
+        {
+            if (   pParameters->pcszStatusCommand
+                && !writeCommand(pParameters->enmFormat,
+                                 pParameters->pcszStatusCommand))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             STATUS_ARGUMENTS, sizeof(STATUS_ARGUMENTS) - 1))
+        {
+            if (   pParameters->pcszStatusArguments
+                && !writeQuoted(pParameters->enmFormat,
+                                pParameters->pcszStatusArguments))
+                return false;
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             HAVE_STATUS_COMMAND,
+                             sizeof(HAVE_STATUS_COMMAND) - 1))
+        {
+            if (!pParameters->pcszStatusCommand)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             NO_STATUS_COMMAND, sizeof(NO_STATUS_COMMAND) - 1))
+        {
+            if (pParameters->pcszStatusCommand)
+                skipLine(*ppachTemplate, cchTemplate, &cchWritten);
+        }
+        else if (getSequence(*ppachTemplate, cchTemplate, &cchWritten,
+                             "%%", 2))
         {
             rc = RTStrmPutCh(g_pStdOut, '%');
@@ -491,5 +684,4 @@
                 return false;
             }
-            cchWritten += 2;
         }
         else
@@ -502,4 +694,16 @@
    }
     return true;
+}
+
+bool getSequence(const char *pach, size_t cch, size_t *pcchRead,
+                 const char *pcszSequence, size_t cchSequence)
+{
+    if (   cch - *pcchRead >= cchSequence
+        && !RTStrNCmp(pach + *pcchRead, pcszSequence, cchSequence))
+    {
+        *pcchRead += cchSequence;
+        return true;
+    }
+    return false;
 }
 
@@ -582,5 +786,5 @@
 };
 
-bool writeArguments(enum ENMFORMAT enmFormat, const char *pcszArguments)
+bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted)
 {
     /* Was the last character seen a back slash? */
@@ -593,5 +797,5 @@
         if (!outputCharacter('\''))
             return false;
-    for (; *pcszArguments; ++pcszArguments)
+    for (; *pcszQuoted; ++pcszQuoted)
     {
         if (fEscaped)
@@ -602,5 +806,5 @@
             /* One-letter escapes. */
             for (pachEscapes = aachEscapes; (*pachEscapes)[0]; ++pachEscapes)
-                if (*pcszArguments == (*pachEscapes)[0])
+                if (*pcszQuoted == (*pachEscapes)[0])
                 {
                     if (!escapeAndOutputCharacter(enmFormat, (*pachEscapes)[1]))
@@ -611,5 +815,5 @@
                 continue;
             /* Octal. */
-            if (*pcszArguments >= '0' && *pcszArguments <= '7')
+            if (*pcszQuoted >= '0' && *pcszQuoted <= '7')
             {
                 uint8_t cNum;
@@ -617,19 +821,19 @@
                 char achDigits[4];
                 int rc;
-                RTStrCopy(achDigits, sizeof(achDigits), pcszArguments);
+                RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted);
                 rc = RTStrToUInt8Ex(achDigits, &pchNext, 8, &cNum);
                 if (rc == VWRN_NUMBER_TOO_BIG)
                 {
                     RTStrmPrintf(g_pStdErr, "Invalid octal sequence at \"%.16s\"\n",
-                                 pcszArguments - 1);
+                                 pcszQuoted - 1);
                     return false;
                 }
                 if (!escapeAndOutputCharacter(enmFormat, cNum))
                     return false;
-                pcszArguments += pchNext - achDigits - 1;
+                pcszQuoted += pchNext - achDigits - 1;
                 continue;
             }
             /* Hexadecimal. */
-            if (*pcszArguments == 'x')
+            if (*pcszQuoted == 'x')
             {
                 uint8_t cNum;
@@ -637,5 +841,5 @@
                 char achDigits[3];
                 int rc;
-                RTStrCopy(achDigits, sizeof(achDigits), pcszArguments + 1);
+                RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted + 1);
                 rc = RTStrToUInt8Ex(achDigits, &pchNext, 16, &cNum);
                 if (   rc == VWRN_NUMBER_TOO_BIG
@@ -644,16 +848,16 @@
                 {
                     RTStrmPrintf(g_pStdErr, "Invalid hexadecimal sequence at \"%.16s\"\n",
-                                 pcszArguments - 1);
+                                 pcszQuoted - 1);
                     return false;
                 }
                 if (!escapeAndOutputCharacter(enmFormat, cNum))
                     return false;
-                pcszArguments += pchNext - achDigits;
+                pcszQuoted += pchNext - achDigits;
                 continue;
             }
             /* Output anything else non-zero as is. */
-            if (*pcszArguments)
+            if (*pcszQuoted)
             {
-                if (!escapeAndOutputCharacter(enmFormat, *pcszArguments))
+                if (!escapeAndOutputCharacter(enmFormat, *pcszQuoted))
                     return false;
                 continue;
@@ -663,5 +867,5 @@
         }
         /* Argument separator. */
-        if (*pcszArguments == ' ')
+        if (*pcszQuoted == ' ')
         {
             if (!fNextArgument && !outputArgumentSeparator(enmFormat))
@@ -673,5 +877,5 @@
             fNextArgument = false;
         /* Start of escape sequence. */
-        if (*pcszArguments == '\\')
+        if (*pcszQuoted == '\\')
         {
             fEscaped = true;
@@ -679,5 +883,5 @@
         }
         /* Anything else. */
-        if (!outputCharacter(*pcszArguments))
+        if (!outputCharacter(*pcszQuoted))
             return false;
     }
@@ -695,2 +899,14 @@
     return false;
 }
+
+void skipLine(const char *pach, size_t cch, size_t *pcchRead)
+{
+    while (   *pcchRead < cch
+           && (pach)[*pcchRead] != '\n'
+           && (pach)[*pcchRead] != '\r')
+        ++*pcchRead;
+    while (   *pcchRead < cch
+           && (   (pach)[*pcchRead] == '\n'
+               || (pach)[*pcchRead] == '\r'))
+        ++*pcchRead;
+}
Index: trunk/src/VBox/Installer/linux/install_service/init_template.sh
===================================================================
--- trunk/src/VBox/Installer/linux/install_service/init_template.sh	(revision 44119)
+++ trunk/src/VBox/Installer/linux/install_service/init_template.sh	(revision 44437)
@@ -58,14 +58,6 @@
 # as possible, but in particular without trying to look perfectly native.
 #
-# To use this template as an init script, replace the following text sequences
-# (wrapped in percent characters) with the values you need:
-# COMMAND: Path to the service binary or script, with all required escaping for
-# characters which are special in shell scripts.
-# ARGUMENTS: The arguments to pass to the binary when starting the service,
-# with all required escaping for characters which are special in shell scripts.
-# SERVICE_NAME: The name of the service, using ASCII characters 33 to 126 only.
-# DESCRIPTION: Short description of the service, suitable for use in texts like
-# "DESCRIPTION successfully started", using Utf-8 characters 32 to 126 and 128
-# and upwards. 
+# See the inline documentation in the code for generate_service_file for
+# details of the generation process.
 
 ## Time out in seconds when shutting down the service.
@@ -264,8 +256,9 @@
     test -d "${LOCK_FOLDER}" && touch "${LOCK_FILE}"
     test -n "`pidofproc %COMMAND%`" && exit 0
-    %COMMAND% %ARGUMENTS% >/dev/null 2>&1 &
-    pid="$!"
-    pidfile="`pidfilename %COMMAND%`"
-    echo "${pid}" > "${pidfile}"
+%HAVE_DAEMON%    %COMMAND% %ARGUMENTS% >/dev/null 2>&1 &
+%HAVE_DAEMON%    pid="$!"
+%HAVE_DAEMON%    pidfile="`pidfilename %COMMAND%`"
+%HAVE_DAEMON%    echo "${pid}" > "${pidfile}"
+%HAVE_ONESHOT%    %COMMAND% %ARGUMENTS% >/dev/null 2>&1 || abort "%DESCRIPTION% failed to start!"
     do_success
 }
@@ -273,5 +266,6 @@
 stop()
 {
-    killproc %COMMAND% || abort "%DESCRIPTION% failed to stop!"
+%HAVE_STOP_COMMAND%    %STOP_COMMAND% %STOP_ARGUMENTS% || abort "%DESCRIPTION% failed to stop!"
+%HAVE_DAEMON%    killproc %COMMAND% || abort "%DESCRIPTION% failed to stop!"
     rm -f "${LOCK_FILE}"
     log_success_msg "%DESCRIPTION% successfully stopped."
@@ -281,22 +275,24 @@
 status()
 {
-    pid="`pidofproc %COMMAND%`"
-    test -n "${pid}" &&
-    {
-        echo "%SERVICE_NAME% running, process ${pid}"
-        exit 0
-    }
-    test -f "`pidfilename %COMMAND%`" &&
-    {
-        echo "%SERVICE_NAME% not running but PID-file present."
-        exit 1
-    }
-    test -f "${LOCK_FILE}" &&
-    {
-        echo "%SERVICE_NAME% not running but lock file present."
-        exit 2
-    }
-    echo "%SERVICE_NAME% not running."
-    exit 3
+%HAVE_STATUS_COMMAND%    %STATUS_COMMAND% %STATUS_ARGUMENTS%
+%HAVE_STATUS_COMMAND%    exit
+%NO_STATUS_COMMAND%    pid="`pidofproc %COMMAND%`"
+%NO_STATUS_COMMAND%    test -n "${pid}" &&
+%NO_STATUS_COMMAND%    {
+%NO_STATUS_COMMAND%        echo "%SERVICE_NAME% running, process ${pid}"
+%NO_STATUS_COMMAND%        exit 0
+%NO_STATUS_COMMAND%    }
+%NO_STATUS_COMMAND%    test -f "`pidfilename %COMMAND%`" &&
+%NO_STATUS_COMMAND%    {
+%NO_STATUS_COMMAND%        echo "%SERVICE_NAME% not running but PID-file present."
+%NO_STATUS_COMMAND%        exit 1
+%NO_STATUS_COMMAND%    }
+%NO_STATUS_COMMAND%    test -f "${LOCK_FILE}" &&
+%NO_STATUS_COMMAND%    {
+%NO_STATUS_COMMAND%        echo "%SERVICE_NAME% not running but lock file present."
+%NO_STATUS_COMMAND%        exit 2
+%NO_STATUS_COMMAND%    }
+%NO_STATUS_COMMAND%    echo "%SERVICE_NAME% not running."
+%NO_STATUS_COMMAND%    exit 3
 }
 
Index: trunk/src/VBox/Installer/linux/install_service/install_service.sh
===================================================================
--- trunk/src/VBox/Installer/linux/install_service/install_service.sh	(revision 44119)
+++ trunk/src/VBox/Installer/linux/install_service/install_service.sh	(revision 44437)
@@ -33,5 +33,6 @@
 
   `basename $0` --help|--enable|--disable|--force-enable|--force-disable
-                      |--remove <options>
+                      |--remove [--prefix <prefix>]
+                      -- <pass-through parameters>
 
 Create a system service which runs a command.  In order to make it possible to
@@ -39,6 +40,8 @@
 the command to be run:
  - That it can be started safely even if all its dependencies are not started
-   and will sleep if necessary until it can start work.  Ideally it should start
-   accepting input as early as it can, but delay handling it if necessary.
+   and will sleep if necessary until it can start work.  Ideally it should
+   start accepting input as early as it can, but delay handling it if
+   necessary, and delay accessing its dependencies until it actually needs
+   them.
  - That it does not background to simplify service process management.
  - That it can be safely shut down using SIGTERM.
@@ -49,9 +52,8 @@
 
 We currently support System V init only.  This will probably soon be extended
-to BSD init, OpenRC, Pardus Comar and systemd, but probably not Upstart which
-currently requires modifying init files to disable a service.  We also try to
-enable our service (if requested) in all init systems we find, as we do not know
-which one is in active use.  We assume that this will not have any adverse
-effects.
+to BSD init, OpenRC and systemd, but probably not Upstart which currently
+requires modifying init files to disable a service.  We also try to enable our
+service (if requested) in all init systems we find, as we do not know which one
+is in active use.  We assume that this will not have any adverse effects.
 
  --help|--usage
@@ -72,13 +74,12 @@
       with "--enable", "--disable", "--force-enable" or "--force-disable".
 
-Basic options:
+Option:
 
   --prefix <prefix>
       Treat all paths as relative to <prefix> rather than /etc.
 
-Required service options:
-
+Pass-through parameters will be passed through to the "generate_service_file"
+tool.
 EOF
-    "${script_folder}/../helpers/generate_service_file" --list-options
 }
 
@@ -94,12 +95,8 @@
 ACTION=""
 PREFIX="/etc/"
-ARGUMENTS=""
 SERVICE_NAME=""
 
 # Process arguments.
-## @todo Pass more through unmodified to generate_service_file to reduce
-#        duplication.  Or then again, maybe the hassle of perserving the
-#        positional parameters is not worth it.
-while test x"${#}" != "x0"; do
+while test x"${1}" != "x--"; do
     case "${1}" in
     "--help"|"--usage")
@@ -123,49 +120,19 @@
         PREFIX="${2}"
         shift 2;;
-    "--command")
-        test -z "${2}" && abort "${1}: missing argument."
-        COMMAND="${2}"
-        shift 2;;
-    "--arguments")
-        test -z "${2}" && abort "${1}: missing argument."
-        ARGUMENTS="${2}"
-        shift 2;;
-    "--description")
-        test -z "${2}" && abort "${1}: missing argument."
-        DESCRIPTION="${2}"
-        shift 2;;
-    "--service-name")
-        test -z "${2}" && abort "${1}: missing argument."
-        SERVICE_NAME="${2}"
-        shift 2;;
     *)
         abort "Unknown option ${1}.";;
     esac
 done
+shift
 
 # Check required options and set default values for others.
 test -z "${ACTION}" &&
     abort "Please supply an install action."
-if test -n "${INSTALL}"; then
-    test -z "${COMMAND}" &&
-        abort "Please supply a start command."
-    test -f "${COMMAND}" && test -x "${COMMAND}" ||
-        abort "The start command must be an executable file."
-    case "${COMMAND}" in
-        /*) ;;
-        *) abort "The start command must have an absolute path." ;;
-    esac
-    test -z "${DESCRIPTION}" &&
-        abort "Please supply a service description."
-else
-    test -z "${COMMAND}" && test -z "${SERVICE_NAME}" &&
-        abort "Please supply a service name or a start command."
-fi
-# Get the service name from the command path if not explicitly
-# supplied.
+
+# Get the service name.
+SERVICE_NAME=`echo "%SERVICE_NAME%" |
+    "${script_folder}/../helpers/generate_service_file" --format shell "${@}"`
 test -z "${SERVICE_NAME}" &&
-    SERVICE_NAME="`expr "${COMMAND}" : '.*/\(.*\)\..*'`"
-test -z "${SERVICE_NAME}" &&
-    SERVICE_NAME="`expr "${COMMAND}" : '.*/\(.*\)'`"
+    abort "Please supply a command path."
 
 # Keep track of whether we found at least one initialisation system.
@@ -193,5 +160,5 @@
         test -f "${path}/${SERVICE_NAME}" && update="${UPDATE}"
         if test -n "${INSTALL}"; then
-            "${script_folder}/../helpers/generate_service_file" --format shell --command "${COMMAND}" --arguments "${ARGUMENTS}" --description "${DESCRIPTION}" --service-name "${SERVICE_NAME}" < "${script_folder}/init_template.sh" > "${path}/${SERVICE_NAME}"
+            "${script_folder}/../helpers/generate_service_file" --format shell "${@}" < "${script_folder}/init_template.sh" > "${path}/${SERVICE_NAME}"
             chmod a+x "${path}/${SERVICE_NAME}"
         else
@@ -200,5 +167,6 @@
         # Attempt to install using both system V symlinks and OpenRC, assuming
         # that both will not be in operation simultaneously (but may be
-        # switchable).  BSD init expects the user to enable services explicitly.
+        # switchable).  BSD init expects the user to enable services
+        # explicitly.
         if test -z "${update}"; then
             # Various known combinations of sysvinit rc directories.
