Lösung: NFS ohne Warten: automount_nfs_noauto

Hier ist die Musterlösung zu Aufgabe: NFS ohne Warten: automount_nfs_noauto.

Diese Lösung funktioniert exakt in dieser Form nur unter SuSE Linux ab 6.0. Im Prinzip sehr ähnliche Lösungen sind natürlich auf jedem Unix machbar, unterscheiden sich aber in Details (verschiedene run level, andere Verzeichnisse für die Startskripte).

Die Musterlösung erinnert etwas an ein Telefonbuch: viel Text, wenig Handlung. Der Kern des Ganzen ist ein unscheinbares nohup mount $f >/dev/null 2>&1 &. Damit wird ein mount im Hintergrund ausgeführt. Das vorangestellte nohup (no hang up) bewirkt, daß der Hintergrundprozeß ein SIGHUP überlebt. Dieses Signal wird an einen Prozeß geschickt, wenn er keinen Vaterprozeß mehr hat (wenn er also länger läuft als der Prozeß, der ihn gestartet hat).

Mit einem solchen nohup mount $f >/dev/null 2>&1 & wird ein mount im Hintergrund ausgeführt, ohne daß der Aufrufer auf den Erfolg (oder Mißerfolg) des Aufrufs wartet.

Damit wird gemountet, wenn möglich, aber der Aufrufer (während des Bootens) muß nicht auf das Ende des Vorgangs warten. Während mount gemütlich darauf wartet, ob sich jemand am anderen Ende des Kabels meldet, kann der Rechner ja weiter booten.

Interessant ist das Skript aus drei Gründen:

Das Skript habe ich aus der vorhandenen Datei /sbin/init.d/skeleton übernommen, und nur abgeändert. Die englischen Kommentare stammen noch aus dem Original.

#! /bin/sh
# Time-stamp: "01.10.02 08:41 automount_nfs_noauto klaus@wachtler.de"
# (getetest auf aw41)
#
######################################################################
#
#           ///| |||\    || ||||    //
#          ////| |||\\   || ||||   //
#         ////|| ||||\\  || ||||  //
#        //// || |||| \\ || |||| //
#       ////==|| ||||  \\|| ||||//
#      ////   || ||||   \\| |||//
#     ////    || ||||    \| ||//
#                           |//
#         ||||    /|    //  //  ///| ||||=====//  ////=======
#         ||||   /||   //  //  ////| ||||    //  ////
#         ||||  //||  //  //  ////|| ||||   //  ////
#         |||| // || //  //  //// || ||||  //  ////
#         ||||//  ||//  //  ////==|| ||||=((  ((((=======
#         |||//   |//  //  ////   || ||||  \\  \\\\ 
#         |||/    |/  //  ////    || ||||   \\  \\\\=======
#
#####################################################################
#
# Copyright:
############
#
# (C) 2001-2002 AnyWare
#
# Dipl.Ing. Klaus Wachtler
# Breidingstr. 17
# 29614 Soltau
#
# Tel. (+49)-5191-70 2 71, (+49)-171-45 53 039
# Fax: (+49)-5191-70 2 72
# eMail AnyWare@Wachtler.de
#
#####################################################################
#
# Aufgabenstellung:
###################
#
# Mounten von NFS-Verzeichnissen im Hintergrund
#
#####################################################################
#
# Geschichte:
#############
#
# Datum:     Name: Vers. Aenderung:
#
# 07.02.2001 kw    0.0   erster Entwurf (übernommen aus
#                        /sbin/init.d/skeleton von SuSE 6.2)
#
# 09.01.2002 kw          Ergänzungen, mehrfache Versuche bis Erfolg
#
#
#####################################################################
#
# Dieses Skript kann mit dem Parameter "start" aufgerufen werden, und
# mountet dann alle Verzeichisse, die in der /etc/fstab mit dem Typ
# "nfs" (in der dritten Spalte) eingetragen sind, und bei den Optionen
# (vierte Spalte) "noauto" eingetragen ist.
# Diese Verzeichnisse werden im Hintergrund gemountet, und blockieren
# deshalb nicht das weitere Booten, wenn das Skript beim Starten des
# Rechners abgearbeitet wird.
#
# Sinnvoll ist das Skript vor allem bei Notebooks o.ä., die manchmal
# an einem Netz hängen (und dann etwas mounten sollen), manchmal aber
# ohne Netzanbindung booten (und dann nicht minutenlang auf eine
# Verbindung warten sollen, die es nie geben wird).
#
# Ab der Version vom 09.01.2002 wird das Mounten nicht nur einmal
# probiert, sondern das Skript ruft für jedes zu mountende
# Verzeichnis eine shell im Hintergrund auf, und versucht darin das
# Mounten dann etwas länger (für den Fall, daß ein Server erst nach dem
# Client hochkommt).
#
# Installation:
#
# Dieses Skript muß nach /sbin/init.d/automount_nfs_noauto (alte
# SuSE-Versionen, bis etwa 6.x) beziehungsweise nach /etc/rc.d (alle
# anderen Linux) kopiert werden.
# Dann müssen in /sbin/init.d/rc2.d (alte SuSE) beziehungsweise in allen
# gewünschten /etc/rc.d/rc?.d (alle anderen; beispielsweise in
# /etc/rc.d/rc3.d bis /etc/rc.d/rc5.d) zwei symbolische Links
# angelegt werden:
# (Beispiel alte SuSE:)
#   cd /sbin/init.d/rc3.d
#   ln -s  ../automount_nfs_noauto S40automount_nfs_noauto
#   ln -s  ../automount_nfs_noauto K40automount_nfs_noauto
#   cd /sbin/init.d/rc4.d
#   ln ...
#   ...
# (Beispiel alle anderen:)
#   cd /etc/rc.d
#   for i in 3 4 5; do
#       cd rc$i.d;
#       ln -s  ../automount_nfs_noauto S22automount_nfs_noauto;
#       ln -s  ../automount_nfs_noauto K02automount_nfs_noauto;
#       cd ..;
#   done
#
# Die S*- und die K*-Skripte (bzw. die über die symbolischen Links mit
# diesen Namen adressierten Programme) werden in ihrer alphabetischen
# Reihenfolge ausgeführt.
# In den obigen Beispielen sind S22... und K02... ggf. entsprechend
# abzuändern, so daß beim Starten erst alle Skripte ausgeführt werden,
# die von automount_nfs_noauto benötigt werden (insbesondere werden dies
# das Starten des Netzwerks an sich und das Routing sowie der port mapper
# sein, der von NFS benötigt wird), und erst nach diesem Skript dürfen
# die kommen, die von den gemounteten Verzeichnissen abhängen.
# Wird beispielsweise das Netzwerk mit S05network gestartet, das
# Routing mit S07route und der port mapper mit S08portmap, dann muß
# dieses Skript mindestens mit S09... beginnen, weil es sonst
# unsinnigerweise vor dem port mapper gestartet wird.
# Beim Herunterfahren mit den K-Skripten analog, wobei das vorliegende
# Skript beim Herunterfahren z.Zt. gar nichts macht (beim Beenden von
# NFS werden die gemounteten Verzeichnisse ohnehin gekappt, also
# brauchen wir das hier nicht zu machen).
# Insofern könnte man die K-Skripte beziehungsweise die symbolischen
# Links auch gleich weglassen...
#
#
#####################################################################
#
# Umgebung:
############
#
# Windows 32 API
# AnyWare logfile
# VarArr<>
# Qt 1
# Qt 2
# omniORB 2.7.1
# omniORB 3.5
#
#####################################################################
#
# Getestet:
###########
#
# SuSE Linux 6.2 bis 7.3, bash 2.05
#
#####################################################################



# /etc/rc.config (falls vorhanden) in aktueller shell ausführen:
test -e /etc/rc.config && . /etc/rc.config
# Die Variable liste wird zu einem String, der -mit Leerzeichen
# getrennt- alle zu mountenden Verzeichnisse enthält.
#
# Zur Erklärung wird folgende /etc/fstab genommen:
#   # Demo-/etc/fstab
#   /dev/hda1    swap                 swap          defaults       0 0
#   /dev/hda2    /                    ext2          defaults       1 1
#
#   /dev/hdc     /cdrom               iso9660       ro,noauto,user 0 0
#
#   /dev/fd0     /floppy              auto          noauto,user    0 0
#
#   aw33:/home/klaus /home/aw33_klaus nfs         noauto,user,exec 0 0
#
#   aw33:/home/miniarchiv /home/miniarchiv nfs    noauto,user,exec 0 0
#
#   server:/home/fileserver /home/fileserver nfs  noauto,user,exec 0 0
#
#   none            /proc                 proc      defaults       0 0
#
# Erklärungen:
#
# 1) "reguläre Ausdrücke" (regular expression,
#    RE) bezeichnen Strings,
#    die nach bestimmten Regeln aufgebaut sind,
#    und auf einen gegebenen Text entweder zutreffen
#    oder halt nicht.
#    Beispiel: der RE "fängt mit 'A' an, dann kommen
#    zwei Ziffern, dann ein 'B', dann beliebig viele
#    Ziffern, dann ein 'Z'" trifft auf die
#    Texte "A79B123456789Z" und "A12B11Z" zu,
#    aber nicht auf den Text "AttBZ".
#    RE sind in "man 7 regex" beschrieben.
#    Leider gibt es davon zwei Varianten:
#    a) "basic RE" (alte, einfachere Version)
#       und
#    b) "modern" oder "extended" RE.
#    Tatsächlich werden REs natürlich nicht mit Worten beschrieben
#    ("fängt mit 'A' an..."), sondern etwas kryptischer.
#    Dazu mehr weiter unten und in 'man 7 regex'.
#
# 2) 'cat /etc/fstab' kopiert die Datei
#    /etc/fstab zur Standardausgabe
#
# 3) 'col -x' kopiert von Standardeingabe
#    zur Standardausgabe, ersetzt aber
#    alle TAB durch einige Leerzeichen.
#    Dadurch wird die Behandlung in den
#    folgenden "regulären Ausdrücken"
#    einfacher.
# 4) egrep filtert aus seiner Standardeingabe
#    alle Zeilen, auf die der
#    im einzigen Argument angegebene reguläre
#    Ausdruck zutrifft, und kopiert
#    diese Treffer zur Standardausgabe.
#    egrep verwendet "modern" REs.
#    In diesem Fall sind alle Zeilen aus
#    /etc/fstab gewünscht, deren
#    dritte Spalte 'nfs' heißt, und bei
#    denen in der vierten Spalte
#    das Wort 'noauto' vorkommt.
#    Es werden zwei grep verwendet:
#    - der erste läßt alles durch, was kein
#      Kommentar ist,
#    - der zweite läßt alles durch, was in der
#      dritten Spalte "nfs" enthält, und in der
#      vierten irgendwo "noauto".
#    Das erste grep hat den RE "^[ ]*#.*$".
#    Dabei steht '^' für den Zeilenanfang,
#    '[ ]*' für beliebig viele Leerzeichen,
#    '#' für das Kommentarzeichen selbst
#    und '.*' für beliebig viele Zeichen
#    bis zum Zeilenende '$'.
#    Der RE trifft also auf alle Kommentarzeilen
#    in der /etc/fstab zu.
#    Mit der Option -v wird die Wirkung des grep
#    umgedreht; es werden also alle
#    Nicht-Kommentarzeilen durchgelassen.
#    Das zweite grep hat dann den RE
#    "^[^ ]+[ ]+[^ ]+[ ]+nfs[ ]+[^ ]*noauto.*$".
#    Der reguläre Ausdruck beginnt
#    mit einem '^' (für: Zeilenanfang),
#    dann kommt '[^ ]+' (mindestens ein
#    Nicht-Leerzeichen für die erste
#    Spalte),
#    dann '[ ]+' (mindestens ein Leerzeichen
#    für den Platz zwischen erster
#    und zweiter Spalte),
#    dann wieder '[^ ]+' (mindestens ein
#    Nicht-Leerzeichen für die zweite Spalte),
#    dann '[ ]+' (mindestens ein Leerzeichen
#    für den Platz zwischen zwischen zweiter
#    und dritter Spalte), dann
#    'nfs', weil in der dritten Spalte
#    genau dieser Text stehen soll,
#    dann '[ ]+' (mindestens ein Leerzeichen
#    für den Platz zwischen zwischen dritter
#    und vierter Spalte), dann
#    '[^ ]*noauto.*' (keines, eines oder mehrere
#    Nicht-Leerzeichen, dann der Text 'noauto',
#    dann beliebige Zeichen), dann
#    '$' für das Zeilenende.
#    Alle Zeilen, auf die das beschriebene
#    Muster zutrifft, werden zur Standardausgabe
#    weitergereicht.
#    Die Standardausgabe von egrep besteht also
#    aus den folgenden Zeilen:
#      aw33:/home/klaus /home/aw33_klaus  nfs  noauto,user,exec   0 0
#      aw33:/home/miniarchiv /home/miniarchiv nfs noauto,user,exec  0 0
#      server:/home/fileserver /home/fileserver nfs noauto,user,exec 0 0
#
# 5) sed kopiert ebenfalls von der Standardeingabe
#    zur Standardausgabe. Dabei kann es aber den
#    Text editieren. Die Kommandos, wie editiert
#    werden soll, werden hinter der Option '-e'
#    angegeben.
#    Mit dem Kommando s/regulärer_Ausdruck/neuer_Text/
#    wird ein Text, auf den 'regulärer_Ausdruck'
#    zutrifft, durch den Text 'neuerText' ersetzt.
#    Der anzugebende RE ist jetzt leider -anders als
#    bei egrep- ein "basic RE".
#    Hauptunterschied: es gibt kein '+' für "eines
#    oder mehrere". Stattdessen muß man das was man
#    haben will, einmal hinschreiben, und dann nochmal
#    mit einem '*' dahinter (für: "keines, eines,
#    oder mehrere").
#    Zudem kann man bei sed im RE einzelne Bereiche
#    mit '\(' und '\)' klammern. Das hat für den
#    RE selbst erstmal keine Auswirkung, aber die
#    so markierten Bereiche aus einem zum RE passenden
#    Text können im Ersetzungstext wieder
#    verwendet werden. '\1' ist der erste
#    geklammerte Ausdruck, '\2' der zweite usw.
#    'sed -e "s/^[^ ][^ ]*[ ][ ]*\([^ ][^ ]*\).*$/\1/"'
#    ist also so zu lesen:
#    Ersetze aus der Standardeingabe alles, worauf der
#    RE '^[^ ][^ ]*[ ][ ]*\([^ ][^ ]*\).*$' zutrifft,
#    durch '\1', also den Inhalt des ersten Klammerpaares
#    im RE.
#    Zum RE:
#    '^' steht wieder für einen Zeilenanfang.
#    '[^ ][^ ]*' steht für: genau ein Nicht-Leerzeichen
#    ('[^ ]'), gefolgt von keinem, einem, oder mehreren
#    Nicht-Leerzeichen ('[^ ]*'); zusammen also mindestens
#    einem Nicht-Leerzeichen (für die erste Spalte).
#    Dann kommt eines oder mehrere Leerzeichen
#    ('[ ][ ]*') zwischen der ersten und der zweiten
#    Spalte.
#    Dann kommen wieder eines oder mehrere
#    Nicht-Leerzeichen ('\([^ ][^ ]*\)', für
#    die zweite Spalte). Dieser Bereich ist
#    mit '\(' und '\)' geklammert, und kann im
#    Ersetzungstext später als '\1' verwendet werden.
#    Danach dürfen beliebig viele beliebige Zeichen
#    ('.*') bis zum Zeilenende ('$') folgen.
#    Jeder Text, auf den dieses Muster zutrifft (das
#    ist in unserem Fall hoffentlich jede Zeile,
#    da unser egrep-Kommando so gebaut ist, daß nur solche
#    Zeilen geliefert werden) wird ersetzt durch '\1',
#    also der zweiten Spalte aus den gelieferten Zeilen,
#    weil dieser Bereich im RE entsprechend geklammert ist.
#    Die Standardausgabe von sed besteht also
#    aus den folgenden Zeilen:
#      /home/aw33_klaus
#      /home/miniarchiv
#      /home/fileserver
#
# 6) Diese ganze Kommandokette:
#      cat /etc/fstab|col...|sed -e /.../.../
#    würde also aus der Beispiel-/etc/fstab
#    die Ausgabe:
#      /home/aw33_klaus
#      /home/miniarchiv
#      /home/fileserver
#    erzeugen.
#
# 7) Wenn man in der shell ein Kommando (oder eine ganze
#    pipe, wie hier) mit verkehrten Apostrophen
#    klammert (also mit '`'), dann wird in der
#    Kommandozeile der geklammerte Bereich als Programm ausgeführt,
#    aus dem Kommando entfernt und durch seine eigene Standardausgabe
#    ersetzt.
#    Besteht die Ersetzung aus mehreren Zeilen, dann werden
#    die einzelnen Zeilen mit Leerzeichen getrennt
#    aneinandergereiht.
#
# Die folgende Anweisung entspricht also in unserem
# Beispiel der Zeile:
#    liste="/home/aw33_klaus /home/miniarchiv /home/fileserver"
#
liste=`cat /etc/fstab|col -x|\
       egrep -v "^[ ]*#.*$"|\
       egrep "^[^ ]+[ ]+[^ ]+[ ]+nfs[ ]+[^ ]*noauto.*$"|\
       sed -e "s/^[^ ][^ ]*[ ][ ]*\([^ ][^ ]*\).*$/\1/"`
echo $liste



# Determine the base and follow a runlevel link name.
base=${0##*/}
link=${base#*[SK][0-9][0-9]}

# Force execution if not called by a runlevel directory.
#   ???  test $link = $base && START_FOO=yes
#   ???  test "$START_FOO" = yes || exit 0

# The echo return value for success (defined in /etc/rc.config).
return=$rc_done

case "$1" in
    start)
	#echo -n "Starting service foo"
	## Start daemon with startproc(8). If this fails
	## the echo return value is set appropriate.

	#startproc /usr/sbin/foo || return=$rc_failed

        echo Versuche, NFS-Pfade im Hintergrund zu mounten...

        # f wird nacheinander auf einen der Werte
        # "/home/aw33_klaus", "/home/miniarchiv" und
        # "/home/fileserver" gesetzt:
        for f in $liste
        do
            echo Mounte $f im Hintergrund...
            # alte Version: einmal versuchen, zu mounten;
            #               dann aufgeben:
            # nohup mount $f >/dev/null 2>&1 &

            # Version 09.01.2002:
            # solange (im Hintergrund) versuchen, bis mount
            # geklappt hat.
            # Dazu wird nach dem ersten Versuch eine Pause von
            # 1 sec gemacht,
            # nach dem zweiten Versuch eine Pause von 2 sec, und
            # so fort bis 60 sec. Dann werden die Pausen nicht
            # mehr länger.
            # Im Erfolgsfall wird eine kurze Meldung nach
            # /dev/tty1 ausgegeben; das sollte die erste
            # Textkonsole sein.
            nohup bash -c '(export n=0;\
                            until mount '"$f"' >/dev/null 2>&1;\
                            do\
                               if [ $n -lt 60 ];\
                               then\
                                    n=`expr $n + 1`;\
                               fi;\
                               sleep $n;\
                            done;\
                            echo -e "\n'"$f"' gemounted!">/dev/tty1;\
                            echo -e "mfgkw">/dev/tty1\
                            )' >devnull 2>&1 &
        done

        #echo "Fertig mit automount_nfs_noauto!"
	echo -e "$return"
	;;
    stop)
	echo "umounten der automount_nfs_noauto-Verzeichnisse"
	## Stop daemon with killproc(8) and if this fails
	## set echo the echo return value.

        # f wird nacheinander auf einen der Werte
        # "/home/aw33_klaus", "/home/miniarchiv" und
        # "/home/fileserver" gesetzt:
        for f in $liste
        do
            echo umounte $f im Hintergrund...
            nohup umount $f >/dev/null 2>&1 &
        done

	#killproc -TERM /usr/sbin/foo || return=$rc_failed

	echo -e "$return"
	;;
    restart)
	## If first returns OK call the second, if first or
	## second command fails, set echo return value.
	$0 stop  &&  $0 start  ||  return=$rc_failed
	;;
    reload)
	## Choose ONE of the following two cases:

	## First possibility: A few services accepts a signal
	## to reread the (changed) configuration.

	#echo -n "Reload service foo"
	#killproc -HUP /usr/sbin/foo || return=$rc_failed
	#echo -e "$return"

	## Exclusive possibility: Some services must be stopped
	## and started to force a new load of the configuration.

	$0 stop  &&  $0 start  ||  return=$rc_failed
	;;
    status)
	echo -n "Checking for service foo: "
	## Check status with checkproc(8), if process is running
	## checkproc will return with exit status 0.

	#checkproc /usr/sbin/foo && echo OK || echo No process
	;;
    probe)
	## Optional: Probe for the necessity of a reload,
	## give out the argument which is required for a reload.

	#test /etc/foo.conf -nt /var/run/foo.pid && echo reload
	;;
    *)
	echo "Usage: $0 {start|stop|status|restart|reload[|probe]}"
	exit 1
	;;
esac

# Inform the caller not only verbosely and set an exit status.
test "$return" = "$rc_done" || exit 1
exit 0

AnyWare@Wachtler.de