use std::os::fd::AsRawFd;
use std::sync::OnceLock;
use io_uring::IoUring;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct UringCapabilities {
pub read: bool,
pub write: bool,
pub fsync: bool,
pub poll_add: bool,
pub accept: bool,
pub openat: bool,
pub close: bool,
pub provide_buffers: bool,
pub send_zc: bool,
}
static CAPABILITIES: OnceLock<UringCapabilities> = OnceLock::new();
const PROBE_OPS: usize = 64;
const IO_URING_OP_SUPPORTED: u16 = 1;
const IORING_REGISTER_PROBE: libc::c_uint = 8;
const IORING_OP_POLL_ADD: u32 = 6;
const IORING_OP_ACCEPT: u32 = 13;
const IORING_OP_OPENAT: u32 = 18;
const IORING_OP_CLOSE: u32 = 19;
const IORING_OP_READ: u32 = 22;
const IORING_OP_WRITE: u32 = 23;
const IORING_OP_PROVIDE_BUFFERS: u32 = 31;
const IORING_OP_SEND_ZC: u32 = 47;
const IORING_OP_FSYNC: u32 = 3;
#[must_use]
pub fn probe_capabilities() -> UringCapabilities {
*CAPABILITIES.get_or_init(|| {
let Ok(ring) = IoUring::new(8) else {
return UringCapabilities::default();
};
let mut bytes = vec![0u8; 1024];
let rc = unsafe {
libc::syscall(
libc::SYS_io_uring_register,
ring.as_raw_fd(),
IORING_REGISTER_PROBE,
bytes.as_mut_ptr() as *mut libc::c_void,
64 as libc::c_uint,
)
};
if rc < 0 {
return UringCapabilities::default();
}
let last = usize::from(bytes[0]);
let ops_len = usize::from(bytes[1]).min(PROBE_OPS);
let supported = |opcode: u32| -> bool {
let opcode = opcode as usize;
if opcode >= ops_len || opcode > last {
return false;
}
let offset = 8 + opcode * 8 + 2;
let flags = u16::from_ne_bytes([bytes[offset], bytes[offset + 1]]);
(flags & IO_URING_OP_SUPPORTED) != 0
};
UringCapabilities {
read: supported(IORING_OP_READ),
write: supported(IORING_OP_WRITE),
fsync: supported(IORING_OP_FSYNC),
poll_add: supported(IORING_OP_POLL_ADD),
accept: supported(IORING_OP_ACCEPT),
openat: supported(IORING_OP_OPENAT),
close: supported(IORING_OP_CLOSE),
provide_buffers: supported(IORING_OP_PROVIDE_BUFFERS),
send_zc: supported(IORING_OP_SEND_ZC),
}
})
}