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

air-stdio — brique stdio/console bas-niveau

Couche : 1. Statut : implémentée (brique bas-niveau). Périmètre : roadmap couche 1 (libc + PAL) §2.D.1 (décision BDFL). Verrou : ADR-056 D3/D8 (futex maison + reset post-fork). Conventions : ADR-021 (EINTR remonté, conv.2). no_std : ADR-048 / ADR-055. Nommage : ADR-029 (zéro abréviation).

1. Raison d’être

Les deux toits d’Air — la libc (FILE*) et le PAL (std::io::{Stdin, Stdout, Stderr}) — ont besoin du même socle stdio bas-niveau : des handles vers les descripteurs standard, des E/S brutes, un test « est-ce un terminal ? », et de quoi sérialiser des séquences d’écritures. L’audit PAL a relevé que « stdio bas-niveau manque ». air-stdio est cette brique unique et partagée : chaque toit la réutilise au lieu de la réinventer (et de diverger).

2. Périmètre — le niveau BRUT

Cette crate fournit exactement :

ÉlémentContenu
Handlesstdin / stdout / stderrStandardStream liant 0 / 1 / 2
E/S brutesStandardStream::read / writeun seul syscall, sans buffering
TerminalStandardStream::is_terminal (via air_terminal::tty::isatty)
Verrou par fluxStandardStream::lockStreamGuard (RAII), futex maison 3 états
atforkreset_after_fork — réinitialise les trois verrous dans l’enfant

HORS PÉRIMÈTRE (libc §4, au-dessus)

Cette brique ne bufferise pas et ne formate pas. Vivent dans le toit :

  • le moteur FILE* bufferisé (buffers ligne/bloc, fflush, setvbuf) ;
  • printf / scanf et tout formatage ;
  • la boucle sur écriture courte et le retry EINTR (la brique remonte EINTR tel quel — ADR-021 conv.2 — et rend le compte d’octets d’un seul syscall, potentiellement partiel).

3. Conception : opérations ⟂ liaison

La logique des opérations travaille sur un BorrowedFd quelconque (fonctions privées raw_read / raw_write / raw_is_terminal) ; la liaison aux descripteurs 0 / 1 / 2 est isolée dans stdin / stdout / stderr. Cette séparation rend toute la logique exerçable sur des pipes / ptys en test, sans jamais toucher le vrai stdout/stderr (aucune pollution de la sortie de test, aucun root).

StandardStream est une poignée triviale (Copy) : un BorrowedFd<'static> (le descripteur, ouvert pour toute la vie du processus) + une référence 'static vers le verrou du flux.

4. E/S brutes

  • read(&self, buffer: &mut [u8]) -> Result<usize, Errno> — délègue à air_sys_syscall::fs::read. 0 = fin de fichier.
  • write(&self, buffer: &[u8]) -> Result<usize, Errno> — délègue à air_sys_syscall::fs::write. Le compte rendu peut être inférieur à buffer.len() (écriture courte).

Aucune boucle, aucune bufferisation, aucun retry : un syscall par appel, EINTR remonté à l’appelant (le toit écrit la boucle explicite).

5. Verrou par flux + atfork

Chaque flux possède un mot de verrou statique (AtomicU32) : un futex maison à 3 états (UNLOCKED / LOCKED / CONTENDED, façon Drepper), calqué sur air-alloc/src/lock.rs. Chemin non contendu = un compare_exchange + un swap, sans syscall ; le futex n’entre en jeu que sous contention réelle.

StandardStream::lock prend le verrou et rend un StreamGuard (RAII) qui le relâche au Drop. Le toit s’en sert pour rendre atomique une séquence d’écritures (deux write d’un même println! ne s’entrelacent pas). La brique ne pose jamais le verrou elle-même — read/write sont brutes et non verrouillées ; la sérialisation est une politique du toit.

reset_after_fork réinitialise les trois verrous à l’état déverrouillé. Motivation atfork (ADR-056 D8) : après un fork, un verrou tenu par un autre thread au moment du fork resterait éternellement bloqué dans l’enfant (ce thread n’y existe pas). Le gardien de fork d’air-runtime l’appellera dans l’enfant, exactement comme pour air-alloc::reset_after_fork — sens des dépendances air-runtime → air-stdio, jamais l’inverse.

6. no_std, dépendances, tests

#![no_std], zéro allocation, core seul (couche 1 std-free). Aucune dépendance externe : seulement les crates sœurs air-sys-syscall (read/write/ futex), air-sys-types (FD/Errno/futex) et air-terminal (isatty). Sous #[cfg(test)] la crate redevient std (pipes/ptys réels, threads).

Couverture 100 % (lignes + branches, couche 1) : E/S sondées sur des pipes (succès + EBADF sur mauvais sens d’accès), is_terminal sur PTY réel vs pipe, verrou sondé en contention déterministe deux threads (technique d’air-alloc, sans minutage) et reset_after_fork testé dans le parent sans fork réel.

7. Dépendance différée (§2.D.2)

La roadmap §2.D.2 prévoit qu’air-filesystem prenne une dépendance sur air-stdio (E/S de plus haut niveau adossées aux handles standard). Elle est différée jusqu’à un consommateur concret : introduire la dépendance sans usage la rendrait inutilisée (cargo machete).