This section discusses the Unix initialization files: command scripts that perform most of the work associated with taking the system to multiuser mode. Although similar activities take place under System V and BSD, the mechanisms by which they are initiated are quite different. Of the systems we are considering, FreeBSD follows the traditional BSD style, AIX is a hybrid of the two, and all the other versions use the System V scheme.
Understanding the initialization scripts on your system is a vital part of system administration. You should have a pretty good sense of where they are located and what they do. That way, you’ll be able to recognize any problems at boot time right away, and you’ll know what corrective action to take. Also, from time to time, you’ll probably need to modify them to add new services (or to disable ones you’ve decided you don’t need). We’ll discuss customizing initialization scripts later in this chapter.
Although the names, directory locations, and actual shell program code for system initialization scripts varies widely between BSD-based versions of Unix and those derived from System V, the activities accomplished by each set of scripts as a whole differs in only minor ways. In high-level terms, the BSD boot process is controlled by a relatively small number of scripts in the /etc directory, with names beginning with rc, which are executed sequentially. In contrast, System V executes a large number of scripts (as high as 50 or more), organized in a three-tiered hierarchy.
Tip
Unix initialization scripts are written using the Bourne shell (/bin/sh). As a convenience, Bourne shell programming features are summarized in Appendix A.
Aspects of the boot process are also controlled by configuration files that modify the operations of the boot scripts. Such files consist of a series of variable definitions that are read in at the beginning of a boot script and whose values determine which commands in the script are executed. These variables can specify things like whether a subsystem is started at all, the command-line options to use when starting a daemon, and the like. Generally, these files are edited manually, but some systems provide graphical tools for this purpose. The dialog on the left in Figure 4-1 shows the utility provided by SuSE Linux 7 as part of its YaST2 administration tool.
The dialog on the right shows the new run-level editor provided by
YaST2 on SuSE 8 systems. In this example, we are enabling inetd
in run levels 2, 3, and 5.
The organization of system initialization scripts on traditionalBSD systems such as FreeBSD is the essence of simplicity. In the past, boot-time activities occurred via a series of only three or four shell scripts, usually residing in /etc, with names beginning with rc. Under FreeBSD, this number has risen to about 20 (although not all of them apply to every system).
Multiuser-mode system initialization under BSD-based operating
systems is controlled by the file /etc/rc. During a boot to multiuser mode,
init
executes the rc
script, which in turn calls other
rc.* scripts. If the system is
booted to single-user mode, rc
begins executing when the single-user shell is exited.
The boot script configuration files /etc/default/rc.conf, /etc/rc.conf, and /etc/rc.conf.local control the functioning of the rc script. The first of these files is installed by the operating system and should not be modified. The other two files contain overrides to settings in the first file (although the latter is seldom used).
Here are some example entries from /etc/rc.conf:
accounting_enable="YES" check_quotas="YES" defaultrouter="192.168.29.204" hostname="ada.ahania.com" ifconfig_xl0="inet 192.168.29.216 netmask 255.255.255.0" inetd_enable="YES" nfs_client_enable="YES" nfs_server_enable="YES" portmap_enable="YES" sendmail_enable="NO" sshd_enable="YES"
This file enables the accounting, inetd
, NFS, portmapper
, and ssh
subsystems and disables sendmail
. It causes disk quotas to be
checked at boot time, and specifies various network settings,
including the Ethernet interface.
The system initialization scripts on a System V-style system are much more numerous and complexly interrelated than those under BSD. They all revolve around the notion of the current system run level, a concept to which we now turn.
At any given time, a computer system can be in one of three conditions: off (not running, whether or not it has power), single-user mode, or multiuser mode (normal operating conditions). These three conditions may be thought of as three implicitly defined system states.
System V-based systems take this idea to its logical extreme and explicitly define a series of system states, called run levels, each of which is designated by a one-character name that is usually a number. At any given time, the system is at one of these states, and it can be sent to another one using various administrative commands. The defined run levels are listed in Table 4-2.
Table 4-2. System V-style run levels
In most implementations, states 1 and s/S are not distinguished in practice, and not all states are predefined by all implementations. State 3 is the defined normal operating mode for networked systems. In practice, some systems collapse run levels 2 and 3, supporting all networking functions at run level 2 and ignoring run level 3, or making them identical so that 2 and 3 become alternate names for the same system state. We will use separate run levels 2 and 3 in our examples, making run level 3 the system default level.
Note that the pseudo-run levels (a, b, c, and q/Q) do not
represent distinct system states, but rather function as ways of
getting init
to perform certain
tasks on demand.
Table 4-3 lists the run levels defined by the various operating systems we are considering. Note that FreeBSD does not use run levels.
The command who -r
may be used to display the current run level and the
time it was initiated:
$ who -r
. run level 3 Mar 14 11:14 3 0 S Previous run level was S.
The output indicates that this system was taken to run level 3 from run level S on March 14. The 0 value between the 3 and the S indicates the number of times the system had been at the current run level immediately prior to entering it this time. If the value is nonzero, it often indicates previous unsuccessful boots.
On Linux systems, the runlevel
command lists the previous and
current run levels.
Now for some concrete examples. Let’s assume a system whose normal, everyday system state is state 3 (networked multiuser mode). When you boot this system after the power has been off, it moves from state 0 to state 3. If you shut the system down to single-user mode, it moves from state 3 through state 0 to state s. When you reboot the system, it moves from state 3 through state 6 and state 0, and then back to state 3.[12]
The telinit
utility may be used to change the current
systemrun level. Its name comes from the fact that it
tells the init
process what to do next. It takes the
new run level as its argument. The following command tells the
system to reboot:
# telinit 6
Tru64 does not include the telinit
command. However, because telinit
is just a link to init
that has been given a different name
to highlight what it does, you can easily create it if
desired:
#cd /sbin
#ln init telinit
You can also just use init
itself: init 6
.
AIX also omits the telinit
command, since it does not implement run levels in the usual
manner.
System V-style systems organize the initialization proc ess in a much more complex way, using three levels of initialization files:
/etc/inittab, which is
init
’s configuration file.A series of primary scripts named rc n (where n is the run level), typically stored in /etc or /sbin.
A collection of auxiliary, subsystem-specific scripts for each run level, typically located in subdirectories named rcn.d under /etc or /sbin.
In addition, some systems also provide configuration files that define variables specifying or modifying the functioning of some of these scripts.
On a boot, when init
takes
control from the kernel, it scans its configuration file, /etc/inittab, to determine what to do
next. This file defines init
’s
actions whenever the system enters a new run level; it contains
instructions to carry out when the system goes down (run level 0),
when it boots to single-user mode (run level S), when booting to
multiuser mode (run level 2 or 3), when rebooting (run level 6), and
so on.
Each entry in the inittab configuration file implicitly defines a process to be run at one or more run levels. Sometimes, this process is an actual daemon that continues executing as long as the system remains in a given run level. More often, the process is a shell script that is executed when the system enters one of the run levels specified in its inittab entry.
When the system changes run levels, init
consults the inittab file to
determine the processes that should be running at the new run level.
It then kills all currently running processes that should not be
running at the new level and starts all processes specified for the
new run level that are not already running.
Typically, the commands to execute at the start of each run
level are contained in a script named rc n,
where n is the run level number
(these scripts are usually stored in the /etc directory). For example, when the
system moves to run level 2, init
reads the /etc/inittab file,
which tells it to execute rc2.
rc2 then executes the scripts
stored in the directory /etc/rc2.d. Similarly, when a running
system is rebooted, it moves first from run level 2 to run level 6,
a special run level that tells the system to shut down and
immediately reboot, where it usually executes rc0 and the scripts in /etc/rc0.d, and then changes to run level
2, again executing rc2 and the
files in /etc/rc2.d. A few
systems use a single rc script
and pass the run level as its argument: rc
2
.
A simple version of the System V rebooting process is illustrated in Figure 4-2 (assuming run level 2 as the normal operating state). We will explain all of the complexities and eccentricities in it as this section progresses.
As we’ve seen, top-level control of changing system
states is handled by the file /etc/inittab, read by init
. This file contains entries that tell
the system what to do when it enters the various defined system
states.
Entries in the inittab have the following form:
cc
:levels
:action
:process
where cc is a unique,
case-sensitive label identifying each entry (subsequent entries with
duplicate labels are ignored).[13] levels is a list
of run levels to which the entry applies; if it is blank, the entry
applies to all of them. When the system enters a new state, init
processes all entries specified for
that run level in the inittab
file, in the order they are listed in the file.
process is the command to
execute, and action indicates
how init
is to treat the process
started by the entry. The most important action keywords are the following:
- wait
Start the process and wait for it to finish before going on to the next entry for this run state.
- respawn
Start the process and automatically restart it when it dies (commonly used for
getty
terminal line server processes).- once
Start the process if it’s not already running. Don’t wait for it.
- boot
Execute entry only at boot time; start the process but don’t wait for it.
- bootwait
Execute entry only at boot time and wait for it to finish.
- initdefault
Specify the default run level (the one to reboot to).
- sysinit
Used for activities that need to be performed before
init
tries to access the system console (for example, initializing the appropriate device).- off
If the process associated with this entry is running, kill it. Also used to comment out unused terminal lines.
Comments may be included on separate lines or at the end of any entry by preceding the comment with a number sign (#).
Here is a sample inittab file:
# set default init level -- multiuser mode with networking is:3:initdefault: # initial boot scripts fs::bootwait:/etc/bcheckrc </dev/console >/dev/console 2>&1 br::bootwait:/etc/brc </dev/console >/dev/console 2>&1 # shutdown script r0:06:wait:/etc/rc0 >/dev/console 2>&1 </dev/console # run level changes r1:1:wait:/sbin/shutdown -y -iS -g0 >/dev/console 2>&1 r2:23:wait:/etc/rc2 >/dev/console 2>&1 </dev/console r3:3:wait:/etc/rc3 >/dev/console 2>&1 </dev/console pkg:23:once:/usr/sfpkg/sfpkgd # start daemon directly # off and reboot states off:0:wait:/sbin/uadmin 2 0 >/dev/console 2>&1 </dev/console rb:6:wait:/sbin/uadmin 2 1 >/dev/console 2>&1 </dev/console # terminal initiation co:12345:respawn:/sbin/getty console console t0:234:respawn:/sbin/getty tty0 9600 t1:234:respawn:/sbin/getty tty1 9600 t2:234:off:/sbin/getty tty2 9600 # special run level acct:a:once:/etc/start_acct # start accounting
This file logically consists of seven major sections, which we’ve separated with blank lines. The first section, consisting of a single entry, sets the default run level, which in this case is networked multiuser mode (level 3).
The second section contains processes started when the system
is booted. In the sample file, this consists of running the
/etc/bcheckrc and /etc/brc preliminary boot scripts
commonly used on System V systems in addition to the rcn structure. The bcheckrc
script’s main function is to
prepare the root filesystem and other critical filesystems like
/usr and /var. Both scripts are allowed to
complete before init
goes on to
the next inittab entry.
The third section of the sample inittab file specifies the commands to
execute whenever the system is brought down, either during a system
shutdown and halt (to run level 0) or during a reboot (run level 6).
In both cases, the script /etc/rc0 is executed, and init
waits for it to finish before
proceeding.
The fourth section, headed “run level changes,” specifies the
commands to run when system states 1, 2, and 3 begin. For state 1,
the shutdown
command listed in
the sample file takes the system to single-user mode. Some systems
execute the rc1 initialization
file when the system enters state 1 instead of a shutdown
command like the one
above.
For state 2, init
executes
the rc2 initialization script;
for state 3, init
executes
rc2 followed by rc3. In all three states, each process is
allowed to finish before init
goes on to the next entry. The final entry in this section starts a
process directly instead of calling a script. The sfpkgd
daemon is started only once per run
level, when the system first enters run level 2 or 3. Of course, if
the daemon is already running, it will not be restarted.
The fifth section specifies commands to run (after rc0) when the system enters run levels 0
and 6. In both cases, init
runs
the uadmin
command, which
initiates system shutdown. The arguments to uadmin
specify how the shutdown is to be
handled. Many modern systems have replaced this legacy command,
folding its functionality into the shutdown
command (as we’ll see shortly).
Of the System V systems we are considering, only Solaris still uses
uadmin
.
The sixth section initializes the system’s terminal lines via
getty
processes (which are
discussed in Chapter 12).
The final section of the inittab file illustrates the use of
special run level a. This entry is used only when a telinit a
command is executed by the
system administrator, at which point the start_acct script is run. The run levels
a, b, and c are available to be defined as needed.
As we’ve seen, init
typically executes a script named rc n
when entering run level n
(rc2 for state 2, for example).
Although the boot (or shutdown) process to each system state is
controlled by the associated rc
n script, the actual commands
to be executed are stored in a series of files in the subdirectory
rcn.d. Thus, when the system enters state 0,
init
runs rc0 (as directed in the inittab file), which in turn runs the
scripts in rc0.d.
The contents of an atypically small rc2.d directory (on a system that doesn’t use a separate run level 3) are listed below:
$ ls -C /etc/rc2.d
K30tcp S15preserve S30tcp S50RMTMPFILES
K40nfs S20sysetup S35bsd S75cron
S01MOUNTFSYS S21perf S40nfs S85lp
All filenames begin with one of two initial filename characters (S and K), followed by a two-digit number, and they all end with a descriptive name. The rcn scripts execute the K-files (as I’ll call them) in their associated directory in alphabetical order, followed by the S-files, also in alphabetical order (this scheme is easiest to understand if all numbers are the same length; hence the leading zeros on numbers under 10). Numbers do not need to be unique.
In this directory, files would be executed in the order K30tcp, K40nfs, S01MOUNTFSYS, S15preserve, and so on, ending with S75cron and S85lp. K-files are generally used to kill processes (and perform related functions) when transitioning to a different state; S-files are used to start processes and perform other initialization functions.
The files in the rc*.d subdirectories are usually links to those files in the subdirectory init.d, where the real files live. For example, the file rc2.d/S30tcp is actually a link to init.d/tcp. You see how the naming conventions work: the final portion of the name in the rcn.d directory is the same as the filename in the init.d directory.
The file K30tcp is also a link to init.d/tcp. The same file in init.d is used for both the kill and start scripts for each subsystem. The K and S links can be in the same rcn.d subdirectory, as is the case for the TCP/IP initialization file, or in different subdirectories. For example, in the case of the print spooling subsystem, the S-file might be in rc2.d while the K-file is in rc0.d.
The same file in init.d can be put to both uses because it is passed a parameter indicating whether it was run as a K-file or an S-file. Here is an example invocation, from an rc2 script:
# If the directory /etc/rc2.d exists, # run the K-files in it ... if [ -d /etc/rc2.d ]; then for f in /etc/rc2.d/K* { if [ -s ${f} ]; then # pass the parameter "stop" to the file /bin/sh ${f} stop fi } # and then the S-files: for f in /etc/rc2.d/S* { if [ -s ${f} ]; then # pass the parameter "start" to the file /bin/sh ${f} start fi } fi
When a K-file is executed, it is passed the parameter stop; when an S-file is executed, it is passed start. The script file will use this parameter to figure out whether it is being run as a K-file or an S-file.
Here is a simple example of the script file, init.d/cron, which controls the cron
facility. By examining it, you’ll be
able to see the basic structure of a System V initialization
file:
#!/bin/sh case $1 in # commands to execute if run as "Snncron" 'start') # remove lock file from previous cron rm -f /usr/lib/cron/FIFO # start cron if executable exists if [ -x /sbin/cron ]; then /sbin/cron echo "starting cron." fi ;; # commands to execute if run as "Knncron" 'stop') pid=`/bin/ps -e | grep ' cron$' | \ sed -e 's/^ *//' -e 's/ .*//'` if [ "${pid}" != "" ]; then kill ${pid} fi ;; # handle other arguments *) echo "Usage: /etc/init.d/cron {start|stop}" exit 1 ;; esac
The first section in the case
statement is executed when the script
is passed start as its first
argument (when it’s an S-file); the second section is used when it
is passed stop, as a K-file. The
start commands remove any old lock file and then start the cron
daemon if its executable is present
on the system. The stop commands figure out the process ID of the
cron
process and kill it if it’s
running. Some scripts/operating systems define additional valid
parameters, including restart
(equivalent to stop then start) and status.
The file /etc/init.d/cron might be linked to both /etc/rc2.d/S75cron and /etc/rc0.d/K75cron. The cron facility is then started by rc2 during multiuser boots and stopped by rc0 during system shutdowns and reboots.
Sometimes scripts are even more general, explicitly testing for the conditions under which they were invoked:
set `who -r` Determine previous run level. if [ $8 != "0" ] The return code of the previous state change. then exit fi case $arg1 in 'start') if [ $9 = "S" ] Check the previous run level. then echo "Starting process accounting" /usr/lib/acct/startup fi ;; ...
This file uses various parts of the output from who -r
:
$ who -r
. run level 2 Mar 14 11:14 2 0 S
The set
command assigns
successive words in the output from the who
command to the shell script arguments
$1 through $9. The script uses them to test whether
the current system state was entered without errors, exiting if it
wasn’t. It also checks whether the immediately previous state was
single-user mode, as would be the case on this system on a boot or
reboot. These tests ensure that accounting is started only during a
successful boot and not when single-user mode has been entered due
to boot errors or when moving from one multiuser state to
another.
On many systems, the functioning of the various boot scripts can be controlled and modified by settings in one or more related configuration files. These settings may enable or disable subsystems, specify command-line arguments for starting daemons, and the like. Generally, such settings are stored in separate files named for the corresponding subsystem, but sometimes they are all stored in a single file (as on SuSE Linux systems, in /etc/rc.config).
Here are two configuration files from a Solaris system; the first is /etc/default/sendmail:
DAEMON=yes Enable the daemon. QUEUE=1h Set the poll interval to 1 hour.
The next file is /etc/default/samba:
# Options to smbd SMBDOPTIONS="-D" # Options to nmbd NMBDOPTIONS="-D"
The first example specifies whether the associated daemon should be started, as well as one of its arguments, and the second file specifies the arguments to be used when starting the two Samba daemons.
Table 4-4 summarizes the boot scripts and configuration files used by the various System V-style operating systems we are considering. A few notes about some of them will follow.
Table 4-4. Boot scripts for System V-style operating systems
Component | Location |
---|---|
inittab file | Usual: /etc |
rc* files | Usual: /sbin/rcn AIX: /etc/rc.* HP-UX: /sbin/rc n [14] Linux: /etc/rc.d/rc n a |
rcn.d and init.d subdirectories | Usual: /sbin/rc n .d and /sbin/init.d AIX: /etc/rc.d/rc n .d (but they are empty) Linux: /etc/rc.d/rc n .d and /etc/rc.d/init.d (Red Hat); /etc/init.d/rc n .d and /etc/init.d (SuSE) Solaris: /etc/rc n .d and /etc/init.d |
Boot script configuration files | AIX: none used FreeBSD: /etc/rc.conf , and/or /etc/rc.conf.local Linux: /etc/sysconfig/* (Red Hat, SuSE 8); /etc/rc.config and /etc/rc.config.d/* (SuSE 7) |
[14] n is the parameter to rc. |
Solaris uses a standard System V boot script scheme. The script rcS (in /sbin) replaces bcheckrc
, but it performs the same
functions. Solaris uses separate rcn scripts for each run level from 0
through 6 (excluding rc4, which a
site must create on its own), but the scripts for run levels 0, 5,
and 6 are just links to a single script, called with different
arguments for each run level. There are separate rcn.d directories for run levels 0 through 3
and S.
Unlike on some other systems, run level 5 is a “firmware” (maintenance) mode, defined as follows:
s5:5:wait:/sbin/rc5 >/dev/msglog 2>&1 </dev/console of:5:wait:/sbin/uadmin 2 6 >/dev/msglog 2>&1 </dev/console
These entries illustrate the Solaris msglog device, which sends output to one or more console devices via a single redirection operation.
Solaris inittab files also usually contain entries for the Service Access Facility daemons, such as the following:
sc:234:respawn:/usr/lib/saf/sac -t 300 ... co:234:respawn:/usr/lib/saf/ttymon ...
Run level 3 on Solaris systems is set up as the remote file-sharing state. When TCP/IP is the networking protocol in use, this means that general networking and NFS client activities—such as mounting remote disks—occur as part of run level 2, but NFS server activities do not occur until the system enters run level 3, when local filesystems become available to other systems. The rc2 script, and thus the scripts in rc2.d, are executed for both run levels by an inittab entry like this one:
s2:23:wait:/sbin/rc2 ...
Tru64 feels generally like a BSD-style operating system. Its
initialization scripts are one of the few places where its true,
System V-style origins are revealed. It uses bcheckrc
to check (if necessary) and mount
the local filesystems.
Tru64 defines only four run levels: 0, S, 2, and 3. The latter
two differ in that run level 3 is the normal, fully networked state
and is usually init
’s default run
level. Run level 2 is a nonnetworked state. It is designed so that
it can be invoked easily from a system at run level 3. The /sbin/rc2.d directory contains a
multitude of K-files designed to terminate all of the various
network servers and network-dependent subsystems. Most of the
K-files operate by running the ps
command, searching its output for the PID of a specific server
process, and then killing it if it is running. The majority of the
S-files in the subdirectory exit immediately if they are run at any
time other than a boot from single-user mode. Taken together, the
files in rc2.d ensure a
functional but isolated system, whether run level 2 is reached as
part of a boot or reboot, or via a transition from run level
3.
Most Linux systems use a vanilla, System V-style boot
script hierarchy. The Linux init
package supports the special action keyword ctrlaltdel that allows you to trap
CTRL-ALT-DELETE sequences (the standard method of rebooting a PC),
as in this example, which calls the shutdown
command and reboots the
system:
ca::ctrlaltdel:/sbin/shutdown -r now
Linux distributions also provide custom initial boot scripts (run prior to rc). For example, Red Hat Linux uses /etc/rc.d/rc.sysinit for this purpose, and SuSE Linux systems use /etc/init.d/boot. These scripts focus on the earliest boot tasks such as checking and mounting filesystems, setting the time zone, and initializing and activating swap space.
It’s possible to eliminate most of the layers of initialization scripts that are standard under System V. Consider this AIX inittab file:
init:2:initdefault: brc::sysinit:/sbin/rc.boot 3 >/dev/console 2>&1 rc:2:wait:/etc/rc 2>&1 | alog -tboot > /dev/console srcmstr:2:respawn:/usr/sbin/srcmstr tcpip:2:wait:/etc/rc.tcpip > /dev/console 2>&1 nfs:2:wait:/etc/rc.nfs > /dev/console 2>&1 ihshttpd:2:wait:/usr/HTTPServer/bin/httpd > /dev/console 2>&1 cron:2:respawn:/usr/sbin/cron qdaemon:2:wait:/usr/bin/startsrc -sqdaemon cons::respawn:/etc/getty /dev/console tty0:2:respawn:/etc/getty /dev/tty0
Other than starting a server process for the system console and executing the file /etc/bcheckrc at boot time, nothing is defined for any run level other than state 2 (multiuser mode).
This is the approach taken by AIX. When the system enters
state 2, a series of initialization files are run in sequence: in
this case, /etc/rc, /etc/rc.tcpip, and /etc/rc.nfs (with the System Resource
Controller starting up in the midst of them). Then several daemons
are started via their own inittab entries. After the scripts
complete, getty
processes are
started. Since /etc/rcn.d subdirectories are not used at all,
this setup is a little different from that used on BSD
systems.
More recent AIX operating system revisions do include hooks for other run levels, modifying the preceding inittab entries in this way:
# Note that even run level 6 is included! tcpip:23456789:wait:/etc/rc.tcpip > /dev/console 2>&1
The /etc/rc.d/rcn.d subdirectories are provided, but they are all empty.
Sooner or later, you will want to make additions or modifications to the standard boot process. Making additions is less risky than changing existing scripts. We’ll consider the two types of modifications separately.
Note
Before adding to or modifying system boot scripts, you should be very familiar with their contents and understand what every line within them does. You should also save a copy of the original script so you can easily restore the previous version should problems occur.
When you want to add commands to the boot process, the first thing you need to determine is whether there is already support for what you want to do. See if there is an easy way to get what you want: changing a configuration file variable, for example, or adding a link to an existing file in init.d.
If the operating system has made no provisions for the tasks you want to accomplish, you must next figure out where in the process the new commands should be run. It is easiest to add items at the end of the standard boot process, but occasionally this is not possible.
It is best to isolate your changes from the standard system initialization files as much as possible. Doing so makes them easier to test and debug and also makes them less vulnerable to being lost when the operating system is upgraded and the previous boot scripts are replaced by new versions. Under the BSD scheme, the best way to accomplish this is to add a line to rc (or any other script that you need to change) that calls a separate script that you provide:
. /etc/rc.site_specific >/dev/console 2>&1
Ideally, you would place this at the end of rc, and the additional commands needed on that system would be placed into the new script. Note that the script is sourced with the dot command so that it inherits the current environment from the calling script. This does constrain it to being a Bourne shell script.
Tip
Some systems contain hooks for an rc.local script specifically designed for this purpose (stored in /etc like rc). FreeBSD does—it is called near the end of rc—but you will have to create the file yourself.
On System V systems, there are more options. One approach is to add one or more additional entries to the inittab file (placing them as late in the file as possible):
site:23:wait:/etc/rc.site_specific >/dev/console 2>&1 h96:23:once:/usr/local/bin/h96d
The first entry runs the same shell script we added before, and the second entry starts a daemon process. Starting a daemon directly from inittab (rather than from some other initialization file) is useful in two circumstances: when you want the daemon started only at boot time and when you want it to be restarted automatically if it terminates. You would use the inittab actions once and respawn, respectively, to produce these two ways of handling the inittab entry.
Alternatively, if your additions need to take place at a very
specific point in the boot process, you will need to add a file to
the appropriate rcn.d subdirectories. Following the System V
practice is best in this case: place the new file in the init.d directory, giving it a descriptive
name, and then create links to other directories as needed. Choose
the filenames for the links carefully, so that your new files are
executed at the proper point in the sequence. If you are in doubt,
executing the ls -1
command in
the appropriate directory provides you with an unambiguous list of
the current ordering of the scripts within it, and you will be able
to determine what number to use for your new one.
Disabling parts of the boot process is also relatively easy. The method for doing so depends on the initialization scripts used by your operating system. The various possibilities are (in decreasing order of preference):
Disable a subsystem by setting the corresponding control variable to no or 0 in one of the boot script configuration files. For example:
sendmail_enable="no"
Remove the link in the rcn.d directory to the init.d directory in the case of System V-style boot scripts. Alternatively, you can rename the link, for example, by adding another character to the beginning (I add an underscore: _K20nfs). That way, it is easy to reinstate the file later.
In some cases, you will need to comment out an entry in /etc/inittab (when a daemon that you don’t want is started directly).
Comment out the relevant lines of initialization scripts that you don’t want to use. This is the only option under FreeBSD when no rc.conf parameter has been defined for a command or subsystem.
Linux systems often provide graphical utilities for adding and
removing links to files in init.d. Figure 4-3 illustrates the
ksysv
utility running on a Red Hat Linux system.
The main window lists the scripts assigned as S-files (upper lists) and K-files for each run level. The Available Services list shows all of the files in init.d. You can add a script by dragging it from that list box to the appropriate run level pane, and you can remove one by dragging it to the trash can (we are in the process of deleting the annoying Kudzu hardware detection utility in the example).
Clicking on any entry brings up the smaller dialog at the bottom of the figure (both of whose panels are shown as separate windows). You can specify the location within the sequence of scripts using the Entry panel. The Service panel displays a brief description of the daemon’s purpose and contains buttons with which you can start, stop, and restart it. If appropriate, you can use the Edit button to view and potentially modify the startup script for this facility.
While it is usually best to avoid it, sometimes you have no choice but to modify the commands in standard boot scripts. For example, certain networking functions stopped working on several systems I take care of immediately after an operating system upgrade. The reason was a bug in an initialization script, illustrated by the following:
# Check the mount of /. If remote, skip rest of setup. mount | grep ' / ' | grep ' nfs ' 2>&1 > /dev/null if [ "$?" -eq 0 ] then exit fi
The second line of the script is trying to figure out whether the root filesystem is local or remote—in other words, whether the system is a diskless workstation or not. It assumes that if it finds a root filesystem that is mounted via NFS, it must be a diskless system. However, on my systems, lots of root filesystems from other hosts are mounted via NFS, and this condition produced a false positive for this script, causing it to exit prematurely. The only solution in a case like this is to fix the script so that your system works properly.
Whenever you change a system script, keep these recommendations in mind:
As a precaution, before modifying them in any way, copy the files you intend to change, and write-protect the copies. Use the
-p
option of thecp
command, if it is supported, to duplicate the modification times of the original files as well as their contents; this data can be invaluable should you need to roll back to a previous, working configuration. For example:#
cp -p /etc/rc /etc/rc.orig
#cp -p /etc/rc.local /etc/rc.local.orig
#chmod a-w /etc/rc*.orig
If your version of
cp
doesn’t have a-p
option, use a process like this one:#
cd /etc
#mv rc rc.orig; cp rc.orig rc
#mv rc.local rc.local.orig; cp rc.local.orig rc.local
#chmod a-w rc.orig rc.local.orig
Similarly, when you make further modifications to an already customized script, save a copy before doing so, giving it a different extension, such as .save. This makes the modification process reversible; in the worst case, when the system won’t boot because of bugs in your new versions—and this happens to everyone—you can just boot to single-user mode and copy the saved, working versions over the new ones.
Make some provision for backing up modified scripts regularly so that they can be restored easily in an emergency. This topic is discussed in detail in Chapter 11.
For security reasons, the system initialization scripts (including any old or saved copies of them) should be owned by root and not be writable by anyone but the owner. In some contexts, protecting them against any non-root access is appropriate.
System boot scripts often provide both good and bad shell programming examples. If you write boot scripts or add commands to existing ones, keep these recommended programming practices in mind:
Use full pathnames for all commands (or use one of the other methods for ensuring that the proper command executable is run).
Explicitly test for the conditions under which the script is run if it is relying on the system being in some known state. Don’t assume, for example, that there are no users on the system or that a daemon the script will be starting isn’t already running; have the script check to make sure. Initialization scripts often get run in other contexts and at times other than those for which their writers originally designed them.
Handle all cases that might arise from any given action, not just the ones that you expect to result. This includes handling invalid arguments to the script and providing a usage message.
Provide lots of informational and error messages for the administrators who will see the results of the script.
Include plenty of comments within the script itself.
[12] In practice, booting to state 3 often involves implicitly moving through state 2, given the way that inittab configuration files employing both states are usually set up.
[13] Conventionally, labels are 2 characters long, but the actual limit is usually four characters, and some systems allow labels of up to 14 characters.
Get Essential System Administration, 3rd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.