mod builder;
mod error;
mod event;
mod raw;
pub use crate::builder::{FeatureFlags, UffdBuilder};
pub use crate::error::{Error, Result};
pub use crate::event::{Event, FaultKind, ReadWrite};
use bitflags::bitflags;
use libc::{self, c_void};
use nix::errno::Errno;
use nix::unistd::read;
use std::mem;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
pub struct EventBuffer(Vec<raw::uffd_msg>);
impl EventBuffer {
pub fn new(size: usize) -> Self {
Self(vec![unsafe { mem::zeroed() }; size])
}
}
#[derive(Debug)]
pub struct Uffd {
fd: RawFd,
}
impl Drop for Uffd {
fn drop(&mut self) {
unsafe { libc::close(self.fd) };
}
}
impl AsFd for Uffd {
fn as_fd(&self) -> BorrowedFd<'_> {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl AsRawFd for Uffd {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl IntoRawFd for Uffd {
fn into_raw_fd(self) -> RawFd {
self.fd
}
}
impl FromRawFd for Uffd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
Uffd { fd }
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RegisterMode: u64 {
const MISSING = raw::UFFDIO_REGISTER_MODE_MISSING;
#[cfg(feature = "linux5_7")]
const WRITE_PROTECT = raw::UFFDIO_REGISTER_MODE_WP;
}
}
impl Uffd {
pub fn register(&self, start: *mut c_void, len: usize) -> Result<IoctlFlags> {
self.register_with_mode(start, len, RegisterMode::MISSING)
}
pub fn register_with_mode(
&self,
start: *mut c_void,
len: usize,
mode: RegisterMode,
) -> Result<IoctlFlags> {
let mut register = raw::uffdio_register {
range: raw::uffdio_range {
start: start as u64,
len: len as u64,
},
mode: mode.bits(),
ioctls: 0,
};
unsafe {
raw::register(self.as_raw_fd(), &mut register as *mut raw::uffdio_register)?;
}
Ok(IoctlFlags::from_bits_retain(register.ioctls))
}
pub fn unregister(&self, start: *mut c_void, len: usize) -> Result<()> {
let mut range = raw::uffdio_range {
start: start as u64,
len: len as u64,
};
unsafe {
raw::unregister(self.as_raw_fd(), &mut range as *mut raw::uffdio_range)?;
}
Ok(())
}
pub unsafe fn copy(
&self,
src: *const c_void,
dst: *mut c_void,
len: usize,
wake: bool,
) -> Result<usize> {
let mut copy = raw::uffdio_copy {
src: src as u64,
dst: dst as u64,
len: len as u64,
mode: if wake {
0
} else {
raw::UFFDIO_COPY_MODE_DONTWAKE
},
copy: 0,
};
let _ =
raw::copy(self.as_raw_fd(), &mut copy as *mut raw::uffdio_copy).map_err(|errno| {
match errno {
Errno::EAGAIN => Error::PartiallyCopied(copy.copy as usize),
_ => Error::CopyFailed(errno),
}
})?;
if copy.copy < 0 {
Err(Error::CopyFailed(Errno::from_i32(-copy.copy as i32)))
} else {
Ok(copy.copy as usize)
}
}
pub unsafe fn zeropage(&self, start: *mut c_void, len: usize, wake: bool) -> Result<usize> {
let mut zeropage = raw::uffdio_zeropage {
range: raw::uffdio_range {
start: start as u64,
len: len as u64,
},
mode: if wake {
0
} else {
raw::UFFDIO_ZEROPAGE_MODE_DONTWAKE
},
zeropage: 0,
};
let _ = raw::zeropage(self.as_raw_fd(), &mut zeropage as &mut raw::uffdio_zeropage)
.map_err(Error::ZeropageFailed)?;
if zeropage.zeropage < 0 {
Err(Error::ZeropageFailed(Errno::from_i32(
-zeropage.zeropage as i32,
)))
} else {
Ok(zeropage.zeropage as usize)
}
}
pub fn wake(&self, start: *mut c_void, len: usize) -> Result<()> {
let mut range = raw::uffdio_range {
start: start as u64,
len: len as u64,
};
unsafe {
raw::wake(self.as_raw_fd(), &mut range as *mut raw::uffdio_range)?;
}
Ok(())
}
#[cfg(feature = "linux5_7")]
pub fn write_protect(&self, start: *mut c_void, len: usize) -> Result<()> {
let mut ioctl = raw::uffdio_writeprotect {
range: raw::uffdio_range {
start: start as u64,
len: len as u64,
},
mode: raw::UFFDIO_WRITEPROTECT_MODE_WP,
};
unsafe {
raw::write_protect(
self.as_raw_fd(),
&mut ioctl as *mut raw::uffdio_writeprotect,
)?;
}
Ok(())
}
#[cfg(feature = "linux5_7")]
pub fn remove_write_protection(
&self,
start: *mut c_void,
len: usize,
wake: bool,
) -> Result<()> {
let mut ioctl = raw::uffdio_writeprotect {
range: raw::uffdio_range {
start: start as u64,
len: len as u64,
},
mode: if wake {
0
} else {
raw::UFFDIO_WRITEPROTECT_MODE_DONTWAKE
},
};
unsafe {
raw::write_protect(
self.as_raw_fd(),
&mut ioctl as *mut raw::uffdio_writeprotect,
)?;
}
Ok(())
}
pub fn read_event(&self) -> Result<Option<Event>> {
let mut buf = [unsafe { std::mem::zeroed() }; 1];
let mut iter = self.read(&mut buf)?;
let event = iter.next().transpose()?;
assert!(iter.next().is_none());
Ok(event)
}
pub fn read_events<'a>(
&self,
buf: &'a mut EventBuffer,
) -> Result<impl Iterator<Item = Result<Event>> + 'a> {
self.read(&mut buf.0)
}
fn read<'a>(
&self,
msgs: &'a mut [raw::uffd_msg],
) -> Result<impl Iterator<Item = Result<Event>> + 'a> {
const MSG_SIZE: usize = std::mem::size_of::<raw::uffd_msg>();
let buf = unsafe {
std::slice::from_raw_parts_mut(msgs.as_mut_ptr() as _, msgs.len() * MSG_SIZE)
};
let count = match read(self.as_raw_fd(), buf) {
Err(e) if e == Errno::EAGAIN => 0,
Err(e) => return Err(Error::SystemError(e)),
Ok(0) => return Err(Error::ReadEof),
Ok(bytes_read) => {
let remainder = bytes_read % MSG_SIZE;
if remainder != 0 {
return Err(Error::IncompleteMsg {
read: remainder,
expected: MSG_SIZE,
});
}
bytes_read / MSG_SIZE
}
};
Ok(msgs.iter().take(count).map(|msg| Event::from_uffd_msg(msg)))
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct IoctlFlags: u64 {
const REGISTER = 1 << raw::_UFFDIO_REGISTER;
const UNREGISTER = 1 << raw::_UFFDIO_UNREGISTER;
const WAKE = 1 << raw::_UFFDIO_WAKE;
const COPY = 1 << raw::_UFFDIO_COPY;
const ZEROPAGE = 1 << raw::_UFFDIO_ZEROPAGE;
#[cfg(feature = "linux5_7")]
const WRITE_PROTECT = 1 << raw::_UFFDIO_WRITEPROTECT;
const API = 1 << raw::_UFFDIO_API;
const _ = !0;
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ptr;
use std::thread;
#[test]
fn test_read_event() -> Result<()> {
const PAGE_SIZE: usize = 4096;
unsafe {
let uffd = UffdBuilder::new().close_on_exec(true).create()?;
let mapping = libc::mmap(
ptr::null_mut(),
PAGE_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
assert!(!mapping.is_null());
uffd.register(mapping, PAGE_SIZE)?;
let ptr = mapping as usize;
let thread = thread::spawn(move || {
let ptr = ptr as *mut u8;
*ptr = 1;
});
match uffd.read_event()? {
Some(Event::Pagefault {
rw: ReadWrite::Write,
addr,
..
}) => {
assert_eq!(addr, mapping);
uffd.zeropage(addr, PAGE_SIZE, true)?;
}
_ => panic!("unexpected event"),
}
thread.join().expect("failed to join thread");
uffd.unregister(mapping, PAGE_SIZE)?;
assert_eq!(libc::munmap(mapping, PAGE_SIZE), 0);
}
Ok(())
}
#[test]
fn test_nonblocking_read_event() -> Result<()> {
const PAGE_SIZE: usize = 4096;
unsafe {
let uffd = UffdBuilder::new()
.close_on_exec(true)
.non_blocking(true)
.create()?;
let mapping = libc::mmap(
ptr::null_mut(),
PAGE_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
assert!(!mapping.is_null());
uffd.register(mapping, PAGE_SIZE)?;
assert!(uffd.read_event()?.is_none());
let ptr = mapping as usize;
let thread = thread::spawn(move || {
let ptr = ptr as *mut u8;
*ptr = 1;
});
loop {
match uffd.read_event()? {
Some(Event::Pagefault {
rw: ReadWrite::Write,
addr,
..
}) => {
assert_eq!(addr, mapping);
uffd.zeropage(addr, PAGE_SIZE, true)?;
break;
}
Some(_) => panic!("unexpected event"),
None => thread::sleep(std::time::Duration::from_millis(50)),
}
}
thread.join().expect("failed to join thread");
uffd.unregister(mapping, PAGE_SIZE)?;
assert_eq!(libc::munmap(mapping, PAGE_SIZE), 0);
}
Ok(())
}
#[test]
fn test_read_events() -> Result<()> {
unsafe {
const MAX_THREADS: usize = 5;
const PAGE_SIZE: usize = 4096;
const MEM_SIZE: usize = PAGE_SIZE * MAX_THREADS;
let uffd = UffdBuilder::new().close_on_exec(true).create()?;
let mapping = libc::mmap(
ptr::null_mut(),
MEM_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
assert!(!mapping.is_null());
uffd.register(mapping, MEM_SIZE)?;
let mut seen = [false; MAX_THREADS];
let mut threads = Vec::new();
for i in 0..MAX_THREADS {
let seen = &mut seen[i] as *mut _ as usize;
let ptr = (mapping as *mut u8).add(PAGE_SIZE * i) as usize;
threads.push(thread::spawn(move || {
let seen = seen as *mut bool;
let ptr = ptr as *mut u8;
*seen = true;
*ptr = 1;
}));
}
loop {
let done = seen.iter().all(|b| *b);
thread::sleep(std::time::Duration::from_millis(50));
if done {
break;
}
}
let mut buf = EventBuffer::new(MAX_THREADS);
let mut iter = uffd.read_events(&mut buf)?;
let mut seen = [false; MAX_THREADS];
for _ in 0..MAX_THREADS {
match iter
.next()
.transpose()?
.expect("failed to read all events; potential race condition was hit")
{
Event::Pagefault {
rw: ReadWrite::Write,
addr,
..
} => {
let index = (addr as usize - mapping as usize) / PAGE_SIZE;
assert_eq!(seen[index], false);
seen[index] = true;
uffd.zeropage(addr, PAGE_SIZE, true)?;
}
_ => panic!("unexpected event"),
}
}
assert!(seen.iter().all(|b| *b));
for thread in threads {
thread.join().expect("failed to join thread");
}
uffd.unregister(mapping, MEM_SIZE)?;
assert_eq!(libc::munmap(mapping, MEM_SIZE), 0);
}
Ok(())
}
#[cfg(feature = "linux5_7")]
#[test]
fn test_write_protect() -> Result<()> {
const PAGE_SIZE: usize = 4096;
unsafe {
let uffd = UffdBuilder::new()
.require_features(FeatureFlags::PAGEFAULT_FLAG_WP)
.close_on_exec(true)
.create()?;
let mapping = libc::mmap(
ptr::null_mut(),
PAGE_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
assert!(!mapping.is_null());
assert!(uffd
.register_with_mode(
mapping,
PAGE_SIZE,
RegisterMode::MISSING | RegisterMode::WRITE_PROTECT
)?
.contains(IoctlFlags::WRITE_PROTECT));
let ptr = mapping as usize;
let thread = thread::spawn(move || {
let ptr = ptr as *mut u8;
*ptr = 1;
*ptr = 2;
});
loop {
match uffd.read_event()? {
Some(Event::Pagefault {
kind,
rw: ReadWrite::Write,
addr,
..
}) => match kind {
FaultKind::WriteProtected => {
assert_eq!(addr, mapping);
assert_eq!(*(addr as *const u8), 0);
uffd.remove_write_protection(mapping, PAGE_SIZE, true)?;
break;
}
FaultKind::Missing => {
assert_eq!(addr, mapping);
uffd.zeropage(mapping, PAGE_SIZE, false)?;
assert_eq!(*(addr as *const u8), 0);
uffd.write_protect(mapping, PAGE_SIZE)?;
uffd.wake(mapping, PAGE_SIZE)?;
}
},
_ => panic!("unexpected event"),
}
}
thread.join().expect("failed to join thread");
assert_eq!(*(mapping as *const u8), 2);
uffd.unregister(mapping, PAGE_SIZE)?;
assert_eq!(libc::munmap(mapping, PAGE_SIZE), 0);
}
Ok(())
}
}