From Fedora Project Wiki

EPEL SysV Initscripts

This document describes the guidelines for SysV-style Initscripts, for use and inclusion in EPEL 5/6 packages. EPEL 7 and newer MUST provide systemd units and follow the appropriate Fedora systemd guidelines.

Initscripts on the filesystem

Packages with SysV-style initscripts must put them into /etc/rc.d/init.d. A rpm macro exists for this directory, %_initddir. Note: The %_initddir macro does not exist on RHEL 5 or older. For those releases, you should use the deprecated %_initrddir macro.


In the past, some packages were putting files in /etc/init.d instead of /etc/rc.d/init.d. (/etc/init.d is a symlink to /etc/rc.d/init.d)

This split made it more difficult to use yum install against the initscript filename and path. Since /etc/init.d is the symlink (and because the Filesystem Hierarchy Standard does not mandate a location), EPEL 5/6 requires that all SysV-style initscripts must go into the full /etc/rc.d/init.d directory.

Initscript packaging

Initscripts must not be marked as %config files.

Although init files live in /etc, they are scripts to be executed, not configured. Any configuration should be made available through /etc/sysconfig/<service> rather than in the init script itself. A valid exception to this rule would be existing packages where configuration is still done via the init file. In this case, the init file could be marked as %config following the rules from the Configuration files section to preserve a users configuration upon upgrade, hopefully so that the user can migrate said configuration to a new /etc/sysconfig/<service> config file.

Init scripts should also have 0755 permissions.

Initscripts in spec file scriptlets

Requires(post): chkconfig
Requires(preun): chkconfig
# This is for /sbin/service
Requires(preun): initscripts
...
%post
# This adds the proper /etc/rc*.d links for the script
/sbin/chkconfig --add <script>

%preun
if [ $1 -eq 0 ] ; then
    /sbin/service <script> stop >/dev/null 2>&1
    /sbin/chkconfig --del <script>
fi

'if [ $1 -eq 0 ] ' checks that this is the actual deinstallation of the package, as opposed to just removing the old package on upgrade. These statements stop the service, and remove the /etc/rc*.d links.

# This is for /sbin/service
Requires(postun): initscripts
...
%postun
if [ "$1" -ge "1" ] ; then
    /sbin/service <script> condrestart >/dev/null 2>&1 || :
fi

'if [ "$1" -ge "1" ] checks that this is an upgrade of the package. If so, restart the service if it's running. (This may not be appropriate for all services.)

Why don't we....

  • run 'chkconfig <service> on'?

If a service should be enabled by default, make this the default in the init script. Doing otherwise will cause the service to be turned on on upgrades if the user explicitly disabled it.

Note that the default for most network-listening scripts is off. This is done for better security. We have multiple tools that can enable services, including GUIs.

  • start the service after installation?

Installations can be in changeroots, in an installer context, or in other situations where you don't want the services started.

Initscript template

Below is the template for EPEL SysV-style initscripts. The sections are explained in detail below.

#!/bin/sh
#
# <daemonname> <summary>
#
# chkconfig:   <default runlevel(s)> <start> <stop>
# description: <description, split multiple lines with \
#              a backslash>

### BEGIN INIT INFO
# Provides: 
# Required-Start: 
# Required-Stop: 
# Should-Start: 
# Should-Stop: 
# Default-Start: 
# Default-Stop: 
# Short-Description: 
# Description:      
### END INIT INFO

# Source function library.
. /etc/rc.d/init.d/functions

exec="/path/to/<daemonname>"
prog="<service name>"
config="<path to major config file>"

[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog

lockfile=/var/lock/subsys/$prog

start() {
    [ -x $exec ] || exit 5
    [ -f $config ] || exit 6
    echo -n $"Starting $prog: "
    # if not running, start it up here, usually something like "daemon $exec"
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    # stop it here, often "killproc $prog"
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

reload() {
    restart
}

force_reload() {
    restart
}

rh_status() {
    # run checks to determine if the service is running or use generic status
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}


case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
        restart
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac
exit $?

Careful Handling of /var/lock/subsys/<service_name> mechanism

Every SysV-style initscript that starts a resident daemon -- as opposed to those that start tasks (e.g. firstboot and kdump) -- must manage a /var/lock/subsys/<service_name> file.

This is important because on shutdown, SysV init fails to stop services that do not create /var/lock/subsys/<service_name> files. Some upstreams do not code these specifics into their scripts and thus their services are forced to crash on shutdown. This shutdown behavior has been present in EPEL for a long time, but it is not uncommon to see external service scripts which do not implement lock files.

Why do SysV initscripts with resident daemons require lock files?

When a correctly configured service is started through an init script, a file is touched in the /var/lock/subsys/ directory with the same name as the init script. When the service is stopped, this file is removed. The contents of this file are unimportant for the scope of this guideline, as long as the filename is the same as the init script.

This file represents that a service's subsystem is locked, which means the service should be running. Since a service may consist of multiple executables with different names, finding the process ID (PID) of a single executable may not be sufficient to determine the status of the entire service itself. For this reason, the command:

service <initscript> status

checks both the PID of the executable and the file in the /var/lock/subsys/ directory. If the PID is not found but the subsystem is locked, you will receive a message similar to this:

<service> dead but subsys locked

Managing a service's subsystem has two purposes. First, if the service does not lock the subsystem, it can still be started and stopped through the service interface. However, when switching runlevels, the rc scripts check for the existence of the file in /var/lock/subsys/. If this file is not found, the service will not correctly start or stop between runlevels, even if there are start and kill symbolic links in the /etc/rc#.d/ directories.

Second, the /var/lock/subsys/ directory is checked during reboots and shutdowns. The order of a shutdown is as follows:

1. Run service <initscript> stop for all known services 2. Run kill -SIGTERM to terminate all processes 3. Pause for five seconds 4. Run kill -SIGKILL to kill all remaining processes

This method of shutting down is ordered such that processes are killed as gracefully as possible. During a shutdown, the script /etc/rc.d/init.d/killall checks the /var/lock/subsys/ directory to see if any subsystems are still locked. If a service's subsystem is locked after all other services have been stopped (i.e. step 1 above has completed), the killall script uses the subsystem filename to call service <initscript> stop. This attempts to stop the service gracefully before handing off to steps 2 through 4, which forcefully terminate the process itself.

This /etc/rc excerpt shows that when a system switches runlevels, K* services are only stopped if they have lock files.

for i in /etc/rc$runlevel.d/K* ; do
        # Check if the subsystem is already up.
        subsys=${i#/etc/rc$runlevel.d/K??}
        [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] || continue
        < .... service-stopping code .... >
done

This excerpt from /etc/init.d/killall shows that, like /etc/rc, it only runs the stop function for services that have a lock file present in the subsys directory.

for i in /var/lock/subsys/* ; do
        # Check if the script is there.
        [ -f "$i" ] || continue

        # Get the subsystem name.
        subsys=${i#/var/lock/subsys/}

        # Networking could be needed for NFS root.
        [ $subsys = network ] && continue

        # Bring the subsystem down.
        if [ -f /etc/init.d/$subsys ]; then
                /etc/init.d/$subsys stop
        elif [ -f /etc/init.d/$subsys.init ]; then
                /etc/init.d/$subsys.init stop
        else
                rm -f "$i"
        fi
done

Checking your system SysV-style initscripts for potential risks

Since it is common for third-party software to provide SysV-style initscripts, you can check your system SysV-style initscripts for potential risks from lack of lock file handling by running the following command:

for i in /etc/init.d/*; do grep -q /var/lock/subsys $i || echo "$i@doesn't manage lock files"; done | column -ts@

Please note that some scripts which do not start resident daemons or are "special" (e.g. bluetooth, firstboot, halt, kdump, ksm, libvirt-guests, netcf-transaction, portreserve, single, sysstat) may appear as false-positives in this test and can be ignored. The main purpose of this check is to help you look for initscripts from third party sources to ensure that they pass this safety check.

Chkconfig Header

Every EPEL SysV-style initscript must contain a chkconfig header. This header is composed of two parts, a "# chkconfig:" line, and a "# description:" line.

# chkconfig: line

The chkconfig: line in a SysV-style initscript is used to determine the runlevels in which the service should be started by default. It is also used to set the "priority", or order in which the services are started within a runlevel. All EPEL SysV-style initscripts must have this line.

# chkconfig: <startlevellist> <startpriority> <endpriority>
  • <startlevellist> is a list of the runlevels for which the service should be started by default. Only services which are really required for a vital system should define runlevels here. If no runlevels are defined, a - should be used in place of the runlevels list.
  • <startpriority> is the "priority" weight for starting the service. Services are started in numerical order, starting at 0.
  • <endpriority> is the "priority" weight for stopping the service. Services are stopped in numerical order, starting at 0. By default, you should set the <endpriority> equal to 100 - <startpriority>.

For example:

# chkconfig: 2345 20 80 

This means that the service will start by default on runlevels 2, 3, 4, and 5, with a startup priority of 20, and a shutdown priority of 80.

More commonly, the service is off by default on all runlevels, which looks like this:

# chkconfig: - 20 80

# description: line

The second line in the chkconfig header contains a description for the service. All EPEL SysV-style initscripts must have this line.

# description: <description of service>

The description of service may be more than one line long, continued with '\' characters. The initial comment and following whitespace on any additional lines are ignored, but should be used.

For example:

# description: Saves and restores system entropy pool for \
#              higher quality random number generation.

LSB Header

LSB Headers are not required for EPEL SysV-style initscripts, but they may be used. There is no requirement in the LSB certification for any system scripts to be LSB compliant, and it can cause issues with ordering.

If LSB Headers are used in a EPEL SysV-style initscript, it must follow these guidelines.

The LSB Header is composed of the following sections:

  • # Provides:
  • # Required-Start:
  • # Required-Stop:
  • # Should-Start:
  • # Should-Stop:
  • # Default-Start:
  • # Default-Stop:
  • # Short-Description:
  • # Description:

Boundary Comments

The LSB Header is bounded by comments, specifically, the beginning of the header is marked with:

### BEGIN INIT INFO

The end of the LSB Header is marked with:

### END INIT INFO

All LSB Header entries must have these boundary comments.

Facility Names

Boot facilities are used to indicate dependencies in initialization scripts. Facility names are assigned to scripts by the Provides: keyword. Facility names that begin with a dollar sign ('$') are reserved system facility names. Facility names are only recognized in the context of the initscript comment block (LSB Header) and are not available in the body of the init script. In particular, the use of the leading '$' character does not imply system facility names are subject to shell variable expansion, since they appear inside comments.

LSB compliant init implementations are supposed to provide the following system facility names:

  • $local_fs:: all local file systems are mounted
  • $network:: basic networking support is available. Example: a server program could listen on a socket.
  • $named:: IP name-to-address translation, using the interfaces described in this specification, are available to the level the system normally provides them. Example: if a DNS query daemon normally provides this facility, then that daemon has been started.
  • $portmap:: daemons providing SunRPC/ONCRPC portmapping service as defined in RFC 1833: Binding Protocols for ONC RPC Version 2 (if present) are running.
  • $remote_fs:: all remote file systems are available. In some configurations, file systems such as /usr may be remote. Many applications that require $local_fs will probably also require $remote_fs.
  • $syslog:: system logger is operational.
  • $time:: the system time has been set, for example by using a network-based time program such as ntp or rdate, or via the hardware Real Time Clock.

Other (non-system) facilities may be defined in the # Provides: line in the LSB Header.

# Provides: line

The # Provides: line in the LSB Header lists any boot facilities that this service provides. Other services can reference these boot facilities in their # Required-Start: and # Required-Stop: lines.

# Provides: boot_facility_1 [boot_facility_2...]

When an initscript is run with a start argument, the boot facility or facilities specified by the Provides keyword shall be deemed present and hence init scripts which require those boot facilities should be started later. When an initscript is run with a stop argument, the boot facilities specified by the Provides keyword are deemed no longer present.

In EPEL, a # Provides: line listing the name of the service that the initscript starts is not needed as the name of the service is implicitly Provided.

# Required-Start: line

The # Required-Start: line in the LSB Header lists any boot facilities which must be available during startup of this service.

# Required-Start: boot_facility_1 [boot_facility_2...]

This line is optional, if an initscript has no need for requiring other boot facilities before starting, it should be omitted.

# Required-Stop: line

The # Required-Stop: line in the LSB Header lists any boot facilities which should NOT be stopped before shutting down this service.

# Required-Stop: boot_facility_1 [boot_facility_2...]

This line is optional, if an initscript has no need for requiring that other boot facilities must be stopped only after it has shutdown, then the line should be omitted.

# Should-Start: line

The # Should-Start: line in the LSB Header lists any facilities, which, if present, should be available during startup of this service. The intent is to allow for "optional" dependencies which do not cause the service to fail if a facility is not available.

# Should-Start: boot_facility_1 [boot_facility_2...]

This line is optional, if an initscript has no use for starting other optional dependencies before hand, it should be omitted.

# Should-Stop: line

The # Should-Stop: line in the LSB Header lists any facilities, which, if present, should only be stopped after shutting down this service. The intent is to allow for "optional" dependencies which do not cause the service to fail if a facility is not available.

# Should-Stop: boot_facility_1 [boot_facility_2...]

This line is optional, if an initscript has no use for preventing other optional dependencies from stopping until after it has shutdown, the line should be omitted.

How LSB Provides actually work in EPEL

EPEL uses chkconfig for script enablement (chkconfig --add) and script activation/deactivation (chkconfig on/chkconfig off). When these tasks occur, the LSB dependencies are read, and the start and stop priorities of the scripts are then adjusted to satisfy those dependencies.

What this means:

  • LSB header dependencies are honored (albeit in a static mechanism)
  • If you use LSB headers, your start and stop priority may end up being different than what is in the # chkconfig: line

# Default-Start: line

The # Default-Start: line in the LSB Header lists the runlevels for which the service will be enabled by default. These runlevels are space-separated, unlike the Chkconfig header.

# Default-Start: run_level_1 [run_level_2...]

Each EPEL SysV-style initscript which needs to start by default in any runlevel must include this line in the LSB Header, and it must match the list of runlevels defined for startup in the Chkconfig header. Only services which are really required for a vital system should define runlevels here. If the service does not start by default in any runlevel, this line should be omitted.

For example, if a service starts by default in runlevels 3, 4, and 5 only, the LSB Header in the initscript would specify:

# Default-Start: 3 4 5

More commonly, the service does not start by default in any runlevel. In this case, the line should be omitted.

# Default-Stop: line

The # Default-Stop: line in the LSB Header lists the runlevels for which the service will not be started by default. These runlevels are space-separated, and must contain all of the numeric runlevels not used in the # Default-Start: line.

# Default-Stop: run_level_1 [run_level_2...]

Each EPEL SysV-style initscript which needs to start by default in any runlevel must include this line in the LSB Header (if the # Default-Start: line is present, then there must also be a # Default-Stop: line.). If the service does not start by default in any runlevel, this line should be omitted.

For example, if a service starts by default in runlevels 3, 4, and 5 only, then the # Default-Stop: line in the LSB Header must specify runlevels 0, 1, 2, and 6:

# Default-Stop: 0 1 2 6

Note that the runlevels must be explicitly set in the # Default-Stop: line, there are no automatic default settings derived from the # Default-Start: line.

# Short-Description: line

The # Short-Description: line in the LSB Header provides a brief summary of the actions of the init script. This must be no longer than a single, 80 character line of text.

# Short-Description: This service is a mail server.

All EPEL SysV-style initscripts must contain the # Short-Description: line in the LSB Header. It can be considered roughly equivalent to the Summary: field in an RPM spec file.

# Description: line

The # Description: line in the LSB Header provides a more complete description of the actions of the initscript. It may span mulitple lines, where each continuation line must begin with a '#' followed by tab character or a '#' followed by at least two space characters. The multiline description is terminated by the first line that does not match this criteria.

Example:

# Description: Bluetooth services for service discovery, authentication,
#              Human Interface Devices, etc.

All EPEL SysV-style initscripts must contain the # Description: line in the LSB Header. It can be considered roughly equivalent to the %description section in an RPM spec file. It must contain the same text as the # description: line in the chkconfig header.

LSB Header Example

Here is a complete LSB Header to illustrate the correct use of the LSB Headers:

### BEGIN INIT INFO
# Provides: OurDB
# Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop OurDB
# Description: OurDB is a very fast and reliable database
#              engine used for illustrating init scripts
### END INIT INFO

LSB Headers are not required for EPEL SysV-style initscripts, but if it is used in the initscript, then #Provides:, # Short-Description:, and # Description: are required to be present.

Initialization of Environment Variables

Since initscripts may be run manually by a system administrator with non-standard environment variable values for PATH, USER, LOGNAME, etc., init scripts should not depend on the values of these environment variables. They should be set to known/default values if they are needed.

Required Actions

All SysV-style initscripts in EPEL must have implementations of the following actions:

  • start: starts the service
  • stop: stops the service
  • restart: stop and restart the service if the service is already running, otherwise just start the service
  • condrestart (and try-restart): restart the service if the service is already running, if not, do nothing
  • reload: reload the configuration of the service without actually stopping and restarting the service (if the service does not support this, do nothing)
  • force-reload: reload the configuration of the service and restart it so that it takes effect
  • status: print the current status of the service
  • usage: by default, if the initscript is run without any action, it should list a "usage message" that has all actions (intended for use)

condrestart and try-restart

EPEL SysV-style initscripts must support both the condrestart and try-restart action. These two actions are intended to serve an identical purpose, and must not differ in behavior. In fact, it is highly recommended that packagers implement condrestart and try-restart as equivalent options in the case statement:

condrestart|try-restart)
    rh_status_q || exit 0
    restart
    ;;

Initscripts must be on their best behavior

EPEL SysV-style initscripts must behave sensibly if they are started when the service is already running, or stopped when the service is not running. They must not kill unrelated (but perhaps, similarly-named) user processes as a result of their normal actions. The best way to achieve this is to use the init-script functions provided by /etc/rc.d/init.d/functions :

# Source function library.
. /etc/rc.d/init.d/functions

If a service reloads its configuration automatically (as in the case of cron, for example), the reload action of the initscript must behave as if the configuration was reloaded successfully. The restart, condrestart, try-restart, reload and force-reload actions may be atomic; that is if a service is known not to be operational after a restart or reload, the script may return an error without any further action.

Exit Codes for the Status Action

If the status action is requested, the initscript must return the correct exit status code, from this list:

0:	program is running or service is OK
1:	program is dead and /var/run pid file exists
2:	program is dead and /var/lock lock file exists
3:	program is not running
4:	program or service status is unknown
5-99:	reserved for future LSB use
100-149:	reserved for distribution use
150-199:	reserved for application use
200-254:	reserved

EPEL does not currently define any distribution specific status codes.

Exit Codes for non-Status Actions

For all other initscript actions, the init script must return an exit status of zero if the action was successful. In addition to straightforward success, the following situations are also to be considered successful:

  • restarting a service (instead of reloading it) with the force-reload argument
  • running start on a service already running
  • running stop on a service already stopped or not running
  • running restart on a service already stopped or not running
  • running condrestart or try-restart on a service already stopped or not running

In case of an error while processing any non-Status initscript action, the initscript must print an error message and exit with the appropriate non-zero status code:

1:	generic or unspecified error (current practice)
2:	invalid or excess argument(s)
3:	unimplemented feature (for example, "reload")
4:	user had insufficient privilege
5:	program is not installed
6:	program is not configured
7:	program is not running
8-99:	reserved for future LSB use
100-149:	reserved for distribution use
150-199:	reserved for application use
200-254:	reserved

EPEL does not currently define any distribution specific status codes.

Other Actions

You are not prohibited from adding other commands, but you should list all commands which you intend to be used interactively to the usage message.

Functions in /etc/init.d/functions

Here are some commonly used functions provided by /etc/init.d/functions:

daemon function

Starts a daemon, if it is not already running. Does other useful things like keeping the daemon from dumping core if it terminates unexpectedly.

daemon  [ --check <name> ]  [ --user <username>] 
        [+/-nicelevel]  program [arguments]  [&] 

        --check <name>:
           Check that <name> is running, as opposed to simply the
           first argument passed to daemon().
        --user <username>:
           Run command as user <username>

killproc function

Sends a signal to the program; by default it sends a SIGTERM, and if the process doesn't die, it sends a SIGKILL a few seconds later. It also tries to remove the pidfile, if it finds one.

killproc program [signal] 

pidofproc function

Tries to find the pid of a program; checking likely pidfiles, and using the pidof program. Used mainly from within other functions in this file, but also available to scripts.

pidofproc program

status function

Prints status information. Assumes that the program name is the same as the servicename.

status program

Note that this is different from the Status action, this is a function that is usually used when the Status action is executed:

case "$1" in

...

  status)
        status hcid
        RETVAL=$?
        ;;

...