Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,15 @@ where
/// [`map_copy()`]: MmapOptions::map_copy()
/// [`map_copy_read_only()`]: MmapOptions::map_copy_read_only()
#[derive(Clone, Debug, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct MmapOptions {
offset: u64,
len: Option<usize>,
huge: Option<u8>,
stack: bool,
populate: bool,
no_reserve_swap: bool,
no_probe_handle: bool,
}

impl MmapOptions {
Expand Down Expand Up @@ -392,6 +394,44 @@ impl MmapOptions {
self
}

/// Do not probe the file handle for the widest supported protection
/// level when creating the mapping.
///
/// This option only has an effect on Windows. There, by default,
/// creating a mapping issues up to two extra `CreateFileMappingW` calls
/// to discover whether the handle supports write and/or execute access,
/// so that the underlying section is created with the widest protection
/// the handle supports and the mapping can later be transitioned, e.g.
/// with [`Mmap::make_mut()`].
///
/// With this option set, the section and view are created with exactly
/// the protection the mapping type requires (e.g. `PAGE_READONLY` and
/// `FILE_MAP_READ` for [`map()`][MmapOptions::map]). This guarantees a
/// read-only mapping never creates a writable section on the file, but
/// transitioning the returned mapping to a wider protection will fail.
///
/// # Example
///
/// ```
/// use memmap2::MmapOptions;
/// use std::fs::File;
///
/// # fn main() -> std::io::Result<()> {
/// let file = File::open("LICENSE-MIT")?;
///
/// let mmap = unsafe {
/// MmapOptions::new().no_probe_handle().map(&file)?
/// };
///
/// assert_eq!(&b"Copyright"[..], &mmap[..9]);
/// # Ok(())
/// # }
/// ```
pub fn no_probe_handle(&mut self) -> &mut Self {
self.no_probe_handle = true;
self
}

/// Creates a read-only memory map backed by a file.
///
/// # Safety
Expand Down Expand Up @@ -435,6 +475,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| Mmap { inner })
}
Expand All @@ -460,6 +501,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| Mmap { inner })
}
Expand Down Expand Up @@ -509,6 +551,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| MmapMut { inner })
}
Expand Down Expand Up @@ -552,6 +595,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| MmapMut { inner })
}
Expand Down Expand Up @@ -599,6 +643,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| Mmap { inner })
}
Expand Down Expand Up @@ -648,6 +693,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| MmapRaw { inner })
}
Expand All @@ -671,6 +717,7 @@ impl MmapOptions {
self.offset,
self.populate,
self.no_reserve_swap,
!self.no_probe_handle,
)
.map(|inner| MmapRaw { inner })
}
Expand Down Expand Up @@ -1850,6 +1897,34 @@ mod test {
assert_eq!(nulls, &read);
}

#[test]
fn map_without_handle_probing() {
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.write_all(b"exact contents").unwrap();

let readonly = File::open(&path).unwrap();
let mmap = unsafe { MmapOptions::new().no_probe_handle().map(&readonly).unwrap() };
assert_eq!(b"exact contents", &mmap[..]);

// Without probing, the section is created strictly read-only even
// for a writable handle, so the mapping cannot be transitioned to
// writable.
#[cfg(windows)]
{
let mmap = unsafe { MmapOptions::new().no_probe_handle().map(&file).unwrap() };
assert!(mmap.make_mut().is_err());
}
}

#[test]
fn map_offset() {
let tempdir = tempfile::tempdir().unwrap();
Expand Down
23 changes: 19 additions & 4 deletions src/stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,33 @@ impl MmapInner {
Err(io::ErrorKind::Unsupported.into())
}

pub fn map(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result<MmapInner> {
pub fn map(_: usize, _: &File, _: u64, _: bool, _: bool, _: bool) -> io::Result<MmapInner> {
MmapInner::new()
}

pub fn map_exec(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result<MmapInner> {
pub fn map_exec(
_: usize,
_: &File,
_: u64,
_: bool,
_: bool,
_: bool,
) -> io::Result<MmapInner> {
MmapInner::new()
}

pub fn map_mut(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result<MmapInner> {
pub fn map_mut(_: usize, _: &File, _: u64, _: bool, _: bool, _: bool) -> io::Result<MmapInner> {
MmapInner::new()
}

pub fn map_copy(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result<MmapInner> {
pub fn map_copy(
_: usize,
_: &File,
_: u64,
_: bool,
_: bool,
_: bool,
) -> io::Result<MmapInner> {
MmapInner::new()
}

Expand All @@ -35,6 +49,7 @@ impl MmapInner {
_: u64,
_: bool,
_: bool,
_: bool,
) -> io::Result<MmapInner> {
MmapInner::new()
}
Expand Down
5 changes: 5 additions & 0 deletions src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ impl MmapInner {
offset: u64,
populate: bool,
no_reserve: bool,
_probe_handle: bool,
) -> io::Result<MmapInner> {
let populate = if populate { MAP_POPULATE } else { 0 };
let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
Expand All @@ -266,6 +267,7 @@ impl MmapInner {
offset: u64,
populate: bool,
no_reserve: bool,
_probe_handle: bool,
) -> io::Result<MmapInner> {
let populate = if populate { MAP_POPULATE } else { 0 };
let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
Expand All @@ -284,6 +286,7 @@ impl MmapInner {
offset: u64,
populate: bool,
no_reserve: bool,
_probe_handle: bool,
) -> io::Result<MmapInner> {
let populate = if populate { MAP_POPULATE } else { 0 };
let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
Expand All @@ -302,6 +305,7 @@ impl MmapInner {
offset: u64,
populate: bool,
no_reserve: bool,
_probe_handle: bool,
) -> io::Result<MmapInner> {
let populate = if populate { MAP_POPULATE } else { 0 };
let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
Expand All @@ -320,6 +324,7 @@ impl MmapInner {
offset: u64,
populate: bool,
no_reserve: bool,
_probe_handle: bool,
) -> io::Result<MmapInner> {
let populate = if populate { MAP_POPULATE } else { 0 };
let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
Expand Down
39 changes: 28 additions & 11 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::fs::File;
use std::mem::ManuallyDrop;
use std::os::raw::c_void;
use std::os::windows::io::{FromRawHandle, RawHandle};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{io, mem, ptr};

type BOOL = i32;
Expand Down Expand Up @@ -234,9 +235,10 @@ impl MmapInner {
offset: u64,
_populate: bool,
_no_reserve: bool,
probe_handle: bool,
) -> io::Result<MmapInner> {
let write = protection_supported(handle, PAGE_READWRITE);
let exec = protection_supported(handle, PAGE_EXECUTE_READ);
let write = probe_handle && protection_supported(handle, PAGE_READWRITE);
let exec = probe_handle && protection_supported(handle, PAGE_EXECUTE_READ);
let mut access = FILE_MAP_READ;
let protection = match (write, exec) {
(true, true) => {
Expand Down Expand Up @@ -267,8 +269,9 @@ impl MmapInner {
offset: u64,
_populate: bool,
_no_reserve: bool,
probe_handle: bool,
) -> io::Result<MmapInner> {
let write = protection_supported(handle, PAGE_READWRITE);
let write = probe_handle && protection_supported(handle, PAGE_READWRITE);
let mut access = FILE_MAP_READ | FILE_MAP_EXECUTE;
let protection = if write {
access |= FILE_MAP_WRITE;
Expand All @@ -290,8 +293,9 @@ impl MmapInner {
offset: u64,
_populate: bool,
_no_reserve: bool,
probe_handle: bool,
) -> io::Result<MmapInner> {
let exec = protection_supported(handle, PAGE_EXECUTE_READ);
let exec = probe_handle && protection_supported(handle, PAGE_EXECUTE_READ);
let mut access = FILE_MAP_READ | FILE_MAP_WRITE;
let protection = if exec {
access |= FILE_MAP_EXECUTE;
Expand All @@ -313,8 +317,9 @@ impl MmapInner {
offset: u64,
_populate: bool,
_no_reserve: bool,
probe_handle: bool,
) -> io::Result<MmapInner> {
let exec = protection_supported(handle, PAGE_EXECUTE_READWRITE);
let exec = probe_handle && protection_supported(handle, PAGE_EXECUTE_READWRITE);
let mut access = FILE_MAP_COPY;
let protection = if exec {
access |= FILE_MAP_EXECUTE;
Expand All @@ -336,9 +341,10 @@ impl MmapInner {
offset: u64,
_populate: bool,
_no_reserve: bool,
probe_handle: bool,
) -> io::Result<MmapInner> {
let write = protection_supported(handle, PAGE_READWRITE);
let exec = protection_supported(handle, PAGE_EXECUTE_READ);
let write = probe_handle && protection_supported(handle, PAGE_READWRITE);
let exec = probe_handle && protection_supported(handle, PAGE_EXECUTE_READ);
let mut access = FILE_MAP_COPY;
let protection = if exec {
access |= FILE_MAP_EXECUTE;
Expand Down Expand Up @@ -528,10 +534,21 @@ fn protection_supported(handle: RawHandle, protection: DWORD) -> bool {
}

fn allocation_granularity() -> usize {
unsafe {
let mut info = mem::zeroed();
GetSystemInfo(&mut info);
info.dwAllocationGranularity as usize
static ALLOCATION_GRANULARITY: AtomicUsize = AtomicUsize::new(0);

match ALLOCATION_GRANULARITY.load(Ordering::Relaxed) {
0 => {
let allocation_granularity = unsafe {
let mut info = mem::zeroed();
GetSystemInfo(&mut info);
info.dwAllocationGranularity as usize
};

ALLOCATION_GRANULARITY.store(allocation_granularity, Ordering::Relaxed);

allocation_granularity
}
allocation_granularity => allocation_granularity,
}
}

Expand Down