#![cfg(target_os = "linux")]
use std::os::unix::io::RawFd;
pub struct DirectIoBackend {
fd: RawFd,
capacity: u64,
block_size: u32,
read_only: bool,
}
impl DirectIoBackend {
pub fn new(path: &std::path::Path, read_only: bool) -> std::io::Result<Self> {
let flags = if read_only {
libc::O_RDONLY | libc::O_DIRECT | libc::O_CLOEXEC
} else {
libc::O_RDWR | libc::O_DIRECT | libc::O_CLOEXEC
};
let path_cstr = std::ffi::CString::new(path.to_string_lossy().as_bytes())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let fd = unsafe { libc::open(path_cstr.as_ptr(), flags, 0o644) };
if fd < 0 {
return Err(std::io::Error::last_os_error());
}
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::fstat(fd, &mut stat) };
if ret < 0 {
unsafe { libc::close(fd) };
return Err(std::io::Error::last_os_error());
}
let capacity = stat.st_size as u64 / 512;
tracing::info!(
"Opened {} with O_DIRECT, capacity={} sectors",
path.display(),
capacity
);
Ok(Self {
fd,
capacity,
block_size: 512,
read_only,
})
}
pub fn pread(&self, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
let ret = unsafe {
libc::pread(
self.fd,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
offset as libc::off_t,
)
};
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
pub fn pwrite(&self, offset: u64, buf: &[u8]) -> std::io::Result<usize> {
if self.read_only {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Device is read-only",
));
}
let ret = unsafe {
libc::pwrite(
self.fd,
buf.as_ptr() as *const libc::c_void,
buf.len(),
offset as libc::off_t,
)
};
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(ret as usize)
}
}
pub fn sync(&self) -> std::io::Result<()> {
let ret = unsafe { libc::fdatasync(self.fd) };
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
}
impl Drop for DirectIoBackend {
fn drop(&mut self) {
if self.fd >= 0 {
unsafe { libc::close(self.fd) };
}
}
}