From 50454d1cea00bfbd10233165d60eed621ba6b686 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 12 Mar 2024 21:33:06 -0700 Subject: [PATCH] feat: Add support for HermitOS HermitOS is a microkernel target aiming to provide a simple OS for virtualized applications. It recently added support for the poll() and eventfd() system calls, which means we can target it with our poll() based backend. Hermit does not have a traditional libc; instead it uses hermit-abi. However rustix does not support using hermit-abi as its underlying layer yet. So we have to build a shim layer until it does. Closes #177 cc bytecodealliance/rustix#1012 Signed-off-by: John Nunley --- .github/workflows/ci.yml | 17 ++-- Cargo.toml | 9 +- README.md | 4 +- src/lib.rs | 8 +- src/poll.rs | 203 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 216 insertions(+), 25 deletions(-) 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(()) }