use more_asserts::assert_le;
use more_asserts::assert_lt;
use std::io;
use std::ptr;
use std::slice;
fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
(size + (page_size - 1)) & !(page_size - 1)
}
#[derive(Debug)]
pub struct Mmap {
ptr: usize,
total_size: usize,
accessible_size: usize,
}
impl Mmap {
pub fn new() -> Self {
let empty = Vec::<u8>::new();
Self {
ptr: empty.as_ptr() as usize,
total_size: 0,
accessible_size: 0,
}
}
pub fn with_at_least(size: usize) -> Result<Self, String> {
let page_size = region::page::size();
let rounded_size = round_up_to_page_size(size, page_size);
Self::accessible_reserved(rounded_size, rounded_size)
}
#[cfg(not(target_os = "windows"))]
pub fn accessible_reserved(
accessible_size: usize,
mapping_size: usize,
) -> Result<Self, String> {
let page_size = region::page::size();
assert_le!(accessible_size, mapping_size);
assert_eq!(mapping_size & (page_size - 1), 0);
assert_eq!(accessible_size & (page_size - 1), 0);
if mapping_size == 0 {
return Ok(Self::new());
}
Ok(if accessible_size == mapping_size {
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
mapping_size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
)
};
if ptr as isize == -1_isize {
return Err(io::Error::last_os_error().to_string());
}
Self {
ptr: ptr as usize,
total_size: mapping_size,
accessible_size,
}
} else {
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
mapping_size,
libc::PROT_NONE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
)
};
if ptr as isize == -1_isize {
return Err(io::Error::last_os_error().to_string());
}
let mut result = Self {
ptr: ptr as usize,
total_size: mapping_size,
accessible_size,
};
if accessible_size != 0 {
result.make_accessible(0, accessible_size)?;
}
result
})
}
#[cfg(target_os = "windows")]
pub fn accessible_reserved(
accessible_size: usize,
mapping_size: usize,
) -> Result<Self, String> {
use winapi::um::memoryapi::VirtualAlloc;
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_NOACCESS, PAGE_READWRITE};
let page_size = region::page::size();
assert_le!(accessible_size, mapping_size);
assert_eq!(mapping_size & (page_size - 1), 0);
assert_eq!(accessible_size & (page_size - 1), 0);
if mapping_size == 0 {
return Ok(Self::new());
}
Ok(if accessible_size == mapping_size {
let ptr = unsafe {
VirtualAlloc(
ptr::null_mut(),
mapping_size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
)
};
if ptr.is_null() {
return Err(io::Error::last_os_error().to_string());
}
Self {
ptr: ptr as usize,
total_size: mapping_size,
accessible_size,
}
} else {
let ptr =
unsafe { VirtualAlloc(ptr::null_mut(), mapping_size, MEM_RESERVE, PAGE_NOACCESS) };
if ptr.is_null() {
return Err(io::Error::last_os_error().to_string());
}
let mut result = Self {
ptr: ptr as usize,
total_size: mapping_size,
accessible_size,
};
if accessible_size != 0 {
result.make_accessible(0, accessible_size)?;
}
result
})
}
#[cfg(not(target_os = "windows"))]
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<(), String> {
let page_size = region::page::size();
assert_eq!(start & (page_size - 1), 0);
assert_eq!(len & (page_size - 1), 0);
assert_lt!(len, self.total_size);
assert_lt!(start, self.total_size - len);
let ptr = self.ptr as *const u8;
unsafe { region::protect(ptr.add(start), len, region::Protection::READ_WRITE) }
.map_err(|e| e.to_string())
}
#[cfg(target_os = "windows")]
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<(), String> {
use winapi::ctypes::c_void;
use winapi::um::memoryapi::VirtualAlloc;
use winapi::um::winnt::{MEM_COMMIT, PAGE_READWRITE};
let page_size = region::page::size();
assert_eq!(start & (page_size - 1), 0);
assert_eq!(len & (page_size - 1), 0);
assert_lt!(len, self.len());
assert_lt!(start, self.len() - len);
let ptr = self.ptr as *const u8;
if unsafe {
VirtualAlloc(
ptr.add(start) as *mut c_void,
len,
MEM_COMMIT,
PAGE_READWRITE,
)
}
.is_null()
{
return Err(io::Error::last_os_error().to_string());
}
Ok(())
}
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr as *const u8, self.total_size) }
}
pub fn as_slice_accessible(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr as *const u8, self.accessible_size) }
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.total_size) }
}
pub fn as_mut_slice_accessible(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.accessible_size) }
}
pub fn as_ptr(&self) -> *const u8 {
self.ptr as *const u8
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr as *mut u8
}
pub fn len(&self) -> usize {
self.total_size
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn duplicate(&mut self, _size_hint: Option<usize>) -> Result<Self, String> {
let mut new = Self::accessible_reserved(self.accessible_size, self.total_size)?;
new.as_mut_slice_accessible()
.copy_from_slice(self.as_slice_accessible());
Ok(new)
}
}
impl Drop for Mmap {
#[cfg(not(target_os = "windows"))]
fn drop(&mut self) {
if self.total_size != 0 {
let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.total_size) };
assert_eq!(r, 0, "munmap failed: {}", io::Error::last_os_error());
}
}
#[cfg(target_os = "windows")]
fn drop(&mut self) {
if self.len() != 0 {
use winapi::ctypes::c_void;
use winapi::um::memoryapi::VirtualFree;
use winapi::um::winnt::MEM_RELEASE;
let r = unsafe { VirtualFree(self.ptr as *mut c_void, 0, MEM_RELEASE) };
assert_ne!(r, 0);
}
}
}
fn _assert() {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<Mmap>();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_up_to_page_size() {
assert_eq!(round_up_to_page_size(0, 4096), 0);
assert_eq!(round_up_to_page_size(1, 4096), 4096);
assert_eq!(round_up_to_page_size(4096, 4096), 4096);
assert_eq!(round_up_to_page_size(4097, 4096), 8192);
}
}