ureeves-userfaultfd 0.8.1

Rust bindings for the Linux userfaultfd functionality
Documentation
use crate::error::{Error, Result};
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;
                const EVENT_REMAP = raw::UFFD_FEATURE_EVENT_REMAP;
                const EVENT_REMOVE = raw::UFFD_FEATURE_EVENT_REMOVE;
                const MISSING_HUGETLBFS = raw::UFFD_FEATURE_MISSING_HUGETLBFS;
                const MISSING_SHMEM = raw::UFFD_FEATURE_MISSING_SHMEM;
                const EVENT_UNMAP = raw::UFFD_FEATURE_EVENT_UNMAP;
                const SIGBUS = raw::UFFD_FEATURE_SIGBUS;
                const THREAD_ID = raw::UFFD_FEATURE_THREAD_ID;
            }
        }
    } 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;
                const EVENT_REMAP = raw::UFFD_FEATURE_EVENT_REMAP;
                const EVENT_REMOVE = raw::UFFD_FEATURE_EVENT_REMOVE;
                const MISSING_HUGETLBFS = raw::UFFD_FEATURE_MISSING_HUGETLBFS;
                const MISSING_SHMEM = raw::UFFD_FEATURE_MISSING_SHMEM;
                const EVENT_UNMAP = raw::UFFD_FEATURE_EVENT_UNMAP;
            }
        }
    }
}
/// A builder for initializing `Uffd` objects.
///
/// ```
/// use ureeves_userfaultfd::UffdBuilder;
///
/// let uffd = UffdBuilder::new()
///     .close_on_exec(true)
///     .non_blocking(true)
///     .user_mode_only(true)
///     .create();
/// assert!(uffd.is_ok());
/// ```
pub struct UffdBuilder {
    close_on_exec: bool,
    non_blocking: bool,
    user_mode_only: bool,
    req_features: FeatureFlags,
    req_ioctls: IoctlFlags,
}

impl UffdBuilder {
    /// Create a new builder with no required features or ioctls, `close_on_exec` and
    /// `non_blocking` both set to `false`, and `user_mode_only` set to `true`.
    pub fn new() -> UffdBuilder {
        UffdBuilder {
            close_on_exec: false,
            non_blocking: false,
            user_mode_only: true,
            req_features: FeatureFlags::empty(),
            req_ioctls: IoctlFlags::empty(),
        }
    }

    /// Enable the close-on-exec flag for the new userfaultfd object (see the description of
    /// `O_CLOEXEC` in [`open(2)`](http://man7.org/linux/man-pages/man2/open.2.html)).
    pub fn close_on_exec(&mut self, close_on_exec: bool) -> &mut Self {
        self.close_on_exec = close_on_exec;
        self
    }

    /// Enable non-blocking operation for the userfaultfd object.
    ///
    /// If this is set to `false`, `Uffd::read_event()` will block until an event is available to
    /// read. Otherwise, it will immediately return `None` if no event is available.
    pub fn non_blocking(&mut self, non_blocking: bool) -> &mut Self {
        self.non_blocking = non_blocking;
        self
    }

    /// Enable user-mode only flag for the userfaultfd object.
    ///
    /// If set to `false`, the process must have the `CAP_SYS_PTRACE` capability starting with Linux 5.11
    /// or object creation will fail with EPERM. When set to `true`, userfaultfd can't be used
    /// to handle kernel-mode page faults such as when kernel tries copying data to userspace.
    ///
    /// When used with kernels older than 5.11, this has no effect; the process doesn't need
    /// `CAP_SYS_PTRACE` and can handle kernel-mode page faults.
    pub fn user_mode_only(&mut self, user_mode_only: bool) -> &mut Self {
        self.user_mode_only = user_mode_only;
        self
    }

    /// Add a requirement that a particular feature or set of features is available.
    ///
    /// If a required feature is unavailable, `UffdBuilder.create()` will return an error.
    pub fn require_features(&mut self, feature: FeatureFlags) -> &mut Self {
        self.req_features |= feature;
        self
    }

    /// Add a requirement that a particular ioctl or set of ioctls is available.
    ///
    /// If a required ioctl is unavailable, `UffdBuilder.create()` will return an error.
    pub fn require_ioctls(&mut self, ioctls: IoctlFlags) -> &mut Self {
        self.req_ioctls |= ioctls;
        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
        let mut flags = 0;
        if self.close_on_exec {
            flags |= libc::O_CLOEXEC;
        }
        if self.non_blocking {
            flags |= libc::O_NONBLOCK;
        }

        if self.user_mode_only {
            flags |= raw::UFFD_USER_MODE_ONLY as i32;
        }

        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 {
            api: raw::UFFD_API,
            features: self.req_features.bits(),
            ioctls: 0,
        };
        unsafe {
            raw::api(uffd.fd, &mut api as *mut raw::uffdio_api)?;
        }
        let supported = IoctlFlags::from_bits_retain(api.ioctls);
        if !supported.contains(self.req_ioctls) {
            Err(Error::UnsupportedIoctls(supported))
        } else {
            Ok(uffd)
        }
    }
}