use std::fmt::{self, Debug};
use std::fs::File;
use std::io::{self, Cursor, Read, Seek};
use std::path::Path;
pub trait Reader: Read + Seek {}
impl<T> Reader for T where T: Read + Seek {}
pub struct MappedFileReader<'map> {
mapped_file: &'map [u8],
cursor: Cursor<&'map [u8]>,
}
impl Debug for MappedFileReader<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MappedFileReader").finish()
}
}
impl MappedFileReader<'_> {
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
let file = File::open(path)?;
let mapped_file = memory_map_file(&file)?;
Ok(Self {
mapped_file,
cursor: Cursor::new(mapped_file),
})
}
}
impl Read for MappedFileReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.cursor.read(buf)
}
}
impl Seek for MappedFileReader<'_> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.cursor.seek(pos)
}
}
impl Drop for MappedFileReader<'_> {
fn drop(&mut self) {
unmap_memory_mapped_file(self.mapped_file).expect("failed to unmap");
}
}
#[cfg(windows)]
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
mod windows {
use std::fs::File;
use std::os::windows::prelude::AsRawHandle;
use std::os::windows::raw::HANDLE;
use std::{io, ptr, slice};
const PAGE_READONLY: DWORD = 2;
const FILE_MAP_READ: DWORD = 4;
type DWORD = u32;
type BOOL = u32;
type SIZE_T = usize;
type LPCSTR = *mut u8;
type LPVOID = *const u8;
unsafe extern "system" {
fn CreateFileMappingA(
h: HANDLE,
file_mapping_attrs: *const u8,
protect: DWORD,
max_size_high: DWORD,
max_size_low: DWORD,
name: LPCSTR,
) -> HANDLE;
fn MapViewOfFile(
file_mapping_object: HANDLE,
desired_access: DWORD,
file_offset_high: DWORD,
file_offset_low: DWORD,
number_of_bytes_to_map: SIZE_T,
) -> LPVOID;
fn CloseHandle(h: HANDLE) -> BOOL;
fn UnmapViewOfFile(base_address: LPVOID) -> BOOL;
}
pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> {
let file_handle = file.as_raw_handle();
let mapping_handle = unsafe {
CreateFileMappingA(
file_handle,
ptr::null_mut(),
PAGE_READONLY,
0,
0,
ptr::null_mut(),
)
};
if mapping_handle.is_null() {
return Err(io::Error::last_os_error());
}
let size = file.metadata()?.len().try_into().unwrap();
let base = unsafe { MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, size) };
if base.is_null() {
unsafe {
CloseHandle(mapping_handle);
}
return Err(io::Error::last_os_error());
}
unsafe {
CloseHandle(mapping_handle);
}
assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
Ok(unsafe { slice::from_raw_parts(base, size) })
}
pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
match unsafe { UnmapViewOfFile(data.as_ptr()) } {
0 => Err(io::Error::last_os_error()),
_ => Ok(()),
}
}
}
#[cfg(windows)]
use windows::{memory_map_file, unmap_memory_mapped_file};
#[cfg(unix)]
mod unix {
use std::fs::File;
use std::os::fd::AsRawFd;
use std::{io, ptr, slice};
const PROT_READ: i32 = 1;
const MAP_SHARED: i32 = 1;
const MAP_FAILED: *const u8 = usize::MAX as _;
unsafe extern "system" {
fn mmap(
addr: *const u8,
length: usize,
prot: i32,
flags: i32,
fd: i32,
offset: i32,
) -> *const u8;
fn munmap(addr: *const u8, length: usize) -> i32;
}
pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> {
let file_fd = file.as_raw_fd();
let size = file.metadata()?.len().try_into().unwrap();
let ret = unsafe { mmap(ptr::null_mut(), size, PROT_READ, MAP_SHARED, file_fd, 0) };
if ret == MAP_FAILED {
return Err(io::Error::last_os_error());
}
assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
Ok(unsafe { slice::from_raw_parts(ret, size) })
}
pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
match unsafe { munmap(data.as_ptr(), data.len()) } {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}
#[cfg(unix)]
use unix::{memory_map_file, unmap_memory_mapped_file};
#[cfg(not(any(windows, unix)))]
fn unimplemented() -> u32 {}