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

Roadmap — libc C-ABI scopée OpenSSH (sur la couche 1 scellée)

✅ STATUT 2026-07-10 — M0–M4 livrés ; JALON M5 (toolchain std-sur-Air) ATTEINT. La libc C-ABI compte ~256 symboles (Rust pur) ; std lie et tourne contre libair (zéro glibc), hello-std s’exécute on-target 2 arches (hello from std on air

  • exit 0) — la toolchain Rust full-Air est prouvée (ADR-076).

⚠️ Recadrage BDFL (2026-07-10) — le jalon « compiler OpenSSH réel » est RETIRÉ. Cette roadmap a rempli son rôle : compiler OpenSSH n’était qu’une fonction de forçage (le consommateur C le plus exigeant, pour piloter la libc et révéler les additifs couche 1). Elle a donné une libc C-ABI de ~256 symboles et une toolchain std prouvée. On ne vise pas à faire tourner le C d’OpenSSH upstream sur Air. La vraie cible = air-sshd, notre serveur SSH full Rust (async io_uring, wire-compat OpenSSH, conf binaire — ADR-074). Le §6 « M5 — OpenSSH » ci-dessous est conservé comme historique ; la production courante passe à air-sshd. Statut vivant : etat-avancement.md.

But. Produire la libc C-ABI d’Air, bornée à ce dont OpenSSH a besoin (ADR-046/047), comme toit sur la couche 1 scellée (couche-1-v1.0, re-scellée additivement jusqu’à couche-1-v1.9). Jalon final = sshd + ssh configurent, linkent et tournent sur Air. La libc sert de spec-de-certitude pour le PAL (std::sys) et de driver pour révéler les additifs v1.x de la couche 1.

Ordre stratégique (décision BDFL) : libc d’abord (consommateur le plus exigeant, sur-ensemble des besoins du PAL) → PAL ensuite (sous-ensemble + delta Rust) → ABI C air_* (riche/sécure/hors-normes) en couronnement.

Priorité de production, dans cet ordre : SÉCURITÉ > QUALITÉ > PERFORMANCE (Principe 5 : sur-sécuriser puis mesurer, jamais l’inverse).

1. Périmètre & non-buts

  • Cible unique : OpenSSH (sshd + ssh + ssh-keygen/agent). Rien de plus.
  • Familles dans le scope (ADR-046, ~25-30) : crt/startup, errno, stdio (FILE*), string/mem, stdlib (malloc/exit/getenv/qsort…), unistd/fcntl, stat/dirent, signal, pthread, network (socket/getaddrinfo), time, termios/pty, pwd/grp/shadow, setjmp/longjmp, misc (getopt/realpath/getpass/openpty/syslog/basename).
  • Non-buts (hors scope, différés) : locale complète (minimum vital seulement), printf/scanf exotiques (positions %n$, locale numérique), aio, IPC SysV, mq, dlopen/ldso, wordexp/glob (rejetés RCE, ADR-046 D7), NSS/pam, iconv large. Ajoutés additivement si un besoin OpenSSH réel émerge.
  • Kernel = bible, pas POSIX : conformité fonctionnelle (SHOULD→MUST), pas conformité de certification. Voir §3.

2. Architecture

  • Rust-first, shim mince. Chaque symbole libc = un #[no_mangle] pub extern "C" fin au-dessus d’une brique couche 1 scellée (dual-face : la brique Rust est conçue 1 fois, consommée par la libc et le PAL). L’unsafe/FFI est assumé côté frontière C (C n’a pas de sûreté) ; l’implémentation derrière reste des appels couche 1 sûrs.
  • Structure de crates (à figer en M0, sur le modèle air-base-capi) : des crates *-capi par famille (ex. air-stdio-capi, air-posix-io-capi…) agrégées en l’artefact final libc (.so + .a) + objets crt1.o/crti.o/crtn.o.
  • Architecture « logique mesurée / shims C-ABI exclus » (gravée dès M1-b, étendue M1-c)le POURQUOI : nos symboles #[no_mangle] extern "C" (malloc, memcpy, fwrite…) surchargent les symboles du runtime de couverture LLVM et de l’allocateur du harnais ⇒ l’écriture du profil llvm-cov échoue (crate à 0 %). Règle absolue : une crate dont on veut MESURER la couverture ne lie AUCUNE crate exportant un symbole libc #[no_mangle] (ni sien, ni transitif). On scinde donc : la LOGIQUE pure (politique d’allocation, moteur format, FILE* bufferisé, atexit/getenv…) vit dans des crates sans #[no_mangle], donc mesurées à ~100 % (air-libc-fmt, air-libc-alloc, air-libc-stdio, air-libc-stdlib, …) ; les shims #[no_mangle] extern "C" minces qui délèguent vivent dans air-libc-capi (exclus du gate de couverture, --ignore-filename- regex). Les shims divergents (exit/abort/_Exit -> !) y vivent aussi (insondables par un test du gate, comme air-process/src/exit.rs).
  • crt / startup : _start__libc_start_mainmain, câblé sur air_runtime::start::bootstrap (déjà prouvé on-target, selftest exit 42) : TLS, environ/argv/envp, atexit, errno #[thread_local].
  • errno : per-thread via air-runtime (déjà en place). Zéro globale libc (vision Air) ; errno in-band, pas d’état caché.
  • FILE* : struct C de la couche libc (au-dessus de la couche 1), bufferisée (_IOFBF/_IOLBF/_IONBF), enveloppant un AirFile/air-stdio (fd) + tampon + orientation + flags error/eof + verrou (flockfile). La couche 1 reste sans tampon ; toute la bufferisation vit ici.
  • sigaction : au-dessus d’air-signal. Sur les fautes synchrones : fourni mais INERTE (compile, réussit, handler jamais appelé, coredump toujours — ADR-064 §2). Signaux asynchrones : émulés via signalfd + drainage (ou sigaction réel non- faute, décision M3).

3. Doctrine de conformité (rigueur exigée)

Chaque symbole porte, dans sa doc (§4), une étiquette de conformité :

ÉtiquetteSens
POSIXPILE conforme C/POSIX : comportement, valeurs de retour, errno identiques à la norme.
POSIX-PARTIELFonctionnellement POSIX, avec déviations documentées (ex. erreur plus stricte, zéro troncature muette, borne explicite). Chaque déviation listée + justifiée.
NON-POSIX-DÉLIBÉRÉOn choisit de dévier (ex. sigaction inerte sur fautes) — raison obligatoire + renvoi ADR.
AIR-EXTExtension Air (air_* : variantes riches/sécures qui restituent le Result). Hors POSIX, valeur ajoutée.
  • Registre central : docs/libc-conformance.md — table symbole → étiquette → (déviation/justification/ADR). Vérifié en CI (chaque symbole exporté a une étiquette).
  • Cas phares à tracer : sigaction/signal inertes sur fautes (NON-POSIX-DÉLIBÉRÉ, ADR-064) ; free/air_free (POSIX void vs AIR-EXT int) ; toute fonction qui refuse une troncature muette (POSIX-PARTIEL, ADR-032).

4. Documentation — 3 sorties depuis UNE source

Source de vérité unique : les blocs de doc sur les extern "C" Rust (///), incluant l’étiquette de conformité (§3) et le POURQUOI (transparence Air : dire ce qu’on fait, les pièges, les alternatives — pas que la signature).

  1. Rust : rustdoc natif sur les fonctions FFI.
  2. Doxygen : le header C committé (.h, déjà discipliné en CI — gate « header ABI C ≟ committé ») porte les commentaires doxygen /** */, miroir de la doc Rust (mêmes étiquettes). Doxygen → HTML + XML.
  3. man (3ᵉ format) : dérivé de doxygenGENERATE_MAN = YES produit les pages man(3) depuis la même source doxygen. → Oui, le 3ᵉ format est produisible depuis les deux premiers (précisément : depuis la source doxygen).
  • Cohérence outillée : un gate CI vérifie Rust-doc ↔ header ↔ étiquettes (extension du gate header ABI C committé existant). Aucune dérive silencieuse.
  • Pinning (ADR-025) : version de doxygen figée (reproductibilité).

5. Règles de production (intégralement respectées, dans l’ordre)

① SÉCURITÉ. Zéro UB ; toute donnée externe parsée (format de printf/scanf, getaddrinfo, strtol…) est bornée + fuzzée (cargo-fuzz). Pas de troncature muette (ADR-032). Les tables d’état couche 1 (handles/allocs) alimentent la traçabilité (vision instrumentable, ADR-063 en debug). Frontière C = unsafe assumé et localisé ; implémentation derrière = couche 1 sûre. ② QUALITÉ. cargo xtask barrier vert ; couverture (100 % lignes sur la logique Rust ; les shims triviaux couverts par les tests de conformité fonctionnels + harnais C réels) ; tests de conformité ABI (ADR-012 : air-abi-check/air-symver sur les symboles exportés) ; corpus de vecteurs (ex. printf contre une référence). Zéro Co-Authored-By ; DCO + signature. ③ PERFORMANCE. Après sécurité + qualité (Principe 5) : mesurer (chemins chauds = memcpy/stdio/malloc), justifier par écrit toute optimisation qui allège une défense, garantir l’invariant par construction. Cible matériel modeste (Pi 4).

6. Jalons (topologiques — chacun testable, additif, PR par PR)

  • M0 — Fondations. ☑ fait. Structure de crates *-capi + conventions ABI-C figées ; pipeline doc (rustdoc + doxygen + man) câblé + gate cohérence ; crt (crt1/i/n sur air-runtime::bootstrap) + errno ; libc-conformance.md initialisé. Test : int main(){return 42;} C linke sur la libc Air et sort 42 on-target.
  • M1 — stdio + string/mem + stdlib cœur. FILE* bufferisé sur air-stdio/AirFile (fopen/fclose/fread/fwrite/fflush/setvbuf/fgets/fputs), moteur vfprintf fuzzé, malloc/free/calloc/realloc (sur air-alloc), str*/mem*, exit/ atexit/abort, getenv/setenv (sur air-env). **Test : printf hello-world C
    • une poignée de programmes C réels.** Découpé en tranches PR : M1-a string/mem ☑, M1-b malloc + moteur de format fuzzé ☑, M1-c FILE*+exit/atexit/getenv ☑, M1-d frontière variadique printf (option A : shim C va_arg + glue Rust mesurée air-libc-printf + hello-world C) ☑.
    • Frontière variadique printf — décision BDFL : option (A). Rust-stable ne peut définir une fonction C variadique (c_variadic = nightly), or la libc doit rester sur stable (ADR-025, barrier, couverture). Retenu : parseur + moteur de format 100 % Rust fuzzé (surface d’attaque N°1 en Rust) + un shim C mince faisant le seul va_arg mécanique (piloté par le plan d’args du parseur, puis re-délègue au moteur Rust). C’est la 1ʳᵉ exception nommée à « zéro C » — minimale, sans logique ni surface d’attaque, justifiée par l’impossibilité c_variadic sur stable. À consigner dans docs/EXCEPTIONS.md + le registre de conformité à l’impl (M1-d).
  • M2 — I/O fichier & métadonnées. ☑ fait (validé). open/ openat/creat/read/write/close/lseek/pread/pwrite/dup/dup2/dup3/fcntl/pipe/pipe2 (sur couche 0, FD brut, EINTR remonté, O_CLOEXEC POSIX rétabli), stat/fstat/ lstat/fstatat/access/faccessat (struct stat ABI Air), opendir/fdopendir/ readdir/closedir/rewinddir (DIR opaque + struct dirent, parseur getdents64 fuzzé), mkstemp/mkdtemp (entropie kernel), realpath (sur canonicalize), getcwd/chdir/fchdir. Architecture : logique mesurée air-libc-fileio + shims air-libc-capi::fileio + shim C variadique fileio_shim.c (open/openat/ fcntl, option A — même exception « zéro C » que printf). Déviations gravées D-M2.1..D-M2.5 (cf. libc-conformance.md). Test : I/O fichier + parcours de répertoire (round-trips réels + fuzz fuzz_libc_dirent).
  • M3 — signaux & threads. ☑ fait. sigaction/signal (inertes sur fautes, ADR-064), sigprocmask/kill/raise/sigsuspend (sur air-signal) ; pthread_* (create/ join/mutex/cond/rwlock/once/key+dtor, sur air-thread+TLS). Test : threading + signaux non-faute drainés ; faute → coredump, handler ignoré.
  • M4 — réseau, temps, tty, comptes, setjmp, misc. ☑ fait. socket/bind/connect/listen/ accept/send/recv/getaddrinfo/getnameinfo/inet_pton (sur air-socket) ; time/ clock_gettime/nanosleep/gmtime/strftime ; tcgetattr/tcsetattr/cfmakeraw/openpty (sur air-terminal) ; getpwnam/getpwuid/getgrnam (backends /etc air-config) ; setjmp/longjmp (îlot asm, ADR-047 §4-H) ; getopt/getpass/syslog/basename.
  • M5 — OpenSSH. RETIRÉ (recadrage BDFL 2026-07-10) — conservé comme historique. L’intention d’origine : fournir les headers manquants, compiler OpenSSH upstream contre la libc Air, combler les manques révélés. Cette étape est abandonnée en tant que jalon : compiler OpenSSH n’était qu’une fonction de forçage (elle a rempli son rôle — libc ~256 symboles + toolchain std). Remplacée par air-sshd : notre serveur SSH full Rust (async io_uring, wire-compat OpenSSH, conf binaire — ADR-074). Le jalon « toolchain std-sur-Air » (M5 au sens du suivi) est, lui, ATTEINT (cf. bandeau). Les manques libc éventuels révélés par air-sshd restent traités par descellement additif v1.x documenté.

(Chaque jalon = une ou plusieurs PR ; barrier vert + conformité + doc + coverage avant merge. Chaque additif couche 1 révélé est traité par RFC additif.)

7. Points de cohabitation avec le PAL (à capturer au fil de l’eau)

Dans un process mixte Rust(PAL)+C(libc), à documenter dès qu’un jalon les touche : allocateur partagé (air-alloc sous les deux) · errno cohérent (per-thread air-runtime) · TLS (un seul modèle) · fd 0/1/2 (bufferisation FILE* vs stdio du PAL) · délivrance des signaux (un seul régime). Ces notes deviennent la spec de certitude du PAL.

8. Rapport de fin de production (livrable exigé)

En fin de production (et à chaque jalon majeur mergé), un rapport détaillé :

  • Choix faits + POURQUOI, famille par famille (dont structure de crates, modèle FILE*, errno, gestion signaux).
  • Table de conformité consolidée (POSIX / POSIX-PARTIEL / NON-POSIX-DÉLIBÉRÉ / AIR-EXT) + journal des déviations (chacune justifiée + ADR).
  • Additifs couche 1 v1.x déclenchés par la libc (descellements additifs).
  • Notes de cohabitation PAL (§7).
  • Mesures de performance (chemins chauds) + toute défense allégée justifiée.

9. Exécution autonome

Même boucle que la campagne de sceau : production déléguée claude -p headless (carbon/speedy), superviseur pilote git (branche/valide/barrier/PR/squash-merge/ re-vérif main+trailer) ; doc pure = commit direct main signé (worktree isolé) ; zéro Co-Authored-By. Pilotage autonome jalon par jalon ; rapport détaillé en fin de production (§8). Un seul acte réservé à autorisation BDFL explicite : si un choix structurant non prévu émerge (ex. un NON-POSIX-DÉLIBÉRÉ nouveau, ou un compromis sécurité), je le remonte avant de graver.