#![allow(unsafe_code)]
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use io_uring::IoUring;
use io_uring::opcode;
use io_uring::types::Fd;
use parking_lot::Mutex;
use crate::Result;
use crate::errors::PagedbError;
use crate::vfs::traits::VfsFile;
use crate::vfs::types::{ReadReq, WriteReq};
pub struct IouringFile {
file: std::fs::File,
writable: bool,
ring: Arc<Mutex<IoUring>>,
}
impl IouringFile {
pub(crate) fn new(file: std::fs::File, writable: bool, ring: Arc<Mutex<IoUring>>) -> Self {
Self {
file,
writable,
ring,
}
}
unsafe fn submit_one(
ring: &mut IoUring,
entry: &io_uring::squeue::Entry,
user_data: u64,
) -> std::io::Result<i32> {
unsafe {
ring.submission()
.push(entry)
.map_err(|_| std::io::Error::other("submission queue full"))?;
}
ring.submit_and_wait(1)?;
let mut result = None;
{
let mut cq = ring.completion();
cq.sync();
for cqe in cq.by_ref() {
if cqe.user_data() == user_data {
result = Some(cqe.result());
break;
}
}
}
let res =
result.ok_or_else(|| std::io::Error::other("io_uring: expected CQE not found"))?;
if res < 0 {
Err(std::io::Error::from_raw_os_error(-res))
} else {
Ok(res)
}
}
unsafe fn submit_batch(
ring: &mut IoUring,
entries: &[io_uring::squeue::Entry],
) -> std::io::Result<Vec<i32>> {
let total = entries.len();
if total == 0 {
return Ok(Vec::new());
}
let chunk_size = crate::vfs::iouring::ring::RING_DEPTH as usize;
let mut results = vec![0i32; total];
let mut base = 0usize;
while base < total {
let end = (base + chunk_size).min(total);
let chunk_len = end - base;
{
let mut sq = ring.submission();
for (i, entry) in entries[base..end].iter().enumerate() {
let tagged = entry.clone().user_data(i as u64);
unsafe {
sq.push(&tagged)
.map_err(|_| std::io::Error::other("submission queue full"))?;
}
}
}
ring.submit_and_wait(chunk_len)?;
let mut found = 0usize;
{
let mut cq = ring.completion();
cq.sync();
for cqe in cq.by_ref() {
let ud = cqe.user_data();
if ud < chunk_len as u64 {
#[allow(clippy::cast_possible_truncation)]
let idx = base + ud as usize;
results[idx] = cqe.result();
found += 1;
}
if found == chunk_len {
break;
}
}
}
if found < chunk_len {
return Err(std::io::Error::other(
"io_uring: fewer CQEs returned than submitted",
));
}
base = end;
}
Ok(results)
}
}
unsafe impl Send for IouringFile {}
impl VfsFile for IouringFile {
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let fd = Fd(self.file.as_raw_fd());
let len = u32::try_from(buf.len())
.map_err(|_| PagedbError::Io(std::io::Error::other("buffer too large for u32")))?;
let entry = opcode::Read::new(fd, buf.as_mut_ptr(), len)
.offset(offset)
.build()
.user_data(0);
let mut ring = self.ring.lock();
let n = unsafe { Self::submit_one(&mut ring, &entry, 0) }.map_err(PagedbError::Io)?;
#[allow(clippy::cast_sign_loss)]
Ok(n as usize)
}
async fn read_at_vectored(&self, reqs: &mut [ReadReq<'_>]) -> Result<()> {
if reqs.is_empty() {
return Ok(());
}
let fd = Fd(self.file.as_raw_fd());
let mut entries: Vec<io_uring::squeue::Entry> = Vec::with_capacity(reqs.len());
for (i, req) in reqs.iter_mut().enumerate() {
let len = u32::try_from(req.buf.len())
.map_err(|_| PagedbError::Io(std::io::Error::other("buffer too large for u32")))?;
entries.push(
opcode::Read::new(fd, req.buf.as_mut_ptr(), len)
.offset(req.offset)
.build()
.user_data(i as u64),
);
}
let mut ring = self.ring.lock();
let results =
unsafe { Self::submit_batch(&mut ring, &entries) }.map_err(PagedbError::Io)?;
drop(entries);
for (req, &res) in reqs.iter_mut().zip(results.iter()) {
if res < 0 {
return Err(PagedbError::Io(std::io::Error::from_raw_os_error(-res)));
}
#[allow(clippy::cast_sign_loss)]
let nread = res as usize;
for b in &mut req.buf[nread..] {
*b = 0;
}
}
Ok(())
}
async fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<usize> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
if buf.is_empty() {
return Ok(0);
}
let fd = Fd(self.file.as_raw_fd());
let len = u32::try_from(buf.len())
.map_err(|_| PagedbError::Io(std::io::Error::other("buffer too large for u32")))?;
let entry = opcode::Write::new(fd, buf.as_ptr(), len)
.offset(offset)
.build()
.user_data(0);
let mut ring = self.ring.lock();
let n = unsafe { Self::submit_one(&mut ring, &entry, 0) }.map_err(PagedbError::Io)?;
#[allow(clippy::cast_sign_loss)]
Ok(n as usize)
}
async fn write_at_vectored(&mut self, reqs: &[WriteReq<'_>]) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
if reqs.is_empty() {
return Ok(());
}
let fd = Fd(self.file.as_raw_fd());
let mut entries: Vec<io_uring::squeue::Entry> = Vec::with_capacity(reqs.len());
for (i, req) in reqs.iter().enumerate() {
let len = u32::try_from(req.buf.len())
.map_err(|_| PagedbError::Io(std::io::Error::other("buffer too large for u32")))?;
entries.push(
opcode::Write::new(fd, req.buf.as_ptr(), len)
.offset(req.offset)
.build()
.user_data(i as u64),
);
}
let mut ring = self.ring.lock();
let results =
unsafe { Self::submit_batch(&mut ring, &entries) }.map_err(PagedbError::Io)?;
for &res in &results {
if res < 0 {
return Err(PagedbError::Io(std::io::Error::from_raw_os_error(-res)));
}
}
Ok(())
}
async fn sync(&mut self) -> Result<()> {
let fd = Fd(self.file.as_raw_fd());
let entry = opcode::Fsync::new(fd).build().user_data(0);
let mut ring = self.ring.lock();
unsafe { Self::submit_one(&mut ring, &entry, 0) }.map_err(PagedbError::Io)?;
Ok(())
}
async fn truncate(&mut self, len: u64) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
#[allow(clippy::cast_possible_wrap)]
let rc = unsafe { libc::ftruncate(self.file.as_raw_fd(), len as libc::off_t) };
if rc != 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
}
async fn len(&self) -> Result<u64> {
let meta = self.file.metadata().map_err(PagedbError::Io)?;
Ok(meta.len())
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.len().await? == 0)
}
fn supports_direct_io(&self) -> bool {
true
}
}