#![allow(unsafe_code)]
use std::ffi::c_void;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use parking_lot::Mutex;
use block2::{DynBlock, RcBlock};
use dispatch2::{DispatchData, DispatchIO, DispatchIOCloseFlags, DispatchQueue, DispatchRetained};
use crate::Result;
use crate::errors::PagedbError;
use crate::vfs::blocking::offload;
use crate::vfs::traits::{VfsFile, checked_signed_file_len};
use crate::vfs::types::{ReadReq, WriteReq};
pub struct GcdFile {
file: Arc<std::fs::File>,
writable: bool,
channel: DispatchRetained<DispatchIO>,
queue: DispatchRetained<DispatchQueue>,
}
impl GcdFile {
pub(crate) fn new(
file: std::fs::File,
writable: bool,
queue: DispatchRetained<DispatchQueue>,
) -> Result<Self> {
let dup_fd = unsafe { libc::dup(file.as_raw_fd()) };
if dup_fd == -1 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
let cleanup_block: RcBlock<dyn Fn(libc::c_int)> = RcBlock::new(move |_err: libc::c_int| {
unsafe {
libc::close(dup_fd);
}
});
let channel = unsafe {
DispatchIO::new(
dispatch2::DispatchIOStreamType::DISPATCH_IO_RANDOM,
dup_fd,
&queue,
&cleanup_block,
)
};
Ok(Self {
file: Arc::new(file),
writable,
channel,
queue,
})
}
fn checked_dispatch_offset(offset: u64, len: usize) -> Result<libc::off_t> {
let last = if len > 0 {
let last_delta = u64::try_from(len - 1).map_err(|_| {
PagedbError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"buffer length does not fit in u64",
))
})?;
offset.checked_add(last_delta).ok_or_else(|| {
PagedbError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"dispatch_io offset range overflow",
))
})?
} else {
offset
};
libc::off_t::try_from(last).map_err(|_| {
PagedbError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"dispatch_io offset range does not fit into libc::off_t",
))
})?;
offset.try_into().map_err(|_| {
PagedbError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"dispatch_io offset does not fit into libc::off_t",
))
})
}
}
impl Drop for GcdFile {
fn drop(&mut self) {
self.channel.close(DispatchIOCloseFlags(0));
}
}
fn submit_read(
channel: &DispatchIO,
queue: &DispatchQueue,
offset: libc::off_t,
len: usize,
tx: tokio::sync::oneshot::Sender<std::io::Result<Vec<u8>>>,
) {
let accum: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::with_capacity(len)));
let sender = Arc::new(Mutex::new(Some(tx)));
let handler: RcBlock<dyn Fn(u8, *mut c_void, libc::c_int)> =
RcBlock::new(move |done: u8, data: *mut c_void, error: libc::c_int| {
if !data.is_null() {
let d: &DispatchData =
unsafe { &*data.cast::<DispatchData>() };
let bytes = d.to_vec();
accum.lock().extend_from_slice(&bytes);
}
if done != 0 {
let result = if error != 0 {
Err(std::io::Error::from_raw_os_error(error))
} else {
Ok(std::mem::take(&mut *accum.lock()))
};
if let Some(s) = sender.lock().take() {
let _ = s.send(result);
}
}
});
let handler_ptr: *mut DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)> =
RcBlock::as_ptr(&handler).cast::<DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)>>();
unsafe {
channel.read(offset, len, queue, handler_ptr);
}
drop(handler);
}
fn submit_write(
channel: &DispatchIO,
queue: &DispatchQueue,
offset: libc::off_t,
buf: &[u8],
tx: tokio::sync::oneshot::Sender<std::io::Result<()>>,
) {
let data = DispatchData::from_bytes(buf);
let sender = Arc::new(Mutex::new(Some(tx)));
let handler: RcBlock<dyn Fn(u8, *mut c_void, libc::c_int)> = RcBlock::new(
move |done: u8, _remaining: *mut c_void, error: libc::c_int| {
if done != 0 {
let result = if error != 0 {
Err(std::io::Error::from_raw_os_error(error))
} else {
Ok(())
};
if let Some(s) = sender.lock().take() {
let _ = s.send(result);
}
}
},
);
let handler_ptr: *mut DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)> =
RcBlock::as_ptr(&handler).cast::<DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)>>();
unsafe {
channel.write(offset, &data, queue, handler_ptr);
}
drop(handler);
drop(data);
}
impl VfsFile for GcdFile {
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let len = buf.len();
let offset = Self::checked_dispatch_offset(offset, len)?;
let (tx, rx) = tokio::sync::oneshot::channel::<std::io::Result<Vec<u8>>>();
submit_read(&self.channel, &self.queue, offset, len, tx);
let data = rx
.await
.map_err(|_| PagedbError::Io(std::io::Error::other("dispatch_io read cancelled")))?
.map_err(PagedbError::Io)?;
let n = data.len().min(len);
buf[..n].copy_from_slice(&data[..n]);
Ok(n)
}
async fn read_at_vectored(&self, reqs: &mut [ReadReq<'_>]) -> Result<()> {
for req in reqs.iter() {
Self::checked_dispatch_offset(req.offset, req.buf.len())?;
}
for req in reqs.iter_mut() {
let n = self.read_at(req.offset, req.buf).await?;
for b in &mut req.buf[n..] {
*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 len = buf.len();
let offset = Self::checked_dispatch_offset(offset, len)?;
let (tx, rx) = tokio::sync::oneshot::channel::<std::io::Result<()>>();
submit_write(&self.channel, &self.queue, offset, buf, tx);
rx.await
.map_err(|_| PagedbError::Io(std::io::Error::other("dispatch_io write cancelled")))?
.map_err(PagedbError::Io)?;
Ok(len)
}
async fn write_at_vectored(&mut self, reqs: &[WriteReq<'_>]) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
for req in reqs {
Self::checked_dispatch_offset(req.offset, req.buf.len())?;
}
for req in reqs {
self.write_at(req.offset, req.buf).await?;
}
Ok(())
}
async fn sync(&mut self) -> Result<()> {
let file = Arc::clone(&self.file);
offload(move || {
let rc = unsafe { libc::fsync(file.as_raw_fd()) };
if rc != 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
})
.await
}
async fn truncate(&mut self, len: u64) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
let len = checked_signed_file_len(len, "ftruncate")?;
let file = Arc::clone(&self.file);
offload(move || {
let rc = unsafe { libc::ftruncate(file.as_raw_fd(), len) };
if rc != 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
})
.await
}
async fn len(&self) -> Result<u64> {
let file = Arc::clone(&self.file);
offload(move || Ok(file.metadata().map_err(PagedbError::Io)?.len())).await
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.len().await? == 0)
}
fn supports_direct_io(&self) -> bool {
false
}
}