use std::io;
use std::ptr::NonNull;
use zeroize::Zeroize;
pub(crate) struct LockedPage {
ptr: NonNull<u8>,
len: usize,
release: unsafe fn(NonNull<u8>, usize),
}
unsafe impl Send for LockedPage {}
unsafe impl Sync for LockedPage {}
impl LockedPage {
pub(crate) fn new() -> io::Result<LockedPage> {
let len = usize::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) })
.map_err(|_| io::Error::other("the page size is unknown"))?;
let addr = unsafe {
libc::mmap(
std::ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if addr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
let ptr =
NonNull::new(addr.cast::<u8>()).ok_or_else(|| io::Error::other("null mapping"))?;
let page = LockedPage {
ptr,
len,
release: unmap,
};
if unsafe { libc::madvise(addr, len, libc::MADV_DONTDUMP) } != 0 {
return Err(io::Error::last_os_error());
}
if unsafe { libc::mlock(addr, len) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(page)
}
pub(crate) fn bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
pub(crate) fn bytes_mut(&mut self) -> &mut [u8] {
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
}
impl Drop for LockedPage {
fn drop(&mut self) {
self.bytes_mut().zeroize();
unsafe { (self.release)(self.ptr, self.len) }
}
}
unsafe fn unmap(ptr: NonNull<u8>, len: usize) {
unsafe { libc::munmap(ptr.as_ptr().cast(), len) };
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use super::*;
thread_local! {
static RELEASED: RefCell<Option<Vec<u8>>> = const { RefCell::new(None) };
}
unsafe fn record_then_release(ptr: NonNull<u8>, len: usize) {
let bytes = unsafe { std::slice::from_raw_parts(ptr.as_ptr(), len) }.to_vec();
RELEASED.with(|r| *r.borrow_mut() = Some(bytes));
unsafe { unmap(ptr, len) }
}
#[test]
fn a_page_is_zeroed_before_it_is_released() {
let mut page = LockedPage::new().expect("page");
page.bytes_mut().fill(0xa5);
let len = page.bytes().len();
page.release = record_then_release;
drop(page);
let released = RELEASED.with(|r| r.borrow_mut().take()).expect("released");
assert_eq!(released, vec![0; len]);
}
}