Chapter 7

Customizing the boot process
 

 
 
In this chapter:
 
 
* Discovering where customizations can be made
* Analyzing rc-scripts
* Integrating new rc-scripts into the boot scheme
* Setting up new runlevels
 
 
 
Now that we've covered SuSE's standard setup of the booting process, it's time to show you ways to customize this feature. How you choose to customize the boot process depends on what you want to achieve and what your needs are. A couple of options exist for adding functionality.
 
* /sbin/init.d/boot.local
As we've seen before, this script is executed during the machine's boot process and is meant to be customized for local needs. However, this script is executed only once during booting, and network services are not available at this point. General and somewhat basic tasks are listed here. As an example, you can write to /proc/sys/* here to customize kernel parameters.
* /sbin/init.d/boot.d
The same as boot.local. If you have something more complicated than simply echoing parameters to files in /proc, you should consider putting them in a separate file. This keeps things modular and makes updates easier.
All scripts found in /sbin/init.d/boot.d will be executed during the initial boot.
* new rc-script
Writing a new rc-script and linking it to the runlevel you need the service in is probably the best way to add new services or customized functionality to your machine.
 
 
7.1 Writing rc-scripts
 

Writing rc scripts There is no magic in writing rc-scripts. They are basically very small scripts that must fulfill certain criterias. If you have a little experience in writing shell scripts, you will be fine.
 
In Chapter 6, you saw that the job of an rc-script is to start and stop a particular service. You can of course have two scripts: one to start the service and one to stop it.
 

TIP On the surface, using two scripts sounds logical but is not recommended. All scripts provided by SuSE do both, and so should yours. When switching runlevels, rc calls the scripts with the parameter start to begin a service and stop to halt a service. This makes it pretty obvious what the total must have requirement for rc-scripts is: start your service if you are called with start, and33 end your service when called with stop. Furthermore, there are some additional parameters for the script that governs its actions. None of this is mandatory. If the script handles start and stop in the prescribed way, it will work fine for simple services.
 
 
With the 6.0 release, SuSE has changed the structure slightly and recommends that rc-scripts match these conditions:
 
 
* Read settings from /etc/rc.config.
The file /etc/rc.config is SuSE's central configuration file. To keep things clear, it is a good idea to put needed settings in this file and to read them before starting the service. If your new service needs more than just a few variables, it might be a good idea to create a separate file in /etc/rc.config.d, which is reserved for this service. SuSE is cleaning up this large and somewhat unwieldy file and splitting it up into service-dependent files in this directory.
One variable you should consider is START_FOO , where FOO denotes the name of47 your service. Most SuSE scripts use this variable to determine whether they should be started at system boot or not. If this variable is set to yes, the scripts only power up their service. In the 6.0 release, this setting takes effect only if the script is called during system boot by /sbin/init.d/rc. If you call the script during runtime, it starts the service independently of the value of the START_* variable in /etc/rc.config. Later, you'll see how this can be done in the example.
* Handle additional parameters besides start and stop.
It's quite convenient to allow some services to reload their configuration, to report their status, or to restart themselves. Because no standardized way of doing this exists, the SuSE rc-scripts act as wrappers to provide a standardized way of doing this. If you want to do this, your script should handle these parameters:
 
* restart: Halts the service and starts it again. The easiest way to do this is to call yourself with stop and start.
* reload: Lets the service reread all its configuration files. Some applications support this feature by reloading their configuration files when they receive a special signal (usually SIGHUP). If your service doesn't do this, the system will behave as if the command were restart.
* status: Checks to see whether the service is available. The script should return with exit status 0 if the service is available, with -1 if it is not.
* probe: Checks to see whether the configuration files have changed since the service has been started and prints reload if this is the case. This function makes it easy for other scripts to refresh the service you provide, if necessary.
 
* Print the list of supported parameters.
This is completely optional but quite nice to have. For example, someone calls your script with the parameter reload, but you don't support this feature. This branch will tell the user what parameters are valid. This feature is accessed when the requested action or argument is not defined within the program or script.
 
 
SuSE provides an example rc-script in /sbin/init.d/skeleton. It shows you just how easy it is to implement all the features we've listed.
 
The script begins with a comment section that states its purpose. Change the information to reflect your service as we go along:
 
 
#! /bin/sh 
# Copyright (c) 1995-1998 SuSE GmbH Nuernberg, Germany. 
# 
# Author:  
# 
# /sbin/init.d/<skeleton> 
# 
#   and symbolic its link 
# 
# /sbin/rc<skeleton> 
# 
 
 
Next, the major configuration file, /etc/rc.config, is read so that all settings made in this file can be used in the script. If you decide that you need a separate configuration file, you may want to include this one, too. In any case, include /etc/rc.config, because it defines some variables you will need in your script.
 
 
. /etc/rc.config 
 
 
Now it's up to the script to decide whether the script should be executed at all. If this script is called by rc, the value of START_FOO should be used, but if the script is called from the command line, it should be ignored.
 
rc doesn't call the script directly; rather, it uses the link in the runlevel subdirectory. The decision on which to run is made by looking at the name of the called instance of the script. If the name starts with an S or K, followed by a two digit number, it assumes that rc called it, and it exits if START_FOO is not set to yes.
 
Otherwise, it goes on by temporarily setting it to yes, in case this variable is checked somewhere else in the script.
 
 
# Determine the base and follow a runlevel link name. 
base=${0##*/} 
link=${base#*[SK][0-9][0-9]} 

 
# Force execution if not called by a runlevel directory. test $link = $base && START_FOO=yes test "$START_FOO" = yes || exit 0
 
 
The next step is to initialize the return variable to $rc_done and enter the case statement to111 decide what to do. The variable $rc_done does not contain the value returned by the script. Its content is the string, which will be echoed to the screen to indicate whether the service has been started successfully. There are three predefined values in /etc/rc.config:
 
 
* $rc_done
Prints done on the right margin of the screen, to indicate that the process was successful.
* $rc_failed
Prints failed on the right margin of the screen if the procedure was not successful.
* $rc_unused
Prints unused. Use this for disabled services.
* $rc_reset
Turns off all attributes and switches on the standard character set. Used by the Master Resource Control to reset the screen settings.
 
 
 
# The echo return value for success (defined in /etc/rc.config). 
return=$rc_done5 case "$1" in 
 
 
This is the start case. Do whatever is necessary to start your service in this branch of the case statement. In the example, SuSE uses startproc to start the service. The startproc utility checks for all processes of the specified executable and starts it if no processes are found. Note that startproc is designed to start a daemon but not a kernel thread or a program that enables a kernel thread. The startproc utility does not use the process id to search for a process but rather the full path of the corresponding program that is used to identify the executable. You can use this utility or simply start the service yourself after you make sure it's not already running. Even then, more than one invocation may be okay for your kind of service.
 
One thing you should do is note the process id (PID) in the file /var/run/your-service-name.pid. This will make it easier to check whether the service is running and to send signals to it. After the service has been started successfully, you print $return to show the user that everything went well. If an error occurred, set $return to $rc_failed, as described earlier.
 
 
    start) 
        echo -n "Starting service foo" 
        ## Start daemon with startproc(8). If this fails 
        ## the echo return value is set appropriate. 

 
#startproc /usr/sbin/foo || return=$rc_failed
 
echo -e "$return" ;;
 
 
The stop case is almost the same as the start service. Again, SuSE likes to use killproc to do this, because it makes things easy. But, of course, you're welcome to use any method you want.
 
 
    stop) 
        echo -n "Shutting down service foo" 
        ## Stop daemon with killproc(8) and if this fails 
        ## set echo the echo return value. 
        #killproc -TERM /usr/sbin/foo || return=$rc_failed 

 
echo -e "$return" ;;
 
 
The restart case is the easiest. If you have done your job for start and stop, you can just use the example code as provided. It does nothing other than call itself twice with appropriate parameters and sets the return variable according to the return values.
 
 
    restart) 
        ## If first returns OK call the second, if first or 
        ## second command fails, set echo return value. 
        $0 stop  &&  $0 start  ||  return=$rc_failed 
        ;; 
 
 
Reloading the configuration may be tricky. The example lists two ways to do this. As we said earlier, some applications that receive a SIGHUP signal react by reloading their configuration files. This is the first thing that could happen. If your application or daemon doesn't have this feature, and doesn't provide any other method for reloading revised configuration files, the only option is to stop the service and start it again, as shown in the last part of the example.
 
 
    reload) 
        ## Choose ONE of the following two cases: 

 
## First possibility: A few services accept a signal ## to reread the (revised) configuration.
 
#echo -n "Reload service foo" #killproc -HUP /usr/sbin/foo || return=$rc_failed #echo -e "$return"
 
## Exclusive possibility: Some services must be stopped ## and started to force a new load of the configuration.
 
#$0 stop && $0 start || return=$rc_failed ;;
 
 
The next entry is to check whether your service is active. SuSE uses the utility checkproc to do this. You can do it this way, or if you're feeling chatty, give status information on the service if you think that it's useful.
 
 
    status) 
        echo -n "Checking for service foo: " 
        ## Check status with checkproc(8), if process is running 
        ## checkproc will return with exit status 0. 

 
#checkproc /usr/sbin/foo && echo OK || echo No process ;;
 
 
The next step is to check whether the configuration needs to be reloaded. An easy way to do this is to check whether the configuration files have been modified more recently than the PID file of the service in /var/run. This is what SuSE shows in the example. This has certain preconditions to carefully consider. First, of course, the PID file has to exist. Second, the PID file has to be touched when the configuration is reloaded. You may want to take care of this in the start and the reload routines.
 
 
    probe) 
        ## Optional: Probe for the necessity of a reload, 
        ## give out the argument which is required for a reload. 

 
#test /etc/foo.conf -nt /var/run/foo.pid && echo reload ;;
 
 
Last but not least, report which parameters you support. This part of the script will be executed when all the other cases didn't fit the parameter given to the script.
 
 
    *) 
        echo "Usage: $0 {start|stop|status|restart|reload|probe}" 
        exit 1 
        ;; 
esac 
 
 
Now all possible parameters have been covered. If you have special needs, you can add more parameters as you like. For example, backup and restore may be useful for database servers. Or statistics for network services. It's up to you -- what you need and what your service offers.
 
 
# Inform the caller not only verbosely and set an exit status. 
test "$return" = "$rc_done" || exit 1 
exit 0 
 
 
The last thing to do in the rc-script is to return 0 if the script could do whatever it was told to do successfully, or 1 in case of an error. SuSE compares $return with $rc_done and calls exit with the proper value.
 
7.2 Adding an rc-script to a runlevel
 

After your script is ready, you should test it by calling with every possible parameter. It may be too late to fix errors when it's called during the boot process.
 
To add the script to a runlevel, it simply has to be linked into the runlevels subdirectory. The naming scheme was mentioned earlier. You need one link starting with a capital S to start the service when entering the runlevel, and one beginning with capital K to stop it when leaving. The two-digit number provides the order in which the scripts are executed. Scripts with lower numbers are started first. This is true for start and stop scripts. If, for example, your service needs networking, make sure that the number for the start link is higher than the one for the network script, and that the number for the stop link is lower. This ensures that networking is started prior to your service, and that your service will be stopped before the network goes down.
 
You can put links in each runlevel subdirectory you want your service to activate. A service is not limited to one runlevel.
 

7.3 Creating customized runlevels
 

We've seen how we can change the default runlevel, and how to customize a given runlevel by adding new services to it. The remaining task is to create a whole new runlevel.
 
Before you begin, carefully consider why you need to do it. It's not always easy to decide. For example, if you run a database server, you may assume that it would be handy to use runlevel 4 as the database server mode. That's not a bad idea, but do you really need a separate runlevel for the database?
 
To decide, think about the difference between the current runlevels and the one you have in mind. If the difference is that the database is the only addition, then it's not worth having a separate runlevel. You can start and stop the database by calling its rc-script as easily as changing between runlevels. But if you want to have only the database running and none of the other network services such as FTP, HTTP, DNS and SMTP server, then a separate runlevel might make sense. The greater the distance between the standard runlevels and your new runlevel, the more sense it makes to create a new one. Maybe this isn't the case at all. Maybe you want to create a new runlevel just to see how it works. Things don't always have to make sense or have a purpose. Sometimes it's simply nice to see what is in the realm of possibility.
 

TIP Creating a runlevel isn't a big deal, anyway. The easiest way is to do it is to take an existing runlevel as a template and modify it until it fits your needs.
 
 
Continuing with the database server example, you create runlevel 4 for the database. You want the machine to be networked, but only a few selected services should be active besides the database.
 
The first step is to copy all the links from /sbin/init.d/rc2.d to /sbin/init.d/rc4.d, the subdirectory for runlevel 4:
 
 
# cd /sbin/init.d259  
# cp -d rc2.d/* rc4.d 
 
 
Now you remove everything you don't want to have in this runlevel from /sbin/init.d/rc4.d:
 
 
# cd /sbin/init.d/rc4.d 
# for i in axnet mysql apache apmd inn lpd rwhod sendmail sshd \\ 
nfsserver pcnfsd named routed nfs firewall masquerade serial ; do 
> rm ???$i 
> done 
 
 
One script should be present in each runlevel. This is /sbin/init.d/init.d/boot.setup. Its job is to set various terminal parameters such as console font and keyboard parameters when switching from single-user mode to another runlevel.
 
The next step is to enable the runlevel in /etc/inittab by removing the comment character at the line responsible for runlevel 4. The line should look like this:
 
 
l4:4:wait:/sbin/init.d/rc 4272 
 
 
There is a second thing you have to pay attention to while editing the inittab. The getty processes for the console and for modems are spawned by init directly out of inittab. Because they are preset to be active only in runlevels one, two and three, you have to add them to the new runlevel; otherwise, you won't be able to log onto the system after you enter this runlevel:
 
 
1:1234:respawn:/sbin/mingetty --noclear tty1 
2:1234:respawn:/sbin/mingetty tty2 
3:1234:respawn:/sbin/mingetty tty3 
4:1234:respawn:/sbin/mingetty tty4 
5:1234:respawn:/sbin/mingetty tty5 
6:1234:respawn:/sbin/mingetty tty6 
 
 
The last step is to add your rc-scripts to the new runlevel:
 
 
# cd /sbin/init.d/rc4.d 
# ln -s ../myrcscript S20myrcsript 
# ln -s ../myrcscript K20myrcsript 
# ln -s ../mysecondrcscript S21mysecondrcsript 
# ln -s ../mysecondrcscript K19mysecondrcsript 
 
 
Pay attention to the numbers you use, and make sure that all services you need for the database are powered up before your script gets executed.
 
Now check whether your runlevel works by changing into it, using init:
 
 
# init 4 
 
CAUTION If something goes wrong, you can change into the runlevel you came from, fix what went wrong, and try again. If everything is okay, you are done! If you want, you can make this your default runlevel, which means that this runlevel will be entered on system boot. However, you should be certain that everything is okay before doing this.
 
 
 
Summary:
  There are certain ways to customize the boot process in SuSE Linux: adding simple commands to the existing rc-scripts, creating new rc-scripts and modifing runlevel or setting up new runlevels.
 
The rc-scripts are very structured and have to fullfill some reqirements in order to fit into the SuSE boot scheme. They must respect the parameters start and stop to enable and disable the service for which they are responible. They read settings from the system-wide configuration file /etc/rc.config and should offer the possibility to be disabled within this file.
 
New runlevels are seldom needed. Most tasks can be made easier by modifying an existent runlevel. Creating a new one is simple, especially when an existing runlevel is used as a template and modified in incremental stages.
--
Back Up Contents Next
--

Copyright (c) 1999 by Terrehon Bowden and Bodo Bauer
To contact the author please sent mail to bb@bb-zone.com