diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 304ecfc..ef7d339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,16 +62,10 @@ jobs: RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg polling_test_epoll_pipe if: startsWith(matrix.os, 'ubuntu') - run: cargo hack build --feature-powerset --no-dev-deps - - name: Add rust-src - if: startsWith(matrix.rust, 'nightly') - run: rustup component add rust-src # TODO: broken due to https://github.com/rust-lang/rust/pull/119026. # - name: Check selected Tier 3 targets # if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest' # run: cargo check -Z build-std --target=riscv32imc-esp-espidf - - name: Check haiku - if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest' - run: cargo check -Z build-std --target x86_64-unknown-haiku - name: Clone async-io run: git clone https://github.com/smol-rs/async-io.git # The async-io Cargo.toml already has a patch section at the bottom, so we @@ -93,6 +87,9 @@ jobs: run: rustup update stable - name: Install cross uses: taiki-e/install-action@cross + - name: Add rust-src + if: startsWith(matrix.rust, 'nightly') + run: rustup component add rust-src # We don't test BSDs, since we already test them in Cirrus. - name: Android if: startsWith(matrix.os, 'ubuntu') @@ -118,9 +115,17 @@ jobs: rustup target add x86_64-unknown-illumos cargo build --target x86_64-unknown-illumos - name: Redox + if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest' run: | rustup target add x86_64-unknown-redox cargo check --target x86_64-unknown-redox + - name: HermitOS + if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest' + run: | + cargo -Zbuild-std check --target x86_64-unknown-hermit + - name: Check haiku + if: startsWith(matrix.rust, 'nightly') && matrix.os == 'ubuntu-latest' + run: cargo check -Z build-std --target x86_64-unknown-haiku wine: runs-on: ubuntu-22.04 diff --git a/Cargo.toml b/Cargo.toml index ee112a4..3aa2455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,10 @@ rustdoc-args = ["--cfg", "docsrs"] cfg-if = "1" tracing = { version = "0.1.37", default-features = false } -[target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies] -rustix = { version = "0.38.31", features = ["event", "fs", "pipe", "process", "std", "time"], default-features = false } +[target.'cfg(any(unix, target_os = "fuchsia", target_os = "vxworks"))'.dependencies.rustix] +version = "0.38.31" +features = ["event", "fs", "pipe", "process", "std", "time"] +default-features = false [target.'cfg(windows)'.dependencies] concurrent-queue = "2.2.0" @@ -43,6 +45,9 @@ features = [ "Win32_System_WindowsProgramming", ] +[target.'cfg(target_os = "hermit")'.dependencies.hermit-abi] +version = "0.3.9" + [dev-dependencies] easy-parallel = "3.1.0" fastrand = "2.0.0" diff --git a/README.md b/README.md index 93d449a..fab575d 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ https://docs.rs/polling) Portable interface to epoll, kqueue, event ports, and IOCP. Supported platforms: -- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android +- [epoll](https://en.wikipedia.org/wiki/Epoll): Linux, Android, RedoxOS - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD, DragonFly BSD - [event ports](https://illumos.org/man/port_create): illumos, Solaris -- [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems +- [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, HermitOS, other Unix systems - [IOCP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports): Windows, Wine (version 7.13+) Polling is done in oneshot mode, which means interest in I/O events needs to be reset after diff --git a/src/lib.rs b/src/lib.rs index 1a2e449..42c1afe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! - [kqueue](https://en.wikipedia.org/wiki/Kqueue): macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD, //! DragonFly BSD //! - [event ports](https://illumos.org/man/port_create): illumos, Solaris -//! - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, other Unix systems +//! - [poll](https://en.wikipedia.org/wiki/Poll_(Unix)): VxWorks, Fuchsia, HermitOS, other Unix systems //! - [IOCP](https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports): Windows, Wine (version 7.13+) //! //! By default, polling is done in oneshot mode, which means interest in I/O events needs to @@ -107,6 +107,7 @@ cfg_if! { use kqueue as sys; } else if #[cfg(any( target_os = "vxworks", + target_os = "hermit", target_os = "fuchsia", target_os = "horizon", unix, @@ -1017,8 +1018,11 @@ impl fmt::Debug for Poller { } cfg_if! { - if #[cfg(unix)] { + if #[cfg(any(unix, target_os = "hermit"))] { + #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd, AsFd, BorrowedFd}; + #[cfg(target_os = "hermit")] + use std::os::hermit::io::{AsRawFd, RawFd, AsFd, BorrowedFd}; /// A resource with a raw file descriptor. pub trait AsRawSource { diff --git a/src/poll.rs b/src/poll.rs index 0a37ff5..d219538 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -6,8 +6,12 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Condvar, Mutex}; use std::time::{Duration, Instant}; -use rustix::event::{poll, PollFd, PollFlags}; +#[cfg(not(target_os = "hermit"))] use rustix::fd::{AsFd, AsRawFd, BorrowedFd}; +#[cfg(target_os = "hermit")] +use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd}; + +use syscall::{poll, PollFd, PollFlags}; // std::os::unix doesn't exist on Fuchsia type RawFd = std::os::raw::c_int; @@ -443,7 +447,182 @@ fn cvt_mode_as_remove(mode: PollMode) -> io::Result { } } -#[cfg(not(target_os = "espidf"))] +#[cfg(unix)] +mod syscall { + pub(super) use rustix::event::{poll, PollFd, PollFlags}; + + #[cfg(target_os = "espidf")] + pub(super) use rustix::event::{eventfd, EventfdFlags}; + #[cfg(target_os = "espidf")] + pub(super) use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; + #[cfg(target_os = "espidf")] + pub(super) use rustix::io::{read, write}; +} + +#[cfg(target_os = "hermit")] +mod syscall { + // TODO: Remove this shim once HermitOS is supported in Rustix. + + use std::fmt; + use std::io; + use std::marker::PhantomData; + use std::ops::BitOr; + + pub(super) use std::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; + + /// Create an eventfd. + pub(super) fn eventfd(count: u64, _flags: EventfdFlags) -> io::Result { + let fd = unsafe { hermit_abi::eventfd(count, 0) }; + + if fd < 0 { + Err(io::Error::from_raw_os_error(unsafe { + hermit_abi::get_errno() + })) + } else { + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) + } + } + + /// Read some bytes. + pub(super) fn read(fd: BorrowedFd<'_>, bytes: &mut [u8]) -> io::Result { + let count = unsafe { hermit_abi::read(fd.as_raw_fd(), bytes.as_mut_ptr(), bytes.len()) }; + + cvt(count) + } + + /// Write some bytes. + pub(super) fn write(fd: BorrowedFd<'_>, bytes: &[u8]) -> io::Result { + let count = unsafe { hermit_abi::write(fd.as_raw_fd(), bytes.as_ptr(), bytes.len()) }; + + cvt(count) + } + + /// Safe wrapper around the `poll` system call. + pub(super) fn poll(fds: &mut [PollFd<'_>], timeout: i32) -> io::Result { + let call = unsafe { + hermit_abi::poll( + fds.as_mut_ptr() as *mut hermit_abi::pollfd, + fds.len(), + timeout, + ) + }; + + cvt(call as isize) + } + + /// Safe wrapper around `pollfd`. + #[repr(transparent)] + pub(super) struct PollFd<'a> { + inner: hermit_abi::pollfd, + _lt: PhantomData>, + } + + impl<'a> PollFd<'a> { + pub(super) fn from_borrowed_fd(fd: BorrowedFd<'a>, inflags: PollFlags) -> Self { + Self { + inner: hermit_abi::pollfd { + fd: fd.as_raw_fd(), + events: inflags.0, + revents: 0, + }, + _lt: PhantomData, + } + } + + pub(super) fn revents(&self) -> PollFlags { + PollFlags(self.inner.revents) + } + } + + impl AsFd for PollFd<'_> { + fn as_fd(&self) -> BorrowedFd<'_> { + unsafe { BorrowedFd::borrow_raw(self.inner.fd) } + } + } + + impl fmt::Debug for PollFd<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PollFd") + .field("fd", &format_args!("0x{:x}", self.inner.fd)) + .field("events", &PollFlags(self.inner.events)) + .field("revents", &PollFlags(self.inner.revents)) + .finish() + } + } + + /// Wrapper around polling flags. + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub(super) struct PollFlags(i16); + + impl PollFlags { + /// Empty set of flags. + pub(super) const fn empty() -> Self { + Self(0) + } + + pub(super) const IN: PollFlags = PollFlags(hermit_abi::POLLIN); + pub(super) const OUT: PollFlags = PollFlags(hermit_abi::POLLOUT); + pub(super) const WRBAND: PollFlags = PollFlags(hermit_abi::POLLWRBAND); + pub(super) const ERR: PollFlags = PollFlags(hermit_abi::POLLERR); + pub(super) const HUP: PollFlags = PollFlags(hermit_abi::POLLHUP); + pub(super) const PRI: PollFlags = PollFlags(hermit_abi::POLLPRI); + + /// Tell if this contains some flags. + pub(super) fn contains(self, flags: PollFlags) -> bool { + self.0 & flags.0 != 0 + } + + /// Set a flag. + pub(super) fn set(&mut self, flags: PollFlags, set: bool) { + if set { + self.0 |= flags.0; + } else { + self.0 &= !(flags.0); + } + } + + /// Tell if this is empty. + pub(super) fn is_empty(self) -> bool { + self.0 == 0 + } + + /// Tell if this intersects with some flags. + pub(super) fn intersects(self, flags: PollFlags) -> bool { + self.contains(flags) + } + } + + impl BitOr for PollFlags { + type Output = PollFlags; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + + #[derive(Debug, Copy, Clone)] + pub(super) struct EventfdFlags; + + impl EventfdFlags { + pub(super) fn empty() -> Self { + Self + } + } + + /// Convert a number to an actual result. + #[inline] + fn cvt(len: isize) -> io::Result { + if len < 0 { + Err(io::Error::from_raw_os_error(unsafe { + hermit_abi::get_errno() + })) + } else { + Ok(len as usize) + } + } +} + +#[cfg(not(any(target_os = "espidf", target_os = "hermit")))] mod notify { use std::io; @@ -534,16 +713,14 @@ mod notify { } } -#[cfg(target_os = "espidf")] +#[cfg(any(target_os = "espidf", target_os = "hermit"))] mod notify { use std::io; use std::mem; - use rustix::event::PollFlags; - use rustix::event::{eventfd, EventfdFlags}; - - use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; - use rustix::io::{read, write}; + use super::syscall::{ + eventfd, read, write, AsFd, AsRawFd, BorrowedFd, EventfdFlags, OwnedFd, PollFlags, RawFd, + }; /// A notification pipe. /// @@ -573,8 +750,8 @@ mod notify { let flags = EventfdFlags::empty(); let event_fd = eventfd(0, flags).map_err(|err| { - match err { - rustix::io::Errno::PERM => { + match io::Error::from(err) { + err if err.kind() == io::ErrorKind::PermissionDenied => { // EPERM can happen if the eventfd isn't initialized yet. // Tell the user to call esp_vfs_eventfd_register. io::Error::new( @@ -582,7 +759,7 @@ mod notify { "failed to initialize eventfd for polling, try calling `esp_vfs_eventfd_register`" ) }, - err => io::Error::from(err), + err => err, } })?; @@ -601,14 +778,14 @@ mod notify { /// Notifies the `Poller` instance via the eventfd file descriptor. pub(super) fn notify(&self) -> Result<(), io::Error> { - write(&self.event_fd, &1u64.to_ne_bytes())?; + write(self.event_fd.as_fd(), &1u64.to_ne_bytes())?; Ok(()) } /// Pops a notification (if any) from the eventfd file descriptor. pub(super) fn pop_notification(&self) -> Result<(), io::Error> { - read(&self.event_fd, &mut [0; mem::size_of::()])?; + read(self.event_fd.as_fd(), &mut [0; mem::size_of::()])?; Ok(()) }