Durch das Event-Konzept empfiehlt sich Upstart besonders für Dienste, die in Abhängigkeit von anderen äußeren Einflüssen gesteuert werden sollen. Ein Beispiel dafür ist das Programm VDR, das den Rechner zum Festplattenrecorder macht.
Bei Desktop-PCs kann man getrost davon ausgehen, dass eine einmal eingebaute DVB-Empfangskarte ständig präsent ist. Bei Notebooks hingegen, die man mit einem USB-Empfänger unterwegs zusätzlich als DVB-T-Fernseher nutzen möchte, ist dies nicht immer der Fall. Sinnvoll ist es hier, VDR nur dann zu starten, wenn der DVB-T-Empfänger auch angeschlossen ist. Dazu ist jedoch eine Umstellung des Init-Skripts auf Upstart erforderlich.
Damit das zum VDR-Paket gehörige Init-Skript zukünftig nicht mehr dazwischen funkt, muss es deaktiviert werden. Dies kann man unter Ubuntu vorübergehend, bis zum nächsten Upgrade des VDR-Pakets, per update-rc.d erledigen:
update-rc.d -f vdr remove
Damit Upstart überhaupt mitbekommt, wann ein DVB-Empfänger angeschlossen wird, muss eine Udev-Regeldatei (siehe Link) unter /etc/udev/rules.d hinzugefügt werden, die ein Upstart-Event auslöst:
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"
Diese Udev-Regel trifft nur auf DVB-Geräte zu, die per USB angeschlossen werden und ein DVB-Ausgabegerät mit der Kernel-Bezeichnung dvbX.dvr0 anlegen. Meldet der Kernel ein entsprechendes Gerät, erzeugt Udev per initctl emit das Upstart-Event dvb-device-add. Der Parameter --quiet weist initctl an, auf die üblichen Statusmeldungen zu verzichten.
Alle weiteren Parameter betreffen den Initctl-Befehl emit: So blockiert Initctl normalerweise, bis das ausgelöste Event vollständig verarbeitet wurde – was bei einem Dienst bedeutet, dass der Initctl-Aufruf erst wieder zurückkehrt, wenn der Dienst wieder beendet wurde. Dies wird mit dem Parameter --no-wait verhindert: Initctl beendet sich sofort, nachdem das Upstart-Event abgesetzt wurde.
Der Parameter -e erlaubt es, Umgebungsvariablen an den Upstart-Job zu übermitteln – in diesem Fall sind es die Variablen UDEV_KERNEL mit dem Kernel-Namen des Geräts und UDEV_DEVPATH mit dem Pfad zum Gerätebaum unterhalb von SysFS.
Um das Event dvb-device-add kümmert sich der Upstart-Job dvb (siehe Listing am Ende des Artikels). Seine Aufgabe ist es, für jedes DVB-Device im Verzeichnis /var/run/dvb eine Datei anzulegen, in der der SysFS-Pfad zum Gerätebaum gespeichert wird. Auf diese Weise lässt sich später zurückverfolgen, welches DVB-Device zu welchem USB-Gerät gehört. Anschließend löst der Job das Upstart-Signal vdr-start aus.
Der Upstart-Job vdr zum Aufruf von VDR ist trivial, als Events erwartet er vdr-start und vdr-stop. Zudem soll VDR nur während der Runlevel 2 bis 5 laufen:
start on vdr-start
stop on (vdr-stop
or runlevel [!2345])
exec /usr/sbin/vdr-upstart
Das Skript vdr-upstart erledigt die Hauptarbeit für den Aufruf von VDR: Es prüft, ob VDR in der Datei /etc/defaults/vdr überhaupt aktiviert wurde, bindet diverse Konfigurationsdateien ein und ruft dann das Start-Skript runvdr im Vordergrund auf. Ausgangsbasis für vdr-upstart war das Init-Skript aus dem VDR-Paket.
Durch Einfügen der Udev-Regel sowie der beiden Upstart-Jobs ist nun sichergestellt, dass VDR nur dann startet, wenn auch mindestens ein DVB-Gerät angeschlossen wurde. Schließt man mehrere an, so macht dies nichts: Der dvb-Job endet stets, nachdem er die Datei mit dem SysFS-Pfad unter /var/run/dvb angelegt und das Upstart-Event vdr-start ausgelöst hat, und wird für jedes weitere Gerät erneut abgearbeitet.
VDR hingegen läuft im Vordergrund, der Job wird von initctl list daher mit dem Status "running" geführt. Somit ignoriert Upstart das Start-Signal vdr-start, sodass VDR bei mehreren DVB-Empfängern nicht mehrfach gestartet wird.
Deutlich komplexer als das Starten von VDR ist das Beenden: Sinnvollerweise sollte VDR erst dann abgeschaltet werden, wenn auch der letzte DVB-Empfänger entfernt wurde. Solange VDR jedoch läuft, hält das Programm die DVB-Devices unter /dev/dvb geöffnet – weshalb sie der Kernel selbst dann nicht entfernt, wenn der USB-Empfänger längst herausgezogen wurde, es sich also nur noch um Phantome handelt. Es gibt dementsprechend auch kein Udev-Event, dass ein DVB-Gerät entfernt wurde, solange VDR läuft. Hinzu kommt, dass der SysFS-Baum des Geräts seit Kernel 2.6.29 ebenfalls erst dann abgeräumt wird, wenn das letzte Device geschlossen wurde. Es ist also ein wenig Heuristik nötig, um bei laufendem VDR zu erfahren, dass der DVB-Empfänger bereits eingepackt wurde.
Die Lösung ist, per Udev sämtliche Events zu beobachten, die entfernte USB-Geräte betreffen:
SUBSYSTEMS=="usb", ACTION=="remove", \
RUN+="/sbin/initctl --quiet emit --no-wait \
-e UDEV_DEVPATH=$devpath device-remove"
Der Upstart-Job dvb reagiert auch auf das Event device-remove und vergleicht den Device-Path des gerade entfernten USB-Geräts mit den Gerätepfaden der USB-DVB-Empfänger, die im Verzeichnis /var/run/dvb abgelegt sind. Stimmt der Basispfad überein, geht der Job davon aus, dass der betreffende DVB-Empfänger entfernt wurde, und löscht die zugehörige Datei in /var/run/dvb. Erst wenn der letzte DVB-Empfänger entfernt wurde, löst der dvb-Job das Upstart-Event vdr-stop aus – woraufhin VDR beendet wird und Udev die DVB-Geräteeinträge unterhalb von /dev/dvb abräumt.
Das Beispiel zeigt, welche Flexibilität sich durch den Einsatz von Upstart erreichen lässt – aber auch, wie kompliziert eine sinnvolle Umstellung der althergebrachten Init-Skripte auf das Upstart-Konzept ist. Bis auch das letzte SysV-Init-Skript auf Upstart migriert wurde, dürfte es daher noch eine ganze Weile dauern. (mid)
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