It’s been quite some time since I last posted about FreeBSD packaging; today I’m coming back to it to talk a bit about other things that can go in the files/ directory. I just recently got packaging for OpenAFS in good enough shape to submit to the FreeBSD Ports Collection (the PR is here); there’s a little bit of cleverness in the Makefile that I’ll skip for now, in favor of the rc scripts.
Long, long ago, in the early days of Unix (read: before my time), there was a single shell script /etc/rc (the Jargon file claims it to stand for “runcom”) that would be run during system startup, executing commands to set up the local environment, start daemons, etc.. Eventually it grew so huge that it was split up into multiple files, and eventually a large infrastructure was created so that each service would have a script in /etc/rc.d/ and the administrator had mechanisms for controlling which scripts would be run when. Many of these controls are placed in /etc/rc.conf, and the rc scripts for software from the Ports Collection go in /usr/local/etc/rc.d to keep them separate from the base system.
Instead of just being a shell script that is sourced at startup, modern usage involves invocations such as:
/usr/local/etc/rc.d/afsd onestart /usr/local/etc/rc.d/afsd forcestop /usr/local/etc/rc.d/afsd start
with multiple variations on the “start” and “stop” commands. In order to be started, the appropriate rc.conf variable must be set to enable that service; onestart is a way to start it manually regardless. In order for this to work, each rc script has to define several shell functions that hook into the (massive) rc.subr (that’s “subroutine”) infrastructure. Here’s what I ended up with in files/afsd.in:
#!/bin/sh # # we require afsserver for the (rare, untested) case when a client # and server are running on the same machine -- the client must not # start until the server is running. # # PROVIDE: afsd # REQUIRE: afsserver named
These keywords are used to order all the rc scripts on system startup (and shutdown) — dependencies are declared explicitly.
. /etc/rc.subr
name="afsd"
rcvar="afsd_enable"
start_cmd="afsd_start"
start_precmd="afsd_prestart"
stop_cmd="afsd_stop"
command="%%PREFIX%%/sbin/${name}"
kmod="libafs"
vicedir="%%PREFIX%%/etc/openafs"
The reason for the .in suffix on this file is because it has variable substitution applied to it. Here, %%PREFIX%% gets expanded to the current prefix that the port is being built with; this is usually /usr/local but can be other things.
load_rc_config "$name"
eval "${rcvar}=\${${rcvar}:-'NO'}"
This is us checking if we’re listed in rc.conf; default to disabled if not mentioned.
afsd_prestart()
This is one of the functions that hooks into rc.subr — AFS requires several configuration files and a kernel module to be in place before it can start, so we check that they’re all there and give a useful error if not. This is quite helpful for users who are not familiar with how to start afsd manually.
{
# not going very far without a kernel module
if ! kldstat -qm afs; then
echo "Loading AFS kernel module..."
if ! kldload $kmod; then
echo "Failed to enable kernel support. Aborting."
return 1;
fi
fi
# now we have a kernel module; check for conffiles
for file in cacheinfo ThisCell CellServDB; do
if [ ! -f ${vicedir}/${file} ]; then
echo "${vicedir}/${file} does not exist. Not starting AFS client."
return 1
fi
done
# need a mountpoint and a cache dir (well, if we have a disk cache)
for dir in $(awk -F: '{print $1, $2}' ${vicedir}/cacheinfo); do
if [ ! -d ${dir} ]; then
echo "${dir} does not exist. Not starting AFS client."
return 2
fi
done
}
afsd_start()
{
# you probably don't want to change these
afsd_default_args="-memcache -dynroot -fakestat-all -afsdb"
# either set explicit extra args or just a size; default medium
afsd_args=${afsd_args:-'MEDIUM'}
case ${afsd_args} in
LARGE)
afsd_args="-stat 2800 -dcache 2400 -daemons 5 -volumes 128"
;;
MEDIUM)
afsd_args="-stat 2000 -dcache 800 -daemons 3 -volumes 70"
;;
SMALL)
afsd_args="-stat 300 -dcache 100 -daemons 2 -volumes 50"
;;
esac
${command} ${afsd_default_args} ${afs_args}
}
The actual start function. We check to see if we’ve been given extra arguments, using sane defaults if not. There are also a few things that you basically always want (for example, non-memcache is currently broken), which are listed in a local variable.
afsd_stop()
{
afsdir=$(awk -F: '{print $1}' ${vicedir}/cacheinfo)
umount ${afsdir}
_return=$?
[ "${_return}" -ne 0 ] && [ -n "${rc_force}" ] && umount -f ${afsdir}
kldunload ${kmod}
}
Stopping does not actually involve touching afsd at all — those processes will happily ignore whatever you throw at them. We must check that AFS is mounted (as someone might be erroneously running onestop), and then just run the umount command to stop things. We also check for whether force is being used, passing that on to umount if needed.
run_rc_command "$1"
This last line is very important! It looks very mundane, but it is how we actually interface with the rc system; rc.subr defines this function, which does all the necessary variable-name munging and calls the appropriate function(s) that we have defined.
At install time for the port, the substituted variables are replaced, and the script is installed into ${PREFIX}/etc/rc.d and added to the package list to be removed at deinstall time. All in all, we wrap a standard interface around the complicated afsd semantics.