#![deny(clippy::all, clippy::pedantic)]
#![allow(
// pedantic exceptions
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::doc_markdown,
clippy::explicit_deref_methods,
clippy::missing_errors_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::needless_pass_by_value,
clippy::return_self_not_must_use,
clippy::unreadable_literal,
clippy::upper_case_acronyms,
)]
#![allow(clippy::len_without_is_empty, clippy::missing_safety_doc)]
#[cfg_attr(unix, path = "unix.rs")]
#[cfg_attr(windows, path = "windows.rs")]
#[cfg_attr(not(any(unix, windows)), path = "stub.rs")]
mod os;
use crate::os::{file_len, MmapInner};
#[cfg(unix)]
mod advice;
#[cfg(unix)]
pub use crate::advice::{Advice, UncheckedAdvice};
#[cfg(not(any(unix, windows)))]
use std::fs::File;
#[cfg(unix)]
use std::os::unix::io::{AsFd, BorrowedFd};
#[cfg(windows)]
use std::os::windows::io::{AsHandle, BorrowedHandle};
use std::{
fmt,
io::{Error, ErrorKind, Result},
ops::{Deref, DerefMut},
slice,
};
#[cfg(not(any(unix, windows)))]
pub struct MmapRawDescriptor<'a>(&'a File);
#[cfg(unix)]
pub struct MmapRawDescriptor<'a>(BorrowedFd<'a>);
#[cfg(windows)]
pub struct MmapRawDescriptor<'a>(BorrowedHandle<'a>);
pub trait MmapAsRawDesc {
fn as_raw_desc(&self) -> MmapRawDescriptor<'_>;
}
#[cfg(not(any(unix, windows)))]
impl MmapAsRawDesc for &File {
fn as_raw_desc(&self) -> MmapRawDescriptor<'_> {
MmapRawDescriptor(self)
}
}
#[cfg(unix)]
impl<T: AsFd + ?Sized> MmapAsRawDesc for T {
fn as_raw_desc(&self) -> MmapRawDescriptor<'_> {
MmapRawDescriptor(self.as_fd())
}
}
#[cfg(windows)]
impl<T: AsHandle + ?Sized> MmapAsRawDesc for T {
fn as_raw_desc(&self) -> MmapRawDescriptor<'_> {
MmapRawDescriptor(self.as_handle())
}
}
fn check_range(offset: usize, len: usize, map_len: usize) -> Result<bool> {
let end = offset
.checked_add(len)
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "offset + len overflows usize"))?;
if end > map_len {
return Err(Error::new(
ErrorKind::InvalidInput,
"offset + len exceeds the memory map's length",
));
}
Ok(len != 0)
}
#[derive(Clone, Debug, Default)]
pub struct MmapOptions {
offset: u64,
len: Option<usize>,
huge: Option<u8>,
stack: bool,
populate: bool,
no_reserve_swap: bool,
}
impl MmapOptions {
pub fn new() -> MmapOptions {
MmapOptions::default()
}
pub fn offset(&mut self, offset: u64) -> &mut Self {
self.offset = offset;
self
}
pub fn len(&mut self, len: usize) -> &mut Self {
self.len = Some(len);
self
}
fn validate_len(len: u64) -> Result<usize> {
if isize::try_from(len).is_err() {
return Err(Error::new(
ErrorKind::InvalidData,
"memory map length overflows isize",
));
}
Ok(len as usize)
}
fn get_len<T: MmapAsRawDesc>(&self, file: &T) -> Result<usize> {
let len = if let Some(len) = self.len {
len as u64
} else {
let desc = file.as_raw_desc();
let file_len = file_len(desc.0)?;
if file_len < self.offset {
return Err(Error::new(
ErrorKind::InvalidData,
"memory map offset is larger than length",
));
}
file_len - self.offset
};
Self::validate_len(len)
}
pub fn stack(&mut self) -> &mut Self {
self.stack = true;
self
}
pub fn huge(&mut self, page_bits: Option<u8>) -> &mut Self {
self.huge = Some(page_bits.unwrap_or(0));
self
}
pub fn populate(&mut self) -> &mut Self {
self.populate = true;
self
}
pub fn no_reserve_swap(&mut self) -> &mut Self {
self.no_reserve_swap = true;
self
}
pub unsafe fn map<T: MmapAsRawDesc>(&self, file: T) -> Result<Mmap> {
let desc = file.as_raw_desc();
MmapInner::map(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| Mmap { inner })
}
pub unsafe fn map_exec<T: MmapAsRawDesc>(&self, file: T) -> Result<Mmap> {
let desc = file.as_raw_desc();
MmapInner::map_exec(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| Mmap { inner })
}
pub unsafe fn map_mut<T: MmapAsRawDesc>(&self, file: T) -> Result<MmapMut> {
let desc = file.as_raw_desc();
MmapInner::map_mut(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| MmapMut { inner })
}
pub unsafe fn map_copy<T: MmapAsRawDesc>(&self, file: T) -> Result<MmapMut> {
let desc = file.as_raw_desc();
MmapInner::map_copy(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| MmapMut { inner })
}
pub unsafe fn map_copy_read_only<T: MmapAsRawDesc>(&self, file: T) -> Result<Mmap> {
let desc = file.as_raw_desc();
MmapInner::map_copy_read_only(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| Mmap { inner })
}
pub fn map_anon(&self) -> Result<MmapMut> {
let len = self.len.unwrap_or(0);
let len = Self::validate_len(len as u64)?;
MmapInner::map_anon(
len,
self.stack,
self.populate,
self.huge,
self.no_reserve_swap,
)
.map(|inner| MmapMut { inner })
}
pub fn map_raw<T: MmapAsRawDesc>(&self, file: T) -> Result<MmapRaw> {
let desc = file.as_raw_desc();
MmapInner::map_mut(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| MmapRaw { inner })
}
pub fn map_raw_read_only<T: MmapAsRawDesc>(&self, file: T) -> Result<MmapRaw> {
let desc = file.as_raw_desc();
MmapInner::map(
self.get_len(&file)?,
desc.0,
self.offset,
self.populate,
self.no_reserve_swap,
)
.map(|inner| MmapRaw { inner })
}
}
pub struct Mmap {
inner: MmapInner,
}
impl Mmap {
pub unsafe fn map<T: MmapAsRawDesc>(file: T) -> Result<Mmap> {
MmapOptions::new().map(file)
}
pub fn make_mut(mut self) -> Result<MmapMut> {
self.inner.make_mut()?;
Ok(MmapMut { inner: self.inner })
}
#[cfg(unix)]
pub fn advise(&self, advice: Advice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub unsafe fn unchecked_advise(&self, advice: UncheckedAdvice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub fn advise_range(&self, advice: Advice, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub unsafe fn unchecked_advise_range(
&self,
advice: UncheckedAdvice,
offset: usize,
len: usize,
) -> Result<()> {
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub fn lock(&self) -> Result<()> {
self.inner.lock()
}
#[cfg(unix)]
pub fn unlock(&self) -> Result<()> {
self.inner.unlock()
}
#[cfg(target_os = "linux")]
pub unsafe fn remap(&mut self, new_len: usize, options: RemapOptions) -> Result<()> {
self.inner.remap(new_len, options)
}
}
#[cfg(feature = "stable_deref_trait")]
unsafe impl stable_deref_trait::StableDeref for Mmap {}
impl Deref for Mmap {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.inner.ptr(), self.inner.len()) }
}
}
impl AsRef<[u8]> for Mmap {
#[inline]
fn as_ref(&self) -> &[u8] {
self.deref()
}
}
impl fmt::Debug for Mmap {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt
.debug_struct("Mmap")
.field("ptr", &self.as_ptr())
.field("len", &self.len())
.finish()
}
}
pub struct MmapRaw {
inner: MmapInner,
}
impl MmapRaw {
pub fn map_raw<T: MmapAsRawDesc>(file: T) -> Result<MmapRaw> {
MmapOptions::new().map_raw(file)
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.inner.ptr()
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut u8 {
self.inner.ptr() as *mut u8
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn flush(&self) -> Result<()> {
let len = self.len();
self.inner.flush(0, len)
}
pub fn flush_async(&self) -> Result<()> {
let len = self.len();
self.inner.flush_async(0, len)
}
pub fn flush_range(&self, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.flush(offset, len)
}
pub fn flush_async_range(&self, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.flush_async(offset, len)
}
#[cfg(unix)]
pub fn advise(&self, advice: Advice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub unsafe fn unchecked_advise(&self, advice: UncheckedAdvice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub fn advise_range(&self, advice: Advice, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub unsafe fn unchecked_advise_range(
&self,
advice: UncheckedAdvice,
offset: usize,
len: usize,
) -> Result<()> {
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub fn lock(&self) -> Result<()> {
self.inner.lock()
}
#[cfg(unix)]
pub fn unlock(&self) -> Result<()> {
self.inner.unlock()
}
#[cfg(target_os = "linux")]
pub unsafe fn remap(&mut self, new_len: usize, options: RemapOptions) -> Result<()> {
self.inner.remap(new_len, options)
}
}
impl fmt::Debug for MmapRaw {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt
.debug_struct("MmapRaw")
.field("ptr", &self.as_ptr())
.field("len", &self.len())
.finish()
}
}
impl From<Mmap> for MmapRaw {
fn from(value: Mmap) -> Self {
Self { inner: value.inner }
}
}
impl From<MmapMut> for MmapRaw {
fn from(value: MmapMut) -> Self {
Self { inner: value.inner }
}
}
pub struct MmapMut {
inner: MmapInner,
}
impl MmapMut {
pub unsafe fn map_mut<T: MmapAsRawDesc>(file: T) -> Result<MmapMut> {
MmapOptions::new().map_mut(file)
}
pub fn map_anon(length: usize) -> Result<MmapMut> {
MmapOptions::new().len(length).map_anon()
}
pub fn flush(&self) -> Result<()> {
let len = self.len();
self.inner.flush(0, len)
}
pub fn flush_async(&self) -> Result<()> {
let len = self.len();
self.inner.flush_async(0, len)
}
pub fn flush_range(&self, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.flush(offset, len)
}
pub fn flush_async_range(&self, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.flush_async(offset, len)
}
pub fn make_read_only(mut self) -> Result<Mmap> {
self.inner.make_read_only()?;
Ok(Mmap { inner: self.inner })
}
pub fn make_exec(mut self) -> Result<Mmap> {
self.inner.make_exec()?;
Ok(Mmap { inner: self.inner })
}
#[cfg(unix)]
pub fn advise(&self, advice: Advice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub unsafe fn unchecked_advise(&self, advice: UncheckedAdvice) -> Result<()> {
self.inner.advise(advice.to_raw(), 0, self.inner.len())
}
#[cfg(unix)]
pub fn advise_range(&self, advice: Advice, offset: usize, len: usize) -> Result<()> {
if !check_range(offset, len, self.inner.len())? {
return Ok(());
}
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub unsafe fn unchecked_advise_range(
&self,
advice: UncheckedAdvice,
offset: usize,
len: usize,
) -> Result<()> {
self.inner.advise(advice.to_raw(), offset, len)
}
#[cfg(unix)]
pub fn lock(&self) -> Result<()> {
self.inner.lock()
}
#[cfg(unix)]
pub fn unlock(&self) -> Result<()> {
self.inner.unlock()
}
#[cfg(target_os = "linux")]
pub unsafe fn remap(&mut self, new_len: usize, options: RemapOptions) -> Result<()> {
self.inner.remap(new_len, options)
}
}
#[cfg(feature = "stable_deref_trait")]
unsafe impl stable_deref_trait::StableDeref for MmapMut {}
impl Deref for MmapMut {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.inner.ptr(), self.inner.len()) }
}
}
impl DerefMut for MmapMut {
#[inline]
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.inner.mut_ptr(), self.inner.len()) }
}
}
impl AsRef<[u8]> for MmapMut {
#[inline]
fn as_ref(&self) -> &[u8] {
self.deref()
}
}
impl AsMut<[u8]> for MmapMut {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl fmt::Debug for MmapMut {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt
.debug_struct("MmapMut")
.field("ptr", &self.as_ptr())
.field("len", &self.len())
.finish()
}
}
#[derive(Copy, Clone, Default, Debug)]
#[cfg(target_os = "linux")]
pub struct RemapOptions {
may_move: bool,
}
#[cfg(target_os = "linux")]
impl RemapOptions {
pub fn new() -> Self {
Self::default()
}
pub fn may_move(mut self, may_move: bool) -> Self {
self.may_move = may_move;
self
}
pub(crate) fn into_flags(self) -> rustix::mm::MremapFlags {
if self.may_move {
rustix::mm::MremapFlags::MAYMOVE
} else {
rustix::mm::MremapFlags::empty()
}
}
}
#[cfg(test)]
mod test {
#[cfg(unix)]
use crate::advice::{Advice, UncheckedAdvice};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, BorrowedFd};
#[cfg(windows)]
use std::os::windows::fs::OpenOptionsExt;
use std::{
fs::{File, OpenOptions},
io::{Read, Write},
mem,
};
#[cfg(windows)]
const GENERIC_ALL: u32 = 0x10000000;
use super::{Mmap, MmapMut, MmapOptions, MmapRaw};
#[test]
fn map_file() {
let expected_len = 128;
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(expected_len as u64).unwrap();
let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
let len = mmap.len();
assert_eq!(expected_len, len);
let zeros = vec![0; len];
let incr: Vec<u8> = (0..len as u8).collect();
assert_eq!(&zeros[..], &mmap[..]);
(&mut mmap[..]).write_all(&incr[..]).unwrap();
assert_eq!(&incr[..], &mmap[..]);
}
#[test]
#[cfg(unix)]
fn map_fd() {
let expected_len = 128;
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(expected_len as u64).unwrap();
let raw_fd = file.as_raw_fd();
let mut mmap = unsafe { MmapMut::map_mut(BorrowedFd::borrow_raw(raw_fd)).unwrap() };
let len = mmap.len();
assert_eq!(expected_len, len);
let zeros = vec![0; len];
let incr: Vec<u8> = (0..len as u8).collect();
assert_eq!(&zeros[..], &mmap[..]);
(&mut mmap[..]).write_all(&incr[..]).unwrap();
assert_eq!(&incr[..], &mmap[..]);
}
#[test]
fn map_empty_file() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
let mmap = unsafe { Mmap::map(&file).unwrap() };
assert!(mmap.is_empty());
assert_eq!(mmap.as_ptr().align_offset(mem::size_of::<usize>()), 0);
let mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
assert!(mmap.is_empty());
assert_eq!(mmap.as_ptr().align_offset(mem::size_of::<usize>()), 0);
}
#[test]
fn map_anon() {
let expected_len = 128;
let mut mmap = MmapMut::map_anon(expected_len).unwrap();
let len = mmap.len();
assert_eq!(expected_len, len);
let zeros = vec![0; len];
let incr: Vec<u8> = (0..len as u8).collect();
assert_eq!(&zeros[..], &mmap[..]);
(&mut mmap[..]).write_all(&incr[..]).unwrap();
assert_eq!(&incr[..], &mmap[..]);
}
#[test]
fn map_anon_zero_len() {
assert!(MmapOptions::new().map_anon().unwrap().is_empty());
}
#[test]
#[cfg(target_pointer_width = "32")]
fn map_anon_len_overflow() {
let res = MmapMut::map_anon(0x80000000);
assert_eq!(
res.unwrap_err().to_string(),
"memory map length overflows isize"
);
}
#[test]
fn file_write() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(128).unwrap();
let write = b"abc123";
let mut read = [0u8; 6];
let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
(&mut mmap[..]).write_all(write).unwrap();
mmap.flush().unwrap();
file.read_exact(&mut read).unwrap();
assert_eq!(write, &read);
}
#[test]
fn flush_range() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(128).unwrap();
let write = b"abc123";
let mut mmap = unsafe {
MmapOptions::new()
.offset(2)
.len(write.len())
.map_mut(&file)
.unwrap()
};
(&mut mmap[..]).write_all(write).unwrap();
mmap.flush_async_range(0, write.len()).unwrap();
mmap.flush_range(0, write.len()).unwrap();
}
#[test]
fn flush_range_rejects_out_of_bounds() {
let mmap = MmapMut::map_anon(128).unwrap();
let err = mmap.flush_range(0, 129).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let err = mmap.flush_range(64, 65).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let err = mmap.flush_range(usize::MAX, 1).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let err = mmap.flush_async_range(0, 129).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let err = mmap.flush_range(129, 0).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
mmap.flush_range(0, 128).unwrap();
mmap.flush_range(128, 0).unwrap();
mmap.flush_range(64, 0).unwrap();
mmap.flush_range(0, 0).unwrap();
mmap.flush_async_range(128, 0).unwrap();
}
#[test]
fn raw_flush_range_rejects_out_of_bounds() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap_raw_range");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(64).unwrap();
let mmap = MmapRaw::map_raw(&file).unwrap();
let err = mmap.flush_range(0, 65).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
let err = mmap.flush_async_range(usize::MAX, 1).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
#[cfg(unix)]
fn advise_range_rejects_out_of_bounds() {
let mmap = MmapMut::map_anon(128).unwrap();
let err = mmap.advise_range(Advice::Normal, 0, 129).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
fn map_copy() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(128).unwrap();
let nulls = b"\0\0\0\0\0\0";
let write = b"abc123";
let mut read = [0u8; 6];
let mut mmap = unsafe { MmapOptions::new().map_copy(&file).unwrap() };
(&mut mmap[..]).write_all(write).unwrap();
mmap.flush().unwrap();
(&mmap[..]).read_exact(&mut read).unwrap();
assert_eq!(write, &read);
file.read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
let mmap2 = unsafe { MmapOptions::new().map(&file).unwrap() };
(&mmap2[..]).read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
}
#[test]
fn map_copy_read_only() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(128).unwrap();
let nulls = b"\0\0\0\0\0\0";
let mut read = [0u8; 6];
let mmap = unsafe { MmapOptions::new().map_copy_read_only(&file).unwrap() };
(&mmap[..]).read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
let mmap2 = unsafe { MmapOptions::new().map(&file).unwrap() };
(&mmap2[..]).read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
}
#[test]
fn map_offset() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
let offset = u64::from(u32::MAX) + 2;
let len = 5432;
file.set_len(offset + len as u64).unwrap();
let mmap = unsafe { MmapOptions::new().offset(offset).map_mut(&file).unwrap() };
assert_eq!(len, mmap.len());
let mut mmap = unsafe {
MmapOptions::new()
.offset(offset)
.len(len)
.map_mut(&file)
.unwrap()
};
assert_eq!(len, mmap.len());
let zeros = vec![0; len];
let incr: Vec<_> = (0..len).map(|i| i as u8).collect();
assert_eq!(&zeros[..], &mmap[..]);
(&mut mmap[..]).write_all(&incr[..]).unwrap();
assert_eq!(&incr[..], &mmap[..]);
}
#[test]
fn index() {
let mut mmap = MmapMut::map_anon(128).unwrap();
mmap[0] = 42;
assert_eq!(42, mmap[0]);
}
#[test]
fn sync_send() {
fn is_sync_send<T>(_val: T)
where
T: Sync + Send,
{
}
let mmap = MmapMut::map_anon(129).unwrap();
is_sync_send(mmap);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn jit_x86(mut mmap: MmapMut) {
mmap[0] = 0xB8; mmap[1] = 0xAB;
mmap[2] = 0x00;
mmap[3] = 0x00;
mmap[4] = 0x00;
mmap[5] = 0xC3;
let mmap = mmap.make_exec().expect("make_exec");
let jitfn: extern "C" fn() -> u8 = unsafe { mem::transmute(mmap.as_ptr()) };
assert_eq!(jitfn(), 0xab);
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn jit_x86_anon() {
jit_x86(MmapMut::map_anon(4096).unwrap());
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn jit_x86_file() {
let tempdir = tempfile::tempdir().unwrap();
let mut options = OpenOptions::new();
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let file = options
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(tempdir.path().join("jit_x86"))
.expect("open");
file.set_len(4096).expect("set_len");
jit_x86(unsafe { MmapMut::map_mut(&file).expect("map_mut") });
}
#[test]
fn mprotect_file() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let mut options = OpenOptions::new();
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let mut file = options
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.expect("open");
file.set_len(256_u64).expect("set_len");
let mmap = unsafe { MmapMut::map_mut(&file).expect("map_mut") };
let mmap = mmap.make_read_only().expect("make_read_only");
let mut mmap = mmap.make_mut().expect("make_mut");
let write = b"abc123";
let mut read = [0u8; 6];
(&mut mmap[..]).write_all(write).unwrap();
mmap.flush().unwrap();
(&mmap[..]).read_exact(&mut read).unwrap();
assert_eq!(write, &read);
file.read_exact(&mut read).unwrap();
assert_eq!(write, &read);
let mmap2 = unsafe { MmapOptions::new().map(&file).unwrap() };
(&mmap2[..]).read_exact(&mut read).unwrap();
assert_eq!(write, &read);
let mmap = mmap.make_exec().expect("make_exec");
drop(mmap);
}
#[test]
fn mprotect_copy() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let mut options = OpenOptions::new();
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let mut file = options
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.expect("open");
file.set_len(256_u64).expect("set_len");
let mmap = unsafe { MmapOptions::new().map_copy(&file).expect("map_mut") };
let mmap = mmap.make_read_only().expect("make_read_only");
let mut mmap = mmap.make_mut().expect("make_mut");
let nulls = b"\0\0\0\0\0\0";
let write = b"abc123";
let mut read = [0u8; 6];
(&mut mmap[..]).write_all(write).unwrap();
mmap.flush().unwrap();
(&mmap[..]).read_exact(&mut read).unwrap();
assert_eq!(write, &read);
file.read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
let mmap2 = unsafe { MmapOptions::new().map(&file).unwrap() };
(&mmap2[..]).read_exact(&mut read).unwrap();
assert_eq!(nulls, &read);
let mmap = mmap.make_exec().expect("make_exec");
drop(mmap);
}
#[test]
fn mprotect_anon() {
let mmap = MmapMut::map_anon(256).expect("map_mut");
let mmap = mmap.make_read_only().expect("make_read_only");
let mmap = mmap.make_mut().expect("make_mut");
let mmap = mmap.make_exec().expect("make_exec");
drop(mmap);
}
#[test]
fn raw() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmapraw");
let mut options = OpenOptions::new();
let mut file = options
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.expect("open");
file.write_all(b"abc123").unwrap();
let mmap = MmapOptions::new().map_raw(&file).unwrap();
assert_eq!(mmap.len(), 6);
assert!(!mmap.as_ptr().is_null());
assert_eq!(unsafe { std::ptr::read(mmap.as_ptr()) }, b'a');
}
#[test]
fn raw_read_only() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmaprawro");
File::create(&path).unwrap().write_all(b"abc123").unwrap();
let mmap = MmapOptions::new()
.map_raw_read_only(File::open(&path).unwrap())
.unwrap();
assert_eq!(mmap.len(), 6);
assert!(!mmap.as_ptr().is_null());
assert_eq!(unsafe { std::ptr::read(mmap.as_ptr()) }, b'a');
}
#[test]
#[cfg(feature = "stable_deref_trait")]
fn owning_ref() {
let mut map = MmapMut::map_anon(128).unwrap();
map[10] = 42;
let owning = owning_ref::OwningRef::new(map);
let sliced = owning.map(|map| &map[10..20]);
assert_eq!(42, sliced[0]);
let map = sliced.into_owner().make_read_only().unwrap();
let owning = owning_ref::OwningRef::new(map);
let sliced = owning.map(|map| &map[10..20]);
assert_eq!(42, sliced[0]);
}
#[test]
#[cfg(unix)]
fn advise() {
let expected_len = 128;
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap_advise");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(expected_len as u64).unwrap();
let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
mmap
.advise(Advice::Random)
.expect("mmap advising should be supported on unix");
let len = mmap.len();
assert_eq!(expected_len, len);
let zeros = vec![0; len];
let incr: Vec<u8> = (0..len as u8).collect();
assert_eq!(&zeros[..], &mmap[..]);
mmap
.advise_range(Advice::Sequential, 0, mmap.len())
.expect("mmap advising should be supported on unix");
(&mut mmap[..]).write_all(&incr[..]).unwrap();
assert_eq!(&incr[..], &mmap[..]);
let mmap = unsafe { Mmap::map(&file).unwrap() };
mmap
.advise(Advice::Random)
.expect("mmap advising should be supported on unix");
assert_eq!(&incr[..], &mmap[..]);
}
#[test]
#[cfg(target_os = "linux")]
fn advise_writes_unsafely() {
let page_size = rustix::param::page_size();
let mut mmap = MmapMut::map_anon(page_size).unwrap();
mmap.as_mut().fill(255);
let mmap = mmap.make_read_only().unwrap();
let a = mmap.as_ref()[0];
unsafe {
mmap
.unchecked_advise(crate::UncheckedAdvice::DontNeed)
.unwrap();
}
let b = mmap.as_ref()[0];
assert_eq!(a, 255);
assert_eq!(b, 0);
}
#[test]
#[cfg(target_os = "linux")]
fn advise_writes_unsafely_to_part_of_map() {
let page_size = rustix::param::page_size();
let mut mmap = MmapMut::map_anon(2 * page_size).unwrap();
mmap.as_mut().fill(255);
let mmap = mmap.make_read_only().unwrap();
let a = mmap.as_ref()[0];
let b = mmap.as_ref()[page_size];
unsafe {
mmap
.unchecked_advise_range(crate::UncheckedAdvice::DontNeed, page_size, page_size)
.unwrap();
}
let c = mmap.as_ref()[0];
let d = mmap.as_ref()[page_size];
assert_eq!(a, 255);
assert_eq!(b, 255);
assert_eq!(c, 255);
assert_eq!(d, 0);
}
#[cfg(target_os = "linux")]
fn is_locked() -> bool {
let status =
&std::fs::read_to_string("/proc/self/status").expect("/proc/self/status should be available");
for line in status.lines() {
if line.starts_with("VmLck:") {
let numbers = line.replace(|c: char| !c.is_ascii_digit(), "");
return numbers != "0";
}
}
panic!("cannot get VmLck information")
}
#[test]
#[cfg(unix)]
fn lock() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap_lock");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.unwrap();
file.set_len(128).unwrap();
let mmap = unsafe { Mmap::map(&file).unwrap() };
#[cfg(target_os = "linux")]
assert!(!is_locked());
mmap.lock().expect("mmap lock should be supported on unix");
#[cfg(target_os = "linux")]
assert!(is_locked());
mmap
.lock()
.expect("mmap lock again should not cause problems");
#[cfg(target_os = "linux")]
assert!(is_locked());
mmap
.unlock()
.expect("mmap unlock should be supported on unix");
#[cfg(target_os = "linux")]
assert!(!is_locked());
mmap
.unlock()
.expect("mmap unlock again should not cause problems");
#[cfg(target_os = "linux")]
assert!(!is_locked());
}
#[test]
#[cfg(target_os = "linux")]
fn remap_grow() {
use crate::RemapOptions;
let initial_len = 128;
let final_len = 2000;
let zeros = vec![0u8; final_len];
let incr: Vec<u8> = (0..final_len).map(|v| v as u8).collect();
let file = tempfile::tempfile().unwrap();
file.set_len(final_len as u64).unwrap();
let mut mmap = unsafe { MmapOptions::new().len(initial_len).map_mut(&file).unwrap() };
assert_eq!(mmap.len(), initial_len);
assert_eq!(&mmap[..], &zeros[..initial_len]);
unsafe {
mmap
.remap(final_len, RemapOptions::new().may_move(true))
.unwrap();
}
assert_eq!(mmap.len(), final_len);
assert_eq!(&mmap[..], &zeros);
mmap.copy_from_slice(&incr);
}
#[test]
#[cfg(target_os = "linux")]
fn remap_shrink() {
use crate::RemapOptions;
let initial_len = 20000;
let final_len = 400;
let incr: Vec<u8> = (0..final_len).map(|v| v as u8).collect();
let file = tempfile::tempfile().unwrap();
file.set_len(initial_len as u64).unwrap();
let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
assert_eq!(mmap.len(), initial_len);
unsafe { mmap.remap(final_len, RemapOptions::new()).unwrap() };
assert_eq!(mmap.len(), final_len);
mmap.copy_from_slice(&incr);
}
#[test]
#[cfg(target_os = "linux")]
#[cfg(target_pointer_width = "32")]
fn remap_len_overflow() {
use crate::RemapOptions;
let file = tempfile::tempfile().unwrap();
file.set_len(1024).unwrap();
let mut mmap = unsafe { MmapOptions::new().len(1024).map(&file).unwrap() };
let res = unsafe { mmap.remap(0x80000000, RemapOptions::new().may_move(true)) };
assert_eq!(
res.unwrap_err().to_string(),
"memory map length overflows isize"
);
assert_eq!(mmap.len(), 1024);
}
#[test]
#[cfg(target_os = "linux")]
fn remap_with_offset() {
use crate::RemapOptions;
let offset = 77;
let initial_len = 128;
let final_len = 2000;
let zeros = vec![0u8; final_len];
let incr: Vec<u8> = (0..final_len).map(|v| v as u8).collect();
let file = tempfile::tempfile().unwrap();
file.set_len(final_len as u64 + offset).unwrap();
let mut mmap = unsafe {
MmapOptions::new()
.len(initial_len)
.offset(offset)
.map_mut(&file)
.unwrap()
};
assert_eq!(mmap.len(), initial_len);
assert_eq!(&mmap[..], &zeros[..initial_len]);
unsafe {
mmap
.remap(final_len, RemapOptions::new().may_move(true))
.unwrap();
}
assert_eq!(mmap.len(), final_len);
assert_eq!(&mmap[..], &zeros);
mmap.copy_from_slice(&incr);
}
#[test]
fn mmap_options_builder_setters_coverage() {
let mut opts = MmapOptions::new();
opts
.offset(0)
.len(4096)
.stack()
.huge(Some(21))
.huge(None)
.populate()
.no_reserve_swap();
}
#[test]
fn map_options_offset_past_file_len() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("small");
File::create(&path).unwrap().write_all(b"tiny").unwrap();
let file = File::open(&path).unwrap();
let err = unsafe { MmapOptions::new().offset(1024).map(&file) }.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn as_ref_as_mut_debug_impls() {
let mut m = MmapMut::map_anon(32).unwrap();
<MmapMut as AsMut<[u8]>>::as_mut(&mut m)[0] = 7;
assert_eq!(<MmapMut as AsRef<[u8]>>::as_ref(&m)[0], 7);
let dbg = format!("{m:?}");
assert!(dbg.starts_with("MmapMut"));
let mmap = m.make_read_only().unwrap();
assert_eq!(<Mmap as AsRef<[u8]>>::as_ref(&mmap).len(), 32);
let dbg = format!("{mmap:?}");
assert!(dbg.starts_with("Mmap"));
let raw: MmapRaw = mmap.into();
let dbg = format!("{raw:?}");
assert!(dbg.starts_with("MmapRaw"));
}
#[test]
fn mmap_raw_api_coverage() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("raw_api");
let mut options = OpenOptions::new();
options.read(true).write(true).create(true).truncate(true);
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let file = options.open(&path).unwrap();
file.set_len(256).unwrap();
let _ = MmapRaw::map_raw(&file).unwrap();
let mmap = MmapOptions::new().map_raw(&file).unwrap();
assert_eq!(mmap.len(), 256);
assert!(!mmap.as_ptr().is_null());
assert!(!mmap.as_mut_ptr().is_null());
mmap.flush().unwrap();
mmap.flush_async().unwrap();
mmap.flush_range(0, 64).unwrap();
mmap.flush_async_range(64, 64).unwrap();
#[cfg(unix)]
{
mmap.advise(Advice::Random).unwrap();
mmap
.advise_range(Advice::Sequential, 0, mmap.len())
.unwrap();
unsafe {
mmap.unchecked_advise(UncheckedAdvice::DontNeed).unwrap();
}
unsafe {
mmap
.unchecked_advise_range(UncheckedAdvice::DontNeed, 0, mmap.len())
.unwrap();
}
mmap.lock().unwrap();
mmap.unlock().unwrap();
}
let ro = MmapOptions::new()
.map_raw_read_only(File::open(&path).unwrap())
.unwrap();
assert_eq!(ro.len(), 256);
}
#[test]
fn mmap_mut_flush_async_coverage() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("flush_async");
let mut options = OpenOptions::new();
options.read(true).write(true).create(true).truncate(true);
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let file = options.open(&path).unwrap();
file.set_len(128).unwrap();
let mut mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
mmap[0] = 1;
mmap.flush_async().unwrap();
mmap.flush_async_range(0, 16).unwrap();
}
#[test]
#[cfg(unix)]
fn mmap_mut_advise_coverage() {
let mmap = MmapMut::map_anon(128).unwrap();
mmap.advise_range(Advice::Random, 0, 128).unwrap();
unsafe {
mmap.unchecked_advise(UncheckedAdvice::DontNeed).unwrap();
}
unsafe {
mmap
.unchecked_advise_range(UncheckedAdvice::DontNeed, 0, 128)
.unwrap();
}
}
#[test]
fn populate_no_reserve_map_variants() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("variants");
let mut options = OpenOptions::new();
options.read(true).write(true).create(true).truncate(true);
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let file = options.open(&path).unwrap();
file.set_len(4096).unwrap();
unsafe {
let _m = MmapOptions::new()
.populate()
.no_reserve_swap()
.map(&file)
.unwrap();
let _m = MmapOptions::new()
.populate()
.no_reserve_swap()
.map_mut(&file)
.unwrap();
let _m = MmapOptions::new()
.populate()
.no_reserve_swap()
.map_copy(&file)
.unwrap();
let _m = MmapOptions::new()
.populate()
.no_reserve_swap()
.map_copy_read_only(&file)
.unwrap();
}
let _m = MmapOptions::new()
.stack()
.populate()
.no_reserve_swap()
.len(4096)
.map_anon()
.unwrap();
}
#[test]
#[cfg(target_os = "linux")]
fn advise_is_supported_linux_variants() {
for a in [
Advice::Normal,
Advice::Random,
Advice::Sequential,
Advice::WillNeed,
Advice::DontFork,
Advice::DoFork,
Advice::Mergeable,
Advice::Unmergeable,
Advice::HugePage,
Advice::NoHugePage,
Advice::DontDump,
Advice::DoDump,
Advice::HwPoison,
Advice::PopulateRead,
Advice::PopulateWrite,
] {
let _ = a.is_supported();
}
for a in [
UncheckedAdvice::DontNeed,
UncheckedAdvice::Free,
UncheckedAdvice::Remove,
] {
let _ = a.is_supported();
}
}
#[test]
#[cfg(target_vendor = "apple")]
fn apple_advise_variants() {
let mut m = MmapMut::map_anon(4096).unwrap();
m.as_mut().fill(1);
let m = m.make_read_only().unwrap();
for a in [
Advice::Normal,
Advice::Random,
Advice::Sequential,
Advice::WillNeed,
Advice::ZeroWiredPages,
] {
let _ = m.advise(a);
}
unsafe {
let _ = m.unchecked_advise(UncheckedAdvice::Free);
let _ = m.unchecked_advise(UncheckedAdvice::FreeReusable);
let _ = m.unchecked_advise(UncheckedAdvice::FreeReuse);
let _ = m.unchecked_advise(UncheckedAdvice::DontNeed);
}
}
#[test]
fn map_exec_coverage() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap_exec");
let mut options = OpenOptions::new();
options.read(true).write(true).create(true).truncate(true);
#[cfg(windows)]
options.access_mode(GENERIC_ALL);
let file = options.open(&path).unwrap();
file.set_len(4096).unwrap();
let file = File::open(&path).unwrap();
let _ = unsafe {
MmapOptions::new()
.populate()
.no_reserve_swap()
.map_exec(&file)
};
let _ = unsafe { MmapOptions::new().map_exec(&file) };
}
#[test]
#[cfg(unix)]
fn mmap_mut_lock_and_raw_conversion() {
let mut mmap = MmapMut::map_anon(4096).unwrap();
mmap[0] = 42;
mmap.lock().unwrap();
mmap.unlock().unwrap();
let raw: MmapRaw = mmap.into();
assert_eq!(raw.len(), 4096);
}
#[test]
#[cfg(unix)]
fn mmap_advise_range_coverage() {
let mmap = MmapMut::map_anon(4096).unwrap().make_read_only().unwrap();
mmap.advise_range(Advice::Random, 0, 4096).unwrap();
unsafe {
mmap
.unchecked_advise_range(UncheckedAdvice::DontNeed, 0, 4096)
.unwrap();
}
}
#[test]
fn map_anon_huge_flag_coverage() {
let _ = MmapOptions::new()
.huge(Some(21))
.len(2 * 1024 * 1024)
.map_anon();
}
#[test]
fn validate_len_overflow() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("small");
File::create(&path).unwrap().write_all(b"hi").unwrap();
let file = File::open(&path).unwrap();
let err = unsafe { MmapOptions::new().len(usize::MAX).map(&file) }.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
#[cfg(target_os = "linux")]
fn mmap_raw_remap_grow() {
use crate::RemapOptions;
let file = tempfile::tempfile().unwrap();
file.set_len(8192).unwrap();
let mut mmap = MmapOptions::new().len(4096).map_raw(&file).unwrap();
assert_eq!(mmap.len(), 4096);
unsafe {
mmap
.remap(8192, RemapOptions::new().may_move(true))
.unwrap();
}
assert_eq!(mmap.len(), 8192);
}
}