![]() |
|
|
7.12.2 Dämon, sprich mit uns ...
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Level | Konstante | Bedeutung |
| 0 | LOG_EMERG | System ist unbrauchbar. |
| 1 | LOG_ALERT | Dringend irgendwelche Aktionen einleiten |
| 2 | LOG_CRIT | Kritische Nachrichten |
| 3 | LOG_ERR | Normaler Fehler |
| 4 | LOG_WARNING | Warnung |
| 5 | LOG_NOTICE | Normale, bedeutende Nachricht (Standard) |
| 6 | LOG_INFO | Normale Nachricht |
| 7 | LOG_DEBUG | Unwichtige Nachricht |
Der zweite Teil der Kombination von priority ist facility, die zur Identifizierung des Prozesstyps dient, der die Log-Nachricht verschickt hat. Wird hier kein Wert verwendet, gilt als Standardeinstellung LOG_USER. Hier ein Überblick zu den facilitys der Log-Nachrichten.
| facility | Bedeutung |
| LOG_AUTH | Autorisierung |
| LOG_AUTHPRIV | Autorisierung (privat) |
| LOG_FTP | FTP-Dämon |
| LOG_KERN | Kernel-Nachrichten |
| LOG_USER | Nachricht von Usern |
| LOG_MAIL | E-Mail-Subsystem |
| LOG_DAEMON | Verschiedene Dämonen |
| LOG_SYSLOG | syslog-interne Nachrichten |
| LOG_LPR | Drucker-Subsystem |
| LOG_NEWS | News-Subsystem |
| LOG_UUCP | UUCP-Subsystem |
| LOG_CRON | Cron-Dämon |
| LOG_LOCAL0...7 | Reserviert für lokale Verwendung (Eigenbedarf) |
Da bei weitem nicht für jedes Programm und jeden Dämon eine extra Konstante zugewiesen werden kann (aus Platzgründen innerhalb des level integers), versehen die meisten Anwendungen ihre Log-Einräge sowieso mit dem eigenen Namen, z. B. mit openlog(), siehe unten.
Mit dem Parameter message geben Sie die Nachricht bei syslog() als eine printf()-typische Formatzeichenkette an.
Wenn ein Prozess die Funktion syslog() aufruft, wird ein UNIX Domain Datagram Socket angelegt und mittels connect() mit dem Pfadnamen des angelegten Sockets (meistens /var/run/log) verbunden. Das Socket bleibt bis zum Ende des Prozesses offen oder bis closelog() aufgerufen wird.
Optional kann auch die Funktion openlog() vor dem ersten Aufruf von syslog() aufgerufen werden:
#include <syslog.h> void openlog(const char *ident, int option, int facility);
Mit ident legen Sie eine Zeichenkette fest, gewöhnlich der Programmname, die als Indikator für erzeugte Syslog-Nachrichten dient. Folgende optionen und deren Bedeutung können Sie verwenden (es kann auch eine ODER-Verknüpfung für mehrere Optionen verwendet werden).
| Options | Bedeutung |
| LOG_CONS | Ausgabe erfolgt direkt auf die Konsole, sollte ein Fehler mit der Verbindung zum Dämon auftreten. |
| LOG_NDELAY | Verbindung zum Login-Dämon wird sofort aufgebaut (das ist ja implizit bei einer Verbindung). Nicht, wie normalerweise, wenn die erste Nachricht geschrieben wurde. |
| LOG_PERROR | Die Nachricht wird außer an den syslog-Dämon noch zusätzlich an den Fehlerkanal (stderr) geschickt. |
| LOG_PID | Wird ein Programm mehrfach gestartet, ist es sinnvoll, diese Option zu verwenden, da jede syslog-Nachricht eine PID enthält. Sie können damit genau ermitteln, welcher Prozess diese Nachricht geschickt hat. Beispielsweise wenn Sie mit fork() einen Kindprozess zur Erledigung bestimmter Aufgaben erzeugen. |
Da beim Aufruf von openlog() gewöhnlich kein UNIX Domain Socket angelegt wird (sondern erst beim ersten syslog()-Aufruf), können Sie mit der Option LOG_NDELAY ein Socket beim Aufruf von openlog() erzeugen. Die Syslog-Typen für den Parameter facility haben Sie bereits kennen gelernt (LOG_KERN,LOG_USER,LOG_MAIL,LOG_DAEMON ...).
In der Praxis könnte somit ein openlog()-Aufruf folgendermaßen aussehen:
openlog("lpd-daemon", LOG_PID, LOG_LPR);
Hier dient als Kennung »lpd-daemon«. Es wird außerdem immer die Prozess-ID mit ausgegeben (LOG_PID), und als Einrichtung wird das Druckersystem verwendet.
Wenn die Anwendung das Senden der Log-Nachrichten beendet hat, wird closelog() aufgerufen.
#include <syslog.h> void closelog( void );
Ihnen das ganze Thema zum syslogd zu präsentieren, würde hier zu weit gehen. Hierfür sei ein umfassender Blick in die entsprechende Manual Page empfohlen. Aber dennoch will ich Ihnen das Konzept anhand eines einfachen Beispiels demonstrieren. Zu diesem Zwecke benötigen Sie allerdings vollen Zugriff auf den Rechner als root. Sofern Sie (noch) nicht ganz bewandert im Umgang mit Linux sind, rate ich Ihnen, diesen Text zur Sicherheit erst zu lesen. Machen Sie ggf. ein Backup von der Datei /etc/syslog.conf.
Öffnen Sie zuerst die Datei /etc/syslog.conf mit einem Editor. In meinem Fall gehe ich wie folgt vor:
$su Passwort:******* # mcedit /etc/syslog.conf ... # Mail logging mail.=debug;mail.=info;mail.=notice -/var/log/mail/info mail.=warn -/var/log/mail/warnings mail.err -/var/log/mail/errors ...
Sie bekommen nun die komplette Datei in /etc/syslog.conf zu Gesicht. In gekürzter Form soll hier der Log-Eintrag für Mail verwendet werden, z. B. der Eintrag:
mail.=debug;mail.=info;mail.=notice
Dies bedeutet nun, falls der syslog-Dämon eine Nachricht mit der Priorität LOG_DEBUG, LOG_INFO oder LOG_NOTICE und facility=LOG_MAIL erhält, wird diese in die Datei /var/log/mail/info geschrieben.
Liest syslog die Nachricht mit der Priorität LOG_WARNING, wie dies in der nächsten Zeile mit mail.=warn angegeben ist, wird die Ausgabe in /var/log/mail/warnings geschrieben.
Hierzu soll nun ein eigener Typ erstellt werden, der eine einfache Textausgabe auf ein Terminal macht. In diesem Fall auf /dev/tty1 – passen Sie die Angabe bitte Ihren Bedürfnissen an. Ein einfaches »tty« auf der Kommandozeile gibt Ihnen Auskunft darüber, welches Ihr aktueller Terminal ist. Eine eigene Routine können Sie mit LOG_LOCALx (x ist 0 bis 7) verwenden. Nach einer Neuinstallation sollte hierfür noch keiner dieser Einträge belegt sein. Schreiben Sie nun ans Ende der Datei /etc/syslog.conf folgenden Eintrag, und speichern Sie die Datei anschließend ab:
#Testroutine, local0.=notice -/dev/tty1
Das Programm, das zur Identifizierung des Prozesstyps jetzt LOG_LOCAL0 und die Priorität LOG_NOTICE verschickt, kann seine Ausgabe auf das Terminal /dev/tty1 machen. In der Praxis sollten Sie nur Nachrichten mit höchster Priorität (LOG_EMERG) auf ein Terminal ausgeben – worüber Sie letztendlich selbst entscheiden. Idealer ist es, in jedem Fall, die Nachricht in eine Log-Datei zu schreiben, je nach Priorität der Nachricht. Wie Sie die syslog.conf-Datei dementsprechend konfigurieren, entnehmen Sie bitte der Manual Page zu syslogd. Jetzt ein Programmbeispiel, das die eben eingerichtete Identifizierung LOG_LOCAL0 mit der Priorität LOG_NOTICE verwendet.
/* logging.c */ #include <stdio.h> #include <stdlib.h> #include <unistd. h.> #include <syslog.h> int main(int argc, char **argv) { openlog( "logging", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0 ); /* Viel Code */ /* Etwas ist passiert -> Schick es an /dev/tty1 */ syslog(LOG_NOTICE, "Hallo Welt mit syslog\n"); /* Ende */ closelog(); return EXIT_SUCCESS; }
Vorher müssen Sie den syslog-Dämon neu starten, damit die neue Konfiguration in syslog.conf übernommen wird. Dies können Sie folgendermaßen erledigen (su):
# kill -SIGTERM `cat /var/run/syslogd.pid` # syslogd
Oder einfacher:
# kill -HUP `pidof syslogd`
Jetzt können Sie das Listing übersetzen und starten. Von wo Sie die Anwendung starten, ist letztendlich egal. Auf jeden Fall sollten Sie jetzt im Terminal /dev/tty1 (in X zu erreichen mit der Tastenkombination (STRG)+(ALT)+(F1)) etwa folgende Ausgabe erhalten haben:
[Datum] [Uhrzeit] test2[12345]: Hallo Welt mit syslog
Die Ausgabe haben Sie lediglich mit dem Aufruf
syslog(LOG_NOTICE, "Hallo Welt mit syslog\n");
aktiviert. In der Praxis kann syslog() auf der Suche nach Fehlern in Programmen (insbesondere in Programmen, die kein Terminal haben) sehr helfen – was übrigens auch mit dem Debugger GDB möglich ist. Sie können zum Beispiel überwachen, wer gerade wann auf ein bestimmtes Programm zugreift (und wieder Murks gemacht hat):
openlog("myprogramm", LOG_PID | LOG_CONS, LOG_LOCAL0);
if( (strcmp(nam1, login_name)) == 0)
syslog(LOG_NOTICE, "Bing hat sich gerade eingeloggt");
else if( (strcmp(nam2, login_name)) == 0)
syslog(LOG_NOTICE, "Bong hat sich gerade eingeloggt");
Bedenken Sie aber, wenn Sie dies auf dem Bildschirm ausgeben wollen, wer das eventuell auch lesen könnte. Linux selbst gibt normalerweise Nachrichten auf dem Bildschirm nur mit der allerhöchsten Priorität aus. Natürlich und vor allem anderen können Sie den syslog-Dämon zum Log(gen) eines selbst geschriebenen Dämons verwenden.
Jetzt, da Sie wissen, wie Sie der Ausgabe von Dämonen Herr werden, sind Sie so weit, selbst welche zu beschwören. Einen fast kompletten Dämonprozess können Sie in den folgenden sieben Schritten einrichten (programmiertechnisch gesehen):
| fork() – Erst starten Sie mit fork() einen neuen Kindprozess, worauf Sie anschließend den Elternprozess beenden. Da die Shell nun »glaubt«, dass sich der Elternprozess beendet hat, »denkt« diese, dass die Anwendung fertig ist, und es wird der Kindprozess automatisch im Hintergrund ausgeführt. Die weiteren Schritte beziehen sich jetzt auf den »Kindprozess«. |
| setsid() – Als Nächstes legen Sie mit der Funktion setsid() eine neue Session (Sitzung) an. Damit wird der Prozess Sessionführer der Session und der Prozessgruppenführer einer neuen Gruppe. Anschließend hat der Prozess kein Terminal (CTTY) mehr. Die Syntax zu setsid(): |
| Signal SIGHUP ignorieren – Da wir jetzt gleich nochmals einen Kindprozess starten und den Elternprozess beenden, müssen Sie das Signal SIGHUP ignorieren, da der zu beendende Prozess der Sessionführer ist und allen anderen Prozessen in der aktuellen Session ein SIGHUP-Signal senden würde. Zwei weitere Signale, die ein Dämon nicht erhalten sollte, sind SIGINT und SIGWINCH. |
| fork(), die Zweite – Mit einem erneuten fork()-Aufruf gehen Sie sicher, dass der Elternprozess der erste Kindprozess ist, der beendet wird. Dadurch sorgt man dafür, dass ein Dämon nicht automatisch ein Steuerterminal erhält, falls dieser eins öffnen würde. Gerade unter SVR4 kann der Sessionführer ein Terminal öffnen, so dass dieses Terminal zum Steuerterminal wird – genau das wollen Sie ja bei Dämonprozessen vermeiden. |
| chdir() (und chroot()) – Das Arbeitsverzeichnis des Prozesses sollte nun auf das Root-Verzeichnis abgeändert werden – wobei es hierbei auch Dämonen gibt, die ein anderes Arbeitsverzeichnis verwenden. |
| umask() – Die Dateimodus-Erstellungsmaske müssen Sie jetzt auf 0 setzen. Damit stellen Sie sicher, wenn der Dämon eine Datei anlegen sollte, dass die Berechtigungsbits der geerbten Dateimodus-Erstellungsmaske keine Auswirkungen auf die neue Datei haben. Manchmal ist aber eine gewisse umask erwünscht ..., es muss also nicht unbedingt umask(0) sein, sondern kann auch umask(022) sein – natürlich vom Dämon selbst zu setzen. |
| Schließen aller geöffneten Filedeskriptoren mit close(). Ein Elternprozess könnte Ihnen nämlich noch einen störenden Filedeskriptor vererbt haben. Und zur Sicherheit schließen Sie in einer Schleife alle Filedeskriptoren. Wobei hier das Problem entsteht, wie viele Filedeskriptoren verwendet werden. So müssen Sie die Anzahl fast selbst festlegen, da die geöffnete Anzahl unendlich sein kann. Gute Werte sind 32, 64 oder 128 Filedeskriptoren – auch wenn diese nicht offen sind. Eventuell können Sie auch die symbolische Konstante _SC_OPEN_MAX (falls vorhanden) oder OPEN_MAX aus stdio.h (garantiert vorhanden) verwenden. |
| syslogd mit openlog() aufrufen |
Hierzu das Listing, das die einzelnen Schritte demonstriert. Im Beispiel wird auch davon ausgegangen, dass Sie die Schritte im Kapitel zuvor mit dem syslog-Dämon und der Funktion syslog() gemacht haben und sich in der Datei /etc/syslog.conf folgender Eintrag befindet:
#Testroutine, local0.=notice -/dev/tty1
Über Sinn und Unsinn der Ausgabe auf eine Konsole wurde bereits gesprochen. Sie können gerne auch testweise andere Einträge in der Datei /etc/syslog.conf machen. Eine gute Dokumentation können Sie auf der Manual Page von syslogd(8) und syslog.conf(5) nachlesen. Sollten Sie ein anderes Terminal zur Ausgabe verwendet haben, dann verwenden Sie eben dies. Das anschließende Listing startet einen Dämon – legt sich für 15 Sekunden schlafen und beendet diesen wieder – nicht sehr sinnvoll. Alle Ausgaben werden im Beispiel auf das Terminal gemacht, das Sie in der Datei /etc/syslog.conf eingetragen haben.
/* daemon.c */ #include <stdio.h> #include <unistd. h.> #include <syslog.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <stdlib.h> typedef void (*sighandler_t)(int); static sighandler_t handle_signal (int sig_nr, sighandler_t signalhandler) { struct sigaction neu_sig, alt_sig; neu_sig.sa_handler = signalhandler; sigemptyset (&neu_sig.sa_mask); neu_sig.sa_flags = SA_RESTART; if (sigaction (sig_nr, &neu_sig, &alt_sig) < 0) return SIG_ERR; return alt_sig.sa_handler; } static void start_daemon (const char *log_name, int facility) { int i; pid_t pid; /* Elternprozess beenden, somit haben wir einen Waisen */ /* dessen sich jetzt vorerst init annimmt */ if ((pid = fork ()) != 0) exit (EXIT_FAILURE); /* Kindprozess wird zum Sessionführer. Misslingt */ /* dies, kann der Fehler daran liegen, dass der */ /* Prozess bereits Sessionführer ist */ if (setsid() < 0) { printf("%s kann nicht Sessionführer werden!\n", log_name); exit (EXIT_FAILURE); } /* Signal SIGHUP ignorieren */ handle_signal (SIGHUP, SIG_IGN); /* Oder einfach: signal(SIGHUP, SIG_IGN) ... */ /* Das Kind terminieren */ if ((pid = fork ()) != 0) exit (EXIT_FAILURE); /* Gründe für das Arbeitsverzeichnis: * + core-Datei wird im aktuellen Arbeitsverzeichnis * hinterlegt. * + Damit bei Beendigung mit umount das Dateisystem * sicher abgehängt werden kann */ chdir ("/"); /* Damit wir nicht die Bitmaske vom Elternprozess */ /* erben bzw. diese bleibt, stellen wir diese auf 0 */ umask (0); /* Wir schließen alle geöffneten Filedeskriptoren ... */ for (i = sysconf (_SC_OPEN_MAX); i > 0; i--) close (i); /* Da unser Dämonprozess selbst kein Terminal für */ /* die Ausgabe hat.... */ openlog ( log_name, LOG_PID | LOG_CONS| LOG_NDELAY, facility ); } int main (int argc, char **argv) { int time = 15; start_daemon ("meinDaemon", LOG_LOCAL0); while (1) { /* Enlosschleifen: Hier sollte nun der Code für den * Daemon stehen, was immer er auch tun soll. * Bspw. E-Mails abholen, versenden (was kein * Verweis sein soll, um mit dem Spammen anzufangen); * Bei Fehlermeldungen beispielsweise: * if(dies_ist_passiert) * syslog(LOG_WARNING, "dies_ist_Passiert"); * else if(das_ist_Passiert) * syslog(LOG_INFO, "das_ist_Passiert"); */ syslog( LOG_NOTICE, "Daemon gestartet ...\n"); sleep(time); syslog( LOG_NOTICE, "Daemon läuft bereits %d Sekunden\n", time ); break; } syslog( LOG_NOTICE, "Daemon hat sich beendet\n"); closelog(); return EXIT_SUCCESS; }
Das Programm bei der Ausführung:
$ gcc -o daemon daemon.c $ ./daemon
Ein Blick auf das Terminal (bei mir /dev/tty1):
Mar 10 07:06:26 linux meinDaemon[2684]: Daemon gestartet ... Mar 10 07:06:41 linux meinDaemon[2684]: Daemon läuft bereits 15 Sekunden Mar 10 07:06:41 linux meinDaemon[2684]: Daemon hat sich beendet
Während der Ausführung dürfen Sie gerne die Prozessüberwachung auflisten. Sie finden den Dämonprozess für gewöhnlich mit folgendem Eintrag in der Prozesstabelle:
$ ps xj PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND ... 1 2691 2690 2690 ? -1 S 500 0:00 ./daemon ...
Sie erkennen den Dämon speziell daran, dass dieser kein Steuerterminal (TTY=?) und als Erkennung des Elternprozesses die PPID 1 (init) hat.
| << zurück |
|
||||||||||||
|
||||||||||||
|
||||||||||||
Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich
ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen,
wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist
urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der
Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und
Verarbeitung in elektronischen Systemen.