aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builder.rs62
-rw-r--r--src/error.rs6
-rw-r--r--src/lib.rs9
-rw-r--r--src/raw.rs15
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 {
diff --git a/src/lib.rs b/src/lib.rs
index 9a7641b..e18ef3c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
diff --git a/src/raw.rs b/src/raw.rs
index 332c459..28039c7 100644
--- a/src/raw.rs
+++ b/src/raw.rs
@@ -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)
+);