use std::alloc::{self, Layout};
use std::fs::File;
use std::io::{self, Read};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::ptr::NonNull;
const PAGE_SIZE: usize = 4096;
const BUF_CAPACITY: usize = 256 * 1024;
struct AlignedReadBuffer {
ptr: NonNull<u8>,
layout: Layout,
len: usize,
pos: usize,
}
unsafe impl Send for AlignedReadBuffer {}
impl AlignedReadBuffer {
fn new(capacity: usize) -> io::Result<Self> {
let layout = Layout::from_size_align(capacity, PAGE_SIZE)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let ptr = unsafe { alloc::alloc(layout) };
let ptr = NonNull::new(ptr)
.ok_or_else(|| io::Error::new(io::ErrorKind::OutOfMemory, "aligned alloc failed"))?;
Ok(Self {
ptr,
layout,
len: 0,
pos: 0,
})
}
fn capacity(&self) -> usize {
self.layout.size()
}
fn remaining(&self) -> usize {
self.len - self.pos
}
fn read_ptr(&self) -> *const u8 {
unsafe { self.ptr.as_ptr().add(self.pos) }
}
fn consume(&mut self, n: usize) {
debug_assert!(n <= self.remaining());
self.pos += n;
}
fn fill_from(&mut self, fd: i32) -> io::Result<usize> {
self.pos = 0;
self.len = 0;
let n = unsafe {
libc::read(fd, self.ptr.as_ptr().cast(), self.capacity())
};
if n < 0 {
return Err(io::Error::last_os_error());
}
self.len = n.cast_unsigned();
Ok(self.len)
}
}
impl Drop for AlignedReadBuffer {
fn drop(&mut self) {
unsafe { alloc::dealloc(self.ptr.as_ptr(), self.layout) };
}
}
pub struct DirectReader {
file: File,
buf: AlignedReadBuffer,
eof: bool,
}
impl DirectReader {
pub(crate) fn raw_fd(&self) -> std::os::unix::io::RawFd {
self.file.as_raw_fd()
}
pub(crate) fn skip(&mut self, n: u64) -> io::Result<()> {
let buffered = self.buf.remaining() as u64;
if n <= buffered {
#[allow(clippy::cast_possible_truncation)] self.buf.consume(n as usize);
return Ok(());
}
let past_buf = n - buffered;
#[allow(clippy::cast_possible_truncation)] self.buf.consume(buffered as usize);
use std::io::{Seek, SeekFrom};
let current_pos = self.file.stream_position()?;
let target = current_pos + past_buf;
let aligned = target & !(PAGE_SIZE as u64 - 1);
self.file.seek(SeekFrom::Start(aligned))?;
if aligned < target {
#[allow(clippy::cast_possible_truncation)]
let gap = (target - aligned) as usize;
let filled = self.buf.fill_from(self.file.as_raw_fd())?;
if filled < gap {
self.eof = true;
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"skip past end of file",
));
}
self.buf.consume(gap);
} else {
self.buf.pos = 0;
self.buf.len = 0;
}
Ok(())
}
pub fn open(path: &Path) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECT)
.open(path)?;
let buf = AlignedReadBuffer::new(BUF_CAPACITY)?;
Ok(Self {
file,
buf,
eof: false,
})
}
}
impl Read for DirectReader {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.buf.remaining() > 0 {
let n = out.len().min(self.buf.remaining());
unsafe {
std::ptr::copy_nonoverlapping(self.buf.read_ptr(), out.as_mut_ptr(), n);
}
self.buf.consume(n);
return Ok(n);
}
if self.eof {
return Ok(0);
}
let filled = self.buf.fill_from(self.file.as_raw_fd())?;
if filled == 0 {
self.eof = true;
return Ok(0);
}
let n = out.len().min(self.buf.remaining());
unsafe {
std::ptr::copy_nonoverlapping(self.buf.read_ptr(), out.as_mut_ptr(), n);
}
self.buf.consume(n);
Ok(n)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn aligned_read_buffer_basics() {
let buf = AlignedReadBuffer::new(PAGE_SIZE * 4).unwrap();
assert_eq!(buf.len, 0);
assert_eq!(buf.pos, 0);
assert_eq!(buf.capacity(), PAGE_SIZE * 4);
assert_eq!(buf.remaining(), 0);
assert_eq!(buf.ptr.as_ptr() as usize % PAGE_SIZE, 0);
}
#[test]
fn direct_reader_reads_file() {
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("bench-tmp");
drop(std::fs::create_dir_all(&dir));
let path = dir.join("direct_reader_test.bin");
let data: Vec<u8> = (0..10000u32).flat_map(u32::to_le_bytes).collect();
{
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(&data).unwrap();
}
let result = DirectReader::open(&path);
drop(std::fs::remove_file(&path));
drop(std::fs::remove_dir(&dir));
match result {
Ok(mut reader) => {
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap();
assert_eq!(buf, data);
}
Err(e) if e.raw_os_error() == Some(libc::EINVAL) => {
eprintln!("Skipping direct_reader test: O_DIRECT not supported (EINVAL)");
}
Err(e) => panic!("unexpected error: {e}"),
}
}
}