#![allow(unsafe_code)]
use std::path::Path;
use crate::error::StorageError;
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
pub struct MmapBuffer {
backing: Backing,
len: usize,
}
impl MmapBuffer {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, StorageError> {
#[cfg(all(target_os = "linux", feature = "libc"))]
{
if let Ok(mapped) = linux::map_private(path.as_ref()) {
return Ok(mapped);
}
}
#[cfg(target_os = "windows")]
{
if let Ok(mapped) = win::map_view(path.as_ref()) {
return Ok(mapped);
}
}
Self::read_to_aligned(path.as_ref())
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
pub fn with_view<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
f(self.as_bytes())
}
fn as_bytes(&self) -> &[u8] {
match &self.backing {
Backing::Heap(b) => {
unsafe { core::slice::from_raw_parts(b.as_ptr().cast::<u8>(), self.len) }
}
#[cfg(all(target_os = "linux", feature = "libc"))]
Backing::Mmap(m) => {
unsafe { core::slice::from_raw_parts(m.ptr, self.len) }
}
#[cfg(target_os = "windows")]
Backing::WinMap(m) => {
unsafe { core::slice::from_raw_parts(m.ptr, self.len) }
}
}
}
fn read_to_aligned(path: &Path) -> Result<Self, StorageError> {
let bytes: Vec<u8> = std::fs::read(path).map_err(|_| StorageError::Io)?;
Self::from_bytes(&bytes)
}
pub fn from_bytes(src: &[u8]) -> Result<Self, StorageError> {
let n_u64 = src.len().div_ceil(core::mem::size_of::<u64>()).max(1);
let mut storage: Vec<u64> = alloc::vec![0u64; n_u64];
unsafe {
core::ptr::copy_nonoverlapping(
src.as_ptr(),
storage.as_mut_ptr().cast::<u8>(),
src.len(),
);
}
Ok(Self {
backing: Backing::Heap(storage.into_boxed_slice()),
len: src.len(),
})
}
}
enum Backing {
Heap(Box<[u64]>),
#[cfg(all(target_os = "linux", feature = "libc"))]
Mmap(linux::MmapRegion),
#[cfg(target_os = "windows")]
WinMap(win::WinMapping),
}
#[cfg(all(target_os = "linux", feature = "libc"))]
mod linux {
use super::*;
use core::ffi::c_void;
use std::os::unix::io::AsRawFd;
pub struct MmapRegion {
pub ptr: *const u8,
pub len: usize,
}
unsafe impl Send for MmapRegion {}
unsafe impl Sync for MmapRegion {}
impl Drop for MmapRegion {
fn drop(&mut self) {
if !self.ptr.is_null() && self.len > 0 {
unsafe {
libc::munmap(self.ptr as *mut c_void, self.len);
}
}
}
}
pub fn map_private(path: &std::path::Path) -> Result<super::MmapBuffer, StorageError> {
let file = std::fs::File::open(path).map_err(|_| StorageError::Io)?;
let len = file.metadata().map_err(|_| StorageError::Io)?.len() as usize;
if len == 0 {
return super::MmapBuffer::from_bytes(&[]);
}
let ptr = unsafe {
libc::mmap(
core::ptr::null_mut(),
len,
libc::PROT_READ,
libc::MAP_PRIVATE,
file.as_raw_fd(),
0,
)
};
if ptr == libc::MAP_FAILED {
return crate::error::cold_storage_error_io();
}
Ok(super::MmapBuffer {
backing: super::Backing::Mmap(MmapRegion {
ptr: ptr.cast::<u8>(),
len,
}),
len,
})
}
}
#[cfg(target_os = "windows")]
mod win {
use super::*;
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::System::Memory::{
CreateFileMappingW, FILE_MAP_READ, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile,
PAGE_READONLY, UnmapViewOfFile,
};
pub struct WinMapping {
pub ptr: *const u8,
view: MEMORY_MAPPED_VIEW_ADDRESS,
mapping: HANDLE,
}
unsafe impl Send for WinMapping {}
unsafe impl Sync for WinMapping {}
impl Drop for WinMapping {
fn drop(&mut self) {
unsafe {
UnmapViewOfFile(self.view);
if !self.mapping.is_null() {
CloseHandle(self.mapping);
}
}
}
}
pub fn map_view(path: &std::path::Path) -> Result<super::MmapBuffer, StorageError> {
let file = std::fs::File::open(path).map_err(|_| StorageError::Io)?;
let len = file.metadata().map_err(|_| StorageError::Io)?.len() as usize;
if len == 0 {
return super::MmapBuffer::from_bytes(&[]);
}
let raw_handle = file.as_raw_handle() as HANDLE;
let mapping = unsafe {
CreateFileMappingW(
raw_handle,
core::ptr::null::<SECURITY_ATTRIBUTES>(),
PAGE_READONLY,
0,
0,
core::ptr::null(),
)
};
if mapping.is_null() {
return crate::error::cold_storage_error_io();
}
let view = unsafe { MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0) };
if view.Value.is_null() {
unsafe {
CloseHandle(mapping);
}
return crate::error::cold_storage_error_io();
}
Ok(super::MmapBuffer {
backing: super::Backing::WinMap(WinMapping {
ptr: view.Value.cast::<u8>(),
view,
mapping,
}),
len,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn read_fallback_returns_aligned_view() {
let bytes: Vec<u8> = (0u8..=200).collect();
let buf = MmapBuffer::from_bytes(&bytes).expect("alloc");
let mut captured: Vec<u8> = Vec::new();
buf.with_view(|view| {
captured.extend_from_slice(view);
assert_eq!(view.as_ptr() as usize & 0b111, 0);
});
assert_eq!(captured, bytes);
assert_eq!(buf.len(), bytes.len());
}
#[test]
fn open_round_trip_via_tmpfile() {
let dir = std::env::temp_dir();
let path = dir.join(format!("rssn_mmap_test_{}.bin", std::process::id()));
{
let mut f = std::fs::File::create(&path).expect("create");
f.write_all(&[0xDE, 0xAD, 0xBE, 0xEF, 0x42, 0x00, 0x11, 0x22, 0x33])
.expect("write");
}
let buf = MmapBuffer::open(&path).expect("open");
buf.with_view(|view| {
assert_eq!(view.len(), 9);
assert_eq!(view[0], 0xDE);
assert_eq!(view[8], 0x33);
assert_eq!(view.as_ptr() as usize & 0b111, 0);
});
let _ = std::fs::remove_file(&path);
}
#[test]
fn empty_file_works() {
let dir = std::env::temp_dir();
let path = dir.join(format!("rssn_mmap_empty_{}.bin", std::process::id()));
std::fs::File::create(&path).expect("create");
let buf = MmapBuffer::open(&path).expect("open");
assert_eq!(buf.len(), 0);
buf.with_view(|view| {
assert!(view.is_empty());
});
let _ = std::fs::remove_file(&path);
}
}