use std::{
ffi::c_void,
io,
mem::{offset_of, size_of},
os::fd::{FromRawFd, OwnedFd, RawFd},
ptr::{self, NonNull},
time::Duration,
};
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringSqe {
pub opcode: u8,
pub flags: u8,
pub ioprio: u16,
pub fd: i32,
pub off: u64,
pub addr: u64,
pub len: u32,
pub op_flags: u32,
pub user_data: u64,
pub buf_index: u16,
pub personality: u16,
pub file_index: u32,
pub addr3: u64,
pub pad: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct IoUringCqe {
pub user_data: u64,
pub res: i32,
pub flags: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoSqringOffsets {
pub head: u32,
pub tail: u32,
pub ring_mask: u32,
pub ring_entries: u32,
pub flags: u32,
pub dropped: u32,
pub array: u32,
pub resv1: u32,
pub user_addr: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoCqringOffsets {
pub head: u32,
pub tail: u32,
pub ring_mask: u32,
pub ring_entries: u32,
pub overflow: u32,
pub cqes: u32,
pub flags: u32,
pub resv1: u32,
pub user_addr: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringParams {
pub sq_entries: u32,
pub cq_entries: u32,
pub flags: u32,
pub sq_thread_cpu: u32,
pub sq_thread_idle: u32,
pub features: u32,
pub wq_fd: u32,
pub resv: [u32; 3],
pub sq_off: IoSqringOffsets,
pub cq_off: IoCqringOffsets,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringGeteventsArg {
pub sigmask: u64,
pub sigmask_sz: u32,
pub min_wait_usec: u32,
pub ts: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringProbe {
pub last_op: u8,
pub ops_len: u8,
pub resv: u16,
pub resv2: [u32; 3],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringProbeOp {
pub op: u8,
pub resv: u8,
pub flags: u16,
pub resv2: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringBuf {
pub addr: u64,
pub len: u32,
pub bid: u16,
pub resv: u16,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringBufReg {
pub ring_addr: u64,
pub ring_entries: u32,
pub bgid: u16,
pub flags: u16,
pub min_left: u32,
pub resv: [u32; 5],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct IoUringRecvmsgOut {
pub namelen: u32,
pub controllen: u32,
pub payloadlen: u32,
pub flags: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringRsrcUpdate {
pub offset: u32,
pub resv: u32,
pub data: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringRsrcRegister {
pub nr: u32,
pub flags: u32,
pub resv2: u64,
pub data: u64,
pub tags: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct IoUringRsrcUpdate2 {
pub offset: u32,
pub resv: u32,
pub data: u64,
pub tags: u64,
pub nr: u32,
pub resv2: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct KernelTimespec {
pub tv_sec: i64,
pub tv_nsec: i64,
}
const _: () = {
assert!(size_of::<IoUringSqe>() == 64);
assert!(offset_of!(IoUringSqe, fd) == 4);
assert!(offset_of!(IoUringSqe, off) == 8);
assert!(offset_of!(IoUringSqe, addr) == 16);
assert!(offset_of!(IoUringSqe, len) == 24);
assert!(offset_of!(IoUringSqe, op_flags) == 28);
assert!(offset_of!(IoUringSqe, user_data) == 32);
assert!(offset_of!(IoUringSqe, buf_index) == 40);
assert!(offset_of!(IoUringSqe, personality) == 42);
assert!(offset_of!(IoUringSqe, file_index) == 44);
assert!(offset_of!(IoUringSqe, addr3) == 48);
assert!(size_of::<IoUringCqe>() == 16);
assert!(offset_of!(IoUringCqe, res) == 8);
assert!(offset_of!(IoUringCqe, flags) == 12);
assert!(size_of::<IoSqringOffsets>() == 40);
assert!(offset_of!(IoSqringOffsets, flags) == 16);
assert!(offset_of!(IoSqringOffsets, array) == 24);
assert!(size_of::<IoCqringOffsets>() == 40);
assert!(offset_of!(IoCqringOffsets, overflow) == 16);
assert!(offset_of!(IoCqringOffsets, cqes) == 20);
assert!(size_of::<IoUringParams>() == 120);
assert!(offset_of!(IoUringParams, flags) == 8);
assert!(offset_of!(IoUringParams, sq_thread_idle) == 16);
assert!(offset_of!(IoUringParams, features) == 20);
assert!(offset_of!(IoUringParams, sq_off) == 40);
assert!(offset_of!(IoUringParams, cq_off) == 80);
assert!(size_of::<IoUringGeteventsArg>() == 24);
assert!(offset_of!(IoUringGeteventsArg, sigmask_sz) == 8);
assert!(offset_of!(IoUringGeteventsArg, min_wait_usec) == 12);
assert!(offset_of!(IoUringGeteventsArg, ts) == 16);
assert!(size_of::<IoUringProbe>() == 16);
assert!(size_of::<IoUringProbeOp>() == 8);
assert!(offset_of!(IoUringProbeOp, flags) == 2);
assert!(size_of::<IoUringBuf>() == 16);
assert!(offset_of!(IoUringBuf, len) == 8);
assert!(offset_of!(IoUringBuf, bid) == 12);
assert!(offset_of!(IoUringBuf, resv) == 14);
assert!(size_of::<IoUringBufReg>() == 40);
assert!(offset_of!(IoUringBufReg, ring_entries) == 8);
assert!(offset_of!(IoUringBufReg, bgid) == 12);
assert!(offset_of!(IoUringBufReg, flags) == 14);
assert!(size_of::<IoUringRecvmsgOut>() == 16);
assert!(offset_of!(IoUringRecvmsgOut, payloadlen) == 8);
assert!(size_of::<IoUringRsrcUpdate>() == 16);
assert!(size_of::<IoUringRsrcRegister>() == 32);
assert!(size_of::<IoUringRsrcUpdate2>() == 32);
assert!(offset_of!(IoUringRsrcUpdate2, data) == 8);
assert!(offset_of!(IoUringRsrcUpdate2, nr) == 24);
assert!(offset_of!(IoUringRsrcUpdate, data) == 8);
assert!(size_of::<KernelTimespec>() == 16);
assert!(offset_of!(KernelTimespec, tv_nsec) == 8);
};
pub(crate) const IORING_SETUP_IOPOLL: u32 = 1 << 0;
pub(crate) const IORING_SETUP_SQPOLL: u32 = 1 << 1;
pub(crate) const IORING_SETUP_SQ_AFF: u32 = 1 << 2;
pub(crate) const IORING_SETUP_CQSIZE: u32 = 1 << 3;
pub(crate) const IORING_SETUP_CLAMP: u32 = 1 << 4;
pub(crate) const IORING_SETUP_ATTACH_WQ: u32 = 1 << 5;
pub(crate) const IORING_SETUP_R_DISABLED: u32 = 1 << 6;
pub(crate) const IORING_SETUP_SUBMIT_ALL: u32 = 1 << 7;
pub(crate) const IORING_SETUP_COOP_TASKRUN: u32 = 1 << 8;
pub(crate) const IORING_SETUP_TASKRUN_FLAG: u32 = 1 << 9;
pub(crate) const IORING_SETUP_SINGLE_ISSUER: u32 = 1 << 12;
pub(crate) const IORING_SETUP_DEFER_TASKRUN: u32 = 1 << 13;
pub(crate) const IORING_SETUP_NO_SQARRAY: u32 = 1 << 16;
pub(crate) const IORING_ENTER_GETEVENTS: u32 = 1 << 0;
pub(crate) const IORING_ENTER_SQ_WAKEUP: u32 = 1 << 1;
pub(crate) const IORING_ENTER_SQ_WAIT: u32 = 1 << 2;
pub(crate) const IORING_ENTER_EXT_ARG: u32 = 1 << 3;
pub(crate) const IORING_ENTER_REGISTERED_RING: u32 = 1 << 4;
pub(crate) const IORING_ENTER_NO_IOWAIT: u32 = 1 << 7;
pub(crate) const IORING_SQ_NEED_WAKEUP: u32 = 1 << 0;
pub(crate) const IORING_SQ_CQ_OVERFLOW: u32 = 1 << 1;
pub(crate) const IORING_SQ_TASKRUN: u32 = 1 << 2;
pub(crate) const IORING_FEAT_SINGLE_MMAP: u32 = 1 << 0;
pub(crate) const IORING_FEAT_NODROP: u32 = 1 << 1;
pub(crate) const IORING_FEAT_SUBMIT_STABLE: u32 = 1 << 2;
pub(crate) const IORING_FEAT_RW_CUR_POS: u32 = 1 << 3;
pub(crate) const IORING_FEAT_CUR_PERSONALITY: u32 = 1 << 4;
pub(crate) const IORING_FEAT_FAST_POLL: u32 = 1 << 5;
pub(crate) const IORING_FEAT_POLL_32BITS: u32 = 1 << 6;
pub(crate) const IORING_FEAT_SQPOLL_NONFIXED: u32 = 1 << 7;
pub(crate) const IORING_FEAT_EXT_ARG: u32 = 1 << 8;
pub(crate) const IORING_FEAT_NATIVE_WORKERS: u32 = 1 << 9;
pub(crate) const IORING_FEAT_RSRC_TAGS: u32 = 1 << 10;
pub(crate) const IORING_FEAT_CQE_SKIP: u32 = 1 << 11;
pub(crate) const IORING_FEAT_LINKED_FILE: u32 = 1 << 12;
pub(crate) const IORING_FEAT_REG_REG_RING: u32 = 1 << 13;
pub(crate) const IORING_FEAT_RECVSEND_BUNDLE: u32 = 1 << 14;
pub(crate) const IORING_FEAT_MIN_TIMEOUT: u32 = 1 << 15;
pub(crate) const IORING_FEAT_RW_ATTR: u32 = 1 << 16;
pub(crate) const IORING_FEAT_NO_IOWAIT: u32 = 1 << 17;
pub(crate) const IOSQE_FIXED_FILE: u8 = 1 << 0;
pub(crate) const IOSQE_IO_DRAIN: u8 = 1 << 1;
pub(crate) const IOSQE_IO_LINK: u8 = 1 << 2;
pub(crate) const IOSQE_IO_HARDLINK: u8 = 1 << 3;
pub(crate) const IOSQE_ASYNC: u8 = 1 << 4;
pub(crate) const IOSQE_BUFFER_SELECT: u8 = 1 << 5;
pub(crate) const IOSQE_CQE_SKIP_SUCCESS: u8 = 1 << 6;
pub(crate) const IORING_CQE_F_BUFFER: u32 = 1 << 0;
pub(crate) const IORING_CQE_F_MORE: u32 = 1 << 1;
pub(crate) const IORING_CQE_F_SOCK_NONEMPTY: u32 = 1 << 2;
pub(crate) const IORING_CQE_F_NOTIF: u32 = 1 << 3;
pub(crate) const IORING_CQE_F_BUF_MORE: u32 = 1 << 4;
pub(crate) const IORING_CQE_BUFFER_SHIFT: u32 = 16;
pub(crate) const IORING_REGISTER_PROBE: u32 = 8;
pub(crate) const IORING_REGISTER_ENABLE_RINGS: u32 = 12;
pub(crate) const IORING_REGISTER_IOWQ_MAX_WORKERS: u32 = 19;
pub(crate) const IORING_REGISTER_RING_FDS: u32 = 20;
pub(crate) const IORING_UNREGISTER_RING_FDS: u32 = 21;
pub(crate) const IORING_REGISTER_PBUF_RING: u32 = 22;
pub(crate) const IORING_REGISTER_FILES2: u32 = 13;
pub(crate) const IORING_REGISTER_FILES_UPDATE2: u32 = 14;
pub(crate) const IORING_RSRC_REGISTER_SPARSE: u32 = 1 << 0;
pub(crate) const IORING_UNREGISTER_PBUF_RING: u32 = 23;
pub(crate) const IOU_PBUF_RING_INC: u16 = 2;
pub(crate) const IORING_OFF_SQ_RING: u64 = 0;
pub(crate) const IORING_OFF_CQ_RING: u64 = 0x800_0000;
pub(crate) const IORING_OFF_SQES: u64 = 0x1000_0000;
pub(crate) const IORING_ACCEPT_MULTISHOT: u16 = 1 << 0;
pub(crate) const IORING_RECVSEND_POLL_FIRST: u16 = 1 << 0;
pub(crate) const IORING_RECV_MULTISHOT: u16 = 1 << 1;
pub(crate) const IORING_ASYNC_CANCEL_ALL: u32 = 1 << 0;
pub(crate) const IORING_ASYNC_CANCEL_FD: u32 = 1 << 1;
pub(crate) const IORING_ASYNC_CANCEL_ANY: u32 = 1 << 2;
pub(crate) const IORING_ASYNC_CANCEL_FD_FIXED: u32 = 1 << 3;
pub(crate) const IORING_TIMEOUT_ABS: u32 = 1 << 0;
pub(crate) const IORING_TIMEOUT_BOOTTIME: u32 = 1 << 2;
pub(crate) const IORING_TIMEOUT_REALTIME: u32 = 1 << 3;
pub(crate) const IORING_TIMEOUT_ETIME_SUCCESS: u32 = 1 << 5;
pub(crate) const IORING_POLL_ADD_MULTI: u32 = 1 << 0;
pub(crate) const IORING_MSG_DATA: u64 = 0;
pub(crate) const IORING_FSYNC_DATASYNC: u32 = 1 << 0;
pub(crate) const IO_URING_OP_SUPPORTED: u16 = 1 << 0;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Opcode {
Nop,
Readv,
Writev,
Fsync,
ReadFixed,
WriteFixed,
PollAdd,
PollRemove,
SyncFileRange,
Sendmsg,
Recvmsg,
Timeout,
TimeoutRemove,
Accept,
AsyncCancel,
LinkTimeout,
Connect,
Fallocate,
Openat,
Close,
FilesUpdate,
Statx,
Read,
Write,
Fadvise,
Madvise,
Send,
Recv,
Openat2,
EpollCtl,
Splice,
ProvideBuffers,
RemoveBuffers,
Tee,
Shutdown,
Renameat,
Unlinkat,
Mkdirat,
Symlinkat,
Linkat,
MsgRing,
Fsetxattr,
Setxattr,
Fgetxattr,
Getxattr,
Socket,
UringCmd,
SendZc,
SendmsgZc,
ReadMultishot,
Waitid,
FutexWait,
FutexWake,
FutexWaitv,
FixedFdInstall,
Ftruncate,
Bind,
Listen,
RecvZc,
EpollWait,
ReadvFixed,
WritevFixed,
Pipe,
Nop128,
UringCmd128,
}
pub(crate) const fn cqe_buffer_id(flags: u32) -> Option<u16> {
if flags & IORING_CQE_F_BUFFER != 0 {
Some((flags >> IORING_CQE_BUFFER_SHIFT) as u16)
} else {
None
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct OpcodeSet([u64; 4]);
impl OpcodeSet {
pub(crate) fn insert(&mut self, op: u8) {
self.0[usize::from(op) / 64] |= 1 << (op % 64);
}
pub(crate) fn contains(&self, op: Opcode) -> bool {
let op = op as u8;
self.0[usize::from(op) / 64] & (1 << (op % 64)) != 0
}
}
pub(crate) const PROBE_OPS: usize = 256;
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct ProbeBuf {
pub head: IoUringProbe,
pub ops: [IoUringProbeOp; PROBE_OPS],
}
impl Default for ProbeBuf {
fn default() -> Self {
Self {
head: IoUringProbe::default(),
ops: [IoUringProbeOp::default(); PROBE_OPS],
}
}
}
impl KernelTimespec {
pub(crate) fn from_duration(d: Duration) -> Self {
Self {
tv_sec: i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
tv_nsec: i64::from(d.subsec_nanos()),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct RecvmsgParts<'a> {
pub header: IoUringRecvmsgOut,
pub name: &'a [u8],
pub control: &'a [u8],
pub payload: &'a [u8],
}
impl IoUringRecvmsgOut {
pub(crate) fn parse(
buf: &[u8],
msg_namelen: u32,
msg_controllen: u32,
) -> Option<RecvmsgParts<'_>> {
const HDR: usize = size_of::<IoUringRecvmsgOut>();
let word = |i: usize| -> Option<u32> {
let b = buf.get(i * 4..i * 4 + 4)?;
Some(u32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
};
let header = Self {
namelen: word(0)?,
controllen: word(1)?,
payloadlen: word(2)?,
flags: word(3)?,
};
let name_end = HDR.checked_add(usize::try_from(msg_namelen).ok()?)?;
let control_end = name_end.checked_add(usize::try_from(msg_controllen).ok()?)?;
if buf.len() < control_end {
return None;
}
let name_len = (header.namelen as usize).min(msg_namelen as usize);
let control_len = (header.controllen as usize).min(msg_controllen as usize);
let payload = &buf[control_end..];
Some(RecvmsgParts {
header,
name: &buf[HDR..HDR + name_len],
control: &buf[name_end..name_end + control_len],
payload: &payload[..payload.len().min(header.payloadlen as usize)],
})
}
}
pub(crate) fn io_uring_setup(entries: u32, params: &mut IoUringParams) -> io::Result<OwnedFd> {
let r = unsafe {
libc::syscall(
libc::SYS_io_uring_setup,
entries as libc::c_long,
ptr::from_mut(params),
)
};
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(unsafe { OwnedFd::from_raw_fd(r as RawFd) })
}
pub(crate) unsafe fn io_uring_enter(
fd: RawFd,
to_submit: u32,
min_complete: u32,
flags: u32,
arg: *const c_void,
argsz: usize,
) -> io::Result<u32> {
unsafe { enter_syscall(fd, to_submit, min_complete, flags, arg, argsz) }
}
#[inline]
unsafe fn enter_syscall(
fd: RawFd,
to_submit: u32,
min_complete: u32,
flags: u32,
arg: *const c_void,
argsz: usize,
) -> io::Result<u32> {
unsafe {
crate::io::sys::syscall(
libc::SYS_io_uring_enter,
[
fd as usize,
to_submit as usize,
min_complete as usize,
flags as usize,
arg as usize,
argsz,
],
)
}
.map(|n| n as u32)
}
pub(crate) unsafe fn io_uring_register(
fd: RawFd,
opcode: u32,
arg: *const c_void,
nr_args: u32,
) -> io::Result<u32> {
let r = unsafe {
libc::syscall(
libc::SYS_io_uring_register,
fd as libc::c_long,
opcode as libc::c_long,
arg,
nr_args as libc::c_long,
)
};
if r < 0 {
return Err(io::Error::last_os_error());
}
Ok(r as u32)
}
#[derive(Debug)]
pub(crate) struct Mapping {
ptr: NonNull<u8>,
len: usize,
}
impl Mapping {
fn map(len: usize, flags: libc::c_int, fd: RawFd, offset: u64) -> io::Result<Self> {
if len == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cannot map zero bytes",
));
}
let p = unsafe {
libc::mmap(
ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
flags,
fd,
offset as libc::off_t,
)
};
if p == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let ptr = NonNull::new(p.cast::<u8>())
.ok_or_else(|| io::Error::other("the kernel mapped memory at address zero"))?;
Ok(Self { ptr, len })
}
pub(crate) fn ring(fd: RawFd, len: usize, offset: u64) -> io::Result<Self> {
Self::map(len, libc::MAP_SHARED | libc::MAP_POPULATE, fd, offset)
}
pub(crate) fn anon(len: usize) -> io::Result<Self> {
Self::map(len, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, -1, 0)
}
pub(crate) fn as_ptr(&self) -> *mut u8 {
self.ptr.as_ptr()
}
pub(crate) fn len(&self) -> usize {
self.len
}
}
impl Drop for Mapping {
fn drop(&mut self) {
unsafe { libc::munmap(self.ptr.as_ptr().cast(), self.len) };
}
}