You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
804 lines
39 KiB
Plaintext
804 lines
39 KiB
Plaintext
---
|
|
title: Practical rc.d scripting in BSD
|
|
authors:
|
|
- author: Yar Tikhiy
|
|
email: yar@FreeBSD.org
|
|
copyright: 2005-2006, 2012 The FreeBSD Project
|
|
releaseinfo: "$FreeBSD$"
|
|
trademarks: ["freebsd", "netbsd", "general"]
|
|
---
|
|
|
|
= Practical rc.d scripting in BSD
|
|
:doctype: article
|
|
:toc: macro
|
|
:toclevels: 1
|
|
:icons: font
|
|
:sectnums:
|
|
:sectnumlevels: 6
|
|
:source-highlighter: rouge
|
|
:experimental:
|
|
|
|
include::shared/en/urls.adoc[]
|
|
|
|
[.abstract-title]
|
|
Abstract
|
|
|
|
Beginners may find it difficult to relate the facts from the formal documentation on the BSD [.filename]#rc.d# framework with the practical tasks of [.filename]#rc.d# scripting.
|
|
In this article, we consider a few typical cases of increasing complexity, show [.filename]#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 [.filename]#rc.d#.
|
|
|
|
'''
|
|
|
|
toc::[]
|
|
|
|
[[rcng-intro]]
|
|
== Introduction
|
|
|
|
The historical BSD had a monolithic startup script, [.filename]#/etc/rc#.
|
|
It was invoked by man: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, [.filename]#/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 [.filename]#/etc/rc#.
|
|
For instance, [.filename]#/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 [.filename]#/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 [.filename]#/etc/rc# for the sake of starting the most important subsystems separately.
|
|
The notorious example was [.filename]#/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 [.filename]#/etc/netstart# mutated into [.filename]#/etc/rc.network#.
|
|
The latter was no longer an ordinary script; it comprised of large, tangled man:sh[1] functions called from [.filename]#/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 [.filename]#/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 [.filename]#rc# system.
|
|
Thus BSD [.filename]#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 [.filename]#/etc/rc.d#.
|
|
Soon we will learn about more components of the [.filename]#rc.d# system and see how the individual scripts are invoked.
|
|
|
|
The basic ideas behind BSD [.filename]#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 man: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 [.filename]#/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 [.filename]#/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 man:sh[1] functions and collected in [.filename]#/etc/rc.subr#.
|
|
Now a typical script can be just a few lines' worth of man:sh[1] code.
|
|
Finally, an important part of the [.filename]#rc.d# framework is man:rcorder[8], which helps [.filename]#/etc/rc# to run the small scripts orderly with respect to dependencies between them.
|
|
It can help [.filename]#/etc/rc.shutdown#, too, because the proper order for the shutdown sequence is opposite to that of startup.
|
|
|
|
The BSD [.filename]#rc.d# design is described in <<lukem, the original article by Luke Mewburn>>, and the [.filename]#rc.d# components are documented in great detail in <<manpages, the respective manual pages>>.
|
|
However, it might not appear obvious to an [.filename]#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 [.filename]#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 [.filename]#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 man:sh[1] scripting language in order to master [.filename]#rc.d#.
|
|
In addition, you should know how the system performs userland startup and shutdown tasks, which is described in man:rc[8].
|
|
|
|
This article focuses on the FreeBSD branch of [.filename]#rc.d#.
|
|
Nevertheless, it may be useful to NetBSD developers, too, because the two branches of BSD [.filename]#rc.d# not only share the same design but also stay similar in their aspects visible to script authors.
|
|
|
|
[[rcng-task]]
|
|
== Outlining the task
|
|
|
|
A little consideration before starting `$EDITOR` will not hurt.
|
|
In order to write a well-tempered [.filename]#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.
|
|
|
|
[[rcng-dummy]]
|
|
== A dummy script
|
|
|
|
The following script just emits a message each time the system boots up:
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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 man:chmod[1].)
|
|
For example, a system admin can run our script manually, from the command line:
|
|
|
|
[source,shell]
|
|
....
|
|
# /etc/rc.d/dummy start
|
|
....
|
|
|
|
[NOTE]
|
|
====
|
|
In order to be properly managed by the [.filename]#rc.d# framework, its scripts need to be written in the man:sh[1] language.
|
|
If you have a service or port that uses a binary control utility or a startup routine written in another language, install that element in [.filename]#/usr/sbin# (for the system) or [.filename]#/usr/local/sbin# (for ports) and call it from a man:sh[1] script in the appropriate [.filename]#rc.d# directory.
|
|
====
|
|
|
|
[TIP]
|
|
====
|
|
If you would like to learn the details of why [.filename]#rc.d# scripts must be written in the man:sh[1] language, see how [.filename]#/etc/rc# invokes them by means of `run_rc_script`, then study the implementation of `run_rc_script` in [.filename]#/etc/rc.subr#.
|
|
====
|
|
|
|
➋ In [.filename]#/etc/rc.subr#, a number of man:sh[1] functions are defined for an [.filename]#rc.d# script to use.
|
|
The functions are documented in man:rc.subr[8].
|
|
While it is theoretically possible to write an [.filename]#rc.d# script without ever using man:rc.subr[8], its functions prove extremely handy and make the job an order of magnitude easier. So it is no surprise that everybody resorts to man:rc.subr[8] in [.filename]#rc.d# scripts.
|
|
We are not going to be an exception.
|
|
|
|
An [.filename]#rc.d# script must "source"[.filename]#/etc/rc.subr# (include it using "`.`") _before_ it calls man:rc.subr[8] functions so that man:sh[1] has an opportunity to learn the functions.
|
|
The preferred style is to source [.filename]#/etc/rc.subr# first of all.
|
|
|
|
[NOTE]
|
|
====
|
|
Some useful functions related to networking are provided by another include file, [.filename]#/etc/network.subr#.
|
|
====
|
|
|
|
➌ [[name-var]]The mandatory variable `name` specifies the name of our script.
|
|
It is required by man:rc.subr[8].
|
|
That is, each [.filename]#rc.d# script _must_ set `name` before it calls man:rc.subr[8] functions.
|
|
|
|
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 [.filename]#rc.d# scripting is to enclose values assigned to variables in double quotes.
|
|
Keep in mind that it is just a style issue that may not always be applicable.
|
|
You can safely omit quotes from around simple words without man:sh[1] metacharacters in them, while in certain cases you will need single quotes to prevent any interpretation of the value by man:sh[1].
|
|
A programmer should be able to tell the language syntax from style conventions and use both of them wisely.
|
|
====
|
|
|
|
➍ The main idea behind man:rc.subr[8] is that an [.filename]#rc.d# script provides handlers, or methods, for man:rc.subr[8] to invoke.
|
|
In particular, `start`, `stop`, and other arguments to an [.filename]#rc.d# script are handled this way.
|
|
A method is a man:sh[1] expression stored in a variable named `argument_cmd`, where _argument_ corresponds to what can be specified on the script's command line.
|
|
We will see later how man:rc.subr[8] provides default methods for the standard arguments.
|
|
|
|
[NOTE]
|
|
====
|
|
To make the code in [.filename]#rc.d# more uniform, it is common to use `${name}` wherever appropriate.
|
|
Thus a number of lines can be just copied from one script to another.
|
|
====
|
|
|
|
➎ We should keep in mind that man:rc.subr[8] provides default methods for the standard arguments.
|
|
Consequently, we must override a standard method with a no-op man: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 `${name}` to the names of all functions defined in our script so they never clash with the functions from man:rc.subr[8] or another common include file.
|
|
====
|
|
|
|
➐ This call to man:rc.subr[8] loads man:rc.conf[5] variables.
|
|
Our script makes no use of them yet, but it still is recommended to load man:rc.conf[5] because there can be man:rc.conf[5] variables controlling man:rc.subr[8] itself.
|
|
|
|
➑ Usually this is the last command in an [.filename]#rc.d# script.
|
|
It invokes the man:rc.subr[8] machinery to perform the requested action using the variables and methods our script has provided.
|
|
|
|
[[rcng-confdummy]]
|
|
== A configurable dummy script
|
|
|
|
Now let us add some controls to our dummy script.
|
|
As you may know, [.filename]#rc.d# scripts are controlled with man:rc.conf[5].
|
|
Fortunately, man:rc.subr[8] hides all the complications from us.
|
|
The following script uses man:rc.conf[5] via man: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 [.filename]#rc.d# script can just support enabling and disabling its service.
|
|
On the other hand, a mandatory [.filename]#rc.d# script can have configuration variables.
|
|
We will do both things in the same script though:
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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 `rcvar` specifies the name of the ON/OFF knob variable.
|
|
|
|
➋ Now `load_rc_config` is invoked earlier in the script, before any man:rc.conf[5] variables are accessed.
|
|
|
|
[NOTE]
|
|
====
|
|
While examining [.filename]#rc.d# scripts, keep in mind that man:sh[1] defers the evaluation of expressions in a function until the latter is called.
|
|
Therefore it is not an error to invoke `load_rc_config` as late as just before `run_rc_command` and still access man:rc.conf[5] variables from the method functions exported to `run_rc_command`.
|
|
This is because the method functions are to be called by `run_rc_command`, which is invoked _after_ `load_rc_config`.
|
|
====
|
|
|
|
➌ A warning will be emitted by `run_rc_command` if `rcvar` itself is set, but the indicated knob variable is unset.
|
|
If your [.filename]#rc.d# script is for the base system, you should add a default setting for the knob to [.filename]#/etc/defaults/rc.conf# and document it in man:rc.conf[5].
|
|
Otherwise it is your script that should provide a default setting for the knob.
|
|
The canonical approach to the latter case is shown in the example.
|
|
|
|
[NOTE]
|
|
====
|
|
You can make man:rc.subr[8] act as though the knob is set to `ON`, irrespective of its current setting, by prefixing the argument to the script with `one` or `force`, as in `onestart` or `forcestop`.
|
|
Keep in mind though that `force` has other dangerous effects we will touch upon below, while `one` just overrides the ON/OFF knob.
|
|
E.g., assume that `dummy_enable` is `OFF`.
|
|
The following command will run the `start` method in spite of the setting:
|
|
|
|
[source,shell]
|
|
....
|
|
# /etc/rc.d/dummy onestart
|
|
....
|
|
|
|
====
|
|
|
|
➍ Now the message to be shown at boot time is no longer hard-coded in the script.
|
|
It is specified by an man:rc.conf[5] variable named `dummy_msg`.
|
|
This is a trivial example of how man:rc.conf[5] variables can control an [.filename]#rc.d# script.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
The names of all man:rc.conf[5] variables used exclusively by our script _must_ have the same prefix: `${name}_`.
|
|
For example: `dummy_mode`, `dummy_state_file`, and so on.
|
|
====
|
|
|
|
[NOTE]
|
|
====
|
|
While it is possible to use a shorter name internally, e.g., just `msg`, adding the unique prefix `${name}_` to all global names introduced by our script will save us from possible collisions with the man:rc.subr[8] namespace.
|
|
|
|
As a rule, [.filename]#rc.d# scripts of the base system need not provide defaults for their man:rc.conf[5] variables because the defaults should be set in [.filename]#/etc/defaults/rc.conf# instead.
|
|
On the other hand, [.filename]#rc.d# scripts for ports should provide the defaults as shown in the example.
|
|
====
|
|
|
|
➎ Here we use `dummy_msg` to actually control our script, i.e., to emit a variable message.
|
|
Use of a shell function is overkill here, since it only runs a single command; an equally valid alternative is:
|
|
|
|
[.programlisting]
|
|
....
|
|
start_cmd="echo \"$dummy_msg\""
|
|
....
|
|
|
|
[[rcng-daemon]]
|
|
== Startup and shutdown of a simple daemon
|
|
|
|
We said earlier that man: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 [.filename]#rc.d# script for such a daemon called `mumbled`.
|
|
Here it is:
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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 `command` variable is meaningful to man:rc.subr[8].
|
|
If it is set, man:rc.subr[8] will act according to the scenario of serving a conventional daemon.
|
|
In particular, the default methods will be provided for such arguments: `start`, `stop`, `restart`, `poll`, and `status`.
|
|
|
|
The daemon will be started by running `$command` with command-line flags specified by `$mumbled_flags`.
|
|
Thus all the input data for the default `start` method are available in the variables set by our script.
|
|
Unlike `start`, other methods may require additional information about the process started.
|
|
For instance, `stop` must know the PID of the process to terminate it.
|
|
In the present case, man:rc.subr[8] will scan through the list of all processes, looking for a process with its name equal to `procname`.
|
|
The latter is another variable of meaning to man:rc.subr[8], and its value defaults to that of `command`.
|
|
In other words, when we set `command`, `procname` is effectively set to the same value.
|
|
This enables our script to kill the daemon and to check if it is running in the first place.
|
|
|
|
[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 man:rc.subr[8].
|
|
You should additionally set `command_interpreter` to let man:rc.subr[8] know the actual name of the process if `$command` is a script.
|
|
|
|
For each [.filename]#rc.d# script, there is an optional man:rc.conf[5] variable that takes precedence over `command`.
|
|
Its name is constructed as follows: `${name}_program`, where `name` is the mandatory variable we discussed <<name-var, earlier>>.
|
|
E.g., in this case it will be `mumbled_program`.
|
|
It is man:rc.subr[8] that arranges `${name}_program` to override `command`.
|
|
|
|
Of course, man:sh[1] will permit you to set `${name}_program` from man:rc.conf[5] or the script itself even if `command` is unset.
|
|
In that case, the special properties of `${name}_program` are lost, and it becomes an ordinary variable your script can use for its own purposes.
|
|
However, the sole use of `${name}_program` is discouraged because using it together with `command` became an idiom of [.filename]#rc.d# scripting.
|
|
====
|
|
|
|
For more detailed information on default methods, refer to man:rc.subr[8].
|
|
|
|
[[rcng-daemon-adv]]
|
|
== Startup and shutdown of an advanced daemon
|
|
|
|
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.
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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 `$command` can be passed in `command_args`.
|
|
They will be added to the command line after `$mumbled_flags`.
|
|
Since the final command line is passed to `eval` for its actual execution, input and output redirections can be specified in `command_args`.
|
|
|
|
[NOTE]
|
|
====
|
|
_Never_ include dashed options, like `-X` or `--foo`, in `command_args`.
|
|
The contents of `command_args` will appear at the end of the final command line, hence they are likely to follow arguments present in `${name}_flags`; but most commands will not recognize dashed options after ordinary arguments.
|
|
A better way of passing additional options to `$command` is to add them to the beginning of `${name}_flags`.
|
|
Another way is to modify `rc_flags` <<rc-flags, as shown later>>.
|
|
====
|
|
|
|
➋ A good-mannered daemon should create a _pidfile_ so that its process can be found more easily and reliably.
|
|
The variable `pidfile`, if set, tells man:rc.subr[8] where it can find the pidfile for its default methods to use.
|
|
|
|
[NOTE]
|
|
====
|
|
In fact, man: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 `faststart` argument.
|
|
====
|
|
|
|
➌ If the daemon cannot run unless certain files exist, just list them in `required_files`, and man:rc.subr[8] will check that those files do exist before starting the daemon.
|
|
There also are `required_dirs` and `required_vars` for directories and environment variables, respectively.
|
|
They all are described in detail in man:rc.subr[8].
|
|
|
|
[NOTE]
|
|
====
|
|
The default method from man:rc.subr[8] can be forced to skip the prerequisite checks by using `forcestart` as the argument to the script.
|
|
====
|
|
|
|
➍ We can customize signals to send to the daemon in case they differ from the well-known ones.
|
|
In particular, `sig_reload` specifies the signal that makes the daemon reload its configuration; it is SIGHUP by default.
|
|
Another signal is sent to stop the daemon process;
|
|
the default is SIGTERM, but this can be changed by setting `sig_stop` appropriately.
|
|
|
|
[NOTE]
|
|
====
|
|
The signal names should be specified to man:rc.subr[8] without the `SIG` prefix, as it is shown in the example.
|
|
The FreeBSD version of man:kill[1] can recognize the `SIG` prefix, but the versions from other OS types may not.
|
|
====
|
|
|
|
➎➏ Performing additional tasks before or after the default methods is easy.
|
|
For each command-argument supported by our script, we can define `argument_precmd` and `argument_postcmd`.
|
|
These man:sh[1] commands are invoked before and after the respective method, as it is evident from their names.
|
|
|
|
[NOTE]
|
|
====
|
|
Overriding a default method with a custom `argument_cmd` still does not prevent us from making use of `argument_precmd` or `argument_postcmd` if we need to.
|
|
In particular, the former is good for checking custom, sophisticated conditions that should be met before performing the command itself.
|
|
Using `argument_precmd` along with `argument_cmd` lets us logically separate the checks from the action.
|
|
|
|
Do not forget that you can cram any valid man: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 `extra_commands` and provide methods to handle them.
|
|
|
|
[NOTE]
|
|
====
|
|
The `reload` command is special. On the one hand, it has a preset method in man:rc.subr[8].
|
|
On the other hand, `reload` is not offered by default.
|
|
The reason is that not all daemons use the same reload mechanism and some have nothing to reload at all.
|
|
So we need to ask explicitly that the builtin functionality be provided.
|
|
We can do so via `extra_commands`.
|
|
|
|
What do we get from the default method for `reload`? Quite often daemons reload their configuration upon reception of a signal - typically, SIGHUP.
|
|
Therefore man:rc.subr[8] attempts to reload the daemon by sending a signal to it.
|
|
The signal is preset to SIGHUP but can be customized via `sig_reload` if necessary.
|
|
====
|
|
|
|
➑⓮ Our script supports two non-standard commands, `plugh` and `xyzzy`.
|
|
We saw them listed in `extra_commands`, and now it is time to provide methods for them.
|
|
The method for `xyzzy` is just inlined while that for `plugh` is implemented as the `mumbled_plugh` function.
|
|
|
|
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., man:devd[8] if specified in man:devd.conf[5].
|
|
|
|
The full list of available commands can be found in the usage line printed by man:rc.subr[8] when the script is invoked without arguments.
|
|
For example, here is the usage line from the script under study:
|
|
|
|
[source,shell]
|
|
....
|
|
# /etc/rc.d/mumbled
|
|
Usage: /etc/rc.d/mumbled [fast|force|one](start|stop|restart|rcvar|reload|plugh|xyzzy|status|poll)
|
|
....
|
|
|
|
⓭ 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, `xyzzy` is not implemented as a function here.
|
|
In addition, there can be a pre-command and post-command, which should be invoked orderly.
|
|
So the proper way for a script to run its own command is by means of man:rc.subr[8], as shown in the example.
|
|
|
|
➒ A handy function named `checkyesno` is provided by man:rc.subr[8].
|
|
It takes a variable name as its argument and returns a zero exit code if and only if the variable is set to `YES`, or `TRUE`, or `ON`, or `1`, case insensitive;
|
|
a non-zero exit code is returned otherwise.
|
|
In the latter case, the function tests the variable for being set to `NO`, `FALSE`, `OFF`, or `0`, case insensitive;
|
|
it prints a warning message if the variable contains anything else, i.e., junk.
|
|
|
|
Keep in mind that for man:sh[1] a zero exit code means true and a non-zero exit code means false.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
The `checkyesno` function takes a __variable name__.
|
|
Do not pass the expanded _value_ of a variable to it; it will not work as expected.
|
|
|
|
The following is the correct usage of `checkyesno`:
|
|
|
|
[.programlisting]
|
|
....
|
|
if checkyesno mumbled_enable; then
|
|
foo
|
|
fi
|
|
....
|
|
|
|
On the contrary, calling `checkyesno` as shown below will not work - at least not as expected:
|
|
|
|
[.programlisting]
|
|
....
|
|
if checkyesno "${mumbled_enable}"; then
|
|
foo
|
|
fi
|
|
....
|
|
|
|
====
|
|
|
|
➓ [[rc-flags]]We can affect the flags to be passed to `$command` by modifying `rc_flags` in `$start_precmd`.
|
|
|
|
⓫ 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 man:rc.subr[8] functions: `debug`, `info`, `warn`, and `err`.
|
|
The latter function then exits the script with the code specified.
|
|
|
|
⓬ The exit codes from methods and their pre-commands are not just ignored by default.
|
|
If `argument_precmd` returns a non-zero exit code, the main method will not be performed.
|
|
In turn, `argument_postcmd` will not be invoked unless the main method returns a zero exit code.
|
|
|
|
[NOTE]
|
|
====
|
|
However, man: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 `force`, as in `forcestart`.
|
|
====
|
|
|
|
[[rcng-hookup]]
|
|
== Connecting a script to the rc.d framework
|
|
|
|
After a script has been written, it needs to be integrated into [.filename]#rc.d#.
|
|
The crucial step is to install the script in [.filename]#/etc/rc.d# (for the base system) or [.filename]#/usr/local/etc/rc.d# (for ports).
|
|
Both [.filename]#bsd.prog.mk# and [.filename]#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 [.filename]#src/etc/rc.d# through the [.filename]#Makefile# found there.
|
|
Port scripts can be installed using `USE_RC_SUBR` as described link:{porters-handbook}#rc-scripts[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 man:rcorder[8] already.
|
|
Now it is time to have a close look at it.
|
|
In a nutshell, man: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 (man:rcorder[8] can be instructed via options to include or omit the files having particular keywords listed.)
|
|
|
|
It is no surprise that man:rcorder[8] can handle only text files with a syntax close to that of man:sh[1].
|
|
That is, special lines understood by man:rcorder[8] look like man:sh[1] comments.
|
|
The syntax of such special lines is rather rigid to simplify their processing.
|
|
See man:rcorder[8] for details.
|
|
|
|
Besides using man: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 man:rc.conf[5].
|
|
|
|
With this general knowledge in mind, let us consider the simple daemon script enhanced with dependency stuff:
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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, `PROVIDE:` condition should be the same as `${name}`.
|
|
====
|
|
|
|
➋➌ So our script indicates which "conditions" provided by other scripts it depends on.
|
|
According to the lines, our script asks man:rcorder[8] to put it after the script(s) providing [.filename]#DAEMON# and [.filename]#cleanvar#, but before that providing [.filename]#LOGIN#.
|
|
|
|
[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 [.filename]#UPPERCASE# names.
|
|
Their list and purposes can be found in man: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 man:rc.conf[5].
|
|
Obviously, man:rcorder[8] cannot track such details, and man: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 <<forcedep, below>>
|
|
====
|
|
|
|
[[keywords]]➍ As we remember from the above text, man:rcorder[8] keywords can be used to select or leave out some scripts.
|
|
Namely any man:rcorder[8] consumer can specify through `-k` and `-s` options which keywords are on the "keep list" and "skip list", respectively.
|
|
From all the files to be dependency sorted, man: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, man:rcorder[8] is used by [.filename]#/etc/rc# and [.filename]#/etc/rc.shutdown#.
|
|
These two scripts define the standard list of FreeBSD [.filename]#rc.d# keywords and their meanings as follows:
|
|
|
|
[[forcedep]]➎ To begin with, `force_depend` should be used with much care.
|
|
It is generally better to revise the hierarchy of configuration variables for your [.filename]#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 man:rcorder[8] knows nothing about such details.
|
|
Fortunately, our script has access to all man:rc.conf[5] variables.
|
|
If `frotz_enable` is true, we hope for the best and rely on [.filename]#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.
|
|
|
|
[[rcng-args]]
|
|
== Giving more flexibility to an rc.d script
|
|
|
|
When invoked during startup or shutdown, an [.filename]#rc.d# script is supposed to act on the entire subsystem it is responsible for.
|
|
E.g., [.filename]#/etc/rc.d/netif# should start or stop all network interfaces described by man:rc.conf[5].
|
|
Either task can be uniquely indicated by a single command argument such as `start` or `stop`.
|
|
Between startup and shutdown, [.filename]#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 man: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 [.filename]#rc.d# script calls for an extra argument, the interface name.
|
|
|
|
Fortunately, man: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 man:rc.subr[8] gain access to the extra command-line arguments.
|
|
Should it just grab them directly? Not by any means.
|
|
Firstly, an man:sh[1] function has no access to the positional parameters of its caller, but man:rc.subr[8] is just a sack of such functions.
|
|
Secondly, the good manner of [.filename]#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 man: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:
|
|
|
|
[.programlisting]
|
|
....
|
|
#!/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 `start` can end up as positional parameters to the respective method.
|
|
We can use them in any way according to our task, skills, and fancy.
|
|
In the current example, we just pass all of them to man:echo[1] as one string in the next line - note `$*` within the double quotes.
|
|
Here is how the script can be invoked now:
|
|
|
|
[source,shell]
|
|
....
|
|
# /etc/rc.d/dummy start
|
|
Nothing started.
|
|
|
|
# /etc/rc.d/dummy start Hello world!
|
|
Greeting message: Hello world!
|
|
....
|
|
|
|
➋ The same applies to any method our script provides, not only to a standard one.
|
|
We have added a custom method named `kiss`, and it can take advantage of the extra arguments not less than `start` does. E.g.:
|
|
|
|
[source,shell]
|
|
....
|
|
# /etc/rc.d/dummy kiss
|
|
A ghost gives you a kiss.
|
|
|
|
# /etc/rc.d/dummy kiss Once I was Etaoin Shrdlu...
|
|
A ghost gives you a kiss and whispers: Once I was Etaoin Shrdlu...
|
|
....
|
|
|
|
➌ If we want just to pass all extra arguments to any method, we can merely substitute `"$@"` for `"$1"` in the last line of our script, where we invoke `run_rc_command`.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
An man:sh[1] programmer ought to understand the subtle difference between `$*` and `$@` as the ways to designate all positional parameters.
|
|
For its in-depth discussion, refer to a good handbook on man:sh[1] scripting.
|
|
_Do not_ use the expressions until you fully understand them because their misuse will result in buggy and insecure scripts.
|
|
====
|
|
|
|
[NOTE]
|
|
====
|
|
Currently `run_rc_command` may have a bug that prevents it from keeping the original boundaries between arguments.
|
|
That is, arguments with embedded whitespace may not be processed correctly.
|
|
The bug stems from `$*` misuse.
|
|
====
|
|
|
|
[[rcng-furthur]]
|
|
== Further reading
|
|
|
|
[[lukem]]http://www.mewburn.net/luke/papers/rc.d.pdf[The original article by Luke Mewburn] offers a general overview of [.filename]#rc.d# and detailed rationale for its design decisions.
|
|
It provides insight on the whole [.filename]#rc.d# framework and its place in a modern BSD operating system.
|
|
|
|
[[manpages]]The manual pages man:rc[8], man:rc.subr[8], and man:rcorder[8] document the [.filename]#rc.d# components in great detail.
|
|
You cannot fully use the [.filename]#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 [.filename]#/etc/rc.d# in a live system.
|
|
Its contents are easy and pleasant to read because most rough corners are hidden deep in man:rc.subr[8].
|
|
Keep in mind though that the [.filename]#/etc/rc.d# scripts were not written by angels, so they might suffer from bugs and suboptimal design decisions.
|
|
Now you can improve them!
|