|
|
||||||
Familie Cron - Teil 1Wem die Stunde schlägtvon Patricia Jung |
Da sitzt man nichtsahnend am Rechner, tippt an einem Skript herum -- und plötzlich fängt die Festplatte an zu rödeln. Schweißausbruch: Bin ich gehackt worden? Spinnt die Platte? Hab' ich Halluzinationen? Nichts von alledem: Stoisch geht ein pünktlicher Diener seinem Tag- (oder Nacht-)Werk nach und updatet die locate-Datenbank, auf daß auch alle Dateien im System an der richtigen Stelle zu finden seien, sollte jemand nach irgendwas suchen. Sein Name: cron. Seine Aufgabe: all die langweiligen, öfter anfallenden Terminaufgaben zu erledigen, die die faule Ruth (und unter Umständen auch der eine oder andere weniger privilegierte User) dann doch nicht ein ums andere Mal von Hand aufrufen will.
cron, auch wenn er nicht unbedingt ein abschließendes d im Namen trägt, ist ein Daemon, der von init beim Booten gestartet wird und von da an die in verschiedenen crontabs eingetragenen Termingeschäfte managt. Damit hören dann aber auch schon die Gemeinsamkeiten innerhalb der Familie auf: Crondaemonen gehören zu den UNIX-Kommandos, die sich von UNIX zu UNIX unterscheiden. Manche crons merken sich, wann sie das nächste Mal arbeiten müssen und legen sich in der Zwischenzeit schlafen, andere wachen jede Minute auf und schauen nach, ob es etwas für sie zu tun gibt. Zu diesen hektischeren Charakteren gehört der cron von Paul Vixie, der auf den meisten Linux-Systemen seinen Dienst verrichtet. Als Alternative steht -- ebenfalls hektisch und etwas schlanker -- Matthew Dillons crond zur Verfügung, der unter anderem bei der Slackware mitgeliefert wird.
Wo sich die Wiege von cron befindet, ist wiederum von Linux-Distribution zu Distribution verschieden: Zwar ist das Binary in der Regel unter /usr/sbin/cron(d) zu finden, beim initschen cron-Aufruf scheiden sich jedoch die Geister. Dank unterschiedlicher Bootkonzepte sucht z.B. Debian nach dem entsprechenden Skript unter /etc/init.d/cron, Red Hat 5.0 unter /etc/rc.d/init.d/crond und Caldera OpenLinux im selben Verzeichnis nach cron. Red Hat 4.2 verwendet /etc/rc.d/init.d/cron.init und S.u.S.E. /sbin/init.d/cron.
Kaum, daß unser Dämon geboren ist, fällt er schon in hektische Aktivität und liest aus der /etc/crontab, welcher Dienst am System als nächstes von ihm erwartet wird. Mit dieser einen Aufgabenliste ist es jedoch nicht getan: Dem Linux Filesystem Hierarchy Standard (FHS, Nachfolger vom Linux Filesystem Standard FSSTND) entsprechend erwarten ihn im /var/spool/cron/-Zweig die Wunschzettel der faulen User. Wer sich nicht daran hält, zeigt Tabelle 1. Und wer seine User partout verwirren will, paßt diese und andere Dateinamen, Pfade etc. vor dem Kompilieren einfach in den Headerdateien des Quelltextes an.
Tabelle 1: Spoolverzeichnisse für persönliche Crontabellen | |
Debian 1.3.1 | /var/spool/cron/crontabs/ |
RedHat 4.2 | /var/spool/cron/ |
RedHat 5.0 | /var/spool/cron/crontabs/ |
Caldera OpenLinux 1.2 | /var/spool/cron/ |
S.u.S.E. 4.4, 5.2 | /var/cron/tabs/ |
Slackware 3.4 | /var/spool/cron/crontabs/ |
Nun sind Benutzer eine recht wankelmütige Spezies: Sie ändern diese Wunschlisten in höchst unvorhersehbaren Abständen mit Hilfe des Kommandos crontab. Dann hat ein Cron-Dämon zwei Möglichkeiten: Entweder er weigert sich, ständig seine Aufgaben neu zu lesen -- dann wird Ruth ihn sehr schnell in die ewigen Cybergründe jagen, auf daß ein neuer cron die aktuelle Taskliste einliest. Oder ihm ist sein Leben lieb -- was bedeutet, daß er jede Minute nachschauen muß, ob sich die Modifikationszeit der systemweiten Crontab bzw. des Spoolverzeichnisses geändert hat, und er entsprechend seine Aufgabenliste zu modifizieren hat. Einen Trost gibt es für Crons wie Vixie- oder Dillons Cron, die sich für diese hektische Lebensweise entschieden haben: Noch schneller müssen sie nicht arbeiten, denn die Minute ist die kleinste Zeiteinheit, die ein User in einer Crontabelle spezifizieren kann.
Damit cron seine Aufträge versteht, müssen sie in einem fest vorgegebenen sechs- (benutzerdefinierte Crontabs, siehe Tabelle 1) bzw. siebenspaltigen (systemweite Crontabelle /etc/crontab) Tabellenformat in die jeweilige Crontab-Datei eingetragen werden; Whitespace-Zeichen dienen als Spaltentrenner (Tabelle 2). Ein Eintrag darf dabei keinen Zeilenumbruch enthalten (NEWLINE kann auch nicht durch einen Backslash escaped werden.) und 1024 Zeichen (inklusive NEWLINE) nicht überschreiten -- überzählige Zeichen werden einfach abgeschnitten und führen damit in der Regel zu Syntaxfehlern. Matthew Dillons crond begrenzt zudem die Einträge je Benutzer auf 256 (oder was man vor dem Kompilieren in die Headerdatei defs.h eingetragen hat).
Tabelle 2: Format eines Crontab-Eintrags | ||||||
Minute(n) | Stunde(n) | Tag(e) | Monat(e) | Wochentag(e) | User | auszuführender Befehl |
mögliche Werte | ||||||
0--59, * | 0--23, * | 1--31, * | 1--12, *, Jan--Dec oder jan--dec | 0--6, *, Sun--Sat oder sun--sat, bei Vixie auch 7 für Sonntag | nur in /etc/crontab |
Tabelle 3 listet auf, welche Eintragsvarianten in jeder der fünf Zeitspalten möglich sind. Wie von anderen GPLten Tools gewohnt, bieten die unter Linux eingesetzten Crons komfortablere Optionen als andere; so gehören die textuellen Abkürzungen der Wochentage und Monatsnamen nicht zum Standardrepertoire, ebensowenig wie die Angabe der Schrittweite durch /N.
Tabelle 3: Format einer Zeitangabe in einem Crontab-Eintrag | ||
Syntax | Bedeutung | Beispiel |
---|---|---|
* | keine Angabe (Eintrag spielt keine Rolle bei der Ermittlung der Zeitangabe) |
1 1 * 1 1 01 1 * jan mon #jeden Montag im #Januar um 1:01 Uhr #Datum egal |
n | einmalige Aktion innerhalb der Zeitskala der Spalte zum Zeitpunkt n (mögliche Werte für n siehe Tabelle 2) |
1 1 1 1 * 1 1 1 Jan * #1.1. 1:01 Uhr |
n0,n1,...,nm | mehrmalige Aktion zu den Zeitpunkten nx innerhalb der Zeitskala der Spalte |
0,20,40 1,13 * * * #1:00, 1:20, #1:40, 13:00, #13:20, 13:40 Uhr |
n0-nm | Wiederholung der Aktion an allen möglichen Zeitpunkten im Zeitraum n0 bis einschließlich nm (for (i=0; i<=m; i++){...}) |
0 0 1-15 1-2 * #1.-15.Januar und #1.-15. Februar #mitternachts |
n0-nm/N | Wiederholung der Aktion an jedem N-ten Zeitpunkt im angegebenen Zeitraum (for (i=0; i<=m; i+=N){...}) |
0 0 1-31/2 * * #jeden ungeraden Tag #um Mitternacht #Achtung: Die Zählung #beginnt jeden Monat #neu, daher nicht exakt #jeden _zweiten_ Tag! |
Sowohl Vixie- als auch Dillons Cron lassen Mischformen, z.B. 6,8-12, zu. Etwas gewöhnungsbedürftig ist die Tatsache, daß Zeiteinträge wie 1 1 1,2 * Mon an jedem ersten und zweiten des Monats sowie an jedem Montag zur Ausführung des Auftrags führen, und nicht -- wie vermutlich erwartet -- nur dann, wenn ein erster oder zweiter des Monats auf einen Montag fällt. Letzteres Verhalten wird nur von einigen System-V-kompatiblen Crons an den Tag gelegt, nicht aber von den Linux-Crons. Aufträge, die nur dann ausgeführt werden, wenn es sich bei einem bestimmten Datum um einen bestimmten Wochentag handelt, sind somit nicht drin.
Zusätzlich zu den Aufträgen können in den Crontabellen Kommentare (beginnend mit dem Hashzeichen # bis zum Zeilenende) eingefügt werden. Vixie-Cron wertet auch Umgebungsvariablen in Bourne-Shell-Syntax aus. Tabelle 4 stellt die wichtigsten vor. Dillons Cron macht diesen Schnickschnack nicht mit -- will man dennoch nicht darauf verzichten, muß man ein Shellskript schreiben, das man von cron aufrufen läßt.
Tabelle 4: Umgebungsvariablen in Crontabellen | ||
Umgebungsvariable | Beschreibung | Beispiel |
---|---|---|
MAILTO | Normalerweise schickt Cron sämtlichen Output per E-Mail an die auftraggebende Benutzerin. MAILTO definiert einen anderen Empfänger. |
MAILTO=lucy,trish #Der Output der folgenden #Cronjobs geht jetzt #per Mail an die User #lucy und trish... MAILTO="" #... und jetzt an gar #niemanden mehr. |
PATH | Pfad, in dem nach von Cron auszuführenden Kommandos gesucht wird.
Default ist /usr/local/bin:/usr/bin:/bin:. (bei Vixie). Achtung: Setzt man PATH in der persönlichen Crontab, wird dieser Wert vollständig überschrieben; Anhängen funktioniert nicht. |
PATH=$PATH:/home/trish/bin #$PATH wird _nicht_ expan- #diert; es werden nunmehr #nur Kommandos aus #/home/trish/bin gefunden. |
SHELL | Shell, in der die Kommandos ausgeführt werden sollen.
Default ist /bin/sh (im übrigen auch bei Dillons Cron). |
SHELL=/usr/bin/tcsh #Statt /bin/sh wird #die tcsh verwendet. |
Da die Crontabelle sequentiell interpretiert wird, ist es möglich, für jeden Cronjob eine eigene Umgebung zu definieren, indem die relevanten Umgebungsvariablen vor jedem Eintrag neu gesetzt werden:
MAILTO="trish,lucy" */10 * * * * finger chris MAILTO=trish 0 * * * * cat /home/trish/.todoWährend der Output des finger-Kommandos alle 10 Minuten sowohl an trish als auch an lucy gemailt wird, bekommt lediglich trish jeweils zur vollen Stunde ihre Todo-Liste zugeschickt.
Während die systemweiten Aufträge in /etc/crontab direkt von Ruth editiert werden (Näheres dazu im zweiten Teil dieser Geschichte), geht das für alle benutzerspezifischen Aufträge in /var/spool/cron/crontabs/ (oder so, siehe Tabelle 1) nur über den Umweg /usr/bin/crontab. Dieses Programm läuft suid root mit allen Nachteilen und wird benutzt zum Installieren, Deinstallieren und Anzeigen -- kurz: zum Verwalten der persönlichen Crontabellen.
Vor die persönliche Crontabelle hat Ruth bei Vixie-Cron jedoch allow- und deny-Dateien (vgl. Tabelle 5) gesetzt. Etwas Äquivalentes konnte ich bei Dillons Cron nicht entdecken.
Tabelle 5: Erlaubnis erteilende und verweigernde Dateien bei den verschiedenen Distributionen | |
Debian 1.3.1 | /var/spool/cron/allow |
/var/spool/cron/deny | |
Red Hat 4.2, 5.0 | /etc/cron.allow |
/etc/cron.deny | |
Caldera OpenLinux 1.2 | /etc/cron.allow |
/etc/cron.deny | |
S.u.S.E. 4.4, 5.2 | /var/cron/allow |
/var/cron/deny |
Wenn diese Dateien nicht existieren, dürfen alle User nach Herzenslust den kleinen cron mit Aufträgen überhäufen. Manche Crons verbieten gerade bei dieser Konstellation allen Usern den Zugriff -- bei Vixie-Cron hängt das Verhalten davon ab, ob vor dem Kompilieren die Variable ALLOW_ONLY_ROOT in der Headerdatei config.h definiert wurde.
Sobald die allow-Datei vorhanden ist, muß jede Benutzerin, die die Dienste von cron nutzen will, darin mit UserID eingetragen sein. Dummerweise ist diese Datei sehr empfindlich, was überflüssige Leerzeichen betrifft: Ein Eintrag wie
__chris lucy_ trish
(_ steht für ein Leerzeichen) erlaubt es lediglich trish, crons Dienste in Anspruch zu nehmen; bei chris sind zwei führende Leerzeichen, bei lucy ein Leerzeichen am Wortende zuviel, als daß die beiden korrekt als privilegierte Benutzer registriert wären.
Das gilt auch für die deny-Datei, in der die Benutzer aufgelistet sind, die cron nicht benutzen dürfen. Ist eine Benutzerin in beiden Dateien eingetragen, hat der Eintrag in allow Vorrang.
Wie bekommt nun ein Benutzer heraus, ob cron seinen Wünschen nachkommen wird? Das Kommando
crontab -l
gibt Aufschluß: Nichtprivilegierte User erhalten dann eine Meldung wie
You (chris) are not allowed to use this program (crontab) See crontab(1) for more information
Benutzerinnen, denen cron zu Diensten ist, erhalten auf dieses Kommando hin entweder die Meldung
lucy@bluegrass:/home/lucy $ crontab -l no crontab for lucy
wenn sie bisher keine persönliche Crontabelle hatten, oder ihnen wird ihre Crontabelle aufgelistet (siehe Listing 1).
Listing 1: Beispiel einer persönlichen Crontab |
trish@bluegrass:/home/trish $ crontab -l # DO NOT EDIT THIS FILE - edit the master and reinstall. # (/tmp/crontab.XXXXa11176 installed on Tue Mar 3 22:21:01 1998) # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $) 0-59/10 * * * * (who|grep lucy > /dev/null 2>&1; if [ $? = 0 ]; then date; echo -e "\n\tLucy logged in\n"; fi) 0 10-12,13-22 * * * if [ -f /home/trish/.todo ]; then cat /home/trish/.todo; fi 20 7 * * Mon-Fri /usr/local/bin/mpg123 -b 2048 -z `find /home/trish/music -name \*.mp3` > /dev/null 2>&1 DISPLAY=":0.0" 0,15,30,45 * 24,25,26 12 * /home/trish/bin/snow > /dev/null 2>&1 |
Die erste, nicht auskommentierte Zeile in Listing 1 implementiert eine Art Baby-, äh, Pinguin-Phone: Um die Kontrolle darüber zu behalten, wann meine Linux-Pinguinin Lucy auf meinem System rumhackt, schaut cron alle 10 Minuten nach, ob Lucy eingeloggt ist und schickt mir im Falle des Falles eine Mail mit dem Output von date und echo. Die Standardausgabe und eventuelle Fehlermeldungen von who|grep lucy werden nicht mitgemailt, weil beides schon vorher im Nirvana von /dev/null landet. Anstelle von 0-59/10 hätte man für "alle zehn Minuten" auch */10 schreiben können.
Der zweite Cronjob mailt mir zwischen 10 und 12 sowie zwischen 13 und 22 Uhr jeweils zur vollen Stunde -- so vorhanden -- den Inhalt der Datei ~/.todo.
Und weil zeitiges Aufstehen immer ein Problem, aber leider nicht zu vermeiden ist, weckt mich Cronjob Nr. 3 montags bis freitags um 7.20 in der Früh mit etwas netter Mpeg3-Musik. Nachahmern sei empfohlen, die Lautstärkeregelung per xmix o.ä. am Abend zuvor nicht zu vergessen und die Anlage über Nacht anzulassen ;-) Wer einen Kommandozeilenmixer (z.B. mix) installiert hat, kann sich des ersten Problems natürlich auch durch einen entsprechend modifizierten crontab-Eintrag à la
25 7 * * Tue-Fri (/home/trish/bin/mix vb=50;/usr/local/bin/mpg123 -b 2048 -z `find /home/trish/music -name \*.mp3`) > /dev/null 2>&1entledigen.
Für den letzten Cron-Eintrag wird zunächst die Umgebungsvariable DISPLAY auf :0.0 gesetzt. Mit /home/trish/bin/snow wird das Skript aus Listing 2 gestartet. Sollte es nicht existieren oder sonstwelche Fehlermeldungen und Standardausgaben produzieren, wird trish diesmal nicht per Mail informiert, da alle Ausgaben nach /dev/null geleitet werden. Das Datum verrät, daß dieser Cronjob wohl weihnachtliche Stimmung verbreiten soll.
Listing 2: Das Skript snow aus Listing 1 |
#!/bin/sh - USER=trish WM=fvwm2 #genutzter Windowmanager if [ "`ps -a --format USER,comm|grep $USER' *'$WM`" ]; then # dirty Hack: Um herauszufinden, ob $USER unter X arbeitet, # wird geschaut, ob $WM unter ihrer UserID laeuft /bin/bash -c /usr/bin/X11/xsnow & /usr/bin/sleep 10m /usr/bin/killall xsnow # 10 Minuten Schneegestoeber else # falls $USER nicht eingeloggt ist oder nicht unter X arbeitet, # gibt's 'ne nette Wainaxmehl /bin/echo -e "\tMerry X-Mas\n\t\tDein cron" |/usr/bin/mail $USER fi exit |
Nun gibt es in den ersten zwei Cronjobs aus Listing 1 ein Problem: Sie sind darauf angewiesen, daß sich grep, who, date, echo und cat im Default-PATH befinden. Falls es Ruth einfallen sollte, cron aus irgendwelchen Gründen (zum Beispiel einer Sicherheitslücke wegen) mit anderen Default-Werten neu zu kompilieren, steht trish im Regen. Vorsichtshalber wird sie also PATH besser selbst in /var/spool/cron/crontabs/trish festlegen. Wie schon erwähnt, darf sie da als kleines Userlein jedoch nicht ohne weiteres mit ihrem Lieblingseditor drin herumschreiben. Stattdessen wird crontab mit der Option -e bemüht.
Daraufhin kopiert crontab die persönliche Crontabelle zunächst erst einmal in eine temporäre Datei. In der darf man jetzt nach Herzenslust (und den obengenannten Regeln) mit dem Default-Editor oder dem Lieblingseditor aus der in ~/.bash_profile o.ä. gesetzten Umgebungsvariablen VISUAL bzw. EDITOR rumkritzeln und auch zwischenspeichern. Erst, wenn man den Editor geschlossen hat, ersetzt crontab die bisherige persönliche Crontabelle im Spoolverzeichnis durch die temporäre Datei. Die aktuelle Crontab aus Listing 1 hieß früher beispielsweise /tmp/crontab.XXXXa11176. Diese umständlich anmutende Verfahrensweise wird schnell verständlich, wenn man bedenkt, daß cron jede Minute angerannt kommt, um nach den Modifikationszeiten der Crontabellen zu schauen und modifizierte Dateien neu zu laden. Wenn die jetzt mitten im Editiervorgang noch etwas unvollständig zwischengespeichert wurde, hätte man den Salat.
Existierte für den crontab -e aufrufenden User bislang noch keine Crontabelle, wird sie unter dem entsprechenden Usernamen im Spoolverzeichnis erzeugt. Bei ungültigen Einträgen, wie z.B. einem Monat 0 oder einem nullter Tag des Monats, gibt es eine interaktive Fehlermeldung, z.B.:
crontab: installing new crontab "/tmp/crontab.XXXXa01029":14: bad month errors in crontab file, can't install. Do you want to retry the same edit?
crontab kann man zufriedenstellen, indem man mit y oder n antwortet: Ein Nein bedeutet, die fehlerhafte Änderung zu verwerfen (Die temporäre Datei wird trotzdem aufgehoben.), ein Ja läßt einen den Fehler korrigieren. Die von mir verwendete 1994er Version von Paul Vixie hat allerdings einen Bug: Der unmögliche Zeitschritt /0 wird nicht interaktiv abgefangen, und die Endlosschleife, in die man beim vergeblichen Versuch, diese fehlerhafte Crontab zu installieren, gerät, sollte man mutig mit CTRL-C abbrechen.
crontab läßt sich auch nichtinteraktiv benutzen: Ganz in Ruhe kann man so erst einmal im stillen Kämmerlein die ultimative Crontabelle erstellen und optimieren, um letztlich die endgültige Version mit einem beherzten crontab <Dateiname> ins Spoolverzeichnis zu kopieren.
Will man die Cron-Geister, die man rief, vollständig loswerden, genügt ein crontab -r, und die persönliche Crontabelle wird aus dem Spoolverzeichnis gelöscht.
Im Zeitalter grafischer Benutzeroberflächen soll es Leute geben, die für alles mögliche ein grafisches Frontend haben wollen. Doch ein unerschrockenes Häuflein von UNIX-Kommandos weigerte sich bislang standhaft, schöne bunte Kleider anzuziehen, weil es gerade Mode ist. Zu diesen tapferen Recken gehörten auch cron und >crontab. Doch mittlerweile brökelt die Front: Beim GNOME-Projekt wird schon etwas länger an einem Crontab-Manager namens Cromagnon (siehe Abbildung 1) gestrickt und auch KDE kann mit kcrontab 0.1.17 aufwarten.
Sieht nett aus -- kann aber noch nix, außer über sich selbst Auskunft zu geben. Aldy Hernandez, der Autor, ist jedoch gerade fleißig dabei, seine Guile-Sourcen nach C umzuschreiben, und dann soll sich das Testen nicht mehr nur aufs Angucken von Bildchen beschränken. (Vielleicht ist es ja schon so weit, wenn dieser Artikel erscheint...)
... hilft meistens der Blick in den Quellcode. Zumindest bei Vixie-Cron lohnt sich der, denn tief darin versteckt liegen ein paar Perlen, die Leuten, denen die Syntax einer Crontabelle zu kryptisch ist, das Leben erleichtern: Statt einen Cronjob zum Zeitpunkt 0 0 * * * auszuführen, kann man auch selbsterklärend @midnight (sprich: at midnight, um Mitternacht) sagen. Zudem gibt es noch ein nettes Feature:
@reboot echo "bluegrass hat gerade rebootet" | mail -s'Reboot bluegrass' trish@freiburg.linux.de
informiert mich per Mail, wenn mein Rechner bluegrass wegen eines Stromausfalls o.ä. rebooten mußte.
Tabelle 6 listet die möglichen Makros auf. Bevor man sie in der Praxis einsetzt, sollte man sich allerdings vergewissern, daß man wirklich Vixie-Cron verwendet -- mit diesen Features kommt Dillons Cron nämlich nicht klar. Zudem wurden diese Makros wohl mit voller Absicht nicht dokumentiert: Einige funktionieren beim 1994er Vixie-Cron nämlich noch nicht wie erwartet.
Tabelle 6: Proprietäre Crontab-Erweiterungen für Vixie-Cron (nicht dokumentiert) | ||
Makro | entspricht | funktioniert stattdessen allerdings |
---|---|---|
@reboot | beim Reboot | |
@yearly | 0 0 1 1 * | |
@annually | wie @yearly | |
@monthly | 0 0 1 * * | wie @daily |
@weekly | 0 0 * * 1 | wie @daily |
@daily | 0 0 * * * | |
@midnight | wie @daily | |
@hourly | 0 * * * * | gar nicht |
Infos |
[1] Sourcen, Manpages und READMEs zu cron(d) und crontab [2] http://www.gnome.org/ [3] Æleen Frisch: Essential System Administration, 2nd Ed., O'Reilly & Associates 1995, 381 |
Die Autorin |
Patricia Jung findet seit etwa 2 1/2 Jahren, daß Linux das perfekte Betriebssystem für Mädchen ist (Spätestens seit Linus Version 2.0 weiblich ist, sollte das eigentlich auch der weiblichen Hälfte der Menschheit so langsam klar werden *hoff*...), und schon ein paar Jahre länger, daß allen, die das Tastaturwegziehen der Ermutigung zum Selbermachen vorziehen, das Keyboard an den Fingern irreversibel festkleben müßte. Im Moment versucht sie, ihre Linux-Begeisterung als Verlagsvolontärin bei Markt&Technik auch an ihrer Arbeitsstelle weiter zu verbreiten. Patricia und ihre Linux-Pinguinin Lucy sind zu erreichen unter trish@freiburg.linux.de. |
Copyright © 1998 Linux-Magazin Verlag