diff --git a/src/lib.rs b/src/lib.rs index 24b6d422..2f8d38d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,8 @@ pub struct MmapOptions { offset: u64, len: Option, stack: bool, + locked: bool, + private: bool, } impl MmapOptions { @@ -164,6 +166,24 @@ impl MmapOptions { self } + /// Configures the memory map to be locked using. + /// + /// This option corresponds to the `MAP_LOCKED` flag on Linux, and has no effect on Window and MacOS. + /// + /// Note this requires privileged access. + pub fn lock(&mut self) -> &mut Self { + self.locked = true; + self + } + + /// Configures the memory map to be private. + /// + /// This option corresponds to the `MAP_PRIVATE` flag on Linux. + pub fn private(&mut self) -> &mut Self { + self.private = true; + self + } + /// Creates a read-only memory map backed by a file. /// /// # Errors @@ -193,7 +213,7 @@ impl MmapOptions { /// # } /// ``` pub unsafe fn map(&self, file: &File) -> Result { - MmapInner::map(self.get_len(file)?, file, self.offset).map(|inner| Mmap { inner: inner }) + MmapInner::map(self.get_len(file)?, file, self.offset, self.locked, self.private).map(|inner| Mmap { inner: inner }) } /// Creates a readable and executable memory map backed by a file. @@ -203,7 +223,7 @@ impl MmapOptions { /// This method returns an error when the underlying system call fails, which can happen for a /// variety of reasons, such as when the file is not open with read permissions. pub unsafe fn map_exec(&self, file: &File) -> Result { - MmapInner::map_exec(self.get_len(file)?, file, self.offset) + MmapInner::map_exec(self.get_len(file)?, file, self.offset, self.locked, self.private) .map(|inner| Mmap { inner: inner }) } @@ -241,7 +261,7 @@ impl MmapOptions { /// # } /// ``` pub unsafe fn map_mut(&self, file: &File) -> Result { - MmapInner::map_mut(self.get_len(file)?, file, self.offset) + MmapInner::map_mut(self.get_len(file)?, file, self.offset, self.locked, self.private) .map(|inner| MmapMut { inner: inner }) } @@ -270,7 +290,7 @@ impl MmapOptions { /// # } /// ``` pub unsafe fn map_copy(&self, file: &File) -> Result { - MmapInner::map_copy(self.get_len(file)?, file, self.offset) + MmapInner::map_copy(self.get_len(file)?, file, self.offset, self.locked) .map(|inner| MmapMut { inner: inner }) } @@ -283,7 +303,7 @@ impl MmapOptions { /// /// This method returns an error when the underlying system call fails. pub fn map_anon(&self) -> Result { - MmapInner::map_anon(self.len.unwrap_or(0), self.stack).map(|inner| MmapMut { inner: inner }) + MmapInner::map_anon(self.len.unwrap_or(0), self.stack, self.locked, self.private).map(|inner| MmapMut { inner: inner }) } } @@ -409,6 +429,26 @@ impl Mmap { self.inner.make_mut()?; Ok(MmapMut { inner: self.inner }) } + + /// Uses `mlock` to lock the whole memory map into RAM. + /// + /// Note this requires privileged access. + #[cfg(unix)] + pub fn mlock(&mut self) -> Result<()> { + self.inner.mlock()?; + + Ok(()) + } + + /// Uses `munlock` to unlock the whole memory map. + /// + /// Note this requires privileged access. + #[cfg(unix)] + pub fn munlock(&mut self) -> Result<()> { + self.inner.munlock()?; + + Ok(()) + } } impl Deref for Mmap { @@ -638,6 +678,26 @@ impl MmapMut { self.inner.make_exec()?; Ok(Mmap { inner: self.inner }) } + + /// Uses `mlock` to lock the whole memory map into RAM. + /// + /// Note this requires privileged access. + #[cfg(unix)] + pub fn mlock(&mut self) -> Result<()> { + self.inner.mlock()?; + + Ok(()) + } + + /// Uses `munlock` to unlock the whole memory map. + /// + /// Note this requires privileged access. + #[cfg(unix)] + pub fn munlock(&mut self) -> Result<()> { + self.inner.munlock()?; + + Ok(()) + } } impl Deref for MmapMut { diff --git a/src/unix.rs b/src/unix.rs index 4838e7e4..e25adccc 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -18,6 +18,21 @@ const MAP_STACK: libc::c_int = libc::MAP_STACK; )))] const MAP_STACK: libc::c_int = 0; + +#[cfg(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "freebsd", + target_os = "android" +))] +const MAP_LOCKED: libc::c_int = libc::MAP_LOCKED; + +#[cfg(not(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "freebsd", + target_os = "android" +)))] +const MAP_LOCKED: libc::c_int = 0; + pub struct MmapInner { ptr: *mut libc::c_void, len: usize, @@ -66,53 +81,63 @@ impl MmapInner { } } - pub fn map(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result { + let locked = if locked { MAP_LOCKED } else { 0 }; + let private = if private { libc::MAP_PRIVATE } else { libc::MAP_SHARED }; MmapInner::new( len, libc::PROT_READ, - libc::MAP_SHARED, + locked | private, file.as_raw_fd(), offset, ) } - pub fn map_exec(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_exec(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result { + let locked = if locked { MAP_LOCKED } else { 0 }; + let private = if private { libc::MAP_PRIVATE } else { libc::MAP_SHARED }; MmapInner::new( len, libc::PROT_READ | libc::PROT_EXEC, - libc::MAP_SHARED, + locked | private, file.as_raw_fd(), offset, ) } - pub fn map_mut(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_mut(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result { + let locked = if locked { MAP_LOCKED } else { 0 }; + let private = if private { libc::MAP_PRIVATE } else { libc::MAP_SHARED }; MmapInner::new( len, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED, + locked | private, file.as_raw_fd(), offset, ) } - pub fn map_copy(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_copy(len: usize, file: &File, offset: u64, locked: bool) -> io::Result { + let locked = if locked { MAP_LOCKED } else { 0 }; + MmapInner::new( len, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE, + libc::MAP_PRIVATE | locked, file.as_raw_fd(), offset, ) } /// Open an anonymous memory map. - pub fn map_anon(len: usize, stack: bool) -> io::Result { + pub fn map_anon(len: usize, stack: bool, locked: bool, private: bool) -> io::Result { let stack = if stack { MAP_STACK } else { 0 }; + let locked = if locked { MAP_LOCKED } else { 0 }; + let private = if private { libc::MAP_PRIVATE } else { libc::MAP_SHARED }; MmapInner::new( len, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED | libc::MAP_ANON | stack, + libc::MAP_ANON | stack | locked | private, -1, 0, ) @@ -188,6 +213,26 @@ impl MmapInner { pub fn len(&self) -> usize { self.len } + + pub fn mlock(&self) -> io::Result<()> { + unsafe { + if libc::mlock(self.ptr, self.len) == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + } + + pub fn munlock(&self) -> io::Result<()> { + unsafe { + if libc::munlock(self.ptr, self.len) == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + } } impl Drop for MmapInner { diff --git a/src/windows.rs b/src/windows.rs index d8aa99d2..2762f1f4 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -74,7 +74,7 @@ impl MmapInner { } } - pub fn map(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result { let write = protection_supported(file.as_raw_handle(), PAGE_READWRITE); let exec = protection_supported(file.as_raw_handle(), PAGE_EXECUTE_READ); let mut access = FILE_MAP_READ; @@ -101,7 +101,7 @@ impl MmapInner { Ok(inner) } - pub fn map_exec(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_exec(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result { let write = protection_supported(file.as_raw_handle(), PAGE_READWRITE); let mut access = FILE_MAP_READ | FILE_MAP_EXECUTE; let protection = if write { @@ -118,7 +118,7 @@ impl MmapInner { Ok(inner) } - pub fn map_mut(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_mut(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result { let exec = protection_supported(file.as_raw_handle(), PAGE_EXECUTE_READ); let mut access = FILE_MAP_READ | FILE_MAP_WRITE; let protection = if exec { @@ -135,7 +135,7 @@ impl MmapInner { Ok(inner) } - pub fn map_copy(len: usize, file: &File, offset: u64) -> io::Result { + pub fn map_copy(len: usize, file: &File, offset: u64, _locked: bool) -> io::Result { let exec = protection_supported(file.as_raw_handle(), PAGE_EXECUTE_READWRITE); let mut access = FILE_MAP_COPY; let protection = if exec { @@ -152,7 +152,7 @@ impl MmapInner { Ok(inner) } - pub fn map_anon(len: usize, _stack: bool) -> io::Result { + pub fn map_anon(len: usize, _stack: bool, _locked: bool, _private: bool) -> io::Result { unsafe { // Create a mapping and view with maximum access permissions, then use `VirtualProtect` // to set the actual `Protection`. This way, we can set more permissive protection later