diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/builder.rs | 62 | ||||
-rw-r--r-- | src/error.rs | 6 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/raw.rs | 15 |
4 files changed, 79 insertions, 13 deletions
diff --git a/src/builder.rs b/src/builder.rs index b89efb4..a200148 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,11 +3,17 @@ use crate::raw; use crate::{IoctlFlags, Uffd}; use bitflags::bitflags; use nix::errno::Errno; +use std::fs::{File, OpenOptions}; +use std::io::ErrorKind; +use std::os::fd::AsRawFd; + +const UFFD_DEVICE_PATH: &str = "/dev/userfaultfd"; cfg_if::cfg_if! { if #[cfg(any(feature = "linux5_7", feature = "linux4_14"))] { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -23,6 +29,7 @@ cfg_if::cfg_if! { } else { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -113,6 +120,47 @@ impl UffdBuilder { self } + fn uffd_from_dev(&self, file: &mut File, flags: i32) -> Result<Uffd> { + match unsafe { raw::new_uffd(file.as_raw_fd(), flags) } { + Err(err) => Err(err.into()), + Ok(fd) => Ok(Uffd { fd }), + } + } + + fn uffd_from_syscall(&self, flags: i32) -> Result<Uffd> { + let fd = match Errno::result(unsafe { raw::userfaultfd(flags) }) { + Ok(fd) => fd, + // setting the USER_MODE_ONLY flag on kernel pre-5.11 causes it to return EINVAL. + // If the user asks for the flag, we first try with it set, and if kernel gives + // EINVAL we try again without the flag set. + Err(Errno::EINVAL) if self.user_mode_only => Errno::result(unsafe { + raw::userfaultfd(flags & !raw::UFFD_USER_MODE_ONLY as i32) + })?, + Err(e) => return Err(e.into()), + }; + + // Wrap the fd up so that a failure in this function body closes it with the drop. + Ok(Uffd { fd }) + } + + // Try to get a UFFD file descriptor using `/dev/userfaultfd`. If that fails + // fall back to calling the system call. + fn open_file_descriptor(&self, flags: i32) -> Result<Uffd> { + // If `/dev/userfaultfd` exists we'll try to get the file descriptor from it. If the file + // doesn't exist we will fall back to calling the system call. This means, that if the + // device exists but the calling process does not have access rights to it, this will fail, + // i.e. we will not fall back to calling the system call. + match OpenOptions::new() + .read(true) + .write(true) + .open(UFFD_DEVICE_PATH) + { + Ok(mut file) => self.uffd_from_dev(&mut file, flags), + Err(err) if err.kind() == ErrorKind::NotFound => self.uffd_from_syscall(flags), + Err(err) => Err(Error::OpenDevUserfaultfd(err)), + } + } + /// Create a `Uffd` object with the current settings of this builder. pub fn create(&self) -> Result<Uffd> { // first do the syscall to get the file descriptor @@ -128,19 +176,7 @@ impl UffdBuilder { flags |= raw::UFFD_USER_MODE_ONLY as i32; } - let fd = match Errno::result(unsafe { raw::userfaultfd(flags) }) { - Ok(fd) => fd, - // setting the USER_MODE_ONLY flag on kernel pre-5.11 causes it to return EINVAL. - // If the user asks for the flag, we first try with it set, and if kernel gives - // EINVAL we try again without the flag set. - Err(Errno::EINVAL) if self.user_mode_only => Errno::result(unsafe { - raw::userfaultfd(flags & !raw::UFFD_USER_MODE_ONLY as i32) - })?, - Err(e) => return Err(e.into()), - }; - - // Wrap the fd up so that a failure in this function body closes it with the drop. - let uffd = Uffd { fd }; + let uffd = self.open_file_descriptor(flags)?; // then do the UFFDIO_API ioctl to set up and ensure features and other ioctls are available let mut api = raw::uffdio_api { diff --git a/src/error.rs b/src/error.rs index 5cd8926..f66806a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::io; + use crate::IoctlFlags; use nix::errno::Errno; use thiserror::Error; @@ -47,6 +49,10 @@ pub enum Error { /// Zeropage ioctl failure with `errno` value. #[error("Zeropage failed: {0}")] ZeropageFailed(Errno), + + /// Could not open /dev/userfaultfd even though it exists + #[error("Error accessing /dev/userfaultfd: {0}")] + OpenDevUserfaultfd(io::Error), } impl From<nix::Error> for Error { @@ -21,6 +21,7 @@ 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}; /// Represents an opaque buffer where userfaultfd events are stored. @@ -53,6 +54,12 @@ impl Drop for Uffd { } } +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 @@ -73,6 +80,7 @@ impl FromRawFd for Uffd { bitflags! { /// The registration mode used when registering an address range with `Uffd`. + #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct RegisterMode: u64 { /// Registers the range for missing page faults. const MISSING = raw::UFFDIO_REGISTER_MODE_MISSING; @@ -359,6 +367,7 @@ impl Uffd { bitflags! { /// Used with `UffdBuilder` and `Uffd::register()` to determine which operations are available. + #[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; @@ -23,3 +23,18 @@ nix::ioctl_readwrite!( _UFFDIO_WRITEPROTECT, uffdio_writeprotect ); + +// ioctls for /dev/userfaultfd + +// This is the `/dev/userfaultfd` ioctl() from creating a new userfault file descriptor. +// It is a "bad" ioctl in the sense that it is defined as an _IOC: +// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/userfaultfd.h#L17, +// aka `nix::ioctl_none`, however it does receive an integer argument: +// https://elixir.bootlin.com/linux/latest/source/fs/userfaultfd.c#L2186. That is the same argument +// that the userfaultfd() system call receives. +nix::ioctl_write_int_bad!( + /// Create a new userfault file descriptor from the `/dev/userfaultfd` + /// device. This receives the same arguments as the userfaultfd system call. + new_uffd, + nix::request_code_none!(USERFAULTFD_IOC, 0x00) +); |