Initscripts policy
Содержание
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:
- LSB System Initialization
- openSUSE: Packaging init scripts
- Linux Assigned Names And Numbers Authority