Skip to content

feat: add support for lock and private on nix based systems #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
70 changes: 65 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub struct MmapOptions {
offset: u64,
len: Option<usize>,
stack: bool,
locked: bool,
private: bool,
}

impl MmapOptions {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -193,7 +213,7 @@ impl MmapOptions {
/// # }
/// ```
pub unsafe fn map(&self, file: &File) -> Result<Mmap> {
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.
Expand All @@ -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<Mmap> {
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 })
}

Expand Down Expand Up @@ -241,7 +261,7 @@ impl MmapOptions {
/// # }
/// ```
pub unsafe fn map_mut(&self, file: &File) -> Result<MmapMut> {
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 })
}

Expand Down Expand Up @@ -270,7 +290,7 @@ impl MmapOptions {
/// # }
/// ```
pub unsafe fn map_copy(&self, file: &File) -> Result<MmapMut> {
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 })
}

Expand All @@ -283,7 +303,7 @@ impl MmapOptions {
///
/// This method returns an error when the underlying system call fails.
pub fn map_anon(&self) -> Result<MmapMut> {
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 })
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
65 changes: 55 additions & 10 deletions src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -66,53 +81,63 @@ impl MmapInner {
}
}

pub fn map(len: usize, file: &File, offset: u64) -> io::Result<MmapInner> {
pub fn map(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result<MmapInner> {
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<MmapInner> {
pub fn map_exec(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result<MmapInner> {
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<MmapInner> {
pub fn map_mut(len: usize, file: &File, offset: u64, locked: bool, private: bool) -> io::Result<MmapInner> {
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<MmapInner> {
pub fn map_copy(len: usize, file: &File, offset: u64, locked: bool) -> io::Result<MmapInner> {
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<MmapInner> {
pub fn map_anon(len: usize, stack: bool, locked: bool, private: bool) -> io::Result<MmapInner> {
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,
)
Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl MmapInner {
}
}

pub fn map(len: usize, file: &File, offset: u64) -> io::Result<MmapInner> {
pub fn map(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result<MmapInner> {
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;
Expand All @@ -101,7 +101,7 @@ impl MmapInner {
Ok(inner)
}

pub fn map_exec(len: usize, file: &File, offset: u64) -> io::Result<MmapInner> {
pub fn map_exec(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result<MmapInner> {
let write = protection_supported(file.as_raw_handle(), PAGE_READWRITE);
let mut access = FILE_MAP_READ | FILE_MAP_EXECUTE;
let protection = if write {
Expand All @@ -118,7 +118,7 @@ impl MmapInner {
Ok(inner)
}

pub fn map_mut(len: usize, file: &File, offset: u64) -> io::Result<MmapInner> {
pub fn map_mut(len: usize, file: &File, offset: u64, _locked: bool, _private: bool) -> io::Result<MmapInner> {
let exec = protection_supported(file.as_raw_handle(), PAGE_EXECUTE_READ);
let mut access = FILE_MAP_READ | FILE_MAP_WRITE;
let protection = if exec {
Expand All @@ -135,7 +135,7 @@ impl MmapInner {
Ok(inner)
}

pub fn map_copy(len: usize, file: &File, offset: u64) -> io::Result<MmapInner> {
pub fn map_copy(len: usize, file: &File, offset: u64, _locked: bool) -> io::Result<MmapInner> {
let exec = protection_supported(file.as_raw_handle(), PAGE_EXECUTE_READWRITE);
let mut access = FILE_MAP_COPY;
let protection = if exec {
Expand All @@ -152,7 +152,7 @@ impl MmapInner {
Ok(inner)
}

pub fn map_anon(len: usize, _stack: bool) -> io::Result<MmapInner> {
pub fn map_anon(len: usize, _stack: bool, _locked: bool, _private: bool) -> io::Result<MmapInner> {
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
Expand Down