diff --git a/Cargo.lock b/Cargo.lock index 2ac571fc39..92c5e7611a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,17 @@ dependencies = [ "unsafe-io", ] +[[package]] +name = "cap-tempfile" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2f6f45ddb06ff26f4cf2ba9838d5826d52e1a5f6b321d71f114bb38cf34a57" +dependencies = [ + "cap-std", + "rand 0.8.3", + "uuid", +] + [[package]] name = "cap-time-ext" version = "0.13.7" @@ -3060,6 +3071,15 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.2", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -3180,13 +3200,16 @@ dependencies = [ name = "wasi-tokio" version = "0.26.0" dependencies = [ + "anyhow", "bitflags", "cap-fs-ext", "cap-std", + "cap-tempfile", "cap-time-ext", "fs-set-times", "lazy_static", "libc", + "posish", "system-interface", "tempfile", "tokio", diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index 777578bd4b..49e79f3d75 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -15,7 +15,7 @@ include = ["src/**/*", "LICENSE" ] wasi-common = { path = "../", version = "0.26.0" } wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } wiggle = { path = "../../wiggle", version = "0.26.0" } -tokio = { version = "1.5.0", features = [ "rt", "fs", "time", "io-util", "net"] } +tokio = { version = "1.5.0", features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"] } cap-std = "0.13.7" cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" @@ -27,6 +27,8 @@ bitflags = "1.2" [target.'cfg(unix)'.dependencies] libc = "0.2" +posish = "0.6.1" + [target.'cfg(windows)'.dependencies] winapi = "0.3" @@ -35,3 +37,5 @@ lazy_static = "1.4" [dev-dependencies] tempfile = "3.1.0" tokio = { version = "1.5.0", features = [ "macros" ] } +anyhow = "1" +cap-tempfile = "0.13.7" diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/tokio/src/dir.rs index 9188d0cca7..f3d6623b62 100644 --- a/crates/wasi-common/tokio/src/dir.rs +++ b/crates/wasi-common/tokio/src/dir.rs @@ -76,7 +76,7 @@ impl WasiDir for Dir { return Err(Error::not_supported().context("SYNC family of FdFlags")); } - let f = asyncify(move || self.0.open_with(Path::new(path), &opts)).await?; + let f = asyncify(move || self.0.open_with(Path::new(path), &opts))?; let mut f = File::from_cap_std(f); // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: if fdflags.contains(FdFlags::NONBLOCK) { @@ -88,15 +88,15 @@ impl WasiDir for Dir { async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { let path = unsafe { std::mem::transmute::<_, &'static str>(path) }; let d = if symlink_follow { - asyncify(move || self.0.open_dir(Path::new(path))).await? + asyncify(move || self.0.open_dir(Path::new(path)))? } else { - asyncify(move || self.0.open_dir_nofollow(Path::new(path))).await? + asyncify(move || self.0.open_dir_nofollow(Path::new(path)))? }; Ok(Box::new(Dir::from_cap_std(d))) } async fn create_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.create_dir(Path::new(path))).await?; + asyncify(|| self.0.create_dir(Path::new(path)))?; Ok(()) } async fn readdir( @@ -106,7 +106,7 @@ impl WasiDir for Dir { // 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 = asyncify(|| self.0.dir_metadata()).await?; + let dir_meta = asyncify(|| self.0.dir_metadata())?; let rd = vec![ { let name = ".".to_owned(); @@ -150,24 +150,24 @@ impl WasiDir for Dir { } async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - asyncify(|| self.0.symlink(src_path, dest_path)).await?; + asyncify(|| self.0.symlink(src_path, dest_path))?; Ok(()) } async fn remove_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_dir(Path::new(path))).await?; + asyncify(|| self.0.remove_dir(Path::new(path)))?; Ok(()) } async fn unlink_file(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_file_or_symlink(Path::new(path))).await?; + asyncify(|| self.0.remove_file_or_symlink(Path::new(path)))?; Ok(()) } async fn read_link(&self, path: &str) -> Result { - let link = asyncify(|| self.0.read_link(Path::new(path))).await?; + let link = asyncify(|| self.0.read_link(Path::new(path)))?; Ok(link) } async fn get_filestat(&self) -> Result { - let meta = asyncify(|| self.0.dir_metadata()).await?; + let meta = asyncify(|| self.0.dir_metadata())?; Ok(Filestat { device_id: meta.dev(), inode: meta.ino(), @@ -185,9 +185,9 @@ impl WasiDir for Dir { follow_symlinks: bool, ) -> Result { let meta = if follow_symlinks { - asyncify(|| self.0.metadata(Path::new(path))).await? + asyncify(|| self.0.metadata(Path::new(path)))? } else { - asyncify(|| self.0.symlink_metadata(Path::new(path))).await? + asyncify(|| self.0.symlink_metadata(Path::new(path)))? }; Ok(Filestat { device_id: meta.dev(), @@ -213,8 +213,7 @@ impl WasiDir for Dir { asyncify(|| { self.0 .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path)) - }) - .await?; + })?; Ok(()) } async fn hard_link( @@ -229,7 +228,7 @@ impl WasiDir for Dir { .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; let src_path = Path::new(src_path); let target_path = Path::new(target_path); - asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path)).await?; + asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path))?; Ok(()) } async fn set_times( @@ -253,8 +252,7 @@ impl WasiDir for Dir { convert_systimespec(mtime), ) } - }) - .await?; + })?; Ok(()) } } @@ -270,7 +268,7 @@ fn convert_systimespec(t: Option) -> Option Result { use unsafe_io::AsUnsafeFile; - asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)).await + asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)) } } @@ -86,7 +86,7 @@ impl WasiFile for File { Ok(filetype_from(&meta.file_type())) } async fn get_fdflags(&self) -> Result { - let fdflags = asyncify(|| self.0.get_fd_flags()).await?; + let fdflags = asyncify(|| self.0.get_fd_flags())?; Ok(from_sysif_fdflags(fdflags)) } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { @@ -97,7 +97,7 @@ impl WasiFile for File { ) { return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); } - asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags))).await?; + asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags)))?; Ok(()) } async fn get_filestat(&self) -> Result { @@ -118,11 +118,11 @@ impl WasiFile for File { Ok(()) } async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - asyncify(move || self.0.advise(offset, len, convert_advice(advice))).await?; + asyncify(move || self.0.advise(offset, len, convert_advice(advice)))?; Ok(()) } async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { - asyncify(move || self.0.allocate(offset, len)).await?; + asyncify(move || self.0.allocate(offset, len))?; Ok(()) } async fn set_times( @@ -133,8 +133,7 @@ impl WasiFile for File { asyncify(|| { self.0 .set_times(convert_systimespec(atime), convert_systimespec(mtime)) - }) - .await?; + })?; Ok(()) } async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { @@ -155,7 +154,7 @@ impl WasiFile for File { bufs: &mut [io::IoSliceMut<'a>], offset: u64, ) -> Result { - let n = asyncify(move || self.0.read_vectored_at(bufs, offset)).await?; + let n = asyncify(move || self.0.read_vectored_at(bufs, offset))?; Ok(n.try_into()?) } async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { @@ -169,7 +168,7 @@ impl WasiFile for File { bufs: &[io::IoSlice<'a>], offset: u64, ) -> Result { - let n = asyncify(move || self.0.write_vectored_at(bufs, offset)).await?; + let n = asyncify(move || self.0.write_vectored_at(bufs, offset))?; Ok(n.try_into()?) } async fn seek(&self, pos: std::io::SeekFrom) -> Result { @@ -177,12 +176,12 @@ impl WasiFile for File { Ok(self.0.inner().seek(pos).await?) } async fn peek(&self, buf: &mut [u8]) -> Result { - let n = asyncify(move || self.0.peek(buf)).await?; + let n = asyncify(move || self.0.peek(buf))?; Ok(n.try_into()?) } async fn num_ready_bytes(&self) -> Result { use unsafe_io::AsUnsafeFile; - asyncify(|| self.0.as_file_view().num_ready_bytes()).await + asyncify(|| self.0.as_file_view().num_ready_bytes()) } #[cfg(not(windows))] async fn readable(&mut self) -> Result<(), Error> { @@ -194,9 +193,18 @@ impl WasiFile for File { use tokio::io::{unix::AsyncFd, Interest}; use unsafe_io::os::posish::AsRawFd; let rawfd = self.0.as_raw_fd(); - let asyncfd = AsyncFd::with_interest(rawfd, Interest::READABLE)?; - let _ = asyncfd.readable().await?; - Ok(()) + 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 isnt supported by epoll because it is immediately + // available for reading: + Ok(()) + } + Err(e) => Err(e.into()), + } } #[cfg(windows)] async fn readable(&mut self) -> Result<(), Error> { @@ -214,9 +222,18 @@ impl WasiFile for File { use tokio::io::{unix::AsyncFd, Interest}; use unsafe_io::os::posish::AsRawFd; let rawfd = self.0.as_raw_fd(); - let asyncfd = AsyncFd::with_interest(rawfd, Interest::WRITABLE)?; - let _ = asyncfd.writable().await?; - Ok(()) + 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 isnt supported by epoll because it is immediately + // available for writing: + Ok(()) + } + Err(e) => Err(e.into()), + } } #[cfg(windows)] async fn writable(&mut self) -> Result<(), Error> { diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index a5ff5f8ca7..455e97e6b3 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -1,13 +1,17 @@ mod dir; mod file; -mod sched; +pub mod sched; +pub mod stdio; use std::cell::RefCell; use std::path::Path; use std::rc::Rc; -pub use wasi_cap_std_sync::{clocks_ctx, random_ctx, Dir}; +pub use wasi_cap_std_sync::{clocks_ctx, random_ctx}; use wasi_common::{Error, Table, WasiCtx}; +pub use dir::Dir; +pub use file::File; + use crate::sched::sched_ctx; pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder); @@ -81,10 +85,10 @@ impl WasiCtxBuilder { } pub fn preopened_dir( self, - dir: Dir, + dir: cap_std::fs::Dir, guest_path: impl AsRef, ) -> Result { - let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); + let dir = Box::new(Dir::from_cap_std(dir)); Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?)) } pub fn build(self) -> Result { @@ -92,19 +96,10 @@ impl WasiCtxBuilder { } } -pub(crate) async fn asyncify<'a, F, T>(f: F) -> Result +pub(crate) fn asyncify<'a, F, T>(f: F) -> Result where F: FnOnce() -> Result + Send + 'a, T: Send + 'static, { - // spawn_blocking requires a 'static function, but since we await on the - // JoinHandle the lifetime of the spawn will be no longer than this function's body - let f: Box Result + Send + 'a> = Box::new(f); - let f = unsafe { - std::mem::transmute::<_, Box Result + Send + 'static>>(f) - }; - match tokio::task::spawn_blocking(|| f()).await { - Ok(res) => Ok(res?), - Err(_) => panic!("spawn_blocking died"), - } + tokio::task::block_in_place(f).map_err(Into::into) } diff --git a/crates/wasi-common/tokio/src/sched.rs b/crates/wasi-common/tokio/src/sched.rs index fd41f32996..1b97166641 100644 --- a/crates/wasi-common/tokio/src/sched.rs +++ b/crates/wasi-common/tokio/src/sched.rs @@ -1,12 +1,12 @@ #[cfg(unix)] mod unix; #[cfg(unix)] -use unix::poll_oneoff; +pub use unix::poll_oneoff; #[cfg(windows)] mod windows; #[cfg(windows)] -use windows::poll_oneoff; +pub use windows::poll_oneoff; use wasi_common::{ sched::{Duration, Poll, WasiSched}, @@ -18,7 +18,7 @@ pub fn sched_ctx() -> Box { #[wiggle::async_trait] impl WasiSched for AsyncSched { - async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { poll_oneoff(poll).await } async fn sched_yield(&self) -> Result<(), Error> { diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index f286b0d29a..614a5f16de 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -1,48 +1,87 @@ -use cap_std::time::Duration; -use std::convert::TryInto; use std::future::Future; -use std::ops::Deref; use std::pin::Pin; -use std::task::Context; +use std::task::{Context, Poll as FPoll}; use wasi_common::{ - file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, Poll, }, - Error, ErrorExt, + Context as _, Error, }; -pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { +struct FirstReady<'a, T>(Vec + 'a>>>); + +impl<'a, T> FirstReady<'a, T> { + fn new() -> Self { + FirstReady(Vec::new()) + } + fn push(&mut self, f: impl Future + '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 + // get the opportunity to become ready. + 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 mut futures: Vec>>>> = Vec::new(); - let timeout = poll.earliest_clock_deadline(); + + 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(Box::pin(async move { - f.file()?.readable().await?; - f.complete(f.file()?.num_ready_bytes().await?, RwEventFlags::empty()); - Ok(()) - })); + futures.push(async move { + f.file.readable().await.context("readable future")?; + f.complete( + f.file + .num_ready_bytes() + .await + .context("read num_ready_bytes")?, + RwEventFlags::empty(), + ); + Ok::<(), Error>(()) + }); } Subscription::Write(f) => { - futures.push(Box::pin(async move { - f.file()?.writable().await?; + futures.push(async move { + f.file.writable().await.context("writable future")?; f.complete(0, RwEventFlags::empty()); Ok(()) - })); + }); } Subscription::MonotonicClock { .. } => unreachable!(), } } - - // Incorrect, but lets get the type errors fixed before we write the right multiplexer here: - for f in futures { - f.await?; + if let Some(Some(remaining_duration)) = duration { + tokio::time::timeout(remaining_duration, futures).await??; + } else { + futures.await?; } + Ok(()) } diff --git a/crates/wasi-common/tokio/src/stdio.rs b/crates/wasi-common/tokio/src/stdio.rs new file mode 100644 index 0000000000..1dd7431605 --- /dev/null +++ b/crates/wasi-common/tokio/src/stdio.rs @@ -0,0 +1,295 @@ +use crate::file::convert_systimespec; +use fs_set_times::SetTimes; +use std::any::Any; +use std::convert::TryInto; +use std::io; +use std::io::{Read, Write}; + +use unsafe_io::AsUnsafeFile; +use wasi_common::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, ErrorExt, +}; + +mod internal { + #[cfg(unix)] + use std::os::unix::io::{AsRawFd, RawFd}; + #[cfg(windows)] + use std::os::windows::io::{AsRawHandle, RawHandle}; + use unsafe_io::OwnsRaw; + + pub(super) struct TokioStdin(tokio::io::Stdin); + impl TokioStdin { + pub fn new() -> Self { + TokioStdin(tokio::io::stdin()) + } + } + + #[cfg(windows)] + impl AsRawHandle for TokioStdin { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStdin { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStdin {} + + pub(super) struct TokioStdout(tokio::io::Stdout); + impl TokioStdout { + pub fn new() -> Self { + TokioStdout(tokio::io::stdout()) + } + } + + #[cfg(windows)] + impl AsRawHandle for TokioStdout { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStdout { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStdout {} + + pub(super) struct TokioStderr(tokio::io::Stderr); + impl TokioStderr { + pub fn new() -> Self { + TokioStderr(tokio::io::stderr()) + } + } + #[cfg(windows)] + impl AsRawHandle for TokioStderr { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStderr { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStderr {} +} + +pub struct Stdin(internal::TokioStdin); + +pub fn stdin() -> Stdin { + Stdin(internal::TokioStdin::new()) +} + +#[wiggle::async_trait] +impl WasiFile for Stdin { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + async fn sync(&self) -> Result<(), Error> { + Ok(()) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Unknown) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::empty()) + } + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.as_file_view().metadata()?; + Ok(Filestat { + device_id: 0, + inode: 0, + filetype: self.get_filetype().await?, + nlink: 0, + size: meta.len(), + atim: meta.accessed().ok(), + mtim: meta.modified().ok(), + ctim: meta.created().ok(), + }) + } + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Err(Error::badf()) + } + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + let n = self.0.as_file_view().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 write_vectored<'a>(&self, _bufs: &[io::IoSlice<'a>]) -> Result { + Err(Error::badf()) + } + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + 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(()) + } + + #[cfg(not(windows))] + async fn num_ready_bytes(&self) -> Result { + Ok(posish::io::fionread(&self.0)?) + } + #[cfg(windows)] + async fn num_ready_bytes(&self) -> Result { + // conservative but correct is the best we can do + Ok(0) + } + + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } +} + +macro_rules! wasi_file_write_impl { + ($ty:ty) => { + #[wiggle::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + async fn sync(&self) -> Result<(), Error> { + Ok(()) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Unknown) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::APPEND) + } + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.as_file_view().metadata()?; + Ok(Filestat { + device_id: 0, + inode: 0, + filetype: self.get_filetype().await?, + nlink: 0, + size: meta.len(), + atim: meta.accessed().ok(), + mtim: meta.modified().ok(), + ctim: meta.created().ok(), + }) + } + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Err(Error::badf()) + } + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn read_vectored<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + Err(Error::badf()) + } + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + let n = self.0.as_file_view().write_vectored(bufs)?; + Ok(n.try_into().map_err(|c| Error::range().context(c))?) + } + 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 peek(&self, _buf: &mut [u8]) -> Result { + Err(Error::badf()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + async fn num_ready_bytes(&self) -> Result { + Ok(0) + } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + } + }; +} + +pub struct Stdout(internal::TokioStdout); + +pub fn stdout() -> Stdout { + Stdout(internal::TokioStdout::new()) +} +wasi_file_write_impl!(Stdout); + +pub struct Stderr(internal::TokioStderr); + +pub fn stderr() -> Stderr { + Stderr(internal::TokioStderr::new()) +} +wasi_file_write_impl!(Stderr); diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs new file mode 100644 index 0000000000..b0e220c86b --- /dev/null +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -0,0 +1,27 @@ +use anyhow::{Context, Error}; +use std::ops::Deref; +use wasi_common::{ + file::{FdFlags, OFlags}, + sched::{Poll, Userdata}, + WasiDir, WasiFile, +}; +use wasi_tokio::{sched::poll_oneoff, Dir, File}; + +#[tokio::test(flavor = "multi_thread")] +async fn main() -> Result<(), Error> { + let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; + workspace.create_dir("d").context("create dir")?; + let d = workspace.open_dir("d").context("open dir")?; + let d = Dir::from_cap_std(d); + + let mut readable_f = d + .open_file(false, "f", OFlags::CREATE, true, false, FdFlags::empty()) + .await + .context("create readable file")?; + + let mut poll = Poll::new(); + poll.subscribe_read(&mut *readable_f, Userdata::from(123)); + let poll_events = poll_oneoff(&mut poll).await?; + + Ok(()) +}