Hotplug: un exemple

Pour cette clef USB, il a fallu que je comprenne le mécanisme du hotplug. Extrèmement efficace, hal+dbus+udev m’a l’air moins simple…
Le principe: Si le noyau est compilé avec l’option CONFIG_HOTPLUG=y, il a apparait /proc/sys/kernel/hotplug.
Si on fait echo “blop” > /proc/sys/kernel/hotplug, le programme blop sera appelé à chaque évènement hotplug.

Le paquet hotplug est entièrement en bash (et pas en Perl, hein Matt :slightly_smiling:) et s’avère très puissant. [malheureusement, il s’occupe du chargement des modules et du coup est incompatible avec udev, il faudrait virer la gestion des modules du script]. Un exemple sera beaucoup plus parlant. Supposons qu’on veuille faire un système PNP lors de l’insertion des clefs USB, à savoir

  1. Entrée dans /etc/fstab
  2. Création d’un répertoire dans /auto
    Bien évidemment cela disparait au retrait.
    hotplug appelle les scripts automatiquement de la façon suivante:
    Dans /etc/hotplug/usb (car ce sont des périphériques USB), on il cherche les fichier nom.usermap. Si la signature du périphérique si trouve, il appelle le script indiqué sur la ligne. Ce fichier usermap est une suite de ligne de la forme

Pour faire ce fichier, il faut s’inspirer du fichier /etc/hotplug/usb.distmap du paquet hotplug(même format mais avec les noms du modules au lieu du script) pour trouver les lignes correspondantes, par exemple ici, on cherche les clefs USB donc il suffit de récupérer les lignes commençant par usb-storage et remplacer usb-storage par le nom de notre script. Ça donnerait par exemple

$ grep usb-storage /etc/hotplug/usb.distmap | sed -e '1,$s/usb-storage/clef/' > /etc/hotplug/usb/clef.usermap

avec cela, à chaque introduction d’une clef USB, le script /etc/hotplug/usb/clef sera appelé. Les arguments sont passés sous forme de variables d’environnement. On distingue

  • DEVICE qui correspond à l’adresse du périphérique USB par exemple
    /proc/bus/usb/001/001
  • ACTION qui correspond à l’opération faite, essentiellement “add” et “remove”
    [Rq: udev aurait un dispositif hotplug avec DEVNAME au lieu de DEVICE, à vérifier]
  • REMOVER voir plus bas

A partir de ce moment là, on peut faire le travail. Par exemple pour nous, récupérer à partir de dmesg les partitions de la clef USB, changer le groupe de $DEVICE, modifier fstab, etc…
Cela donne le script suivant:

#!/bin/sh
GROUP=disk
if [ ! -f /var/run/hotplug.clef ] ; then
    exit 0
fi
if [ "$ACTION" = "add" ] && [ -f "$DEVICE" ]
then
# 1) On change le groupe de périphérique en disk
    if getent group $GROUP > /dev/null; then
        chmod 660 "$DEVICE"
        chown root:$GROUP "$DEVICE"
    fi
# 2) On récupère la sortie de dmesg
   sleep 4
  while ! dmesg | tail -n 1 | grep -q "scan complete" ; do sleep $[2/10]; done
# est ce que c'est du DEVFS ou pas?
  if [ `dmesg | tail -n 5 | grep "^ sd.:"`AA = "AA" ] ; then
# merdouille, c'est du devfs avec des /dev/scsi/host et...
# on récupère le périphérique (sdqque chose=
    LDEV=`dmesg | tail -n 9 | grep -E "SCSI " | tail -n 1 | sed -e 's/.* \(sd.\):.*$/\1/'`
# on récupère les partitions
    LISTE_PARTITION=`dmesg | tail -n 5 | grep -E "^ /dev/scsi/hos.*:" | sed -e 's/.*: //' | sed -e 's/p//g'`
# Si il n'y a pas de partition, c'est une clef montable directement
    if  echo $LISTE_PARTITION | grep -q -E "^unknow" ; then
        LISTE_PARTITION=$LDEV
    else
# sinon on récupère les partitons
        LI=""
        for g in $LISTE_PARTITION ; do
            f=$LDEV$g
            LI=$LI$f" "
        done
        LISTE_PARTITION=$LI
    fi
# sinon, cas normal non DEVFS
else
    LISTE_PARTITION=`dmesg | tail -n 5 | grep "^ sd.:" | sed -e 's/.*: //'`
    if ! echo $LISTE_PARTITION | grep -q -E "^sd" ; then
        LISTE_PARTITION=`dmesg | tail -n 5 | grep "^ sd.:" | sed -e 's/ *\(sd.*\):.*/\1/'`
    fi
fi
# LISTE_PARTITIONS contient les partitions (avec éventuellement sda: au lieu de sda)
    LISTE=""
    for g in $LISTE_PARTITION ; do
        f=`echo $g | sed -e 's/://'`
         if echo $f | grep -q -E "^sd" ; then
# on crée le répertoire
             mkdir -p /auto/$f
# on crée l'entrée dans /etc/fstab
             echo "#$DEVICE" >> /etc/fstab
# si l'entrée existe déjà, la nouvelle entrée est commentée
             if grep -q /dev/$f /etc/fstab > /dev/null ; then
                 echo -n "#" >> /etc/fstab
             fi
             echo /dev/$f  /auto/$f  auto    user,defaults 0 0 >> /etc/fstab
             LISTE2=$LISTE" "$f
             LISTE=$LISTE2
         fi
    done
# voilà, à ce stade c'est fini pour l'insertion

mais il faut s’occuper de la suppression du répertoire, de l’entrée dans /etc/fstab lors du retrait de la clef. C’est là qu’intervient REMOVER. REMOVER contient le nom du script qui sera appelé lors du retrait de la clef par hotplug. En génral, il suffit donc de terminer le script par un ln -s de $REMOVER vers le script. Mais ici, les opérations sont plus compliquées et il faut passer la liste des repertoires à supprimer comme argument. On va donc fabriquer le script REMOVER en lui faisant faire un appel au script clef lui même avec les bonnes variables d’environnement:

    echo "#!/bin/sh" >> $REMOVER
    echo "DEVICE="$DEVICE" ACTION=\"remove\" DIR=\"""$LISTE"\"" /etc/hotplug/usb/clef" >> $REMOVER
    chmod +x $REMOVER
# fin de l'insertion
fi
# fi de ACTION="add"

Il suffit de rajouter maintenant ce que l’on fait à la suppression:

if [ "$ACTION" = "remove" ]
then
# on démonte en force le répertoire (même si c'est trop tard)
    umount -f /auto/$DIR 2> /dev/null
# on supprime le répertoire
    pushd /auto
    rmdir $DIR
    popd
# On vire les lignes correspondantes dans /etc/fstab
    AVIRER=`echo $DEVICE |  sed -e 's|/|\\\\/|g'`
    echo "sed -e '/^#"$AVIRER"/{N;d}' /etc/fstab > /tmp/fstab"> /tmp/edite
    sh /tmp/edite
    mv /tmp/fstab /etc/fstab
# on dégage les modules éventuellement
    rmmod sd_mod 2> /dev/null
    rmmod usb_storage 2> /dev/null
fi

Voilà, avec tout ça on se retrouve avec un Plug and Play sur les clefs USB très simple d’emploi et efficace.

Bonsoir fran.b,
trés interessant, mais j’hésiste, car ça n’est pas tout à fait du clé en main lol … que dois je faire pour tester le principe ?

  • j’installe hotplug (si apt veut bien, vu les conflits)
  • et j’adapte le script d’hotplug avec tes modifications ?

Sinon, bravo :wink:

En fait tu peux charger le script sur
http://boisson.homeip.net/clef/MontageClefUSB-complet.tgz
Il te faut hotplug d’installé. Tu fais un

cd /

tar xzf MontageClefUSB-complet.tgz

/etc/init.d/clef start

et voilà, l’introduction d’une clef crée un repertoire dans /auto et une ligne adéquat dans /etc/fstab.
Les fichiers dans l’archive sont
./etc/init.d/clef
./etc/hotplug/usb/clef
./etc/hotplug/usb/clef.rmv
./etc/hotplug/usb/clef.usermap
./etc/rc0.d/K99clef
./etc/rc1.d/K99clef
./etc/rc2.d/S99clef
./etc/rc3.d/S99clef
./etc/rc4.d/S99clef
./etc/rc5.d/S99clef
./etc/rc6.d/K99clef

Ah oui, merci, mais attends, ça se complique … car je n’utilise pas inetd, mais xinetd, je n’ai donc pas de rc*.d … tout est géré par le seul fichier /etc/runlevel.conf, une voir deux ligne par process, suivant qu’on traite le shutdown en même temps que le boot ou pas pour le daemon en question.
bon ben je vais y réfléchir aussi, si je peux … merci.

/etc/rc*.d ne dépend pas de inetd mais des scripts initiaux lancés au boot…

Sinon, il suffit de lancer au démarrage en dernier le script /etc/init.d/clef start
J’ai mis une balise pour que le script ne commence son rôle qu’après le boot afin d’éviter les interférences.