[ << ] [ < ] [ Home ] [ > ] [ >> ]


5. Initscripts

Content:

5.a. Runlevels

Booting your System

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 into memory after which it tells the CPU to run the kernel. When the kernel 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 in order to have a successfully booted system.

Finally, when all scripts are executed, init activates the terminals (in most cases just the virtual consoles which are hidden beneith Alt-F1, Alt-F2, etc.) attaching a special process called agetty to it. This process will then make sure you are able to log on through these terminals by running login.

Init Scripts

Now init doesn't just execute the scripts in /etc/init.d randomly. Even more, it doesn't run all scripts in /etc/init.d, 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 inside /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 doesn't decide all that 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 explained to you, you will remember that init's first action is to mount all filesystems. This is defined in the following line from /etc/inittab:

Code listing 1: The system initialisation line in /etc/inittab

si::sysinit:/sbin/rc sysinit

This line tells init that it must run /sbin/rc sysinit to initialize the system. The /sbin/rc script takes care of the initialisation, so you might say that init doesn't do much -- it delegates the task of initialising the system to another process.

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

Code listing 2: The system initialisation, continued

rc::bootwait:/sbin/rc boot

Again the rc script performs the 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:

Code listing 3: The initdefault line

id:3:initdefault:

In this case (which the majority of Gentoo users will use), the runlevel id is 3. Using this information, init checks what it must run to start runlevel 3:

Code listing 4: The runlevel definitions

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 to start the services (now with argument default). Again note that the argument of rc is the same as the subdirectory from /etc/runlevels.

When rc has finished, init decides what virtual consoles it should activate and what commands need to be run at each console:

Code listing 5: The virtual consoles definition

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 initscripts) that must be executed when you enter or leave a runlevel.

In Gentoo, there are seven runlevels defined: three internal runlevels, and four user-defined runlevels. The internal runlevels are called sysinit, shutdown and reboot and 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 the 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 or broken.

To start, stop or restart a service (and all depending services), start, stop and restart should be used:

Code listing 6: Starting Postfix

# /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:

Code listing 7: Stopping Postfix but keep the depending services running

# /etc/init.d/postfix pause

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

Code listing 8: Status information for postfix

# /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:

Code listing 9: Resetting status information for postfix

# /etc/init.d/postfix zap

To also ask what dependencies the service has, you can use iuse or ineed. 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.

Code listing 10: Requesting a list of all necessary services on which Postfix depends

# /etc/init.d/postfix ineed

Similarly, you can ask what services require the service (needsme) or can use it (usesme):

Code listing 11: Requesting a list of all services that require Postfix

# /etc/init.d/postfix needsme

Finally, you can ask what dependencies the service requires but that are missing:

Code listing 12: Requesting a list of missing dependencies for Postfix

# /etc/init.d/postfix broken

5.b. Working with rc-update

What is rc-update?

Gentoo's init system uses a dependency-tree to decide what service needs to be started first. As this is a tedious task that we wouldn't want our users to do manually, we have created tools 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

You have already added init scripts to the "default" runlevel during the installation of Gentoo. At that time you might not have had a clue what the "default" is for, but now you should. 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 rc-update the add or del argument, followed by the init script and the runlevel. For instance:

Code listing 13: Removing Postfix from the default runlevel

# rc-update del postfix default

The rc-update show command will show all the available init scripts and list at which runlevels they will execute:

Code listing 14: Receiving init script information

# rc-update show

5.c. Configuring Services

Why the Need for Extra Configuration?

Init scripts can be quite complex. It is therefore not really interesting to have the users directly edit the init script, 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 being afraid that your configuration changes are undone.

The /etc/conf.d Directory

Gentoo provides an easy way to configure such a service: every init script that can be configured has a file in /etc/conf.d. For instance, the apache2 initscript (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:

Code listing 15: Variable defined in /etc/conf.d/apache2

APACHE2_OPTS="-D PHP4"

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

5.d. Writing Init Scripts

Do I Have To?

No. Writing an init script is usually not necessary as Gentoo 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.

Do not use the init script provided by the service if it isn't explicitly written for Gentoo: Gentoo's init scripts are not compatible with the init scripts used by other distributions!

Layout

The basic layout of an init script is shown below.

Code listing 16: Basic layout of an init script

#!/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 other sections are optional.

Dependencies

There are two dependencies you can define: use and need. As we have mentioned before, the need dependency is more strict than the use dependency. Following this dependency type you enter the service you depend on, or the virtual dependency.

A virtual dependency is a dependency that a service provides, but that is not provided solely by that service. Your 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) we made sure that all these services provide a virtual dependency.

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

Code listing 17: Dependency information for Postfix

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

As you can see, the postfix service:

Controlling the Order

In some cases you might not require a service, but want your service to be started before (or after) another service if it is available on the system (note the conditional - this is no dependency anymore) and ran in the same runlevel (note the conditional - only services in the same runlevel are involved). You can provide this information using the before or after settings.

As an example we view the settings of the Portmap service:

Code listing 18: The depend() function in the Portmap service

depend() {
  need net
  before inetd
  before xinetd
}

You can also use the "*" glob to catch all services in the same runlevel, although this isn't adviseable.

Code listing 19: Running an init script as first script in the runlevel

depend() {
  before *
}

Standard Functions

Next to the depend() functionality, you also need to define the start() function. This one contains all the commands necessary to initialize your service. It is adviseable to use the ebegin and eend functions to inform the user about what is happening:

Code listing 20: Example 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. As for start-stop-daemon, there is an excellent man page available if you need more information:

Code listing 21: Getting the man page for start-stop-daemon

# man start-stop-daemon

Other functions you can define are: stop() and restart(). You are not obliged to define these functions! Our init system is intelligent enough to fill these functions by itself if you use start-stop-daemon.

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:

Code listing 22: Supporting the restartdelay option

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):

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

5.e. Changing the Runlevel Behaviour

Who might benefit 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 Gentoo 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 defalt runlevel you want to use.

Using SOFTLEVEL

First of all, create the runlevel directory for your second "default" runlevel. As an example we create the offline runlevel:

Code listing 23: Creating a runlevel directory

# mkdir /etc/runlevels/offline

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

Code listing 24: Adding the necessary init scripts

# ls /etc/runlevels/default
acpid  domainname  local  net.eth0  netmount  postfix  syslog-ng  vixie-cron
# rc-update add acpid offline
# rc-update add domainname offline
# rc-update add local offline
# rc-update add syslog-ng offline
# rc-update add vixie-cron offline

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

Code listing 25: Adding an entry for the offline runlevel

title Gentoo Linux Offline Usage
  root (hd0,0)
  kernel (hd0,0)/kernel-2.4.25 root=/dev/hda3 softlevel=offline

Voila, you're all set now. 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.


[ << ] [ < ] [ Home ] [ > ] [ >> ]