Lancer une application dès le démarrage du système

Lorsqu'un programme doit démarrer dès l'initialisation du système, sans qu'un utilisateur n'ait besoin de se connecter pour le lancer, il est indispensable de respecter certaines règles pour simplifier la mise en service et éviter les problèmes de sécurité. Nous examinerons dans cet article les principaux points importants à envisager, en détaillant les différents types d'interfaces-utilisateur que l'application peut mettre en oeuvre et les configurations qui en découlent.

Introduction

Le développement de Linux permet de multiplier le nombre de stations de travail de type Unix en employant du matériel peu coûteux, dont les performances et la fiabilité connaissent néanmoins une amélioration constante. De plus, l'engouement du public pour Linux met à la disposition des entreprises un nombre important d'administrateurs système et développeurs compétents (c'est peut-être finalement cet engouement du public qui manquait aux projets BSD libres pour une large diffusion dans le monde professionnel).

On peut ainsi rencontrer de plus en plus fréquemment, surtout dans les environnements de production, des sites composés d'une multitude de machines, chacune étant dédiée à une seule et unique tâche. Telle station contient un système d'enregistrement et de statistiques, telle autre un programme de visualisation temps-réel des données, telle autre enfin un outil de diagnostique des mesures capable d'alerter un opérateur en cas de danger potentiel. Une machine dédiée peut également servir à superviser les autres stations et le réseau lui-même, détectant les pannes, les saturations ou les absence de trafic. Dans d'autres environnements, certains ordinateurs seront réservés pour la journalisation des connexions externes, et d'autres serviront à mettre en lumière les tentatives d'intrusion.

Pour simplifier la tâche de l'administrateur et des utilisateurs, il est souhaitable que l'application principale d'une machine soit lancée automatiquement lors du démarrage de cette dernière, sans avoir besoin de passer par une phase de connexion utilisateur / mot de passe. Il faut quand même observer quelques règles de prudence concernant la sécurité du système pour éviter qu'une maladresse non intentionnelle dégrade la configuration de la station.

Nous supposerons donc que l'on dispose d'une application qui doit être installée pour démarrer dès l'initialisation de la machine. En fonction de l'interface utilisateur la configuration sera différente, aussi examinerons-nous plusieurs cas. Pour des raisons de sécurité nous pourrons aussi être amenés à lancer l'application indirectement par le biais d'un petit programme gérant les identifiants de l'utilisateur.

Initialisation du système

Sans entrer vraiment dans les détails, nous devons parcourir rapidement les diverses étapes du démarrage d'une machine pour bien voir où notre application devra s'insérer.

Lorsqu'un ordinateur est mis sous tension, un programme se trouvant en mémoire morte est exécuté. Il vérifie l'état de la mémoire vive, initialise les ports d'entrée-sortie puis, sous contrôle du paramétrage inscrit dans le setup, charge et lance le code se trouvant sur les premiers secteur du disque dur, d'une disquette, voire d'un CD rom. Sur la plupart des stations Linux il s'agit du programme de lancement Lilo, mais on peut aussi rencontrer une image du noyau directement inscrite sur une disquette. Quoiqu'il en soit, l'initialisation du système d'exploitation commence véritablement à ce moment avec le chargement en mémoire du noyau, et son démarrage. Celui-ci détecte et initialise les périphériques, prépare ses structures de données, puis lance un premier processus, init.

Le processus init, dont le PID vaut 1 par convention, consulte le fichier /etc/inittab, et lance les applications qui y sont mentionnées. Un système Linux dispose de plusieurs niveaux d'exécution, qui correspondent à des configurations différentes des applications démarrées. Le niveau d'exécution par défaut est indiqué en début de fichier, par exemple :

  # Démarrage par défaut au niveau 5
  id:5:initdefault:
Sur la plupart des distributions Linux, le niveau d'exécution 3 correspond à un démarrage avec tous les utilitaires standards, mais en mode texte (console) alors que les niveaux 4 ou 5 correspondent au démarrage graphique avec X-Window. On rencontre alors une ligne du genre :
  # Lancer XDM (X-Window Display Manager) dans les niveaux 4 et 5
  x:45:respawn:/usr/X11R6/bin/xdm -nodaemon

Dans le fichier /etc/inittab se trouvent également les lancement des démons système (comme sendmail, crond ou inetd) ainsi que les utilitaires de la famille getty qui gèrent la connexion sur des consoles virtuelles en mode texte.

Le fichier /etc/inittab sera donc le point central de notre configuration, et mérite que l'on s'arrête quelques instants sur sa composition, qui est relativement simple. Les lignes commençant par le caractère # représentent des commentaires, qui sont ignorés. Sinon, les autres lignes ont la structure suivante :

  identifiant:niveaux:type_action:commande

L'identifiant doit être unique. Il s'agit d'un ou deux caractères qui seront essentiellement utilisés pour diagnostiquer les problèmes lors de la lecture des fichiers-journaux du système. Le second champ indique la liste des niveaux d'exécution pour lesquels la ligne s'applique. Certaines lignes ont un rôle particulier et laissent ce champ vide, mais cela ne nous concernera pas ici. Le type d'action indiqué en troisième position permet de qualifier la ligne. Les types les plus intéressants pour nous sont :

D'autres types d'action existent, comme initdefault qui spécifie le niveau d'exécution au démarrage, sysinit qui paramètre le lancement des applications durant le boot, ou encore powerfail ou powerokwait qui gèrent les coupures et reprises de courant grâce à un système d'alimentation protégée.

La partie commande de la ligne devra comporter le chemin d'accès complet jusqu'au fichier exécutable à lancer, suivi de ses éventuels arguments.

Interface utilisateur

En fait, la configuration que nous adopterons pour lancer automatiquement notre application au démarrage du système dépendra essentiellement de son interface utilisateur, qui nous indiquera où et comment placer notre programme. Nous allons étudier en premier lieu le cas des applications n'ayant pas de contact avec l'utilisateur. Nous examinerons ensuite les situations où le programme dispose d'une interface en mode texte, puis en mode graphique.

Application sans interaction avec l'utilisateur

Le cas le plus courant d'application lancée directement au démarrage du système est celui des démons. Il s'agit de programme s'exécutant à l'arrière-plan, sans terminal de contrôle, et chargés pour la plupart de travaux administratifs comme l'exécution de jobs différés (cron), le routage et la distribution du courrier électronique (sendmail), la gestion des connexions réseau (inetd) ou l'impression en tâche de fond (lpd).

Un tel programme doit respecter un certain nombre de règles :

Ces opérations sont réalisées dès l'initialisation du programme par quelques lignes de code. Par exemple le morceaux de programme suivant lance sous forme de démon l'application proprement dite, représentée par la routine mon_application() :
  /* demon.c */
 
  #include <limits.h>
  #include <unistd.h>
  
  int mon_application (int argc, char * argv []);  

    int
  main (int argc, char * argv [])
  {
    int fd;
    if (fork () != 0)
      exit (0);
    setsid();
    for (fd = 0; fd < OPEN_MAX; fd ++)
      close (fd);
    chdir ("/");
    
    return (mon_application (argc, argv));
  }

Démarrage du logiciel

Pour lancer l'exécution du programme, nous allons agir dans /etc/inittab. Il s'agit de la méthode la plus générale, fonctionnant sur toutes les distributions de Linux, et même sur la majeure partie des Unix. Certains administrateurs rechignent à modifier directement ce fichier, en préfèrant agir sur les scripts qu'il lance automatiquement. On peut ainsi ajouter les commandes que nous voulons dans le script rc.local par exemple. C'est une bonne méthode car elle évite de surcharger un fichier système en y apportant des modifications à chaque fois qu'un démon est ajouté. Toutefois nous nous plaçons ici dans le cas d'une machine dédiée essentiellement à une application donnée, et l'intervention sur le fichier /etc/inittab se justifie très bien si elle est limitée à l'installation de ce logiciel.

L'application que nous lançons passe automatiquement à l'arrière-plan. La commande utilisée pour le démarrage va donc se terminer presque immédiatement (dans le exit(0) suivant le fork()). Il n'est donc pas question de demander un démarrage de type respawn, car init essayerait de la relancer tout de suite, bouclant ainsi sur cette ligne pendant quelques secondes au bout desquelles il abandonnerait en différant le lancement pour réessayer au bout de quelques minutes. En attendant, la boucle respawn aurait lancé un nombre important de démons en parallèle, surchargeant le système.

Pour démarrer un démon, on préfère en général un lancement de type wait, init attendant que le processus ait basculé à l'arrière-plan pour continuer son travail. On ajoute donc dans /etc/inittab une ligne du type :

    lm:3:wait:/usr/local/bin/mon_demon
Les caractères du premier champ sont choisis arbitrairement de manière à former un identificateur unique dans le fichier. Le niveau d'exécution indiqué ensuite est celui auquel le démon sera actif. Sauf exception, il s'agira du niveau d'exécution indiqué dans la ligne initdefault au début du fichier.

Transmission de messages administratifs

L'application n'ayant pas de possibilité de dialoguer avec l'utilisateur par l'intermédiaire du terminal, elle doit implémenter son propre mécanisme pour interagir avec son environnement. Selon les cas, elle peut employer des sockets, des tubes nommés, des fichiers et des signaux, tout cela au gré du programmeur ou plutôt du concepteur du logiciel. En revanche, pour pouvoir transmettre des informations de diagnostique, indispensables pour la mise au point et le débogage du système, il faut disposer d'un mécanisme remplaçant l'écriture de message sur la sortie d'erreur.

Nous employons pour cela les possibilités offertes par le démon syslogd. Il s'agit d'un système de journalisation qui centralise tous les messages d'erreur ou de diagnostique émis par les applications l'invoquant, et qui permet d'enregistrer dans un fichier, d'afficher à l'écran, d'envoyer par courrier électronique, ou d'imprimer les traces d'exécution. La configuration du démon syslogd lui-même se paramètre à l'aide du fichier /etc/syslog.conf comme cela a été décrit dans l'article "syslog : maîtrisez l'historique" de Vincent Renardias, paru dans le numéro 19 de Linux Magazine.

Pour le programmeur, l'utilisation de syslog est très simple, puisqu'elle se réduit à l'invocation de trois routines de la bibliothèque C, déclarées dans <syslog.h>. Ces fonctions sont les suivantes :

La configuration permet d'orienter les messages à sa guise en fonction de leur provenance et de leur urgence. Aussi n'hésiterons-nous pas à transmettre des avertissements pour toutes les situations significatives. Même en fonctionnement normal, il est très commode pour l'administrateur de s'assurer de la bonne santé d'un système fonctionnant de manière autonome en jetant un coup d'oeil dans les fichiers de logs comme /var/log/messages.

L'utilisation du système syslog n'est pas réservée aux applications en langage C. Il existe un utilitaire nommé logger qui offre une interface pour les scripts shell. On l'utilisera souvent avec les options :

Il est ensuite suivi du message proprement dit. Voici un exemple :
  #! /bin/sh
  # log.sh exemple d'invocation de syslog
  
  logger -i -t $0 -p user.err "Erreur grave"

  logger -i -t $0 -p user.info "Information utile"

  logger -i -t $0 -p user.debug "Message de déboguage"
Sur la plupart des systèmes les messages de déboguage ne sont pas enregistrés. Nous allons donc modifier notre configuration dans /etc/syslog.conf pour les diriger vers un nouveau fichier, et demander à syslogd de relire sa configuration en lui envoyant un signal SIGHUP. La ligne ajoutée dans /etc/syslog.conf est :
  user.=debug   /var/log/debug
Voici un exemple d'utilisation de notre script :
  # killall -HUP syslogd
  # ./log.sh
  # tail /var/log/messages
  [...]
  Nov 13 16:34:15 venux syslogd 1.3-3: restart.
  nov 13 16:34:29 venux ./log.sh[2859]: Erreur grave
  nov 13 16:34:29 venux ./log.sh[2860]: Information utile
  # tail /var/log/debug
  nov 13 16:34:29 venux ./log.sh[2861]: Message de déboguage
  #

Nous avons donc vu comment un programme sans interface utilisateur peut transmettre quand même des informations administratives au système. Ce mécanisme est aussi largement utilisé dans des programmes interactifs, quand un message doit être dirigé vers l'administrateur système et non pas vers l'utilisateur final.

Limitation des privilèges

Un programme lancé par le processus init s'exécute automatiquement sous l'identité root. Pour un démon tournant à l'arrière-plan, et dont les communications avec les utilisateurs sont soigneusement contrôlées, cela ne pose pas de gros problème. Toutefois lorsque l'application peut être manipulée par un utilisateur quelconque, comme nous le rencontrerons dans les logiciels étudiés plus loin, un véritable danger se présente.

Sans même mettre en doute les intentions bienveillantes de l'utilisateur, une simple maladresse sous l'identité root peut être catastrophique ; imaginez par exemple que l'on sauvegarde les statistiques du mois en cours dans /etc/passwd, /vmlinuz, ou /dev/hda.

Il faut donc arriver à restreindre les privilèges de l'application. Pour cela on peut dans certains cas utiliser les bits Set-UID et Set-GID du fichier exécutable afin que le programme fonctionne automatiquement sous l'identité du propriétaire et du groupe du fichier (que l'on donnera à un utilisateur lambda sans privilège). Ce mécanisme peut parfois être suffisant, mais deux inconvénients se présentent quand même :

Il nous faut donc disposer d'un moyen de diminuer les privilèges des deux types d'UID, et de lancer des scripts avec ces mêmes modifications. 

Nous allons écrire un programme qui prend en argument une chaîne de caractères correspondant à un fichier exécutable suivi d'éventuels arguments. Ce processus va modifier ses UID et GID effectifs et réels pour correspondre à ceux du fichier exécutable visé, puis va l'invoquer avec la commande system().

Ce programme ne dispose pas personnellement de privilèges particuliers, car il n'est conçu que pour en perdre. Ainsi il n'a d'intérêt que lorsqu'il est invoqué par root pour faire démarrer un programme appartenant à un utilisateur lambda. Comme l'invocation sera figée dans des fichiers système sous le contrôle unique de l'administrateur, ce programme ne peut en aucun cas servir à exploiter des failles de sécurité. On peut donc sans risque utiliser la fonction system() si dangereuse dans d'autres circonstances (programmes Set-UID root par exemple).

    /* lanceur.c */
    #include <stdlib.h>
    #include <string.h>
    #include <syslog.h>
    #include <unistd.h>
    #include <sys/stat.h>

    void
set_uid (const char * fichier)
{
    struct stat etat;
    char *      executable;
    char *      blanc;
    /* Copie du nom du fichier pour pouvoir le modifier */
    if ((executable = (char *) malloc (strlen (fichier) + 1)) == NULL) {
        syslog (LOG_ERR, "Mémoire manquante");
        exit (1);
    }
    strcpy (executable, fichier);
    /* Remplacement du premier blanc ou tabulation par un \0 */
    /* Ceci permet de supprimer les éventuels arguments */
    blanc = strchr (executable, ' ');
    if (blanc != NULL)
        blanc [0] = '\0';
    blanc = strchr (executable, '\t');
    if (blanc != NULL)
        blanc [0] = '\0';
    /* Lecture des informations concernant le fichier */
    if (stat (executable, & etat) != 0) {
        free (executable);
        syslog (LOG_ERR, "Fichier %s inaccessible", executable);
        exit (1);
    }
    free (executable);
    /* On fixe les UID/GID effectifs et réels */
    setreuid (etat . st_uid, etat . st_uid);
    setregid (etat . st_gid, etat . st_gid);
}

    int
main (int argc, char * argv [])
{
    openlog (argv [0], 0, LOG_USER);

    if (argc != 2) {
        syslog (LOG_ERR, "Syntaxe : %s commande", argv [0]);
        exit (1);
    }
	
    /* Utilisation des UID et GID du fichier exécutable */
    set_uid (argv [1]);

    /* Lancement du programme */
    system (argv [1]);
    return (0);
}

L'exécution suivante montre bien que lorsque root demande le lancement d'un fichier exécutable appartenant à un autre utilisateur, les UID et GID sont bien modifiés de telle manière que le retour à l'UID root nécessite la saisie du mot de passe (ce qui n'aurait pas été le cas si les UID/GID réels n'étaient pas modifiés).

  $ cc lanceur.c -o lanceur -Wall
  $ chmod +s lanceur
  $ ls -l lanceur
  -rwsrwsr-x  1 ccb  ccb   15680 Nov 16 11:21 lanceur
  $ cp /bin/sh .
  $ ls -l sh
  -rwxr-xr-x  1 ccb  ccb  373176 Nov 16 11:21 sh
  $ su
  Password:
  # ./lanceur ./sh
  bash$ whoami
  ccb
  bash$ su
  Password:
  su: incorrect password
  bash$ exit
  # exit
  $ 

Notez que si l'on doit transmettre des arguments à la commande lancée, il faut encadrer le tout par des guillemets ainsi :

  $ lanceur "/usr/local/bin/mon_application et des arguments"
La ligne de lancement dans le fichier /etc/inittab sera donc finalement modifiée ainsi :
  lm:3:wait:/usr/local/bin/lanceur "/usr/local/bin/mon_demon arg1 arg2"

Application avec une interface en mode texte

Le cas d'une application disposant d'une interface utilisateur en mode texte est intéressant car nous allons devoir gérer nous-même l'affection d'un terminal virtuel. Nous supposons que le but du système est de faire tourner automatiquement un logiciel employant une interface sur un terminal texte, soit avec les flux d'entrées-sorties standards, soit en employant une bibliothèque plus élaborée comme ncurses. Nous voici donc pratiquement revenu au temps du bon vieux Dos. Cette piste n'est d'ailleurs pas totalement incongrue, et pour faire tourner une application ne nécessitant pas la mise en oeuvre de toute la puissance d'un système Unix ni de toutes les possibilités d'interface réseau, le projet Freedos peut être très intéressant en permettant à une application de fonctionner à partir d'une disquette (donc sur des systèmes embarqués sans disque dur).

Notre application va devoir s'attribuer un terminal texte à la manière des utilitaires de la famille getty (mingetty, mgetty, agetty, etc.) Cela s'obtient en ouvrant un pseudo-fichier /dev/ttyX, puis en dupliquant le descripteur obtenu pour l'affecter aux entrées et sorties standards. Le code nécessaire est le suivant :

    void
get_tty (const char * tty)
{
    int    fd;
    /* Ouverture du fichier indiqué */	
    if ((fd = open (tty, O_RDWR, 0)) < 0) {
        syslog (LOG_ERR, "Impossible d'ouvrir %s", tty);
        exit (1);
    }
    /* Vérification qu'il s'agit bien d'un terminal */
    if (! isatty (fd)) {
        syslog (LOG_ERR, "%s n'est pas un terminal", tty);
        exit (1);
    }
    /* Duplication du descripteur sur stdin, stdout, et stderr */
    if ((dup2 (fd, 0) < 0) || (dup2 (fd, 1) < 0) || (dup2 (fd, 2) < 0)) {
        syslog (LOG_ERR, "Erreur dans dup2()");
        exit (1);
    }
    close (fd);
}
Cette routine est appelée en lui passant en argument le nom du pseudo-fichier /dev/ttyX désiré. Pour que l'ouverture réussisse, il faut que le programme s'exécute sous l'identité root. Le plus simple est donc de réaliser l'ouverture avant de modifier les UID et GID comme nous l'avons fait plus haut. Le programme de lancement est alors modifié pour recevoir en premier argument le nom du terminal à ouvrir.
    /* lanceur_texte.c */    
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    #include <syslog.h>
    #include <unistd.h>
    #include <sys/stat.h>

    void
set_uid (const char * fichier)
{
    /* Même code que dans lanceur.c */
}

    void
get_tty (const char * tty)
{
    /* Code présenté ci-dessus */
}

    int
main (int argc, char * argv [])
{
    openlog (argv [0], 0, LOG_USER);

    if (argc != 3) {
        syslog (LOG_ERR, "Syntaxe : %s tty fichier", argv [0]);
        exit (1);
    }
    /* Ouverture de la console indiquée en premier argument */
    get_tty (argv [1]);

    /* Utilisation des UID et GID du fichier exécutable */
    set_uid (argv [2]);

    /* Lancement du programme */
    system (argv [2]);
    return (0);
}

Le démarrage du programme va se faire en principe sur la première console virtuelle, /dev/tty1, avec le niveau d'exécution 3 par défaut. Il va donc falloir remplacer le lancement de getty sur cette console. Il est probable que dans votre fichier /etc/inittab vous rencontriez une ligne du type

1:2345:respawn:/sbin/mingetty tty1
ou
1:2345:respawn:/sbin/mgetty -rb tty1
Il va falloir supprimer cette ligne, et la remplacer par:
1:3:respawn:/usr/local/bin/lanceur_texte /dev/tty1 "/usr/local/bin/mon_appli"

Le niveau d'exécution sera adapté à celui par défaut. Ici l'application ne passe pas en arrière-plan, contrairement aux démons que nous avons vus plus haut, aussi peut-on employer un lancement de type respawn qui redémarrera le logiciel si l'utilisateur l'arrête par erreur. On n'oubliera pas de donner la propriété de l'exécutable mon_appli à un utilisateur et un groupe non privilégiés.

Le lecteur désireux d'expérimenter rapidement cette configuration pourra utiliser comme application en mode texte le Midnight Commander "mc" qui utilise la bibliothèque ncurses :

# cp /usr/bin/mc /usr/local/bin/
# chown nobody.nobody /usr/local/bin/mc
# 
Editer le fichier /etc/inittab, mettre en commentaire la ligne contenant le getty, et en ajouter une faisant démarrer le lanceur :
# Run gettys in standard runlevels
# 1:2345:respawn:/sbin/mingetty tty1                                            
1:2345:respawn:/usr/local/bin/lanceur_texte /dev/tty1 /usr/local/bin/mc
Demander à init de relire ce fichier.
# kill -1 1
#

Basculer sur le terminal virtuel 1 (Contrôle-Alt-F1), et terminer le getty qui y fonctionne (avec Contrôle-D par exemple). À partir de ce moment, mc démarrera automatiquement sur cette console, avec une identité sans privilège (ce que l'on peut vérifier en essayant de déplacer des fichiers système).

Application graphique sous X-Window

Dans la majeure partie des cas actuels on désirera probablement faire tourner une application graphique sous X-Window plutôt qu'un logiciel en mode texte. Les problèmes qui se posent alors sont d'un autre ordre. Nous allons présenter ici une méthode pour les résoudre, mais d'autres techniques seraient sans doutes applicables, chacun pouvant essayer d'expérimenter à son gré.

Le principe retenu ici est de regrouper toutes les commandes nécessaires dans un seul script shell que l'on lance directement dans /etc/inittab. On supprimera alors la ligne qui démarre Xdm, Kdm, Prefdm ou autres :

  x:45:respawn:/usr/X11R6/bin/xdm -nodaemon
On remplacera cette ligne par :
  x:45:respawn:/usr/local/bin/graphique.sh

Le script shell graphique.sh va donc faire démarrer un serveur X-Window en lançant le logiciel /usr/X11R6/bin/X. Dès lors l'écran sera initialisé en mode graphique, avec un fond contenant une sorte de damier - assez laid - et un curseur de souris en forme de croix. On peut observer cette configuration en invoquant la commande "xsetroot -def" dans une fenêtre Xterm sous X11.

Pour pouvoir lancer notre application, nous devons reprendre la main, aussi le lancement de X doit-il être suivi d'un & pour le passer à l'arrière-plan. Nous pouvons dorénavant exécuter notre logiciel, en l'invoquant au travers du programme lanceur vu plus haut, qui limite les privilèges en modifiant les UID et GID. Il est obligatoire de faire suivre l'invocation du programme de l'argument "-display :0.0" qui sera analysé par la bibliothèque Xt, pour que l'application entre en contact avec le bon serveur X. Pour nos expériences, je propose de recopier le fichier exécutable xterm et de modifier son appartenance :

  # cp /usr/X11R6/bin/xterm /usr/local/bin/
  # chown nobody.nobody /usr/local/bin/xterm
  # 
Notre premier script graphique.sh aura donc cette allure :
  #! /bin/sh
  /usr/X11R6/bin/X &
  /usr/local/bin/lanceur "/usr/local/bin/xterm -display :0.0"

Cela fonctionne, mais l'ensemble est un peu décevant, le fond d'écran présente encore son allure de damier fatiguant pour les yeux, et le curseur de la souris conserve sa forme de croix X11. Nous voudrions donc modifier ces paramètres pour rendre l'aspect plus attrayant. Pour cela, il suffit d'invoquer l'utilitaire xsetroot, dont l'option -solid permet de préciser une couleur unie pour le fond, et l'option -cursor_name autorise le chargement d'un curseur de souris différent. Les noms des différents curseurs sont mentionnés dans le fichier /usr/include/X11/cursorfont.h (avec un préfixe XC_ à supprimer). Le curseur le plus courant est left_ptr qui affiche une petite flèche dirigée vers le haut à gauche.

Nous essayons donc d'invoquer xsetroot avant notre application, et... cela ne fonctionne pas ! En effet dès que la connexion entre le serveur X et son premier client (xsetroot) est rompue, le serveur se termine. Il faut donc que notre application démarre en premier, à l'arrière-plan, et que nous invoquions xsetroot par la suite. On peut imaginer quelque chose comme :

  #! /bin/sh
  /usr/X11R6/bin/X &
  /usr/local/bin/lanceur "/usr/local/bin/xterm -display :0.0" &
  /usr/X11R6/bin/xsetroot -solid black -cursor_name left_ptr -display :0.0

Le problème qui apparaît à présent est que ce script se termine presque tout de suite puisque X et xterm sont à l'arrière-plan et que xsetroot dure très peu de temps. Comme nous avons configuré notre fichier /etc/inittab avec une commande respawn, init essaye de relancer le script immédiatement, et finit par boucler.

La solution est de faire appel aux possibilités offertes par Bash pour passer un processus à l'arrière-plan, mémoriser son PID, et attendre la fin d'un processus particulier. Le script final va donc avoir l'aspect suivant :

  #!/bin/sh

  # Lancer X à l'arrière-plan
  /usr/X11R6/bin/X &
  # Mémoriser le PID de la dernière commande en arrière-plan (X)
  PID_X=$!

  # Lancer l'application à l'arrière-plan
  /usr/local/bin/lanceur "/usr/local/bin/xterm -geometry +200+200 -display :0.0 " &
  # Mémoriser son PID
  PID_APPLI=$!

  # Configurer le fond d'écran et le curseur avec xsetroot
  /usr/X11R6/bin/xsetroot -solid black -cursor_name left_ptr -display :0.0 

  # Attendre la fin de l'application
  wait $PID_APPLI

  # Tuer par précaution le serveur X (qui devrait être déjà fini)
  kill -TERM $PID_X
Cette fois-ci le résultat est exactement ce que l'on attendait. Le fond d'écran est acceptable, et l'application est relancée - ainsi que le serveur X - si jamais elle se termine. Le fait qu'aucun gestionnaire de fenêtre ne soit lancé prive l'application des bordures, barre de titre, etc. mais cela n'est pas contraignant si le poste est dédié à une seule utilisation donc a fortiori à une seule fenêtre principale.

Conclusion

Nous avons étudié trois manière de mettre en place une application pour qu'elle soit automatiquement lancée au démarrage de la machine, sans nécessiter d'intervention de l'utilisateur. La configuration d'une application sans interface utilisateur se fait de manière assez classique, en employant les techniques usuelles des démons Unix.

En revanche la mise en service d'applications s'appuyant sur des interfaces en mode texte ou graphique est plus complexe, mais le résultat est intéressant car il montre bien la souplesse de paramétrage d'une machine Unix. J'ai utilisé avec succès ces configurations dans des logiciels de supervision de communication série (en mode texte), et des systèmes de visualisation graphique destinés à du personnel peu familiarisé avec Unix et X-Window (pompiers).


Christophe Blaess
<ccb@club-internet.fr>
http://perso.club-internet.fr/ccb/