diff options
Diffstat (limited to 'src/VBox/Installer/linux/install_service')
4 files changed, 1487 insertions, 0 deletions
diff --git a/src/VBox/Installer/linux/install_service/Makefile.kmk b/src/VBox/Installer/linux/install_service/Makefile.kmk new file mode 100644 index 00000000..812a75ea --- /dev/null +++ b/src/VBox/Installer/linux/install_service/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Linux installer init file generator. +# + +# +# Copyright (C) 2006-2012 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# generate_service_file +# +PROGRAMS.linux += generate_service_file + +generate_service_file_TEMPLATE = VBOXR3EXE +generate_service_file_SOURCES = generate_service_file.cpp +generate_service_file_INST = $(INST_BIN)helpers/ +generate_service_file_LIBS = $(LIB_RUNTIME) +ifdef VBOX_WITH_RUNPATH + generate_service_file_LDFLAGS = '$(VBOX_GCC_RPATH_OPT)$(VBOX_WITH_RUNPATH)' +else ifdef VBOX_WITH_RELATIVE_RUNPATH + generate_service_file_LDFLAGS = '$(VBOX_GCC_RPATH_OPT)$(VBOX_WITH_RELATIVE_RUNPATH)/..' +endif + +INSTALLS.linux += linux-install-service-bin +linux-install-service-bin_INST = bin/scripts/ +linux-install-service-bin_MODE = a+rx,u+w +linux-install-service-bin_SOURCES = \ + install_service.sh=>install_service + +INSTALLS.linux += linux-install-service-nobin +linux-install-service-nobin_INST = bin/scripts/ +linux-install-service-nobin_MODE = a+r,u+w +linux-install-service-nobin_SOURCES = \ + init_template.sh + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Installer/linux/install_service/generate_service_file.cpp b/src/VBox/Installer/linux/install_service/generate_service_file.cpp new file mode 100644 index 00000000..2ff27573 --- /dev/null +++ b/src/VBox/Installer/linux/install_service/generate_service_file.cpp @@ -0,0 +1,912 @@ +/* $Id: generate_service_file.cpp $ */ +/** @file + * Read a service file template from standard input and output a service file + * to standard output generated from the template based on arguments passed to + * the utility. See the usage text for more information. + */ + +/* + * Copyright (C) 2012-2013 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** + * 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> + +#include <iprt/ctype.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#ifndef READ_SIZE +/** How much of the input we read at a time. Override to something small for + * testing. */ +# define READ_SIZE _1M +#endif + +/* Macros for the template substitution sequences to guard against mis-types. */ +#define COMMAND "%COMMAND%" +#define ARGUMENTS "%ARGUMENTS%" +#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) +{ + static bool s_fShown; /* show only once */ + + RTPrintf(VBOX_PRODUCT " Service File Generator Version " + VBOX_VERSION_STRING "\n" + "(C) 2012" /* "-" VBOX_C_YEAR */ " " VBOX_VENDOR "\n" + "All rights reserved.\n" + "\n"); +} + +static void showOptions(void); + +void showUsage(const char *pcszArgv0) +{ + const char *pcszName = strrchr(pcszArgv0, '/'); + if (!pcszName) + pcszName = pcszArgv0; + RTPrintf( +"Usage:\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.\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" +" --version|-V\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\n"); + RTPrintf( +"Parameters:\n" +"\n"); + 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.\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. 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. 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.\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" + ); +} + +/** @name Template format. + * @{ + */ +enum ENMFORMAT +{ + /** No format selected. */ + FORMAT_NONE = 0, + /** Shell script format. */ + FORMAT_SHELL +}; +/** @} */ + +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 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(struct SERVICEPARAMETERS *pParameters); + +int main(int cArgs, char **apszArgs) +{ + int rc = RTR3InitExe(cArgs, &apszArgs, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + enum + { + OPTION_FORMAT = 1, + OPTION_COMMAND, + OPTION_ARGUMENTS, + OPTION_DESCRIPTION, + OPTION_SERVICE_NAME, + OPTION_ONE_SHOT, + OPTION_STOP_COMMAND, + OPTION_STOP_ARGUMENTS, + OPTION_STATUS_COMMAND, + OPTION_STATUS_ARGUMENTS + }; + + static const RTGETOPTDEF s_aOptions[] = + { + { "--format", OPTION_FORMAT, + RTGETOPT_REQ_STRING }, + { "--command", OPTION_COMMAND, + RTGETOPT_REQ_STRING }, + { "--arguments", OPTION_ARGUMENTS, + RTGETOPT_REQ_STRING }, + { "--description", OPTION_DESCRIPTION, + 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; + struct SERVICEPARAMETERS Parameters = { FORMAT_NONE }; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, cArgs, apszArgs, s_aOptions, + RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'h': + showUsage(apszArgs[0]); + return RTEXITCODE_SUCCESS; + break; + + case 'V': + showLogo(); + return RTEXITCODE_SUCCESS; + break; + + case OPTION_FORMAT: + 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", Parameters.pcszCommand)) + return(RTEXITCODE_SYNTAX); + Parameters.pcszCommand = ValueUnion.psz; + if (!checkAbsoluteFilePath("--command", + Parameters.pcszCommand)) + return(RTEXITCODE_SYNTAX); + break; + + case OPTION_ARGUMENTS: + 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", + 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", + 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; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (Parameters.enmFormat == FORMAT_NONE) + { + RTStrmPrintf(g_pStdErr, "--format must be specified.\n"); + return(RTEXITCODE_SYNTAX); + } + 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; +} + +/** Print an error and return true if an option is already set. */ +bool errorIfSet(const char *pcszName, bool isSet) +{ + if (isSet) + RTStrmPrintf(g_pStdErr, "%s may only be specified once.\n", pcszName); + return isSet; +} + +/** Match the string to a known format and return that (or "none" and print an + * error). */ +enum ENMFORMAT getFormat(const char *pcszName, const char *pcszValue) +{ + if (!strcmp(pcszValue, "shell")) + return FORMAT_SHELL; + RTStrmPrintf(g_pStdErr, "%s: unknown format %s.\n", pcszName, pcszValue); + return FORMAT_NONE; +} + +/** Check that the string is an absolute path to a file or print an error. */ +bool checkAbsoluteFilePath(const char *pcszName, const char *pcszValue) +{ + if (RTPathFilename(pcszValue) && RTPathStartsWithRoot(pcszValue)) + 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. */ +bool checkPrintable(const char *pcszName, const char *pcszValue) +{ + 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; + } + } + return true; +} + +/** Check that the string does not contain any non-graphic characters. */ +static bool checkGraphic(const char *pcszName, const char *pcszValue) +{ + const char *pcch = pcszValue; + for (; *pcch; ++pcch) + { + if (!RT_C_IS_GRAPH(*pcch)) + { + RTStrmPrintf(g_pStdErr, "%s: invalid character after \"%.*s\".\n", + pcszName, pcch - pcszValue, pcszValue); + return false; + } + } + return true; +} + +static bool createServiceFileCore(char **ppachTemplate, + struct SERVICEPARAMETERS + *pParamters); + +/** + * Read standard input and write it to standard output, doing all substitutions + * as per the usage documentation. + * @note This is a wrapper around the actual function to simplify resource + * allocation without requiring a single point of exit. + */ +bool createServiceFile(struct SERVICEPARAMETERS *pParameters) +{ + char *pachTemplate = NULL; + 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 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, + struct SERVICEPARAMETERS *pParameters) +{ + /* The size of the template data we have read. */ + size_t cchTemplate = 0; + /* The size of the buffer we have allocated. */ + size_t cbBuffer = 0; + /* How much of the template data we have written out. */ + size_t cchWritten = 0; + int rc = VINF_SUCCESS; + /* First of all read in the file. */ + while (rc != VINF_EOF) + { + size_t cchRead; + + if (cchTemplate == cbBuffer) + { + cbBuffer += READ_SIZE; + *ppachTemplate = (char *)RTMemRealloc((void *)*ppachTemplate, + cbBuffer); + } + if (!*ppachTemplate) + { + RTStrmPrintf(g_pStdErr, "Out of memory.\n"); + return false; + } + rc = RTStrmReadEx(g_pStdIn, *ppachTemplate + cchTemplate, + cbBuffer - cchTemplate, &cchRead); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error reading input: %Rrc\n", rc); + return false; + } + if (!cchRead) + rc = VINF_EOF; + cchTemplate += cchRead; + } + while (true) + { + /* Find the next '%' character if any and write out up to there (or the + * end if there is no '%'). */ + char *pchNext = (char *) memchr((void *)(*ppachTemplate + cchWritten), + '%', cchTemplate - cchWritten); + size_t cchToWrite = pchNext + ? pchNext - *ppachTemplate - cchWritten + : cchTemplate - cchWritten; + rc = RTStrmWrite(g_pStdOut, *ppachTemplate + cchWritten, cchToWrite); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + cchWritten += cchToWrite; + if (!pchNext) + break; + /* And substitute any of our well-known strings. We favour code + * readability over efficiency here. */ + if (getSequence(*ppachTemplate, cchTemplate, &cchWritten, + COMMAND, sizeof(COMMAND) - 1)) + { + if (!pParameters->pcszCommand) + { + 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(pParameters->pcszCommand); + const char *pcszExtension = + RTPathExt(pParameters->pcszCommand); + char *pszName = RTStrDupN(pcszFileName, + pcszExtension + ? pcszExtension - pcszFileName + : RTPATH_MAX); + bool fRc; + if (!pszName) + { + RTStrmPrintf(g_pStdErr, "Out of memory.\n"); + return false; + } + fRc = writePrintableString(pParameters->enmFormat, + pszName); + RTStrFree(pszName); + if (!fRc) + return false; + } + } + 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, '%'); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + } + else + { + RTStrmPrintf(g_pStdErr, "Unknown substitution sequence in input at \"%.*s\"\n", + RT_MIN(16, cchTemplate - cchWritten), + *ppachTemplate + cchWritten); + return false; + } + } + 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; +} + +/** Write a character to standard output and print an error and return false on + * failure. */ +bool outputCharacter(char ch) +{ + int rc = RTStrmWrite(g_pStdOut, &ch, 1); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + return true; +} + +/** Write a string to standard output and print an error and return false on + * failure. */ +bool outputString(const char *pcsz) +{ + int rc = RTStrmPutStr(g_pStdOut, pcsz); + if (RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Error writing output: %Rrc\n", rc); + return false; + } + return true; +} + +/** Write a character to standard output, adding any escaping needed for the + * format being written. */ +static bool escapeAndOutputCharacter(enum ENMFORMAT enmFormat, char ch) +{ + if (enmFormat == FORMAT_SHELL) + { + if (ch == '\'') + return outputString("\'\\\'\'"); + return outputCharacter(ch); + } + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + return false; +} + +/** Write a character to standard output, adding any escaping needed for the + * format being written. */ +static bool outputArgumentSeparator(enum ENMFORMAT enmFormat) +{ + if (enmFormat == FORMAT_SHELL) + return outputString("\' \'"); + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + return false; +} + +bool writeCommand(enum ENMFORMAT enmFormat, const char *pcszCommand) +{ + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + for (; *pcszCommand; ++pcszCommand) + if (enmFormat == FORMAT_SHELL) + { + if (*pcszCommand == '\'') + { + if (!outputString("\'\\\'\'")) + return false; + } + else if (!outputCharacter(*pcszCommand)) + return false; + } + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + return true; +} + +const char aachEscapes[][2] = +{ + { 'a', '\a' }, { 'b', '\b' }, { 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' }, + { 't', '\t' }, { 'v', '\v' }, { 0, 0 } +}; + +bool writeQuoted(enum ENMFORMAT enmFormat, const char *pcszQuoted) +{ + /* Was the last character seen a back slash? */ + bool fEscaped = false; + /* Was the last character seen an argument separator (an unescaped space)? + */ + bool fNextArgument = false; + + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + for (; *pcszQuoted; ++pcszQuoted) + { + if (fEscaped) + { + bool fRc = true; + const char (*pachEscapes)[2]; + fEscaped = false; + /* One-letter escapes. */ + for (pachEscapes = aachEscapes; (*pachEscapes)[0]; ++pachEscapes) + if (*pcszQuoted == (*pachEscapes)[0]) + { + if (!escapeAndOutputCharacter(enmFormat, (*pachEscapes)[1])) + return false; + break; + } + if ((*pachEscapes)[0]) + continue; + /* Octal. */ + if (*pcszQuoted >= '0' && *pcszQuoted <= '7') + { + uint8_t cNum; + char *pchNext; + char achDigits[4]; + int rc; + 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", + pcszQuoted - 1); + return false; + } + if (!escapeAndOutputCharacter(enmFormat, cNum)) + return false; + pcszQuoted += pchNext - achDigits - 1; + continue; + } + /* Hexadecimal. */ + if (*pcszQuoted == 'x') + { + uint8_t cNum; + char *pchNext; + char achDigits[3]; + int rc; + RTStrCopy(achDigits, sizeof(achDigits), pcszQuoted + 1); + rc = RTStrToUInt8Ex(achDigits, &pchNext, 16, &cNum); + if ( rc == VWRN_NUMBER_TOO_BIG + || rc == VWRN_NEGATIVE_UNSIGNED + || RT_FAILURE(rc)) + { + RTStrmPrintf(g_pStdErr, "Invalid hexadecimal sequence at \"%.16s\"\n", + pcszQuoted - 1); + return false; + } + if (!escapeAndOutputCharacter(enmFormat, cNum)) + return false; + pcszQuoted += pchNext - achDigits; + continue; + } + /* Output anything else non-zero as is. */ + if (*pcszQuoted) + { + if (!escapeAndOutputCharacter(enmFormat, *pcszQuoted)) + return false; + continue; + } + RTStrmPrintf(g_pStdErr, "Trailing back slash in argument.\n"); + return false; + } + /* Argument separator. */ + if (*pcszQuoted == ' ') + { + if (!fNextArgument && !outputArgumentSeparator(enmFormat)) + return false; + fNextArgument = true; + continue; + } + else + fNextArgument = false; + /* Start of escape sequence. */ + if (*pcszQuoted == '\\') + { + fEscaped = true; + continue; + } + /* Anything else. */ + if (!outputCharacter(*pcszQuoted)) + return false; + } + if (enmFormat == FORMAT_SHELL) + if (!outputCharacter('\'')) + return false; + return true; +} + +bool writePrintableString(enum ENMFORMAT enmFormat, const char *pcszString) +{ + if (enmFormat == FORMAT_SHELL) + return outputString(pcszString); + RTStrmPrintf(g_pStdErr, "Error: unknown template format.\n"); + 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; +} diff --git a/src/VBox/Installer/linux/install_service/init_template.sh b/src/VBox/Installer/linux/install_service/init_template.sh new file mode 100755 index 00000000..ed9c9a48 --- /dev/null +++ b/src/VBox/Installer/linux/install_service/init_template.sh @@ -0,0 +1,317 @@ +#!/bin/sh +# +# VirtualBox generic init script. +# +# Copyright (C) 2012-2013 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +### BEGIN INIT INFO +# Required-Start: $local_fs +# Should-Start: $syslog +# Required-Stop: $local_fs +# Should-Stop: $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: %DESCRIPTION% +### END INIT INFO + +## @todo We should really replace the daemon starting, stopping and checking +# code with a tool of our own written in C, which we could always use +# instead of the LSB functions. + +cr=" +" +tab=" " +IFS=" ${cr}${tab}" +'unset' -f unalias +'unalias' -a +unset -f command +PATH=/bin:/sbin:/usr/bin:/usr/sbin:$PATH + +## A generic service script which can be used, after substituting some place- +# holders with service-specific values, to run most services on LSB, System V +# or BSD-compatible service management systems. As we control both the service +# code and the init script we try to push as much as possible of the logic into +# the service and out of the very system-dependent service configuration +# scripts and files. See the help text of the "install_service.sh" helper +# script for more details. +# +# Furthermore, to simplify deployment, we will install all init scripts using +# this generic template manually during the post install phase or at run time +# using LSB functions if they are available (as they should be on most common +# modern distributions) or manually placing the file in the appropriate +# directory and creating symbolic links on System V or writing to rc.local on +# BSD-compatible systems. Systems requiring different treatment will be added +# here when we add support for them, but we will try to keep everything as +# generic as we can. +# +# In general, we try to behave as natively as we reasonably can on the most +# important target systems we support and to work well enough on as many others +# as possible, but in particular without trying to look perfectly native. +# +# 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. +SHUT_DOWN_TIME_OUT=5 +## If this is set to an empty value then the LSB init functions will not be +# used. This is intended for testing the fallback commands. +LSB_FUNCTIONS="/lib/lsb/init-functions" + +# Silently exit if the package was uninstalled but not purged. +test -r %COMMAND% || exit 0 + +## The function definition at the start of every non-trivial shell script! +abort() +{ + log_failure_msg "$*" + exit 1 +} + +## Exit successfully. +do_success() +{ + log_success_msg "%DESCRIPTION% successfully started." + exit 0 +} + +## Set the error message. +set_error() +{ + test -z "${error}" && error="${1}" +} + +# Gentoo/OpenRC perculiarity. +if test "x${0}" = "x/sbin/rc" || test "x${0}" = "xrc"; then + shift +fi + +# Process arguments. +action="" +error="" +prefix="/var" +while test x"${#}" != "x0"; do + case "${1}" in + --lsb-functions) + test x"${#}" = "x1" && + set_error "${1}: missing argument." + LSB_FUNCTIONS="${2}" + shift 2;; + --prefix) + test x"${#}" = "x1" && + set_error "${1}: missing argument." + prefix="${2}" + shift 2;; + --help) + cat << EOF +Usage: + + ${0} {start|stop|restart|status} [<options>] + + start|stop|restart|status + Start/stop/restart/report status for the service. + +Options: + + --lsb-functions <script> + Take the standard LSB init functions from <script> instead of from the + normal location, or use our own versions if <script> is an empty string. + + --prefix <folder> + Use the folder <folder> for storing variable data instead of "/var". The + child folder "run" must exist. +EOF + exit 0;; + start|stop|restart|force-reload|condrestart|try-restart|reload|status) + test -z "${action}" || + set_error "More than one action requested." + action="${1}" + shift;; + *) + set_error "Unknown option \"${1}\". Try \"${0} --help\" for more information." + shift;; + esac +done + +## Set Redhat and Fedora lock directory +LOCK_FOLDER="${prefix}/lock/subsys/" +LOCK_FILE="${LOCK_FOLDER}/%SERVICE_NAME%" + +# Use LSB functions if available. Success and failure messages default to just +# "echo" if the LSB functions are not available, so call these functions with +# messages which clearly read as success or failure messages. +test -n "${LSB_FUNCTIONS}" && test -f "${LSB_FUNCTIONS}" && + . "${LSB_FUNCTIONS}" + +type log_success_msg >/dev/null 2>&1 || + log_success_msg() + { + cat << EOF +${*} +EOF + } + +type log_failure_msg >/dev/null 2>&1 || + log_failure_msg() + { + cat << EOF +${*} +EOF + } + +## Get the LSB standard PID-file name for a binary. +pidfilename() +{ + echo "${prefix}/run/${1##*/}.pid" +} + +## Get the PID-file for a process like the LSB functions do ( "-p" or by name). +pidfileofproc() +{ + if test x"${1}" = "x-p"; then + echo "${2}" + else + pidfilename "${1}" + fi +} + +## Read the pids from an LSB PID-file, checking that they are positive numbers. +pidsfromfile() +{ + pids="" + test -r "${1}" && + read -r pids < "${1}" 2>/dev/null + for i in $pids; do + test 1 -le "${i}" || return 1 + done + echo "${pids}" +} + +## Check whether the binary $1 with the pids $2... is running. +procrunning() +{ + binary="${1}" + shift + case "`ps -p "${@}" -f 2>/dev/null`" in *"${binary}"*) + return 0;; + esac + return 1 +} + +# We prefer our own implementations of pidofproc and killproc over falling back +# to distribution ones with unknown quirks. +# type pidofproc >/dev/null 2>&1 || + pidofproc() + { + pidfile="`pidfileofproc "${@}"`" + test "x${1}" = "x-p" && shift 2 + pids="`pidsfromfile "${pidfile}"`" + procrunning "${1}" ${pids} && echo "${pids}" + } + +# type killproc >/dev/null 2>&1 || + killproc() + { + pidfile="`pidfileofproc "${@}"`" + test "x${1}" = "x-p" && shift 2 + pids="`pidsfromfile "${pidfile}"`" + if test -n "${2}"; then + procrunning "${1}" ${pids} || return 1 + kill "${2}" ${pids} + return 0 + else + rm -f "${pidfile}" + procrunning "${1}" ${pids} || return 0 + kill "${pids}" + # Short busy wait for the process to terminate. + stamp="`times`" + while test x"${stamp}" = x"`times`"; do + procrunning "${1}" ${pids} || return 0 + done + # Slow sleeping wait if it is still running. + for high in "" 1 2 3 4 5 6 7 8 9; do + for time in ${high}0 ${high}1 ${high}2 ${high}3 ${high}4 ${high}5 ${high}6 ${high}7 ${high}8 ${high}9; do + sleep 1 + procrunning "${1}" ${pids} || return 0 + if test "${time}" = "${SHUT_DOWN_TIME_OUT}"; then + kill -9 "${pid}" + return 0 + fi + done + done + return 0 + fi + } + +start() +{ + test -d "${LOCK_FOLDER}" && touch "${LOCK_FILE}" + test -n "`pidofproc %COMMAND%`" && exit 0 +%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 +} + +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." + return 0 +} + +status() +{ +%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 +} + +test -z "${error}" || abort "${error}" + +case "${action}" in +start) + start;; +stop) + stop;; +restart|force-reload) + start + stop;; +condrestart|try-restart) + status || exit 0 + stop + start;; +reload) + ;; +status) + status;; +esac diff --git a/src/VBox/Installer/linux/install_service/install_service.sh b/src/VBox/Installer/linux/install_service/install_service.sh new file mode 100755 index 00000000..89311043 --- /dev/null +++ b/src/VBox/Installer/linux/install_service/install_service.sh @@ -0,0 +1,209 @@ +#!/bin/sh + +# +# Script to install services within a VirtualBox installation. +# +# Copyright (C) 2012-2013 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# Clean up before we start. +cr=" +" +tab=" " +IFS=" ${cr}${tab}" +'unset' -f unalias +'unalias' -a 2>/dev/null +'unset' -f command +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. +usage() { + cat << EOF +Usage: + + `basename $0` --help|--enable|--disable|--force-enable|--force-disable + |--remove [--prefix <prefix>] + -- <pass-through parameters> + +Create a system service which runs a command. In order to make it possible to +do this in a simple and portable manner, we place a number of requirements on +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 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. + - That if all running copies of the main process binary are stopped first the + service can be re-started and will do any necessary clean-up automatically. + - That any output which must not be lost go either to the system log or to the + service's private log. + +We currently support System V init only. This will probably soon be extended +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 + Print this help text and exit. + + --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". + +Option: + + --prefix <prefix> + Treat all paths as relative to <prefix> rather than /etc. + +Pass-through parameters will be passed through to the "generate_service_file" +tool. +EOF +} + +## The function definition at the start of every non-trivial shell script! +abort() { + ## $1 Error text to output to standard error. + cat >&2 << EOF +$1 +EOF + exit 1 +} + +ACTION="" +PREFIX="/etc/" +SERVICE_NAME="" + +# Process arguments. +while test x"${1}" != "x--"; do + case "${1}" in + "--help"|"--usage") + usage + exit 0;; + "--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") + test -z "${2}" && abort "${1}: missing argument." + PREFIX="${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." + +# Get the service name. +SERVICE_NAME=`echo "%SERVICE_NAME%" | + "${script_folder}/../helpers/generate_service_file" --format shell "${@}"` +test -z "${SERVICE_NAME}" && + abort "Please supply a command path." + +# Keep track of whether we found at least one initialisation system. +found_init="" + +# 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 + # Check permissions for the init path. + test -w "${path}" || abort "No permission to write to \"${path}\"." + # And for the System V symlink directories. + 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 + # And for the OpenRC symlink directories. + if test -d "${PREFIX}/runlevel/"; then + test -w "${PREFIX}/runlevel/" || + abort "No permission to write to \"${PREFIX}/runlevel\"". + fi + found_init="true" + update="" + test -f "${path}/${SERVICE_NAME}" && update="${UPDATE}" + if test -n "${INSTALL}"; then + "${script_folder}/../helpers/generate_service_file" --format shell "${@}" < "${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}"; 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 + rm -f "${i}" + done + # And OpenRC. + test -d "${PREFIX}/runlevel/" && + for i in "/${PREFIX}/runlevel"/*/"${SERVICE_NAME}"; do + rm -f "${i}" + done + # Various known combinations of sysvinit rc directories. + 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 + # Paranoia test first. + test -d "${PREFIX}/${i}/K80${SERVICE_NAME}" || + ln -sf "${path}/${SERVICE_NAME}" "${PREFIX}/${i}/K80${SERVICE_NAME}" + fi + done + for i in rc2.d rc3.d rc4.d rc5.d rc.d/rc2.d rc.d/rc3.d rc.d/rc4.d rc.d/rc5.d; do + if test -d "${PREFIX}/${i}"; then + # Paranoia test first. + test -d "${PREFIX}/${i}/S20${SERVICE_NAME}" || + ln -sf "${path}/${SERVICE_NAME}" "${PREFIX}/${i}/S20${SERVICE_NAME}" + fi + done + # And OpenRC. + test -d "${PREFIX}/runlevel/default" && + ln -sf "${path}/${SERVICE_NAME}" "/${PREFIX}/runlevel/default/" + fi + fi + break + fi +done + +test -z "${found_init}" && + abort "No supported initialisation system found." +exit 0 |