rc.d scripts are used to start services on system startup, and to give administrators a standard way of stopping, starting and restarting the service. Ports integrate into the system rc.d framework. Details on its usage can be found in the rc.d Handbook chapter. Detailed explanation of available commands is provided in rc(8) and rc.subr(8). Finally, there is an article on practical aspects of rc.d scripting.
One or more rc.d scripts can be installed:
USE_RC_SUBR= doormand
Scripts must be placed in the files subdirectory and a .in suffix must be added to their filename. Standard SUB_LIST expansions will be used for this file. Use of the %%PREFIX%% and %%LOCALBASE%% expansions is strongly encouraged as well. More on SUB_LIST in the relevant section.
Prior to FreeBSD 6.1-RELEASE, integration with rcorder(8) is available by using USE_RCORDER instead of USE_RC_SUBR. However, use of this method is not necessary unless the port has an option to install itself in the base, or the service needs to run prior to the FILESYSTEMS rc.d script in the base.
As of FreeBSD 6.1-RELEASE, local rc.d scripts (including those installed by ports) are included in the overall rcorder(8) of the base system.
Example simple rc.d script:
#!/bin/sh # $FreeBSD$ # # PROVIDE: doormand # REQUIRE: LOGIN # KEYWORD: shutdown # # Add the following lines to /etc/rc.conf.local or /etc/rc.conf # to enable this service: # # doormand_enable (bool): Set to NO by default. # Set it to YES to enable doormand. # doormand_config (path): Set to %%PREFIX%%/etc/doormand/doormand.cf # by default. . /etc/rc.subr name=doormand rcvar=doormand_enable load_rc_config $name : ${doormand_enable:="NO"} : ${doormand_config="%%PREFIX%%/etc/doormand/doormand.cf"} command=%%PREFIX%%/sbin/${name} pidfile=/var/run/${name}.pid command_args="-p $pidfile -f $doormand_config" run_rc_command "$1"
Unless there is a good reason to start the service earlier all ports scripts should use
REQUIRE: LOGIN
If the service runs as a particular user (other than root) this is mandatory.
KEYWORD: shutdown
is included in the script above because the mythical port we are using as an example starts a service, and should be shut down cleanly when the system shuts down. If the script is not starting a persistent service this is not necessary.
For optional configuration elements the "=" style of default variable assignment is preferable to the ":=" style here, since the former sets a default value only if the variable is unset, and the latter sets one if the variable is unset or null. A user might very well include something like
doormand_flags=""
in their rc.conf.local file, and a variable substitution using ":=" would inappropriately override the user's intention. The _enable variable is not optional, and should use the ":" for the default.
Note: No new scripts should be added with the .sh suffix.
It is possible to have a service stopped automatically as part of the deinstall routine. We advise using this feature only when it is absolutely necessary to stop a service before its files go away. Usually, it is up to the administrator's discretion to decide, whether to stop the service on deinstall or not. Also note this affects upgrades, too.
A line like this goes in the pkg-plist:
@stopdaemon doormand
The argument must match the content of USE_RC_SUBR variable.
Before contributing a port with an rc.d script, and more importantly, before committing one, please consult the following checklist to be sure that it is ready.
If this is a new file, does it have .sh in the file name? If so that should be changed to just file.in since new rc.d files may not end with that extension.
Does the file have a $FreeBSD$ tag?
Do the name of the file (minus .in), the PROVIDE line, and $name all match? The file name matching PROVIDE makes debugging easier, especially for rcorder(8) issues. Matching the file name and $name makes it easier to figure out which variables are relevant in rc.conf[.local]. The latter is also what you might call “policy” for all new scripts, including those in the base system.
Is the REQUIRE line set to LOGIN? This is mandatory for scripts that run as a non-root user. If it runs as root, is there a good reason for it to run prior to LOGIN? If not, it should run there so that we can loosely group local scripts to a point in rcorder(8) after most everything in the base is already running.
Does the script start a persistent service? If so, it should have KEYWORD: shutdown.
Make sure there is no KEYWORD: FreeBSD present. This has not been necessary or desirable for years. It is also an indication that the new script was copy/pasted from an old script, so extra caution should be given to the review.
If the script uses an interpreted language like perl,
python, or ruby, make certain
that command_interpreter
is set appropriately.
Otherwise,
# service name stop
will probably not work properly. See service(8) for more information.
Have all occurrences of /usr/local been replaced with %%PREFIX%%?
Do the default variable assignments come after load_rc_config
?
Are there default assignments to empty strings? They should be removed, but double-check that the option is documented in the comments at the top of the file.
Are things that are set in variables actually used in the script?
Are options listed in the default name_flags
things that are actually mandatory? If so, they
should be in command_args
. The -d
option is a red flag (pardon the pun) here, since it is
usually the option to “daemonize” the process, and therefore is
actually mandatory.
The name_flags
variable should never be included in command_args
(and vice versa, although that error is less
common).
Does the script execute any code unconditionally? This is frowned on. Usually
these things can/should be dealt with through a start_precmd
.
All boolean tests should utilize the checkyesno
function. No hand-rolled tests for [Yy][Ee][Ss],
etc.
If there is a loop (for example, waiting for something to start) does it have a counter to terminate the loop? We do not want the boot to be stuck forever if there is an error.
Does the script create files or directories that need specific permissions, for example, a pid file that needs to be owned by the user that runs the process? Rather than the traditional touch(1)/chown(8)/chmod(1) routine, consider using install(1) with the proper command line arguments to do the whole procedure with one step.