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