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=`set_rcvar` 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:
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, PROVIDE: condition should be the same as ${name}.
Note: The BEFORE: line should not be abused to work around an incomplete dependency list in the other script. The appropriate case for using BEFORE: is when the other script does not care about ours, but our script can do its task better if run before the other one. A typical real-life example is the network interfaces vs. the firewall: While the interfaces do not depend on the firewall in doing their job, the system security will benefit from the firewall being ready before there is any network traffic.
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 UPPERCASE names. Their list and purposes can be found in rc(8).
Keep in mind that putting a service name in the REQUIRE: line does not guarantee that the service will actually be running by the time our script starts. The required service may fail to start or just be disabled in rc.conf(5). Obviously, rcorder(8) cannot track such details, and rc(8) will not do that either. Consequently, the application started by our script should be able to cope with any required services being unavailable. In certain cases, we can help it as discussed below.
-k
and -s
options which keywords are on the “keep list” and “skip list”,
respectively. From all the files to be dependency sorted, rcorder(8) will pick
only those having a keyword from the keep list (unless empty) and not having a keyword
from the skip list.In FreeBSD, rcorder(8) is used by /etc/rc and /etc/rc.shutdown. These two scripts define the standard list of FreeBSD rc.d keywords and their meanings as follows:
The service is not for jail(8) environment. The automatic startup and shutdown procedures will ignore the script if inside a jail.
The service is to be started manually or not started at all. The automatic startup procedure will ignore the script. In conjunction with the shutdown keyword, this can be used to write scripts that do something only at system shutdown.
This keyword is to be listed explicitly if the service needs to be stopped before system shutdown.
Note: When the system is going to shut down, /etc/rc.shutdown runs. It assumes that most rc.d scripts have nothing to do at that time. Therefore /etc/rc.shutdown selectively invokes rc.d scripts with the shutdown keyword, effectively ignoring the rest of the scripts. For even faster shutdown, /etc/rc.shutdown passes the
faststop
command to the scripts it runs so that they skip preliminary checks, e.g., the pidfile check. As dependent services should be stopped before their prerequisites, /etc/rc.shutdown runs the scripts in reverse dependency order.If writing a real rc.d script, you should consider whether it is relevant at system shutdown time. E.g., if your script does its work in response to the
start
command only, then you need not include this keyword. However, if your script manages a service, it is probably a good idea to stop it before the system proceeds to the final stage of its shutdown sequence described in halt(8). In particular, a service should be stopped explicitly if it needs considerable time or special actions to shut down cleanly. A typical example of such a service is a database engine.
force_depend
should be used with much care. It is generally
better to revise the hierarchy of configuration variables for your rc.d scripts if they are interdependent.If you still cannot do without force_depend
, the example
offers an idiom of how to invoke it conditionally. In the example, our mumbled daemon requires that another one, frotz, be started in advance. However, frotz is optional, too; and rcorder(8) knows
nothing about such details. Fortunately, our script has access to all rc.conf(5) variables.
If frotz_enable is true, we hope for the best and rely on rc.d to have started frotz. Otherwise we
forcibly check the status of frotz. Finally, we enforce our
dependency on frotz if it is found to be not running. A warning
message will be emitted by force_depend
because it should
be invoked only if a misconfiguration has been detected.