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 collectif —notes/inventaire-additifs-couches-avant-re-sceau-fr.md— où ils sont les 10ᵉ et 11ᵉ. Le numéro de version (couche-1-v3.9si 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-sshdlancé parair-launchdsur un exécuteur ne mourait qu’auSIGKILL. 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— installesourcesur le descripteurtargetde l’enfant, au-delà de 0/1/2.targetdoit être ≥ 3 et n’être demandé qu’une fois ; les deux conditions sont vérifiées auspawn, qui refuse (InvalidInput) plutôt que d’arbitrer. Le descripteur installé traverse l’execve(dup3, donc sansFD_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-processgagne deux méthodes ; les consommateurs decouche-1-v3.8restent 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/statuset vérifie les deux comportements (hérité sans le geste, vide avec).
Alternatives rejetées
- Utiliser
spawn_process(couche 1, déjà capable deDup2) depuis le lanceur. Rejeté : il rend unPidnu, sanspidfd. 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 unstdin, 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
SIGKILLd’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 parSIGTERMefficace, et leSIGKILLredevient le dernier recours qu’il doit être.
Licence du document : MPL 2.0