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

Spec couche 0 — Module io_uring : document maître d’inventaire

Spécification technique — Version 1.0 (cible kernel figée : Linux 6.12 LTS)

Rôle de ce document. C’est la carte de référence du module air-sys-syscall::io_uring. Il énumère exhaustivement la surface ABI d’io_uring telle qu’elle existe dans le kernel 6.12 (plancher figé), la classe (retenue / obsolète-évacuée), la rattache à un Temps de spécification et à un symbole de façade Rust, puis se termine par le squelette rustdoc du cœur de l’API.

Il rend l’objectif « exposer toutes les fonctionnalités d’io_uring » vérifiable : toute entrée du header uapi io_uring.h de la v6.12 doit apparaître ici avec une décision. C’est l’entrée à valider avant la production des specs détaillées par Temps et avant que l’implémentation (corps des fonctions) soit confiée.

Relation avec l’existant. Ce document remplace le rôle d’inventaire de io-uring-overview.md (calibré sur un kernel ~5.15, devenu incomplet). Les décisions structurantes restent celles de l’ADR-022 ; les deux décisions de soundness de la section 3 étendent ADR-022 et feront l’objet d’un amendement RFC (cf. §3.4).


1. Cible kernel figée et méthodologie

1.1 Décision de périmètre

  • Plancher = Linux 6.12 LTS. Tout ce qui est antérieur à 6.12 est évacué au démarrage. On n’implémentera des chemins pour kernels plus anciens que s’il y a une demande explicite (et alors par feature-gate + fallback documenté).
  • Source de vérité = le header include/uapi/linux/io_uring.h du tag v6.12. Tout symbole présent dans ce header est dans le périmètre ; tout symbole présent uniquement dans master (postérieur) est hors périmètre de démarrage et n’entrera que plus tard, par détection runtime, sans amendement (ADR-022, statut futur).
  • Une seule façade : la moderne. Quand le kernel 6.12 offre déjà une variante moderne d’une fonctionnalité, Air n’expose que la moderne. L’ancienne est consignée dans docs/UNSUPPORTED.md avec justification (cohérent avec « variantes modernes préférées » du CLAUDE.md). Cf. §4.

1.2 Hors périmètre de démarrage (présent dans master, absent de 6.12)

Recensé ici pour mémoire, pour ne pas le réintroduire par erreur :

  • Opcodes : RECV_ZC, EPOLL_WAIT, READV_FIXED, WRITEV_FIXED, PIPE, NOP128, URING_CMD128.
  • Register opcodes : SEND_MSG_RING, ZCRX_IFQ, ZCRX_CTRL, RESIZE_RINGS, MEM_REGION, QUERY, BPF_FILTER.
  • Setup flags : HYBRID_IOPOLL, CQE_MIXED, SQE_MIXED, SQ_REWIND.
  • CQE flags : IORING_CQE_F_SKIP, IORING_CQE_F_32.
  • Enter flags : EXT_ARG_REG, NO_IOWAIT.
  • Features : RW_ATTR, NO_IOWAIT.
  • uring_cmd socket op : SOCKET_URING_OP_TX_TIMESTAMP, SOCKET_URING_OP_GETSOCKNAME (6.12 n’a que SIOCINQ/SIOCOUTQ/GET/SETSOCKOPT).

2. Le modèle io_uring en bref (rappel d’ancrage)

io_uring repose sur trois syscalls et un protocole d’anneaux mmappés :

SyscallRôle
io_uring_setup(entries, params)Crée le ring, retourne un FD, remplit les offsets de mmap.
io_uring_enter(fd, to_submit, min_complete, flags, arg, argsz)Soumet des SQE et/ou attend des CQE.
io_uring_register(fd, opcode, arg, nr_args)Enregistre/configure des ressources (buffers, files, restrictions…).

Trois zones mmappées : l’anneau de soumission (SQ), l’anneau de complétion (CQ), et le tableau de SQE (64 octets, ou 128 avec SETUP_SQE128). Le CQE fait 16 octets (ou 32 avec SETUP_CQE32). Le couplage user_data (posé dans le SQE, rendu dans le CQE) est le fil qui relie une soumission à sa complétion — c’est le pivot de la façade Air (cf. §3.1).

La surface fonctionnelle se décompose en 8 axes énumérables ; les sections 5.1 à 5.8 les épuisent un par un.

2.1 Terminologie et sigles (référence)

io_uring emploie des sigles standardisés (kernel, man pages, liburing). Ce tableau les définit une fois pour toutes ; la documentation Air privilégie la forme verbeuse et n’utilise les sigles que là où ils correspondent à un nom ABI kernel littéral (qu’on ne renomme jamais, par exactitude).

SigleForme complète (EN)Forme complète (FR)Statut dans la façade Air
SQsubmission queuefile de soumissionsigle = anneau kernel ; façade verbeuse (submission_queue_*)
CQcompletion queuefile de complétionsigle = anneau kernel ; façade verbeuse (completion_queue_*)
SQEsubmission queue entryentrée de soumissiontype façade RawSubmissionQueueEntry ; struct kernel io_uring_sqe (gardé)
CQEcompletion queue entryentrée de complétiontype façade RawCompletionQueueEntry ; struct kernel io_uring_cqe (gardé)
FDfile descriptordescripteur de fichiertypes std OwnedFd/BorrowedFd/RawFd (gardés)
ZCzero-copycopie zérofaçade verbeuse : submit_send_zero_copy, ZeroCopyFlags, into_zero_copy_buffer
op / opcodeoperation codecode d’opérationfaçade verbeuse : IoUringOpcode, RawOpcode
SQPOLLsubmission-queue pollingscrutation de la file de soumissionnom de flag kernel IORING_SETUP_SQPOLL (gardé) ; type SqpollIoUring
NAPINew API (busy-poll réseau)nom de mécanisme kernel (gardé)
sq_entries / cq_entriessubmission/completion queue depthprofondeur des fileschamps ABI de io_uring_params (gardés littéralement)

Règle appliquée. Les identifiants de la façade Air sont écrits en toutes lettres (submission_queue_*, completion_queue_*, IoUringOpcode, ZeroCopyFlags, in_flight, etc.) — verbeux et sans ambiguïté pour le développeur (Principe 7). Les noms ABI du kernel (io_uring_sqe, io_uring_cqe, IORING_*, sqe->res, cq_entries…) sont conservés verbatim : les renommer introduirait une divergence avec le kernel et les man pages, donc moins de précision, pas plus.

⚠️ Précision factuelle. Le « S » de SQ/SQE est Submission (soumission), pas « Send ». SQ = submission queue, SQE = submission queue entry.


3. Décisions de soundness gelées (étendent ADR-022)

Ces décisions déterminent toutes les signatures de la façade. Elles sont gelées avant l’écriture du moindre corps de fonction.

3.1 Décision S1 — État des opérations en vol : slab pré-alloué

Problème. Le modèle « transfert d’ownership » (ADR-022, Décision 3) prend le buffer en argument et le rend à la complétion. Entre soumission et complétion, il faut garer quelque part {buffer, métadonnées, type d'op}, retrouvable par user_data. Une HashMap<u64, _> allouerait par opération — ce qui viole la règle CLAUDE.md « pas d’allocation heap dans le happy path ».

Décision. L’IoUring possède un slab pré-alloué dimensionné à la profondeur de la SQ (sq_entries). Chaque opération en vol occupe un slot. Le SubmissionToken encapsule un index de slot + génération (compteur de génération anti-réutilisation), pas un user_data brut. user_data côté kernel = l’index de slot encodé.

Conséquences :

  • Zéro allocation par opération dans le happy path : le buffer transféré est déplacé (move) dans le slot, pas copié ni réalloué.
  • Back-pressure naturelle : slab plein ⇒ submit_* retourne Err(Errno::EBUSY) (ou un type d’erreur dédié) avant d’atteindre le kernel.
  • Le slot est libéré quand la complétion correspondante est consommée et que l’ownership du buffer est rendu à l’appelant.

3.2 Décision S2 — Téardown sûr : Drop quiesce, shutdown() explicite

Problème. Si l’IoUring (ou un buffer en vol) est libéré alors que le kernel a encore des opérations en cours, le kernel peut écrire dans de la mémoire libérée. C’est le hasard de soundness io_uring + Rust. ADR-022 ne le traitait pas.

Décision (fidèle à « sur-sécuriser puis dégraisser après mesure », Principe 5).

  • shutdown(self) -> Result<(), Errno> : voie propre et explicite. Émet une annulation globale (IORING_REGISTER_SYNC_CANCEL ou ASYNC_CANCEL_ANY), draine les complétions restantes, puis ferme le FD. Ne bloque pas indéfiniment (timeout).
  • Drop : filet de sécurité. Si des opérations sont encore en vol, Drop quiesce (annule + draine de façon bloquante) avant de rendre la mémoire. Le coût (blocage potentiel en Drop) est assumé et documenté ; l’utilisateur soucieux de performance appelle shutdown() explicitement.
  • Les slots de la Décision S1 retiennent l’ownership des buffers tant que l’op n’est pas complétée ⇒ le buffer ne peut pas être libéré avant le kernel.

3.3 Décision S3 — Restrictions & sandbox comme primitive capability

IORING_REGISTER_RESTRICTIONS + SETUP_R_DISABLED + REGISTER_ENABLE_RINGS forment une primitive de confinement : créer un ring désactivé, restreindre les opcodes/flags/register-ops autorisés, puis l’activer. Cela mappe directement sur le modèle d’entitlements signés d’ADR-010 et les capabilities AirCom (ADR-001).

Décision. Cette primitive est traitée en première classe (Temps 3f, cf. §6), pas noyée dans la registration générique. La façade expose un constructeur IoUringBuilder capable d’appliquer un jeu de restrictions avant l’activation, de sorte qu’un service Air confiné reçoive un ring dont le kernel garantit qu’il ne peut émettre que les opérations autorisées par son manifeste.

3.4 Statut RFC

S1, S2, S3 étendent ADR-022 sans contredire ses 10 décisions. Elles sont consignées dans l’ADR-028 (« Soundness et téardown du module io_uring »), complément d’ADR-022 — cf. ../../adrs/ADR-028-soundness-io-uring-fr.md.


4. Règle « pas d’obsolète » : décisions concrètes

Variantes legacy évacuées vers UNSUPPORTED.md (le kernel 6.12 offre déjà la moderne) :

Legacy (évacué)Remplaçant moderne (exposé)Raison
IORING_REGISTER_BUFFERS (reg 0)IORING_REGISTER_BUFFERS2 (reg 15) + BUFFERS_UPDATE (16)Tagging des ressources (FEAT_RSRC_TAGS), sparse, update partiel.
IORING_REGISTER_FILES (reg 2)IORING_REGISTER_FILES2 (reg 13) + FILES_UPDATE2 (14)Idem ; struct io_uring_files_update est marqué deprecated dans le header.
IORING_REGISTER_FILES_UPDATE (reg 6)IORING_REGISTER_FILES_UPDATE2 (reg 14)Idem.
IORING_OP_PROVIDE_BUFFERS (op 31)IORING_REGISTER_PBUF_RING (reg 22)Provided buffers ring-mapped, sans syscall par lot, consommation incrémentale (IOU_PBUF_RING_INC).
IORING_OP_REMOVE_BUFFERS (op 32)IORING_UNREGISTER_PBUF_RING (reg 23)Idem.
Champ SQE poll_events (__u16)poll32_events (__u32)Le header note « compatibility » ; on n’expose que la version 32 bits.

Note : UNREGISTER_BUFFERS (1) et UNREGISTER_FILES (3) restent exposés — ce sont les désenregistrements des ressources enregistrées via les variantes *2. EVENTFD_ASYNC (7) et EPOLL_CTL (op 29) restent (non obsolètes : respectivement variante utile et seule intégration epoll en 6.12).


5. Inventaire exhaustif des 8 axes

Légende colonne « Décision » : R = retenu/exposé · O = obsolète, évacué vers UNSUPPORTED.md · G = gated runtime (probe/feature, exposé mais faillible).

5.1 Axe A — Setup flags IORING_SETUP_* (17 en 6.12)

Exposés comme bitflags SetupFlags, appliqués via IoUringBuilder.

FlagBitDécisionSymbole façade / note
IOPOLL0RSetupFlags::IOPOLL — polling I/O (stockage O_DIRECT).
SQPOLL1RSetupFlags::SQPOLL — thread kernel de poll de la SQ (Temps 3e).
SQ_AFF2RSetupFlags::SQ_AFF — affinité CPU du thread SQPOLL.
CQSIZE3RIoUringBuilder::cq_entries().
CLAMP4RSetupFlags::CLAMP.
ATTACH_WQ5RIoUringBuilder::attach_work_queue(&IoUring) — partage du pool io-wq.
R_DISABLED6Rprimitive sandbox (Temps 3f, §3.3).
SUBMIT_ALL7RSetupFlags::SUBMIT_ALL.
COOP_TASKRUN8RSetupFlags::COOP_TASKRUN.
TASKRUN_FLAG9RSetupFlags::TASKRUN_FLAG.
SQE12810Rrequis pour URING_CMD à payload large (Temps 2d, Temps 4).
CQE3211Rrequis pour certaines complétions étendues.
SINGLE_ISSUER12Rrecommandé Air : reactor thread-per-core.
DEFER_TASKRUN13Rrecommandé Air (combiné SINGLE_ISSUER) : latence basse.
NO_MMAP14Rmémoire des anneaux fournie par l’appelant.
REGISTERED_FD_ONLY15Rring fd uniquement enregistré (combine REG_REG_RING).
NO_SQARRAY16Rsupprime l’indirection du tableau d’index SQ.

5.2 Axe B — Opérations IORING_OP_* (58 en 6.12, indices 0–57)

Colonne « Temps » = spec détaillée de destination (cf. §6).

Opcode#DécisionTempsSymbole façade
NOP0R2csubmit_nop
READV1R2asubmit_readv
WRITEV2R2asubmit_writev
FSYNC3R2asubmit_fsync
READ_FIXED4R2a/3asubmit_read_fixed (buffer enregistré)
WRITE_FIXED5R2a/3asubmit_write_fixed
POLL_ADD6R2csubmit_poll_add (+ flags multi/level)
POLL_REMOVE7R2csubmit_poll_remove
SYNC_FILE_RANGE8R2asubmit_sync_file_range
SENDMSG9R2bsubmit_send_message
RECVMSG10R2bsubmit_receive_message
TIMEOUT11R2csubmit_timeout
TIMEOUT_REMOVE12R2csubmit_timeout_remove / update
ACCEPT13R2bsubmit_accept (+ multishot Temps 3d)
ASYNC_CANCEL14R2csubmit_cancel
LINK_TIMEOUT15R2c/3csubmit_link_timeout
CONNECT16R2bsubmit_connect
FALLOCATE17R2asubmit_fallocate
OPENAT18Oévacué → OPENAT2 (superset, OpenHow).
CLOSE19R2asubmit_close
FILES_UPDATE20R2c/3asubmit_files_update (op, distinct du register)
STATX21R2asubmit_statx
READ22R2asubmit_read
WRITE23R2asubmit_write
FADVISE24R2asubmit_fadvise
MADVISE25R2asubmit_madvise
SEND26R2bsubmit_send
RECV27R2bsubmit_receive (+ multishot Temps 3d)
OPENAT228R2asubmit_openat2
EPOLL_CTL29R2csubmit_epoll_ctl
SPLICE30R2asubmit_splice
PROVIDE_BUFFERS31Oévacué → PBUF_RING (reg 22).
REMOVE_BUFFERS32Oévacué → UNREGISTER_PBUF_RING.
TEE33R2asubmit_tee
SHUTDOWN34R2bsubmit_shutdown
RENAMEAT35R2asubmit_renameat (renameat2 sémantique)
UNLINKAT36R2asubmit_unlinkat
MKDIRAT37R2asubmit_mkdirat
SYMLINKAT38R2asubmit_symlinkat
LINKAT39R2asubmit_linkat
MSG_RING40R2csubmit_msg_ring (data / send_fd)
FSETXATTR41R2asubmit_fsetxattr
SETXATTR42R2asubmit_setxattr
FGETXATTR43R2asubmit_fgetxattr
GETXATTR44R2asubmit_getxattr
SOCKET45R2bsubmit_socket
URING_CMD46R2dsubmit_uring_cmd (+ socket cmds)
SEND_ZC47R2bsubmit_send_zero_copy (zero-copy, CQE NOTIF)
SENDMSG_ZC48R2bsubmit_send_message_zero_copy
READ_MULTISHOT49R3dsubmit_read_multishot
WAITID50R2csubmit_waitid
FUTEX_WAIT51R2csubmit_futex_wait
FUTEX_WAKE52R2csubmit_futex_wake
FUTEX_WAITV53R2csubmit_futex_waitv
FIXED_FD_INSTALL54R2c/3asubmit_fixed_fd_install
FTRUNCATE55R2asubmit_ftruncate
BIND56R2bsubmit_bind
LISTEN57R2bsubmit_listen

Bilan opcodes : 55 retenus, 3 évacués (OPENAT, PROVIDE_BUFFERS, REMOVE_BUFFERS).

5.3 Axe C — Register opcodes IORING_REGISTER_* (31 en 6.12, 0–30)

Register op#DécisionTempsSymbole façade
REGISTER_BUFFERS0OBUFFERS2.
UNREGISTER_BUFFERS1R3aRegisteredBuffers::unregister
REGISTER_FILES2OFILES2.
UNREGISTER_FILES3R3aFixedFdTable::unregister
REGISTER_EVENTFD4R3aIoUring::register_eventfd
UNREGISTER_EVENTFD5R3aIoUring::unregister_eventfd
REGISTER_FILES_UPDATE6OFILES_UPDATE2.
REGISTER_EVENTFD_ASYNC7R3aregister_eventfd_async
REGISTER_PROBE8R1IoUring::probe / supports_op
REGISTER_PERSONALITY9R3aIoUring::register_personality
UNREGISTER_PERSONALITY10R3aunregister_personality
REGISTER_RESTRICTIONS11R3fIoUringBuilder::restrict (§3.3)
REGISTER_ENABLE_RINGS12R3fIoUringBuilder::enable
REGISTER_FILES213R3aFixedFdTable::register
REGISTER_FILES_UPDATE214R3aFixedFdTable::update
REGISTER_BUFFERS215R3aRegisteredBuffers::register
REGISTER_BUFFERS_UPDATE16R3aRegisteredBuffers::update
REGISTER_IOWQ_AFF17R3aIoUring::set_work_queue_affinity
UNREGISTER_IOWQ_AFF18R3aclear_work_queue_affinity
REGISTER_IOWQ_MAX_WORKERS19R3aset_work_queue_max_workers
REGISTER_RING_FDS20R3aIoUring::register_ring_fd
UNREGISTER_RING_FDS21R3aunregister_ring_fd
REGISTER_PBUF_RING22R3bProvidedBufferRing::register
UNREGISTER_PBUF_RING23R3bProvidedBufferRing::unregister
REGISTER_SYNC_CANCEL24R1/2cIoUring::sync_cancel (utilisé par S2)
REGISTER_FILE_ALLOC_RANGE25R3aFixedFdTable::set_alloc_range
REGISTER_PBUF_STATUS26R3bProvidedBufferRing::status
REGISTER_NAPI27R3aIoUring::register_napi
UNREGISTER_NAPI28R3aunregister_napi
REGISTER_CLOCK29R3aIoUring::register_clock
REGISTER_CLONE_BUFFERS30R3aRegisteredBuffers::clone_from
USE_REGISTERED_RING (flag 1<<31)R1appliqué de façon transparente quand le ring fd est enregistré.

Bilan register : 28 retenus, 3 évacués (REGISTER_BUFFERS, REGISTER_FILES, REGISTER_FILES_UPDATE).

5.4 Axe D — Flags par SQE IOSQE_* (7)

FlagDécisionSymbole façade
FIXED_FILERimplicite via les variantes « fixed » (FD enregistré).
IO_DRAINRSubmitOptions::drain().
IO_LINKRLinkedChainBuilder (Temps 3c).
IO_HARDLINKRLinkedChainBuilder::add_hard_link.
ASYNCRSubmitOptions::force_async().
BUFFER_SELECTRimplicite via ProvidedBufferRing (Temps 3b).
CQE_SKIP_SUCCESSRSubmitOptions::skip_cqe_on_success() (FEAT_CQE_SKIP).

5.5 Axe E — Flags de CQE IORING_CQE_F_* (5 en 6.12)

FlagDécisionExposition
BUFFERRCompletion::buffer_id() -> Option<u16>.
MORERCompletion::has_more() (multishot, Temps 3d).
SOCK_NONEMPTYRCompletion::socket_has_pending_data().
NOTIFRCompletion::is_notif() (zero-copy send, Temps 2b).
BUF_MORERconsommation incrémentale (IOU_PBUF_RING_INC, Temps 3b).

5.6 Axe F — Flags d’io_uring_enter (6 en 6.12)

Gérés en interne par la façade (pas exposés crûment) :

FlagDécisionUsage interne
GETEVENTSRsubmit_and_wait, wait_completion.
SQ_WAKEUPRréveil du thread SQPOLL (Temps 3e).
SQ_WAITRattente que la SQ se vide (SQPOLL).
EXT_ARGRtimeout d’attente (getevents_arg), wait_completion_timeout.
REGISTERED_RINGRappliqué si ring fd enregistré (transparent).
ABS_TIMERRtimeout absolu (FEAT_MIN_TIMEOUT).

5.7 Axe G — Features IORING_FEAT_* (16 en 6.12)

Lues dans io_uring_params.features après setup, exposées via IoUringCapabilities :

SINGLE_MMAP, NODROP, SUBMIT_STABLE, RW_CUR_POS, CUR_PERSONALITY, FAST_POLL, POLL_32BITS, SQPOLL_NONFIXED, EXT_ARG, NATIVE_WORKERS, RSRC_TAGS, CQE_SKIP, LINKED_FILE, REG_REG_RING, RECVSEND_BUNDLE, MIN_TIMEOUT. → tous R, mappés sur des prédicats IoUringCapabilities::*.

5.8 Axe H — Protocole d’anneau brut (Temps 4)

Structures kernel exposées en #[repr(C)] dans ::raw, + flags d’anneau :

  • io_uring_sqe (64 o / 128 o), io_uring_cqe (16 o / 32 o).
  • io_sqring_offsets, io_cqring_offsets, io_uring_params.
  • Flags d’anneau SQ : SQ_NEED_WAKEUP, SQ_CQ_OVERFLOW, SQ_TASKRUN.
  • Flag d’anneau CQ : CQ_EVENTFD_DISABLED.
  • Offsets mmap : OFF_SQ_RING, OFF_CQ_RING, OFF_SQES, OFF_PBUF_RING.
  • Structures d’argument register : io_uring_rsrc_register, io_uring_rsrc_update(2), io_uring_buf(_ring|_reg|_status), io_uring_napi, io_uring_clock_register, io_uring_clone_buffers, io_uring_sync_cancel_reg, io_uring_file_index_range, io_uring_probe(_op), io_uring_restriction, io_uring_getevents_arg, io_uring_recvmsg_out.
  • Enums : io_uring_op, io_uring_register_op, io_uring_register_restriction_op, io_uring_msg_ring_flags, io_uring_socket_op, io_uring_register_pbuf_ring_flags, io_wq_type.

6. Découpage en Temps révisé

Le découpage d’ADR-022 est conservé et étendu (ajouts : 2d uring_cmd, séparation 3b provided-buffers-ring, 3f restrictions/sandbox) :

TempsSous-modulePérimètreSymboles
1(racine)Cycle de vie du ring, setup flags, slab (S1), Drop/shutdown (S2), soumission/complétion, Completion, probe/capabilities.~20
2a(racine)Filesystem (read/write/sync/open/stat/dir/perm/xattr/splice/ftruncate…).26
2b(racine)Réseau (accept/connect/send/recv/msg/shutdown/socket/bind/listen + zero-copy send/sendmsg).12
2c(racine)Async-spécifiques (nop/timeout/cancel/poll/futex/waitid/msg_ring/epoll_ctl/files_update/fixed_fd_install).15
2d::cmdURING_CMD : passthrough + commandes socket (SIOCINQ/OUTQ, get/setsockopt).3-5
3a::registrationRessources fixes modernes : files2/buffers2/update, ring_fds, eventfd, personality, iowq aff/workers, napi, clock, clone_buffers, alloc_range.~25
3b::providedProvided buffers ring-mapped (PBUF_RING/STATUS, incrémental).6-8
3c::linkedChaînes liées (soft/hard link, link_timeout).5-7
3d::multishotMultishot (accept/recv/poll/read_multishot).5-7
3e::sharedMulti-thread : LockedIoUring, RingPool, SqpollIoUring.10-12
3f::sandboxRestrictions + R_DISABLED + ENABLE_RINGS (capability, §3.3).6-8
4::rawProtocole brut (SQE/CQE, anneaux, structs register).8-12

7. Squelette rustdoc du cœur (Temps 1) — à valider avant implémentation

Ce squelette est le contrat que Claude Code devra implémenter (corps = todo!()). Les submit_* exhaustifs par opcode (Temps 2a–2d) et les sous-modules (3a–3f, 4) sont produits dans leurs specs dédiées sur le même modèle. Conventions couche 0 (ADR-021) appliquées : Result<_, Errno>, EINTR remonté, Option<T> au lieu de sentinelles, FD en OwnedFd/ BorrowedFd, pas d’alloc heap dans le happy path (S1).

#![allow(unused)]
fn main() {
//! Module `air-sys-syscall::io_uring` — façade typée d'io_uring (cible 6.12).
//!
//! Niveau d'abstraction 2 (ADR-022, Décision 1) : soumission/complétion typée.
//! Le niveau 1 (anneaux bruts) vit dans [`raw`]. Les buffers suivent le modèle
//! de transfert d'ownership (ADR-022, Décision 3), garés dans un slab
//! pré-alloué (S1) ; le téardown est sûr par `Drop` quiescent + [`IoUring::shutdown`]
//! (S2).

use crate::Errno;
use core::num::NonZeroU32;
use std::os::fd::{BorrowedFd, OwnedFd};

// ---------------------------------------------------------------------------
// Construction & cycle de vie
// ---------------------------------------------------------------------------

/// Anneau io_uring. `Send` mais pas `Sync` (ADR-022, Décision 6) : un reactor
/// par thread. Pour le multi-thread, voir [`shared`].
pub struct IoUring {
    /* fd: OwnedFd, mmaps SQ/CQ/SQE, slab d'ops en vol (S1), capabilities */
}

/// Construit un [`IoUring`], applique setup flags et restrictions (S3) avant
/// activation.
pub struct IoUringBuilder { /* ... */ }

impl IoUringBuilder {
    /// Démarre un builder pour `entries` SQE (arrondi à la puissance de 2 sup.).
    pub fn new(entries: NonZeroU32) -> Self { todo!() }

    /// Taille explicite de la CQ (`SETUP_CQSIZE`).
    pub fn with_completion_queue_entries(self, entries: NonZeroU32) -> Self { todo!() }

    /// Active des flags de setup (cf. [`SetupFlags`]).
    pub fn with_flags(self, flags: SetupFlags) -> Self { todo!() }

    /// Partage le pool io-wq d'un ring existant (`SETUP_ATTACH_WQ`).
    pub fn attach_work_queue(self, other: &IoUring) -> Self { todo!() }

    /// Crée le ring désactivé (`R_DISABLED`) et applique des restrictions
    /// (S3). Doit être suivi de [`IoUringBuilder::enable`].
    pub fn restrict(self, restrictions: &[Restriction]) -> Self { todo!() }

    /// Finalise : `io_uring_setup(2)`. Si `restrict` a été utilisé, le ring
    /// est créé désactivé et doit être activé via [`IoUring::enable`].
    pub fn build(self) -> Result<IoUring, Errno> { todo!() }
}

impl IoUring {
    /// Raccourci : `IoUringBuilder::new(entries).build()`.
    pub fn new(entries: NonZeroU32) -> Result<Self, Errno> { todo!() }

    /// Active un ring créé avec `R_DISABLED` (`REGISTER_ENABLE_RINGS`).
    pub fn enable(&mut self) -> Result<(), Errno> { todo!() }

    /// Téardown propre (S2) : annule les ops en vol, draine, ferme le FD.
    /// Préférer à un `drop` implicite sur chemin chaud.
    pub fn shutdown(self) -> Result<(), Errno> { todo!() }
}

impl Drop for IoUring {
    /// Filet de sécurité (S2) : si des ops sont en vol, quiesce (annule +
    /// draine, **bloquant**) avant de libérer la mémoire. Coût assumé.
    fn drop(&mut self) { todo!() }
}

// ---------------------------------------------------------------------------
// Soumission & complétion
// ---------------------------------------------------------------------------

impl IoUring {
    /// Soumet les SQE en attente (`io_uring_enter`, sans attente). Retourne le
    /// nombre soumis. EINTR remonté (ADR-021, conv. 2).
    pub fn submit(&mut self) -> Result<u32, Errno> { todo!() }

    /// Soumet puis attend au moins `want` complétions.
    pub fn submit_and_wait(&mut self, want: u32) -> Result<u32, Errno> { todo!() }

    /// Attend (bloquant) la prochaine complétion et la consomme.
    pub fn wait_completion(&mut self) -> Result<Completion, Errno> { todo!() }

    /// Attend une complétion avec délai maximal (`EXT_ARG`/`ABS_TIMER`).
    pub fn wait_completion_timeout(
        &mut self,
        timeout: core::time::Duration,
    ) -> Result<Option<Completion>, Errno> { todo!() }

    /// Récupère une complétion si disponible, sans bloquer.
    pub fn try_completion(&mut self) -> Option<Completion> { todo!() }

    /// Itère les complétions disponibles dans la CQ.
    pub fn completions(&mut self) -> CompletionIter<'_> { todo!() }

    /// Options par-opération (link, drain, async, skip-cqe) appliquées à la
    /// prochaine soumission.
    pub fn with(&mut self, opts: SubmitOptions) -> &mut Self { todo!() }
}

/// Jeton opaque reliant une soumission à sa complétion. Encapsule un index de
/// slot du slab + génération (compteur de génération anti-réutilisation) — *pas* un `user_data` brut (S1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubmissionToken { /* slot: u32, gen: u32 */ }

/// Jeton d'une opération multishot (plusieurs complétions). Voir [`multishot`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MultishotToken { /* ... */ }

/// Options de soumission (flags `IOSQE_*` exposés sûrement).
#[derive(Debug, Clone, Copy, Default)]
pub struct SubmitOptions { /* ... */ }

impl SubmitOptions {
    pub fn drain(self) -> Self { todo!() }
    pub fn force_async(self) -> Self { todo!() }
    pub fn skip_cqe_on_success(self) -> Self { todo!() }
}

// ---------------------------------------------------------------------------
// Complétion
// ---------------------------------------------------------------------------

/// Une complétion (CQE) typée. Méthodes d'interprétation selon l'op soumise
/// (ADR-022, Décision 9) ; en cas d'échec, retournent `Err(Errno)`.
pub struct Completion { /* user_data décodé, res, flags */ }

impl Completion {
    /// Jeton de l'opération qui a produit cette complétion.
    pub fn token(&self) -> SubmissionToken { todo!() }
    /// Résultat brut du kernel (sémantique dépendante de l'op).
    pub fn raw_result(&self) -> i32 { todo!() }
    /// Flags de complétion (cf. [`CompletionFlags`]).
    pub fn flags(&self) -> CompletionFlags { todo!() }

    /// `IORING_CQE_F_MORE` : d'autres complétions suivront (multishot).
    pub fn has_more(&self) -> bool { todo!() }
    /// `IORING_CQE_F_NOTIF` : complétion de notification zero-copy.
    pub fn is_notif(&self) -> bool { todo!() }
    /// ID du buffer fourni consommé (`IORING_CQE_F_BUFFER`).
    pub fn buffer_id(&self) -> Option<u16> { todo!() }
    /// `IORING_CQE_F_SOCK_NONEMPTY` : données restantes après un recv.
    pub fn socket_has_pending_data(&self) -> bool { todo!() }

    // Interprétations typées (échantillon ; complétées par Temps 2a–2d) :
    /// Octets lus + restitution du buffer transféré (S1).
    pub fn into_read_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
    /// Octets écrits + restitution du buffer.
    pub fn into_write_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
    /// FD accepté (`submit_accept`), CLOEXEC par défaut.
    pub fn accepted_fd(self) -> Result<OwnedFd, Errno> { todo!() }
    /// FD ouvert (`submit_openat2`).
    pub fn opened_fd(self) -> Result<OwnedFd, Errno> { todo!() }
    /// Succès sans valeur de retour (close, fsync…).
    pub fn completed(&self) -> Result<(), Errno> { todo!() }
}

/// Itérateur sur les complétions disponibles.
pub struct CompletionIter<'ring> { /* ... */ }

// ---------------------------------------------------------------------------
// Capacités & introspection
// ---------------------------------------------------------------------------

impl IoUring {
    /// Vrai si le kernel courant supporte `op` (`REGISTER_PROBE`, Décision 8).
    pub fn supports_op(&self, op: IoUringOpcode) -> bool { todo!() }
    /// Features négociées au setup (axe G).
    pub fn capabilities(&self) -> IoUringCapabilities { todo!() }
    /// Annulation synchrone globale ou ciblée (`REGISTER_SYNC_CANCEL`),
    /// brique de [`IoUring::shutdown`] (S2).
    pub fn sync_cancel(&mut self, target: CancelTarget) -> Result<u32, Errno> { todo!() }
}

/// Features io_uring du kernel courant (axe G). Prédicats stables.
#[derive(Debug, Clone, Copy)]
pub struct IoUringCapabilities { /* bits features */ }

impl IoUringCapabilities {
    pub fn single_mmap(&self) -> bool { todo!() }
    pub fn nodrop(&self) -> bool { todo!() }
    pub fn cqe_skip(&self) -> bool { todo!() }
    pub fn min_timeout(&self) -> bool { todo!() }
    pub fn recvsend_bundle(&self) -> bool { todo!() }
    pub fn reg_reg_ring(&self) -> bool { todo!() }
    /* … un prédicat par feature de l'axe G … */
}

/// Énumération des opérations (axe B) pour le probe et les restrictions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IoUringOpcode { Nop, Readv, Writev, /* … 55 variantes retenues … */ }

/// Cible d'annulation pour [`IoUring::sync_cancel`].
#[derive(Debug, Clone, Copy)]
pub enum CancelTarget {
    Token(SubmissionToken),
    Fd(BorrowedFd<'static>),
    Op(IoUringOpcode),
    Any,
}

// ---------------------------------------------------------------------------
// Bitflags & sandbox (Temps 3f, détaillé dans ::sandbox)
// ---------------------------------------------------------------------------

bitflags::bitflags! {
    /// Flags de `io_uring_setup` (axe A, 17 flags en 6.12).
    pub struct SetupFlags: u32 {
        const IOPOLL = 1 << 0;
        const SQPOLL = 1 << 1;
        const SQ_AFF = 1 << 2;
        const CLAMP = 1 << 4;
        const R_DISABLED = 1 << 6;
        const SUBMIT_ALL = 1 << 7;
        const COOP_TASKRUN = 1 << 8;
        const TASKRUN_FLAG = 1 << 9;
        const SQE128 = 1 << 10;
        const CQE32 = 1 << 11;
        const SINGLE_ISSUER = 1 << 12;
        const DEFER_TASKRUN = 1 << 13;
        const NO_MMAP = 1 << 14;
        const REGISTERED_FD_ONLY = 1 << 15;
        const NO_SQARRAY = 1 << 16;
        /* CQSIZE / ATTACH_WQ exposés via méthodes du builder */
    }

    /// Flags de complétion (axe E, 5 flags en 6.12).
    pub struct CompletionFlags: u32 {
        const BUFFER = 1 << 0;
        const MORE = 1 << 1;
        const SOCK_NONEMPTY = 1 << 2;
        const NOTIF = 1 << 3;
        const BUF_MORE = 1 << 4;
    }
}

/// Une restriction appliquée avant activation (S3 ; `io_uring_restriction`).
#[derive(Debug, Clone, Copy)]
pub enum Restriction {
    /// N'autoriser que cet opcode de soumission.
    AllowOp(IoUringOpcode),
    /// N'autoriser que ce register op.
    AllowRegister(u8),
    /// Flags SQE autorisés.
    SqeFlagsAllowed(SubmitOptions),
    /// Flags SQE requis sur chaque soumission.
    SqeFlagsRequired(SubmitOptions),
}
}

8. Traits transverses pressentis (à arbitrer en Temps 1)

À valider : faut-il un trait commun aux opérations soumissibles pour factoriser linked/multishot, ou des méthodes inhérentes suffisent-elles (verbosité au service de la clarté, Principe 7) ?

  • trait Submittable — une opération qui peut être soumise (utilisée par LinkedChainBuilder). Candidat ; risque d’abstraction prématurée.
  • trait FixedResource — ressource enregistrable (FD / buffer). Candidat.

Recommandation : ne pas introduire ces traits au Temps 1. Les ajouter seulement si les Temps 3a/3c en révèlent le besoin par répétition mesurée.


9. Travail à reprendre (specs détaillées par Temps)

Produire, au niveau de détail des autres familles (signature complète, syscall sous-jacent, préconditions/# Safety, comportement, erreurs, perf, tests, exemples), les fichiers : io-uring-1-core.md, io-uring-2a-filesystem.md, io-uring-2b-network.md, io-uring-2c-async.md, io-uring-2d-cmd.md, io-uring-3a-registration.md, io-uring-3b-provided.md, io-uring-3c-linked.md, io-uring-3d-multishot.md, io-uring-3e-shared.md, io-uring-3f-sandbox.md, io-uring-4-raw.md.

Puis : version anglaise de ce document maître.


Licence du document : MPL 2.0 Statut : Document maître d’inventaire du module air-sys-syscall::io_uring, cible kernel 6.12 LTS. À valider avant production des specs détaillées par Temps.