#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
use linux as imp;
#[cfg(target_os = "linux")]
pub(crate) mod linux_iouring;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use macos as imp;
#[cfg(target_os = "windows")]
mod windows;
#[cfg(target_os = "windows")]
use windows as imp;
#[cfg(target_os = "windows")]
pub(crate) mod windows_nvme;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
mod unknown;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
use unknown as imp;
use std::alloc::{self, Layout};
use std::ptr::NonNull;
pub(crate) struct AlignedBuf {
ptr: NonNull<u8>,
layout: Layout,
pub(crate) len: usize,
}
impl AlignedBuf {
pub(crate) fn new(size: usize, align: usize) -> crate::Result<Self> {
let layout =
Layout::from_size_align(size, align).map_err(|_| crate::Error::AlignmentRequired {
detail: "invalid size/align combination for Direct IO buffer",
})?;
let ptr = unsafe { alloc::alloc_zeroed(layout) };
let ptr = NonNull::new(ptr).ok_or(crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::OutOfMemory,
"Direct IO aligned buffer allocation failed",
)))?;
Ok(AlignedBuf {
ptr,
layout,
len: size,
})
}
pub(crate) fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for AlignedBuf {
fn drop(&mut self) {
unsafe { alloc::dealloc(self.ptr.as_ptr(), self.layout) };
}
}
#[inline]
pub(crate) fn round_up(n: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two(), "align must be a power of two");
(n + align - 1) & !(align - 1)
}
#[inline]
pub(crate) fn open_write_new(
path: &std::path::Path,
use_direct: bool,
) -> crate::Result<(std::fs::File, bool)> {
imp::open_write_new(path, use_direct)
}
#[inline]
pub(crate) fn open_read(
path: &std::path::Path,
use_direct: bool,
) -> crate::Result<(std::fs::File, bool)> {
imp::open_read(path, use_direct)
}
#[inline]
pub(crate) fn open_append(path: &std::path::Path) -> crate::Result<std::fs::File> {
imp::open_append(path)
}
#[inline]
pub(crate) fn open_write_at(path: &std::path::Path) -> crate::Result<std::fs::File> {
imp::open_write_at(path)
}
#[inline]
pub(crate) fn write_all(file: &std::fs::File, data: &[u8]) -> crate::Result<()> {
imp::write_all(file, data)
}
#[inline]
pub(crate) fn write_all_direct(
file: &std::fs::File,
data: &[u8],
sector_size: u32,
) -> crate::Result<()> {
imp::write_all_direct(file, data, sector_size)
}
#[inline]
pub(crate) fn write_at(file: &std::fs::File, offset: u64, data: &[u8]) -> crate::Result<()> {
imp::write_at(file, offset, data)
}
#[inline]
pub(crate) fn read_all(file: &std::fs::File) -> crate::Result<Vec<u8>> {
imp::read_all(file)
}
#[inline]
pub(crate) fn read_all_direct(
file: &std::fs::File,
file_size: u64,
sector_size: u32,
) -> crate::Result<Vec<u8>> {
imp::read_all_direct(file, file_size, sector_size)
}
#[inline]
pub(crate) fn read_range(file: &std::fs::File, offset: u64, len: usize) -> crate::Result<Vec<u8>> {
imp::read_range(file, offset, len)
}
#[inline]
pub(crate) fn sync_data(file: &std::fs::File) -> crate::Result<()> {
imp::sync_data(file)
}
#[inline]
pub(crate) fn sync_full(file: &std::fs::File) -> crate::Result<()> {
imp::sync_full(file)
}
#[inline]
pub(crate) fn atomic_rename(from: &std::path::Path, to: &std::path::Path) -> crate::Result<()> {
imp::atomic_rename(from, to)
}
#[inline]
pub(crate) fn sync_parent_dir(path: &std::path::Path) -> crate::Result<()> {
imp::sync_parent_dir(path)
}
#[inline]
pub(crate) fn copy_file(src: &std::path::Path, dst: &std::path::Path) -> crate::Result<u64> {
imp::copy_file(src, dst)
}
#[inline]
pub(crate) fn probe_sector_size(path: &std::path::Path) -> u32 {
imp::probe_sector_size(path)
}
#[allow(dead_code)]
#[inline]
pub(crate) fn probe_direct_io_available() -> bool {
imp::probe_direct_io_available()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_up_no_op_when_aligned() {
assert_eq!(round_up(512, 512), 512);
assert_eq!(round_up(4096, 512), 4096);
}
#[test]
fn test_round_up_pads_to_next_boundary() {
assert_eq!(round_up(1, 512), 512);
assert_eq!(round_up(513, 512), 1024);
assert_eq!(round_up(4097, 4096), 8192);
}
#[test]
fn test_round_up_zero_returns_zero() {
assert_eq!(round_up(0, 512), 0);
}
#[test]
fn test_aligned_buf_creates_and_drops_cleanly() {
let buf = AlignedBuf::new(4096, 512).expect("alloc aligned buf");
assert_eq!(buf.len, 4096);
assert!(buf.as_slice().iter().all(|&b| b == 0), "must be zero-init");
}
#[test]
fn test_aligned_buf_write_and_read() {
let mut buf = AlignedBuf::new(512, 512).expect("alloc");
buf.as_mut_slice()[0] = 0xAB;
assert_eq!(buf.as_slice()[0], 0xAB);
}
#[test]
fn test_probe_sector_size_returns_nonzero() {
let path = std::env::temp_dir();
let size = probe_sector_size(&path);
assert!(
size >= 512,
"sector size must be at least 512, got {}",
size
);
}
#[test]
fn test_probe_direct_io_available_returns_bool() {
let _available = probe_direct_io_available();
}
}