Artikel mit ‘bash’ getagged

Die Annalen der Bash-Geschichte

Dienstag, 02. Oktober 2012

Heute mal wieder eine Episode aus den „adventures in modern computing“.

Wer die Bash (oder eine andere gängige Shell, hier geht es aber spezifisch um die Bash) kennt, wird höchstwahrscheinlich ihre äußerst nützliche History-Funktion kennen. Die Bash loggt die Befehle die man auf ihr eingibt und hält sie in einem Puffer bereit, auf den man verschiedentlich zugreifen kann um Befehle erneut auszuführen, anzupassen oder sich einfach ins Gedächtnis zu rufen. In erster Linie ist dies wohl als Arbeitserleichterung gedacht, es hat aber auch Anklänge eines regelrechten Logs, allerdings mit der Einschränkung, daß in der History normalerweise nur die 500 letzten Befehlszeilen vorgehalten werden (mit ein paar Tricks drumherum, z.B. kann man das mehrfache Speichern wiederholter Befehle ganz oder teilweise vermeiden). 500 wird heute vielen als ein vergleichsweise kleiner Wert erscheinen und das sehen offenbar die meisten Betriebssystem-Schmiede genauso und liefern ihre Software mit höheren Voreinstellungen aus (manchmal nur 1000 Zeilen, manchmal aber auch 10000 oder 40000 oder noch mehr). Speicher ist schließlich billig, oder?

Nun, wer eine SSD sein Eigen nennt sieht das vermutlich etwas anders, aber vor allem gibt es noch einen anderen Speicher der nicht sooo billig ist: RAM. Normalerweise liest die Bash nämlich den gesamten Inhalt ihrer History-Datei ein wenn sie startet. Das verlangsamt den Start der Bash und erhöht ihren RAM-Bedarf. Wer also die Bash History auch als Log nutzen möchte und daher die Variable $HISTSIZE auf einen sehr hohen Wert setzt, zahlt dafür mit verringerter Performance. Das fällt auf aktuellen Computern vermutlich gar nicht mal so sehr auf, zumindest bis die Datei so allmählich über ihre ersten Megabytes hinaus gewachsen ist. Auch für diesen Fall ist in der Bash bereits vorgesorgt: es gibt neben der Variablen $HISTSIZE noch die Variable $HISTFILESIZE, erstere definiert wie viele Zeilen der Datei beim Starten der Bash in den Puffer gelesen werden, letztere ist nützlich, um die tatsächliche Größe der Datei (in Zeilen, nicht in Byte) abweichend zu definieren. Eine Möglichkeit ist also, $HISTSIZE vergleichsweise klein zu wählen und $HISTFILESIZE extrem groß zu wählen. So spart man RAM und auch etwas Zeit beim Starten der Bash, kann aber sehr viele Befehle in der History halten. Die sind dann zwar nicht direkt über Funktionen der Bash zugänglich, aber es gibt ja noch die Möglichkeit die Datei zu durchsuchen, z.B. mit dem Programm grep.

Nun würde ich aber gerne ein ewiges Log haben. Auf Kundensystemen ist das zwar kein Ersatz für eine gute Dokumentation des Setups, aber es ist eine gute Ergänzung dazu. Auf meinen eigenen Computer will ich das sogar noch viel mehr, weil ich da auch häufiger mal Dinge ausprobiere, für die ich gar keinen alltäglichen Nutzen habe, an die ich mich aber gerne „erinnern“ können möchte, selbst wenn es nur Spielereien sind wie etwa „Twitter auf der Kommandozeile nutzen“ oder „Nachgucken was für ein Wochentag ein bekanntes historisches Datum hatte“ oder dergleichen. Hier habe ich dann unweigerlich das Problem, daß die History auf mehr Zeilen anwachsen könnte als ich in $HISTFILESIZE erlaubt habe. Zwar kann ich den Wert dort sehr hoch setzen, sogar lächerlich hoch, aber trotzdem ist es eine Grenze und es ist weiterhin nötig, daß die Bash sich anschaut wie groß die Datei überhaupt ist, was mir ja völlig egal wäre. Zumal ich stark annehme, daß ich niemals mehr Zeilen in der History-Datei erlauben könnte als die Bash als größte Zahl unterstützt. Die Bash selbst mag zwar nicht typisiert sein, aber trotzdem wird sie nicht unendlich lange Zahlen verarbeiten können.

Ich hab ein bißchen rumprobiert, konnte aber keine wirklich gute Lösung finden. Laut Dokumentation bewirkt eine $HISTFILESIZE von 0, daß gar keine History-Datei unterhalten wird. Ich würde da am liebsten „unendlich“ reinschreiben, aber das geht nunmal nicht. Die Dokumentation der Bash sagt aber noch, daß das Fehlen der Variable $HISTFILESIZE dazu führen würde, daß die Datei beliebig groß sein darf. Also änderte ich meine .bashrc:

export HISTSIZE=10000
#export HISTFILESIZE=1000000000000000000
unset HISTFILESIZE

Das hatte aber nicht den gewünschten Effekt. Die Bash setzte den Wert von $HISTFILESIZE beharrlich auf 10.000, also den gleichen Wert wie $HISTSIZE. Nur warum?

Ich überlegte bereits, mir eine Logrotate-Config für die History-Datei zu erstellen (das mache ich vielleicht trotzdem noch), aber es ließ mich nicht in Ruhe, daß das nicht mit „Bordmitteln“ der Bash funktionieren wollte, denn eigentlich sollte das was ich mir wünschte durchaus gehen. Nach längerem vergeblichen Suchen (ich kann gar nicht oft genug erwähnen, wie sehr ich Foren hasse), hab ich dann auf einer Mailingliste den entscheidenden Hinweis gefunden: Die Bash liest zuerst ihre Configs ein und dann die History-Datei und wenn zu diesem Zeitpunt die Variable $HISTFILESIZE nicht gesetzt ist, wird sie auf den gleichen Wert gesetzt wie $HISTSIZE. Das ist natürlich vollkommen bescheuert, denn dann kann ich ja de facto diese Variable nicht nicht setzen, jedenfalls nicht in einer Config der Bash. Ich müßte also nach jedem Start einer Bash manuell ein „unset HISTFILESIZE“ auslösen, damit meine History nicht auf 10.000 Zeilen zusammengekürzt wird. Unpraktisch.

Wie soll man also die $HISTFILESIZE nicht setzen können, wenn ihr Fehlen gleichzeitig dazu führt, daß sie automatisch gesetzt wird? Ich erinnerte mich düster, daß die Bash durchaus einen Unterschied macht zwischen gar nicht gesetzten Variablen und gesetzten Variablen denen kein Wert zugewiesen wurde, also „NIL“ sind, wie man so schön sagt. Vielleicht meinte die Dokumentation genau das?

Sie meinte es. Dieser Teil meiner .bashrc sorgt für eine History-Datei ohne Größenbegrenzung, von der dann die letzten 10.000 Zeilen in den History-Puffer geladen werden:

# read this number of lines into history buffer on startup
# carefull with this, it will increase bash memory footprint and load time
export HISTSIZE=10000
# HISTFILESIZE is set *after* bash reads the history file
# (which is done after reading any configs like .bashrc)
# if it is unset at this point it is set to the same value as HISTSIZE
# therefore we must set it to NIL, in which case it isn't "unset",
# but doesn't have a value either, go figure
#unset HISTFILESIZE
export HISTFILESIZE=""

Wie George Takei sagen würde: Oooh my!

Zum Stand von Maildir-Support bei CentOS 5

Montag, 14. Februar 2011

Wir setzen auf den meisten unserer Server qmail bzw. netqmail als MTA ein und erfreuen uns dabei an den vielen Vorzügen, die das Maildir-Format bietet. Zwar kommt dabei meistens ein zusätzliches Tool wie vpopmail oder vmailmgr zum Tragen, was virtualisierte Mailuser bietet; auf unserer Hosting-Plattform Uberspace.de bieten wir aber ganz offiziellen Support für ein „echtes“ Maildir des betreffenden Systemusers. Die netqmail-Dokumentation erläutert dazu in INSTALL.maildir:

The system administrator can set up Maildir as the default for everybody by creating a maildir in the new-user template directory and replacing ./Mailbox with ./Maildir/ in /var/qmail/rc.

Und so sieht das Setup bei uns dann auch aus: Ein /var/qmail/bin/maildirmake /etc/skel/Maildir legt ein Standard-Maildir an, was beim Anlegen eines neuen Users automatisch in dessen Home-Verzeichnis übertragen wird, und qmail-local stellt Mails, die an den Systemuser adressiert sind, dorthin zu.

Nicht völlig überraschend zeigt sich aber, dass traditionelle Mailboxen in einem zentralen Verzeichnis wie z.B. /var/spool/mail derart fest im Betriebssystem verankert sind, dass es mehr als einen Klimmzug braucht, das System sauber an Maildirs anzupassen. Das fängt schon ganz vorne an:

useradd

Beim Anlegen von Benutzern wird standardmäßig eine (leere) Mailbox in /var/spool/mail/$USER angelegt, bzw. in dem Verzeichnis, das in der /etc/login.defs (siehe dort) via MAIL_DIR angegeben ist. Maildirs werden aber nicht unterstützt; genausowenig Mailboxen, die nicht in einem zentralen Verzeichnis liegen, sondern im Home-Verzeichnis des Benutzers. Dieses Anlegen lässt sich nur über die Einstellung CREATE_MAIL_SPOOL=no in der /etc/default/useradd abschalten – was hier ja durchaus Sinn ergibt, denn durch das Anlegen eines Maildirs in /etc/skel haben wir diesen Punkt ohnehin erledigt. (Mit herkömmlichen Mailboxen funktionierte das nicht, da /etc/skel ja die Vorlage für das Home-Verzeichnis des Users ist, die herkömmlichen Mailboxen aber zentralisiert und damit außerhalb des Home-Verzeichnisses liegen.)

$MAIL

Diese Umgebungsvariable wird von verschiedenen Programmen benutzt, um zu identifizieren, wo denn die Mails eines Users liegen. Hier fackelt CentOS 5 nicht lange – in /etc/profile steht hart kodiert:

if [ -x /usr/bin/id ]; then
        USER="`id -un`"
        LOGNAME=$USER
        MAIL="/var/spool/mail/$USER"
fi

Das ist für unsere Zwecke ergo völlig unbrauchbar. Die /etc/profile selbst anzupassen, empfiehlt sich nicht, weil die Änderungen sonst bei jedem Update des setup-RPMs (zu dem diese Datei gehört) hinfällig wären. Stattdessen bietet sich ein Einzeiler in /etc/profile.d an:

# cat /etc/profile.d/maildir.sh 
export MAIL=~/Maildir/

/etc/login.defs

Die man page der login.defs erklärt offenherzig:

Much of the functionality that used to be provided by the shadow password suite is now handled by PAM. Thus, /etc/login.defs is no longer used by programs such as: login(1), passwd(1), su(1)

Trotzdem ist die Datei noch da und wird von einigen Programmen genutzt. Sie sieht im Kopf wie folgt aus:

# *REQUIRED*
#   Directory where mailboxes reside, _or_ name of file, relative to the
#   home directory.  If you _do_ define both, MAIL_DIR takes precedence.
#   QMAIL_DIR is for Qmail
#
#QMAIL_DIR      Maildir
MAIL_DIR        /var/spool/mail
#MAIL_FILE      .mail

Fängt man erstmal an, an dieser Datei – vermutlich erfolglos – herumzubasteln, bekommt man wirklich graue Haare. Die naheliegendste Variante, nämlich den Eintrag QMAIL_DIR durch Entfernen des Rautenzeichens zu aktivieren, ist überraschenderweise keine gute Idee; dass die man page diese Einstellung gar nicht erwähnt, lässt schon tief blicken. So sieht’s dann nämlich aus:

# useradd dummy
configuration error - unknown item 'QMAIL_DIR' (notify administrator)

Fan-tas-tisch. Die NEWS-Datei der shadow-utils erklärt denn auch, dass die Einstellung aus der login.defs entfernt wurde – und zwar 2005. Interessanterweise kennt die man page auch die Einstellung MAIL_FILE nicht, sondern ausschließlich MAIL_DIR. Sie weiß aber auch gleich zu verkünden, dass nur usermod und userdel diese Einstellung benutzen – und useradd sowieso nicht. Was aber sehen wir in src/useradd.c?

      if (strcasecmp (create_mail_spool, "yes") == 0) {
              spool = getdef_str ("MAIL_DIR") ? : "/var/mail";
              file = alloca (strlen (spool) + strlen (user_name) + 2);
              sprintf (file, "%s/%s", spool, user_name);
              fd = open (file, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0);
              [...]
      }

Ist also glatt gelogen; useradd benutzt die Einstellung sehr wohl. Die man page von useradd bekräftigt das auch und erwähnt nicht nur MAIL_DIR, sondern auch MAIL_FILE und behauptet:

The MAIL_DIR and MAIL_FILE variables are used by useradd, usermod, and userdel to create, move, or delete the user´s mail spool.

Das ist nun wiederum auch gelogen, denn wenn MAIL_FILE statt MAIL_DIR gesetzt ist, macht useradd damit … überhaupt nichts. Und auch usermod und userdel, die beim Löschen eines Users dessen Mailbox aus dem zentralen mit MAIL_DIR angegebenen Verzeichnis löschen, unternehmen in Bezug auf Mail schlicht gar nichts, wenn MAIL_FILE gesetzt ist.

Nun, noch weiter in die Tiefe wollen wir hier nicht gehen. Belassen wir es dabei, dass das, was in der /etc/login.defs steht, das, was in der zugehörigen man page steht, das, was in den man pages von useradd, usermod und userdel steht, und schließlich das, was im Code der shadow-utils steht, erheblich voneinander abweicht und sich oftmals sogar widerspricht – kurz, das Ganze funktioniert nur dann sauber, wenn man exakt den traditionellen Standard benutzt: Mailboxen in einem zentralen Verzeichnis.

mailx

CentOS benutzt das mailx-Paket, um den Befehl mail bereitzustellen. Die meisten kennen den Befehl vornehmlich zu dem Zweck, um auf der Kommandozeile direkt Mails via Pipe versenden zu können (some-command | mail recipient@domain.tld). Allerdings ist mail auch ein Mailreader, der auf die Umgebungsvariable MAIL zurückgreift – was von daher erstaunlich ist, weil diese Variable gerade nicht zu denen gehört, die mail laut man page konsultiert:

Mail utilizes the HOME, USER, SHELL, DEAD, PAGER, LISTER, EDITOR, VISUAL and MBOX environment variables.

Trotzdem wird sie benutzt und zeigt dann auch gleich, wieso hier ohnehin Hopfen und Malz verloren ist:

$ mail
/home/jonas/Maildir/: Is a directory

Das mailx-Paket von CentOS hat also schlicht keinen Maildir-Support. Mit dem aktualisierten Paket, das sich in CentOS 6 findet, wird das offenbar kein Problem mehr sein, aber darum soll es hier ja nicht gehen.

bash

Hier endlich mal ein Lichtblick: Die bash unterstützt die Umgebungsvariable MAIL und kann auch korrekt mit Maildirs umgehen. Sie benutzt es, um standardmäßig alle 60 Sekunden nach neuen Mails zu suchen und den Benutzer zu informieren, und das klappt auch:

$ echo | mail $USER ; sleep 60 ; true
You have new mail in /home/jonas/Maildir/

PAM

Möchte man auch direkt beim SSH-Login über neue Mails informiert werden, so kann dies zeitgemäß mit pam_mail.so erledigt werden. Dazu braucht’s nur eine Zeile (danach sshd-Neustart nicht vergessen, sonst wirkt es nicht):

$ tail -1 /etc/pam.d/sshd 
session    optional     pam_mail.so dir=~/Maildir

Die Angabe von dir ist nötig, weil pam_mail.so ansonsten ganz traditionell in /var/mail nach einer Mailbox oder einem Maildir sucht (mit anderen Worten: Überraschung! Dass evtl. ein anderes Verzeichnis als MAIL_DIR in /etc/login.defs definiert ist, spielt überhaupt keine Rolle). Es erkennt automatisch, ob es eine Mailbox oder ein Maildir ist, denn eine Mailbox wäre ja eine Datei, während ein Maildir ein Verzeichnis wäre. So sieht es dann in der Anwendung aus:

$ ssh jonas@helium.uberspace.de
You have new mail in folder /home/jonas/Maildir.

Impressum