Initscripts policy

From Rosalab Wiki
Jump to: navigation, search
Daemon processes often need an initscript to be started at boot time. In order to ease the integration and the management of the service, you need to follow some rules.

Initscript usage

To manage initscripts, you should use service and chkconfig.

service

service is a shell script used to start and stop services, either standalone or run with xinetd. Usage is simple, just use

service <name_of_service> <actiont>

where action can be one of start, stop, status, restart, reload (same as restart if the package doesn't support on-the-fly reloading) or anything the script can support.

Chkconfig

Chkconfig is used to manage the /etc/rc?.d/* and xinetd config files. It removes and adds symlinks in order to start or stop a service at boot for a given runlevel. You should use it to add your script to the system. Macros are provided to wrap the call to chkconfig.

An example

Here is a generic example. You can find others in /etc/init.d/. It should be quite easy to adapt, as you only need to change the variable DAEMON_NAME.

#!/bin/sh
#
### BEGIN INIT INFO
# Provides: some_daemon
# Required-Start: $network
# Required-Stop: $network
# Default-Start: 3 4 5
# Short-Description: nothing
# Description: some_daemon is nothing.
#              Really, nothing.
### END INIT INFO

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

DAEMON_NAME=some_daemon
DAEMON_PROCESS=some_daemon
DAEMON_BINARY=some_daemon
LOCK_FILE=/var/lock/subsys/$DAEMON_NAME
RETVAL=0

# default option, they can be overriden in /etc/sysconfig/$DAEMON_NAME
# of course, you can place what you want.
OPTIONS=
PORT=1234
# this file should be commented, with proper pointer to the doc, and you
should use
# more than one line of option, if possible.
[ -f /etc/sysconfig/$DAEMON_NAME ] && . /etc/sysconfig/$DAEMON_NAME


# here, you can do what you want with the option

start() {
    # if you cannot start the daemon since something is missing ( like a
path that cannot be set by default
    # , place the test here
    # if [ -z "$SOME_VAR" ]; then
    #     echo "You need to set $SOME_VAR in /etc/sysconfig/$DAEMON_NAME"
    #     RETVAL=1
    #     return
    # fi

    [ -f $LOCK_FILE ] && return

    echo -n "Starting $DAEMON_NAME: "
    # use --user to run the daemon under the specified uid
    daemon $DAEMON_BINARY $OPTIONS -p $PORT
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && touch $LOCK_FILE
}

stop() {
    echo -n "Shutting down $DAEMON_NAME: "
    killproc $DAEMON_PROCESS
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && rm -f $LOCK_FILE
}

reload() {
    echo -n "Reloading $DAEMON_NAME configuration: "
    killproc $DAEMON_PROCESS -HUP
    RETVAL=$?
    echo
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status $DAEMON_PROCESS
        RETVAL=$?
        ;;
    reload)
        reload
        ;;
    restart)
        stop
        start
        ;;
    condrestart)
        if [ -f $LOCK_FILE ]; then
            stop
            start
        fi
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|reload|condrestart|status}"
        RETVAL=1
esac

exit $RETVAL

The header

The initscript header should use LSB tags ( chkconfig headers are deprecated and should not be used anymore). See Initscript Header Format for more details.

First part - Configuration

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

DAEMON_NAME=some_daemon
DAEMON_PROCESS=some_daemon
DAEMON_BINARY=some_daemon
LOCK_FILE=/var/lock/subsys/$DAEMON_NAME
RETVAL=0

# default option, they can be overriden in /etc/sysconfig/$DAEMON_NAME
# of course, you can place what you want.
OPTIONS=
PORT=1234
# this file should be commented, with proper pointer to the doc, and you
should use
# more than one line of option, if possible.
[ -f /etc/sysconfig/$DAEMON_NAME ] && . /etc/sysconfig/$DAEMON_NAME

# here, you can do what you want with the option

The first part deals with utility functions provided by /etc/rc.d/init.d/functions/.

The second part deals with useful script global variables. $DAEMON_NAME is used for providing user feedback, $DAEMON_BINARY is used to actually launch the service, and $DAEMON_PROCESS to communicate with it once launched. Depending of the service, they may be different or identical. RETVAL is used for action result status.

The third part deals with service configuration. If the service accepts some options, you should place a configuration file in /etc/sysconfig/. In order to ease the administrator duty and prevent namespace clashes, it should have the same name as the service. Placing default values in the script and in the config file is more robust than default value in the config file only.

Second part - Primitives

Using functions for defining primitives, such as start and stop allows to restart the service without reinvocating the script itself. Each of these function should set RETVAL.

A typical script needs the following functions:

start() {
    # if you cannot start the daemon since something is missing ( like a
path that cannot be set by default
    # , place the test here
    # if [ -z "$SOME_VAR" ]; then
    #     echo "You need to set $SOME_VAR in /etc/sysconfig/$DAEMON_NAME"
    #     RETVAL=1
    #     return
    # fi

    [ -f $LOCK_FILE ] && return

    echo -n "Starting $DAEMON_NAME: "
    # use --user to run the daemon under the specified uid
    daemon $DAEMON_BINARY $OPTIONS -p $PORT
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && touch $LOCK_FILE
}

This function is used to start the service. You have to take care of the lock before launching, to avoid having several instances running simultaneously. The "daemon" function takes care of everything (using libsafe, creating a pidfile, etc.), and should return OK only if the startup succedeed. However, some daemons do not crash at the beginning, like older versions of openldap, and still show OK even if they do not work.

stop() {
    echo -n "Shutting down $DAEMON_NAME: "
    killproc $DAEMON_PROCESS
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && rm -f $LOCK_FILE
}

This function is used to stop the service. The "killproc" function takes care of sending SIGSTOP (or any other given signal) to the process. Then you have to remove the lock file.

reload() {
    echo -n "Reloading $DAEMON_NAME configuration: "
    killproc $DAEMON_PROCESS -HUP
    echo
}

This function is used to reload the service, if the daemon supports it.

Last part - Handling arguments

 
case "$1" in
       .....
esac
exit $RETVAL

A typical script accepts the following actions:

start

Call start() function.

stop

Call stop() function.

status

Call status() function.

restart

    restart)
        stop
        start
        ;;

This action simply call stop and start, without checking if the service is currently running.

condrestart

    condrestart)
        if [ -f $LOCK_FILE ]; then
            stop
            start
        fi
        ;;

This action call stop and start, but only if the service is currently running.

reload

Just call reload() functions, if present. Otherwise, you may as well handle "reload" as "restart"

default action

    *)
        echo "Usage: $0 {start|stop|restart|reload|condrestart|status}"
        RETVAL=1

If nothing else matches the action specified, the script should show an error message, and return an exitvalue of 1 to show there is an error. If you add some actions do not forget to add them in the error message. Bash completion uses a regexp matching "Usage: . {.'}", so do not change the format of the messages.

The daemon function

The "daemon" function is defined in /etc/init.d/functions. Right now, there are some arguments to pass:

  • --user <uid>: to run the daemon under the specified UID
  • --check <name>: to define the name of file with the pid of the process to check (in /var/run/$name.pid )
  • +/-[0-9]: to add a nice level. the nice level can also be set in the config file, with the NICELEVEL variable

Integration in a package

In order to integrate your services in a package, you should be sure of the following points:

File naming

The filename should not contain a dot, as chkconfig will not take it into account. And, it should have the same name as the package, in lower case. The file should be in the directory /etc/rc.d/init.d/, which is linked by /etc/init.d. An rpm macro exists for this path, %_initrddir.

/home/misc $ rpm --eval %_initrddir
/etc/rc.d/init.d

Files permissions

The script should be runnable by root, and therefore, the permissions should be rwxr-xr-x. Msec resets the permissions once installed.

Macro in %preun and %post

In order to register your service, you need to place 2 macros, one in the %preun , one in the %post section :

# service_name is the name of the script
%post
%_post_service service_name

%preun
%_preun_service service_name

See the scripts /usr/share/rpm-helper/add-service and del-service. They take care of adding it to the boot sequence, and stopping or restarting the service if needed.

If the service should not be started by default because it misses some configuration, add the logic for checking in the initscript, and provide documentation regarding the setup to finish.

RPM requirements

As the previous macro use rpm-helper script, you need to be sure that rpm-helper is installed before the scripts are run. All you need is to add these tags to the spec:

Requires(post): rpm-helper
Requires(preun): rpm-helper

These indicate that the %post and %preun scripts need rpm-helper installed to be able to run.

Rpmlint errors

rpmlint uses a dedicated module to check the initscript. You can find it in the rpmlint package, file /usr/share/rpmlint/InitScriptCheck.py.

LSB Compliance

The Linux Standard Base proposes the following specs: http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html

Localisation issues

ROSA uses gprintf, which calls gettext with the localisation domain of "initscripts" to get the localisation of the first string passed to gprintf, replacing macros (such as %s ) with the remaining arguments. To reduce the load for localisers, it is best to ensure that gprintf calls keep the first string constant, so use:

gprintf "Starting %s: " $DAEMON_NAME

instead of:

gprintf "Starting $DAEMON_NAME: "

This allows just one translation to be maintained for each similar call. White space can have an effect too ... so be careful to ensure the string is kept exactly the same.

Note that the usage of "echo" calls will be replaced with gprintf calls (by /usr/share/spec-helper/gprintify.py which is called from /usr/share/spec-helper/spec-helper which is called from /usr/lib/rpm/brp-mandrake ). Sometimes, gprintify can mess up your good use of gprintf ... in which case you should export DONT_GPRINTIFY in the %install section of your spec file to prevent gprintify.py from being called.

Initscript Header format

Initscripts used in ROSA Linux should be described using LSB headers instead of chkconfig headers. Adding LSB headers will allow having a robust services dependency check, and to provide a base for parallelized initialization, so packagers are all encouraged to add LSB headers to their packages.

Migration to LSB headers

Let's take the previous dm init script as an example.

It contained the following comments:
# chkconfig: 5 30 09
# description: This startup script launches the graphical display manager.

LSB comments

We can add LSB headers in a block delimited by the following lines:

### BEGIN INIT INFO
### END INIT INFO

Facility provides

Each initscript should provide a facility name. The services should be named preferably using this policy: http://www.lanana.org/lsbreg/init/init.txt

Facilities that begin with a $ sign are reserved system facilities, such as $network. A complete list is available here: http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/facilname.html

# Provides: dm

Start dependencies

The services required by an initscript should be described using the Required-Start (mandatory service) or Should-Start (optional service) tags.

# Required-Start: xfs
# Should-Start: $network harddrake

Required services

Required-Start means that the listed services must be available for this service. The initscript system will make sure that they are ( chkconfig will enforce the dependency).

Optional services

Should-Start means that the listed services should be available for this service if possible. If an optional service is enabled in this runlevel, it will be started before. If it is not enabled, its start will not be enforced by the initscript system.

Stop dependencies

If another service has to be available during stop, the mandatory dependency should be described using a Required-Stop tag:

# Required-Stop: xfs

The same is available for optional dependencies, using the Should-Stop flag.

Runlevels

You can specify which runlevels the service should be started in using the Default-Start tag.

# Default-Start: 5

Descriptions

Descriptions have to be provided using the Short-Description and Description (potentially multi-line) tags.

# Short-Description: Launches the graphical display manager
# Description: This startup script launches the graphical display
# manager.

Final result

The dm initscript will finally end up with the following LSB header:

### BEGIN INIT INFO
# Provides: dm
# Required-Start: xfs
# Required-Stop: xfs
# Should-Start: $network harddrake
# Default-Start: 5
# Short-Description: Launches the graphical display manager
# Description: This startup script launches the graphical display manager.
### END INIT INFO

Interactive initscripts

Some initscripts request some user input, such as harddrake when a new device is found. For such scripts, LSB asks to use a tag in X-implementor-extension format. In ROSA Linux, X-ROSA-Interactive is used; for compatibility reasons, X-Mandriva-Interactive is also acceptable.

# X-ROSA-Interactive

References

For a more complete reference about the LSB headers, please see:


Note:
This Policy is based on the Mandriva Initscripts Policy.