use std::alloc::{self, Layout};
use std::fs::File;
use std::io::{self, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::ptr::NonNull;
use super::{PAGE_SIZE, alloc_page_aligned};
const BUF_CAPACITY: usize = 256 * 1024;
struct AlignedBuffer {
ptr: NonNull<u8>,
layout: Layout,
len: usize,
}
unsafe impl Send for AlignedBuffer {}
impl AlignedBuffer {
fn new(capacity: usize) -> io::Result<Self> {
let (ptr, layout) = alloc_page_aligned(capacity)?;
Ok(Self { ptr, layout, len: 0 })
}
fn capacity(&self) -> usize {
self.layout.size()
}
fn remaining(&self) -> usize {
self.capacity() - self.len
}
fn as_ptr(&self) -> *const u8 {
self.ptr.as_ptr()
}
fn extend(&mut self, data: &[u8]) {
debug_assert!(data.len() <= self.remaining());
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.as_ptr().add(self.len), data.len());
}
self.len += data.len();
}
fn zero_pad_to(&mut self, new_len: usize) {
debug_assert!(new_len <= self.capacity());
if new_len > self.len {
unsafe {
std::ptr::write_bytes(self.ptr.as_ptr().add(self.len), 0, new_len - self.len);
}
self.len = new_len;
}
}
fn clear(&mut self) {
self.len = 0;
}
}
impl Drop for AlignedBuffer {
fn drop(&mut self) {
unsafe { alloc::dealloc(self.ptr.as_ptr(), self.layout) };
}
}
pub struct DirectWriter {
file: File,
buf: AlignedBuffer,
logical_size: u64,
flushed: bool,
}
impl DirectWriter {
pub fn create(path: &Path) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.custom_flags(libc::O_DIRECT)
.open(path)?;
let buf = AlignedBuffer::new(BUF_CAPACITY)?;
Ok(Self {
file,
buf,
logical_size: 0,
flushed: false,
})
}
fn flush_buf(&mut self) -> io::Result<()> {
let mut written = 0;
let total = self.buf.len;
while written < total {
let n = unsafe {
libc::write(
self.file.as_raw_fd(),
self.buf.as_ptr().add(written).cast(),
total - written,
)
};
if n < 0 {
return Err(io::Error::last_os_error());
}
written += n.cast_unsigned();
}
self.buf.clear();
Ok(())
}
fn flush_final(&mut self) -> io::Result<()> {
if self.flushed {
return Ok(());
}
self.flushed = true;
if self.buf.len > 0 {
let aligned = (self.buf.len + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
self.buf.zero_pad_to(aligned);
self.flush_buf()?;
}
self.file.set_len(self.logical_size)?;
Ok(())
}
pub(crate) fn sync_all(&self) -> io::Result<()> {
self.file.sync_all()
}
}
impl Write for DirectWriter {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.logical_size += data.len() as u64;
let mut offset = 0;
while offset < data.len() {
let chunk = (data.len() - offset).min(self.buf.remaining());
self.buf.extend(&data[offset..offset + chunk]);
offset += chunk;
if self.buf.remaining() == 0 {
self.flush_buf()?;
}
}
Ok(data.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flush_final()
}
}
impl Drop for DirectWriter {
fn drop(&mut self) {
drop(self.flush_final());
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn aligned_buffer_basics() {
let mut buf = AlignedBuffer::new(PAGE_SIZE * 4).unwrap();
assert_eq!(buf.len, 0);
assert_eq!(buf.capacity(), PAGE_SIZE * 4);
assert_eq!(buf.remaining(), PAGE_SIZE * 4);
assert_eq!(buf.as_ptr() as usize % PAGE_SIZE, 0);
buf.extend(&[1, 2, 3, 4, 5]);
assert_eq!(buf.len, 5);
assert_eq!(buf.remaining(), PAGE_SIZE * 4 - 5);
buf.zero_pad_to(PAGE_SIZE);
assert_eq!(buf.len, PAGE_SIZE);
buf.clear();
assert_eq!(buf.len, 0);
}
}