Spec layer 0 — Module io_uring, Stage 2d: URING_CMD (passthrough)
Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS)
Position. Stage 2d specifies the
IORING_OP_URING_CMDoperation (46), a passthrough sub-protocol through which io_uring forwards an opaque command to theuring_cmdhandler of the subsystem that owns the target file (socket, NVMe, ublk…). Dedicated submoduleair-sys-syscall::io_uring::cmd. This is the most “raw” operation among the Stage 2x operations: its semantics depend on the target subsystem, not on io_uring. Reuses the core of Stage 1.
1. Nature and constraints
1.1 The mechanism
URING_CMD carries two things in the SQE:
cmd_op(__u32): the operation specific to the subsystem.- a command area
cmd[]: 16 bytes in a standard SQE (theaddr3/padding area), or 80 bytes if the ring is created withIORING_SETUP_SQE128.
The contents of cmd[] are opaque to io_uring: it is the file’s
f_op->uring_cmd handler that interprets them. Consequence: the facade can
only guarantee safety for the commands it knows in typed form (socket);
everything else is a constrained generic mechanism.
1.2 Stage 2d conventions
SQE128detection: if a command requires more than 16 bytes and the ring does not haveSetupFlags::SQE128, submission fails upfront (Err(EINVAL), Principle 4) — no silent truncation.FIXEDflag:IORING_URING_CMD_FIXED(bit 0) allows using a registered buffer (index inbuf_index) as the data area associated with the command. The only flag available in 6.12.- No multishot:
IORING_URING_CMD_MULTISHOTis later than 6.12 → out of scope (master doc §1.2).
2. Socket commands (safe typed layer)
io_uring 6.12 exposes, via URING_CMD on a socket FD, four commands
(enum io_uring_socket_op). The facade wraps them as typed and safe:
#![allow(unused)]
fn main() {
impl IoUring {
/// SIOCINQ: bytes available to read. cmd_op = SOCKET_URING_OP_SIOCINQ (0).
pub fn submit_socket_inq(&mut self, sock: BorrowedFd<'_>)
-> Result<SubmissionToken, Errno>;
/// SIOCOUTQ: bytes pending transmission. cmd_op = SOCKET_URING_OP_SIOCOUTQ (1).
pub fn submit_socket_outq(&mut self, sock: BorrowedFd<'_>)
-> Result<SubmissionToken, Errno>;
/// getsockopt. cmd_op = SOCKET_URING_OP_GETSOCKOPT (2).
pub fn submit_getsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
optname: i32, value: Vec<u8>)
-> Result<SubmissionToken, Errno>;
/// setsockopt. cmd_op = SOCKET_URING_OP_SETSOCKOPT (3).
pub fn submit_setsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
optname: i32, value: Vec<u8>)
-> Result<SubmissionToken, Errno>;
}
}
inq/outq: completioninto_result() -> i32(byte count). Equivalents:ioctl(SIOCINQ/SIOCOUTQ), but inside the ring, without a synchronousioctl— useful for driving socket flow in a reactor.getsockopt:valueis the output buffer (ownership transferred, S1); the SQE carrieslevel/optname/optlen/optval. Completioninto_buffer_result() -> (Vec<u8>, usize)(value + effective length).setsockopt:value(bytes, Principle 3) moved into the slot. Completioncompleted().SocketOptionLevel: type shared withfamily-net(SOL_SOCKET, IPPROTO_TCP…).- Errors:
ENOPROTOOPT,EINVAL,ENOTSOCK,EFAULT.
These four commands advantageously replace synchronous
ioctl/getsockoptcalls on an asynchronous path, without leaving the reactor.
3. Generic mechanism (arbitrary subsystems)
For subsystems the facade does not know in typed form (NVMe passthrough, ublk, future handlers), two levels are provided.
3.1 Typed level via the UringCommand trait
#![allow(unused)]
fn main() {
/// A passthrough command from a subsystem, serializable into the cmd[] area.
///
/// # Safety
/// The implementor guarantees that `encode` produces a valid command for the
/// `uring_cmd` handler of the targeted `fd`, and that `cmd_op` and any
/// associated buffers satisfy the subsystem's contract.
pub unsafe trait UringCommand {
/// Operation specific to the subsystem (cmd_op).
fn cmd_op(&self) -> u32;
/// Required size of the command area (≤ 16, or ≤ 80 if SQE128).
fn cmd_len(&self) -> usize;
/// Serializes the command into `out` (length `cmd_len`).
fn encode(&self, out: &mut [u8]);
/// Typed interpretation of the completion.
type Output;
fn interpret(&self, completion: &Completion) -> Result<Self::Output, Errno>;
}
impl IoUring {
pub fn submit_uring_cmd<C: UringCommand>(&mut self, fd: BorrowedFd<'_>, cmd: C)
-> Result<SubmissionToken, Errno>;
}
}
- The facade checks
cmd.cmd_len() ≤ 16(or≤ 80ifSQE128), otherwiseErr(EINVAL). Thetraitisunsafebecause the validity of the command depends on the subsystem, outside io_uring’s control. - Example consumer: a storage crate (upper layer) defines a
NvmePassthroughCmd: UringCommand(struct ~72 bytes ⇒ requiresSQE128). Air layer 0 provides the mechanism, not the NVMe-specific structures (those live where they are used).
3.2 Raw unsafe level (escape hatch)
#![allow(unused)]
fn main() {
impl IoUring {
/// # Safety
/// `cmd_data` must be a valid command for the uring_cmd handler of `fd`.
/// Any buffer referenced by the command must remain valid until completion.
/// `cmd_data.len()` ≤ 16 (or ≤ 80 if SQE128).
pub unsafe fn submit_uring_cmd_raw(&mut self, fd: BorrowedFd<'_>,
cmd_op: u32, cmd_data: &[u8], flags: UringCmdFlags)
-> Result<SubmissionToken, Errno>;
}
}
- Last resort for subsystems not yet typed. Exhaustive
# Safetydocumentation (layer 0 convention).UringCmdFlags:FIXED.
4. Summary
| # opcode | Opcode | Facade (safe) | Facade (generic) |
|---|---|---|---|
| 46 | URING_CMD | submit_socket_inq/outq, submit_getsockopt/setsockopt | submit_uring_cmd<C> / submit_uring_cmd_raw (unsafe) |
Socket cmd_op covered (6.12): SIOCINQ (0), SIOCOUTQ (1), GETSOCKOPT
(2), SETSOCKOPT (3). TX_TIMESTAMP/GETSOCKNAME = post-6.12, out of scope.
5. Types added / shared
New: UringCmdFlags (bitflags, FIXED), trait UringCommand. Shared:
SocketOptionLevel (family-net). Completion methods reused:
into_result/into_buffer_result/completed.
6. Test strategy
- Integration:
inq/outqon a loopback socket with known data queued;getsockopt(SO_RCVBUF)/setsockopt(SO_REUSEADDR)round-trip; comparison with synchronousgetsockopt/ioctl(family-net). - SQE128: command > 16 bytes rejected without
SQE128(EINVAL); accepted with it. - Safety: Miri on the
submit_uring_cmd<C>path (serialization into boundedcmd[]); test thatcmd_len> capacity is rejected. - Fuzzing: decoding of
getsockoptcompletions (external kernel data). - Coverage: 100% lines + branches; the
unsafesubmit_uring_cmd_rawtested via a fake handler in a harness, or recorded inCOVERAGE-EXCEPTIONS.mdif not provokable.
7. Key decisions that emerged at Stage 2d
- Two levels of exposure: safe typed socket commands on one side;
trait UringCommand(unsafe trait) + raw formunsafefor arbitrary subsystems on the other. We do not claim to type what only the subsystem knows. - Upfront
SQE128detection — outright rejection rather than silent truncation. - NVMe structures outside layer 0 — Air provides the passthrough mechanism; specific commands (NVMe, ublk) are defined by their consumers.
- No multishot uring_cmd — later than 6.12.
8. Next steps
Next spec: io-uring-3a-registration-en.md (modern fixed resources:
FixedFdTable, RegisteredBuffers, registered ring fd, eventfd, personality,
io-wq affinities, napi, clock, clone_buffers, alloc_range). Global English
translation after validation of the French documents.
Document license: MPL 2.0
Status: Technical specification for Stage 2d (URING_CMD) of the air-sys-syscall::io_uring module, target kernel 6.12 LTS.