2.7 Init scripts

Runlevels

Boot sequence

When you boot your system, you will notice lots of text floating by. If you pay close attention, you will notice this text is the same every time you reboot your system. The sequence of all these actions is called the boot sequence and is more or less statically defined.

First, your boot loader will load the kernel image you have defined in the boot loader configuration. The kernel is then loaded. When it is loaded and run, it initializes all kernel-specific structures and tasks and starts the init process.

This process then makes sure that all filesystems (defined in /etc/fstab) are mounted and ready to be used. Then it executes several scripts located in /etc/init.d, which will start the services you need for a successful booting.

Finally, when all scripts are executed, init activates the terminals (in most cases just the virtual consoles which are hidden beneath ALT+F1, ALT+F2, etc.), attaching a special process called agetty to each console. This process will then allow you to log on through these terminals by running login.

Init scripts

Now init does not run the scripts from /etc/init.d randomly. Even more, it doesn't run all scripts in /etc/init.d, but only the scripts it is told to execute. It decides which scripts to execute by looking into /etc/runlevels.

First, init runs all scripts from /etc/init.d, that have symbolic links in /etc/runlevels/boot. Usually, it will start the scripts in alphabetical order, but some scripts have dependency information in them, telling the system that another script must be run before they can be started.

When all /etc/runlevels/boot referenced scripts are executed, init continues with running the scripts that have a symbolic link to them in /etc/runlevels/default. Again, it will use the alphabetical order to decide what script to run first, unless a script has dependency information in it, in which case the order is changed to provide a valid start-up sequence.

How init works

Of course, init makes no decisions by itself. It needs a configuration file that specifies what actions need to be taken. This configuration file is /etc/inittab.

If you remember the boot sequence we have just described, you will remember that init's first action is to mount all filesystems. This is defined in the following line from /etc/inittab:

si::sysinit:/sbin/rc sysinit

This line tells init to run /sbin/rc sysinit in order to initialize the system. The /sbin/rc script takes care of the initialization, so you might say that init does not do much, it delegates the task of initialising the system to another process.

Second, init executes all scripts that have symbolic links in /etc/runlevels/boot. This is defined with the following line:

rc::bootwait:/sbin/rc boot

Again the rc script performs all necessary tasks. Note that the option given to rc (boot) is the same as the subdirectory of /etc/runlevels that is used.

Now init checks its configuration file to see what runlevel it should run. To decide this, it reads the following line from /etc/inittab:

id:3:initdefault:

In this case (which will suit the majority of Calculate users) the runlevel id is 3. Using this information, init checks what it has to do to run runlevel 3. Here is an example of runlevels

l0:0:wait:/sbin/rc shutdown
l1:S1:wait:/sbin/rc single
l2:2:wait:/sbin/rc nonetwork
l3:3:wait:/sbin/rc default
l4:4:wait:/sbin/rc default
l5:5:wait:/sbin/rc default
l6:6:wait:/sbin/rc reboot

The line that defines level 3, again, uses the rc script (now with the default argument). Note again that the argument of the rc script is the same as the subdirectory from /etc/runlevels.

When rc has finished, init decides which virtual consoles it should activate and what commands need to be run at each console. Here is an example of how virtual consoles are defined:

c1:12345:respawn:/sbin/agetty 38400 tty1 linux
c2:12345:respawn:/sbin/agetty 38400 tty2 linux
c3:12345:respawn:/sbin/agetty 38400 tty3 linux
c4:12345:respawn:/sbin/agetty 38400 tty4 linux
c5:12345:respawn:/sbin/agetty 38400 tty5 linux
c6:12345:respawn:/sbin/agetty 38400 tty6 linux

What is a runlevel?

You have seen that init uses a numbering scheme to decide what runlevel it should activate. A runlevel is a state in which your system is running and contains a collection of scripts (runlevel scripts or init scripts) that must be executed when you enter or leave a runlevel.

Seven runlevels are defined in Calculate: three internal runlevels, and four user-defined runlevels. The internal runlevels are called sysinit, shutdown and reboot. They do exactly what their names imply: initialize the system, powering off the system and rebooting the system.

The user-defined runlevels are those with an accompanying /etc/runlevels subdirectory: boot, default, nonetwork and single. The boot runlevel starts all system-necessary services which all other runlevels use. The remaining three runlevels differ in what services they start: default is used for day-to-day operations, nonetwork is used in case no network connectivity is required, and single is used when you need to fix the system.

Working with init scripts

The scripts that the rc process starts are called init scripts. Each script in /etc/init.d can be executed with the arguments start, stop, restart, pause, zap, status, ineed, iuse, needsme, usesme and broken.

To start, stop or restart a service (and all depending services), start, stop and restart should be used. That's how you start postfix, for example:

/etc/init.d/postfix start

Note: Only the services that need the given service are stopped or restarted. The other depending services (those that use the service but don't need it) are left untouched.

If you want to stop a service, but not the services that depend on it, you can use the pause argument:

/etc/init.d/postfix pause

If you want to see what status a service has (started, stopped, paused, etc.), you can use the status argument. See an example below:

/etc/init.d/postfix status

If the status information tells you that the service is running, but you know that it is not, then you can reset the status information to "stopped" with the zap argument. For postfix, thus, you will have to enter:

/etc/init.d/postfix zap

If you need to know what are the service's dependencies, you can use the iuse or the ineed arguments. With ineed, you can see the services that are really necessary for the correct functioning of the service. iuse, on the other hand, shows the services that can be used by the service, but are not necessary for the correct functioning. Once more, let's apply this to the postfix service, for instance:

/etc/init.d/postfix needsme

Finally, you can ask for the list of the dependencies the service requires that are missing:

/etc/init.d/postfix broken

Using rc-update

What is rc-update?

Calculate's initialization system uses a dependency-tree to decide which services need to be started first. As this is a tedious task our users should not have to do manually, tools were created that ease the administration of the runlevels and init scripts.

With rc-update, you can add and remove init scripts to a runlevel. The rc-update tool will then automatically ask the depscan.sh script to rebuild the dependency tree.

Adding and removing services

The rc-update script requires a second argument that defines the action: add, del or show.

To add or remove an init script, just give the rc-update an add or del argument followed by the init script and the runlevel. If you wanted to remove postfix from the default runlevel, you would enter:

rc-update del postfix default

If you run rc-update show, a list of all enabled init scripts and their runlevels will be shown:

rc-update show

Configuring services

Why do you need extra configuring?

Init scripts can be quite complex. It is therefore not really desirable to have the users edit the init script directly, as it would make it more error-prone. It is however important to be able to configure such a service. For instance, you might want to give more options to the service itself.

A second reason to have this configuration outside the init script is to be able to update the init scripts without the risk that your configuration changes will be undone.

The /etc/conf.d directory

Calculate provides an easy way to configure such a service: every init script that can be configured has a file in /etc/conf.d. For example, the apache2 (called /etc/init.d/apache2) has a configuration file called /etc/conf.d/apache2, which can contain the options you want to give to the Apache 2 server when it is started. Here is an example of a variable defined in /etc/conf.d/apache2:

APACHE2_OPTS="-D PHP4"

Such a configuration file contains variables and variables alone (like /etc/make.conf), making it very easy to configure services. It also allows us to provide more information about the variables (as comments).

Writing initscripts

Do I really have to?

No, writing an init script is usually not necessary as Calculate provides ready-to-use init scripts for all provided services. However, you might have installed a service without using Portage, in which case you will most likely have to create an init script.

Layout

The basic layout of an init script is shown below.

#!/sbin/runscript

depend() {
  (dependency information)
}

start() {
  (commands necessary to start the service)
}

stop() {
  (commands necessary to stop the service)
}

restart() {
  (commands necessary to restart the service)
}

Any init script requires the start() function to be defined. All the other functions are optional.

Dependencies

There are two dependency-alike settings you can define that influence the start-up or sequencing of init scripts: use and need. The need dependency is harder than the use dependency. The name of the service requiring a dependency or a link to a virtual dependency are put after the service's type.

A virtual dependency is a dependency that a service provides, but that is not provided solely by that service. An init script can depend on a system logger, but there are many system loggers available (metalogd, syslog-ng, sysklogd, ...). As you cannot need every single one of them (no sensible system has all these system loggers installed and running), all these services provide a virtual dependency.

Let us take a look at the dependency information for the postfix service.

depend() {
  need net
  use logger dns
  provide mta
}

As you can see, postfix:

  • requires the net service: a virtual dependency provided, for instance, by /etc/init.d/net.eth0;
  • uses the logger: a virtual dependency provided, for instance, by /etc/init.d/syslog-ng;
  • uses the dns service: a virtual dependency provided, for instance, by /etc/init.d/named;
  • provides the mta service: a virtual dependency common for all mail servers.

Startup order

Sometimes you do not need the service itself, but that is be started before (or after) another service, if it is enabled (note the if: this is not a dependency any more) and run in the same runlevel (note that this only about services of the same runlevel). This ordering is handled through the order settings before or after.

Let us have a look at the depend() function in the Portmap service:

depend() {
  need net
  before inetd
  before xinetd
}

You can also use "*" to catch all services in the same runlevel, although this is not advisable. Below is an example of running a script as first script in the runlevel:

depend() {
  before *
}

Standard functions

After the depend() functionality, you will also need to define the start() function. It contains all the commands necessary to initialize your service. It is advisable to use the ebegin and eend functions to inform the user about what is happening. Here is a example of a start() function:

start() {
  ebegin "Starting my_service" 
  start-stop-daemon --start --quiet --exec /path/to/my_service
  eend $?
}

If you need more examples of the start() function, please read the source code of the available init scripts in your /etc/init.d directory. The start-stop-daemon function has an excellent man page available if you need more information. To view the man page on start-stop-daemon enter:

man start-stop-daemon

The stop() and the restart() functions can also be defined. You will not have to define them manually, though! Your init system is intelligent enough to fill in this function by itself if you use start-stop-daemon.

Calculate's init script syntax is based on the Bourne Again Shell (bash), so you are free to use bash-compatible constructs inside your init script.

Adding custom options

If you want your init script to support more options than the ones we have already encountered, you should add the option to the opts variable, and create a function with the same name as the option. For instance, to support an option called restartdelay:

opts="${opts} restartdelay" 

restartdelay() {
  stop
  sleep 3    # Wait 3 seconds before starting again
  start
}

Service Configuration Variables

You don't have to do anything to support a configuration file in /etc/conf.d: if your init script is executed, the following files are automatically sourced (i.e. the variables are available to use):

  • /etc/conf.d/<you init script>
  • /etc/conf.d/basic
  • /etc/rc.conf

If your init script provides a virtual dependency (such as net), the file associated with that dependency (such as /etc/conf.d/net) will be sourced too.

Changing the runlevel behavior

Who benefits from this?

Many laptop users know the situation: at home you need to start net.eth0 while you don't want to start net.eth0 while you're on the road (as there is no network available). With Calculate you can alter the runlevel behaviour to your own will.

For instance you can create a second "default" runlevel which you can boot that has other init scripts assigned to it. You can then select at boottime what default runlevel you want to use.

Using softlevel

First of all, create the runlevel directory for your second "default" runlevel. Let us create the "offline" runlevel as an example. We will create the runlevel directory:

mkdir /etc/runlevels/offline

Add the necessary init scripts to the newly created runlevel. For instance, if you want to have an exact copy of your current "default" runlevel but without net.eth0, type in:

(copyint all services from default runlevel to offline runlevel)
# cd /etc/runlevels/default
# for service in *; do rc-update add $service offline; done
(removing unwanted service from offline runlevel)
# rc-update del net.eth0 offline
(displaying active services for offline runlevel)
# rc-update show offline
(partial sample output)
               acpid | offline
          domainname | offline
               local | offline
            net.eth0 |

Now edit your bootloader configuration and add a new entry for the "offline" runlevel. For instance, add in /boot/grub/grub.conf:

title Calculate Linux Offline Usage
  root (hd0,0)
  kernel /boot/vmlinuz-5bf7e746 root=/dev/hda3 softlevel=offline

Here you are. If you boot your system and select the newly added entry at boot, the "offline" runlevel will be used instead of the "default" one.

Using bootlevel

Using bootlevel is completely analogous to softlevel. The only difference here is that you define a second "boot" runlevel instead of a second "default" runlevel.

Thank you!