From Mageia wiki
Jump to: navigation, search
This page has become outdated and can no longer be trusted!

It requires improvements. If you want to improve it, simply log in and click on the Edit tab.

Please remove this {{Outdated}} template when you're sure this page is completely up-to-date and trustworthy again.


Working with Initscripts

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> <action>

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 succeded. 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 InitScriptCheck.py.

LSB Compliance

The Linux Standard Base proposes the following specs: http://refspecs.freestandards.org/LSB_2.0.0/LSB-Core/LSB-Core/iniscrptact.html

Localisation issues

Mageia 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 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, this facility name is the name of the package/program. 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://www.linuxbase.org/spec/booksets/LSB-Core-generic/LSB-Core-generic/facilname.html So if you have a package foobar, the facility provide should be

# Provides: foobar

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. Since this will be quite Mageia specific, we will use the **X-Mageia-Interactive** tag (the LSB asks for an **X-implementor-extension** format).

# X-Mageia-Interactive

Mageia should probably request a vendor tag from the Linux Assigned Names And Numbers Authority, see: http://www.lanana.org/lsbreg/providers/providers.txt

References

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

This page is available under the Creative Commons Attribution-ShareAlike 2.5 License

Authors

  • Vincent Danen
  • Dick Gevers
  • Christian Welvaart
  • roudoud0u
  • Daniel Kreuter
  • Rémy Clouard