Copyright © 2005-2006, 2012 The FreeBSD Project
FreeBSD is a registered trademark of the FreeBSD Foundation.
NetBSD is a registered trademark of the NetBSD Foundation.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this document, and the FreeBSD Project was aware of the trademark claim, the designations have been followed by the “™” or the “®” symbol.
Beginners may find it difficult to relate the
facts from the formal documentation on the BSD
rc.d
framework with the practical tasks
of rc.d
scripting. In this article,
we consider a few typical cases of increasing complexity,
show rc.d
features suited for each
case, and discuss how they work. Such an examination should
provide reference points for further study of the design
and efficient application of rc.d
.
The historical BSD had a monolithic startup script,
/etc/rc
. It was invoked by
init(8) at system boot time and performed all userland
tasks required for multi-user operation: checking and
mounting file systems, setting up the network, starting
daemons, and so on. The precise list of tasks was not the
same in every system; admins needed to customize it. With
few exceptions, /etc/rc
had to be modified,
and true hackers liked it.
The real problem with the monolithic approach was that
it provided no control over the individual components started
from /etc/rc
. For instance,
/etc/rc
could not restart a single daemon.
The system admin had to find the daemon process by hand, kill it,
wait until it actually exited, then browse through
/etc/rc
for the flags, and finally type
the full command line to start the daemon again. The task
would become even more difficult and prone to errors if the
service to restart consisted of more than one daemon or
demanded additional actions. In a few words, the single
script failed to fulfil what scripts are for: to make the
system admin's life easier.
Later there was an attempt to split out some parts of
/etc/rc
for the sake of starting the
most important subsystems separately. The notorious example
was /etc/netstart
to bring up networking.
It did allow for accessing the network from single-user
mode, but it did not integrate well into the automatic startup
process because parts of its code needed to interleave with
actions essentially unrelated to networking. That was why
/etc/netstart
mutated into
/etc/rc.network
. The latter was no
longer an ordinary script; it comprised of large, tangled
sh(1) functions called from /etc/rc
at different stages of system startup. However, as the startup
tasks grew diverse and sophisticated, the
“quasi-modular” approach became even more of a
drag than the monolithic /etc/rc
had
been.
Without a clean and well-designed framework, the startup
scripts had to bend over backwards to satisfy the needs of
rapidly developing BSD-based operating systems. It became
obvious at last that more steps are necessary on the way to
a fine-grained and extensible rc
system.
Thus BSD rc.d
was born. Its acknowledged
fathers were Luke Mewburn and the NetBSD community. Later
it was imported into FreeBSD. Its name refers to the location
of system scripts for individual services, which is in
/etc/rc.d
. Soon we
will learn about more components of the rc.d
system and see how the individual scripts are invoked.
The basic ideas behind BSD rc.d
are
fine modularity and code
reuse. Fine modularity means
that each basic “service” such as a system daemon
or primitive startup task gets its own sh(1) script able
to start the service, stop it, reload it, check its status.
A particular action is chosen by the command-line argument
to the script. The /etc/rc
script still
drives system startup, but now it merely invokes the smaller
scripts one by one with the start
argument.
It is easy to perform shutdown tasks as well by running the
same set of scripts with the stop
argument,
which is done by /etc/rc.shutdown
. Note
how closely this follows the Unix way of having a set of small
specialized tools, each fulfilling its task as well as possible.
Code reuse means that common operations
are implemented as sh(1) functions and collected in
/etc/rc.subr
. Now a typical script can
be just a few lines' worth of sh(1) code. Finally, an
important part of the rc.d
framework is
rcorder(8), which helps /etc/rc
to
run the small scripts orderly with respect to dependencies
between them. It can help /etc/rc.shutdown
,
too, because the proper order for the shutdown sequence is
opposite to that of startup.
The BSD rc.d
design is described in
the original article by Luke Mewburn,
and the rc.d
components are documented
in great detail in the respective
manual pages. However, it might not appear obvious
to an rc.d
newbie how to tie the numerous
bits and pieces together in order to create a well-styled
script for a particular task. Therefore this article will
try a different approach to describe rc.d
.
It will show which features should be used in a number of
typical cases, and why. Note that this is not a how-to
document because our aim is not at giving ready-made recipes,
but at showing a few easy entrances into the
rc.d
realm. Neither is this article a
replacement for the relevant manual pages. Do not hesitate
to refer to them for more formal and complete documentation
while reading this article.
There are prerequisites to understanding this article.
First of all, you should be familiar with the sh(1)
scripting language in order to master rc.d
.
In addition, you should know how the system performs
userland startup and shutdown tasks, which is described in
rc(8).
This article focuses on the FreeBSD branch of
rc.d
. Nevertheless, it may be useful
to NetBSD developers, too, because the two branches of BSD
rc.d
not only share the same design
but also stay similar in their aspects visible to script
authors.
A little consideration before starting
$EDITOR
will not hurt. In order to write a
well-tempered rc.d
script for a system
service, we should be able to answer the following questions
first:
Is the service mandatory or optional?
Will the script serve a single program, e.g., a daemon, or perform more complex actions?
Which other services will our service depend on, and vice versa?
From the examples that follow we will see why it is important to know the answers to these questions.
The following script just emits a message each time the system boots up:
#!/bin/sh . /etc/rc.subr name="dummy" start_cmd="${name}_start" stop_cmd=":" dummy_start() { echo "Nothing started." } load_rc_config $name run_rc_command "$1"
Things to note are:
An interpreted script should begin with the magic “shebang” line. That line specifies the interpreter program for the script. Due to the shebang line, the script can be invoked exactly like a binary program provided that it has the execute bit set. (See chmod(1).) For example, a system admin can run our script manually, from the command line:
Note:In order to be properly managed by the
Tip:If you would like to learn the details of why
| |
In An Note:Some useful functions related to networking
are provided by another include file,
| |
The mandatory variable
Now it is the right time to choose a unique name for our script once and for all. We will use it in a number of places while developing the script. For a start, let us give the same name to the script file, too. Note:The current style of | |
The main idea behind rc.subr(8) is that an
Note:To make the code in | |
We should keep in mind that rc.subr(8) provides default methods for the standard arguments. Consequently, we must override a standard method with a no-op sh(1) expression if we want it to do nothing. | |
The body of a sophisticated method can be implemented as a function. It is a good idea to make the function name meaningful. Important:It is strongly recommended to add the prefix
| |
This call to rc.subr(8) loads rc.conf(5) variables. Our script makes no use of them yet, but it still is recommended to load rc.conf(5) because there can be rc.conf(5) variables controlling rc.subr(8) itself. | |
Usually this is the last command in an
|
Now let us add some controls to our dummy script. As you
may know, rc.d
scripts are controlled
with rc.conf(5). Fortunately, rc.subr(8) hides all
the complications from us. The following script uses
rc.conf(5) via rc.subr(8) to see whether it is
enabled in the first place, and to fetch a message to show
at boot time. These two tasks in fact are independent. On
the one hand, an rc.d
script can just
support enabling and disabling its service. On the other
hand, a mandatory rc.d
script can have
configuration variables. We will do both things in the same
script though:
#!/bin/sh . /etc/rc.subr name=dummy rcvar=dummy_enable start_cmd="${name}_start" stop_cmd=":" load_rc_config $name : ${dummy_enable:=no} : ${dummy_msg="Nothing started."} dummy_start() { echo "$dummy_msg" } run_rc_command "$1"
What changed in this example?
The variable | |
Now Note:While examining | |
A warning will be emitted by
Note:You can make rc.subr(8) act as though the knob
is set to
| |
Now the message to be shown at boot time is no
longer hard-coded in the script. It is specified by an
rc.conf(5) variable named Important:The names of all rc.conf(5) variables used
exclusively by our script must
have the same prefix: Note:While it is possible to use a shorter name internally,
e.g., just As a rule, | |
Here we use start_cmd="echo \"$dummy_msg\"" |
We said earlier that rc.subr(8) could provide default
methods. Obviously, such defaults cannot be too general.
They are suited for the common case of starting and shutting
down a simple daemon program. Let us assume now that we need
to write an rc.d
script for such a daemon
called mumbled
. Here it is:
#!/bin/sh . /etc/rc.subr name=mumbled rcvar=mumbled_enable command="/usr/sbin/${name}" load_rc_config $name run_rc_command "$1"
Pleasingly simple, isn't it? Let us examine our little script. The only new thing to note is as follows:
The The daemon will be started by running
Note:Some programs are in fact executable scripts. The
system runs such a script by starting its interpreter
and passing the name of the script to it as a command-line
argument. This is reflected in the list of processes,
which can confuse rc.subr(8). You should additionally
set For each Of course, sh(1) will permit you to set
For more detailed information on default methods, refer to rc.subr(8). |
Let us add some meat onto the bones of the previous script and make it more complex and featureful. The default methods can do a good job for us, but we may need some of their aspects tweaked. Now we will learn how to tune the default methods to our needs.
#!/bin/sh . /etc/rc.subr name=mumbled rcvar=mumbled_enable command="/usr/sbin/${name}" command_args="mock arguments > /dev/null 2>&1" pidfile="/var/run/${name}.pid" required_files="/etc/${name}.conf /usr/share/misc/${name}.rules" sig_reload="USR1" start_precmd="${name}_prestart" stop_postcmd="echo Bye-bye" extra_commands="reload plugh xyzzy" plugh_cmd="mumbled_plugh" xyzzy_cmd="echo 'Nothing happens.'" mumbled_prestart() { if checkyesno mumbled_smart; then rc_flags="-o smart ${rc_flags}" fi case "$mumbled_mode" in foo) rc_flags="-frotz ${rc_flags}" ;; bar) rc_flags="-baz ${rc_flags}" ;; *) warn "Invalid value for mumbled_mode" return 1 ;; esac run_rc_command xyzzy return 0 } mumbled_plugh() { echo 'A hollow voice says "plugh".' } load_rc_config $name run_rc_command "$1"
Additional arguments to Note:Never include dashed options,
like | |
A good-mannered daemon should create a
pidfile so that its process can be
found more easily and reliably. The variable
Note:In fact, rc.subr(8) will also use the pidfile
to see if the daemon is already running before starting
it. This check can be skipped by using the
| |
If the daemon cannot run unless certain files exist,
just list them in Note:The default method from rc.subr(8) can be
forced to skip the prerequisite checks by using
| |
We can customize signals to send to the daemon in
case they differ from the well-known ones. In particular,
Note:The signal names should be specified to rc.subr(8)
without the | |
Performing additional tasks before or after the default
methods is easy. For each command-argument supported by
our script, we can define
Note:Overriding a default method with a custom
Do not forget that you can cram any valid sh(1) expressions into the methods, pre-, and post-commands you define. Just invoking a function that makes the real job is a good style in most cases, but never let style limit your understanding of what is going on behind the curtain. | |
If we would like to implement custom arguments, which
can also be thought of as commands
to our script, we need to list them in
Note:The What do we get from the default method for
| |
Our script supports two non-standard commands,
Non-standard commands are not invoked during startup or shutdown. Usually they are for the system admin's convenience. They can also be used from other subsystems, e.g., devd(8) if specified in devd.conf(5). The full list of available commands can be found in the usage line printed by rc.subr(8) when the script is invoked without arguments. For example, here is the usage line from the script under study:
| |
A script can invoke its own standard or non-standard
commands if needed. This may look similar to calling
functions, but we know that commands and shell functions
are not always the same thing. For instance,
| |
A handy function named Keep in mind that for sh(1) a zero exit code means true and a non-zero exit code means false. Important:The The following is the correct usage of
if checkyesno mumbled_enable; then foo fi On the contrary, calling if checkyesno "${mumbled_enable}"; then foo fi | |
We can affect the flags to be
passed to | |
In certain cases we may need to emit an important
message that should go to syslog
as well. This can be done easily with the following
rc.subr(8) functions: | |
The exit codes from methods and their pre-commands
are not just ignored by default. If
Note:However, rc.subr(8) can be instructed from the
command line to ignore those exit codes and invoke all
commands anyway by prefixing an argument with
|
After a script has been written, it needs to be integrated
into rc.d
. The crucial step is to install
the script in /etc/rc.d
(for the base
system) or /usr/local/etc/rc.d
(for
ports). Both <bsd.prog.mk
> and
<bsd.port.mk
> provide convenient
hooks for that, and usually you do not have to worry about
the proper ownership and mode. System scripts should be
installed from src/etc/rc.d
through the
Makefile
found there. Port scripts can
be installed using USE_RC_SUBR
as described
in
the Porter's Handbook.
However, we should consider beforehand the place of our script in the system startup sequence. The service handled by our script is likely to depend on other services. For instance, a network daemon cannot function without the network interfaces and routing up and running. Even if a service seems to demand nothing, it can hardly start before the basic filesystems have been checked and mounted.
We mentioned rcorder(8) already. Now it is time to
have a close look at it. In a nutshell, rcorder(8) takes
a set of files, examines their contents, and prints a
dependency-ordered list of files from the set to
stdout
. The point is to keep dependency
information inside the files so that
each file can speak for itself only. A file can specify the
following information:
the names of the “conditions” (which means services to us) it provides;
the names of the “conditions” it requires;
the names of the “conditions” this file should run before;
additional keywords that can be used to select a subset from the whole set of files (rcorder(8) can be instructed via options to include or omit the files having particular keywords listed.)
It is no surprise that rcorder(8) can handle only text files with a syntax close to that of sh(1). That is, special lines understood by rcorder(8) look like sh(1) comments. The syntax of such special lines is rather rigid to simplify their processing. See rcorder(8) for details.
Besides using rcorder(8) special lines, a script can insist on its dependency upon another service by just starting it forcibly. This can be needed when the other service is optional and will not start by itself because the system admin has disabled it mistakenly in rc.conf(5).
With this general knowledge in mind, let us consider the simple daemon script enhanced with dependency stuff:
#!/bin/sh # PROVIDE: mumbled oldmumble # REQUIRE: DAEMON cleanvar frotz # BEFORE: LOGIN # KEYWORD: nojail shutdown . /etc/rc.subr name=mumbled rcvar=mumbled_enable command="/usr/sbin/${name}" start_precmd="${name}_prestart" mumbled_prestart() { if ! checkyesno frotz_enable && \ ! /etc/rc.d/frotz forcestatus 1>/dev/null 2>&1; then force_depend frotz || return 1 fi return 0 } load_rc_config $name run_rc_command "$1"
As before, detailed analysis follows:
That line declares the names of “conditions” our script provides. Now other scripts can record a dependency on our script by those names. Note:Usually a script specifies a single condition provided. However, nothing prevents us from listing several conditions there, e.g., for compatibility reasons. In any case, the name of the main, or the only,
| |
So our script indicates which “conditions”
provided by other scripts it depends on. According to
the lines, our script asks rcorder(8) to put it after
the script(s) providing Note:The Besides conditions corresponding to a single service
each, there are meta-conditions and their
“placeholder” scripts used to ensure that
certain groups of operations are performed before others.
These are denoted by
Keep in mind that putting a service name in the
| |
As we remember from the above text,
rcorder(8) keywords can be used to select or leave
out some scripts. Namely any rcorder(8) consumer
can specify through In FreeBSD, rcorder(8) is used by
| |
To begin with,
If you still cannot do without
|
When invoked during startup or shutdown, an
rc.d
script is supposed to act on the
entire subsystem it is responsible for. E.g.,
/etc/rc.d/netif
should start or stop all
network interfaces described by rc.conf(5). Either task
can be uniquely indicated by a single command argument such
as start
or stop
. Between
startup and shutdown, rc.d
scripts help
the admin to control the running system, and it is when the
need for more flexibility and precision arises. For instance,
the admin may want to add the settings of a new network
interface to rc.conf(5) and then to start it without
interfering with the operation of the existing interfaces.
Next time the admin may need to shut down a single network
interface. In the spirit of the command line, the respective
rc.d
script calls for an extra argument,
the interface name.
Fortunately, rc.subr(8) allows for passing any number of arguments to script's methods (within the system limits). Due to that, the changes in the script itself can be minimal.
How can rc.subr(8) gain
access to the extra command-line arguments. Should it just
grab them directly? Not by any means. Firstly, an sh(1)
function has no access to the positional parameters of
its caller, but rc.subr(8) is just a sack of such
functions. Secondly, the good manner of
rc.d
dictates that it is for the
main script to decide which arguments are to be passed
to its methods.
So the approach adopted by rc.subr(8) is as follows:
run_rc_command
passes on all its
arguments but the first one to the respective method verbatim.
The first, omitted, argument is the name of the method itself:
start
, stop
, etc. It will
be shifted out by run_rc_command
,
so what is $2
in the original command line will
be presented as $1
to the method, and so on.
To illustrate this opportunity, let us modify the primitive dummy script so that its messages depend on the additional arguments supplied. Here we go:
#!/bin/sh . /etc/rc.subr name="dummy" start_cmd="${name}_start" stop_cmd=":" kiss_cmd="${name}_kiss" extra_commands="kiss" dummy_start() { if [ $# -gt 0 ]; then echo "Greeting message: $*" else echo "Nothing started." fi } dummy_kiss() { echo -n "A ghost gives you a kiss" if [ $# -gt 0 ]; then echo -n " and whispers: $*" fi case "$*" in *[.!?]) echo ;; *) echo . ;; esac } load_rc_config $name run_rc_command "$@"
What essential changes can we notice in the script?
All arguments you type after
| |
The same applies to any method our script provides,
not only to a standard one. We have added a custom method
named
| |
If we want just to pass all extra arguments to
any method, we can merely substitute Important:An sh(1) programmer ought to understand the
subtle difference between Note:Currently |
The original
article by Luke Mewburn offers a general overview of
rc.d
and detailed rationale for its
design decisions. It provides insight on the whole
rc.d
framework and its place in a modern
BSD operating system.
The manual pages rc(8),
rc.subr(8), and rcorder(8) document the
rc.d
components in great detail. You
cannot fully use the rc.d
power without
studying the manual pages and referring to them while writing
your own scripts.
The major source of working, real-life examples is
/etc/rc.d
in a live system. Its contents
are easy and pleasant to read because most rough corners are
hidden deep in rc.subr(8). Keep in mind though that the
/etc/rc.d
scripts were not written by
angels, so they might suffer from bugs and suboptimal design
decisions. Now you can improve them!