From 27713c1662f6297e0aab24e4a5e5c72a687a3405 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Wed, 13 May 2026 14:49:05 -0400 Subject: [PATCH 1/5] Initial attempt to remove cap-std dep --- crates/wasi-common/Cargo.toml | 8 +- crates/wasi-common/src/file.rs | 12 +- crates/wasi-common/src/lib.rs | 3 - .../src/sync/{stdio.rs => _stdio.rs} | 0 crates/wasi-common/src/sync/file.rs | 10 +- crates/wasi-common/src/sync/mod.rs | 28 +- crates/wasi-common/src/sync/net.rs | 393 ------------------ crates/wasi-common/src/sync/sched.rs | 109 ++++- crates/wasi-common/src/sync/sched/unix.rs | 83 ---- crates/wasi-common/src/sync/sched/windows.rs | 220 ---------- crates/wasi-common/src/tokio/dir.rs | 125 ------ crates/wasi-common/src/tokio/file.rs | 247 ----------- crates/wasi-common/src/tokio/mod.rs | 135 ------ crates/wasi-common/src/tokio/net.rs | 6 - crates/wasi-common/src/tokio/sched.rs | 35 -- crates/wasi-common/src/tokio/sched/unix.rs | 103 ----- crates/wasi-common/src/tokio/sched/windows.rs | 15 - crates/wasi-common/src/tokio/stdio.rs | 1 - crates/wasip1/Cargo.toml | 2 +- crates/wasip1/src/fs/dev/wakeup.rs | 346 ++++++--------- crates/wasip1/src/lib.rs | 14 +- crates/wasmtime/src/wasi_threads.rs | 129 +++--- 22 files changed, 297 insertions(+), 1727 deletions(-) rename crates/wasi-common/src/sync/{stdio.rs => _stdio.rs} (100%) delete mode 100644 crates/wasi-common/src/sync/net.rs delete mode 100644 crates/wasi-common/src/sync/sched/unix.rs delete mode 100644 crates/wasi-common/src/sync/sched/windows.rs delete mode 100644 crates/wasi-common/src/tokio/dir.rs delete mode 100644 crates/wasi-common/src/tokio/file.rs delete mode 100644 crates/wasi-common/src/tokio/mod.rs delete mode 100644 crates/wasi-common/src/tokio/net.rs delete mode 100644 crates/wasi-common/src/tokio/sched.rs delete mode 100644 crates/wasi-common/src/tokio/sched/unix.rs delete mode 100644 crates/wasi-common/src/tokio/sched/windows.rs delete mode 100644 crates/wasi-common/src/tokio/stdio.rs diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index f9e4bf0..58cbc57 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -30,7 +30,7 @@ fs-set-times = { workspace = true, optional = true } system-interface = { workspace = true, features = ["cap_std_impls"], optional = true } io-lifetimes = { workspace = true, optional = true } # Optional, enabled by tokio feature: -tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"], optional = true } +tokio = { workspace = true, features = ["time"], optional = true } cap-std = { workspace = true, optional = true } @@ -74,13 +74,7 @@ sync = [ "dep:system-interface", "dep:io-lifetimes", "use_cap_std", -] -tokio = [ - "sync", - "wasmtime/async", - "wiggle/wasmtime_async", "dep:tokio", - "use_cap_std", ] use_cap_std = ["dep:cap-std"] exit = [ "wasmtime", "dep:libc" ] diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs index 514892e..7d02a87 100644 --- a/crates/wasi-common/src/file.rs +++ b/crates/wasi-common/src/file.rs @@ -1,6 +1,8 @@ use crate::{Error, ErrorExt, SystemTimeSpec}; use bitflags::bitflags; use std::any::Any; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; #[async_trait::async_trait] @@ -8,16 +10,6 @@ pub trait WasiFile: Send + Sync { fn as_any(&self) -> &dyn Any; async fn get_filetype(&self) -> Result; - #[cfg(unix)] - fn pollable(&self) -> Option> { - None - } - - #[cfg(windows)] - fn pollable(&self) -> Option { - None - } - fn isatty(&self) -> bool { false } diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index ee141e6..f39f122 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -84,9 +84,6 @@ mod string_array; #[cfg(feature = "sync")] pub mod sync; pub mod table; -#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] -#[cfg(feature = "tokio")] -pub mod tokio; pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock}; pub use ctx::WasiCtx; diff --git a/crates/wasi-common/src/sync/stdio.rs b/crates/wasi-common/src/sync/_stdio.rs similarity index 100% rename from crates/wasi-common/src/sync/stdio.rs rename to crates/wasi-common/src/sync/_stdio.rs diff --git a/crates/wasi-common/src/sync/file.rs b/crates/wasi-common/src/sync/file.rs index dcb024c..099e4c7 100644 --- a/crates/wasi-common/src/sync/file.rs +++ b/crates/wasi-common/src/sync/file.rs @@ -1,6 +1,6 @@ use crate::{ - Error, ErrorExt, file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, ErrorExt, }; use cap_fs_ext::MetadataExt; use fs_set_times::{SetTimes, SystemTimeSpec}; @@ -25,14 +25,6 @@ impl WasiFile for File { fn as_any(&self) -> &dyn Any { self } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } async fn datasync(&self) -> Result<(), Error> { self.0.sync_data()?; Ok(()) diff --git a/crates/wasi-common/src/sync/mod.rs b/crates/wasi-common/src/sync/mod.rs index 4308cc6..bb58e0c 100644 --- a/crates/wasi-common/src/sync/mod.rs +++ b/crates/wasi-common/src/sync/mod.rs @@ -17,9 +17,7 @@ pub mod clocks; pub mod dir; pub mod file; -pub mod net; pub mod sched; -pub mod stdio; pub use cap_std::ambient_authority; pub use cap_std::fs::Dir; @@ -27,8 +25,7 @@ pub use cap_std::net::TcpListener; pub use clocks::clocks_ctx; pub use sched::sched_ctx; -use self::net::Socket; -use crate::{Error, WasiCtx, WasiFile, file::FileAccessMode, table::Table}; +use crate::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile}; use rand::{Rng, SeedableRng}; use std::mem; use std::path::Path; @@ -89,18 +86,6 @@ impl WasiCtxBuilder { self.ctx.set_stderr(f); self } - pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(Box::new(crate::sync::stdio::stdin())) - } - pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(Box::new(crate::sync::stdio::stdout())) - } - pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(Box::new(crate::sync::stdio::stderr())) - } - pub fn inherit_stdio(&mut self) -> &mut Self { - self.inherit_stdin().inherit_stdout().inherit_stderr() - } pub fn preopened_dir( &mut self, dir: Dir, @@ -110,17 +95,6 @@ impl WasiCtxBuilder { self.ctx.push_preopened_dir(dir, guest_path)?; Ok(self) } - pub fn preopened_socket( - &mut self, - fd: u32, - socket: impl Into, - ) -> Result<&mut Self, Error> { - let socket: Socket = socket.into(); - let file: Box = socket.into(); - self.ctx - .insert_file(fd, file, FileAccessMode::READ | FileAccessMode::WRITE); - Ok(self) - } pub fn build(&mut self) -> WasiCtx { assert!(!self.built); let WasiCtxBuilder { ctx, .. } = mem::replace(self, Self::new()); diff --git a/crates/wasi-common/src/sync/net.rs b/crates/wasi-common/src/sync/net.rs deleted file mode 100644 index 55602f6..0000000 --- a/crates/wasi-common/src/sync/net.rs +++ /dev/null @@ -1,393 +0,0 @@ -use crate::{ - Error, ErrorExt, - file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile}, -}; -#[cfg(windows)] -use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; -use io_lifetimes::AsSocketlike; -#[cfg(unix)] -use io_lifetimes::{AsFd, BorrowedFd}; -#[cfg(windows)] -use io_lifetimes::{AsSocket, BorrowedSocket}; -use std::any::Any; -use std::io; -#[cfg(unix)] -use system_interface::fs::GetSetFdFlags; -use system_interface::io::IoExt; -use system_interface::io::IsReadWrite; -use system_interface::io::ReadReady; - -pub enum Socket { - TcpListener(cap_std::net::TcpListener), - TcpStream(cap_std::net::TcpStream), - #[cfg(unix)] - UnixStream(cap_std::os::unix::net::UnixStream), - #[cfg(unix)] - UnixListener(cap_std::os::unix::net::UnixListener), -} - -impl From for Socket { - fn from(listener: cap_std::net::TcpListener) -> Self { - Self::TcpListener(listener) - } -} - -impl From for Socket { - fn from(stream: cap_std::net::TcpStream) -> Self { - Self::TcpStream(stream) - } -} - -#[cfg(unix)] -impl From for Socket { - fn from(listener: cap_std::os::unix::net::UnixListener) -> Self { - Self::UnixListener(listener) - } -} - -#[cfg(unix)] -impl From for Socket { - fn from(stream: cap_std::os::unix::net::UnixStream) -> Self { - Self::UnixStream(stream) - } -} - -#[cfg(unix)] -impl From for Box { - fn from(listener: Socket) -> Self { - match listener { - Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), - Socket::UnixListener(l) => Box::new(crate::sync::net::UnixListener::from_cap_std(l)), - Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), - Socket::UnixStream(l) => Box::new(crate::sync::net::UnixStream::from_cap_std(l)), - } - } -} - -#[cfg(windows)] -impl From for Box { - fn from(listener: Socket) -> Self { - match listener { - Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), - Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), - } - } -} - -macro_rules! wasi_listen_write_impl { - ($ty:ty, $stream:ty) => { - #[async_trait::async_trait] - impl WasiFile for $ty { - fn as_any(&self) -> &dyn Any { - self - } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } - async fn sock_accept(&self, fdflags: FdFlags) -> Result, Error> { - let (stream, _) = self.0.accept()?; - let mut stream = <$stream>::from_cap_std(stream); - stream.set_fdflags(fdflags).await?; - Ok(Box::new(stream)) - } - async fn get_filetype(&self) -> Result { - Ok(FileType::SocketStream) - } - #[cfg(unix)] - async fn get_fdflags(&self) -> Result { - let fdflags = get_fd_flags(&self.0)?; - Ok(fdflags) - } - async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags == crate::file::FdFlags::NONBLOCK { - self.0.set_nonblocking(true)?; - } else if fdflags.is_empty() { - self.0.set_nonblocking(false)?; - } else { - return Err( - Error::invalid_argument().context("cannot set anything else than NONBLOCK") - ); - } - Ok(()) - } - fn num_ready_bytes(&self) -> Result { - Ok(1) - } - } - - #[cfg(windows)] - impl AsSocket for $ty { - #[inline] - fn as_socket(&self) -> BorrowedSocket<'_> { - self.0.as_socket() - } - } - - #[cfg(windows)] - impl AsRawHandleOrSocket for $ty { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() - } - } - - #[cfg(unix)] - impl AsFd for $ty { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } - } - }; -} - -pub struct TcpListener(cap_std::net::TcpListener); - -impl TcpListener { - pub fn from_cap_std(cap_std: cap_std::net::TcpListener) -> Self { - TcpListener(cap_std) - } -} -wasi_listen_write_impl!(TcpListener, TcpStream); - -#[cfg(unix)] -pub struct UnixListener(cap_std::os::unix::net::UnixListener); - -#[cfg(unix)] -impl UnixListener { - pub fn from_cap_std(cap_std: cap_std::os::unix::net::UnixListener) -> Self { - UnixListener(cap_std) - } -} - -#[cfg(unix)] -wasi_listen_write_impl!(UnixListener, UnixStream); - -macro_rules! wasi_stream_write_impl { - ($ty:ty, $std_ty:ty) => { - #[async_trait::async_trait] - impl WasiFile for $ty { - fn as_any(&self) -> &dyn Any { - self - } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } - async fn get_filetype(&self) -> Result { - Ok(FileType::SocketStream) - } - #[cfg(unix)] - async fn get_fdflags(&self) -> Result { - let fdflags = get_fd_flags(&self.0)?; - Ok(fdflags) - } - async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags == crate::file::FdFlags::NONBLOCK { - self.0.set_nonblocking(true)?; - } else if fdflags.is_empty() { - self.0.set_nonblocking(false)?; - } else { - return Err( - Error::invalid_argument().context("cannot set anything else than NONBLOCK") - ); - } - Ok(()) - } - async fn read_vectored<'a>( - &self, - bufs: &mut [io::IoSliceMut<'a>], - ) -> Result { - use std::io::Read; - let n = Read::read_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs)?; - Ok(n.try_into()?) - } - async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - use std::io::Write; - let n = Write::write_vectored(&mut &*self.as_socketlike_view::<$std_ty>(), bufs)?; - Ok(n.try_into()?) - } - async fn peek(&self, buf: &mut [u8]) -> Result { - let n = self.0.peek(buf)?; - Ok(n.try_into()?) - } - fn num_ready_bytes(&self) -> Result { - let val = self.as_socketlike_view::<$std_ty>().num_ready_bytes()?; - Ok(val) - } - async fn readable(&self) -> Result<(), Error> { - let (readable, _writeable) = is_read_write(&self.0)?; - if readable { Ok(()) } else { Err(Error::io()) } - } - async fn writable(&self) -> Result<(), Error> { - let (_readable, writeable) = is_read_write(&self.0)?; - if writeable { Ok(()) } else { Err(Error::io()) } - } - - async fn sock_recv<'a>( - &self, - ri_data: &mut [std::io::IoSliceMut<'a>], - ri_flags: RiFlags, - ) -> Result<(u64, RoFlags), Error> { - if (ri_flags & !(RiFlags::RECV_PEEK | RiFlags::RECV_WAITALL)) != RiFlags::empty() { - return Err(Error::not_supported()); - } - - if ri_flags.contains(RiFlags::RECV_PEEK) { - if let Some(first) = ri_data.iter_mut().next() { - let n = self.0.peek(first)?; - return Ok((n as u64, RoFlags::empty())); - } else { - return Ok((0, RoFlags::empty())); - } - } - - if ri_flags.contains(RiFlags::RECV_WAITALL) { - let n: usize = ri_data.iter().map(|buf| buf.len()).sum(); - self.0.read_exact_vectored(ri_data)?; - return Ok((n as u64, RoFlags::empty())); - } - - let n = self.0.read_vectored(ri_data)?; - Ok((n as u64, RoFlags::empty())) - } - - async fn sock_send<'a>( - &self, - si_data: &[std::io::IoSlice<'a>], - si_flags: SiFlags, - ) -> Result { - if si_flags != SiFlags::empty() { - return Err(Error::not_supported()); - } - - let n = self.0.write_vectored(si_data)?; - Ok(n as u64) - } - - async fn sock_shutdown(&self, how: SdFlags) -> Result<(), Error> { - let how = if how == SdFlags::RD | SdFlags::WR { - cap_std::net::Shutdown::Both - } else if how == SdFlags::RD { - cap_std::net::Shutdown::Read - } else if how == SdFlags::WR { - cap_std::net::Shutdown::Write - } else { - return Err(Error::invalid_argument()); - }; - self.0.shutdown(how)?; - Ok(()) - } - } - #[cfg(unix)] - impl AsFd for $ty { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } - } - - #[cfg(windows)] - impl AsSocket for $ty { - /// Borrows the socket. - fn as_socket(&self) -> BorrowedSocket<'_> { - self.0.as_socket() - } - } - - #[cfg(windows)] - impl AsRawHandleOrSocket for TcpStream { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() - } - } - }; -} - -pub struct TcpStream(cap_std::net::TcpStream); - -impl TcpStream { - pub fn from_cap_std(socket: cap_std::net::TcpStream) -> Self { - TcpStream(socket) - } -} - -wasi_stream_write_impl!(TcpStream, std::net::TcpStream); - -#[cfg(unix)] -pub struct UnixStream(cap_std::os::unix::net::UnixStream); - -#[cfg(unix)] -impl UnixStream { - pub fn from_cap_std(socket: cap_std::os::unix::net::UnixStream) -> Self { - UnixStream(socket) - } -} - -#[cfg(unix)] -wasi_stream_write_impl!(UnixStream, std::os::unix::net::UnixStream); - -pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { - use cap_fs_ext::FileTypeExt; - if ft.is_block_device() { - FileType::SocketDgram - } else { - FileType::SocketStream - } -} - -/// Return the file-descriptor flags for a given file-like object. -/// -/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. -pub fn get_fd_flags(f: Socketlike) -> io::Result { - // On Unix-family platforms, we can use the same system call that we'd use - // for files on sockets here. - #[cfg(not(windows))] - { - let mut out = crate::file::FdFlags::empty(); - if f.get_fd_flags()? - .contains(system_interface::fs::FdFlags::NONBLOCK) - { - out |= crate::file::FdFlags::NONBLOCK; - } - Ok(out) - } - - // On Windows, sockets are different, and there is no direct way to - // query for the non-blocking flag. We can get a sufficient approximation - // by testing whether a zero-length `recv` appears to block. - #[cfg(windows)] - let buf: &mut [u8] = &mut []; - #[cfg(windows)] - match rustix::net::recv(f, buf, rustix::net::RecvFlags::empty()) { - Ok(_) => Ok(crate::file::FdFlags::empty()), - Err(rustix::io::Errno::WOULDBLOCK) => Ok(crate::file::FdFlags::NONBLOCK), - Err(e) => Err(e.into()), - } -} - -/// Return the file-descriptor flags for a given file-like object. -/// -/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. -pub fn is_read_write(f: Socketlike) -> io::Result<(bool, bool)> { - // On Unix-family platforms, we have an `IsReadWrite` impl. - #[cfg(not(windows))] - { - f.is_read_write() - } - - // On Windows, we only have a `TcpStream` impl, so make a view first. - #[cfg(windows)] - { - f.as_socketlike_view::() - .is_read_write() - } -} diff --git a/crates/wasi-common/src/sync/sched.rs b/crates/wasi-common/src/sync/sched.rs index 1b2fa9f..1c36c41 100644 --- a/crates/wasi-common/src/sync/sched.rs +++ b/crates/wasi-common/src/sync/sched.rs @@ -1,17 +1,13 @@ -#[cfg(unix)] -pub mod unix; -#[cfg(unix)] -pub use unix::poll_oneoff; - -#[cfg(windows)] -pub mod windows; -#[cfg(windows)] -pub use windows::poll_oneoff; - use crate::{ + sched::{ + subscription::{RwEventFlags, Subscription}, + Poll, WasiSched, + }, Error, - sched::{Poll, WasiSched}, }; +use std::future::{self, Future}; +use std::pin::{pin, Pin}; +use std::task::{Context, Poll as FPoll}; use std::thread; use std::time::Duration; @@ -24,7 +20,63 @@ impl SyncSched { #[async_trait::async_trait] impl WasiSched for SyncSched { async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { - poll_oneoff(poll).await + if poll.is_empty() { + return Ok(()); + } + + let duration = poll + .earliest_clock_deadline() + .map(|sub| sub.duration_until()); + + let mut futures = FirstReady::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(f) => { + futures.push(async move { + f.file + .readable() + .await + .map_err(|e| e.context("readable future"))?; + f.complete( + f.file + .num_ready_bytes() + .map_err(|e| e.context("read num_ready_bytes"))?, + RwEventFlags::empty(), + ); + Ok::<(), Error>(()) + }); + } + + Subscription::Write(f) => { + futures.push(async move { + f.file + .writable() + .await + .map_err(|e| e.context("writable future"))?; + f.complete(0, RwEventFlags::empty()); + Ok(()) + }); + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + match duration { + Some(Some(remaining)) => match tokio::time::timeout(remaining, futures).await { + Ok(r) => r?, + Err(_deadline_elapsed) => {} + }, + Some(None) => { + let mut futures = pin!(futures); + future::poll_fn(|cx| match futures.as_mut().poll(cx) { + FPoll::Ready(e) => FPoll::Ready(e), + FPoll::Pending => FPoll::Ready(Ok(())), + }) + .await? + } + None => futures.await?, + } + + Ok(()) } async fn sched_yield(&self) -> Result<(), Error> { thread::yield_now(); @@ -38,3 +90,36 @@ impl WasiSched for SyncSched { pub fn sched_ctx() -> Box { Box::new(SyncSched::new()) } + +struct FirstReady<'a, T>(Vec + Send + 'a>>>); + +impl<'a, T> FirstReady<'a, T> { + fn new() -> Self { + FirstReady(Vec::new()) + } + fn push(&mut self, f: impl Future + Send + 'a) { + self.0.push(Box::pin(f)); + } +} + +impl<'a, T> Future for FirstReady<'a, T> { + type Output = T; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> FPoll { + let mut result = FPoll::Pending; + for f in self.as_mut().0.iter_mut() { + match f.as_mut().poll(cx) { + FPoll::Ready(r) => match result { + // First ready gets to set the result. But, continue the loop so all futures + // which are ready simultaneously (often on first poll) get to report their + // readiness. + FPoll::Pending => { + result = FPoll::Ready(r); + } + _ => {} + }, + _ => continue, + } + } + return result; + } +} diff --git a/crates/wasi-common/src/sync/sched/unix.rs b/crates/wasi-common/src/sync/sched/unix.rs deleted file mode 100644 index a20fb23..0000000 --- a/crates/wasi-common/src/sync/sched/unix.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::sched::subscription::{RwEventFlags, Subscription}; -use crate::{Error, ErrorExt, sched::Poll}; -use cap_std::time::Duration; -use rustix::event::{PollFd, PollFlags}; - -pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); - } - let mut pollfds = Vec::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(f) => { - let fd = f - .file - .pollable() - .ok_or(Error::invalid_argument().context("file is not pollable"))?; - pollfds.push(PollFd::from_borrowed_fd(fd, PollFlags::IN)); - } - - Subscription::Write(f) => { - let fd = f - .file - .pollable() - .ok_or(Error::invalid_argument().context("file is not pollable"))?; - pollfds.push(PollFd::from_borrowed_fd(fd, PollFlags::OUT)); - } - Subscription::MonotonicClock { .. } => unreachable!(), - } - } - - let ready = loop { - let poll_timeout = if let Some(t) = poll.earliest_clock_deadline() { - let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); - Some( - duration - .try_into() - .map_err(|_| Error::overflow().context("poll timeout"))?, - ) - } else { - None - }; - tracing::debug!( - poll_timeout = tracing::field::debug(poll_timeout), - poll_fds = tracing::field::debug(&pollfds), - "poll" - ); - match rustix::event::poll(&mut pollfds, poll_timeout.as_ref()) { - Ok(ready) => break ready, - Err(rustix::io::Errno::INTR) => continue, - Err(err) => return Err(std::io::Error::from(err).into()), - } - }; - if ready > 0 { - for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { - let revents = pollfd.revents(); - let (nbytes, rwsub) = match rwsub { - Subscription::Read(sub) => { - let ready = sub.file.num_ready_bytes()?; - (std::cmp::max(ready, 1), sub) - } - Subscription::Write(sub) => (0, sub), - _ => unreachable!(), - }; - if revents.contains(PollFlags::NVAL) { - rwsub.error(Error::badf()); - } else if revents.contains(PollFlags::ERR) { - rwsub.error(Error::io()); - } else if revents.contains(PollFlags::HUP) { - rwsub.complete(nbytes, RwEventFlags::HANGUP); - } else { - rwsub.complete(nbytes, RwEventFlags::empty()); - }; - } - } else { - poll.earliest_clock_deadline() - .expect("timed out") - .result() - .expect("timer deadline is past") - .unwrap() - } - Ok(()) -} diff --git a/crates/wasi-common/src/sync/sched/windows.rs b/crates/wasi-common/src/sync/sched/windows.rs deleted file mode 100644 index a6f4f1f..0000000 --- a/crates/wasi-common/src/sync/sched/windows.rs +++ /dev/null @@ -1,220 +0,0 @@ -// The windows scheduler is unmaintained and due for a rewrite. -// -// Rather than use a polling mechanism for file read/write readiness, -// it checks readiness just once, before sleeping for any timer subscriptions. -// Checking stdin readiness uses a worker thread which, once started, lives for the -// lifetime of the process. -// -// We suspect there are bugs in this scheduler, however, we have not -// taken the time to improve it. See bug #2880. - -use crate::sched::subscription::{RwEventFlags, Subscription}; -use crate::{EnvError, Error, ErrorExt, file::WasiFile, sched::Poll}; -use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; -use std::sync::{LazyLock, Mutex}; -use std::thread; -use std::time::Duration; - -pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - poll_oneoff_(poll, wasi_file_is_stdin).await -} - -pub async fn poll_oneoff_<'a>( - poll: &mut Poll<'a>, - file_is_stdin: impl Fn(&dyn WasiFile) -> bool, -) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); - } - - let mut ready = false; - let waitmode = if let Some(t) = poll.earliest_clock_deadline() { - if let Some(duration) = t.duration_until() { - WaitMode::Timeout(duration) - } else { - WaitMode::Immediate - } - } else { - if ready { - WaitMode::Immediate - } else { - WaitMode::Infinite - } - }; - - let mut stdin_read_subs = Vec::new(); - let mut immediate_reads = Vec::new(); - let mut immediate_writes = Vec::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(r) => { - if file_is_stdin(r.file) { - stdin_read_subs.push(r); - } else if r.file.pollable().is_some() { - immediate_reads.push(r); - } else { - return Err(Error::invalid_argument().context("file is not pollable")); - } - } - Subscription::Write(w) => { - if w.file.pollable().is_some() { - immediate_writes.push(w); - } else { - return Err(Error::invalid_argument().context("file is not pollable")); - } - } - Subscription::MonotonicClock { .. } => unreachable!(), - } - } - - if !stdin_read_subs.is_empty() { - let state = STDIN_POLL - .lock() - .map_err(|_| Error::trap(EnvError::msg("failed to take lock of STDIN_POLL")))? - .poll(waitmode)?; - for readsub in stdin_read_subs.into_iter() { - match state { - PollState::Ready => { - readsub.complete(1, RwEventFlags::empty()); - ready = true; - } - PollState::NotReady | PollState::TimedOut => {} - PollState::Error(ref e) => { - // Unfortunately, we need to deliver the Error to each of the - // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the - // kind, and then back to std::io::Error, and finally to EnvError. - // When its time to turn this into an errno elsewhere, the error kind will - // be inspected. - let ekind = e.kind(); - let ioerror = std::io::Error::from(ekind); - readsub.error(ioerror.into()); - ready = true; - } - } - } - } - for r in immediate_reads { - match r.file.num_ready_bytes() { - Ok(ready_bytes) => { - r.complete(ready_bytes, RwEventFlags::empty()); - ready = true; - } - Err(e) => { - r.error(e); - ready = true; - } - } - } - for w in immediate_writes { - // Everything is always ready for writing, apparently? - w.complete(0, RwEventFlags::empty()); - ready = true; - } - - if !ready { - if let WaitMode::Timeout(duration) = waitmode { - thread::sleep(duration); - } - } - - Ok(()) -} - -pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { - f.as_any().is::() -} - -enum PollState { - Ready, - NotReady, // Not ready, but did not wait - TimedOut, // Not ready, waited until timeout - Error(std::io::Error), -} - -#[derive(Copy, Clone)] -enum WaitMode { - Timeout(Duration), - Infinite, - Immediate, -} - -struct StdinPoll { - request_tx: Sender<()>, - notify_rx: Receiver, -} - -static STDIN_POLL: LazyLock> = LazyLock::new(StdinPoll::new); - -impl StdinPoll { - pub fn new() -> Mutex { - let (request_tx, request_rx) = mpsc::channel(); - let (notify_tx, notify_rx) = mpsc::channel(); - thread::spawn(move || Self::event_loop(request_rx, notify_tx)); - Mutex::new(StdinPoll { - request_tx, - notify_rx, - }) - } - - // This function should not be used directly. - // Correctness of this function crucially depends on the fact that - // mpsc::Receiver is !Sync. - fn poll(&self, wait_mode: WaitMode) -> Result { - match self.notify_rx.try_recv() { - // Clean up possibly unread result from previous poll. - Ok(_) | Err(TryRecvError::Empty) => {} - Err(TryRecvError::Disconnected) => { - return Err(Error::trap(EnvError::msg( - "StdinPoll notify_rx channel closed", - ))); - } - } - - // Notify the worker thread to poll stdin - self.request_tx - .send(()) - .map_err(|_| Error::trap(EnvError::msg("request_tx channel closed")))?; - - // Wait for the worker thread to send a readiness notification - match wait_mode { - WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) { - Ok(r) => Ok(r), - Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut), - Err(RecvTimeoutError::Disconnected) => Err(Error::trap(EnvError::msg( - "StdinPoll notify_rx channel closed", - ))), - }, - WaitMode::Infinite => self - .notify_rx - .recv() - .map_err(|_| Error::trap(EnvError::msg("StdinPoll notify_rx channel closed"))), - WaitMode::Immediate => match self.notify_rx.try_recv() { - Ok(r) => Ok(r), - Err(TryRecvError::Empty) => Ok(PollState::NotReady), - Err(TryRecvError::Disconnected) => Err(Error::trap(EnvError::msg( - "StdinPoll notify_rx channel closed", - ))), - }, - } - } - - fn event_loop(request_rx: Receiver<()>, notify_tx: Sender) -> ! { - use std::io::BufRead; - loop { - // Wait on a request: - request_rx.recv().expect("request_rx channel"); - // Wait for data to appear in stdin. If fill_buf returns any slice, it means - // that either: - // (a) there is some data in stdin, if non-empty, - // (b) EOF was received, if its empty - // Linux returns `POLLIN` in both cases, so we imitate this behavior. - let resp = match std::io::stdin().lock().fill_buf() { - Ok(_) => PollState::Ready, - Err(e) => PollState::Error(e), - }; - // Notify about data in stdin. If the read on this channel has timed out, the - // next poller will have to clean the channel. - notify_tx.send(resp).expect("notify_tx channel"); - } - } -} diff --git a/crates/wasi-common/src/tokio/dir.rs b/crates/wasi-common/src/tokio/dir.rs deleted file mode 100644 index aadc8de..0000000 --- a/crates/wasi-common/src/tokio/dir.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::tokio::{block_on_dummy_executor, file::File}; -use crate::{ - dir::{ReaddirCursor, ReaddirEntity, WasiDir}, - file::{FdFlags, Filestat, OFlags}, - Error, ErrorExt, -}; -use std::any::Any; -use std::path::PathBuf; - -pub struct Dir(crate::sync::dir::Dir); - -impl Dir { - pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { - Dir(crate::sync::dir::Dir::from_cap_std(dir)) - } -} - -#[async_trait::async_trait] -impl WasiDir for Dir { - fn as_any(&self) -> &dyn Any { - self - } - async fn open_file( - &self, - symlink_follow: bool, - path: &str, - oflags: OFlags, - read: bool, - write: bool, - fdflags: FdFlags, - ) -> Result { - let f = block_on_dummy_executor(move || async move { - self.0 - .open_file_(symlink_follow, path, oflags, read, write, fdflags) - })?; - match f { - crate::sync::dir::OpenResult::File(f) => { - Ok(crate::dir::OpenResult::File(Box::new(File::from_inner(f)))) - } - crate::sync::dir::OpenResult::Dir(d) => { - Ok(crate::dir::OpenResult::Dir(Box::new(Dir(d)))) - } - } - } - - async fn create_dir(&self, path: &str) -> Result<(), Error> { - block_on_dummy_executor(|| self.0.create_dir(path)) - } - async fn readdir( - &self, - cursor: ReaddirCursor, - ) -> Result> + Send>, Error> { - struct I(Box> + Send>); - impl Iterator for I { - type Item = Result; - fn next(&mut self) -> Option { - tokio::task::block_in_place(move || self.0.next()) - } - } - - let inner = block_on_dummy_executor(move || self.0.readdir(cursor))?; - Ok(Box::new(I(inner))) - } - - async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.symlink(src_path, dest_path)) - } - async fn remove_dir(&self, path: &str) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.remove_dir(path)) - } - - async fn unlink_file(&self, path: &str) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.unlink_file(path)) - } - async fn read_link(&self, path: &str) -> Result { - block_on_dummy_executor(move || self.0.read_link(path)) - } - async fn get_filestat(&self) -> Result { - block_on_dummy_executor(|| self.0.get_filestat()) - } - async fn get_path_filestat( - &self, - path: &str, - follow_symlinks: bool, - ) -> Result { - block_on_dummy_executor(move || self.0.get_path_filestat(path, follow_symlinks)) - } - async fn rename( - &self, - src_path: &str, - dest_dir: &dyn WasiDir, - dest_path: &str, - ) -> Result<(), Error> { - let dest_dir = dest_dir - .as_any() - .downcast_ref::() - .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; - block_on_dummy_executor( - move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) }, - ) - } - async fn hard_link( - &self, - src_path: &str, - target_dir: &dyn WasiDir, - target_path: &str, - ) -> Result<(), Error> { - let target_dir = target_dir - .as_any() - .downcast_ref::() - .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; - block_on_dummy_executor(move || async move { - self.0.hard_link_(src_path, &target_dir.0, target_path) - }) - } - async fn set_times( - &self, - path: &str, - atime: Option, - mtime: Option, - follow_symlinks: bool, - ) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks)) - } -} diff --git a/crates/wasi-common/src/tokio/file.rs b/crates/wasi-common/src/tokio/file.rs deleted file mode 100644 index fc13ed9..0000000 --- a/crates/wasi-common/src/tokio/file.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::tokio::block_on_dummy_executor; -use crate::{ - Error, - file::{Advice, FdFlags, FileType, Filestat, WasiFile}, -}; -#[cfg(windows)] -use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; -#[cfg(not(windows))] -use io_lifetimes::AsFd; -use std::any::Any; -use std::borrow::Borrow; -use std::io; - -pub struct File(crate::sync::file::File); - -impl File { - pub(crate) fn from_inner(file: crate::sync::file::File) -> Self { - File(file) - } - pub fn from_cap_std(file: cap_std::fs::File) -> Self { - Self::from_inner(crate::sync::file::File::from_cap_std(file)) - } -} - -pub struct TcpListener(crate::sync::net::TcpListener); - -impl TcpListener { - pub(crate) fn from_inner(listener: crate::sync::net::TcpListener) -> Self { - TcpListener(listener) - } - pub fn from_cap_std(listener: cap_std::net::TcpListener) -> Self { - Self::from_inner(crate::sync::net::TcpListener::from_cap_std(listener)) - } -} - -pub struct TcpStream(crate::sync::net::TcpStream); - -impl TcpStream { - pub(crate) fn from_inner(stream: crate::sync::net::TcpStream) -> Self { - TcpStream(stream) - } - pub fn from_cap_std(stream: cap_std::net::TcpStream) -> Self { - Self::from_inner(crate::sync::net::TcpStream::from_cap_std(stream)) - } -} - -#[cfg(unix)] -pub struct UnixListener(crate::sync::net::UnixListener); - -#[cfg(unix)] -impl UnixListener { - pub(crate) fn from_inner(listener: crate::sync::net::UnixListener) -> Self { - UnixListener(listener) - } - pub fn from_cap_std(listener: cap_std::os::unix::net::UnixListener) -> Self { - Self::from_inner(crate::sync::net::UnixListener::from_cap_std(listener)) - } -} - -#[cfg(unix)] -pub struct UnixStream(crate::sync::net::UnixStream); - -#[cfg(unix)] -impl UnixStream { - fn from_inner(stream: crate::sync::net::UnixStream) -> Self { - UnixStream(stream) - } - pub fn from_cap_std(stream: cap_std::os::unix::net::UnixStream) -> Self { - Self::from_inner(crate::sync::net::UnixStream::from_cap_std(stream)) - } -} - -pub struct Stdin(crate::sync::stdio::Stdin); - -pub fn stdin() -> Stdin { - Stdin(crate::sync::stdio::stdin()) -} - -pub struct Stdout(crate::sync::stdio::Stdout); - -pub fn stdout() -> Stdout { - Stdout(crate::sync::stdio::stdout()) -} - -pub struct Stderr(crate::sync::stdio::Stderr); - -pub fn stderr() -> Stderr { - Stderr(crate::sync::stdio::stderr()) -} - -macro_rules! wasi_file_impl { - ($ty:ty) => { - #[async_trait::async_trait] - impl WasiFile for $ty { - fn as_any(&self) -> &dyn Any { - self - } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } - async fn datasync(&self) -> Result<(), Error> { - block_on_dummy_executor(|| self.0.datasync()) - } - async fn sync(&self) -> Result<(), Error> { - block_on_dummy_executor(|| self.0.sync()) - } - async fn get_filetype(&self) -> Result { - block_on_dummy_executor(|| self.0.get_filetype()) - } - async fn get_fdflags(&self) -> Result { - block_on_dummy_executor(|| self.0.get_fdflags()) - } - async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - block_on_dummy_executor(|| self.0.set_fdflags(fdflags)) - } - async fn get_filestat(&self) -> Result { - block_on_dummy_executor(|| self.0.get_filestat()) - } - async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.set_filestat_size(size)) - } - async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.advise(offset, len, advice)) - } - async fn read_vectored<'a>( - &self, - bufs: &mut [io::IoSliceMut<'a>], - ) -> Result { - block_on_dummy_executor(move || self.0.read_vectored(bufs)) - } - async fn read_vectored_at<'a>( - &self, - bufs: &mut [io::IoSliceMut<'a>], - offset: u64, - ) -> Result { - block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset)) - } - async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - block_on_dummy_executor(move || self.0.write_vectored(bufs)) - } - async fn write_vectored_at<'a>( - &self, - bufs: &[io::IoSlice<'a>], - offset: u64, - ) -> Result { - if bufs.iter().map(|i| i.len()).sum::() == 0 { - return Ok(0); - } - block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset)) - } - async fn seek(&self, pos: std::io::SeekFrom) -> Result { - block_on_dummy_executor(move || self.0.seek(pos)) - } - async fn peek(&self, buf: &mut [u8]) -> Result { - block_on_dummy_executor(move || self.0.peek(buf)) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - block_on_dummy_executor(move || self.0.set_times(atime, mtime)) - } - fn num_ready_bytes(&self) -> Result { - self.0.num_ready_bytes() - } - fn isatty(&self) -> bool { - self.0.isatty() - } - - #[cfg(not(windows))] - async fn readable(&self) -> Result<(), Error> { - // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. - // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce - // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this - // async method to ensure this is the only Future which can access the RawFd during the - // lifetime of the AsyncFd. - use std::os::unix::io::AsRawFd; - use tokio::io::{Interest, unix::AsyncFd}; - let rawfd = self.0.borrow().as_fd().as_raw_fd(); - match AsyncFd::with_interest(rawfd, Interest::READABLE) { - Ok(asyncfd) => { - let _ = asyncfd.readable().await?; - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - // if e is EPERM, this file isn't supported by epoll because it is immediately - // available for reading: - Ok(()) - } - Err(e) => Err(e.into()), - } - } - - #[cfg(not(windows))] - async fn writable(&self) -> Result<(), Error> { - // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. - // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce - // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this - // async method to ensure this is the only Future which can access the RawFd during the - // lifetime of the AsyncFd. - use std::os::unix::io::AsRawFd; - use tokio::io::{Interest, unix::AsyncFd}; - let rawfd = self.0.borrow().as_fd().as_raw_fd(); - match AsyncFd::with_interest(rawfd, Interest::WRITABLE) { - Ok(asyncfd) => { - let _ = asyncfd.writable().await?; - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - // if e is EPERM, this file isn't supported by epoll because it is immediately - // available for writing: - Ok(()) - } - Err(e) => Err(e.into()), - } - } - - async fn sock_accept(&self, fdflags: FdFlags) -> Result, Error> { - block_on_dummy_executor(|| self.0.sock_accept(fdflags)) - } - } - #[cfg(windows)] - impl AsRawHandleOrSocket for $ty { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.borrow().as_raw_handle_or_socket() - } - } - }; -} - -wasi_file_impl!(File); -wasi_file_impl!(TcpListener); -wasi_file_impl!(TcpStream); -#[cfg(unix)] -wasi_file_impl!(UnixListener); -#[cfg(unix)] -wasi_file_impl!(UnixStream); -wasi_file_impl!(Stdin); -wasi_file_impl!(Stdout); -wasi_file_impl!(Stderr); diff --git a/crates/wasi-common/src/tokio/mod.rs b/crates/wasi-common/src/tokio/mod.rs deleted file mode 100644 index ce78658..0000000 --- a/crates/wasi-common/src/tokio/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -mod dir; -mod file; -pub mod net; -pub mod sched; -pub mod stdio; - -use self::sched::sched_ctx; -use crate::sync::net::Socket; -pub use crate::sync::{clocks_ctx, random_ctx}; -use crate::{Error, Table, WasiCtx, WasiFile, file::FileAccessMode}; -pub use dir::Dir; -pub use file::File; -pub use net::*; -use std::future::Future; -use std::mem; -use std::path::Path; - -pub struct WasiCtxBuilder { - ctx: WasiCtx, - built: bool, -} - -impl WasiCtxBuilder { - pub fn new() -> Self { - WasiCtxBuilder { - ctx: WasiCtx::new(random_ctx(), clocks_ctx(), sched_ctx(), Table::new()), - built: false, - } - } - pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> { - self.ctx.push_env(var, value)?; - Ok(self) - } - pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> { - for (k, v) in env { - self.ctx.push_env(k, v)?; - } - Ok(self) - } - pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> { - for (key, value) in std::env::vars() { - self.ctx.push_env(&key, &value)?; - } - Ok(self) - } - pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> { - self.ctx.push_arg(arg)?; - Ok(self) - } - pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> { - for a in arg { - self.ctx.push_arg(&a)?; - } - Ok(self) - } - pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> { - for arg in std::env::args() { - self.ctx.push_arg(&arg)?; - } - Ok(self) - } - pub fn stdin(&mut self, f: Box) -> &mut Self { - self.ctx.set_stdin(f); - self - } - pub fn stdout(&mut self, f: Box) -> &mut Self { - self.ctx.set_stdout(f); - self - } - pub fn stderr(&mut self, f: Box) -> &mut Self { - self.ctx.set_stderr(f); - self - } - pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(Box::new(crate::tokio::stdio::stdin())) - } - pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(Box::new(crate::tokio::stdio::stdout())) - } - pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(Box::new(crate::tokio::stdio::stderr())) - } - pub fn inherit_stdio(&mut self) -> &mut Self { - self.inherit_stdin().inherit_stdout().inherit_stderr() - } - pub fn preopened_dir( - &mut self, - dir: cap_std::fs::Dir, - guest_path: impl AsRef, - ) -> Result<&mut Self, Error> { - let dir = Box::new(crate::tokio::dir::Dir::from_cap_std(dir)); - self.ctx.push_preopened_dir(dir, guest_path)?; - Ok(self) - } - pub fn preopened_socket( - &mut self, - fd: u32, - socket: impl Into, - ) -> Result<&mut Self, Error> { - let socket: Socket = socket.into(); - let file: Box = socket.into(); - self.ctx - .insert_file(fd, file, FileAccessMode::READ | FileAccessMode::WRITE); - Ok(self) - } - - pub fn build(&mut self) -> WasiCtx { - assert!(!self.built); - let WasiCtxBuilder { ctx, .. } = mem::replace(self, Self::new()); - self.built = true; - ctx - } -} - -// Much of this mod is implemented in terms of `async` methods from the -// wasmtime_wasi::p2::sync module. These methods may be async in signature, however, -// they are synchronous in implementation (always Poll::Ready on first poll) -// and perform blocking syscalls. -// -// This function takes this blocking code and executes it using a dummy executor -// to assert its immediate readiness. We tell tokio this is a blocking operation -// with the block_in_place function. -pub(crate) fn block_on_dummy_executor<'a, F, Fut, T>(f: F) -> Result -where - F: FnOnce() -> Fut + Send + 'a, - Fut: Future>, - T: Send + 'static, -{ - tokio::task::block_in_place(move || { - wiggle::run_in_dummy_executor(f()).expect("wrapped operation should be synchronous") - }) -} - -#[cfg(feature = "wasmtime")] -super::define_wasi!(async T: Send); diff --git a/crates/wasi-common/src/tokio/net.rs b/crates/wasi-common/src/tokio/net.rs deleted file mode 100644 index 93a807e..0000000 --- a/crates/wasi-common/src/tokio/net.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub use super::file::TcpListener; -pub use super::file::TcpStream; -#[cfg(unix)] -pub use super::file::UnixListener; -#[cfg(unix)] -pub use super::file::UnixStream; diff --git a/crates/wasi-common/src/tokio/sched.rs b/crates/wasi-common/src/tokio/sched.rs deleted file mode 100644 index 7d83fff..0000000 --- a/crates/wasi-common/src/tokio/sched.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[cfg(unix)] -mod unix; -#[cfg(unix)] -pub use unix::poll_oneoff; - -#[cfg(windows)] -mod windows; -#[cfg(windows)] -pub use windows::poll_oneoff; - -use crate::{ - sched::{Duration, Poll, WasiSched}, - Error, -}; - -pub fn sched_ctx() -> Box { - struct AsyncSched; - - #[async_trait::async_trait] - impl WasiSched for AsyncSched { - async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { - poll_oneoff(poll).await - } - async fn sched_yield(&self) -> Result<(), Error> { - tokio::task::yield_now().await; - Ok(()) - } - async fn sleep(&self, duration: Duration) -> Result<(), Error> { - tokio::time::sleep(duration).await; - Ok(()) - } - } - - Box::new(AsyncSched) -} diff --git a/crates/wasi-common/src/tokio/sched/unix.rs b/crates/wasi-common/src/tokio/sched/unix.rs deleted file mode 100644 index dc063e4..0000000 --- a/crates/wasi-common/src/tokio/sched/unix.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::{ - Error, - sched::{ - Poll, - subscription::{RwEventFlags, Subscription}, - }, -}; -use std::future::{self, Future}; -use std::pin::{Pin, pin}; -use std::task::{Context, Poll as FPoll}; - -struct FirstReady<'a, T>(Vec + Send + 'a>>>); - -impl<'a, T> FirstReady<'a, T> { - fn new() -> Self { - FirstReady(Vec::new()) - } - fn push(&mut self, f: impl Future + Send + 'a) { - self.0.push(Box::pin(f)); - } -} - -impl<'a, T> Future for FirstReady<'a, T> { - type Output = T; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> FPoll { - let mut result = FPoll::Pending; - for f in self.as_mut().0.iter_mut() { - match f.as_mut().poll(cx) { - FPoll::Ready(r) => match result { - // First ready gets to set the result. But, continue the loop so all futures - // which are ready simultaneously (often on first poll) get to report their - // readiness. - FPoll::Pending => { - result = FPoll::Ready(r); - } - _ => {} - }, - _ => continue, - } - } - return result; - } -} - -pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); - } - - let duration = poll - .earliest_clock_deadline() - .map(|sub| sub.duration_until()); - - let mut futures = FirstReady::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(f) => { - futures.push(async move { - f.file - .readable() - .await - .map_err(|e| e.context("readable future"))?; - f.complete( - f.file - .num_ready_bytes() - .map_err(|e| e.context("read num_ready_bytes"))?, - RwEventFlags::empty(), - ); - Ok::<(), Error>(()) - }); - } - - Subscription::Write(f) => { - futures.push(async move { - f.file - .writable() - .await - .map_err(|e| e.context("writable future"))?; - f.complete(0, RwEventFlags::empty()); - Ok(()) - }); - } - Subscription::MonotonicClock { .. } => unreachable!(), - } - } - match duration { - Some(Some(remaining)) => match tokio::time::timeout(remaining, futures).await { - Ok(r) => r?, - Err(_deadline_elapsed) => {} - }, - Some(None) => { - let mut futures = pin!(futures); - future::poll_fn(|cx| match futures.as_mut().poll(cx) { - FPoll::Ready(e) => FPoll::Ready(e), - FPoll::Pending => FPoll::Ready(Ok(())), - }) - .await? - } - None => futures.await?, - } - - Ok(()) -} diff --git a/crates/wasi-common/src/tokio/sched/windows.rs b/crates/wasi-common/src/tokio/sched/windows.rs deleted file mode 100644 index f0d90fd..0000000 --- a/crates/wasi-common/src/tokio/sched/windows.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::sync::sched::windows::poll_oneoff_; -use crate::tokio::block_on_dummy_executor; -use crate::{Error, file::WasiFile, sched::Poll}; - -pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - // Tokio doesn't provide us the AsyncFd primitive on Windows, so instead - // we use the blocking poll_oneoff implementation from the wasi_common::sync impl. - // We provide a function specific to this impl's WasiFile types for downcasting - // to a RawHandle. - block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin)) -} - -pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { - f.as_any().is::() -} diff --git a/crates/wasi-common/src/tokio/stdio.rs b/crates/wasi-common/src/tokio/stdio.rs deleted file mode 100644 index c796fc7..0000000 --- a/crates/wasi-common/src/tokio/stdio.rs +++ /dev/null @@ -1 +0,0 @@ -pub use super::file::{Stderr, Stdin, Stdout, stderr, stdin, stdout}; diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 0c2abe5..d685baf 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -29,7 +29,7 @@ io-extras = { workspace = true } windows-sys = { workspace = true, features = ["Win32_System_Threading", "Win32_Security"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -webrogue-wasi-common = { workspace = true, default-features = false, features = ["tokio"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/wasip1/src/fs/dev/wakeup.rs b/crates/wasip1/src/fs/dev/wakeup.rs index 9785e8a..0428a1a 100644 --- a/crates/wasip1/src/fs/dev/wakeup.rs +++ b/crates/wasip1/src/fs/dev/wakeup.rs @@ -1,119 +1,37 @@ use webrogue_wasi_common::{file::FileType, ErrorExt as _, WasiFile}; -#[cfg(unix)] -mod unix { - use std::os::fd::AsFd; +use webrogue_wasi_common::ErrorExt as _; - use webrogue_wasi_common::ErrorExt as _; - - pub struct File { - read_fd: std::os::fd::OwnedFd, - write_fd: std::os::fd::OwnedFd, - } - - impl File { - pub fn new() -> Result { - // let (read_fd, write_fd) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::NONBLOCK | rustix::pipe::PipeFlags::CLOEXEC) - // .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; - let (read_fd, write_fd) = rustix::pipe::pipe() - .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; - for fd in [read_fd.as_fd(), read_fd.as_fd()] { - rustix::io::fcntl_setfd(fd, rustix::io::FdFlags::CLOEXEC) - .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; - rustix::io::ioctl_fionbio(fd, true) - .map_err(|_err| webrogue_wasi_common::Error::not_supported())?; - } - Ok(Self { read_fd, write_fd }) - } - - pub(super) fn acknowledge(&self) -> rustix::io::Result<()> { - use rustix::io::Errno; - - let mut buf = [0u8; 8]; - loop { - match rustix::io::read(self.read_fd.as_fd(), &mut buf) { - Ok(0) => { - return Ok(()); - } - Ok(_) | Err(Errno::INTR) => {} - Err(_) => { - return Ok(()); - } - } - } - } - - pub(super) fn signal(&self) -> rustix::io::Result<()> { - use rustix::io::Errno; - const LEN: usize = 1; - loop { - match rustix::io::write(self.write_fd.as_fd(), &[1u8; LEN]) { - Ok(0) | Err(Errno::INTR | Errno::WOULDBLOCK) => {} - Ok(_) | Err(_) => { - return Ok(()); - } - } - } - } - - pub(super) fn pollable(&self) -> rustix::fd::BorrowedFd<'_> { - use std::os::fd::AsFd as _; - - self.read_fd.as_fd() - } - } +pub struct File { + tx: tokio::sync::watch::Sender, + rx: tokio::sync::watch::Receiver, } -#[cfg(unix)] -pub use unix::File; - -#[cfg(windows)] -mod windows { - use std::{ - os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}, - ptr::{null, null_mut}, - }; - - use windows_sys::Win32::System::Threading::{CreateEventA, ResetEvent, SetEvent}; +impl File { + pub fn new() -> Result { + let (tx, rx) = tokio::sync::watch::channel(false); - pub struct File { - handle: OwnedHandle, + Ok(Self { tx, rx }) } - impl File { - pub fn new() -> Result { - let handle = unsafe { CreateEventA(null(), 1, 0, null()) }; - assert!(handle != null_mut()); - Ok(Self { - handle: unsafe { OwnedHandle::from_raw_handle(handle) }, - }) - } + pub(super) fn acknowledge(&self) -> () { + self.tx.send(false).unwrap() + } - pub(super) fn acknowledge(&self) -> Result<(), ()> { - unsafe { - ResetEvent(self.handle.as_raw_handle()); - } - Ok(()) - } + pub(super) fn signal(&self) -> () { + self.tx.send(true).unwrap() + } - pub(super) fn signal(&self) -> Result<(), ()> { - unsafe { - SetEvent(self.handle.as_raw_handle()); - } - Ok(()) - } + pub(super) async fn wait(&self) -> () { + let mut rx = self.rx.clone(); - pub(super) fn pollable(&self) -> io_extras::os::windows::RawHandleOrSocket { - io_extras::os::windows::RawHandleOrSocket::unowned_from_raw_handle( - self.handle.as_raw_handle(), - ) + while !*rx.borrow() { + // This blocks until the sender updates the state + rx.changed().await.unwrap(); } } } -#[cfg(windows)] -pub use windows::File; - #[async_trait::async_trait] impl WasiFile for File { fn as_any(&self) -> &dyn std::any::Any { @@ -124,22 +42,11 @@ impl WasiFile for File { Ok(FileType::Pipe) } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(File::pollable(self)) - } - - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(File::pollable(self)) - } - async fn read_vectored<'a>( &self, _bufs: &mut [std::io::IoSliceMut<'a>], ) -> Result { - self.acknowledge() - .map_err(|_| webrogue_wasi_common::Error::not_supported())?; + self.acknowledge(); Ok(0) } @@ -147,113 +54,120 @@ impl WasiFile for File { &self, _bufs: &[std::io::IoSlice<'a>], ) -> Result { - self.signal() - .map_err(|_| webrogue_wasi_common::Error::not_supported())?; - + self.signal(); Ok(_bufs.iter().map(|slice| slice.len()).sum::() as u64) } -} - -#[cfg(test)] -mod tests { - use crate::fs::dev::wakeup::File; - use std::{ - sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc, Arc, - }, - thread, - }; - - #[cfg(unix)] - fn wait(wakeup: &Arc) { - use rustix::event::{poll, PollFd, PollFlags}; - - let fd = PollFd::from_borrowed_fd(wakeup.pollable(), PollFlags::IN); - let a = poll(&mut [fd], None).unwrap(); - assert_eq!(a, 1); - } - - #[cfg(windows)] - fn wait(wakeup: &Arc) { - use windows_sys::Win32::System::Threading::{WaitForSingleObject, INFINITE}; - - let handle = wakeup.pollable(); - unsafe { - WaitForSingleObject(handle.as_raw_handle().unwrap(), INFINITE); - } - } - - fn invoke_iters() -> u32 { - 1 + rand::random::() % 10 - } - - #[test] - fn blocks() { - let wakeup = Arc::new(File::new().unwrap()); - let thrd_wakeup = wakeup.clone(); - let counter = Arc::new(AtomicUsize::new(0)); - let thrd_counter = counter.clone(); - let (tx, rx) = mpsc::channel::<()>(); - - const ITERS: usize = 10000; - - let thread = thread::spawn(move || { - for i in 0..ITERS { - assert_eq!(thrd_counter.fetch_add(1, Ordering::SeqCst), 0 + i * 2); - // TODO figure out why invoking signal() multiple times without acknowledge() - // inbetween has a small chance to cause data race on Linux. - thrd_wakeup.signal().unwrap(); - rx.recv().unwrap(); - } - }); - - for i in 0..ITERS { - for _ in 0..invoke_iters() { - wait(&wakeup); - } - assert_eq!(counter.fetch_add(1, Ordering::SeqCst), 1 + i * 2); - for _ in 0..invoke_iters() { - wakeup.acknowledge().unwrap(); - } - tx.send(()).unwrap(); - } - thread.join().unwrap(); + async fn readable(&self) -> Result<(), webrogue_wasi_common::Error> { + self.wait(); + Ok(()) } - #[test] - fn many_signals() { - let wakeup = Arc::new(File::new().unwrap()); - let thrd_wakeup = wakeup.clone(); - let counter = Arc::new(AtomicUsize::new(0)); - let thrd_counter = counter.clone(); - let (tx, rx) = mpsc::channel::<()>(); - let (tx2, rx2) = mpsc::channel::<()>(); - - const ITERS: usize = 10000; - - let thread = thread::spawn(move || { - for i in 0..ITERS { - assert_eq!(thrd_counter.fetch_add(1, Ordering::SeqCst), 0 + i * 2); - // TODO figure out why invoking signal() multiple times without acknowledge() - // inbetween has a small chance to cause data race on Linux. - for _ in 0..invoke_iters() { - thrd_wakeup.signal().unwrap(); - } - tx.send(()).unwrap(); - rx2.recv().unwrap(); - } - }); - - for i in 0..ITERS { - wait(&wakeup); - rx.recv().unwrap(); - assert_eq!(counter.fetch_add(1, Ordering::SeqCst), 1 + i * 2); - wakeup.acknowledge().unwrap(); - tx2.send(()).unwrap(); - } - - thread.join().unwrap(); + async fn writable(&self) -> Result<(), webrogue_wasi_common::Error> { + Ok(()) } } + +// #[cfg(test)] +// mod tests { +// use crate::fs::dev::wakeup::File; +// use std::{ +// sync::{ +// atomic::{AtomicUsize, Ordering}, +// mpsc, Arc, +// }, +// thread, +// }; + +// #[cfg(unix)] +// fn wait(wakeup: &Arc) { +// use rustix::event::{poll, PollFd, PollFlags}; + +// let fd = PollFd::from_borrowed_fd(wakeup.pollable(), PollFlags::IN); +// let a = poll(&mut [fd], None).unwrap(); +// assert_eq!(a, 1); +// } + +// #[cfg(windows)] +// fn wait(wakeup: &Arc) { +// use windows_sys::Win32::System::Threading::{WaitForSingleObject, INFINITE}; + +// let handle = wakeup.pollable(); +// unsafe { +// WaitForSingleObject(handle.as_raw_handle().unwrap(), INFINITE); +// } +// } + +// fn invoke_iters() -> u32 { +// 1 + rand::random::() % 10 +// } + +// #[test] +// fn blocks() { +// let wakeup = Arc::new(File::new().unwrap()); +// let thrd_wakeup = wakeup.clone(); +// let counter = Arc::new(AtomicUsize::new(0)); +// let thrd_counter = counter.clone(); +// let (tx, rx) = mpsc::channel::<()>(); + +// const ITERS: usize = 10000; + +// let thread = thread::spawn(move || { +// for i in 0..ITERS { +// assert_eq!(thrd_counter.fetch_add(1, Ordering::SeqCst), 0 + i * 2); +// // TODO figure out why invoking signal() multiple times without acknowledge() +// // inbetween has a small chance to cause data race on Linux. +// thrd_wakeup.signal().unwrap(); +// rx.recv().unwrap(); +// } +// }); + +// for i in 0..ITERS { +// for _ in 0..invoke_iters() { +// wait(&wakeup); +// } +// assert_eq!(counter.fetch_add(1, Ordering::SeqCst), 1 + i * 2); +// for _ in 0..invoke_iters() { +// wakeup.acknowledge().unwrap(); +// } +// tx.send(()).unwrap(); +// } + +// thread.join().unwrap(); +// } + +// #[test] +// fn many_signals() { +// let wakeup = Arc::new(File::new().unwrap()); +// let thrd_wakeup = wakeup.clone(); +// let counter = Arc::new(AtomicUsize::new(0)); +// let thrd_counter = counter.clone(); +// let (tx, rx) = mpsc::channel::<()>(); +// let (tx2, rx2) = mpsc::channel::<()>(); + +// const ITERS: usize = 10000; + +// let thread = thread::spawn(move || { +// for i in 0..ITERS { +// assert_eq!(thrd_counter.fetch_add(1, Ordering::SeqCst), 0 + i * 2); +// // TODO figure out why invoking signal() multiple times without acknowledge() +// // inbetween has a small chance to cause data race on Linux. +// for _ in 0..invoke_iters() { +// thrd_wakeup.signal().unwrap(); +// } +// tx.send(()).unwrap(); +// rx2.recv().unwrap(); +// } +// }); + +// for i in 0..ITERS { +// wait(&wakeup); +// rx.recv().unwrap(); +// assert_eq!(counter.fetch_add(1, Ordering::SeqCst), 1 + i * 2); +// wakeup.acknowledge().unwrap(); +// tx2.send(()).unwrap(); +// } + +// thread.join().unwrap(); +// } +// } diff --git a/crates/wasip1/src/lib.rs b/crates/wasip1/src/lib.rs index 0ea613c..cf32c60 100644 --- a/crates/wasip1/src/lib.rs +++ b/crates/wasip1/src/lib.rs @@ -9,7 +9,7 @@ pub fn make_ctx( ) -> anyhow::Result { #[cfg(not(target_arch = "wasm32"))] let mut wasi_ctx = { - let mut builder = webrogue_wasi_common::tokio::WasiCtxBuilder::new(); + let mut builder = webrogue_wasi_common::sync::WasiCtxBuilder::new(); // builder.inherit_stdio(); // builder.stdout(Box::new(stdout::STDOutFile {})); // builder.stderr(Box::new(stdout::STDOutFile {})); @@ -116,18 +116,6 @@ pub fn blocking_sleep(millis: i64) { } } -pub fn run_in_runtime(f: impl FnOnce() -> Output) -> Output { - if tokio::runtime::Handle::try_current().is_ok() { - return f(); - } - tokio::runtime::Builder::new_current_thread() - .enable_time() - .enable_io() - .build() - .unwrap() - .block_on(async { return f() }) -} - fn make_runtime() -> tokio::runtime::Runtime { let (tx, rx) = std::sync::mpsc::channel(); diff --git a/crates/wasmtime/src/wasi_threads.rs b/crates/wasmtime/src/wasi_threads.rs index b4739ed..1d8136d 100644 --- a/crates/wasmtime/src/wasi_threads.rs +++ b/crates/wasmtime/src/wasi_threads.rs @@ -67,76 +67,73 @@ impl WasiThreadsCtx { let _ = std::thread::Builder::new() .name(thread_name.clone()) .spawn(move || { - webrogue_wasip1::run_in_runtime(move || { - let mut store = wasmtime::Store::new(instance_pre.module().engine(), host); - let thread = thread_registry.make_thread(store.engine().weak()); - { - let thread = thread.clone(); - store.epoch_deadline_callback(move |_| thread.on_epoch_update_deadline()); - store.set_epoch_deadline(1); - } + let mut store = wasmtime::Store::new(instance_pre.module().engine(), host); + let thread = thread_registry.make_thread(store.engine().weak()); + { + let thread = thread.clone(); + store.epoch_deadline_callback(move |_| thread.on_epoch_update_deadline()); + store.set_epoch_deadline(1); + } - let result: Result, Box> = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - #[cfg(feature = "async")] - if let Some(async_func_runner) = async_func_runner { - return async_func_runner( - AsyncFuncRunnerParams { - store, - thread: thread.clone(), - }, - Box::new(move |mut store| { - Box::pin(async move { - let instance = instance_pre - .instantiate_async(&mut store) - .await - .unwrap(); - let thread_entry_point = instance - .get_typed_func::<(i32, i32), ()>( - &mut store, - WASI_ENTRY_POINT, - ) - .unwrap(); - thread_entry_point - .call_async( - &mut store, - (wasi_thread_id, thread_start_arg), - ) - .await?; - Ok(()) - }) - }), - ) - .map(|_| ()); - } - let instance = instance_pre.instantiate(&mut store)?; - let thread_entry_point = instance - .get_typed_func::<(i32, i32), ()>(&mut store, WASI_ENTRY_POINT) - .unwrap(); - thread_entry_point - .call(&mut store, (wasi_thread_id, thread_start_arg)) - .map_err(|err| anyhow::anyhow!(err)) - })); - - let tid = thread.tid(); - - thread_registry.remove_thread(thread); - - match result { - Err(e) => { - thread_registry.stop_all_threads(StopReason::ThreadError( - tid, - anyhow::anyhow!("{thread_name} panicked: {e:?}"), - )); + let result: Result, Box> = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + #[cfg(feature = "async")] + if let Some(async_func_runner) = async_func_runner { + return async_func_runner( + AsyncFuncRunnerParams { + store, + thread: thread.clone(), + }, + Box::new(move |mut store| { + Box::pin(async move { + let instance = instance_pre + .instantiate_async(&mut store) + .await + .unwrap(); + let thread_entry_point = instance + .get_typed_func::<(i32, i32), ()>( + &mut store, + WASI_ENTRY_POINT, + ) + .unwrap(); + thread_entry_point + .call_async( + &mut store, + (wasi_thread_id, thread_start_arg), + ) + .await?; + Ok(()) + }) + }), + ) + .map(|_| ()); } - Ok(result) => { - if let Err(error) = result { - thread_registry - .stop_all_threads(StopReason::ThreadError(tid, error)); - } + let instance = instance_pre.instantiate(&mut store)?; + let thread_entry_point = instance + .get_typed_func::<(i32, i32), ()>(&mut store, WASI_ENTRY_POINT) + .unwrap(); + thread_entry_point + .call(&mut store, (wasi_thread_id, thread_start_arg)) + .map_err(|err| anyhow::anyhow!(err)) + })); + + let tid = thread.tid(); + + thread_registry.remove_thread(thread); + + match result { + Err(e) => { + thread_registry.stop_all_threads(StopReason::ThreadError( + tid, + anyhow::anyhow!("{thread_name} panicked: {e:?}"), + )); + } + Ok(result) => { + if let Err(error) = result { + thread_registry.stop_all_threads(StopReason::ThreadError(tid, error)); } } - }) + } }); Ok(wasi_thread_id) From 77198a552c8752be9e9eb4d29d3e7967317dc074 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Fri, 15 May 2026 13:28:18 -0400 Subject: [PATCH 2/5] Migrate to std --- Cargo.lock | 36 -- Cargo.toml | 7 - android/runtime/launcher/src/lib.rs | 5 + crates/wasi-common/Cargo.toml | 34 +- crates/wasi-common/src/clocks.rs | 6 - crates/wasi-common/src/file.rs | 2 - crates/wasi-common/src/lib.rs | 78 ---- crates/wasi-common/src/sched.rs | 8 +- crates/wasi-common/src/sched/subscription.rs | 5 +- crates/wasi-common/src/snapshots/preview_1.rs | 15 +- crates/wasi-common/src/sync/_stdio.rs | 196 --------- crates/wasi-common/src/sync/clocks.rs | 34 +- crates/wasi-common/src/sync/dir.rs | 379 +++++++++--------- crates/wasi-common/src/sync/file.rs | 286 +++++-------- crates/wasi-common/src/sync/mod.rs | 15 +- crates/wasi-common/src/sync/stdio.rs | 67 ++++ crates/wasip1/src/fs/dev/wakeup.rs | 10 +- crates/wasip1/src/lib.rs | 18 +- crates/wasmtime/Cargo.toml | 2 +- 19 files changed, 409 insertions(+), 794 deletions(-) delete mode 100644 crates/wasi-common/src/sync/_stdio.rs create mode 100644 crates/wasi-common/src/sync/stdio.rs diff --git a/Cargo.lock b/Cargo.lock index da26729..771eda1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,17 +2042,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix 1.1.4", - "windows-sys 0.59.0", -] - [[package]] name = "fdeflate" version = "0.3.7" @@ -6505,22 +6494,6 @@ dependencies = [ "version-compare", ] -[[package]] -name = "system-interface" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" -dependencies = [ - "bitflags 2.11.0", - "cap-fs-ext", - "cap-std", - "fd-lock", - "io-lifetimes", - "rustix 0.38.44", - "windows-sys 0.59.0", - "winx", -] - [[package]] name = "tao" version = "0.35.2" @@ -8479,21 +8452,12 @@ version = "0.1.0" dependencies = [ "async-trait", "bitflags 2.11.0", - "cap-fs-ext", - "cap-std", - "cap-time-ext", - "fs-set-times", - "io-extras", - "io-lifetimes", - "libc", "log", "rand 0.10.1", "rustix 1.1.4", - "system-interface", "thiserror 2.0.18", "tokio", "tracing", - "wasmtime", "wasmtime-internal-core", "wiggle", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index 9f79c76..aad228b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,15 +199,8 @@ rustls = { version = "0.23", default-features = false } bitflags = "2.9.4" # To remove -cap-fs-ext = "3.4.5" -cap-time-ext = "3.4.5" -fs-set-times = "0.20.3" -io-lifetimes = { version = "2.0.3", default-features = false } -system-interface = { version = "0.27.3", features = ["cap_std_impls"] } thiserror = "2.0.17" tempfile = "3.27.0" -test-log = { version = "0.2.18", default-features = false, features = ["trace"] } -cap-std = "3.4.5" [profile.dev] panic = "abort" diff --git a/android/runtime/launcher/src/lib.rs b/android/runtime/launcher/src/lib.rs index 1e63f10..9e536ae 100644 --- a/android/runtime/launcher/src/lib.rs +++ b/android/runtime/launcher/src/lib.rs @@ -278,6 +278,11 @@ impl LauncherConfig for LauncherConfigImpl { let sdp_answer = rx.recv().await.unwrap(); on_sdp_answer(sdp_answer); }; + #[cfg(not(target_os = "android"))] + { + let _ = sdp_offer; + let _ = on_sdp_answer; + } Ok(()) } } diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index 58cbc57..72ffefd 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -21,28 +21,11 @@ log = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } async-trait = { workspace = true } -# Optional, enabled by wasmtime feature: -wasmtime = { workspace = true, optional = true, features = ['runtime', 'component-model', 'std'] } -# Optional, enabled by sync feature: -cap-fs-ext = { workspace = true, optional = true } -cap-time-ext = { workspace = true, optional = true } -fs-set-times = { workspace = true, optional = true } -system-interface = { workspace = true, features = ["cap_std_impls"], optional = true } -io-lifetimes = { workspace = true, optional = true } # Optional, enabled by tokio feature: tokio = { workspace = true, features = ["time"], optional = true } -cap-std = { workspace = true, optional = true } - -# Optional, enabled by exit feature: -libc = { workspace = true, optional = true } - [target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["fs", "event"] } - -[target.'cfg(windows)'.dependencies] -io-extras = { workspace = true } -rustix = { workspace = true, features = ["net"] } +rustix = { workspace = true } [target.'cfg(windows)'.dependencies.windows-sys] workspace = true @@ -52,7 +35,7 @@ features = [ ] [features] -default = ["trace_log", "wasmtime", "sync"] +default = ["trace_log", "sync"] # This feature enables the `tracing` logs in the calls to target the `log` # ecosystem of backends (e.g. `env_logger`. Disable this if you want to use # `tracing-subscriber`. @@ -60,24 +43,11 @@ trace_log = [ "wiggle/tracing_log", "tracing/log" ] # Need to make the wiggle_metadata feature available to consumers of this # crate if they want the snapshots to have metadata available. wiggle_metadata = ["wiggle/wiggle_metadata"] -# This feature enables integration with wasmtime. -wasmtime = [ - "dep:wasmtime", - "wiggle/wasmtime", -] # This feature enables an implementation of the Wasi traits for a # synchronous wasmtime embedding. sync = [ - "dep:cap-fs-ext", - "dep:cap-time-ext", - "dep:fs-set-times", - "dep:system-interface", - "dep:io-lifetimes", - "use_cap_std", "dep:tokio", ] -use_cap_std = ["dep:cap-std"] -exit = [ "wasmtime", "dep:libc" ] [package.metadata.docs.rs] all-features = true diff --git a/crates/wasi-common/src/clocks.rs b/crates/wasi-common/src/clocks.rs index f3535d4..0ffc3dd 100644 --- a/crates/wasi-common/src/clocks.rs +++ b/crates/wasi-common/src/clocks.rs @@ -1,7 +1,4 @@ use crate::{Error, ErrorExt}; -#[cfg(feature = "use_cap_std")] -use cap_std::time::{Duration, Instant, SystemTime}; -#[cfg(not(feature = "use_cap_std"))] use std::time::{Duration, Instant, SystemTime}; pub enum SystemTimeSpec { @@ -20,9 +17,6 @@ pub trait WasiMonotonicClock: Send + Sync { } pub struct WasiMonotonicOffsetClock { - #[cfg(feature = "use_cap_std")] - pub creation_time: cap_std::time::Instant, - #[cfg(not(feature = "use_cap_std"))] pub creation_time: std::time::Instant, pub abs_clock: Box, } diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs index 7d02a87..82a5fa9 100644 --- a/crates/wasi-common/src/file.rs +++ b/crates/wasi-common/src/file.rs @@ -1,8 +1,6 @@ use crate::{Error, ErrorExt, SystemTimeSpec}; use bitflags::bitflags; use std::any::Any; -use std::future::Future; -use std::pin::Pin; use std::sync::Arc; #[async_trait::async_trait] diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index f39f122..8b23479 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -96,81 +96,3 @@ pub use string_array::{StringArray, StringArrayError}; pub use table::Table; pub(crate) use wasmtime_internal_core::error::Error as EnvError; - -// The only difference between these definitions for sync vs async is whether -// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run) -// or whether they have an internal "dummy executor" that expects the implementation of all -// the async funcs to poll to Ready immediately. -#[cfg(feature = "wasmtime")] -#[doc(hidden)] -#[macro_export] -macro_rules! define_wasi { - ($async_mode:tt $($bounds:tt)*) => { - - use wasmtime::Linker; - use wasmtime_internal_core::error::Result as EnvResult; - - pub fn add_to_linker( - linker: &mut Linker, - get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, - ) -> EnvResult<()> - where U: Send - + crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1, - T: 'static, - $($bounds)* - { - snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?; - Ok(()) - } - - pub mod snapshots { - pub mod preview_1 { - wiggle::wasmtime_integration!({ - // The wiggle code to integrate with lives here: - target: crate::snapshots::preview_1, - witx: ["witx/preview1/wasi_snapshot_preview1.witx"], - errors: { errno => trappable Error }, - $async_mode: * - }); - } - } -}} - -/// Exit the process with a conventional OS error code as long as Wasmtime -/// understands the error. If the error is not an `I32Exit` or `Trap`, return -/// the error back to the caller for it to decide what to do. -/// -/// Note: this function is designed for usage where it is acceptable for -/// Wasmtime failures to terminate the parent process, such as in the Wasmtime -/// CLI; this would not be suitable for use in multi-tenant embeddings. -#[cfg_attr(docsrs, doc(cfg(feature = "exit")))] -#[cfg(feature = "exit")] -pub fn maybe_exit_on_error(e: EnvError) -> EnvError { - use std::process; - use wasmtime::Trap; - - // If a specific WASI error code was requested then that's - // forwarded through to the process here without printing any - // extra error information. - if let Some(exit) = e.downcast_ref::() { - process::exit(exit.0); - } - - // If the program exited because of a trap, return an error code - // to the outside environment indicating a more severe problem - // than a simple failure. - if e.is::() { - eprintln!("Error: {e:?}"); - - if cfg!(unix) { - // On Unix, return the error code of an abort. - process::exit(128 + libc::SIGABRT); - } else if cfg!(windows) { - // On Windows, return 3. - // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 - process::exit(3); - } - } - - e -} diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index 8413b0f..385c655 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -1,14 +1,8 @@ -use crate::Error; use crate::clocks::WasiMonotonicClock; use crate::file::WasiFile; -#[cfg(feature = "use_cap_std")] -use cap_std::time::Instant; -#[cfg(not(feature = "use_cap_std"))] +use crate::Error; use std::time::Instant; pub mod subscription; -#[cfg(feature = "use_cap_std")] -pub use cap_std::time::Duration; -#[cfg(not(feature = "use_cap_std"))] pub use std::time::Duration; pub use subscription::{ diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index 782aca9..0695903 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -1,10 +1,7 @@ -use crate::Error; use crate::clocks::WasiMonotonicClock; use crate::file::WasiFile; +use crate::Error; use bitflags::bitflags; -#[cfg(feature = "use_cap_std")] -use cap_std::time::{Duration, Instant}; -#[cfg(not(feature = "use_cap_std"))] use std::time::{Duration, Instant}; bitflags! { diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 02106e2..a729162 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -1,22 +1,19 @@ use crate::{ - EnvError, I32Exit, SystemTimeSpec, WasiCtx, dir::{DirEntry, OpenResult, ReaddirCursor, ReaddirEntity, TableDirExt}, file::{ Advice, FdFlags, FdStat, FileAccessMode, FileEntry, FileType, Filestat, OFlags, RiFlags, RoFlags, SdFlags, SiFlags, TableFileExt, WasiFile, }, sched::{ - Poll, Userdata, subscription::{RwEventFlags, SubscriptionResult}, + Poll, Userdata, }, + EnvError, I32Exit, SystemTimeSpec, WasiCtx, }; -#[cfg(feature = "use_cap_std")] -use cap_std::time::{Duration, SystemClock}; use std::borrow::Cow; use std::io::{IoSlice, IoSliceMut}; use std::ops::Deref; use std::sync::Arc; -#[cfg(not(feature = "use_cap_std"))] use std::time::Duration; use wiggle::GuestMemory; use wiggle::GuestPtr; @@ -101,9 +98,6 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let precision = Duration::from_nanos(precision); match id { types::Clockid::Realtime => { - #[cfg(feature = "use_cap_std")] - let now = self.clocks.system()?.now(precision).into_std(); - #[cfg(not(feature = "use_cap_std"))] let now = self.clocks.system()?.now(precision); let d = now .duration_since(std::time::SystemTime::UNIX_EPOCH) @@ -1523,11 +1517,6 @@ fn systimespec( if set && now { Err(Error::invalid_argument()) } else if set { - #[cfg(feature = "use_cap_std")] - return Ok(Some(SystemTimeSpec::Absolute( - SystemClock::UNIX_EPOCH + Duration::from_nanos(ts), - ))); - #[cfg(not(feature = "use_cap_std"))] Ok(Some(SystemTimeSpec::Absolute( std::time::SystemTime::UNIX_EPOCH + Duration::from_nanos(ts), ))) diff --git a/crates/wasi-common/src/sync/_stdio.rs b/crates/wasi-common/src/sync/_stdio.rs deleted file mode 100644 index 2975a08..0000000 --- a/crates/wasi-common/src/sync/_stdio.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::sync::file::convert_systimespec; -use fs_set_times::SetTimes; -use std::any::Any; -use std::io::{self, IsTerminal, Read, Write}; -use system_interface::io::ReadReady; - -use crate::{ - Error, ErrorExt, - file::{FdFlags, FileType, WasiFile}, -}; -#[cfg(windows)] -use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; -#[cfg(unix)] -use io_lifetimes::{AsFd, BorrowedFd}; -#[cfg(windows)] -use io_lifetimes::{AsHandle, BorrowedHandle}; - -pub struct Stdin(std::io::Stdin); - -pub fn stdin() -> Stdin { - Stdin(std::io::stdin()) -} - -#[async_trait::async_trait] -impl WasiFile for Stdin { - fn as_any(&self) -> &dyn Any { - self - } - - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } - - async fn get_filetype(&self) -> Result { - if self.isatty() { - Ok(FileType::CharacterDevice) - } else { - Ok(FileType::Unknown) - } - } - async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { - let n = self.0.lock().read_vectored(bufs)?; - Ok(n.try_into().map_err(|_| Error::range())?) - } - async fn read_vectored_at<'a>( - &self, - _bufs: &mut [io::IoSliceMut<'a>], - _offset: u64, - ) -> Result { - Err(Error::seek_pipe()) - } - async fn seek(&self, _pos: std::io::SeekFrom) -> Result { - Err(Error::seek_pipe()) - } - async fn peek(&self, _buf: &mut [u8]) -> Result { - Err(Error::seek_pipe()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; - Ok(()) - } - fn num_ready_bytes(&self) -> Result { - Ok(self.0.num_ready_bytes()?) - } - fn isatty(&self) -> bool { - #[cfg(unix)] - return self.0.as_fd().is_terminal(); - #[cfg(windows)] - return self.0.as_handle().is_terminal(); - } -} -#[cfg(windows)] -impl AsHandle for Stdin { - fn as_handle(&self) -> BorrowedHandle<'_> { - self.0.as_handle() - } -} -#[cfg(windows)] -impl AsRawHandleOrSocket for Stdin { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() - } -} -#[cfg(unix)] -impl AsFd for Stdin { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } -} - -macro_rules! wasi_file_write_impl { - ($ty:ty, $ident:ident) => { - #[async_trait::async_trait] - impl WasiFile for $ty { - fn as_any(&self) -> &dyn Any { - self - } - #[cfg(unix)] - fn pollable(&self) -> Option> { - Some(self.0.as_fd()) - } - #[cfg(windows)] - fn pollable(&self) -> Option { - Some(self.0.as_raw_handle_or_socket()) - } - async fn get_filetype(&self) -> Result { - if self.isatty() { - Ok(FileType::CharacterDevice) - } else { - Ok(FileType::Unknown) - } - } - async fn get_fdflags(&self) -> Result { - Ok(FdFlags::APPEND) - } - async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - let mut io = self.0.lock(); - let n = io.write_vectored(bufs)?; - // On a successful write additionally flush out the bytes to - // handle stdio buffering done by libstd since WASI interfaces - // here aren't buffered. - io.flush()?; - Ok(n.try_into().map_err(|_| { - Error::range().context("converting write_vectored total length") - })?) - } - async fn write_vectored_at<'a>( - &self, - _bufs: &[io::IoSlice<'a>], - _offset: u64, - ) -> Result { - Err(Error::seek_pipe()) - } - async fn seek(&self, _pos: std::io::SeekFrom) -> Result { - Err(Error::seek_pipe()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; - Ok(()) - } - fn isatty(&self) -> bool { - self.0.is_terminal() - } - } - #[cfg(windows)] - impl AsHandle for $ty { - fn as_handle(&self) -> BorrowedHandle<'_> { - self.0.as_handle() - } - } - #[cfg(unix)] - impl AsFd for $ty { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } - } - #[cfg(windows)] - impl AsRawHandleOrSocket for $ty { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() - } - } - }; -} - -pub struct Stdout(std::io::Stdout); - -pub fn stdout() -> Stdout { - Stdout(std::io::stdout()) -} -wasi_file_write_impl!(Stdout, Stdout); - -pub struct Stderr(std::io::Stderr); - -pub fn stderr() -> Stderr { - Stderr(std::io::stderr()) -} -wasi_file_write_impl!(Stderr, Stderr); diff --git a/crates/wasi-common/src/sync/clocks.rs b/crates/wasi-common/src/sync/clocks.rs index bd333e1..cafbf6a 100644 --- a/crates/wasi-common/src/sync/clocks.rs +++ b/crates/wasi-common/src/sync/clocks.rs @@ -1,41 +1,41 @@ +use std::time::{Duration, Instant, SystemTime}; + use crate::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock}; -use cap_std::time::{Duration, Instant, SystemTime}; -use cap_std::{AmbientAuthority, ambient_authority}; -use cap_time_ext::{MonotonicClockExt, SystemClockExt}; -pub struct SystemClock(cap_std::time::SystemClock); +pub struct SystemClock {} impl SystemClock { - pub fn new(ambient_authority: AmbientAuthority) -> Self { - SystemClock(cap_std::time::SystemClock::new(ambient_authority)) + pub fn new() -> Self { + Self {} } } impl WasiSystemClock for SystemClock { fn resolution(&self) -> Duration { - self.0.resolution() + Duration::from_millis(1) } - fn now(&self, precision: Duration) -> SystemTime { - self.0.now_with(precision) + fn now(&self, _precision: Duration) -> SystemTime { + SystemTime::now() } } -pub struct MonotonicClock(cap_std::time::MonotonicClock); +pub struct MonotonicClock {} + impl MonotonicClock { - pub fn new(ambient_authority: AmbientAuthority) -> Self { - MonotonicClock(cap_std::time::MonotonicClock::new(ambient_authority)) + pub fn new() -> Self { + Self {} } } impl WasiMonotonicClock for MonotonicClock { fn resolution(&self) -> Duration { - self.0.resolution() + Duration::from_millis(1) } - fn now(&self, precision: Duration) -> Instant { - self.0.now_with(precision) + fn now(&self, _precision: Duration) -> Instant { + Instant::now() } } pub fn clocks_ctx() -> WasiClocks { WasiClocks::new() - .with_system(SystemClock::new(ambient_authority())) - .with_monotonic(MonotonicClock::new(ambient_authority())) + .with_system(SystemClock::new()) + .with_monotonic(MonotonicClock::new()) } diff --git a/crates/wasi-common/src/sync/dir.rs b/crates/wasi-common/src/sync/dir.rs index ff94f72..cdfdd9e 100644 --- a/crates/wasi-common/src/sync/dir.rs +++ b/crates/wasi-common/src/sync/dir.rs @@ -1,16 +1,23 @@ -use crate::sync::file::{filetype_from, File}; +use crate::sync::file::File; use crate::{ dir::{ReaddirCursor, ReaddirEntity, WasiDir}, file::{FdFlags, FileType, Filestat, OFlags}, Error, ErrorExt, }; -use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, OpenOptionsMaybeDirExt, SystemTimeSpec}; -use cap_std::fs; use std::any::Any; -use std::path::{Path, PathBuf}; -use system_interface::fs::GetSetFdFlags; +use std::fs::{create_dir, metadata, read_dir, remove_dir, remove_file, rename, OpenOptions}; +use std::path::{absolute, PathBuf}; -pub struct Dir(fs::Dir); +#[derive(Clone)] +pub struct Dir { + path: PathBuf, + parent: Option>, +} + +pub enum ResolveResult { + File(PathBuf), + Dir(Dir), +} pub enum OpenResult { File(File), @@ -18,23 +25,96 @@ pub enum OpenResult { } impl Dir { - pub fn from_cap_std(dir: fs::Dir) -> Self { - Dir(dir) + /// Path must be absolute + pub fn from_path(path: PathBuf) -> Self { + debug_assert!(path.is_absolute()); + Dir { + path: path, + parent: None, + } + } + + fn resolve_path(&self, path: &str, existed: Option) -> Result { + if let Some((dir_path, file_path)) = path.split_once('/') { + let new_dir: Self = match dir_path { + "." | "" => self.clone(), + ".." => { + let Some(parent) = self.parent.clone() else { + return Err(Error::perm()); + }; + parent.as_ref().clone() + } + dir_path => { + let dir_path = self.path.join(dir_path); + if !dir_path.exists() { + return Err(Error::not_found()); + } + let (old_path, new_path) = + (|| Ok::<_, Error>((absolute(&self.path)?, absolute(dir_path)?)))()?; + if !new_path.starts_with(old_path) { + return Err(Error::perm()); + } + Dir { + path: new_path, + parent: Some(Box::new(self.clone())), + } + } + }; + return new_dir.resolve_path(file_path, existed); + } + + let new_path = self.path.join(path); + + let (old_path, new_path) = + (|| Ok::<_, Error>((absolute(&self.path)?, absolute(new_path)?)))()?; + if !new_path.starts_with(old_path) { + return Err(Error::perm()); + } + + if new_path.exists() { + if existed == Some(false) { + return Err(Error::io()); + } + let metadata = metadata(&new_path)?; + if metadata.is_dir() { + Ok(ResolveResult::Dir(Dir { + path: new_path, + parent: Some(Box::new(self.clone())), + })) + } else { + Ok(ResolveResult::File(new_path)) + } + } else { + if existed == Some(true) { + return Err(Error::not_found()); + } + Ok(ResolveResult::File(new_path)) + } + } + + fn resolve_path_not_open_dir( + &self, + path: &str, + existed: Option, + ) -> Result { + match self.resolve_path(path, existed)? { + ResolveResult::File(path_buf) => Ok(path_buf), + ResolveResult::Dir(dir) => { + // TODO check if no files are opened within this dir + Ok(dir.path) + } + } } pub fn open_file_( &self, - symlink_follow: bool, path: &str, oflags: OFlags, read: bool, write: bool, fdflags: FdFlags, ) -> Result { - use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; - - let mut opts = fs::OpenOptions::new(); - opts.maybe_dir(true); + let mut opts = OpenOptions::new(); if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) { opts.create_new(true); @@ -60,12 +140,6 @@ impl Dir { if fdflags.contains(FdFlags::APPEND) { opts.append(true); } - - if symlink_follow { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); - } // the DSYNC, SYNC, and RSYNC flags are ignored! We do not // have support for them in cap-std yet. // ideally OpenOptions would just support this though: @@ -85,37 +159,30 @@ impl Dir { } } - let mut f = self.0.open_with(Path::new(path), &opts)?; - if f.metadata()?.is_dir() { - Ok(OpenResult::Dir(Dir::from_cap_std(fs::Dir::from_std_file( - f.into_std(), - )))) - } else if oflags.contains(OFlags::DIRECTORY) { - Err(Error::not_dir().context("expected directory but got file")) - } else { - // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: - if fdflags.contains(crate::file::FdFlags::NONBLOCK) { - let set_fd_flags = f.new_set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?; - f.set_fd_flags(set_fd_flags)?; + let resolved = self.resolve_path(path, Some(true))?; + + match resolved { + ResolveResult::File(path_buf) => { + if oflags.contains(OFlags::DIRECTORY) { + return Err(Error::not_dir()); + } + let file = opts.open(&path_buf)?; + + Ok(OpenResult::File(File::from_std(file, path_buf, fdflags))) + } + ResolveResult::Dir(dir) => { + if !oflags.contains(OFlags::DIRECTORY) { + return Err(Error::not_found()); + } + Ok(OpenResult::Dir(dir)) } - Ok(OpenResult::File(File::from_cap_std(f))) } } pub fn rename_(&self, src_path: &str, dest_dir: &Self, dest_path: &str) -> Result<(), Error> { - self.0 - .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?; - Ok(()) - } - pub fn hard_link_( - &self, - src_path: &str, - target_dir: &Self, - target_path: &str, - ) -> Result<(), Error> { - let src_path = Path::new(src_path); - let target_path = Path::new(target_path); - self.0.hard_link(src_path, &target_dir.0, target_path)?; + let src = self.resolve_path_not_open_dir(src_path, Some(true))?; + let dest = dest_dir.resolve_path_not_open_dir(dest_path, Some(false))?; + rename(src, dest)?; Ok(()) } } @@ -127,14 +194,14 @@ impl WasiDir for Dir { } async fn open_file( &self, - symlink_follow: bool, + _symlink_follow: bool, path: &str, oflags: OFlags, read: bool, write: bool, fdflags: FdFlags, ) -> Result { - let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?; + let f = self.open_file_(path, oflags, read, write, fdflags)?; match f { OpenResult::File(f) => Ok(crate::dir::OpenResult::File(Box::new(f))), OpenResult::Dir(d) => Ok(crate::dir::OpenResult::Dir(Box::new(d))), @@ -142,140 +209,100 @@ impl WasiDir for Dir { } async fn create_dir(&self, path: &str) -> Result<(), Error> { - self.0.create_dir(Path::new(path))?; + let path = self.resolve_path_not_open_dir(path, Some(false))?; + create_dir(path)?; Ok(()) } async fn readdir( &self, cursor: ReaddirCursor, ) -> Result> + Send>, Error> { - // We need to keep a full-fidelity io Error around to check for a special failure mode - // on windows, but also this function can fail due to an illegal byte sequence in a - // filename, which we can't construct an io Error to represent. - enum ReaddirError { - Io(std::io::Error), - IllegalSequence, - } - impl From for ReaddirError { - fn from(e: std::io::Error) -> ReaddirError { - ReaddirError::Io(e) - } - } - - // cap_std's read_dir does not include . and .., we should prepend these. - // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't - // have enough info to make a ReaddirEntity yet. - let dir_meta = self.0.dir_metadata()?; - let rd = vec![ - { - let name = ".".to_owned(); - Ok::<_, ReaddirError>((FileType::Directory, dir_meta.ino(), name)) - }, - { - let name = "..".to_owned(); - Ok((FileType::Directory, dir_meta.ino(), name)) - }, - ] - .into_iter() - .chain({ - // Now process the `DirEntry`s: - let entries = self.0.entries()?.map(|entry| { - let entry = entry?; - let meta = entry.full_metadata()?; - let inode = meta.ino(); - let filetype = filetype_from(&meta.file_type()); - let name = entry - .file_name() - .into_string() - .map_err(|_| ReaddirError::IllegalSequence)?; - Ok((filetype, inode, name)) - }); + let mut entries = vec![ + (FileType::Directory, ".".to_string()), + (FileType::Directory, "..".to_string()), + ]; - // On Windows, filter out files like `C:\DumpStack.log.tmp` which we - // can't get a full metadata for. - #[cfg(windows)] - let entries = entries.filter(|entry| { - use windows_sys::Win32::Foundation::{ - ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION, - }; - if let Err(ReaddirError::Io(err)) = entry { - if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32) - || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32) - { - return false; - } - } - true - }); + for entry in read_dir(&self.path)? { + let entry = entry?; + entries.push(( + if entry.metadata()?.is_dir() { + FileType::Directory + } else { + FileType::RegularFile + }, + entry.file_name().into_string().unwrap(), + )); + } - entries - }) - // Enumeration of the iterator makes it possible to define the ReaddirCursor - .enumerate() - .map(|(ix, r)| match r { - Ok((filetype, inode, name)) => Ok(ReaddirEntity { - next: ReaddirCursor::from(ix as u64 + 1), - filetype, - inode, - name, - }), - Err(ReaddirError::Io(e)) => Err(e.into()), - Err(ReaddirError::IllegalSequence) => Err(Error::illegal_byte_sequence()), - }) - .skip(u64::from(cursor) as usize); + let rd = entries + .into_iter() + .enumerate() + .map(|(ix, (filetype, name))| { + Ok(ReaddirEntity { + next: ReaddirCursor::from(ix as u64 + 1), + filetype, + inode: 0, + name, + }) + }) + .skip(u64::from(cursor) as usize); Ok(Box::new(rd)) } - async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - self.0.symlink(src_path, dest_path)?; - Ok(()) - } async fn remove_dir(&self, path: &str) -> Result<(), Error> { - self.0.remove_dir(Path::new(path))?; + let path = self.resolve_path_not_open_dir(path, Some(true))?; + remove_dir(path)?; Ok(()) } async fn unlink_file(&self, path: &str) -> Result<(), Error> { - self.0.remove_file_or_symlink(Path::new(path))?; + let path = match self.resolve_path(path, Some(true))? { + ResolveResult::File(path) => path, + ResolveResult::Dir(_) => return Err(Error::io()), + }; + remove_file(path)?; Ok(()) } - async fn read_link(&self, path: &str) -> Result { - let link = self.0.read_link(Path::new(path))?; - Ok(link) - } async fn get_filestat(&self) -> Result { - let meta = self.0.dir_metadata()?; Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + device_id: 0, + inode: 0, + filetype: FileType::Directory, + nlink: 1, + size: 4096, + atim: None, + mtim: None, + ctim: None, }) } async fn get_path_filestat( &self, path: &str, - follow_symlinks: bool, + _follow_symlinks: bool, ) -> Result { - let meta = if follow_symlinks { - self.0.metadata(Path::new(path))? - } else { - self.0.symlink_metadata(Path::new(path))? + let path = match self.resolve_path(path, Some(true))? { + ResolveResult::File(path_buf) => path_buf, + ResolveResult::Dir(dir) => dir.path, }; + let metadata = metadata(path)?; Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + device_id: 0, + inode: 0, + filetype: if metadata.is_dir() { + FileType::Directory + } else { + FileType::RegularFile + }, + nlink: 1, + size: if metadata.is_dir() { + 4096 + } else { + metadata.len() + }, + atim: None, + mtim: None, + ctim: None, }) } async fn rename( @@ -290,46 +317,12 @@ impl WasiDir for Dir { .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; self.rename_(src_path, dest_dir, dest_path) } - async fn hard_link( - &self, - src_path: &str, - target_dir: &dyn WasiDir, - target_path: &str, - ) -> Result<(), Error> { - let target_dir = target_dir - .as_any() - .downcast_ref::() - .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; - self.hard_link_(src_path, target_dir, target_path) - } - async fn set_times( - &self, - path: &str, - atime: Option, - mtime: Option, - follow_symlinks: bool, - ) -> Result<(), Error> { - if follow_symlinks { - self.0.set_times( - Path::new(path), - convert_systimespec(atime), - convert_systimespec(mtime), - )?; - } else { - self.0.set_symlink_times( - Path::new(path), - convert_systimespec(atime), - convert_systimespec(mtime), - )?; - } - Ok(()) - } } -fn convert_systimespec(t: Option) -> Option { - match t { - Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), - Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), - None => None, - } -} +// fn convert_systimespec(t: Option) -> Option { +// match t { +// Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), +// Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), +// None => None, +// } +// } diff --git a/crates/wasi-common/src/sync/file.rs b/crates/wasi-common/src/sync/file.rs index 099e4c7..71bb486 100644 --- a/crates/wasi-common/src/sync/file.rs +++ b/crates/wasi-common/src/sync/file.rs @@ -2,21 +2,27 @@ use crate::{ file::{Advice, FdFlags, FileType, Filestat, WasiFile}, Error, ErrorExt, }; -use cap_fs_ext::MetadataExt; -use fs_set_times::{SetTimes, SystemTimeSpec}; -use io_lifetimes::AsFilelike; -use std::any::Any; -use std::io::{self, IsTerminal}; -use system_interface::{ - fs::{FileIoExt, GetSetFdFlags}, - io::{IoExt, ReadReady}, +use std::io::{self}; +use std::{ + any::Any, + io::{Read, Seek, Write}, + path::PathBuf, + sync::Mutex, }; -pub struct File(cap_std::fs::File); +pub struct File { + std_file: Mutex, + path: PathBuf, + fdflags: FdFlags, +} impl File { - pub fn from_cap_std(file: cap_std::fs::File) -> Self { - File(file) + pub(crate) fn from_std(std_file: std::fs::File, path: PathBuf, fdflags: FdFlags) -> Self { + File { + std_file: Mutex::new(std_file), + path, + fdflags, + } } } @@ -26,63 +32,46 @@ impl WasiFile for File { self } async fn datasync(&self) -> Result<(), Error> { - self.0.sync_data()?; + let std_file = self.std_file.lock().unwrap(); + std_file.sync_data().map_err(|_| Error::io())?; Ok(()) } async fn sync(&self) -> Result<(), Error> { - self.0.sync_all()?; + let std_file = self.std_file.lock().unwrap(); + std_file.sync_all().map_err(|_| Error::io())?; Ok(()) } async fn get_filetype(&self) -> Result { - let meta = self.0.metadata()?; - Ok(filetype_from(&meta.file_type())) + Ok(FileType::RegularFile) } async fn get_fdflags(&self) -> Result { - let fdflags = get_fd_flags(&self.0)?; - Ok(fdflags) - } - async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags.intersects( - crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC, - ) { - return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); - } - let set_fd_flags = self.0.new_set_fd_flags(to_sysif_fdflags(fdflags))?; - self.0.set_fd_flags(set_fd_flags)?; - Ok(()) + Ok(self.fdflags) } async fn get_filestat(&self) -> Result { - let meta = self.0.metadata()?; + let std_file = self.std_file.lock().unwrap(); + let metadata = std_file.metadata()?; Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + device_id: 0, + inode: 0, + filetype: FileType::RegularFile, + nlink: 1, + size: metadata.len(), + atim: None, + mtim: None, + ctim: None, }) } async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { - self.0.set_len(size)?; + let std_file = self.std_file.lock().unwrap(); + std_file.set_len(size)?; Ok(()) } - async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - self.0.advise(offset, len, convert_advice(advice))?; - Ok(()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { Ok(()) } async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { - let n = self.0.read_vectored(bufs)?; + let mut std_file = self.std_file.lock().unwrap(); + let n = std_file.read_vectored(bufs)?; Ok(n.try_into()?) } async fn read_vectored_at<'a>( @@ -90,11 +79,14 @@ impl WasiFile for File { bufs: &mut [io::IoSliceMut<'a>], offset: u64, ) -> Result { - let n = self.0.read_vectored_at(bufs, offset)?; + let mut std_file = self.std_file.lock().unwrap(); + std_file.seek(io::SeekFrom::Start(offset))?; + let n = std_file.read_vectored(bufs)?; Ok(n.try_into()?) } async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - let n = self.0.write_vectored(bufs)?; + let mut std_file = self.std_file.lock().unwrap(); + let n = std_file.write_vectored(bufs)?; Ok(n.try_into()?) } async fn write_vectored_at<'a>( @@ -102,141 +94,79 @@ impl WasiFile for File { bufs: &[io::IoSlice<'a>], offset: u64, ) -> Result { - if bufs.iter().map(|i| i.len()).sum::() == 0 { - return Ok(0); - } - let n = self.0.write_vectored_at(bufs, offset)?; + let mut std_file = self.std_file.lock().unwrap(); + std_file.seek(io::SeekFrom::Start(offset))?; + let n = std_file.write_vectored(bufs)?; Ok(n.try_into()?) } async fn seek(&self, pos: std::io::SeekFrom) -> Result { - Ok(self.0.seek(pos)?) - } - async fn peek(&self, buf: &mut [u8]) -> Result { - let n = self.0.peek(buf)?; - Ok(n.try_into()?) - } - fn num_ready_bytes(&self) -> Result { - Ok(self.0.num_ready_bytes()?) + let mut std_file = self.std_file.lock().unwrap(); + Ok(std_file.seek(pos)?) } fn isatty(&self) -> bool { - #[cfg(unix)] - return self.0.as_fd().is_terminal(); - #[cfg(windows)] - return self.0.as_handle().is_terminal(); - } -} - -pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { - use cap_fs_ext::FileTypeExt; - if ft.is_dir() { - FileType::Directory - } else if ft.is_symlink() { - FileType::SymbolicLink - } else if ft.is_socket() { - if ft.is_block_device() { - FileType::SocketDgram - } else { - FileType::SocketStream - } - } else if ft.is_block_device() { - FileType::BlockDevice - } else if ft.is_char_device() { - FileType::CharacterDevice - } else if ft.is_file() { - FileType::RegularFile - } else { - FileType::Unknown - } -} - -#[cfg(windows)] -use io_lifetimes::{AsHandle, BorrowedHandle}; -#[cfg(windows)] -impl AsHandle for File { - fn as_handle(&self) -> BorrowedHandle<'_> { - self.0.as_handle() - } -} - -#[cfg(windows)] -use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; -#[cfg(windows)] -impl AsRawHandleOrSocket for File { - #[inline] - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() + false } } -#[cfg(unix)] -use io_lifetimes::{AsFd, BorrowedFd}; +// pub(crate) fn convert_systimespec(t: Option) -> Option { +// match t { +// Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())), +// Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), +// None => None, +// } +// } -#[cfg(unix)] -impl AsFd for File { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } -} +// pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags { +// let mut out = system_interface::fs::FdFlags::empty(); +// if f.contains(crate::file::FdFlags::APPEND) { +// out |= system_interface::fs::FdFlags::APPEND; +// } +// if f.contains(crate::file::FdFlags::DSYNC) { +// out |= system_interface::fs::FdFlags::DSYNC; +// } +// if f.contains(crate::file::FdFlags::NONBLOCK) { +// out |= system_interface::fs::FdFlags::NONBLOCK; +// } +// if f.contains(crate::file::FdFlags::RSYNC) { +// out |= system_interface::fs::FdFlags::RSYNC; +// } +// if f.contains(crate::file::FdFlags::SYNC) { +// out |= system_interface::fs::FdFlags::SYNC; +// } +// out +// } -pub(crate) fn convert_systimespec(t: Option) -> Option { - match t { - Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())), - Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), - None => None, - } -} +// /// Return the file-descriptor flags for a given file-like object. +// /// +// /// This returns the flags needed to implement [`WasiFile::get_fdflags`]. +// pub fn get_fd_flags(f: Filelike) -> io::Result { +// let f = f.as_filelike().get_fd_flags()?; +// let mut out = crate::file::FdFlags::empty(); +// if f.contains(system_interface::fs::FdFlags::APPEND) { +// out |= crate::file::FdFlags::APPEND; +// } +// if f.contains(system_interface::fs::FdFlags::DSYNC) { +// out |= crate::file::FdFlags::DSYNC; +// } +// if f.contains(system_interface::fs::FdFlags::NONBLOCK) { +// out |= crate::file::FdFlags::NONBLOCK; +// } +// if f.contains(system_interface::fs::FdFlags::RSYNC) { +// out |= crate::file::FdFlags::RSYNC; +// } +// if f.contains(system_interface::fs::FdFlags::SYNC) { +// out |= crate::file::FdFlags::SYNC; +// } +// Ok(out) +// } -pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags { - let mut out = system_interface::fs::FdFlags::empty(); - if f.contains(crate::file::FdFlags::APPEND) { - out |= system_interface::fs::FdFlags::APPEND; - } - if f.contains(crate::file::FdFlags::DSYNC) { - out |= system_interface::fs::FdFlags::DSYNC; - } - if f.contains(crate::file::FdFlags::NONBLOCK) { - out |= system_interface::fs::FdFlags::NONBLOCK; - } - if f.contains(crate::file::FdFlags::RSYNC) { - out |= system_interface::fs::FdFlags::RSYNC; - } - if f.contains(crate::file::FdFlags::SYNC) { - out |= system_interface::fs::FdFlags::SYNC; - } - out -} - -/// Return the file-descriptor flags for a given file-like object. -/// -/// This returns the flags needed to implement [`WasiFile::get_fdflags`]. -pub fn get_fd_flags(f: Filelike) -> io::Result { - let f = f.as_filelike().get_fd_flags()?; - let mut out = crate::file::FdFlags::empty(); - if f.contains(system_interface::fs::FdFlags::APPEND) { - out |= crate::file::FdFlags::APPEND; - } - if f.contains(system_interface::fs::FdFlags::DSYNC) { - out |= crate::file::FdFlags::DSYNC; - } - if f.contains(system_interface::fs::FdFlags::NONBLOCK) { - out |= crate::file::FdFlags::NONBLOCK; - } - if f.contains(system_interface::fs::FdFlags::RSYNC) { - out |= crate::file::FdFlags::RSYNC; - } - if f.contains(system_interface::fs::FdFlags::SYNC) { - out |= crate::file::FdFlags::SYNC; - } - Ok(out) -} - -fn convert_advice(advice: Advice) -> system_interface::fs::Advice { - match advice { - Advice::Normal => system_interface::fs::Advice::Normal, - Advice::Sequential => system_interface::fs::Advice::Sequential, - Advice::Random => system_interface::fs::Advice::Random, - Advice::WillNeed => system_interface::fs::Advice::WillNeed, - Advice::DontNeed => system_interface::fs::Advice::DontNeed, - Advice::NoReuse => system_interface::fs::Advice::NoReuse, - } -} +// fn convert_advice(advice: Advice) -> system_interface::fs::Advice { +// match advice { +// Advice::Normal => system_interface::fs::Advice::Normal, +// Advice::Sequential => system_interface::fs::Advice::Sequential, +// Advice::Random => system_interface::fs::Advice::Random, +// Advice::WillNeed => system_interface::fs::Advice::WillNeed, +// Advice::DontNeed => system_interface::fs::Advice::DontNeed, +// Advice::NoReuse => system_interface::fs::Advice::NoReuse, +// } +// } diff --git a/crates/wasi-common/src/sync/mod.rs b/crates/wasi-common/src/sync/mod.rs index bb58e0c..8514e1c 100644 --- a/crates/wasi-common/src/sync/mod.rs +++ b/crates/wasi-common/src/sync/mod.rs @@ -18,17 +18,15 @@ pub mod clocks; pub mod dir; pub mod file; pub mod sched; +pub mod stdio; -pub use cap_std::ambient_authority; -pub use cap_std::fs::Dir; -pub use cap_std::net::TcpListener; pub use clocks::clocks_ctx; pub use sched::sched_ctx; -use crate::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile}; +use crate::{table::Table, Error, WasiCtx, WasiFile}; use rand::{Rng, SeedableRng}; use std::mem; -use std::path::Path; +use std::path::{absolute, Path}; pub struct WasiCtxBuilder { ctx: WasiCtx, @@ -88,10 +86,10 @@ impl WasiCtxBuilder { } pub fn preopened_dir( &mut self, - dir: Dir, + host_path: impl AsRef, guest_path: impl AsRef, ) -> Result<&mut Self, Error> { - let dir = Box::new(crate::sync::dir::Dir::from_cap_std(dir)); + let dir = Box::new(crate::sync::dir::Dir::from_path(absolute(host_path)?)); self.ctx.push_preopened_dir(dir, guest_path)?; Ok(self) } @@ -106,6 +104,3 @@ impl WasiCtxBuilder { pub fn random_ctx() -> Box { Box::new(rand::rngs::StdRng::from_seed(rand::random())) } - -#[cfg(feature = "wasmtime")] -super::define_wasi!(block_on); diff --git a/crates/wasi-common/src/sync/stdio.rs b/crates/wasi-common/src/sync/stdio.rs new file mode 100644 index 0000000..faf22c7 --- /dev/null +++ b/crates/wasi-common/src/sync/stdio.rs @@ -0,0 +1,67 @@ +use std::any::Any; +use std::sync::Mutex; + +use crate::{ + file::{FdFlags, FileType, WasiFile}, + Error, ErrorExt, +}; + +pub type StdoutFn = Box; +pub struct Stdout { + stdout_fn: StdoutFn, + buf: Mutex>, +} + +impl Stdout { + pub fn new(stdout_fn: StdoutFn) -> Self { + Self { + stdout_fn, + buf: Mutex::new(Vec::new()), + } + } +} + +#[async_trait::async_trait] +impl WasiFile for Stdout { + fn as_any(&self) -> &dyn Any { + self + } + async fn get_filetype(&self) -> Result { + Ok(FileType::CharacterDevice) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::APPEND) + } + async fn write_vectored<'a>(&self, bufs: &[std::io::IoSlice<'a>]) -> Result { + let mut buf = self.buf.lock().unwrap(); + let mut n = 0; + for new_buf in bufs { + n += new_buf.len(); + buf.extend_from_slice(new_buf); + } + let mut lines = buf + .as_slice() + .split(&|byte: &u8| *byte == 10) + .collect::>(); + let new_buf = lines.pop().unwrap().to_vec(); + for line in lines { + (self.stdout_fn)(&String::from_utf8_lossy(line)); + } + *buf = new_buf; + Ok(n.try_into() + .map_err(|_| Error::range().context("converting write_vectored total length"))?) + } + async fn write_vectored_at<'a>( + &self, + _bufs: &[std::io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::seek_pipe()) + } + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::seek_pipe()) + } + fn isatty(&self) -> bool { + true + } +} diff --git a/crates/wasip1/src/fs/dev/wakeup.rs b/crates/wasip1/src/fs/dev/wakeup.rs index 0428a1a..85ba568 100644 --- a/crates/wasip1/src/fs/dev/wakeup.rs +++ b/crates/wasip1/src/fs/dev/wakeup.rs @@ -1,6 +1,4 @@ -use webrogue_wasi_common::{file::FileType, ErrorExt as _, WasiFile}; - -use webrogue_wasi_common::ErrorExt as _; +use webrogue_wasi_common::{file::FileType, WasiFile}; pub struct File { tx: tokio::sync::watch::Sender, @@ -52,14 +50,14 @@ impl WasiFile for File { async fn write_vectored<'a>( &self, - _bufs: &[std::io::IoSlice<'a>], + bufs: &[std::io::IoSlice<'a>], ) -> Result { self.signal(); - Ok(_bufs.iter().map(|slice| slice.len()).sum::() as u64) + Ok(bufs.iter().map(|slice| slice.len()).sum::() as u64) } async fn readable(&self) -> Result<(), webrogue_wasi_common::Error> { - self.wait(); + self.wait().await; Ok(()) } diff --git a/crates/wasip1/src/lib.rs b/crates/wasip1/src/lib.rs index cf32c60..48a3c9d 100644 --- a/crates/wasip1/src/lib.rs +++ b/crates/wasip1/src/lib.rs @@ -9,10 +9,14 @@ pub fn make_ctx( ) -> anyhow::Result { #[cfg(not(target_arch = "wasm32"))] let mut wasi_ctx = { + use webrogue_wasi_common::sync::stdio::Stdout; + let mut builder = webrogue_wasi_common::sync::WasiCtxBuilder::new(); // builder.inherit_stdio(); - // builder.stdout(Box::new(stdout::STDOutFile {})); - // builder.stderr(Box::new(stdout::STDOutFile {})); + builder.stdout(Box::new(Stdout::new(Box::new(|line| println!("{}", line))))); + builder.stderr(Box::new(Stdout::new(Box::new(|line| { + eprintln!("{}", line) + })))); builder.build() }; #[cfg(target_arch = "wasm32")] @@ -35,6 +39,8 @@ pub fn make_ctx( #[cfg(not(target_arch = "wasm32"))] { // TODO check if this check enough + + use std::path::absolute; anyhow::ensure!( !persistent.name.contains("/") && !persistent.name.contains("\\") @@ -47,12 +53,8 @@ pub fn make_ctx( if !real_path.is_dir() { std::fs::create_dir_all(&real_path)?; } - let home_dir = webrogue_wasi_common::sync::dir::Dir::from_cap_std( - webrogue_wasi_common::sync::Dir::open_ambient_dir( - real_path, - webrogue_wasi_common::sync::ambient_authority(), - )?, - ); + let home_dir = + webrogue_wasi_common::sync::dir::Dir::from_path(absolute(real_path)?); wasi_ctx.push_preopened_dir(Box::new(home_dir), &persistent.mapped_path)?; } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 19d4129..9757f00 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -18,6 +18,6 @@ anyhow = { workspace = true } webrogue-wrapp = { workspace = true } webrogue-wasip1 = { workspace = true } wasmtime = { workspace = true, default-features = false, features = ["runtime", "threads", "gc", "gc-drc", "anyhow"] } -webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync", "wasmtime"] } +webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } wiggle = { workspace = true, default-features = false, features = ["wasmtime"] } webrogue-aot-data = { workspace = true, optional = true } From 42d55810d6f3bf5dc397b6c742208d803ae89024 Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Fri, 15 May 2026 13:42:59 -0400 Subject: [PATCH 3/5] Fix --- crates/wasip1/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index d685baf..0dff518 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -17,7 +17,7 @@ async-trait = { workspace = true } rand_core = { workspace = true } rand = { workspace = true, default-features = false, features = ["sys_rng", "thread_rng"] } getrandom = { workspace = true, default-features = false, features = ["wasm_js"] } -tokio = { workspace = true } +tokio = { workspace = true, features = ["sync"] } lazy_static = { workspace = true } rustix = { workspace = true, features = ["std"] } From 153479bf30180dc46b14beb0cb5b13b3b56f247e Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Fri, 15 May 2026 13:45:26 -0400 Subject: [PATCH 4/5] Fix --- crates/wasip1/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 0dff518..0f8421c 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -17,7 +17,7 @@ async-trait = { workspace = true } rand_core = { workspace = true } rand = { workspace = true, default-features = false, features = ["sys_rng", "thread_rng"] } getrandom = { workspace = true, default-features = false, features = ["wasm_js"] } -tokio = { workspace = true, features = ["sync"] } +tokio = { workspace = true, features = ["rt", "time", "sync"] } lazy_static = { workspace = true } rustix = { workspace = true, features = ["std"] } From a8f14466198adabe572dbc3ad70741c55d5811ed Mon Sep 17 00:00:00 2001 From: Artem Borovik Date: Fri, 15 May 2026 13:54:48 -0400 Subject: [PATCH 5/5] remove some more deps + add some fixes --- Cargo.lock | 3 --- Cargo.toml | 1 - crates/wasi-common/src/sync/file.rs | 4 ++-- crates/wasip1/Cargo.toml | 8 -------- crates/wasmtime/Cargo.toml | 2 +- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 771eda1..a356dd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8470,17 +8470,14 @@ dependencies = [ "anyhow", "async-trait", "getrandom 0.4.2", - "io-extras", "lazy_static", "rand 0.10.1", "rand_core 0.10.0", - "rustix 1.1.4", "tokio", "wasmtime-internal-core", "webrogue-wasi-common", "webrogue-wrapp", "wiggle", - "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index aad228b..9abc085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,7 +172,6 @@ blake3 = { version = "1.8.2" } async-trait = { version = "0.1.89" } rand = { version = "0.10", default-features = false } rand_core = { version = "0.10", default-features = false } -io-extras = "0.18.4" softbuffer = "0.4.8" ico = "0.5.0" tar = "0.4" diff --git a/crates/wasi-common/src/sync/file.rs b/crates/wasi-common/src/sync/file.rs index 71bb486..438aba7 100644 --- a/crates/wasi-common/src/sync/file.rs +++ b/crates/wasi-common/src/sync/file.rs @@ -12,7 +12,7 @@ use std::{ pub struct File { std_file: Mutex, - path: PathBuf, + _path: PathBuf, fdflags: FdFlags, } @@ -20,7 +20,7 @@ impl File { pub(crate) fn from_std(std_file: std::fs::File, path: PathBuf, fdflags: FdFlags) -> Self { File { std_file: Mutex::new(std_file), - path, + _path: path, fdflags, } } diff --git a/crates/wasip1/Cargo.toml b/crates/wasip1/Cargo.toml index 0f8421c..3abaca7 100644 --- a/crates/wasip1/Cargo.toml +++ b/crates/wasip1/Cargo.toml @@ -19,14 +19,6 @@ rand = { workspace = true, default-features = false, features = ["sys_rng", "thr getrandom = { workspace = true, default-features = false, features = ["wasm_js"] } tokio = { workspace = true, features = ["rt", "time", "sync"] } lazy_static = { workspace = true } -rustix = { workspace = true, features = ["std"] } - -[target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["pipe"] } - -[target.'cfg(windows)'.dependencies] -io-extras = { workspace = true } -windows-sys = { workspace = true, features = ["Win32_System_Threading", "Win32_Security"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 9757f00..02546d3 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -17,7 +17,7 @@ webrogue-gfxstream = { workspace = true } anyhow = { workspace = true } webrogue-wrapp = { workspace = true } webrogue-wasip1 = { workspace = true } -wasmtime = { workspace = true, default-features = false, features = ["runtime", "threads", "gc", "gc-drc", "anyhow"] } +wasmtime = { workspace = true, default-features = false, features = ["runtime", "threads", "gc", "gc-drc", "anyhow", "component-model"] } webrogue-wasi-common = { workspace = true, default-features = false, features = ["sync"] } wiggle = { workspace = true, default-features = false, features = ["wasmtime"] } webrogue-aot-data = { workspace = true, optional = true }