Migrating init scripts
Its event-based design makes Upstart particularly useful for services which need to react to outside influences, such as VDR, which turns the computer into a digital video recorder.
On a desktop, it can reasonably be assumed that a DVB receiver card, once fitted into the machine, will always be present. By contrast, on a laptop which is to be used as a DVB-T TV with a USB receiver only intermittently plugged in, this is not necessarily the case. In this case, we only want VDR to run when the DVB-T receiver is connected – to achieve this we have to switch to using Upstart as our init script.
The VDR package's own init script needs to be deactivated to prevent it from inopportunely sticking its oar in. In Ubuntu, this can be achieved temporarily until the VDR package is next updated by using update-rc.d:
update-rc.d -f vdr remove
For Upstart to be aware that a DVB receiver has been connected, a udev rules file (see link) needs to be added under /etc/udev/rules.d: to trigger an Upstart event
SUBSYSTEM=="dvb", SUBSYSTEMS=="usb", ACTION=="add", \
KERNEL=="dvb*.dvr0", RUN+="/sbin/initctl \
--quiet emit --no-wait -e UDEV_KERNEL=$kernel \
-e UDEV_DEVPATH=$devpath dvb-device-add"
This udev rule applies only to USB DVB devices which create a DVB output device with the kernel designation dvbX.dvr0. If the kernel reports the presence of such a device, udev uses initctl emit
to generate the dvb-device-add
Upstart event. The --quiet
parameter tells initctl to skip the usual status messages.
All the other parameters relate to the initctl command emit
. Initctl normally waits until the triggered event has been processed in full. For a service, this means that the initctl call does not return until the service has stopped. This is avoided by using the --no-wait
parameter, which terminates initctl as soon as the Upstart event has been triggered.
The -e
parameter permits environment variables to be passed to the Upstart job, in this case UDEV_KERNEL
containing the device's kernel name and UDEV_DEVPATH
containing the path to the device tree under Sysfs.
The dvb Upstart job (see listing at the end of this article) takes care of the dvb-device-add event. Its job is to create a file, for each DVB device in the /var/run/dvb directory, which stores the Sysfs path to the device tree. This ensures that it is subsequently possible to trace which DVB device is associated with which USB device. Finally, the job triggers the vdr-start
Upstart signal.
The vdr Upstart job for calling VDR is trivial. It is triggered by the vdr-start
and vdr-stop
events. VDR also needs to run in runlevels 2 to 5 only.
stop on (vdr-stop
or runlevel [!2345])
exec /usr/sbin/vdr-upstart
It is the vdr-upstart script which does the spadework when it comes to calling VDR. It checks whether VDR is activated in the /etc/defaults/vdr file, loads various configuration files and calls the runvdr start script in the foreground. Vdr-upstart is based on the init script from the VDR package.
By adding the udev rule and the two Upstart jobs, we have ensured that VDR starts only if at least one DVB device is connected. If more than one is connected, it doesn't matter. The dvb job always terminates after creating the file containing the sysfs path in /var/run/dvb and triggering the vdr-start
Upstart event and is processed anew for each additional device.
VDR, by contrast, runs in the foreground. The job is therefore listed by initctl list
with the status 'running'. Upstart consequently ignores the vdr-start
start signal, preventing multiple instances of VDR from being started by multiple DVB receivers.
Phantom devices
Stopping VDR turns out to be a lot more complicated that starting it. VDR needs to be terminated only when the final DVB receiver is removed. As long as VDR is running, however, the program holds the DVB devices under /dev/dvb open, for which reason the kernel does not remove them even when the USB receiver has long been packed away – they are phantom devices. As long as VDR is still running therefore, no udev event corresponding to a DVB device being removed occurs. On top of this, since kernel 2.6.29, the device's Sysfs tree is only cleared out when the last device is closed. With VDR still running, a little educated guesswork is therefore required in order to realise that the DVB receiver has already been put away.
The solution is to monitor all events affecting the removed USB devices via udev.
SUBSYSTEMS=="usb", ACTION=="remove", \
RUN+="/sbin/initctl --quiet emit --no-wait \
-e UDEV_DEVPATH=$devpath device-remove"
The dvb Upstart job also reacts to the device-remove
event and checks the device path of the just-removed USB device against the device paths of the USB DVB receivers stored in the /var/run/dvb directory. If it finds a match, the job assumes that the DVB receiver in question has been removed and deletes the associated file in /var/run/dvb. Only once the final DVB receiver has been removed does the dvb job trigger the vdr-stop
Upstart event, in response VDR is stopped and udev clears out the DVB device entries downstream from /dev/dvb.
Our example illustrates the flexibility which can be achieved using Upstart, but also how complicated adapting the old init scripts to the Upstart concept is. It's likely to be a while yet before the last SysV init script is migrated to Upstart.
Udev-Job dvb.conf
env RUNDIR=/var/run/dvb
start on (dvb-device-add
or device-remove)
emits vdr-start vdr-stop
script
case "$UPSTART_EVENT" in
dvb-device-add)
mkdir -p $RUNDIR
echo ${UDEV_DEVPATH%/dvb/${UDEV_KERNEL}} >\
$RUNDIR/$UDEV_KERNEL
/sbin/initctl --quiet emit --no-wait vdr-start
;;
device-remove)
if [ -d $RUNDIR ]; then
for d in $RUNDIR/*; do
if [ -f $d ]; then
read basedev < $d
if [ -z "$basedev" -o "${UDEV_DEVPATH#${basedev}}" != \
"${UDEV_DEVPATH}" ]; then
rm -f $d
fi
fi
done
rmdir --ignore-fail-on-non-empty $RUNDIR
if [ ! -d $RUNDIR ]; then
/sbin/initctl --quiet emit --no-wait vdr-stop
fi
fi
;;
esac
done
end script