use std::{
fs,
io::Error,
ops::{Deref, DerefMut},
os::fd::{IntoRawFd, RawFd},
path::Path,
ptr::{self, NonNull},
slice,
sync::atomic::{AtomicUsize, Ordering},
};
pub(crate) struct Mmap {
ptr: NonNull<u8>,
len: usize,
}
impl Mmap {
pub(crate) fn new(path: impl AsRef<Path>, len: usize) -> Result<Self, Error> {
let (path, len) = (path.as_ref(), round_up_page_size(len));
if let Some(parent_path) = path.parent() {
fs::create_dir_all(parent_path)?;
}
let file = fs::OpenOptions::new().read(true).write(true).create(true).open(path)?;
if file.metadata()?.len() != len as u64 {
file.set_len(len as u64)?;
}
Self::map(file.into_raw_fd(), len).map(|ptr| Self { ptr, len })
}
fn map(file: RawFd, len: usize) -> Result<NonNull<u8>, Error> {
unsafe {
let ptr = libc::mmap(
ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
file,
0,
);
if ptr == libc::MAP_FAILED {
return Err(Error::last_os_error());
}
debug_assert_eq!(ptr as usize % page_size(), 0, "ptr is not page-aligned");
libc::madvise(ptr, len, libc::MADV_WILLNEED);
Ok(NonNull::new_unchecked(ptr as *mut u8))
}
}
#[inline]
pub(crate) fn len(&self) -> usize {
self.len
}
#[inline]
pub(crate) fn as_ptr(&self) -> *mut u8 {
self.ptr.as_ptr()
}
#[inline]
pub(crate) fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
}
#[inline]
pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.as_ptr(), self.len()) }
}
}
impl Deref for Mmap {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl DerefMut for Mmap {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl Drop for Mmap {
#[inline]
fn drop(&mut self) {
let ptr = self.ptr.as_ptr() as *mut libc::c_void;
_ = unsafe { libc::munmap(ptr, self.len) };
}
}
unsafe impl Send for Mmap {}
unsafe impl Sync for Mmap {}
#[inline]
fn round_up_page_size(value: usize) -> usize {
let page_size = page_size();
((value - 1) / page_size + 1) * page_size
}
fn page_size() -> usize {
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
match PAGE_SIZE.load(Ordering::Acquire) {
0 => {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) } as usize;
PAGE_SIZE.store(page_size, Ordering::Release);
page_size
}
page_size => page_size,
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io, io::Read};
use tempfile::tempdir;
use crate::mmap::{page_size, Mmap};
#[test]
fn test_mmap() -> io::Result<()> {
let dir = tempdir()?;
let path = dir.path().join("test");
let mut mmap = Mmap::new(&path, page_size() + 1)?;
assert_eq!(mmap.len(), 2 * page_size());
const SLICE: &[u8] = b"Hello World";
mmap[..SLICE.len()].copy_from_slice(SLICE);
drop(mmap);
let mut file = File::open(&path)?;
assert_eq!(file.metadata()?.len(), 2 * page_size() as u64);
let mut content = [0; SLICE.len()];
file.read_exact(&mut content)?;
assert_eq!(content, SLICE);
Ok(())
}
}