Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-151 — Additifs couche 1 pour le lancement : passage explicite de descripteurs et masque de signaux

Statut : Accepté (2026-08-04). RFC de structure (ADR-015). Applique le modèle de re-sceau additif du sceau couche 1 (ADR-062), sous la délégation BDFL actée par ADR-065 (les additifs purs sont traités par le superviseur, sans autorisation au cas par cas).

Catégorie : Additifs à la couche 1 scellée. Aucune rupture ; jalons antérieurs conservés.

Cet ADR ne pose aucun tag. La directive en vigueur est de ne plus re-sceller la couche 1 par incrément mais de poser un seul re-sceau à la complétion d’air-ssh/air-sshd. Les deux additifs décrits ici rejoignent donc l’inventaire collectifnotes/inventaire-additifs-couches-avant-re-sceau-fr.md — où ils sont les 10ᵉ et 11ᵉ. Le numéro de version (couche-1-v3.9 si l’inventaire reste sans rupture) se décide au moment du sceau, sur l’inventaire entier, jamais par un incrément pour lui-même. Une première version de cet ADR réclamait le tag pour ses seuls additifs : c’était une lecture périmée de la gouvernance.

Contexte

Le temps 1 d’air-launchd (couche 2, cadré dans notes/air-launchd-couche2-roadmap-fr.md, spécifié dans specs/layer-2/air-launchd.md) demande à AirCommand deux choses qu’elle ne savait pas faire — et qui, exactement comme ADR-062 l’annonçait pour la libc, sont révélées par la production d’un toit, pas par une revue de l’API.

1. Installer un descripteur ailleurs que sur 0/1/2. Le lanceur remet à l’enfant son canal de lancement sur le descripteur 3. AirStdio ne couvre que les trois flux standard, et login_terminal est un cas particulier de PTY. La brique sous-jacente existait pourtant : SpawnFileAction::Dup2, exposée par spawn_process — mais spawn_process ne rend qu’un Pid, sans pidfd, donc sans attente sûre. Il fallait choisir entre installer un descripteur et superviser proprement ; c’est ce faux choix que l’additif supprime.

2. Ne pas transmettre son masque de signaux. Un superviseur bloque SIGTERM/SIGINT pour les consommer sur un signalfd (ADR-020). Ce blocage est hérité à travers le fork et l’execve. Tout service lancé démarrait donc avec SIGTERM bloqué : insensible à l’insistance, arrêtable seulement par SIGKILL, et rien ne le signalait.

Ce second point n’a pas été trouvé par une revue ni par un test, mais par une exécution réelle : air-sshd lancé par air-launchd sur un exécuteur ne mourait qu’au SIGKILL. C’est la justification la plus honnête de l’additif — il ne comble pas une élégance manquante, il ferme un piège silencieux.

Décision

Descellement additif couche-1-v3.9 : on ajoute à air-process (crate scellée), sur le constructeur AirCommand :

  • AirCommand::inherit_fd(target: RawFd, source: OwnedFd) -> Self — installe source sur le descripteur target de l’enfant, au-delà de 0/1/2. target doit être ≥ 3 et n’être demandé qu’une fois ; les deux conditions sont vérifiées au spawn, qui refuse (InvalidInput) plutôt que d’arbitrer. Le descripteur installé traverse l’execve (dup3, donc sans FD_CLOEXEC) ;
  • AirCommand::clear_signal_mask() -> Self — vide le masque de signaux de l’enfant avant l’execve (rt_sigprocmask(SIG_SETMASK, ∅)).

Purement additif : aucune signature publique existante n’est modifiée, aucun comportement existant ne change. Un appelant qui n’appelle ni l’un ni l’autre obtient exactement ce qu’il obtenait sous couche-1-v3.8 — y compris l’héritage du masque, qui reste le défaut.

Deux détails de conception qui ne sont pas des détails

Les sources sont relocalisées avant le fork. Toutes les sources d’inherit_fd sont dupliquées au-dessus de la plus haute cible (F_DUPFD_CLOEXEC), dans le parent. Aucune source ne peut donc porter le numéro d’une cible, et aucun dup3 de l’enfant ne peut écraser une source pas encore installée : l’ordre d’application cesse d’être un piège, au lieu d’être un piège documenté.

Vider le masque n’est pas le défaut. Le faire d’office changerait ce qu’héritent les appelants existants — un shell qui relaie délibérément son masque serait surpris par une décision prise ailleurs. Un superviseur, lui, sait qu’il doit le demander ; la spec de couche 2 le lui dit.

Conséquences

  • Aucun tag posé ici. Les deux additifs sont inscrits à l’inventaire et seront couverts par le re-sceau collectif, sous la délégation d’ADR-065 (additifs purs : pas d’autorisation au cas par cas, mais pas de tag isolé non plus).
  • air-process gagne deux méthodes ; les consommateurs de couche-1-v3.8 restent compatibles.
  • Le lanceur peut remettre un canal et superviser par pidfd — les deux, sans descendre sous la couche 1.
  • La règle « le masque de signaux ne s’hérite pas d’un superviseur » est désormais outillée : un test lit le masque de l’enfant dans /proc/self/status et vérifie les deux comportements (hérité sans le geste, vide avec).

Alternatives rejetées

  • Utiliser spawn_process (couche 1, déjà capable de Dup2) depuis le lanceur. Rejeté : il rend un Pid nu, sans pidfd. Superviser sur un PID, c’est accepter le réemploi de numéro — précisément ce que la couche 1 a banni (ADR-021, variantes modernes).
  • Passer le canal sur stdin (descripteur 0). Rejeté : un service a le droit d’avoir un stdin, et le confondre avec son canal de contrôle mélangerait deux choses dont l’une est un canal de décision.
  • Vider le masque de signaux d’office dans AirCommand. Rejeté ci-dessus : cela changerait le comportement d’appelants existants, ce qui n’est plus un additif.
  • Laisser le lanceur poser un SIGKILL d’emblée. Rejeté : un service qui n’a pas pu draîner est un service qui a perdu des connexions. Le masque vidé rend l’insistance par SIGTERM efficace, et le SIGKILL redevient le dernier recours qu’il doit être.

Licence du document : MPL 2.0