diff options
Diffstat (limited to 'vendor/rustix/src/backend/linux_raw/event/syscalls.rs')
| -rw-r--r-- | vendor/rustix/src/backend/linux_raw/event/syscalls.rs | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/vendor/rustix/src/backend/linux_raw/event/syscalls.rs b/vendor/rustix/src/backend/linux_raw/event/syscalls.rs new file mode 100644 index 00000000..8a4e24bd --- /dev/null +++ b/vendor/rustix/src/backend/linux_raw/event/syscalls.rs @@ -0,0 +1,358 @@ +//! linux_raw syscalls supporting `rustix::event`. +//! +//! # Safety +//! +//! See the `rustix::backend` module documentation for details. +#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + +use crate::backend::conv::{ + by_ref, c_int, c_uint, opt_mut, opt_ref, pass_usize, ret, ret_c_int, ret_error, ret_owned_fd, + ret_usize, size_of, slice_mut, zero, +}; +use crate::event::{epoll, EventfdFlags, FdSetElement, PollFd, Timespec}; +use crate::fd::{BorrowedFd, OwnedFd}; +use crate::io; +use core::ptr::null_mut; +use linux_raw_sys::general::{kernel_sigset_t, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD}; + +#[inline] +pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: Option<&Timespec>) -> io::Result<usize> { + let (fds_addr_mut, fds_len) = slice_mut(fds); + + #[cfg(target_pointer_width = "32")] + unsafe { + // If we don't have Linux 5.1, and the timeout fits in a + // `__kernel_old_timespec`, use plain `ppoll`. + // + // We do this unconditionally, rather than trying `ppoll_time64` and + // falling back on `Errno::NOSYS`, because seccomp configurations will + // sometimes abort the process on syscalls they don't recognize. + #[cfg(not(feature = "linux_5_1"))] + { + use linux_raw_sys::general::__kernel_old_timespec; + + // If we don't have a timeout, or if we can convert the timeout to + // a `__kernel_old_timespec`, the use `__NR_ppoll`. + fn convert(timeout: &Timespec) -> Option<__kernel_old_timespec> { + Some(__kernel_old_timespec { + tv_sec: timeout.tv_sec.try_into().ok()?, + tv_nsec: timeout.tv_nsec.try_into().ok()?, + }) + } + let old_timeout = if let Some(timeout) = timeout { + match convert(timeout) { + // Could not convert timeout. + None => None, + // Could convert timeout. Ok! + Some(old_timeout) => Some(Some(old_timeout)), + } + } else { + // No timeout. Ok! + Some(None) + }; + if let Some(mut old_timeout) = old_timeout { + // Call `ppoll`. + // + // Linux's `ppoll` mutates the timeout argument. Our public + // interface does not do this, because it's not portable to other + // platforms, so we create a temporary value to hide this behavior. + return ret_usize(syscall!( + __NR_ppoll, + fds_addr_mut, + fds_len, + opt_mut(old_timeout.as_mut()), + zero(), + size_of::<kernel_sigset_t, _>() + )); + } + } + + // We either have Linux 5.1 or the timeout didn't fit in + // `__kernel_old_timespec` so `__NR_ppoll_time64` will either + // succeed or fail due to our having no other options. + + // Call `ppoll_time64`. + // + // Linux's `ppoll_time64` mutates the timeout argument. Our public + // interface does not do this, because it's not portable to other + // platforms, so we create a temporary value to hide this behavior. + ret_usize(syscall!( + __NR_ppoll_time64, + fds_addr_mut, + fds_len, + opt_mut(timeout.copied().as_mut()), + zero(), + size_of::<kernel_sigset_t, _>() + )) + } + + #[cfg(target_pointer_width = "64")] + unsafe { + // Call `ppoll`. + // + // Linux's `ppoll` mutates the timeout argument. Our public interface + // does not do this, because it's not portable to other platforms, so + // we create a temporary value to hide this behavior. + ret_usize(syscall!( + __NR_ppoll, + fds_addr_mut, + fds_len, + opt_mut(timeout.copied().as_mut()), + zero(), + size_of::<kernel_sigset_t, _>() + )) + } +} + +pub(crate) unsafe fn select( + nfds: i32, + readfds: Option<&mut [FdSetElement]>, + writefds: Option<&mut [FdSetElement]>, + exceptfds: Option<&mut [FdSetElement]>, + timeout: Option<&crate::timespec::Timespec>, +) -> io::Result<i32> { + let len = crate::event::fd_set_num_elements_for_bitvector(nfds); + + let readfds = match readfds { + Some(readfds) => { + assert!(readfds.len() >= len); + readfds.as_mut_ptr() + } + None => null_mut(), + }; + let writefds = match writefds { + Some(writefds) => { + assert!(writefds.len() >= len); + writefds.as_mut_ptr() + } + None => null_mut(), + }; + let exceptfds = match exceptfds { + Some(exceptfds) => { + assert!(exceptfds.len() >= len); + exceptfds.as_mut_ptr() + } + None => null_mut(), + }; + + #[cfg(target_pointer_width = "32")] + { + // If we don't have Linux 5.1, and the timeout fits in a + // `__kernel_old_timespec`, use plain `pselect6`. + // + // We do this unconditionally, rather than trying `pselect6_time64` and + // falling back on `Errno::NOSYS`, because seccomp configurations will + // sometimes abort the process on syscalls they don't recognize. + #[cfg(not(feature = "linux_5_1"))] + { + use linux_raw_sys::general::__kernel_old_timespec; + + // If we don't have a timeout, or if we can convert the timeout to + // a `__kernel_old_timespec`, the use `__NR_pselect6`. + fn convert(timeout: &Timespec) -> Option<__kernel_old_timespec> { + Some(__kernel_old_timespec { + tv_sec: timeout.tv_sec.try_into().ok()?, + tv_nsec: timeout.tv_nsec.try_into().ok()?, + }) + } + let old_timeout = if let Some(timeout) = timeout { + match convert(timeout) { + // Could not convert timeout. + None => None, + // Could convert timeout. Ok! + Some(old_timeout) => Some(Some(old_timeout)), + } + } else { + // No timeout. Ok! + Some(None) + }; + if let Some(mut old_timeout) = old_timeout { + // Call `pselect6`. + // + // Linux's `pselect6` mutates the timeout argument. Our public + // interface does not do this, because it's not portable to other + // platforms, so we create a temporary value to hide this behavior. + return ret_c_int(syscall!( + __NR_pselect6, + c_int(nfds), + readfds, + writefds, + exceptfds, + opt_mut(old_timeout.as_mut()), + zero() + )); + } + } + + // We either have Linux 5.1 or the timeout didn't fit in + // `__kernel_old_timespec` so `__NR_pselect6_time64` will either + // succeed or fail due to our having no other options. + + // Call `pselect6_time64`. + // + // Linux's `pselect6_time64` mutates the timeout argument. Our public + // interface does not do this, because it's not portable to other + // platforms, so we create a temporary value to hide this behavior. + ret_c_int(syscall!( + __NR_pselect6_time64, + c_int(nfds), + readfds, + writefds, + exceptfds, + opt_mut(timeout.copied().as_mut()), + zero() + )) + } + + #[cfg(target_pointer_width = "64")] + { + // Call `pselect6`. + // + // Linux's `pselect6` mutates the timeout argument. Our public interface + // does not do this, because it's not portable to other platforms, so we + // create a temporary value to hide this behavior. + ret_c_int(syscall!( + __NR_pselect6, + c_int(nfds), + readfds, + writefds, + exceptfds, + opt_mut(timeout.copied().as_mut()), + zero() + )) + } +} + +#[inline] +pub(crate) fn epoll_create(flags: epoll::CreateFlags) -> io::Result<OwnedFd> { + // SAFETY: `__NR_epoll_create1` doesn't access any user memory. + unsafe { ret_owned_fd(syscall_readonly!(__NR_epoll_create1, flags)) } +} + +#[inline] +pub(crate) fn epoll_add( + epfd: BorrowedFd<'_>, + fd: BorrowedFd<'_>, + event: &epoll::Event, +) -> io::Result<()> { + // SAFETY: `__NR_epoll_ctl` with `EPOLL_CTL_ADD` doesn't modify any user + // memory, and it only reads from `event`. + unsafe { + ret(syscall_readonly!( + __NR_epoll_ctl, + epfd, + c_uint(EPOLL_CTL_ADD), + fd, + by_ref(event) + )) + } +} + +#[inline] +pub(crate) fn epoll_mod( + epfd: BorrowedFd<'_>, + fd: BorrowedFd<'_>, + event: &epoll::Event, +) -> io::Result<()> { + // SAFETY: `__NR_epoll_ctl` with `EPOLL_CTL_MOD` doesn't modify any user + // memory, and it only reads from `event`. + unsafe { + ret(syscall_readonly!( + __NR_epoll_ctl, + epfd, + c_uint(EPOLL_CTL_MOD), + fd, + by_ref(event) + )) + } +} + +#[inline] +pub(crate) fn epoll_del(epfd: BorrowedFd<'_>, fd: BorrowedFd<'_>) -> io::Result<()> { + // SAFETY: `__NR_epoll_ctl` with `EPOLL_CTL_DEL` doesn't access any user + // memory. + unsafe { + ret(syscall_readonly!( + __NR_epoll_ctl, + epfd, + c_uint(EPOLL_CTL_DEL), + fd, + zero() + )) + } +} + +#[inline] +pub(crate) unsafe fn epoll_wait( + epfd: BorrowedFd<'_>, + events: (*mut crate::event::epoll::Event, usize), + timeout: Option<&Timespec>, +) -> io::Result<usize> { + // If we don't have Linux 5.1, and the timeout fits in an `i32`, use plain + // `epoll_pwait`. + // + // We do this unconditionally, rather than trying `epoll_pwait2` and + // falling back on `Errno::NOSYS`, because seccomp configurations will + // sometimes abort the process on syscalls they don't recognize. + #[cfg(not(feature = "linux_5_11"))] + { + // If we don't have a timeout, or if we can convert the timeout to an + // `i32`, the use `__NR_epoll_pwait`. + let old_timeout = if let Some(timeout) = timeout { + // Try to convert the timeout; if this is `Some`, we're ok! + timeout.as_c_int_millis() + } else { + // No timeout. Ok! + Some(-1) + }; + if let Some(old_timeout) = old_timeout { + // Call `epoll_pwait`. + return ret_usize(syscall!( + __NR_epoll_pwait, + epfd, + events.0, + pass_usize(events.1), + c_int(old_timeout), + zero() + )); + } + } + + // Call `epoll_pwait2`. + // + // We either have Linux 5.1 or the timeout didn't fit in an `i32`, so + // `__NR_epoll_pwait2` will either succeed or fail due to our having no + // other options. + ret_usize(syscall!( + __NR_epoll_pwait2, + epfd, + events.0, + pass_usize(events.1), + opt_ref(timeout), + zero() + )) +} + +#[inline] +pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> { + unsafe { ret_owned_fd(syscall_readonly!(__NR_eventfd2, c_uint(initval), flags)) } +} + +#[inline] +pub(crate) fn pause() { + unsafe { + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + let error = ret_error(syscall_readonly!( + __NR_ppoll, + zero(), + zero(), + zero(), + zero() + )); + + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] + let error = ret_error(syscall_readonly!(__NR_pause)); + + debug_assert_eq!(error, io::Errno::INTR); + } +} |
