In association with heise online

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


	
Print Version | Permalink: http://h-online.com/-848690
  • Twitter
  • Facebook
  • submit to slashdot
  • StumbleUpon
  • submit to reddit
 


  • July's Community Calendar





The H Open

The H Security

The H Developer

The H Internet Toolkit