2.8 Scripts d'initialisation

Niveaux d'exécution

Démarrer le système

Quand vous démarrez votre système, vous voyez beaucoup de texte défiler à l'écran. Vous remarquerez sans doute que ce texte est le même à chaque démarrage. La séquence d'actions qui se déroule devant vos yeux s'appelle la séquence de démarrage et elle est définie de façon plus ou moins statique.

D'abord, votre chargeur de démarrage charge en ménoire l'image du noyau que vous avez définie dans son fichier de configuration. Ensuite, il demande au processeur d'exécuter ce noyau. Ce dernier initialise alors ses propres structures et tâches puis lance le processus d'initialisation (init).

Ce processus monte les systèmes de fichiers définis dans /etc/fstab et exécute quelques scripts placés dans le répertoire /etc/init.d qui, à leur tour, démarrent les services nécessaires au bon fonctionnement du système.

Finalement, quand tous les scripts ont été exécutés, init active les terminaux (en général, les consoles virtuelles que vous obtenez avec les touches Alt-F1, Alt-F2, etc.) et attache un processus appelé agetty à chacun. Ce processus vous permet de vous identifier sur ces terminaux avec login.

Les scripts d'initialisation

En fait, init n'exécute pas les scripts du répertoire /etc/init.d dans n'importe quel ordre. De plus, il n'exécute pas non plus tous les scripts, mais seulement ceux qui doivent l'être. Les scripts à exécuter sont définis dans /etc/runlevels.

Le processus init exécute d'abord les scripts de /etc/init.d vers lesquels un lien symbolique existe dans /etc/runlevels/boot. Les scripts sont généralement exécutés par ordre alphabétique, mais certains contiennent des dépendances qui indiquent que d'autres scripts doivent être exécutés avant eux.

Quand tous les scripts liés dans /etc/runlevels/boot ont été exécutés, init poursuit avec ceux vers lesquels un lien symbolique existe dans /etc/runlevels/default. Ici aussi, les scripts sont généralement exécutés par ordre alphabétique, sauf quand ils contiennent des informations sur des dépendances qui spécifient une séquence d'exécution particulière.

Comment init fonctionne-t-il ?

Evidemment, init ne décide pas tout seul de ce qu'il doit faire. Il a besoin d'un fichier de configuration qui lui indique quelles actions il doit effectuer. Ce fichier est /etc/inittab.

Dans la séquence de démarrage que nous venons d'expliquer, nous avons dit que la première action de init était de monter les systèmes de fichiers. La ligne du fichier /etc/inittab qui provoque cela est la suivante :

si::sysinit:/sbin/rc sysinit

Cette ligne indique à init qu'il doit exécuter /sbin/rc sysinit pour initialiser le système. C'est le script /sbin/rc qui fait vraiment le travail d'initialisation et non pas init qui ne fait que déléguer les tâches.

Ensuite, init exécute tous les scripts vers lesquels un lien symbolique est défini dans /etc/runlevels/boot. La ligne suivante provoque cela :

rc::bootwait:/sbin/rc boot

Encore une fois, le script rc fait le travail. Remarquez que l'option boot passée au script rc correspond au nom du sous-répertoire qui se trouve dans /etc/runlevels.

Ensuite, init lit son fichier de configuration pour savoir quel runlevel (voir cette notion ci-dessous) il doit exécuter. La ligne suivante dans le fichier /etc/inittab définit le niveau d'exécution :

id:3:initdefault:

Dans ce cas (qui est celui de la majorité des utilisateurs de Calculate), le niveau d'exécution est le numéro 3. Avec ce numéro, init trouve ce qu'il doit exécuter pour lancer le niveau d'exécution 3 :

l0:0:wait:/sbin/rc shutdown
l1:S1:wait:/sbin/rc single
l2:2:wait:/sbin/rc nonetwork
l3:3:wait:/sbin/rc default
l4:4:wait:/sbin/rc default
l5:5:wait:/sbin/rc default
l6:6:wait:/sbin/rc reboot

La ligne qui définit le niveau 3 utilise à nouveau le script rc pour démarrer les services, cette fois-ci avec le paramètre default. Remarquez que, encore une fois, le paramètre correspond au nom du sous-répertoire dans /etc/runlevels.

Quand le script rc a terminé, init trouve la liste des consoles virtuelles à activer et quelles commandes il doit utiliser dans son fichier de configuration :

c1:12345:respawn:/sbin/agetty 38400 tty1 linux
c2:12345:respawn:/sbin/agetty 38400 tty2 linux
c3:12345:respawn:/sbin/agetty 38400 tty3 linux
c4:12345:respawn:/sbin/agetty 38400 tty4 linux
c5:12345:respawn:/sbin/agetty 38400 tty5 linux
c6:12345:respawn:/sbin/agetty 38400 tty6 linux

Qu'est-ce qu'un niveau d'exécution ?

Vous avez constaté que init numérote les niveaux d'exécution, (runlevels) qu'il doit activer. Un niveau d'exécution définit un état dans lequel votre système se trouve et contient les scripts nécessaires pour entrer dans ou quitter cet état.

Dans Calculate, sept niveaux d'exécution sont définis : trois internes et quatre définis par l'utilisateur. Les niveaux d'exécution internes sont sysinit, shutdown et reboot et sont utilisés respectivement pour initialiser, éteindre et redémarrer le système.

Les niveaux d'exécution définis par l'utilisateur sont ceux qui correspondent à un sous-répertoire dans /etc/runlevels : boot, default, nonetwork et single. Le niveau d'exécution boot est utilisé pour démarrer tous les services système utilisés par les autres niveaux d'exécution. Les autres niveaux d'exécution se différencient par les services qu'ils activent : default est utilisé en temps normal, nonetwork est utilisé quand aucune connexion réseau n'est souhaitée et single est utilisé pour résoudre d'éventuels problèmes du système.

Utiliser les scripts d'initialisation

Les scripts que rc exécute sont appelés des scripts d'initialisation. Chaque script peut être exécuté avec les options start, stop, restart, pause, zap, status, ineed, iuse, needsme, usesme ou broken.

Pour démarrer, arrêter ou relancer un service (et les autres services nécessaires éventuels), utilisez start, stop et restart. Ainsi pour démarrer postfix, par exemple :

/etc/init.d/postfix start

Note : seuls les services qui ont besoin du service spécifié sont arrêtés ou redémarrés. Les autres services dépendants (ceux qui l'utilisent mais n'en ont pas un besoin impérieux) ne sont pas affectés.

Pour stopper un service sans toucher aux services qui l'utilisent, utilisez l'option pause :

/etc/init.d/postfix pause

Pour afficher le statut d'un service (démarré, arrêté, en pause), utilisez l'option status, par exemple :

/etc/init.d/postfix status

Si le système affirme qu'un service est actif, mais que vous savez qu'il ne l'est pas, utilisez l'option zap pour réinitialiser son statut à « arrêté ». Pour postfix, cela donne :

/etc/init.d/postfix zap

Vous pouvez aussi afficher les services dont un service a besoin avec les options iuse ou ineed. Avec l'option ineed, les services réellement nécessaires sont affichés. Avec iuse, ce sont les services qui peuvent être utilisés sans être indispensables qui sont affichés. De la même façon, vous pouvez afficher la liste des services qui ont besoin (needsme), ou qui peuvent simplement utiliser (usesme), un service particulier. Toujours pour postfix :

/etc/init.d/postfix needsme

Enfin, vous pouvez aussi demander la liste des services requis qui manquent :

/etc/init.d/postfix broken

Utiliser rc-update

Qu'est-ce que rc-update ?

Calculate construit un arbre de dépendances pour déterminer l'ordre d'exécution des services. Cela est loin d'être trivial ; des outils ont donc été créés qui facilitent l'administration des niveaux d'exécution et des scripts d'initialisation.

La commande rc-update permet d'ajouter ou d'enlever un script d'un niveau d'exécution. Cette commande utilise automatiquement le script depscan.sh qui reconstruit l'arbre des dépendances.

Ajouter et enlever des services

Le script rc-update a besoin d'un second argument qui spécifie l'action à effectuer : add, del ou show pour respectivement ajouter, supprimer ou afficher.

Pour ajouter ou supprimer un service, ajoutez simplement add ou del à la commande rc-update et spécifiez ensuite le nom du script d'initialisation et le niveau d'exécution. Par exemple :

rc-update del postfix default

Vous pouvez aussi lancer rc-update show pour simplement voir les scripts d'initialisation activés et leurs niveaux d'exécution.

rc-update show

Configurer les services

Pourquoi encore configurer ?

Les scripts d'initialisation peuvent être complexes. Il vaut donc mieux éviter que les utilisateurs n'aient à les modifier directement; cela évite bien des problèmes. Cependant, les services ont parfois besoin d'être configurés ou de recevoir certaines options.

Il importe donc de bien séparer les scripts de leur configuration car cela permet de mettre à jour les scripts sans que leur configuration ne soit perdue.

Le répertoire /etc/conf.d

Calculate utilise un système facile pour configurer les services. Chaque script d'initialisation qui peut être configuré a un fichier de configuration dans le répertoire /etc/conf.d. Par exemple, le script d'initialisation d'apache2 (/etc/init.d/apache2) a un fichier de configuration /etc/conf.d/apache2 qui contient les options à passer au serveur Apache 2 quand ce dernier est lancé :

APACHE2_OPTS="-D PHP4"

Un tel fichier de configuration ne contient que des définitions de variables (tout comme /etc/make.conf), ce qui permet de configurer facilement un service. Cela permet aussi de fournir des explications sur ces options (sous forme de commentaires).

Ecrire un script d'initialisation

Dois-je faire cela ?

Non. Rédiger un script d'initialisation n'est généralement pas nécessaire puisque Calculate fournit des scripts complets pour tous les services supportés. Cependant, si vous avez installé un service sans l'aide de Portage, vous devrez sans doute écrire un tel script.

Structure

La structure de base d'un script d'initialisation est décrite ci-dessous.

#!/sbin/runscript

depend() {
  (information sur les dépendances)
}

start() {
  (commandes à exécuter pour démarrer le service)
}

stop() {
  (commandes à exécuter pour arrêter le service)
}

restart() {
  (commandes à exécuter pour redémarrer le service)
}

La partie start() est indispensable, les autres sont facultatives.

Dépendances

Le nom du service qui a une dépendance ou le lien vers une dépendance de type virtual sont placés après le type du service.
Il y a deux paramètres de dépendances que vous pouvez définir, et qui influencent l'ordre d'exécution des scripts d'initialisation : use and need. Une dépendance de type need est plus forte que celle de type use.

Une dépendance d'un service virtuel (virtual) est une dépendance qui se rapporte, non pas à un service unique, mais à un service disponible parmi un groupe de services plus ou moins équvalents et interchangeables. Votre script d'initialisation peut très bien dépendre d'un système de journalisation, mais il existe plusieurs tels systèmes disponibles (metalogd, syslog-ng, sysklogd, ...). Comme vous ne pouvez exprimer le besoin (need ) de chacun d'entre-eux (aucun système raisonnable ne possède tous ces systèmes de journalisation installés et opérationnels), nous faisons en sorte que ces services fournissent, solidairement, un service virtuel qui pourra être utilisé comme dépendance.

Jetons un coup d'œil aux dépendances du service postfix.

depend() {
  need net
  use logger dns
  provide mta
}

Comme vous pouvez le voir, postfix :

  • a besoin du service virtuel net qui est fourni par /etc/init.d/net.eth0, par exemple ;
  • utilise un système de journalisation (service virtuel logger) qui est fourni par /etc/init.d/syslog-ng, par exemple ;
  • utilise le service (virtuel) dns qui est fourni par /etc/init.d/named, par exemple ;
  • fournit le service virtuel mta qui indique qu'un serveur de courrier est disponible.

Ordonner la séquence d'exécution

Parfois vous n'avez pas besoin du service à un moment donné, mais de pouvoir dire au système d'initialisation dans quel ordre démarrer (ou arrêter) les scripts. C'est possible, si (et seulement si) le service est disponible, mais aussi si les deux sont à un même niveau d'exécution. Cet ordonnancement est assuré par l'utilisation des paramètres d'ordonnancement before et after.

Considérons maintenant un exemple de fonction depend() du service Portmap :

depend() {
  need net
  before inetd
  before xinetd
}

Vous pouvez aussi remplacer le nom de service par une étoile ("*") pour spécifier tous les services d'un niveau d'exécution, mais cela n'est pas recommandé. Voici un exemple pour lancer un script avant tous les autres dans un niveau d'exécution :

depend() {
  before *
}

Fonctions standard

En plus de la fonction depend(), vous devez définir la fonction start() qui doit contenir les commandes nécessaires pour activer le service. Il est conseillé d'utiliser les fonctions ebegin et eend pour afficher des messages à l'écran et ainsi informer l'utilisateur de ce qu'il advient de la tentative de démarrage d'un service. Voici un exemple de fonction start() :

start() {
  ebegin "Starting my_service" 
  start-stop-daemon --start --quiet --exec /path/to/my_service
  eend $?
}

Vous trouverez plus d'exemples de fonctions start() dans les sources des scripts d'initialisation, localisés dans le répertoire /etc/init.d. La fonction start-stop-daemon a une très bonne page man ; pour la lire, lancez :

man start-stop-daemon

Il est également possible de définir les fonctions stop() et restart(). Mais vous n'aurez pas à le faire à la main : avec start-stop-daemon, votre système d'initialisation s'en charge.

La syntaxe des scripts d'initialisation de Calculate est basée sur bash (Bourne Again Shell). Vous pouvez donc utiliser toutes les fonctionnalités de bash (ou compatibles sh) dans vos scripts.

Ajouter une option personnalisée

Si vous voulez utiliser une option non prévue par nos scripts, vous devez l'ajouter à la variable opts et créer une fonction qui a le même nom que l'option. Par exemple, pour ajouter une option restartdelay:

opts="${opts} restartdelay" 

restartdelay() {
  stop
  sleep 3    # Temporisation de 3 secondes
  start
}

Variables de configuration d'un service

Vous ne devez rien faire de particulier pour utiliser un fichier de configuration dans /etc/conf.d : avant que votre script d'initalisation ne soit exécuté, les variables des fichiers suivants sont initialisées dans cet ordre :

  • /etc/conf.d/<votre script>
  • /etc/conf.d/basic
  • /etc/rc.conf

De plus, si votre script contribue à un service virtuel (comme net), le fichier de configuration correspondant (comme /etc/conf.d/net) sera également lu.

Modifier le comportement des niveaux d'exécution

Quel intérêt et pour qui ?

Les utilisateurs d'ordinateurs portables connaissent bien le problème : vous devez démarrer net.eth0 à la maison, mais pas lorsque vous êtes en vadrouille puisque vous n'êtes alors plus connecté à votre réseau. Vous pouvez adapter le comportement de Calculate.

Par exemple, vous pouvez créer un second niveau d'exécution similaire au niveau « default », mais sans les options réseau. Vous pourrez ensuite sélectionner le niveau d'exécution au démarrage de votre machine.

Utiliser « softlevel »

Créez votre second niveau d'exécution similaire à « default ». Dans notre exemple, nous créons un niveau « offline » :

mkdir /etc/runlevels/offline

Ajoutez les scripts d'initialisation à votre nouveau niveau d'exécution. Par exemple, pour copier le niveau « default » sauf le script net.eth0 :

(copier tous les services du niveau d'exécution default vers offline)
# cd /etc/runlevels/default
# for service in *; do rc-update add $service offline; done
(supprimer les services superflus du niveau d'exécution offline)
# rc-update del net.eth0 offline
(afficher les services du niveau d'exécution offline)
# rc-update show offline
(affichage partiel)
               acpid | offline
          domainname | offline
               local | offline
            net.eth0 |

Ensuite, modifiez la configuration de votre chargeur de démarrage pour y ajouter une nouvelle option pour le niveau « offline ». Par exemple, ajoutez dans /boot/grub/grub.conf :

title Calculate Linux Offline Usage
  root (hd0,0)
  kernel /boot/vmlinuz-5bf7e746 root=/dev/hda3 softlevel=offline

Voilà, c'est terminé. Si vous redémarrez votre machine et que vous choisissez la nouvelle entrée, le niveau d'exécution « offline » sera utilisé au lieu du niveau « default ».

Utiliser « bootlevel »

L'utilisation de l'option bootlevel est tout à fait analogue à celle de softlevel. La seule différence ici, c'est que vous définissez un deuxième niveau d'exécution « boot » au lieu de définir un deuxième niveau d'exécution « défaut ».

Thank you!