pub struct MmapBackend {
ptr: *mut u8,
size: usize,
read_only: bool,
}
unsafe impl Send for MmapBackend {}
unsafe impl Sync for MmapBackend {}
impl MmapBackend {
pub fn new(path: &std::path::Path, read_only: bool) -> std::io::Result<Self> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(!read_only)
.open(path)?;
let metadata = file.metadata()?;
let size = metadata.len() as usize;
if size == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Cannot mmap empty file",
));
}
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let prot = if read_only {
libc::PROT_READ
} else {
libc::PROT_READ | libc::PROT_WRITE
};
let ptr = unsafe { libc::mmap(std::ptr::null_mut(), size, prot, libc::MAP_SHARED, fd, 0) };
if ptr == libc::MAP_FAILED {
return Err(std::io::Error::last_os_error());
}
tracing::info!(
"Memory-mapped {} at {:p}, size={}",
path.display(),
ptr,
size
);
Ok(Self {
ptr: ptr as *mut u8,
size,
read_only,
})
}
#[must_use]
pub const fn capacity(&self) -> u64 {
(self.size / 512) as u64
}
pub fn read(&self, offset: usize, buf: &mut [u8]) -> std::io::Result<usize> {
if offset >= self.size {
return Ok(0);
}
let len = buf.len().min(self.size - offset);
unsafe {
std::ptr::copy_nonoverlapping(self.ptr.add(offset), buf.as_mut_ptr(), len);
}
Ok(len)
}
pub fn write(&self, offset: usize, buf: &[u8]) -> std::io::Result<usize> {
if self.read_only {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"Mapping is read-only",
));
}
if offset >= self.size {
return Ok(0);
}
let len = buf.len().min(self.size - offset);
unsafe {
std::ptr::copy_nonoverlapping(buf.as_ptr(), self.ptr.add(offset), len);
}
Ok(len)
}
pub fn sync(&self) -> std::io::Result<()> {
let ret = unsafe { libc::msync(self.ptr as *mut libc::c_void, self.size, libc::MS_SYNC) };
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}
#[must_use]
pub const unsafe fn as_ptr(&self) -> *const u8 {
self.ptr
}
#[must_use]
pub const unsafe fn as_mut_ptr(&self) -> *mut u8 {
self.ptr
}
}
impl Drop for MmapBackend {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
libc::munmap(self.ptr as *mut libc::c_void, self.size);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_mmap_backend_creation() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0u8; 8192]).unwrap();
let backend = MmapBackend::new(temp_file.path(), false).unwrap();
assert_eq!(backend.capacity(), 16); }
#[test]
fn test_mmap_backend_read_write() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0u8; 4096]).unwrap();
let backend = MmapBackend::new(temp_file.path(), false).unwrap();
let write_data = b"MmapBackend test data!";
let written = backend.write(0, write_data).unwrap();
assert_eq!(written, write_data.len());
let mut read_data = vec![0u8; write_data.len()];
let read = backend.read(0, &mut read_data).unwrap();
assert_eq!(read, write_data.len());
assert_eq!(&read_data, write_data);
}
#[test]
fn test_mmap_backend_read_at_offset() {
let mut temp_file = NamedTempFile::new().unwrap();
let mut data = vec![0u8; 4096];
data[1024..1034].copy_from_slice(b"TestOffset");
temp_file.write_all(&data).unwrap();
let backend = MmapBackend::new(temp_file.path(), true).unwrap();
let mut buf = vec![0u8; 10];
backend.read(1024, &mut buf).unwrap();
assert_eq!(&buf, b"TestOffset");
}
#[test]
fn test_mmap_backend_read_only() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0u8; 4096]).unwrap();
let backend = MmapBackend::new(temp_file.path(), true).unwrap();
let result = backend.write(0, b"test");
assert!(result.is_err());
}
#[test]
fn test_mmap_backend_read_beyond_bounds() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0u8; 1024]).unwrap();
let backend = MmapBackend::new(temp_file.path(), true).unwrap();
let mut buf = vec![0u8; 100];
let read = backend.read(2000, &mut buf).unwrap();
assert_eq!(read, 0);
}
#[test]
fn test_mmap_backend_partial_read() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0xAA; 1024]).unwrap();
let backend = MmapBackend::new(temp_file.path(), true).unwrap();
let mut buf = vec![0u8; 100];
let read = backend.read(1000, &mut buf).unwrap();
assert_eq!(read, 24); }
#[test]
fn test_mmap_backend_sync() {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&vec![0u8; 4096]).unwrap();
let backend = MmapBackend::new(temp_file.path(), false).unwrap();
backend.write(0, b"sync test").unwrap();
assert!(backend.sync().is_ok());
}
#[test]
fn test_mmap_backend_empty_file_fails() {
let temp_file = NamedTempFile::new().unwrap();
let result = MmapBackend::new(temp_file.path(), true);
assert!(result.is_err());
}
}