use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
const UFFDIO_API: libc::c_ulong = 0xc018_aa3f;
const UFFDIO_REGISTER: libc::c_ulong = 0xc020_aa00;
const UFFDIO_COPY: libc::c_ulong = 0xc028_aa03;
const UFFD_API: u64 = 0xAA;
const UFFDIO_REGISTER_MODE_MISSING: u64 = 1;
const UFFD_EVENT_PAGEFAULT: u8 = 0x12;
#[repr(C)]
struct UffdioApi { api: u64, features: u64, ioctls: u64 }
#[repr(C)]
struct UffdioRange { start: u64, len: u64 }
#[repr(C)]
struct UffdioRegister { range: UffdioRange, mode: u64, ioctls: u64 }
#[repr(C)]
struct UffdioCopy { dst: u64, src: u64, len: u64, mode: u64, copy: i64 }
#[repr(C)]
struct UffdMsg { event: u8, _pad: [u8; 7], arg: [u64; 3] }
#[derive(Clone)]
pub(crate) struct PageImage {
pub regions: Vec<(u64, u64, Vec<u8>)>,
}
impl PageImage {
pub(crate) fn page_at(&self, addr: u64, page: usize) -> Option<&[u8]> {
let base = addr & !((page as u64) - 1);
for (start, end, bytes) in &self.regions {
if base >= *start && base + page as u64 <= *end {
let off = (base - *start) as usize;
return Some(&bytes[off..off + page]);
}
}
None
}
}
pub(crate) fn register_api_and_range(uffd: i32, start: u64, len: u64) -> io::Result<()> {
let mut api = UffdioApi { api: UFFD_API, features: 0, ioctls: 0 };
if unsafe { libc::ioctl(uffd, UFFDIO_API, &mut api) } < 0 {
return Err(io::Error::last_os_error());
}
let mut reg = UffdioRegister {
range: UffdioRange { start, len },
mode: UFFDIO_REGISTER_MODE_MISSING,
ioctls: 0,
};
if unsafe { libc::ioctl(uffd, UFFDIO_REGISTER, &mut reg) } < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn copy_page(uffd: i32, image: &PageImage, addr: u64, page: usize) -> io::Result<()> {
let base = addr & !((page as u64) - 1);
let src = match image.page_at(addr, page) {
Some(s) => s,
None => {
let zero = vec![0u8; page];
let mut c = UffdioCopy {
dst: base, src: zero.as_ptr() as u64, len: page as u64, mode: 0, copy: 0,
};
if unsafe { libc::ioctl(uffd, UFFDIO_COPY, &mut c) } < 0 {
return Err(io::Error::last_os_error());
}
return Ok(());
}
};
let mut c = UffdioCopy {
dst: base, src: src.as_ptr() as u64, len: page as u64, mode: 0, copy: 0,
};
if unsafe { libc::ioctl(uffd, UFFDIO_COPY, &mut c) } < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn poll_once(uffd: i32, timeout_ms: i32) -> io::Result<bool> {
let mut pfd = libc::pollfd { fd: uffd, events: libc::POLLIN, revents: 0 };
let n = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted { return Ok(false); }
return Err(e);
}
Ok(n > 0 && (pfd.revents & libc::POLLIN) != 0)
}
fn handle_ready(uffd: i32, image: &PageImage) -> io::Result<bool> {
let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
let mut msg = UffdMsg { event: 0, _pad: [0; 7], arg: [0; 3] };
let n = unsafe {
libc::read(uffd, &mut msg as *mut _ as *mut libc::c_void,
std::mem::size_of::<UffdMsg>())
};
if n == 0 { return Ok(false); } if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::WouldBlock { return Ok(true); }
return Err(e);
}
if msg.event == UFFD_EVENT_PAGEFAULT {
let addr = msg.arg[1];
copy_page(uffd, image, addr, page)?;
}
Ok(true)
}
pub(crate) fn serve(uffd: i32, image: &PageImage) -> io::Result<()> {
loop {
if poll_once(uffd, -1)? {
if !handle_ready(uffd, image)? { return Ok(()); }
}
}
}
pub(crate) fn serve_until(uffd: i32, image: &PageImage, stop: &AtomicBool) {
while !stop.load(Ordering::SeqCst) {
match poll_once(uffd, 20) {
Ok(true) => { let _ = handle_ready(uffd, image); }
Ok(false) => {}
Err(_) => break,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_at_returns_aligned_slice_within_run() {
let img = PageImage {
regions: vec![(0x1000, 0x3000, {
let mut v = vec![0u8; 0x2000];
for b in &mut v[0x1000..0x2000] { *b = 0xAB; }
v
})],
};
let page = 0x1000usize;
let s = img.page_at(0x2500, page).expect("covered");
assert_eq!(s.len(), page);
assert!(s.iter().all(|&b| b == 0xAB));
assert!(img.page_at(0x9000, page).is_none());
}
#[test]
fn serve_copies_faulted_page_into_a_registered_region() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let page = 4096usize;
let len = page;
let addr = unsafe {
libc::mmap(std::ptr::null_mut(), len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, -1, 0)
};
assert_ne!(addr, libc::MAP_FAILED);
let start = addr as u64;
const UFFD_USER_MODE_ONLY: libc::c_int = 1;
let flags = libc::O_CLOEXEC | libc::O_NONBLOCK;
let uffd = {
let plain = unsafe { libc::syscall(libc::SYS_userfaultfd, flags) } as i32;
if plain >= 0 {
plain
} else {
(unsafe {
libc::syscall(libc::SYS_userfaultfd, flags | UFFD_USER_MODE_ONLY)
}) as i32
}
};
assert!(uffd >= 0, "userfaultfd (tried plain then USER_MODE_ONLY)");
register_api_and_range(uffd, start, len as u64).expect("register");
let img = PageImage { regions: vec![(start, start + len as u64, vec![0x5Au8; len])] };
let stop = Arc::new(AtomicBool::new(false));
let stop2 = stop.clone();
let uffd_copy = uffd;
let img_copy = img.clone();
let h = std::thread::spawn(move || serve_until(uffd_copy, &img_copy, &stop2));
let byte = unsafe { std::ptr::read_volatile(addr as *const u8) };
assert_eq!(byte, 0x5A, "faulted page must be filled by the pager");
stop.store(true, Ordering::SeqCst);
let _ = h.join();
unsafe { libc::munmap(addr, len); }
}
}