diff --git a/src/lib.rs b/src/lib.rs index f0c7605a..2a83d3e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,6 +153,7 @@ 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, @@ -160,6 +161,7 @@ pub struct MmapOptions { stack: bool, populate: bool, no_reserve_swap: bool, + no_probe_handle: bool, } impl MmapOptions { @@ -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 @@ -435,6 +475,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| Mmap { inner }) } @@ -460,6 +501,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| Mmap { inner }) } @@ -509,6 +551,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| MmapMut { inner }) } @@ -552,6 +595,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| MmapMut { inner }) } @@ -599,6 +643,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| Mmap { inner }) } @@ -648,6 +693,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| MmapRaw { inner }) } @@ -671,6 +717,7 @@ impl MmapOptions { self.offset, self.populate, self.no_reserve_swap, + !self.no_probe_handle, ) .map(|inner| MmapRaw { inner }) } @@ -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(); diff --git a/src/stub.rs b/src/stub.rs index aa2e527f..e103fa08 100644 --- a/src/stub.rs +++ b/src/stub.rs @@ -13,19 +13,33 @@ impl MmapInner { Err(io::ErrorKind::Unsupported.into()) } - pub fn map(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result { + pub fn map(_: usize, _: &File, _: u64, _: bool, _: bool, _: bool) -> io::Result { MmapInner::new() } - pub fn map_exec(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result { + pub fn map_exec( + _: usize, + _: &File, + _: u64, + _: bool, + _: bool, + _: bool, + ) -> io::Result { MmapInner::new() } - pub fn map_mut(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result { + pub fn map_mut(_: usize, _: &File, _: u64, _: bool, _: bool, _: bool) -> io::Result { MmapInner::new() } - pub fn map_copy(_: usize, _: &File, _: u64, _: bool, _: bool) -> io::Result { + pub fn map_copy( + _: usize, + _: &File, + _: u64, + _: bool, + _: bool, + _: bool, + ) -> io::Result { MmapInner::new() } @@ -35,6 +49,7 @@ impl MmapInner { _: u64, _: bool, _: bool, + _: bool, ) -> io::Result { MmapInner::new() } diff --git a/src/unix.rs b/src/unix.rs index 6c63e108..32091ae2 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -248,6 +248,7 @@ impl MmapInner { offset: u64, populate: bool, no_reserve: bool, + _probe_handle: bool, ) -> io::Result { let populate = if populate { MAP_POPULATE } else { 0 }; let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 }; @@ -266,6 +267,7 @@ impl MmapInner { offset: u64, populate: bool, no_reserve: bool, + _probe_handle: bool, ) -> io::Result { let populate = if populate { MAP_POPULATE } else { 0 }; let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 }; @@ -284,6 +286,7 @@ impl MmapInner { offset: u64, populate: bool, no_reserve: bool, + _probe_handle: bool, ) -> io::Result { let populate = if populate { MAP_POPULATE } else { 0 }; let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 }; @@ -302,6 +305,7 @@ impl MmapInner { offset: u64, populate: bool, no_reserve: bool, + _probe_handle: bool, ) -> io::Result { let populate = if populate { MAP_POPULATE } else { 0 }; let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 }; @@ -320,6 +324,7 @@ impl MmapInner { offset: u64, populate: bool, no_reserve: bool, + _probe_handle: bool, ) -> io::Result { let populate = if populate { MAP_POPULATE } else { 0 }; let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 }; diff --git a/src/windows.rs b/src/windows.rs index a9f9a239..f037b1e2 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -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; @@ -234,9 +235,10 @@ impl MmapInner { offset: u64, _populate: bool, _no_reserve: bool, + probe_handle: bool, ) -> io::Result { - 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) => { @@ -267,8 +269,9 @@ impl MmapInner { offset: u64, _populate: bool, _no_reserve: bool, + probe_handle: bool, ) -> io::Result { - 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; @@ -290,8 +293,9 @@ impl MmapInner { offset: u64, _populate: bool, _no_reserve: bool, + probe_handle: bool, ) -> io::Result { - 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; @@ -313,8 +317,9 @@ impl MmapInner { offset: u64, _populate: bool, _no_reserve: bool, + probe_handle: bool, ) -> io::Result { - 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; @@ -336,9 +341,10 @@ impl MmapInner { offset: u64, _populate: bool, _no_reserve: bool, + probe_handle: bool, ) -> io::Result { - 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; @@ -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, } }