daemonize()

Tools wie daemonize oder start-stop-daemon starten laut ManPage einen Prozess “als Daemon” unter BSD oder Linux.
Doch die Einrichtung und der automatische Start von Diensten unter unixoiden Systemen ist am Ende dann doch noch etwas ganz anderes.

Und wieder einmal muss ich mich zuerst mal mit mir selbst einigen, wie man das daemon Konzept in C/C++ Programmen integrieren kann.


Während unter Windows ein Hintergrunddienst vom Betriebssystem standardisiert verwaltet wird, lag die Verwaltung in Unix, BSD und Linux stets in den Händen der Hersteller.

Eine Zeit lang waren SysV Init-Scripts der einzige de-facto Standard, bis Linux mit systemd wieder auszuscheren versuchte.
Auf die Linux-Standard-Base (LSB), die ursprünglich versuchte auch die Init-Scripts zu vereinheitlichen, kann man sich leider auch nicht verlassen, da einige Distributionen diesen Standard gar nicht und andere ihn nur halbherzig implementieren.

Problem 1: Wer fork()t?

Ein Prozess wird zum daemon wenn er seine Abhängigkeiten zum Aufrufer unterbricht. Das wird durch zwei fork() Aufrufe erreicht, wo im ersten Kind per setsid() eine neue Session (und Prozessgruppe) erstellt wird und optional auf einen anderen Useraccount gewechselt wird (setuid()).

Das zweite Kind ist dann der eigentliche daemon und wenn sein Eltern- und Großelternprozess danach beendet werden, verliert der Daemon seine Bindung an die Prozessgruppe des ursprünglichen Aufrufers. Der neue parent wird init (bzw. ID 1) und der daemon ist dann nicht mehr von Gruppen-kill Aufrufen seiner Macher bedroht.

Weil man hier aber auch viele Fehler machen kann, hat es sich eingebürgert, dass ein Prozess diese fork()s nicht mehr selbst durchführt. Anstatt dessen ruft das init Script start-stop-daemon auf, der sich selbst entsprechend fork()t und am Ende den daemon Prozess einfach per exec() startet.

Im daemon muss man daher gar nichts mehr tun, nur noch den eigentlich Job.

… doch diese Methode wird nicht von allen Diensten benutzt, und vor allem ist start-stop-daemon oder auch daemonize kein POSIX Standard, womit das auf jeder Distribution anders ablaufen kann.

Problem 2: Wer verwaltet das PID-File?

Weil Dämonen eigenständig laufen und von außen wie gewöhnliche Prozesse aussehen, greifen init Scripts auf spezielle Dateien zu, die die PID des gerade laufenden daemon enthalten sollen, sogenannte PID-Files.

  • Ist kein PID-File da, läuft kein daemon
  • Ist ein PID-File da, aber die enthaltene PID verweist auf keinen laufenden Prozess, läuft kein daemon
  • Ist ein PID-File da und verweist auf einen laufenden Prozess, so ist das die PID des daemon und über diese kann man ihm Signale schicken.

Das init Script liest das PID-File um seine start und stop Parameter entsprechend umsetzen zu können, doch erzeugt wird es an unterschiedlichen Orten.
start-stop-daemon kann es anlegen, oder das init Script legt es selbst an, oder der daemon tut es intern, sobald er läuft.

Auch hier fehlt ein eindeutiger Standard.

Was funktioniert wo?

systemd wollte init Scripts abschaffen, und (ähnlich wie Windows) eine Daemon-Registry aufbauen, wo systemd per Konfiguration alle Start- und PID- Formen korrekt umsetzen kann.

Doch einerseits nutzen viele Dienste weiter nur klassische init Scripts, womit ganz üble Hacks notwendig werden um init Scripts mit systemd zu synchronisieren.
Und andererseits steht systemd in einigen Umgebungen wie z.B. Docker gar nicht zur Verfügung. Da bleiben nur init Scripts ohne systemd Anbindung als einzige Alternative übrig.

Und BSD fügt je nach Geschmacksrichtung auch noch seine eigenen Features und Verzeichniskonvention hinzu.

Kurz: Es ist ein absolutes Versionschaos, was wo funktionieren kann und bedarf somit immer einer individuellen Anpassung.

GATE Implementierung

Vor 10 Jahren habe ich den Ansatz gewählt, dass “meine” Dämonen fremdgesteuert sind, und das init Script zahlreiche Varianten durchprobiert um einen Dienst auf mehr als einer Plattform hochziehen zu können.
Das hat zwar grundsätzlich funktioniert, doch das Script war unglaublich komplex und war damit auch fehleranfällig und nur sehr schwer zu debuggen.

In meiner aktuellen Implementierung, gehe ich einen anderen Weg:
Jeder GATE-daemon verwaltet sich vollständig selbst, das heißt, er fork()t selbst, legt sein PID-File selbstständig an und gibt init Script konforme Exit-Codes zurück.

Damit braucht das init Script nur auf das Binary und die PID-Datei konfiguriert zu sein, um bei start den Daemon Prozess auszuführen und bei stop ein SIGTERM an die PID aus der PID-Datei zu senden.

Der Code zur Erkennung der Systemumgebung wandert dann ins Binary, wo schon der Compiler per #ifdef Unterschiede zwischen Linux und BSD auflösen kann.

Am Ende soll sich jeder Daemon sein init Script selbst generieren können, damit man (so wie auch unter Windows) einfach per mydaemon --register oder mydaemon --unregister den eigenen Dienst korrekt im System integrieren kann.

Fazit

Zu viel Freiheit ist eine Einschränkung.

Oft sind WinAPI Codes komplexer als POSIX Implementierung, doch beim Thema Systemdienst dreht sich das um, wenn man plattformunabhängig bleiben möchte.

Denn während ein paar Codezeilen von NT4 bis Server 2022 stabil einen Dienst verwalten können, erfordern Patches für alle unterschiedlichen Linux- und BSD-Varianten unzählige Implementierung und jede Menge Zusatzaufwand.

Linux erschlägt das Problem mit zahlreicher billiger Arbeitszeit von Studenten, die jährlich das Script-Rad in jeder Distro neu erfinden … aber wäre es nicht sinnvoller diese Zeit in relevantere Features zu stecken?

Als unabhängiger Entwickler wird man so gezwungen seine Software nur auf bestimmten Distros anzubieten, weil man eben nicht alles abdecken kann. Und der Verweis, alles zu Open-Source zu machen schreckt nachhaltig viele Firmen ab.

Gäbe es einen ähnlichen Standard wie das Root-Filesystem für Dienste unter allen POSIX Varianten, wäre die Welt wieder ein ordentliches Stück schöner. (Denn /etc oder /usr erleichtern das Portieren zwischen Linuxen und BSD enorm. Wäre doch schlimm, würden die auch immer anders heißen.)

However… ich hab trotzdem Spaß an Code-Archeologie.