#![allow(unsafe_code)]
use std::ffi::c_void;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use parking_lot::Mutex;
use block2::{Block, DynBlock, RcBlock};
use dispatch2::{DispatchData, DispatchIO, DispatchIOCloseFlags, DispatchQueue, DispatchRetained};
use crate::Result;
use crate::errors::PagedbError;
use crate::vfs::traits::VfsFile;
use crate::vfs::types::{ReadReq, WriteReq};
pub struct GcdFile {
file: 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,
writable,
channel,
queue,
})
}
fn fd(&self) -> std::os::unix::io::RawFd {
self.file.as_raw_fd()
}
}
impl Drop for GcdFile {
fn drop(&mut self) {
self.channel.close(DispatchIOCloseFlags(0));
}
}
fn submit_read(
channel: &DispatchIO,
queue: &DispatchQueue,
offset: u64,
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)> = unsafe {
std::mem::transmute::<
*mut Block<dyn Fn(u8, *mut c_void, libc::c_int)>,
*mut DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)>,
>(RcBlock::as_ptr(&handler))
};
unsafe {
#[allow(clippy::cast_possible_wrap)]
channel.read(offset as libc::off_t, len, queue, handler_ptr);
}
drop(handler);
}
fn submit_write(
channel: &DispatchIO,
queue: &DispatchQueue,
offset: u64,
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)> = unsafe {
std::mem::transmute::<
*mut Block<dyn Fn(u8, *mut c_void, libc::c_int)>,
*mut DynBlock<dyn Fn(bool, *mut DispatchData, libc::c_int)>,
>(RcBlock::as_ptr(&handler))
};
unsafe {
#[allow(clippy::cast_possible_wrap)]
channel.write(offset as libc::off_t, &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 (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_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 (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.write_at(req.offset, req.buf).await?;
}
Ok(())
}
async fn sync(&mut self) -> Result<()> {
let rc = unsafe { libc::fsync(self.fd()) };
if rc != 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
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.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> {
Ok(self.file.metadata().map_err(PagedbError::Io)?.len())
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.len().await? == 0)
}
fn supports_direct_io(&self) -> bool {
false
}
}