This is a read-only archive. Find the latest Linux articles, documentation, and answers at the new Linux.com!

Linux.com

Feature: BSD

A daemon-writer's guide to NetBSD's rc.d system

By Peter Seebach on January 15, 2004 (8:00:00 AM)

Share    Print    Comments   

The NetBSD startup process is extensible, flexible, and a little daunting at first. This article looks at the configuration mechanisms used to enable or disable features, and compares NetBSD's startup procedures to those of other systems.

Introduction

A typical desktop Unix system may easily run dozens of processes, most of which are started without user intervention. Disks are mounted, log files created, and daemons started, and these things must be done in a particular order; if you start a daemon that tries to write its logs in /var/log before mounting the filesystem containing /var, prepare for failure.

In times of yore, vendors handled process startup simply by writing the startup script, /etc/rc, correctly, doing things in the right order. When users wanted to add services, vendors began to provide a second script, /etc/rc.local, which holds local additions to be run only after /etc/rc completes.

Eventually, Unix split into the BSD and System V communities. System V systems, such as AT&T's SVR2 on the 3b1 and 3b2, Amiga Unix, and modern Solaris, invented a whole family of scripts to start and stop system services, and created links to them in special directories; the links had names that indicated the order in which the scripts were to run. For instance, a script called "S01disks" would run before a script called "S02daemons." Each directory corresponded to a "runlevel"; the system started in runlevel 1, and worked its way up, so that runlevel 2 might be a normal multiuser boot, and runlevel 3 a multiuser boot with network services started.

The BSD communities, starting with 386BSD and BSD/386, went with gradually more elaborate variants on the rc.local script, with BSD/OS, for instance, having scripts called "netstart," "rc," "rc.local," and "rc.first." The gradual proliferation allowed the vendor to maintain a standard rc script, with most user changes going in either rc.local (if they could run after the standard system boot) or rc.first (if they had to run before it). However, more elaborate configurations were difficult.

Configuration in /etc/rc.conf

In 1998 NetBSD 1.3 added a new file called rc.conf to set variables that enabled or disabled common system services. In this file each service could have two variables set. The first would be the name of a service, and could be set to "yes" or "no," indicating whether or not to run the service. The second would be a set of flags with which a service might be invoked. For instance, an NFS server might have these lines in /etc/rc.conf:

	nfsd=yes
	nfsd_flags="-tun 4"

With the introduction of rc.conf each actual rc file could almost always be left untouched, with changes made in a configuration file controlling it, rather than in rc itself. Still, the consensus among NetBSD developers was that the existing rc scripts were not sufficiently scalable, and that the System V mechanism had too many flaws and quirks.

After a number of debates, some some frustrated systems architects sat down to design a new system that would address the concerns of people opposed to adopting the System V init.d mechanism. A detailed analysis of this system is beyond the scope of this document, but it's discussed in Luke Mewburn's paper, "The Design and Implementation of the NetBSD rc.d system". To make a long story short, the goal of the rc.d system is to let each service have its own startup/shutdown script, with as much code as possible shared, and some kind of robust ordering mechanism.

With the new startup system, the rc.conf file is no longer a system standard file. Instead, there's a system-provided /etc/defaults/rc.conf file, which provides system defaults, and an admin-provided /etc/rc.conf file, which overrides the default settings. The system file can be upgraded without affecting local settings.

The rc.d scripts are designed to work without a traditional BSD /usr filesystem being mounted, and without requiring many commands to migrate from /usr to the root filesystem, because NetBSD still actively supports weird machines with tiny filesystems.

Understanding an rc.d script

Here's a script taken out of a NetBSD-current system. This script controls the mouse daemon, which reads data from a serial mouse and passes it to the system's standard mouse interface.

	#!/bin/sh
	#
	# $NetBSD: moused,v 1.1 2001/10/29 23:25:01 augustss Exp $
	#

 	# PROVIDE: moused
	# REQUIRE: DAEMON

	. /etc/rc.subr

 	name="moused"
	rcvar=$name
	command="/usr/sbin/${name}"

 	load_rc_config $name
	run_rc_command "$1"

The first four lines do nothing particularly interesting. The next two lines, ignored by /bin/sh, are used by the rcorder script (discussed later in this article) to figure out when a script can be run. The first real line of the script loads the standard rc.subr functions, support routines designed for use by the programs in rc.d. These functions use a handful of standard variables, such as $name, $rcvar, and $command. In this case, all of them end up getting set to the same value.

Next, the script calls two functions. load_rc_config loads any relevant configuration files; this includes /etc/rc.conf, and (if it exists) /etc/rc.conf.d/$name. run_rc_command actually starts the daemon.

There's a lot of work happening behind the scenes in these functions. For instance, the run_rc_command shell function will, if a file has been specified to hold the process ID of a running daemon, check for an existing daemon. It also knows about running daemons as specific users, at priorities other than the default, and so on.

Similarly, the function can do more than just start daemons; it can stop them, restart them, check on their status, or even wait for them to finish shutting down.

Scripts can be very complicated. The /etc/rc.d/network script is nearly as long as this entire article, because it does all the network startup work; it checks for IPV6 support, sets up NIS if needed, checks for an address via DHCP if configured to, and so on.

Understanding rcorder

If you aren't going to run files in lexicographical order, you need some way to order them. Ideally, this would be designed in terms of dependencies, not just an arbitrary order. Something such as "system files first" would be unacceptable; users may need to run special services (such as a VPN configuration script) before system services (such as an NFS mount).

The developers of the rc.d system decided to write a new ordering program, called rcorder, which could have special domain-specific knowledge about what it was doing.

The goal is to allow the writers of new services to impose exactly the requirements of the services, and no more, on the ordering of programs. If a function has to be started after networking is up, but has no other requirements, there is no reason to decide whether it runs before or after another program with the same requirements. On the other hand, if a program must be started after the general networking code is run, but before a specific network service is run, it's nice to be able to indicate that.

rcorder looks at a block of comments in the startup shell scripts that contains lines using four keywords; "PROVIDES," "REQUIRES," "BEFORE," or "KEYWORD." For instance, the rc.d script for altqd, which changes the way network packets are routed, says:

	# PROVIDE: altqd
	# REQUIRE: mountcritremote
	# BEFORE:  SERVERS
	# KEYWORD: shutdown

The PROVIDE line tells rcorder what the name of the service is. The REQUIRE line says what has to have been provided already. In this case, it's "mountcritremote," which does just what the name suggests -- mounts any critical remote filesystems. The BEFORE line says that, wherever this is ordered, it has to be before the SERVERS dependency. The all-caps name on SERVERS is a convention used to indicate a "dummy" dependency used only for ordering. The KEYWORD "shutdown" means that the service wants to be actually stopped when the system is shutting down, not just killed or ignored.

The rcorder script can select only files with a given keyword or skip all files with a given keyword. For instance, on system shutdown, the system shutdown script /etc/rc.shutdown builds a list of shutdown scripts to run, using the -k option to rcorder as follows: files=$(rcorder -k shutdown ${rcshutdown_rcorder_flags} /etc/rc.d/*) Files that don't specify the shutdown flag are skipped. Other files are ordered according to their dependencies. The astute reader may be thinking that running shutdown scripts in dependency order would be a bad idea; if you shut down NFS before you shut down things which used it, you'll have problems. The rc.shutdown script reverses the list before running them, using a reverse_list shell function found in /etc/rc.subr.

The BEFORE feature is what makes this scheme elegant. You don't need to edit the script for a given daemon to add a dependency to it; you just add it to your BEFORE line. It's sort of like a "come from."

Designing an rc.d script

A well-designed script should fit into the existing framework easily. For most scripts, it's easy enough; put PROVIDES and the name of your new service, and a REQUIRES tag with whatever you require; for instance, NETWORKING if you need a network connection, or SERVERS if you're dependent on other servers.

A couple of keywords are noteworthy. The "nostart" keyword indicates that a script should not be run automatically at system boot. This is not the same as setting its rcflag to "no." If you have set the corresponding flag to "no," the script will not start even if you try to start it from the command line. If you set the flag to "yes" but give the script the "nostart" keyword, it will be ignored by the standard system scripts, but the command sh /etc/rc.d/foo start will start it. This might be useful if you have a service you only want on some of the time, but which is complicated to start.

The "shutdown" keyword indicates that a script should be run with a "stop" argument during system shutdown.

Most ordinary daemons require very little special work. Those that do can generally provide a start and a stop command via shell variables named $start_cmd and $stop_cmd. A script that has nothing to do on shutdown may set $stop_cmd to ":" (the shell builtin version of /usr/bin/true). It often makes sense to provide small shell functions for these features.

The best way to design your own script is to look at the existing ones in /etc/rc.d and see which are most similar to what you're doing. Play around with it. The documentation is excellent; the man pages in question are rcorder(8) and rc.subr(8).

Share    Print    Comments   

Comments

on A daemon-writer's guide to NetBSD's rc.d system

There are no comments attached to this item.

This story has been archived. Comments can no longer be posted.



 
Tableless layout Validate XHTML 1.0 Strict Validate CSS Powered by Xaraya