shutdown, Emacs, et Firefox

(Nhat Minh Lê (rz0) @ 2009-08-03 01:12:08)

Depuis quelques temps déjà, Emacs 23 dispose de l’option --daemon, qui permet de lancer l’éditeur en tâche de fond. Les connexions s’y font alors avec emacsclient(1), comme avec l’ancien mode serveur. La différence avec server-start est qu’il n’est pas nécessaire d’avoir une frame toujours ouverte. Cela permet entre autre de garder sa session Emacs indépendante de son environnement graphique. Tout cela n’a été rendu possible, bien sûr, que grâce à l’arrivée du multi-tty, qui permet, comme son nom l’indique, à Emacs de se manifester sur plusieurs supports : X, console, etc.

Quel rapport avec shutdown(8) et Firefox, donc ?

Puisque je lance désormais Emacs directement lors de mon login(1) (quelques lignes qui vont bien, dans mon .profile) et que C-x C-c ne supprime plus le processus entier mais seulement sa manifestation sur un terminal donné, il paraît naturel de vouloir tuer Emacs à la fin de ma session.

Or, si je me connecte avec login, je quitte habituellement ma session en éteignant ma machine avec un sudo shutdown -p now. Au-delà des services gérés par rc(8), cette commande a pour effet d’envoyer un SIGTERM à tous les processus vivants. Cela fonctionne à merveille pour tous les programmes ne nécessitant aucune action à leur fermeture ou prenant adroitement en compte SIGTERM. Ce n’est malheureusement ni le cas d’Emacs, ni celui de Firefox, qui se terminent tous deux brutalement, en n’opérant pas la maintenance requise. En utilisant shutdown simplement de cette façon, je me retrouvais donc au démarrage suivant avec un Emacs mécontent de trouver un vieux fichier desktop et un Firefox grognant que la dernière session s’est terminée anormalement.

Il fallait donc trouver un moyen d’obliger Emacs et Firefox à quitter proprement, et ce depuis la ligne de commandes.

Pour Emacs, je n’eus pas trop de mal à trouver comment attacher une fonction ELisp à la réception du signal SIGUSR2 :

(defun my-sigusr2-handler ()
  (interactive)
  (kill-emacs))
(define-key special-event-map [sigusr2] 'my-sigusr2-handler)

Pour Firefox, en revanche, il fallut davantage peiner. La solution que j’ai finalement adoptée n’est sans doute pas la meilleure, mais elle a le mérite de fonctionner. Elle repose sur l’utilisation de MozRepl et de nc(1) (net/netcat dans pkgsrc) pour injecter du JavaScript dans l’instance de Firefox en cours. Le code JavaScript, trouvé dans un module Perl, est le suivant :

var app = repl.Ci.nsIAppStartup;
repl.Cc["@mozilla.org/toolkit/app-startup;1"].
    getService(app).quit(app.eForceQuit);

Après tous ces efforts, j’obtiens enfin un script shell permettant de tuer Emacs et Firefox en toute sérénité (à exécuter en tant qu’utilisateur) :

TIMEOUT=5
waitfor()
{
        while [ $TIMEOUT -gt 0 ] && /usr/bin/pgrep -U $LOGNAME $1 >/dev/null; do
                sleep 1
                TIMEOUT=$((TIMEOUT-1))
        done
}

# Emacs.
/usr/bin/pkill -USR2 -U $LOGNAME emacs
waitfor emacs

# Firefox.
/usr/pkg/sbin/nc localhost 4242 <<EOF >/dev/null
var app = repl.Ci.nsIAppStartup;
repl.Cc["@mozilla.org/toolkit/app-startup;1"].
    getService(app).quit(app.eForceQuit);
EOF
waitfor firefox

À moi les extinctions en une commande ! Encore une fois, on remarquera que c’est la paresse qui guide l’apprenti-administrateur à améliorer son système. :)