Initialization Files and Boot Scripts

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.

Editing the boot script configuration file on a SuSE Linux system

Figure 4-1. Editing the boot script configuration file on a SuSE Linux system

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.

Initialization Files Under FreeBSD

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.

Initialization Files on System V Systems

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.

System V run levels

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

Run Level

Name and customary purpose

0

Halted state: conditions under which it is safe to turn off the power.

1

System administration/maintenance state.

S and s

Single-user mode.

2

Multiuser mode: the normal operating state for isolated, non-networked systems or networked, non-server systems, depending on the version of Unix.

3

Remote file sharing state: the normal operating state for server systems on networks that share their local resources with other systems (irrespective of whether networking and resource sharing occurs via TCP/IP and NFS or some other protocol).

4, 7, 8, 9

Administrator-definable system states: a generally unused run level, which can be set up and defined locally.

5

Same as run level 3 but running a graphical login program on the system console (e.g., xdm).

6

Shutdown and reboot state: used to reboot the system from some running state (s, 2, 3, or 4). Moving to this state causes the system to be taken down (to run level 0) and then immediately rebooted back to its normal operating state.

Q and q

A pseudo-state that tells init to reread its configuration file /etc/inittab.

a, b, c

Pseudo-run levels that can be defined locally. When invoked, they cause init to run the commands in /etc/inittab corresponding to them without changing the current (numeric) run level.

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.

Table 4-3. Run levels defined by various operating systems

 

AIX

HP-UX

Linux

Tru64

Solaris

Default run level

2

3

3 or 5

3

3

Q

yes

yes

yes

yes

yes

7, 8, 9

yes

no

yes

yes

no

a, b, c

yes

yes

yes

no

yes

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]

Using the telinit command to change run levels

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.

Initialization files overview

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.

Executing System V-style boot scripts

Figure 4-2. Executing System V-style boot scripts

The init configuration file

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.

The rcn initialization scripts

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.

Boot script configuration files

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.

File location summary

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

HP-UX: /etc/rc.config.d/*

Linux: /etc/sysconfig/* (Red Hat, SuSE 8); /etc/rc.config and /etc/rc.config.d/* (SuSE 7)

Solaris: /etc/default/*

Tru64: /etc/rc.config

[14] n is the parameter to rc.

Solaris initialization scripts

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 initialization scripts

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.

Linux initialization scripts

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.

AIX: Making System V work like BSD

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.

Customizing the Boot Process

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.

Adding to the boot scripts

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.

Eliminating certain boot-time activities

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.

Modifying boot script links

Figure 4-3. Modifying boot script links

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.

Modifying standard scripts

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

Guidelines for writing initialization scripts

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.