extern crate alloc;
extern crate core;
use core::mem::MaybeUninit;
use core::ptr;
use std::io::{self, IoSlice};
use std::time::Duration;
pub mod operation;
mod bindings;
#[derive(Debug, Clone, Copy)]
pub struct Completion {
user_data: u64,
res: i32,
pub flags: u32,
}
impl Completion {
pub fn is_ok(&self) -> bool {
self.res >= 0
}
pub fn result(&self) -> i32 {
self.res
}
pub fn user_data(&self) -> u64 {
self.user_data
}
pub fn has_more(&self) -> bool {
(self.flags & bindings::IORING_CQE_F_MORE) != 0
}
pub fn buffer_id(&self) -> Option<u16> {
if (self.flags & bindings::IORING_CQE_F_BUFFER) != 0 {
Some((self.flags >> bindings::IORING_CQE_BUFFER_SHIFT) as u16)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SqeFlags(u8);
impl SqeFlags {
pub const NONE: Self = Self(0);
pub const FIXED_FILE: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_FIXED_FILE_BIT);
pub const ASYNC: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_ASYNC_BIT);
pub const IO_LINK: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_IO_LINK_BIT);
pub const IO_DRAIN: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_IO_DRAIN_BIT);
pub const IO_HARDLINK: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_IO_HARDLINK_BIT);
pub const BUFFER_SELECT: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_BUFFER_SELECT_BIT);
pub const CQE_SKIP_SUCCESS: Self =
Self(1 << bindings::io_uring_sqe_flags_bit_IOSQE_CQE_SKIP_SUCCESS_BIT);
pub const fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub fn bits(self) -> u8 {
self.0
}
}
impl std::ops::BitOr for SqeFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
self.or(rhs)
}
}
pub struct Entry(pub(crate) bindings::io_uring_sqe);
impl Entry {
pub(crate) fn from_sqe(sqe: bindings::io_uring_sqe) -> Self {
Self(sqe)
}
pub(crate) fn into_sqe(self) -> bindings::io_uring_sqe {
self.0
}
}
#[derive(Debug, Clone)]
pub struct Params {
pub sq_entries: u32,
pub flags: u32,
pub sq_thread_cpu: u32,
pub sq_thread_idle: u32,
}
impl Default for Params {
fn default() -> Self {
Self { sq_entries: 128, flags: 0, sq_thread_cpu: 0, sq_thread_idle: 0 }
}
}
impl Params {
pub fn sqpoll(mut self, idle_ms: u32) -> Self {
self.flags |= bindings::IORING_SETUP_SQPOLL;
self.sq_thread_idle = idle_ms;
self
}
pub fn iopoll(mut self) -> Self {
self.flags |= bindings::IORING_SETUP_IOPOLL;
self
}
}
pub struct LioUring {
ring: bindings::io_uring,
flags: u32,
}
impl Drop for LioUring {
fn drop(&mut self) {
unsafe { bindings::io_uring_queue_exit(&raw mut self.ring) };
}
}
impl LioUring {
pub fn new(capacity: u32) -> io::Result<Self> {
Self::with_params(Params { sq_entries: capacity, ..Default::default() })
}
pub fn with_params(params: Params) -> io::Result<Self> {
let mut ring = MaybeUninit::zeroed();
let mut raw_params = bindings::io_uring_params {
sq_entries: params.sq_entries,
cq_entries: 0,
flags: params.flags,
sq_thread_cpu: params.sq_thread_cpu,
sq_thread_idle: params.sq_thread_idle,
features: 0,
wq_fd: 0,
resv: [0; 3],
sq_off: unsafe { std::mem::zeroed() },
cq_off: unsafe { std::mem::zeroed() },
};
let ret = unsafe {
bindings::io_uring_queue_init_params(
params.sq_entries,
ring.as_mut_ptr(),
&raw mut raw_params,
)
};
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
let ring_init = unsafe { ring.assume_init() };
let flags = ring_init.flags;
Ok(Self { ring: ring_init, flags })
}
pub unsafe fn push(
&mut self,
entry: Entry,
user_data: u64,
) -> io::Result<()> {
unsafe { self.push_with_flags(entry, user_data, SqeFlags::NONE) }
}
pub unsafe fn push_with_flags(
&mut self,
entry: Entry,
user_data: u64,
flags: SqeFlags,
) -> io::Result<()> {
let sqe = unsafe { bindings::io_uring_get_sqe(&raw mut self.ring) };
if sqe.is_null() {
return Err(io::Error::new(
io::ErrorKind::WouldBlock,
"submission queue is full",
));
}
unsafe {
(*sqe) = entry.into_sqe();
(*sqe).user_data = user_data;
(*sqe).flags = flags.bits()
}
Ok(())
}
pub fn submit(&mut self) -> io::Result<usize> {
if !self.is_sqpoll() {
let ret =
unsafe { bindings::io_uring_submit_and_wait(&raw mut self.ring, 0) };
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
return Ok(ret as usize);
}
let pending = unsafe {
let sq_tail = self.ring.sq.sqe_tail;
let sq_head = self.ring.sq.sqe_head;
if sq_head != sq_tail {
self.ring.sq.sqe_head = sq_tail;
std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
std::ptr::write_volatile(self.ring.sq.ktail, sq_tail);
}
sq_tail.wrapping_sub(*self.ring.sq.khead)
};
if pending == 0 {
return Ok(0);
}
let needs_wakeup =
unsafe { *self.ring.sq.kflags & bindings::IORING_SQ_NEED_WAKEUP != 0 };
if needs_wakeup {
let ret = unsafe { bindings::io_uring_submit(&raw mut self.ring) };
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(ret as usize)
} else {
Ok(pending as usize)
}
}
pub fn is_sqpoll(&self) -> bool {
(self.flags & bindings::IORING_SETUP_SQPOLL) != 0
}
pub fn sq_space_left(&self) -> usize {
unsafe {
bindings::io_uring_sq_space_left(&self.ring as *const _ as *mut _)
as usize
}
}
pub fn wait(&mut self) -> io::Result<Completion> {
let mut cqe_ptr = ptr::null_mut();
let ret = unsafe {
bindings::io_uring_wait_cqe(&raw mut self.ring, &raw mut cqe_ptr)
};
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
let cqe = unsafe { &*cqe_ptr };
let completion =
Completion { user_data: cqe.user_data, res: cqe.res, flags: cqe.flags };
unsafe { bindings::io_uring_cqe_seen(&raw mut self.ring, cqe_ptr) };
Ok(completion)
}
pub fn wait_timeout(
&mut self,
timeout: Duration,
) -> io::Result<Option<Completion>> {
let mut cqe_ptr = ptr::null_mut();
let mut ts = bindings::__kernel_timespec {
tv_sec: timeout.as_secs() as i64,
tv_nsec: timeout.subsec_nanos() as i64,
};
let ret = unsafe {
bindings::io_uring_wait_cqe_timeout(
&raw mut self.ring,
&raw mut cqe_ptr,
&raw mut ts,
)
};
if ret < 0 {
let errno = -ret;
if errno == libc::ETIME {
return Ok(None);
}
return Err(io::Error::from_raw_os_error(errno));
}
let cqe = unsafe { &*cqe_ptr };
let completion =
Completion { user_data: cqe.user_data, res: cqe.res, flags: cqe.flags };
unsafe { bindings::io_uring_cqe_seen(&raw mut self.ring, cqe_ptr) };
Ok(Some(completion))
}
pub fn try_wait(&mut self) -> io::Result<Option<Completion>> {
let mut cqe_ptr = ptr::null_mut();
let ret = unsafe {
bindings::io_uring_peek_cqe(&raw mut self.ring, &raw mut cqe_ptr)
};
if ret < 0 {
if -ret == libc::EAGAIN {
return Ok(None);
}
return Err(io::Error::from_raw_os_error(-ret));
}
if cqe_ptr.is_null() {
return Ok(None);
}
let cqe = unsafe { &*cqe_ptr };
let completion =
Completion { user_data: cqe.user_data, res: cqe.res, flags: cqe.flags };
unsafe { bindings::io_uring_cqe_seen(&raw mut self.ring, cqe_ptr) };
Ok(Some(completion))
}
pub fn peek(&self) -> io::Result<Option<Completion>> {
let mut cqe_ptr = ptr::null_mut();
let ret = unsafe {
bindings::io_uring_peek_cqe(
&self.ring as *const _ as *mut _,
&raw mut cqe_ptr,
)
};
if ret < 0 {
if -ret == libc::EAGAIN {
return Ok(None);
}
return Err(io::Error::from_raw_os_error(-ret));
}
if cqe_ptr.is_null() {
return Ok(None);
}
let cqe = unsafe { &*cqe_ptr };
Ok(Some(Completion {
user_data: cqe.user_data,
res: cqe.res,
flags: cqe.flags,
}))
}
pub fn cq_ready(&self) -> usize {
unsafe {
bindings::io_uring_cq_ready(&self.ring as *const _ as *mut _) as usize
}
}
pub unsafe fn register_buffers(
&mut self,
buffers: &[IoSlice<'_>],
) -> io::Result<()> {
let iovecs: Vec<libc::iovec> = buffers
.iter()
.map(|buf| libc::iovec {
iov_base: buf.as_ptr() as *mut _,
iov_len: buf.len(),
})
.collect();
let ret = unsafe {
bindings::io_uring_register_buffers(
&raw mut self.ring,
iovecs.as_ptr().cast(),
iovecs.len() as u32,
)
};
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(())
}
pub fn unregister_buffers(&mut self) -> io::Result<()> {
let ret =
unsafe { bindings::io_uring_unregister_buffers(&raw mut self.ring) };
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(())
}
pub fn register_files(&mut self, fds: &[i32]) -> io::Result<()> {
let ret = unsafe {
bindings::io_uring_register_files(
&raw mut self.ring,
fds.as_ptr(),
fds.len() as u32,
)
};
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(())
}
pub fn register_files_update(
&mut self,
offset: u32,
fds: &[i32],
) -> io::Result<()> {
let ret = unsafe {
bindings::io_uring_register_files_update(
&raw mut self.ring,
offset,
fds.as_ptr(),
fds.len() as u32,
)
};
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(())
}
pub fn unregister_files(&mut self) -> io::Result<()> {
let ret =
unsafe { bindings::io_uring_unregister_files(&raw mut self.ring) };
if ret < 0 {
return Err(io::Error::from_raw_os_error(-ret));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_completion_is_ok_positive() {
let c = Completion { user_data: 1, res: 0, flags: 0 };
assert!(c.is_ok());
let c = Completion { user_data: 1, res: 100, flags: 0 };
assert!(c.is_ok());
}
#[test]
fn test_completion_is_ok_negative() {
let c = Completion { user_data: 1, res: -1, flags: 0 };
assert!(!c.is_ok());
let c = Completion { user_data: 1, res: -libc::EBADF, flags: 0 };
assert!(!c.is_ok());
}
#[test]
fn test_completion_result() {
let c = Completion { user_data: 1, res: 42, flags: 0 };
assert_eq!(c.result(), 42);
let c = Completion { user_data: 1, res: -libc::EINVAL, flags: 0 };
assert_eq!(c.result(), -libc::EINVAL);
}
#[test]
fn test_completion_user_data() {
let c = Completion { user_data: 0xDEADBEEF, res: 0, flags: 0 };
assert_eq!(c.user_data(), 0xDEADBEEF);
let c = Completion { user_data: u64::MAX, res: 0, flags: 0 };
assert_eq!(c.user_data(), u64::MAX);
}
#[test]
fn test_completion_has_more() {
let c = Completion { user_data: 1, res: 0, flags: 0 };
assert!(!c.has_more());
let c =
Completion { user_data: 1, res: 0, flags: bindings::IORING_CQE_F_MORE };
assert!(c.has_more());
}
#[test]
fn test_completion_buffer_id_none() {
let c = Completion { user_data: 1, res: 0, flags: 0 };
assert_eq!(c.buffer_id(), None);
}
#[test]
fn test_completion_buffer_id_some() {
let buffer_id: u16 = 42;
let flags = bindings::IORING_CQE_F_BUFFER
| ((buffer_id as u32) << bindings::IORING_CQE_BUFFER_SHIFT);
let c = Completion { user_data: 1, res: 0, flags };
assert_eq!(c.buffer_id(), Some(42));
}
#[test]
fn test_sqe_flags_none_is_zero() {
assert_eq!(SqeFlags::NONE.bits(), 0);
}
#[test]
fn test_sqe_flags_individual_values() {
assert_ne!(SqeFlags::FIXED_FILE.bits(), 0);
assert_ne!(SqeFlags::ASYNC.bits(), 0);
assert_ne!(SqeFlags::IO_LINK.bits(), 0);
assert_ne!(SqeFlags::IO_DRAIN.bits(), 0);
assert_ne!(SqeFlags::IO_HARDLINK.bits(), 0);
assert_ne!(SqeFlags::BUFFER_SELECT.bits(), 0);
assert_ne!(SqeFlags::CQE_SKIP_SUCCESS.bits(), 0);
}
#[test]
fn test_sqe_flags_or_combines() {
let combined = SqeFlags::ASYNC.or(SqeFlags::IO_LINK);
assert_eq!(
combined.bits(),
SqeFlags::ASYNC.bits() | SqeFlags::IO_LINK.bits()
);
}
#[test]
fn test_sqe_flags_bitor_operator() {
let combined = SqeFlags::ASYNC | SqeFlags::IO_DRAIN;
assert_eq!(
combined.bits(),
SqeFlags::ASYNC.bits() | SqeFlags::IO_DRAIN.bits()
);
}
#[test]
fn test_sqe_flags_contains_true() {
let flags = SqeFlags::ASYNC | SqeFlags::IO_LINK;
assert!(flags.contains(SqeFlags::ASYNC));
assert!(flags.contains(SqeFlags::IO_LINK));
}
#[test]
fn test_sqe_flags_contains_false() {
let flags = SqeFlags::ASYNC | SqeFlags::IO_LINK;
assert!(!flags.contains(SqeFlags::FIXED_FILE));
assert!(!flags.contains(SqeFlags::IO_DRAIN));
}
#[test]
fn test_sqe_flags_contains_none() {
let flags = SqeFlags::ASYNC;
assert!(flags.contains(SqeFlags::NONE)); }
#[test]
fn test_sqe_flags_multiple_or() {
let flags = SqeFlags::ASYNC
| SqeFlags::IO_LINK
| SqeFlags::FIXED_FILE
| SqeFlags::IO_DRAIN;
assert!(flags.contains(SqeFlags::ASYNC));
assert!(flags.contains(SqeFlags::IO_LINK));
assert!(flags.contains(SqeFlags::FIXED_FILE));
assert!(flags.contains(SqeFlags::IO_DRAIN));
}
#[test]
fn test_params_default() {
let params = Params::default();
assert_eq!(params.sq_entries, 128);
assert_eq!(params.flags, 0);
assert_eq!(params.sq_thread_cpu, 0);
assert_eq!(params.sq_thread_idle, 0);
}
#[test]
fn test_params_sqpoll() {
let params = Params::default().sqpoll(1000);
assert!((params.flags & bindings::IORING_SETUP_SQPOLL) != 0);
assert_eq!(params.sq_thread_idle, 1000);
}
#[test]
fn test_params_iopoll() {
let params = Params::default().iopoll();
assert!((params.flags & bindings::IORING_SETUP_IOPOLL) != 0);
}
#[test]
fn test_params_chained() {
let params = Params::default().sqpoll(500).iopoll();
assert!((params.flags & bindings::IORING_SETUP_SQPOLL) != 0);
assert!((params.flags & bindings::IORING_SETUP_IOPOLL) != 0);
assert_eq!(params.sq_thread_idle, 500);
}
}