Index: trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp
===================================================================
--- trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp	(revision 44069)
+++ trunk/src/VBox/Installer/linux/install_service/generate_service_file.cpp	(revision 44070)
@@ -52,4 +52,6 @@
 }
 
+static void showOptions(void);
+
 void showUsage(const char *pcszArgv0)
 {
@@ -88,5 +90,12 @@
 "      The format of the template.  Currently only \"shell\" for shell script\n"
 "      is supported.  This affects escaping of strings substituted.\n"
-"\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"
@@ -106,7 +115,7 @@
 "  --arguments <arguments>\n"
 "      The arguments to pass to the executable file when it is started, as a\n"
-"      single parameter.  ASCII characters 0 to 31, \"\'\" and 127 should be escaped\n"
-"      in C string-style and spaces inside words should be preceeded by a back\n"
-"      slash.  Some systemd-style \"%%\" sequences may be added at a future time.\n"
+"      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");
@@ -136,5 +145,4 @@
 static enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue);
 static bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue);
-static bool checkArguments(const char *pcszName, const char *pcszValue);
 static bool checkPrintable(const char *pcszName, const char *pcszValue);
 static bool checkGraphic(const char *pcszName, const char *pcszValue);
@@ -153,5 +161,6 @@
      enum
      {
-         OPTION_FORMAT = 1,
+         OPTION_LIST_OPTIONS = 1,
+         OPTION_FORMAT,
          OPTION_COMMAND,
          OPTION_ARGUMENTS,
@@ -162,4 +171,6 @@
      static const RTGETOPTDEF s_aOptions[] =
      {
+         { "--list-options",       OPTION_LIST_OPTIONS,
+           RTGETOPT_REQ_NOTHING },
          { "--format",             OPTION_FORMAT,
            RTGETOPT_REQ_STRING },
@@ -198,4 +209,9 @@
                  break;
 
+             case OPTION_LIST_OPTIONS:
+                 showOptions();
+                 return RTEXITCODE_SUCCESS;
+                 break;
+
              case OPTION_FORMAT:
                  if (errorIfSet("--format", enmFormat != FORMAT_NONE))
@@ -217,7 +233,6 @@
                  if (errorIfSet("--arguments", pcszArguments))
                      return(RTEXITCODE_SYNTAX);
+                 /* Arguments will be checked while writing them out. */
                  pcszArguments = ValueUnion.psz;
-                 if (!checkArguments("--arguments", pcszArguments))
-                     return(RTEXITCODE_SYNTAX);
                  break;
 
@@ -284,39 +299,4 @@
         return true;
     RTStrmPrintf(g_pStdErr, "%s: %s must be an absolute path of a file.\n", pcszName, pcszValue);
-    return false;
-}
-
-/** Check that the string does not contain any non-printable characters and
- * that the quoting and escaping are balanced. */
-bool checkArguments(const char *pcszName, const char *pcszValue)
-{
-    size_t cQuotes = 0;
-    bool fEscaped = false;
-    const char *pcch = pcszValue;
-    for (; *pcch; ++pcch)
-    {
-        if (!RT_C_IS_PRINT(*pcch))
-        {
-            RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n",
-                         pcszName, pcch - pcszValue, pcszValue);
-            return false;
-        }
-        if (fEscaped)
-            fEscaped = false;
-        else
-        {
-            if (*pcch == '\\')
-                fEscaped == true;
-            if (*pcch == '\'')
-                ++cQuotes;
-        }
-    }
-    if (cQuotes % 2)
-        RTStrmPrintf(g_pStdErr, "%s: quote (\') mismatch.\n", pcszName);
-    else if (fEscaped)
-        RTStrmPrintf(g_pStdErr, "%s: stray backslash at end of value.\n",
-                     pcszName);
-    else
-        return true;
     return false;
 }
@@ -599,6 +579,5 @@
 {
     { 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' },
-    { 't', '\t' }, { 'v', '\v' }, { '\"', '\"' }, { '\'', '\'' },
-    { '\\', '\\' }, { ' ', ' ' }, { 0, 0 }
+    { 't', '\t' }, { 'v', '\v' }, { 0, 0 }
 };
 
@@ -673,6 +652,12 @@
                 continue;
             }
-            /* Reject anything else. */
-            RTStrmPrintf(g_pStdErr, "Invalid escape sequence or trailing back slash in argument.\n");
+            /* Output anything else non-zero as is. */
+            if (*pcszArguments)
+            {
+                if (!escapeAndOutputCharacter(enmFormat, *pcszArguments))
+                    return false;
+                continue;
+            }
+            RTStrmPrintf(g_pStdErr, "Trailing back slash in argument.\n");
             return false;
         }
Index: trunk/src/VBox/Installer/linux/install_service/install_service.sh
===================================================================
--- trunk/src/VBox/Installer/linux/install_service/install_service.sh	(revision 44069)
+++ trunk/src/VBox/Installer/linux/install_service/install_service.sh	(revision 44070)
@@ -24,11 +24,14 @@
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:$PATH
 
+# Get the folder we are running from, as we need other files there.
+script_folder="`dirname "$0"`"
+
 ## Script usage documentation.
-## @todo generate_service_file could be called to print its own documentation.
 usage() {
   cat << EOF
 Usage:
 
-  `basename $0` --help|--usage|<options>
+  `basename $0` --help|--enable|--disable|--force-enable|--force-disable
+                      |--remove <options>
 
 Create a system service which runs a command.  In order to make it possible to
@@ -55,42 +58,27 @@
      Print this help text and exit.
 
-Required options:
-
-  --command <command>
-      The absolute path of the executable file to be started by the service.  No
-      form of quoting should be used here.
-
-  --description <description>
-      A short description of the service which can also be used in sentences
-      like "<description> failed to start", as a single parameter.  ASCII
-      characters 0 to 31 and 127 should not be used.
-
-Other options:
-
-  --arguments <arguments>
-      The arguments to pass to the executable file when it is started, as a
-      single parameter. ASCII characters 0 to 31, "'" and 127 should be escaped
-      in C string-style and spaces inside words should be preceeded by a back
-      slash.  Some systemd-style % sequences may be added at a future time.
-
-  --service-name <name>
-      Specify the name of the service.  By default the base name without the
-      extension of the command binary is used.  Only ASCII characters 33 to 126
-      should be used.
-
-  --enabled
-      Enable the service in normal user run-levels by default.  If this option
-      is not used the service will be disabled by default.  If a version of the
-      service was already installed this option (or its absence) will be
-      ignored unless the "--force" option is also specified.
-
-  --force
-      Respect the presence or absence of the "--enabled" flag even if a previous
-      version of the service was already installed with a different enablement
-      state.
+  --enable|--disable|--force-enable|--force-disable
+      These actions install the service.  If a version of the service was not
+      installed previously, "--enable" and "--force-enable" make it start when
+      entering normal user run-levels and "--disable" and "--force-disable"
+      prevents it from starting when entering any run-level.  If a version of
+      the service was already installed previously, "--enable" and "--disable"
+      simply update it without changing when it starts; "--force-enable" and
+      "--force-disable" behave the same as when no previous version was found.
+      Only one of these options or "--remove" may be specified.
+
+  --remove
+      This action uninstalls the service.  It may not be used in combination
+      with "--enable", "--disable", "--force-enable" or "--force-disable".
+
+Basic options:
 
   --prefix <prefix>
       Treat all paths as relative to <prefix> rather than /etc.
+
+Required service options:
+
 EOF
+    "${script_folder}/generate_service_file" --list-options
 }
 
@@ -104,6 +92,5 @@
 }
 
-enabled=""
-force=""
+ACTION=""
 PREFIX="/etc/"
 ARGUMENTS=""
@@ -116,12 +103,19 @@
 while test x"${#}" != "x0"; do
     case "${1}" in
-    "--help|--usage")
+    "--help"|"--usage")
         usage
         exit 0;;
-    "--enabled")
-        enabled=true
-        shift;;
-    "--force")
-        force=true
+    "--enable"|"--disable"|"--force-enable"|"--force-disable"|"--remove")
+        test -z "${ACTION}" || abort "More than one action specified."
+        ACTION="true"
+        ENABLE=""
+        INSTALL="true"
+        UPDATE=""
+        { test "${1}" = "--enable" || test "${1}" = "--disable"; } &&
+            UPDATE="true"
+        { test "${1}" = "--enable" || test "${1}" = "--force-enable"; } &&
+            ENABLE="true"
+        test "${1}" = "--remove" &&
+            INSTALL=""
         shift;;
     "--prefix")
@@ -151,14 +145,21 @@
 
 # Check required options and set default values for others.
-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."
+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.
@@ -168,28 +169,32 @@
     SERVICE_NAME="`expr "${COMMAND}" : '.*/\(.*\)'`"
 
-# Get the folder we are running from, as we need other files there.
-script_folder="`dirname "$0"`"
-script_folder="`cd "${script_folder}" && pwd`"
-test -d "${script_folder}" ||
-    abort "Failed to find the folder this command is running from."
-
 # Keep track of whether we found at least one initialisation system.
 found_init=""
-# And whether we found a previous service script/file.
-update=""
 
 # Find the best System V/BSD init path if any is present.
 for path in "${PREFIX}/init.d/rc.d" "${PREFIX}/init.d/" "${PREFIX}/rc.d/init.d" "${PREFIX}/rc.d"; do
     if test -d "${path}"; then
+        test -w "${path}" || abort "No permission to write to \"${path}\"."
+        for i in rc0.d rc1.d rc6.d rc.d/rc0.d rc.d/rc1.d rc.d/rc6.d; do
+            if test -d "${PREFIX}/${i}"; then
+                test -w "${PREFIX}/${i}" ||
+                    abort "No permission to write to \"${PREFIX}/${i}\"".
+            fi
+        done
         found_init="true"
-        test -f "${path}/${SERVICE_NAME}" && update="true"
-        "${script_folder}/generate_service_file" --format shell --command "${COMMAND}" --arguments "${ARGUMENTS}" --description "${DESCRIPTION}" --service-name "${SERVICE_NAME}" < "${script_folder}/init_template.sh" > "${path}/${SERVICE_NAME}"
-        chmod a+x "${path}/${SERVICE_NAME}"
+        update=""
+        test -f "${path}/${SERVICE_NAME}" && update="${UPDATE}"
+        if test -n "${INSTALL}"; then
+            "${script_folder}/generate_service_file" --format shell --command "${COMMAND}" --arguments "${ARGUMENTS}" --description "${DESCRIPTION}" --service-name "${SERVICE_NAME}" < "${script_folder}/init_template.sh" > "${path}/${SERVICE_NAME}"
+            chmod a+x "${path}/${SERVICE_NAME}"
+        else
+            rm "${path}/${SERVICE_NAME}"
+        fi
         # 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.
-        if test -z "${update}" || test -n "${force}"; then
+        if test -z "${update}"; then
             # Various known combinations of sysvinit rc directories.
-            for i in ${PREFIX}/rc*.d/[KS]??"${SERVICE_NAME}" ${PREFIX}/rc.d/rc*.d/[KS]??"${SERVICE_NAME}"; do
+            for i in "${PREFIX}"/rc*.d/[KS]??"${SERVICE_NAME}" "${PREFIX}"/rc.d/rc*.d/[KS]??"${SERVICE_NAME}"; do
                 rm -f "$i"
             done
@@ -198,5 +203,5 @@
                 rc-update del "${1}" > /dev/null 2>&1
             # Various known combinations of sysvinit rc directories.
-            if test -n "${enabled}"; then
+            if test -n "${ENABLE}"; then
                 for i in rc0.d rc1.d rc6.d rc.d/rc0.d rc.d/rc1.d rc.d/rc6.d; do
                     if test -d "${PREFIX}/${i}"; then
@@ -224,2 +229,3 @@
 test -z "${found_init}" &&
     abort "No supported initialisation system found."
+exit 0
Index: trunk/src/VBox/Installer/linux/testcase/tstInstallInit.sh
===================================================================
--- trunk/src/VBox/Installer/linux/testcase/tstInstallInit.sh	(revision 44069)
+++ trunk/src/VBox/Installer/linux/testcase/tstInstallInit.sh	(revision 44070)
@@ -19,5 +19,5 @@
 
 tab="	"
-tmpbase="/tmp/tstInstallInit99"
+tmpbase="/tmp/tstInstallInit 99"  # Space in the name for a little stress...
 
 ## The function definition at the start of every non-trivial shell script!
@@ -92,20 +92,28 @@
 esac
 
-# Test an init script installation
+# Create a simulated init system layout.
+create_simulated_init_tree()
+{
+    tmpdir="${1}"
+    rm -rf "${tmpdir}"
+    mkdir -m 0700 "${tmpdir}" || abort "Failed to create a temporary folder."
+    mkdir -p "${tmpdir}/init.d/" "${tmpdir}/rc.d/init.d/"
+    for i in 0 1 2 3 4 5 6; do
+        mkdir "${tmpdir}/rc${i}.d/" "${tmpdir}/rc.d/rc${i}.d/"
+    done
+    mkdir "${tmpdir}/run"
+}
+
+# Test an init script installation.
 print_line "installing an init script."
 failed=""
 # Create a simulated init system layout.
 tmpdir="${tmpbase}0"
-rm -rf "${tmpdir}"
-mkdir -m 0700 "${tmpdir}" || abort "Failed to create a temporary folder."
-mkdir -p "${tmpdir}/init.d/" "${tmpdir}/rc.d/init.d/"
-for i in 0 1 2 3 4 5 6; do
-    mkdir "${tmpdir}/rc${i}.d/" "${tmpdir}/rc.d/rc${i}.d/"
-done
-mkdir "${tmpdir}/run"
+create_simulated_init_tree "${tmpdir}"
 # Create the service binary.
 test_service "${tmpdir}" "service"
 # And install it.
-helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enabled
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enable ||
+    fail_msg "\"helpers/install_service\" failed."
 # Check that the main service file was created as specified.
 if test -x "${tmpdir}/init.d/service"; then
@@ -119,5 +127,5 @@
 # Try to start the service using the symbolic links which should have been
 # created.
-if "${tmpdir}/rc3.d/S20service" --prefix "${tmpdir}" --lsb-functions "" start >/dev/null; then
+if "${tmpdir}/rc3.d/S20service" --prefix "${tmpdir}" --lsb-functions "" start >/dev/null 2>&1; then
     if grep "1: test 2: of 3: my arguments" "${tmpdir}/started" >/dev/null; then
         test -f "${tmpdir}/stopped" &&
@@ -130,9 +138,9 @@
 fi
 # Check the status.
-"${tmpdir}/rc.d/rc5.d/S20service" --prefix "${tmpdir}" --lsb-functions "" status >/dev/null ||
+"${tmpdir}/rc.d/rc5.d/S20service" --prefix "${tmpdir}" --lsb-functions "" status >/dev/null 2>&1 ||
     fail_msg "\"${tmpdir}/rc.d/rc5.d/S20service\" reported the wrong status."
 # Try to stop the service using the symbolic links which should have been
 # created.
-if "${tmpdir}/rc.d/rc6.d/K80service" --prefix "${tmpdir}" --lsb-functions "" stop >/dev/null; then
+if "${tmpdir}/rc.d/rc6.d/K80service" --prefix "${tmpdir}" --lsb-functions "" stop >/dev/null 2>&1; then
     test -f "${tmpdir}/stopped" ||
         echo "\"${tmpdir}/rc.d/rc6.d/K80service\" did not stop correctly."
@@ -141,5 +149,5 @@
 fi
 # Check the status again - now it should be stopped.
-"${tmpdir}/rc.d/rc3.d/S20service" --prefix "${tmpdir}" --lsb-functions "" status >/dev/null &&
+"${tmpdir}/rc.d/rc3.d/S20service" --prefix "${tmpdir}" --lsb-functions "" status >/dev/null 2>&1 &&
     fail_msg "\"${tmpdir}/rc.d/rc3.d/S20service\" reported the wrong status."
 # Final summary.
@@ -149,2 +157,119 @@
     echo SUCCESS
 fi
+
+# Test an init script removal.
+print_line "removing an init script."
+failed=""
+# Create a simulated init system layout.
+tmpdir="${tmpbase}0"
+create_simulated_init_tree "${tmpdir}"
+# Create the service binary.
+test_service "${tmpdir}" "service"
+# Install it.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enable ||
+    fail_msg "\"helpers/install_service\" failed."
+# And remove it again.
+helpers/install_service --command "${tmpdir}/service" --prefix "${tmpdir}" --remove ||
+    fail_msg "\"helpers/install_service\" failed."
+# After uninstallation this should be the only file left in the init tree.
+rm "${tmpdir}/service"
+test "x`find "${tmpdir}" -type f -o -type l`" = "x" ||
+    fail_msg "not all files were removed."
+# Final summary.
+if test -n "${failed}"; then
+    echo "${failed}"
+else
+    echo SUCCESS
+fi
+
+# Test an enabled init script update with --disable.
+print_line "updating an enabled init script with --disable."
+failed=""
+# Create a simulated init system layout.
+tmpdir="${tmpbase}1"
+create_simulated_init_tree "${tmpdir}"
+# Create the service binary.
+test_service "${tmpdir}" "service"
+# Install it.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enable ||
+    fail_msg "\"helpers/install_service\" failed."
+# Install it disabled without forcing.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --disable ||
+    fail_msg "\"helpers/install_service\" failed."
+test "x`find "${tmpdir}"/rc*.d -type l | wc -l`" = "x12" ||
+    fail_msg "links were removed on non-forced disable."
+# Final summary.
+if test -n "${failed}"; then
+    echo "${failed}"
+else
+    echo SUCCESS
+fi
+
+# Test updating a disabled init script with --enable.
+print_line "updating a disabled init script with --enable."
+failed=""
+# Create a simulated init system layout.
+tmpdir="${tmpbase}2"
+create_simulated_init_tree "${tmpdir}"
+# Create the service binary.
+test_service "${tmpdir}" "service"
+# Install it.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --disable ||
+    fail_msg "\"helpers/install_service\" failed."
+# Install it disabled without forcing.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enable ||
+    fail_msg "\"helpers/install_service\" failed."
+test "x`find "${tmpdir}"/rc*.d -type l`" = "x" ||
+    fail_msg "files were installed on non-forced enable."
+# Final summary.
+if test -n "${failed}"; then
+    echo "${failed}"
+else
+    echo SUCCESS
+fi
+
+# Test an enabled init script update with --force-disable.
+print_line "updating an enabled init script with --force-disable."
+failed=""
+# Create a simulated init system layout.
+tmpdir="${tmpbase}3"
+create_simulated_init_tree "${tmpdir}"
+# Create the service binary.
+test_service "${tmpdir}" "service"
+# Install it.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --enable ||
+    fail_msg "\"helpers/install_service\" failed."
+# Install it disabled without forcing.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --force-disable ||
+    fail_msg "\"helpers/install_service\" failed."
+test "x`find "${tmpdir}"/rc*.d -type l`" = "x" ||
+    fail_msg "links were not removed on forced disable."
+# Final summary.
+if test -n "${failed}"; then
+    echo "${failed}"
+else
+    echo SUCCESS
+fi
+
+# Test updating a disabled init script with --force-enable.
+print_line "updating a disabled init script with --force-enable."
+failed=""
+# Create a simulated init system layout.
+tmpdir="${tmpbase}4"
+create_simulated_init_tree "${tmpdir}"
+# Create the service binary.
+test_service "${tmpdir}" "service"
+# Install it.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --disable ||
+    fail_msg "\"helpers/install_service\" failed."
+# Install it disabled without forcing.
+helpers/install_service --command "${tmpdir}/service" --arguments "test of my\ arguments" --description "My description" --prefix "${tmpdir}" --force-enable ||
+    fail_msg "\"helpers/install_service\" failed."
+test "x`find "${tmpdir}"/rc*.d -type l | wc -l`" = "x12" ||
+    fail_msg "files were not installed on forced enable."
+# Final summary.
+if test -n "${failed}"; then
+    echo "${failed}"
+else
+    echo SUCCESS
+fi
