Skip to content

Commit

Permalink
feat: Add support for HermitOS
Browse files Browse the repository at this point in the history
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 <dev@notgull.net>
  • Loading branch information
notgull committed Mar 13, 2024
1 parent 634a77c commit 50454d1
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 25 deletions.
17 changes: 11 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -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
Expand Down
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
203 changes: 190 additions & 13 deletions src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -443,7 +447,182 @@ fn cvt_mode_as_remove(mode: PollMode) -> io::Result<bool> {
}
}

#[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<OwnedFd> {
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<usize> {
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<usize> {
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<usize> {
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<BorrowedFd<'a>>,
}

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<usize> {
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;

Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -573,16 +750,16 @@ 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(
io::ErrorKind::PermissionDenied,
"failed to initialize eventfd for polling, try calling `esp_vfs_eventfd_register`"
)
},
err => io::Error::from(err),
err => err,
}
})?;

Expand All @@ -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::<u64>()])?;
read(self.event_fd.as_fd(), &mut [0; mem::size_of::<u64>()])?;

Ok(())
}
Expand Down

0 comments on commit 50454d1

Please sign in to comment.