use crate::error::{AprenderError, Result};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
fn io_error(msg: impl Into<String>) -> AprenderError {
AprenderError::Other(msg.into())
}
#[derive(Debug)]
pub struct MappedFile {
#[cfg(not(target_arch = "wasm32"))]
mmap: memmap2::Mmap,
#[cfg(target_arch = "wasm32")]
data: Vec<u8>,
path: String,
}
#[cfg(not(target_arch = "wasm32"))]
#[allow(unsafe_code)]
impl MappedFile {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().to_string();
let file = File::open(path.as_ref())
.map_err(|e| io_error(format!("Failed to open file '{path_str}': {e}")))?;
let mmap = unsafe {
memmap2::MmapOptions::new()
.map(&file)
.map_err(|e| io_error(format!("Failed to mmap file '{path_str}': {e}")))?
};
Ok(Self {
mmap,
path: path_str,
})
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
#[inline]
#[must_use]
pub fn slice(&self, start: usize, end: usize) -> Option<&[u8]> {
if start <= end && end <= self.mmap.len() {
Some(&self.mmap[start..end])
} else {
None
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.mmap.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.mmap.is_empty()
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
#[cfg(unix)]
pub fn advise_sequential(&self) -> Result<()> {
self.mmap
.advise(memmap2::Advice::Sequential)
.map_err(|e| io_error(format!("madvise failed: {e}")))
}
#[cfg(unix)]
pub fn advise_random(&self) -> Result<()> {
self.mmap
.advise(memmap2::Advice::Random)
.map_err(|e| io_error(format!("madvise failed: {e}")))
}
}
#[cfg(target_arch = "wasm32")]
impl MappedFile {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().to_string();
let mut file = File::open(path.as_ref())
.map_err(|e| io_error(format!("Failed to open file '{path_str}': {e}")))?;
let mut data = Vec::new();
file.read_to_end(&mut data)
.map_err(|e| io_error(format!("Failed to read file '{path_str}': {e}")))?;
Ok(Self {
data,
path: path_str,
})
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
#[inline]
#[must_use]
pub fn slice(&self, start: usize, end: usize) -> Option<&[u8]> {
if start <= end && end <= self.data.len() {
Some(&self.data[start..end])
} else {
None
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
}
#[derive(Debug)]
pub struct MappedRegion {
data: Vec<u8>,
offset: u64,
length: usize,
}
impl MappedRegion {
#[must_use]
pub fn new(data: Vec<u8>, offset: u64) -> Self {
let length = data.len();
Self {
data,
offset,
length,
}
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn offset(&self) -> u64 {
self.offset
}
#[must_use]
pub fn len(&self) -> usize {
self.length
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
#[must_use]
pub fn slice(&self, start: usize, end: usize) -> Option<&[u8]> {
if end <= self.length && start <= end {
Some(&self.data[start..end])
} else {
None
}
}
}
#[derive(Debug)]
pub struct MemoryMappedFile {
file: File,
size: u64,
path: String,
regions: Vec<MappedRegion>,
}
impl MemoryMappedFile {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().to_string();
let file = File::open(path.as_ref())
.map_err(|e| io_error(format!("Failed to open file '{path_str}': {e}")))?;
let metadata = file
.metadata()
.map_err(|e| io_error(format!("Failed to get file metadata: {e}")))?;
Ok(Self {
file,
size: metadata.len(),
path: path_str,
regions: Vec::new(),
})
}
#[must_use]
pub fn size(&self) -> u64 {
self.size
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
pub fn map_region(&mut self, offset: u64, length: usize) -> Result<&MappedRegion> {
for (idx, region) in self.regions.iter().enumerate() {
if region.offset == offset && region.len() >= length {
return Ok(&self.regions[idx]);
}
}
self.file
.seek(SeekFrom::Start(offset))
.map_err(|e| io_error(format!("Failed to seek: {e}")))?;
let mut data = vec![0u8; length];
self.file
.read_exact(&mut data)
.map_err(|e| io_error(format!("Failed to read region: {e}")))?;
let region = MappedRegion::new(data, offset);
self.regions.push(region);
Ok(self.regions.last().expect("Just pushed"))
}
pub fn read_at(&mut self, offset: u64, length: usize) -> Result<Vec<u8>> {
if offset + length as u64 > self.size {
return Err(io_error("Read past end of file"));
}
self.file
.seek(SeekFrom::Start(offset))
.map_err(|e| io_error(format!("Failed to seek: {e}")))?;
let mut data = vec![0u8; length];
self.file
.read_exact(&mut data)
.map_err(|e| io_error(format!("Failed to read: {e}")))?;
Ok(data)
}
pub fn clear_cache(&mut self) {
self.regions.clear();
}
#[must_use]
pub fn cached_regions(&self) -> usize {
self.regions.len()
}
#[must_use]
pub fn cached_bytes(&self) -> usize {
self.regions.iter().map(MappedRegion::len).sum()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub(crate) struct PageEntry {
pub(crate) offset: u64,
pub(crate) size: usize,
pub(crate) access_count: u64,
pub(crate) last_access: u64,
}
#[path = "page_table.rs"]
mod page_table;
pub(crate) use page_table::PageTable;
#[path = "mmap_proptests.rs"]
mod mmap_proptests;
#[path = "mmap_tests.rs"]
mod mmap_tests;