Layer-0 Spec — io_uring Module: Master Inventory Document
Technical specification — Version 1.0 (frozen kernel target: Linux 6.12 LTS)
Role of this document. This is the reference map of the
air-sys-syscall::io_uringmodule. It exhaustively enumerates the ABI surface of io_uring as it exists in kernel 6.12 (the frozen baseline), classifies each entry (retained / obsolete-dropped), links it to a specification Stage and a Rust façade symbol, and concludes with the rustdoc skeleton of the core API.It makes the goal of “exposing all io_uring features” verifiable: every entry in the uapi header
io_uring.hof v6.12 must appear here with a decision. This document is the entry point to validate before producing the detailed per-Stage specs and before the implementation (function bodies) is handed off.Relation to existing documents. This document replaces the inventory role of
io-uring-overview.md(calibrated against a ~5.15 kernel, now incomplete). Structural decisions remain those of ADR-022; the two soundness decisions in section 3 extend ADR-022 and will be the subject of an RFC amendment (see §3.4).
1. Frozen Kernel Target and Methodology
1.1 Scope Decision
- Baseline = Linux 6.12 LTS. Everything predating 6.12 is dropped at the outset. Paths for older kernels will only be implemented if there is an explicit request (and then via feature-gate + documented fallback).
- Source of truth = the
include/uapi/linux/io_uring.hheader from thev6.12tag. Every symbol present in that header is in scope; any symbol present only inmaster(later) is out of scope at startup and will only be added later, via runtime detection, without amendment (ADR-022, future status). - A single façade: the modern one. When kernel 6.12 already offers a
modern variant of a feature, Air exposes only the modern one. The
legacy variant is recorded in
docs/UNSUPPORTED.mdwith justification (consistent with “modern variants preferred” from CLAUDE.md). See §4.
1.2 Out of Startup Scope (present in master, absent from 6.12)
Listed here for reference, to avoid accidentally reintroducing them:
- 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 only has SIOCINQ/SIOCOUTQ/GET/SETSOCKOPT).
2. The io_uring Model in Brief (Orientation Recap)
io_uring is built on three syscalls and a protocol of mmapped rings:
| Syscall | Role |
|---|---|
io_uring_setup(entries, params) | Creates the ring, returns an FD, fills in the mmap offsets. |
io_uring_enter(fd, to_submit, min_complete, flags, arg, argsz) | Submits SQEs and/or waits for CQEs. |
io_uring_register(fd, opcode, arg, nr_args) | Registers/configures resources (buffers, files, restrictions, …). |
Three mmapped regions: the submission queue (SQ), the completion queue
(CQ), and the SQE array (64 bytes, or 128 with SETUP_SQE128). The
CQE is 16 bytes (or 32 with SETUP_CQE32). The user_data coupling
(written into the SQE, returned in the CQE) is the thread linking a submission
to its completion — it is the pivot of the Air façade (see §3.1).
The functional surface breaks down into 8 enumerable axes; sections 5.1 through 5.8 cover them exhaustively.
2.1 Terminology and Abbreviations (reference)
io_uring uses standardized abbreviations (kernel, man pages, liburing). This table defines them once and for all; the Air documentation prefers the spelled-out form and only uses the abbreviations where they correspond to a literal kernel ABI name (which is never renamed, for accuracy).
| Abbrev. | Full form | Status in the Air façade |
|---|---|---|
| SQ | submission queue | abbreviation = kernel ring; façade is spelled out (submission_queue_*) |
| CQ | completion queue | abbreviation = kernel ring; façade is spelled out (completion_queue_*) |
| SQE | submission queue entry | façade type RawSubmissionQueueEntry; kernel struct io_uring_sqe (kept) |
| CQE | completion queue entry | façade type RawCompletionQueueEntry; kernel struct io_uring_cqe (kept) |
| FD | file descriptor | std types OwnedFd/BorrowedFd/RawFd (kept) |
| ZC | zero-copy | façade spelled out: submit_send_zero_copy, ZeroCopyFlags, into_zero_copy_buffer |
| op / opcode | operation code | façade spelled out: IoUringOpcode, RawOpcode |
| SQPOLL | submission-queue polling | kernel flag name IORING_SETUP_SQPOLL (kept); type SqpollIoUring |
| NAPI | New API (network busy-poll) | kernel mechanism name (kept) |
sq_entries / cq_entries | submission/completion queue depth | ABI fields of io_uring_params (kept verbatim) |
Applied rule. Air façade identifiers are spelled out
(submission_queue_*, completion_queue_*, IoUringOpcode, ZeroCopyFlags,
in_flight, etc.) — verbose and unambiguous for the developer (Principle 7).
Kernel ABI names (io_uring_sqe, io_uring_cqe, IORING_*, sqe->res,
cq_entries…) are kept verbatim: renaming them would diverge from the
kernel and the man pages, hence less precision, not more.
⚠️ Factual note. The “S” in SQ/SQE stands for Submission, not “Send”. SQ = submission queue, SQE = submission queue entry.
3. Frozen Soundness Decisions (Extending ADR-022)
These decisions determine all façade signatures. They are frozen before writing any function body.
3.1 Decision S1 — In-flight Operation State: Pre-allocated Slab
Problem. The “ownership transfer” model (ADR-022, Decision 3) takes the
buffer as an argument and returns it at completion. Between submission and
completion, {buffer, metadata, op type} must be parked somewhere,
retrievable by user_data. A HashMap<u64, _> would allocate per
operation — which violates the CLAUDE.md rule “no heap allocation in the
happy path.”
Decision. The IoUring owns a pre-allocated slab sized to the SQ
depth (sq_entries). Each in-flight operation occupies a slot. The
SubmissionToken encapsulates a slot index + generation (compteur de génération anti-réutilisation),
not a raw user_data. The kernel-side user_data = the encoded slot index.
Consequences:
- Zero allocation per operation in the happy path: the transferred buffer is moved into the slot, not copied or reallocated.
- Natural back-pressure: slab full ⇒
submit_*returnsErr(Errno::EBUSY)(or a dedicated error type) before reaching the kernel. - The slot is freed when the corresponding completion is consumed and ownership of the buffer is returned to the caller.
3.2 Decision S2 — Safe Teardown: Drop Quiesces, Explicit shutdown()
Problem. If the IoUring (or an in-flight buffer) is dropped while the
kernel still has operations in progress, the kernel may write into freed
memory. This is the soundness hazard of io_uring + Rust. ADR-022 did not
address it.
Decision (faithful to “over-secure then trim after measurement,” Principle 5).
shutdown(self) -> Result<(), Errno>: the clean, explicit path. Issues a global cancellation (IORING_REGISTER_SYNC_CANCELorASYNC_CANCEL_ANY), drains remaining completions, then closes the FD. Does not block indefinitely (timeout).Drop: safety net. If operations are still in flight,Dropquiesces (cancels + drains in a blocking fashion) before releasing memory. The cost (potential blocking inDrop) is acknowledged and documented; performance-conscious users callshutdown()explicitly.- The slots from Decision S1 retain ownership of buffers until the op completes ⇒ the buffer cannot be freed before the kernel is done with it.
3.3 Decision S3 — Restrictions & Sandbox as a Capability Primitive
IORING_REGISTER_RESTRICTIONS + SETUP_R_DISABLED + REGISTER_ENABLE_RINGS
form a confinement primitive: create a disabled ring, restrict the
allowed opcodes/flags/register-ops, then enable it. This maps directly
onto the signed-entitlement model of ADR-010 and the AirCom capabilities
(ADR-001).
Decision. This primitive is treated as a first-class citizen (Stage 3f,
see §6), not buried in generic registration. The façade exposes an
IoUringBuilder constructor capable of applying a set of restrictions
before activation, so that a confined Air service receives a ring whose
kernel guarantees it can only issue the operations authorized by its manifest.
3.4 RFC Status
S1, S2, S3 extend ADR-022 without contradicting its 10 decisions. They are
recorded in ADR-028 (“Soundness and teardown of the io_uring module”),
a supplement to ADR-022 — see ../../adrs/ADR-028-soundness-io-uring-en.md.
4. “No Legacy” Rule: Concrete Decisions
Legacy variants dropped to UNSUPPORTED.md (kernel 6.12 already offers
the modern version):
| Legacy (dropped) | Modern replacement (exposed) | Reason |
|---|---|---|
IORING_REGISTER_BUFFERS (reg 0) | IORING_REGISTER_BUFFERS2 (reg 15) + BUFFERS_UPDATE (16) | Resource tagging (FEAT_RSRC_TAGS), sparse, partial update. |
IORING_REGISTER_FILES (reg 2) | IORING_REGISTER_FILES2 (reg 13) + FILES_UPDATE2 (14) | Same; struct io_uring_files_update is marked deprecated in the header. |
IORING_REGISTER_FILES_UPDATE (reg 6) | IORING_REGISTER_FILES_UPDATE2 (reg 14) | Same. |
IORING_OP_PROVIDE_BUFFERS (op 31) | IORING_REGISTER_PBUF_RING (reg 22) | Ring-mapped provided buffers, no syscall per batch, incremental consumption (IOU_PBUF_RING_INC). |
IORING_OP_REMOVE_BUFFERS (op 32) | IORING_UNREGISTER_PBUF_RING (reg 23) | Same. |
SQE field poll_events (__u16) | poll32_events (__u32) | The header notes “compatibility”; only the 32-bit version is exposed. |
Note: UNREGISTER_BUFFERS (1) and UNREGISTER_FILES (3) remain exposed —
they are the deregistration counterparts for resources registered via the *2
variants. EVENTFD_ASYNC (7) and EPOLL_CTL (op 29) remain (not
obsolete: respectively a useful variant and the only epoll integration in 6.12).
5. Exhaustive Inventory of the 8 Axes
Legend for the “Decision” column: R = retained/exposed · O = obsolete,
dropped to UNSUPPORTED.md · G = runtime-gated (probe/feature, exposed
but fallible).
5.1 Axis A — Setup Flags IORING_SETUP_* (17 in 6.12)
Exposed as bitflags SetupFlags, applied via IoUringBuilder.
| Flag | Bit | Decision | Façade symbol / note |
|---|---|---|---|
IOPOLL | 0 | R | SetupFlags::IOPOLL — I/O polling (O_DIRECT storage). |
SQPOLL | 1 | R | SetupFlags::SQPOLL — kernel thread polling the SQ (Stage 3e). |
SQ_AFF | 2 | R | SetupFlags::SQ_AFF — CPU affinity for the SQPOLL thread. |
CQSIZE | 3 | R | IoUringBuilder::cq_entries(). |
CLAMP | 4 | R | SetupFlags::CLAMP. |
ATTACH_WQ | 5 | R | IoUringBuilder::attach_work_queue(&IoUring) — shares the io-wq pool. |
R_DISABLED | 6 | R | sandbox primitive (Stage 3f, §3.3). |
SUBMIT_ALL | 7 | R | SetupFlags::SUBMIT_ALL. |
COOP_TASKRUN | 8 | R | SetupFlags::COOP_TASKRUN. |
TASKRUN_FLAG | 9 | R | SetupFlags::TASKRUN_FLAG. |
SQE128 | 10 | R | required for URING_CMD with large payload (Stage 2d, Stage 4). |
CQE32 | 11 | R | required for certain extended completions. |
SINGLE_ISSUER | 12 | R | Air recommended: thread-per-core reactor. |
DEFER_TASKRUN | 13 | R | Air recommended (combined with SINGLE_ISSUER): low latency. |
NO_MMAP | 14 | R | ring memory provided by the caller. |
REGISTERED_FD_ONLY | 15 | R | ring fd as registered-only (combines REG_REG_RING). |
NO_SQARRAY | 16 | R | removes the SQ index-array indirection. |
5.2 Axis B — Operations IORING_OP_* (58 in 6.12, indices 0–57)
“Stage” column = destination detailed spec (see §6).
| Opcode | # | Decision | Stage | Façade symbol |
|---|---|---|---|---|
NOP | 0 | R | 2c | submit_nop |
READV | 1 | R | 2a | submit_readv |
WRITEV | 2 | R | 2a | submit_writev |
FSYNC | 3 | R | 2a | submit_fsync |
READ_FIXED | 4 | R | 2a/3a | submit_read_fixed (registered buffer) |
WRITE_FIXED | 5 | R | 2a/3a | submit_write_fixed |
POLL_ADD | 6 | R | 2c | submit_poll_add (+ multi/level flags) |
POLL_REMOVE | 7 | R | 2c | submit_poll_remove |
SYNC_FILE_RANGE | 8 | R | 2a | submit_sync_file_range |
SENDMSG | 9 | R | 2b | submit_send_message |
RECVMSG | 10 | R | 2b | submit_receive_message |
TIMEOUT | 11 | R | 2c | submit_timeout |
TIMEOUT_REMOVE | 12 | R | 2c | submit_timeout_remove / update |
ACCEPT | 13 | R | 2b | submit_accept (+ multishot Stage 3d) |
ASYNC_CANCEL | 14 | R | 2c | submit_cancel |
LINK_TIMEOUT | 15 | R | 2c/3c | submit_link_timeout |
CONNECT | 16 | R | 2b | submit_connect |
FALLOCATE | 17 | R | 2a | submit_fallocate |
OPENAT | 18 | O | — | dropped → OPENAT2 (superset, OpenHow). |
CLOSE | 19 | R | 2a | submit_close |
FILES_UPDATE | 20 | R | 2c/3a | submit_files_update (op, distinct from register) |
STATX | 21 | R | 2a | submit_statx |
READ | 22 | R | 2a | submit_read |
WRITE | 23 | R | 2a | submit_write |
FADVISE | 24 | R | 2a | submit_fadvise |
MADVISE | 25 | R | 2a | submit_madvise |
SEND | 26 | R | 2b | submit_send |
RECV | 27 | R | 2b | submit_receive (+ multishot Stage 3d) |
OPENAT2 | 28 | R | 2a | submit_openat2 |
EPOLL_CTL | 29 | R | 2c | submit_epoll_ctl |
SPLICE | 30 | R | 2a | submit_splice |
PROVIDE_BUFFERS | 31 | O | — | dropped → PBUF_RING (reg 22). |
REMOVE_BUFFERS | 32 | O | — | dropped → UNREGISTER_PBUF_RING. |
TEE | 33 | R | 2a | submit_tee |
SHUTDOWN | 34 | R | 2b | submit_shutdown |
RENAMEAT | 35 | R | 2a | submit_renameat (renameat2 semantics) |
UNLINKAT | 36 | R | 2a | submit_unlinkat |
MKDIRAT | 37 | R | 2a | submit_mkdirat |
SYMLINKAT | 38 | R | 2a | submit_symlinkat |
LINKAT | 39 | R | 2a | submit_linkat |
MSG_RING | 40 | R | 2c | submit_msg_ring (data / send_fd) |
FSETXATTR | 41 | R | 2a | submit_fsetxattr |
SETXATTR | 42 | R | 2a | submit_setxattr |
FGETXATTR | 43 | R | 2a | submit_fgetxattr |
GETXATTR | 44 | R | 2a | submit_getxattr |
SOCKET | 45 | R | 2b | submit_socket |
URING_CMD | 46 | R | 2d | submit_uring_cmd (+ socket cmds) |
SEND_ZC | 47 | R | 2b | submit_send_zero_copy (zero-copy, CQE NOTIF) |
SENDMSG_ZC | 48 | R | 2b | submit_send_message_zero_copy |
READ_MULTISHOT | 49 | R | 3d | submit_read_multishot |
WAITID | 50 | R | 2c | submit_waitid |
FUTEX_WAIT | 51 | R | 2c | submit_futex_wait |
FUTEX_WAKE | 52 | R | 2c | submit_futex_wake |
FUTEX_WAITV | 53 | R | 2c | submit_futex_waitv |
FIXED_FD_INSTALL | 54 | R | 2c/3a | submit_fixed_fd_install |
FTRUNCATE | 55 | R | 2a | submit_ftruncate |
BIND | 56 | R | 2b | submit_bind |
LISTEN | 57 | R | 2b | submit_listen |
Opcode summary: 55 retained, 3 dropped (OPENAT, PROVIDE_BUFFERS,
REMOVE_BUFFERS).
5.3 Axis C — Register Opcodes IORING_REGISTER_* (31 in 6.12, 0–30)
| Register op | # | Decision | Stage | Façade symbol |
|---|---|---|---|---|
REGISTER_BUFFERS | 0 | O | — | → BUFFERS2. |
UNREGISTER_BUFFERS | 1 | R | 3a | RegisteredBuffers::unregister |
REGISTER_FILES | 2 | O | — | → FILES2. |
UNREGISTER_FILES | 3 | R | 3a | FixedFdTable::unregister |
REGISTER_EVENTFD | 4 | R | 3a | IoUring::register_eventfd |
UNREGISTER_EVENTFD | 5 | R | 3a | IoUring::unregister_eventfd |
REGISTER_FILES_UPDATE | 6 | O | — | → FILES_UPDATE2. |
REGISTER_EVENTFD_ASYNC | 7 | R | 3a | register_eventfd_async |
REGISTER_PROBE | 8 | R | 1 | IoUring::probe / supports_op |
REGISTER_PERSONALITY | 9 | R | 3a | IoUring::register_personality |
UNREGISTER_PERSONALITY | 10 | R | 3a | unregister_personality |
REGISTER_RESTRICTIONS | 11 | R | 3f | IoUringBuilder::restrict (§3.3) |
REGISTER_ENABLE_RINGS | 12 | R | 3f | IoUringBuilder::enable |
REGISTER_FILES2 | 13 | R | 3a | FixedFdTable::register |
REGISTER_FILES_UPDATE2 | 14 | R | 3a | FixedFdTable::update |
REGISTER_BUFFERS2 | 15 | R | 3a | RegisteredBuffers::register |
REGISTER_BUFFERS_UPDATE | 16 | R | 3a | RegisteredBuffers::update |
REGISTER_IOWQ_AFF | 17 | R | 3a | IoUring::set_work_queue_affinity |
UNREGISTER_IOWQ_AFF | 18 | R | 3a | clear_work_queue_affinity |
REGISTER_IOWQ_MAX_WORKERS | 19 | R | 3a | set_work_queue_max_workers |
REGISTER_RING_FDS | 20 | R | 3a | IoUring::register_ring_fd |
UNREGISTER_RING_FDS | 21 | R | 3a | unregister_ring_fd |
REGISTER_PBUF_RING | 22 | R | 3b | ProvidedBufferRing::register |
UNREGISTER_PBUF_RING | 23 | R | 3b | ProvidedBufferRing::unregister |
REGISTER_SYNC_CANCEL | 24 | R | 1/2c | IoUring::sync_cancel (used by S2) |
REGISTER_FILE_ALLOC_RANGE | 25 | R | 3a | FixedFdTable::set_alloc_range |
REGISTER_PBUF_STATUS | 26 | R | 3b | ProvidedBufferRing::status |
REGISTER_NAPI | 27 | R | 3a | IoUring::register_napi |
UNREGISTER_NAPI | 28 | R | 3a | unregister_napi |
REGISTER_CLOCK | 29 | R | 3a | IoUring::register_clock |
REGISTER_CLONE_BUFFERS | 30 | R | 3a | RegisteredBuffers::clone_from |
USE_REGISTERED_RING (flag 1<<31) | — | R | 1 | applied transparently when the ring fd is registered. |
Register summary: 28 retained, 3 dropped (REGISTER_BUFFERS,
REGISTER_FILES, REGISTER_FILES_UPDATE).
5.4 Axis D — Per-SQE Flags IOSQE_* (7)
| Flag | Decision | Façade symbol |
|---|---|---|
FIXED_FILE | R | implicit via the “fixed” variants (registered FD). |
IO_DRAIN | R | SubmitOptions::drain(). |
IO_LINK | R | LinkedChainBuilder (Stage 3c). |
IO_HARDLINK | R | LinkedChainBuilder::add_hard_link. |
ASYNC | R | SubmitOptions::force_async(). |
BUFFER_SELECT | R | implicit via ProvidedBufferRing (Stage 3b). |
CQE_SKIP_SUCCESS | R | SubmitOptions::skip_cqe_on_success() (FEAT_CQE_SKIP). |
5.5 Axis E — CQE Flags IORING_CQE_F_* (5 in 6.12)
| Flag | Decision | Exposure |
|---|---|---|
BUFFER | R | Completion::buffer_id() -> Option<u16>. |
MORE | R | Completion::has_more() (multishot, Stage 3d). |
SOCK_NONEMPTY | R | Completion::socket_has_pending_data(). |
NOTIF | R | Completion::is_notif() (zero-copy send, Stage 2b). |
BUF_MORE | R | incremental consumption (IOU_PBUF_RING_INC, Stage 3b). |
5.6 Axis F — io_uring_enter Flags (6 in 6.12)
Managed internally by the façade (not exposed raw):
| Flag | Decision | Internal usage |
|---|---|---|
GETEVENTS | R | submit_and_wait, wait_completion. |
SQ_WAKEUP | R | wakeup of the SQPOLL thread (Stage 3e). |
SQ_WAIT | R | wait for the SQ to drain (SQPOLL). |
EXT_ARG | R | wait timeout (getevents_arg), wait_completion_timeout. |
REGISTERED_RING | R | applied if ring fd is registered (transparent). |
ABS_TIMER | R | absolute timeout (FEAT_MIN_TIMEOUT). |
5.7 Axis G — Features IORING_FEAT_* (16 in 6.12)
Read from io_uring_params.features after setup, exposed 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. → all R, mapped to IoUringCapabilities::* predicates.
5.8 Axis H — Raw Ring Protocol (Stage 4)
Kernel structures exposed as #[repr(C)] in ::raw, plus ring flags:
io_uring_sqe(64 b / 128 b),io_uring_cqe(16 b / 32 b).io_sqring_offsets,io_cqring_offsets,io_uring_params.- SQ ring flags:
SQ_NEED_WAKEUP,SQ_CQ_OVERFLOW,SQ_TASKRUN. - CQ ring flag:
CQ_EVENTFD_DISABLED. - mmap offsets:
OFF_SQ_RING,OFF_CQ_RING,OFF_SQES,OFF_PBUF_RING. - Register argument structures:
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. Revised Stage Breakdown
The breakdown from ADR-022 is retained and extended (additions: 2d
uring_cmd, separate 3b provided-buffers-ring, 3f restrictions/sandbox):
| Stage | Sub-module | Scope | Symbols |
|---|---|---|---|
| 1 | (root) | Ring lifecycle, setup flags, slab (S1), Drop/shutdown (S2), submission/completion, Completion, probe/capabilities. | ~20 |
| 2a | (root) | Filesystem (read/write/sync/open/stat/dir/perm/xattr/splice/ftruncate…). | 26 |
| 2b | (root) | Network (accept/connect/send/recv/msg/shutdown/socket/bind/listen + zero-copy send/sendmsg). | 12 |
| 2c | (root) | Async-specific (nop/timeout/cancel/poll/futex/waitid/msg_ring/epoll_ctl/files_update/fixed_fd_install). | 15 |
| 2d | ::cmd | URING_CMD: passthrough + socket commands (SIOCINQ/OUTQ, get/setsockopt). | 3–5 |
| 3a | ::registration | Modern fixed resources: files2/buffers2/update, ring_fds, eventfd, personality, iowq aff/workers, napi, clock, clone_buffers, alloc_range. | ~25 |
| 3b | ::provided | Ring-mapped provided buffers (PBUF_RING/STATUS, incremental). | 6–8 |
| 3c | ::linked | Linked chains (soft/hard link, link_timeout). | 5–7 |
| 3d | ::multishot | Multishot (accept/recv/poll/read_multishot). | 5–7 |
| 3e | ::shared | Multi-thread: LockedIoUring, RingPool, SqpollIoUring. | 10–12 |
| 3f | ::sandbox | Restrictions + R_DISABLED + ENABLE_RINGS (capability, §3.3). | 6–8 |
| 4 | ::raw | Raw protocol (SQE/CQE, rings, register structs). | 8–12 |
7. Core Rustdoc Skeleton (Stage 1) — To Validate Before Implementation
This skeleton is the contract that Claude Code will implement (bodies =
todo!()). The exhaustive per-opcodesubmit_*functions (Stages 2a–2d) and the sub-modules (3a–3f, 4) are produced in their dedicated specs on the same model. Layer-0 conventions (ADR-021) applied:Result<_, Errno>, EINTR propagated to caller,Option<T>instead of sentinels, FDs asOwnedFd/BorrowedFd, no heap allocation in the happy path (S1).
#![allow(unused)]
fn main() {
//! Module `air-sys-syscall::io_uring` — typed io_uring façade (target 6.12).
//!
//! Abstraction level 2 (ADR-022, Decision 1): typed submission/completion.
//! Level 1 (raw rings) lives in [`raw`]. Buffers follow the ownership transfer
//! model (ADR-022, Decision 3), parked in a pre-allocated slab (S1); teardown
//! is safe via quiescent `Drop` + [`IoUring::shutdown`] (S2).
use crate::Errno;
use core::num::NonZeroU32;
use std::os::fd::{BorrowedFd, OwnedFd};
// ---------------------------------------------------------------------------
// Construction & lifecycle
// ---------------------------------------------------------------------------
/// io_uring ring. `Send` but not `Sync` (ADR-022, Decision 6): one reactor
/// per thread. For multi-thread use, see [`shared`].
pub struct IoUring {
/* fd: OwnedFd, SQ/CQ/SQE mmaps, in-flight op slab (S1), capabilities */
}
/// Builds an [`IoUring`], applying setup flags and restrictions (S3) before
/// activation.
pub struct IoUringBuilder { /* ... */ }
impl IoUringBuilder {
/// Starts a builder for `entries` SQEs (rounded up to the next power of two).
pub fn new(entries: NonZeroU32) -> Self { todo!() }
/// Explicit CQ size (`SETUP_CQSIZE`).
pub fn with_completion_queue_entries(self, entries: NonZeroU32) -> Self { todo!() }
/// Enables setup flags (see [`SetupFlags`]).
pub fn with_flags(self, flags: SetupFlags) -> Self { todo!() }
/// Shares the io-wq pool of an existing ring (`SETUP_ATTACH_WQ`).
pub fn attach_work_queue(self, other: &IoUring) -> Self { todo!() }
/// Creates the ring in disabled state (`R_DISABLED`) and applies
/// restrictions (S3). Must be followed by [`IoUringBuilder::enable`].
pub fn restrict(self, restrictions: &[Restriction]) -> Self { todo!() }
/// Finalizes: `io_uring_setup(2)`. If `restrict` was used, the ring is
/// created disabled and must be activated via [`IoUring::enable`].
pub fn build(self) -> Result<IoUring, Errno> { todo!() }
}
impl IoUring {
/// Shortcut: `IoUringBuilder::new(entries).build()`.
pub fn new(entries: NonZeroU32) -> Result<Self, Errno> { todo!() }
/// Activates a ring created with `R_DISABLED` (`REGISTER_ENABLE_RINGS`).
pub fn enable(&mut self) -> Result<(), Errno> { todo!() }
/// Clean teardown (S2): cancels in-flight ops, drains, closes the FD.
/// Prefer over an implicit `drop` on the hot path.
pub fn shutdown(self) -> Result<(), Errno> { todo!() }
}
impl Drop for IoUring {
/// Safety net (S2): if operations are still in flight, quiesces (cancels +
/// drains, **blocking**) before releasing memory. Cost is acknowledged.
fn drop(&mut self) { todo!() }
}
// ---------------------------------------------------------------------------
// Submission & completion
// ---------------------------------------------------------------------------
impl IoUring {
/// Submits pending SQEs (`io_uring_enter`, without waiting). Returns the
/// number submitted. EINTR propagated to caller (ADR-021, conv. 2).
pub fn submit(&mut self) -> Result<u32, Errno> { todo!() }
/// Submits then waits for at least `want` completions.
pub fn submit_and_wait(&mut self, want: u32) -> Result<u32, Errno> { todo!() }
/// Waits (blocking) for the next completion and consumes it.
pub fn wait_completion(&mut self) -> Result<Completion, Errno> { todo!() }
/// Waits for a completion with a maximum deadline (`EXT_ARG`/`ABS_TIMER`).
pub fn wait_completion_timeout(
&mut self,
timeout: core::time::Duration,
) -> Result<Option<Completion>, Errno> { todo!() }
/// Returns a completion if one is available, without blocking.
pub fn try_completion(&mut self) -> Option<Completion> { todo!() }
/// Iterates over available completions in the CQ.
pub fn completions(&mut self) -> CompletionIter<'_> { todo!() }
/// Per-operation options (link, drain, async, skip-cqe) applied to the
/// next submission.
pub fn with(&mut self, opts: SubmitOptions) -> &mut Self { todo!() }
}
/// Opaque token linking a submission to its completion. Encapsulates a slab
/// slot index + generation (compteur de génération anti-réutilisation) — *not* a raw `user_data` (S1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubmissionToken { /* slot: u32, gen: u32 */ }
/// Token for a multishot operation (multiple completions). See [`multishot`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MultishotToken { /* ... */ }
/// Submission options (`IOSQE_*` flags exposed safely).
#[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!() }
}
// ---------------------------------------------------------------------------
// Completion
// ---------------------------------------------------------------------------
/// A typed completion (CQE). Interpretation methods depend on the submitted op
/// (ADR-022, Decision 9); on failure they return `Err(Errno)`.
pub struct Completion { /* decoded user_data, res, flags */ }
impl Completion {
/// Token of the operation that produced this completion.
pub fn token(&self) -> SubmissionToken { todo!() }
/// Raw kernel result (semantics are op-dependent).
pub fn raw_result(&self) -> i32 { todo!() }
/// Completion flags (see [`CompletionFlags`]).
pub fn flags(&self) -> CompletionFlags { todo!() }
/// `IORING_CQE_F_MORE`: more completions will follow (multishot).
pub fn has_more(&self) -> bool { todo!() }
/// `IORING_CQE_F_NOTIF`: zero-copy notification completion.
pub fn is_notif(&self) -> bool { todo!() }
/// ID of the consumed provided buffer (`IORING_CQE_F_BUFFER`).
pub fn buffer_id(&self) -> Option<u16> { todo!() }
/// `IORING_CQE_F_SOCK_NONEMPTY`: data remaining after a recv.
pub fn socket_has_pending_data(&self) -> bool { todo!() }
// Typed interpretations (sample; completed by Stages 2a–2d):
/// Bytes read + return of the transferred buffer (S1).
pub fn into_read_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
/// Bytes written + return of the buffer.
pub fn into_write_result(self) -> Result<(Vec<u8>, usize), Errno> { todo!() }
/// Accepted FD (`submit_accept`), CLOEXEC by default.
pub fn accepted_fd(self) -> Result<OwnedFd, Errno> { todo!() }
/// Opened FD (`submit_openat2`).
pub fn opened_fd(self) -> Result<OwnedFd, Errno> { todo!() }
/// Success with no return value (close, fsync, …).
pub fn completed(&self) -> Result<(), Errno> { todo!() }
}
/// Iterator over available completions.
pub struct CompletionIter<'ring> { /* ... */ }
// ---------------------------------------------------------------------------
// Capabilities & introspection
// ---------------------------------------------------------------------------
impl IoUring {
/// True if the current kernel supports `op` (`REGISTER_PROBE`, Decision 8).
pub fn supports_op(&self, op: IoUringOpcode) -> bool { todo!() }
/// Features negotiated at setup (axis G).
pub fn capabilities(&self) -> IoUringCapabilities { todo!() }
/// Global or targeted synchronous cancellation (`REGISTER_SYNC_CANCEL`),
/// building block of [`IoUring::shutdown`] (S2).
pub fn sync_cancel(&mut self, target: CancelTarget) -> Result<u32, Errno> { todo!() }
}
/// io_uring features of the current kernel (axis G). Stable predicates.
#[derive(Debug, Clone, Copy)]
pub struct IoUringCapabilities { /* feature bits */ }
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!() }
/* … one predicate per feature from axis G … */
}
/// Enumeration of operations (axis B) for probe and restrictions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IoUringOpcode { Nop, Readv, Writev, /* … 55 retained variants … */ }
/// Cancellation target for [`IoUring::sync_cancel`].
#[derive(Debug, Clone, Copy)]
pub enum CancelTarget {
Token(SubmissionToken),
Fd(BorrowedFd<'static>),
Op(IoUringOpcode),
Any,
}
// ---------------------------------------------------------------------------
// Bitflags & sandbox (Stage 3f, detailed in ::sandbox)
// ---------------------------------------------------------------------------
bitflags::bitflags! {
/// `io_uring_setup` flags (axis A, 17 flags in 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 exposed via builder methods */
}
/// Completion flags (axis E, 5 flags in 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;
}
}
/// A restriction applied before activation (S3; `io_uring_restriction`).
#[derive(Debug, Clone, Copy)]
pub enum Restriction {
/// Allow only this submission opcode.
AllowOp(IoUringOpcode),
/// Allow only this register op.
AllowRegister(u8),
/// SQE flags in the allowlist.
SqeFlagsAllowed(SubmitOptions),
/// SQE flags required on every submission.
SqeFlagsRequired(SubmitOptions),
}
}
8. Prospective Cross-cutting Traits (To Be Decided in Stage 1)
To validate: is a common trait for submittable operations needed to factor out linked/multishot handling, or do inherent methods suffice (verbosity in service of clarity, Principle 7)?
trait Submittable— an operation that can be submitted (used byLinkedChainBuilder). Candidate; risk of premature abstraction.trait FixedResource— a registerable resource (FD / buffer). Candidate.
Recommendation: do not introduce these traits in Stage 1. Add them only if Stages 3a/3c reveal the need through measured repetition.
9. Next Steps (Detailed Per-Stage Specs)
Produce, at the same level of detail as the other families (full signature,
underlying syscall, preconditions/# Safety, behavior, errors, performance,
tests, examples), the files: io-uring-1-core-en.md,
io-uring-2a-filesystem-en.md,
io-uring-2b-network-en.md,
io-uring-2c-async-en.md,
io-uring-2d-cmd-en.md,
io-uring-3a-registration-en.md,
io-uring-3b-provided-en.md,
io-uring-3c-linked-en.md,
io-uring-3d-multishot-en.md,
io-uring-3e-shared-en.md,
io-uring-3f-sandbox-en.md,
io-uring-4-raw-en.md.
Then: the English version of this master document.
Document license: MPL 2.0
Status: Master inventory document for the air-sys-syscall::io_uring module, kernel target 6.12 LTS. To be validated before producing the detailed per-Stage specs.