weidner/archives/2011/12/23/

Die Jagd nach dem Schnatz

Das Problem

Neulich las ich bei einem meiner ALIX-Rechnern nach dem Start eine Zeile auf der Konsole, die mich stutzen ließ:

Remounting / as read-write ... Done.
Removing /etc/nologin ... Done.
Remounting / as read-only ... mount: / is busy

Debian GNU/Linux 6.0 voyage ttyS0

Gleich meldete ich mich an, um mir die nötige Gewissheit zu verschaffen:

voyage login: root
Password:
Last login: Tue Dec 13 21:00:06 CET 2011 on ttyS0
...
   < http://linux.voyage.hk >   Version: 0.7 (Build Date 20110628)

root@voyage:~# mount
...
/dev/hda2 on / type ext2 (rw,noatime,errors=continue)

Das war keineswegs, was ich mir von diesem Rechner erhofft hatte.

Ich habe mittlerweile einige Rechner mit Voyage Linux aufgesetzt, um neben seinen anderen Vorzügen insbesondere die nur-lesend eingehängte Root-Partition einzusetzen, in der Hoffnung, damit die Lebensdauer der CompactFlash-Karten, auf denen selbiges gespeichert ist, etwas zu verlängern. Ein Vorzug von Flash-Speichern gegenüber Festplatten ist das Fehlen beweglicher Teile, womit eine ganze Klasse von Ausfallrisiken entfällt. Diesem Vorteil steht jedoch ein Nachteil gegenüber, nämlich das diese - insbesondere die MLC-Speicher mit höherer Kapazität - nur eine begrenzte Anzahl von Schreiboperationen erlauben. Aus diesem Grund habe ich verschiedene Verfahren ausprobiert, um diese Rechner mit nur lesend eingehängtem permanenten Speicher zu betreiben.

Diese Rechner sollen als hochverfügbare Geräte eingesetzt werden. Eingebaut und vergessen, weil immer funktionierend. Doch mit beschreibbarer CompactFlash-Karte kann ich nicht sicher sein, dass diese nicht doch eines Tages ausfällt.

Die Geräte sollten Crash-Only betrieben werden können. Kein Schalter zum Herunterfahren, nur einfach ausschalten. Wenn das Dateisystem beschreibbar eingehängt ist, dann ist beim nächsten Boot ein Dateisystem-Check fällig. Da die Geräte nur eine serielle Konsole haben, bemerkt am Ende niemand, das der Rechner auf ein Eingabe wartet, um das Dateisystem zu überprüfen.

Die Jagd beginnt

Also brachte ich meine Ausrüstung in Stellung und begann mit der Jagd.

Die erste Spur war mount: / is busy. Irgendein Prozess hatte eine Datei zum Schreiben geöffnet und verhindert damit, dass der Kernel das Dateisystem nur-lesend umhängt. Aber welcher? Um das herauszubekommen nehme ich fuser:

root@voyage:~# fuser -vm /
                     USER        PID ACCESS COMMAND
/:                   root     kernel mount /
...
                     root       1467 Frce. dhclient
...

Hier hatte ich schon den Übeltäter. Prozess 1467 war der einzige, mit Zugriffsmodus F und das Programm, das lief, war dhclient. Doch war es das wirklich?

root@voyage:~# remountro
mount: / is busy
root@voyage:~# kill 1467
root@voyage:~# remountro
root@voyage:~# mount
...
/dev/hda2 on / type ext2 (ro,relatime,errors=continue)
...

Kein Zweifel. Der DHCP-Client war es. Operation gelungen, Patient tot.

Natürlich sollte dieser Rechner via DHCP konfiguriert werden, aber trotzdem wollte ich die Root-Partition nur-lesend einhängen.

Also besser noch einmal nachschauen, ob sich da etwas machen lässt. Zunächst erstmal: neuer Start, neues Glück.

root@voyage:~# fuser -vm /
                     USER        PID ACCESS COMMAND
/:                   root     kernel mount /
...
                     root       1431 Frce. dhclient
...

Welche Datei wollte er denn nun eigentlich beschreiben? Das sagt mir ein anderes Werkzeug: lsof.

root@voyage:~# lsof -p 1431
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
dhclient 1431 root  cwd    DIR    3,2     4096     2 /
dhclient 1431 root  rtd    DIR    3,2     4096     2 /
dhclient 1431 root  txt    REG    3,2   440660 26949 /sbin/dhclient
dhclient 1431 root  mem    REG    3,2    42572 25691 /lib/libnss_files-2.11.2.so
dhclient 1431 root  mem    REG    3,2  1319176 25666 /lib/libc-2.11.2.so
dhclient 1431 root  mem    REG    3,2   113964 25656 /lib/ld-2.11.2.so
dhclient 1431 root    0u   CHR    1,3      0t0    11 /dev/null
dhclient 1431 root    1u   CHR    1,3      0t0    11 /dev/null
dhclient 1431 root    2u   CHR    1,3      0t0    11 /dev/null
dhclient 1431 root    3w   REG    3,2     1587  5385 /var/lib/dhcp/dhclient.eth0.leases
dhclient 1431 root    4u  pack   3803      0t0   ALL type=SOCK_PACKET
dhclient 1431 root    5u  IPv4   3807      0t0   UDP *:bootpc

Die Datei /var/lib/dhcp/dhclient.eth0.leases enthält die Daten, die der Client vom DHCP-Server bekommen hat. Nach einem Neustart versucht der DHCP-Client die IP-Adresse vom Server zu bekommen, die in dieser Datei steht.

Versuch einer Lösung

Es gibt bei Voyage Linux in der Konfigurationsdatei /etc/default/voyage-util eine Variable VOYAGE_SYNC_DIRS, in der man Verzeichnisse eintragen kann, bei denen während des Betriebes ein tmpfs eingehängt werden soll. Die Verzeichnisse werden beim ordnungsgemäßen Hinunterfahren des Rechners gesichert und beim Neustart aus den Sicherungen befüllt. Das wäre doch traumhaft. Zwei Fliegen mit einer Klappe. Also schnell

VOYAGE_SYNC_DIRS="var/lib/dhcp"

dort eingetragen, gespeichert, Neustart, und

aus der Traum.

Remounting / as read-write ... Done.
Removing /etc/nologin ... Done.
Remounting / as read-only ... mount: / is busy

Debian GNU/Linux 6.0 voyage ttyS0

Anmelden und kontrollieren:

voyage login: root
Password:
Last login: Tue Dec 13 22:57:08 CET 2011 on ttyS0
...
root@voyage:~# mount
rootfs on / type rootfs (rw)
...
/dev/hda2 on / type ext2 (rw,noatime,errors=continue)
...
tmpfs on /var/lib/dhcp type tmpfs (rw,nosuid,relatime,mode=755)

Da musste doch noch etwas anderes sein.

root@voyage:~# fuser -vm /
                     USER        PID ACCESS COMMAND
/:                   root     kernel mount /
...
                     root       1440 Frce. dhclient
...
root@voyage:~# lsof -p 1440
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
dhclient 1440 root  cwd    DIR    3,2     4096     2 /
dhclient 1440 root  rtd    DIR    3,2     4096     2 /
dhclient 1440 root  txt    REG    3,2   440660 26949 /sbin/dhclient
dhclient 1440 root  mem    REG    3,2    42572 25691 /lib/libnss_files-2.11.2.so
dhclient 1440 root  mem    REG    3,2  1319176 25666 /lib/libc-2.11.2.so
dhclient 1440 root  mem    REG    3,2   113964 25656 /lib/ld-2.11.2.so
dhclient 1440 root    0u   CHR    1,3      0t0    11 /dev/null
dhclient 1440 root    1u   CHR    1,3      0t0    11 /dev/null
dhclient 1440 root    2u   CHR    1,3      0t0    11 /dev/null
dhclient 1440 root    3w   REG    3,2     1008  5385 /var/lib/dhcp/dhclient.eth0.leases
dhclient 1440 root    4u  pack   3830      0t0   ALL type=SOCK_PACKET
dhclient 1440 root    5u  IPv4   3834      0t0   UDP *:bootpc

Da war immer noch die Datei /var/lib/dhcp/dhclient.eth0.leases auf dem Root-Dateisystem (DEVICE 3,2) offen. Die müsste doch eigentlich auf dem tmpfs liegen, das unter /var/lib/dhcp eingehängt ist.

Wie kann das sein, dass eine Datei in einem Dateisystem geöffnet wird, wenn ihr Pfad auf ein anderes Dateisystem verweist?

Es geht genau dann, wenn die Datei geöffnet wird, bevor das tmpfs in dem Verzeichnis eingehängt wird.

Wann wird das Verzeichnis eingehängt und wann der DHCP-Client gestartet?

root@voyage:~# ls /etc/rcS.d/
README               S07module-init-tools      S12procps
S01mountkernfs.sh    S07mtab.sh                S12udev-mtab
S02udev              S08checkfs.sh             S12urandom
S03mountdevsubfs.sh  S09ifupdown               S12voyage-sync
S04bootlogd          S09mountall.sh            S13networking
S05hostname.sh       S10mountall-bootclean.sh  S14portmap
S05hwclockfirst.sh   S11mountoverflowtmp       S15mountnfs.sh
S06checkroot.sh      S12ebtables               S16mountnfs-bootclean.sh
S07hwclock.sh        S12iptables-persistent    S17bootmisc.sh
S07ifupdown-clean    S12pcmciautils
S18stop-bootlogd-single

Die Dateien in den rc?.d/ Verzeichnissen werden traditionell in der lexikalischen Reihenfolge ihrer Namen abgearbeitet. Das heisst S12voyage-sync sollte vor S13networking ausgeführt werden. Was war denn da los. Nun sah ich mir die Bootmeldungen weiter oben nochmal genauer an:

INIT: version 2.88 booting
Using makefile-style concurrent boot in runlevel S.

Oha.

Nebenläufig. Um die Wette sozusagen. Nun ja, makefile-style machte mir noch etwas Hoffnung. Da geht es doch um Abhängigkeiten. Kurz nachgesehen:

root@voyage:~# grep Required-Start /etc/init.d/voyage-sync
# Required-Start:       $local_fs
root@voyage:~# grep Required-Start /etc/init.d/networking
# Required-Start:    mountkernfs $local_fs

Beide haben ähnliche Abhängigkeiten, könnten also ungefähr gleichzeitig starten. Und dann geht es darum, wer schneller ist.

Die Lösung

Ich änderte die Zeile in /etc/init.d/networking ab in

# Required-Start:    mountkernfs $local_fs voyage-sync

und machte das dem System bekannt:

root@voyage:~# update-rc.d networking defaults
update-rc.d: using dependency based boot sequencing
update-rc.d: warning: networking start runlevel arguments (2 3 4 5) do not match LSB Default-Start values (S)
update-rc.d: warning: networking stop runlevel arguments (0 1 6) do not match LSB Default-Stop values (0 6)

Neustart und nun:

Remounting / as read-write ... Done.
Removing /etc/nologin ... Done.
Remounting / as read-only ... Done.

Bingo.

Nebenbei: Normalerweise ist bei Voyage Linux /var/lib/dhcp ein symbolischer Link auf /lib/init/rw/var/lib/dhcp (/lib/init/rw/ wird schon sehr früh als tmpfs eingebunden). Ich weiss nicht, warum das bei diesem Rechner anders war, bei einem frisch aufgesetzten Rechner wäre das Problem nicht aufgetreten. Immerhin war es eine gute Gelegenheit, das Vorgehen bei derartigen Problemen zu erläutern.

Posted 2011-12-23
Tags: