Merge pull request #2832 from bytecodealliance/pch/wiggle_sync_shimming

wasi-common support for tokio, & wiggle support for async methods containing sync code
This commit is contained in:
Pat Hickey
2021-05-07 17:43:42 -07:00
committed by GitHub
56 changed files with 2893 additions and 866 deletions

View File

@@ -13,13 +13,14 @@ include = ["src/**/*", "LICENSE" ]
[dependencies]
wasi-common = { path = "../", version = "0.26.0" }
async-trait = "0.1"
anyhow = "1.0"
cap-std = "0.13.9"
cap-fs-ext = "0.13.9"
cap-time-ext = "0.13.9"
cap-rand = "0.13.9"
fs-set-times = "0.3.1"
unsafe-io = "0.6.2"
unsafe-io = "0.6.5"
system-interface = { version = "0.6.3", features = ["cap_std_impls"] }
tracing = "0.1.19"
bitflags = "1.2"

View File

@@ -15,13 +15,8 @@ impl Dir {
pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
Dir(dir)
}
}
impl WasiDir for Dir {
fn as_any(&self) -> &dyn Any {
self
}
fn open_file(
pub fn open_file_(
&self,
symlink_follow: bool,
path: &str,
@@ -29,7 +24,7 @@ impl WasiDir for Dir {
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error> {
) -> Result<File, Error> {
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
let mut opts = cap_std::fs::OpenOptions::new();
@@ -81,26 +76,67 @@ impl WasiDir for Dir {
if fdflags.contains(wasi_common::file::FdFlags::NONBLOCK) {
f.set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?;
}
Ok(Box::new(File::from_cap_std(f)))
Ok(File::from_cap_std(f))
}
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error> {
pub fn open_dir_(&self, symlink_follow: bool, path: &str) -> Result<Self, Error> {
let d = if symlink_follow {
self.0.open_dir(Path::new(path))?
} else {
self.0.open_dir_nofollow(Path::new(path))?
};
Ok(Box::new(Dir::from_cap_std(d)))
Ok(Dir::from_cap_std(d))
}
fn create_dir(&self, path: &str) -> Result<(), Error> {
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)?;
Ok(())
}
}
#[async_trait::async_trait(?Send)]
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<Box<dyn WasiFile>, Error> {
let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?;
Ok(Box::new(f))
}
async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error> {
let d = self.open_dir_(symlink_follow, path)?;
Ok(Box::new(d))
}
async fn create_dir(&self, path: &str) -> Result<(), Error> {
self.0.create_dir(Path::new(path))?;
Ok(())
}
fn readdir(
async fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>>>, Error> {
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error> {
// 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.
@@ -165,24 +201,24 @@ impl WasiDir for Dir {
Ok(Box::new(rd))
}
fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> {
self.0.symlink(src_path, dest_path)?;
Ok(())
}
fn remove_dir(&self, path: &str) -> Result<(), Error> {
async fn remove_dir(&self, path: &str) -> Result<(), Error> {
self.0.remove_dir(Path::new(path))?;
Ok(())
}
fn unlink_file(&self, path: &str) -> Result<(), Error> {
async fn unlink_file(&self, path: &str) -> Result<(), Error> {
self.0.remove_file_or_symlink(Path::new(path))?;
Ok(())
}
fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
async fn read_link(&self, path: &str) -> Result<PathBuf, Error> {
let link = self.0.read_link(Path::new(path))?;
Ok(link)
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.dir_metadata()?;
Ok(Filestat {
device_id: meta.dev(),
@@ -195,7 +231,11 @@ impl WasiDir for Dir {
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error> {
async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, Error> {
let meta = if follow_symlinks {
self.0.metadata(Path::new(path))?
} else {
@@ -212,16 +252,19 @@ impl WasiDir for Dir {
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> {
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::<Self>()
.ok_or(Error::badf().context("failed downcast to cap-std Dir"))?;
self.0
.rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?;
Ok(())
self.rename_(src_path, dest_dir, dest_path)
}
fn hard_link(
async fn hard_link(
&self,
src_path: &str,
target_dir: &dyn WasiDir,
@@ -231,12 +274,9 @@ impl WasiDir for Dir {
.as_any()
.downcast_ref::<Self>()
.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);
self.0.hard_link(src_path, &target_dir.0, target_path)?;
Ok(())
self.hard_link_(src_path, target_dir, target_path)
}
fn set_times(
async fn set_times(
&self,
path: &str,
atime: Option<wasi_common::SystemTimeSpec>,
@@ -280,7 +320,7 @@ mod test {
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) }
.expect("open ambient temporary dir");
let preopen_dir = Dir::from_cap_std(preopen_dir);
wasi_common::WasiDir::open_dir(&preopen_dir, false, ".")
run(wasi_common::WasiDir::open_dir(&preopen_dir, false, "."))
.expect("open the same directory via WasiDir abstraction");
}
@@ -294,9 +334,8 @@ mod test {
fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
let mut out = HashMap::new();
for readdir_result in dir
.readdir(ReaddirCursor::from(0))
.expect("readdir succeeds")
for readdir_result in
run(dir.readdir(ReaddirCursor::from(0))).expect("readdir succeeds")
{
let entity = readdir_result.expect("readdir entry is valid");
out.insert(entity.name.clone(), entity);
@@ -322,16 +361,15 @@ mod test {
assert!(entities.get(".").is_some());
assert!(entities.get("..").is_some());
preopen_dir
.open_file(
false,
"file1",
OFlags::CREATE,
true,
false,
FdFlags::empty(),
)
.expect("create file1");
run(preopen_dir.open_file(
false,
"file1",
OFlags::CREATE,
true,
false,
FdFlags::empty(),
))
.expect("create file1");
let entities = readdir_into_map(&preopen_dir);
assert_eq!(entities.len(), 3, "should be ., .., file1 {:?}", entities);
@@ -348,4 +386,41 @@ mod test {
FileType::RegularFile
);
}
fn run<F: std::future::Future>(future: F) -> F::Output {
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
let mut f = Pin::from(Box::new(future));
let waker = dummy_waker();
let mut cx = Context::from_waker(&waker);
match f.as_mut().poll(&mut cx) {
Poll::Ready(val) => return val,
Poll::Pending => {
panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store")
}
}
fn dummy_waker() -> Waker {
return unsafe { Waker::from_raw(clone(5 as *const _)) };
unsafe fn clone(ptr: *const ()) -> RawWaker {
assert_eq!(ptr as usize, 5);
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
RawWaker::new(ptr, &VTABLE)
}
unsafe fn wake(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
unsafe fn wake_by_ref(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
unsafe fn drop(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
}
}
}

View File

@@ -20,27 +20,28 @@ impl File {
}
}
#[async_trait::async_trait(?Send)]
impl WasiFile for File {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
async fn datasync(&self) -> Result<(), Error> {
self.0.sync_data()?;
Ok(())
}
fn sync(&self) -> Result<(), Error> {
async fn sync(&self) -> Result<(), Error> {
self.0.sync_all()?;
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
async fn get_filetype(&self) -> Result<FileType, Error> {
let meta = self.0.metadata()?;
Ok(filetype_from(&meta.file_type()))
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
let fdflags = self.0.get_fd_flags()?;
Ok(from_sysif_fdflags(fdflags))
}
fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
if fdflags.intersects(
wasi_common::file::FdFlags::DSYNC
| wasi_common::file::FdFlags::SYNC
@@ -50,7 +51,7 @@ impl WasiFile for File {
}
Ok(self.0.set_fd_flags(to_sysif_fdflags(fdflags))?)
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.metadata()?;
Ok(Filestat {
device_id: meta.dev(),
@@ -63,19 +64,19 @@ impl WasiFile for File {
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
async fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
self.0.set_len(size)?;
Ok(())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
self.0.advise(offset, len, convert_advice(advice))?;
Ok(())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
self.0.allocate(offset, len)?;
Ok(())
}
fn set_times(
async fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
@@ -84,32 +85,46 @@ impl WasiFile for File {
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
let n = self.0.read_vectored(bufs)?;
Ok(n.try_into()?)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
async fn read_vectored_at<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
offset: u64,
) -> Result<u64, Error> {
let n = self.0.read_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
let n = self.0.write_vectored(bufs)?;
Ok(n.try_into()?)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
async fn write_vectored_at<'a>(
&self,
bufs: &[io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error> {
let n = self.0.write_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Ok(self.0.seek(pos)?)
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
let n = self.0.peek(buf)?;
Ok(n.try_into()?)
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
async fn readable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
}
pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {

View File

@@ -1,15 +1,40 @@
#[cfg(unix)]
mod unix;
pub mod unix;
#[cfg(unix)]
pub use unix::*;
pub use unix::poll_oneoff;
#[cfg(windows)]
mod windows;
pub mod windows;
#[cfg(windows)]
pub use windows::*;
pub use windows::poll_oneoff;
use wasi_common::sched::WasiSched;
use std::thread;
use std::time::Duration;
use wasi_common::{
sched::{Poll, WasiSched},
Error,
};
pub struct SyncSched {}
impl SyncSched {
pub fn new() -> Self {
Self {}
}
}
#[async_trait::async_trait(?Send)]
impl WasiSched for SyncSched {
async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> {
poll_oneoff(poll).await
}
async fn sched_yield(&self) -> Result<(), Error> {
thread::yield_now();
Ok(())
}
async fn sleep(&self, duration: Duration) -> Result<(), Error> {
std::thread::sleep(duration);
Ok(())
}
}
pub fn sched_ctx() -> Box<dyn WasiSched> {
Box::new(SyncSched::new())
}

View File

@@ -1,117 +1,97 @@
use cap_std::time::Duration;
use std::convert::TryInto;
use std::ops::Deref;
use std::os::unix::io::{AsRawFd, RawFd};
use wasi_common::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
Poll,
},
Error, ErrorExt,
};
use poll::{PollFd, PollFlags};
pub struct SyncSched;
impl SyncSched {
pub fn new() -> Self {
SyncSched
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 raw_fd = wasi_file_raw_fd(f.file).ok_or(
Error::invalid_argument().context("read subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) });
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
Subscription::Write(f) => {
let raw_fd = wasi_file_raw_fd(f.file).ok_or(
Error::invalid_argument().context("write subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) });
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
let mut pollfds = Vec::new();
let timeout = poll.earliest_clock_deadline();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(f) => {
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
Error::invalid_argument().context("read subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) });
}
}
Subscription::Write(f) => {
let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or(
Error::invalid_argument().context("write subscription fd downcast failed"),
)?;
pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) });
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
let ready = loop {
let poll_timeout = if let Some(t) = timeout {
let duration = t.duration_until().unwrap_or(Duration::from_secs(0));
(duration.as_millis() + 1) // XXX try always rounding up?
.try_into()
.map_err(|_| Error::overflow().context("poll timeout"))?
} else {
libc::c_int::max_value()
};
tracing::debug!(
poll_timeout = tracing::field::debug(poll_timeout),
poll_fds = tracing::field::debug(&pollfds),
"poll"
);
match poll::poll(&mut pollfds, poll_timeout) {
Ok(ready) => break ready,
Err(_) => {
let last_err = std::io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
} else {
return Err(last_err.into());
}
}
}
};
if ready > 0 {
for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) {
if let Some(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::POLLNVAL) {
rwsub.error(Error::badf());
} else if revents.contains(PollFlags::POLLERR) {
rwsub.error(Error::io());
} else if revents.contains(PollFlags::POLLHUP) {
rwsub.complete(nbytes, RwEventFlags::HANGUP);
} else {
rwsub.complete(nbytes, RwEventFlags::empty());
};
}
}
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));
(duration.as_millis() + 1) // XXX try always rounding up?
.try_into()
.map_err(|_| Error::overflow().context("poll timeout"))?
} else {
timeout
.expect("timed out")
.result()
.expect("timer deadline is past")
.unwrap()
libc::c_int::max_value()
};
tracing::debug!(
poll_timeout = tracing::field::debug(poll_timeout),
poll_fds = tracing::field::debug(&pollfds),
"poll"
);
match poll::poll(&mut pollfds, poll_timeout) {
Ok(ready) => break ready,
Err(_) => {
let last_err = std::io::Error::last_os_error();
if last_err.raw_os_error().unwrap() == libc::EINTR {
continue;
} else {
return Err(last_err.into());
}
}
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
std::thread::yield_now();
Ok(())
}
fn sleep(&self, duration: Duration) -> Result<(), Error> {
std::thread::sleep(duration);
Ok(())
};
if ready > 0 {
for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) {
if let Some(revents) = pollfd.revents() {
let (nbytes, rwsub) = match rwsub {
Subscription::Read(sub) => {
let ready = sub.file.num_ready_bytes().await?;
(std::cmp::max(ready, 1), sub)
}
Subscription::Write(sub) => (0, sub),
_ => unreachable!(),
};
if revents.contains(PollFlags::POLLNVAL) {
rwsub.error(Error::badf());
} else if revents.contains(PollFlags::POLLERR) {
rwsub.error(Error::io());
} else if revents.contains(PollFlags::POLLHUP) {
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(())
}
fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option<RawFd> {

View File

@@ -1,3 +1,13 @@
// 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 anyhow::Context;
use std::ops::Deref;
use std::os::windows::io::{AsRawHandle, RawHandle};
@@ -9,132 +19,127 @@ use wasi_common::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
Poll,
},
Error, ErrorExt,
};
pub struct SyncSched {}
impl SyncSched {
pub fn new() -> Self {
Self {}
}
pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle).await
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
// For reuse by wasi-tokio, which has a different WasiFile -> RawHandle translator.
pub async fn poll_oneoff_<'a>(
poll: &mut Poll<'a>,
file_is_stdin: impl Fn(&dyn WasiFile) -> bool,
file_to_handle: impl Fn(&dyn WasiFile) -> Option<RawHandle>,
) -> 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 ready = false;
let timeout = poll.earliest_clock_deadline();
let mut stdin_read_subs = Vec::new();
let mut immediate_subs = Vec::new();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(r) if r.file.as_any().is::<crate::stdio::Stdin>() => {
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.deref()) {
stdin_read_subs.push(r);
} else if file_to_handle(r.file.deref()).is_some() {
immediate_reads.push(r);
} else {
return Err(
Error::invalid_argument().context("read subscription fd downcast failed")
);
}
Subscription::Read(rw) | Subscription::Write(rw) => {
if wasi_file_raw_handle(rw.file.deref()).is_some() {
immediate_subs.push(s);
} else {
return Err(Error::invalid_argument()
.context("read/write subscription fd downcast failed"));
}
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
Subscription::Write(w) => {
if file_to_handle(w.file.deref()).is_some() {
immediate_writes.push(w);
} else {
return Err(
Error::invalid_argument().context("write subscription fd downcast failed")
);
}
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !stdin_read_subs.is_empty() {
let waitmode = if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
WaitMode::Timeout(duration)
} else {
WaitMode::Immediate
}
} else {
if ready {
WaitMode::Immediate
} else {
WaitMode::Infinite
}
};
let state = STDIN_POLL
.lock()
.map_err(|_| Error::trap("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 anyhow::Error.
// 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 sub in immediate_subs {
match sub {
Subscription::Read(r) => {
// XXX This doesnt strictly preserve the behavior in the earlier
// implementation, which would always do complete(0) for reads from
// stdout/err.
match r.file.num_ready_bytes() {
Ok(ready_bytes) => {
r.complete(ready_bytes, RwEventFlags::empty());
ready = true;
}
Err(e) => {
r.error(e);
ready = true;
}
}
}
Subscription::Write(w) => {
// Everything is always ready for writing, apparently?
w.complete(0, RwEventFlags::empty());
if !stdin_read_subs.is_empty() {
let state = STDIN_POLL
.lock()
.map_err(|_| Error::trap("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;
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !ready {
if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
thread::sleep(duration);
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 anyhow::Error.
// 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().await {
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;
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
thread::yield_now();
Ok(())
}
fn sleep(&self, duration: Duration) -> Result<(), Error> {
std::thread::sleep(duration);
Ok(())
if !ready {
if let WaitMode::Timeout(duration) = waitmode {
thread::sleep(duration);
}
}
Ok(())
}
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
f.as_any().is::<crate::stdio::Stdin>()
}
pub fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(
@@ -172,6 +177,7 @@ enum PollState {
Error(std::io::Error),
}
#[derive(Copy, Clone)]
enum WaitMode {
Timeout(Duration),
Infinite,

View File

@@ -22,31 +22,32 @@ pub fn stdin() -> Stdin {
Stdin(std::io::stdin())
}
#[async_trait::async_trait(?Send)]
impl WasiFile for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
async fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
async fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.as_file_view().metadata()?;
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
filetype: self.get_filetype().await?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
@@ -54,35 +55,43 @@ impl WasiFile for Stdin {
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
let n = self.0.as_file_view().read_vectored(bufs)?;
Ok(n.try_into().map_err(|_| Error::range())?)
}
fn read_vectored_at(&self, _bufs: &mut [io::IoSliceMut], _offset: u64) -> Result<u64, Error> {
async fn read_vectored_at<'a>(
&self,
_bufs: &mut [io::IoSliceMut<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn write_vectored(&self, _bufs: &[io::IoSlice]) -> Result<u64, Error> {
async fn write_vectored<'a>(&self, _bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
async fn write_vectored_at<'a>(
&self,
_bufs: &[io::IoSlice<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
async fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
async fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn set_times(
async fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
@@ -91,9 +100,15 @@ impl WasiFile for Stdin {
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
async fn readable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
}
#[cfg(windows)]
impl AsRawHandle for Stdin {
@@ -110,31 +125,32 @@ impl AsRawFd for Stdin {
macro_rules! wasi_file_write_impl {
($ty:ty) => {
#[async_trait::async_trait(?Send)]
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
async fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
async fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.as_file_view().metadata()?;
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
filetype: self.get_filetype().await?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
@@ -142,39 +158,46 @@ macro_rules! wasi_file_write_impl {
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, _bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
Err(Error::badf())
}
fn read_vectored_at(
async fn read_vectored<'a>(
&self,
_bufs: &mut [io::IoSliceMut],
_bufs: &mut [io::IoSliceMut<'a>],
) -> Result<u64, Error> {
Err(Error::badf())
}
async fn read_vectored_at<'a>(
&self,
_bufs: &mut [io::IoSliceMut<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
let n = self.0.as_file_view().write_vectored(bufs)?;
Ok(n.try_into().map_err(|c| Error::range().context(c))?)
}
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
async fn write_vectored_at<'a>(
&self,
_bufs: &[io::IoSlice<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
async fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
async fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
async fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
@@ -183,9 +206,15 @@ macro_rules! wasi_file_write_impl {
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
async fn readable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
}
#[cfg(windows)]
impl AsRawHandle for $ty {

View File

@@ -5,12 +5,12 @@ pub enum SystemTimeSpec {
Absolute(SystemTime),
}
pub trait WasiSystemClock {
pub trait WasiSystemClock: Send + Sync {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> SystemTime;
}
pub trait WasiMonotonicClock {
pub trait WasiMonotonicClock: Send + Sync {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> Instant;
}

View File

@@ -6,9 +6,10 @@ use std::cell::Ref;
use std::ops::Deref;
use std::path::PathBuf;
pub trait WasiDir {
#[wiggle::async_trait]
pub trait WasiDir: Send + Sync {
fn as_any(&self) -> &dyn Any;
fn open_file(
async fn open_file(
&self,
symlink_follow: bool,
path: &str,
@@ -17,26 +18,33 @@ pub trait WasiDir {
write: bool,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error>;
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error>;
fn create_dir(&self, path: &str) -> Result<(), Error>;
fn readdir(
async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error>;
async fn create_dir(&self, path: &str) -> Result<(), Error>;
// XXX the iterator here needs to be asyncified as well!
async fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>>>, Error>;
fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>;
fn remove_dir(&self, path: &str) -> Result<(), Error>;
fn unlink_file(&self, path: &str) -> Result<(), Error>;
fn read_link(&self, path: &str) -> Result<PathBuf, Error>;
fn get_filestat(&self) -> Result<Filestat, Error>;
fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result<Filestat, Error>;
fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>;
fn hard_link(
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error>;
async fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>;
async fn remove_dir(&self, path: &str) -> Result<(), Error>;
async fn unlink_file(&self, path: &str) -> Result<(), Error>;
async fn read_link(&self, path: &str) -> Result<PathBuf, Error>;
async fn get_filestat(&self) -> Result<Filestat, Error>;
async fn get_path_filestat(&self, path: &str, follow_symlinks: bool)
-> Result<Filestat, Error>;
async fn rename(
&self,
path: &str,
dest_dir: &dyn WasiDir,
dest_path: &str,
) -> Result<(), Error>;
async fn hard_link(
&self,
path: &str,
target_dir: &dyn WasiDir,
target_path: &str,
) -> Result<(), Error>;
fn set_times(
async fn set_times(
&self,
path: &str,
atime: Option<SystemTimeSpec>,

View File

@@ -23,7 +23,7 @@
//! The real value of using `anyhow::Error` here is being able to use
//! `anyhow::Result::context` to aid in debugging of errors.
pub use anyhow::Error;
pub use anyhow::{Context, Error};
/// Internal error type for the `wasi-common` crate.
/// Contains variants of the WASI `$errno` type are added according to what is actually used internally by

View File

@@ -4,30 +4,41 @@ use std::any::Any;
use std::cell::{Ref, RefMut};
use std::ops::{Deref, DerefMut};
pub trait WasiFile {
#[wiggle::async_trait]
pub trait WasiFile: Send {
fn as_any(&self) -> &dyn Any;
fn datasync(&self) -> Result<(), Error>; // write op
fn sync(&self) -> Result<(), Error>; // file op
fn get_filetype(&self) -> Result<FileType, Error>; // file op
fn get_fdflags(&self) -> Result<FdFlags, Error>; // file op
fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op
fn get_filestat(&self) -> Result<Filestat, Error>; // split out get_length as a read & write op, rest is a file op
fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error>; // file op
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
fn set_times(
async fn datasync(&self) -> Result<(), Error>; // write op
async fn sync(&self) -> Result<(), Error>; // file op
async fn get_filetype(&self) -> Result<FileType, Error>; // file op
async fn get_fdflags(&self) -> Result<FdFlags, Error>; // file op
async fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op
async fn get_filestat(&self) -> Result<Filestat, Error>; // split out get_length as a read & write op, rest is a file op
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error>; // file op
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
async fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error>;
fn read_vectored(&self, bufs: &mut [std::io::IoSliceMut]) -> Result<u64, Error>; // read op
fn read_vectored_at(&self, bufs: &mut [std::io::IoSliceMut], offset: u64)
-> Result<u64, Error>; // file op
fn write_vectored(&self, bufs: &[std::io::IoSlice]) -> Result<u64, Error>; // write op
fn write_vectored_at(&self, bufs: &[std::io::IoSlice], offset: u64) -> Result<u64, Error>; // file op
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
async fn read_vectored<'a>(&self, bufs: &mut [std::io::IoSliceMut<'a>]) -> Result<u64, Error>; // read op
async fn read_vectored_at<'a>(
&self,
bufs: &mut [std::io::IoSliceMut<'a>],
offset: u64,
) -> Result<u64, Error>; // file op
async fn write_vectored<'a>(&self, bufs: &[std::io::IoSlice<'a>]) -> Result<u64, Error>; // write op
async fn write_vectored_at<'a>(
&self,
bufs: &[std::io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error>; // file op
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error>; // file op that generates a new stream from a file will supercede this
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error>; // read op
async fn num_ready_bytes(&self) -> Result<u64, Error>; // read op
async fn readable(&mut self) -> Result<(), Error>;
async fn writable(&mut self) -> Result<(), Error>;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -111,11 +122,11 @@ impl FileEntry {
Ok(())
}
pub fn get_fdstat(&self) -> Result<FdStat, Error> {
pub async fn get_fdstat(&self) -> Result<FdStat, Error> {
Ok(FdStat {
filetype: self.file.get_filetype()?,
filetype: self.file.get_filetype().await?,
caps: self.caps,
flags: self.file.get_fdflags()?,
flags: self.file.get_fdflags().await?,
})
}
}

View File

@@ -66,7 +66,7 @@ pub use cap_rand::RngCore;
pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock};
pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use dir::WasiDir;
pub use error::{Error, ErrorExt, ErrorKind};
pub use error::{Context, Error, ErrorExt, ErrorKind};
pub use file::WasiFile;
pub use sched::{Poll, WasiSched};
pub use string_array::StringArrayError;

View File

@@ -105,30 +105,31 @@ impl From<&str> for ReadPipe<io::Cursor<String>> {
}
}
impl<R: Read + Any> WasiFile for ReadPipe<R> {
#[wiggle::async_trait]
impl<R: Read + Any + Send + Sync> WasiFile for ReadPipe<R> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
async fn datasync(&self) -> Result<(), Error> {
Ok(()) // trivial: no implementation needed
}
fn sync(&self) -> Result<(), Error> {
async fn sync(&self) -> Result<(), Error> {
Ok(()) // trivial
}
fn get_filetype(&self) -> Result<FileType, Error> {
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
filetype: self.get_filetype().await?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Read :(
atim: None,
@@ -136,44 +137,58 @@ impl<R: Read + Any> WasiFile for ReadPipe<R> {
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
let n = self.borrow().read_vectored(bufs)?;
Ok(n.try_into()?)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
async fn read_vectored_at<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
async fn write_vectored_at<'a>(
&self,
bufs: &[io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
async fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
async fn readable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
}
/// A virtual pipe write end.
@@ -249,30 +264,31 @@ impl WritePipe<io::Cursor<Vec<u8>>> {
}
}
impl<W: Write + Any> WasiFile for WritePipe<W> {
#[wiggle::async_trait]
impl<W: Write + Any + Send + Sync> WasiFile for WritePipe<W> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
async fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
async fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
async fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
filetype: self.get_filetype().await?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Write :(
atim: None,
@@ -280,42 +296,56 @@ impl<W: Write + Any> WasiFile for WritePipe<W> {
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result<u64, Error> {
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
Err(Error::badf())
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
async fn read_vectored_at<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
let n = self.borrow().write_vectored(bufs)?;
Ok(n.try_into()?)
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
async fn write_vectored_at<'a>(
&self,
bufs: &[io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
async fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
async fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
async fn readable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&mut self) -> Result<(), Error> {
Err(Error::badf())
}
}

View File

@@ -1,18 +1,22 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use cap_std::time::{Duration, Instant};
use std::cell::Ref;
use cap_std::time::Instant;
pub mod subscription;
pub use cap_std::time::Duration;
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
pub use subscription::{
MonotonicClockSubscription, RwEventFlags, RwSubscription, Subscription, SubscriptionResult,
};
#[wiggle::async_trait]
pub trait WasiSched {
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
fn sched_yield(&self) -> Result<(), Error>;
fn sleep(&self, duration: Duration) -> Result<(), Error>;
async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error>;
async fn sched_yield(&self) -> Result<(), Error>;
async fn sleep(&self, duration: Duration) -> Result<(), Error>;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Userdata(u64);
impl From<u64> for Userdata {
fn from(u: u64) -> Userdata {
@@ -26,6 +30,8 @@ impl From<Userdata> for u64 {
}
}
pub type PollResults = Vec<(SubscriptionResult, Userdata)>;
pub struct Poll<'a> {
subs: Vec<(Subscription<'a>, Userdata)>,
}
@@ -50,11 +56,11 @@ impl<'a> Poll<'a> {
ud,
));
}
pub fn subscribe_read(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
pub fn subscribe_read(&mut self, file: &'a mut dyn WasiFile, ud: Userdata) {
self.subs
.push((Subscription::Read(RwSubscription::new(file)), ud));
}
pub fn subscribe_write(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) {
pub fn subscribe_write(&mut self, file: &'a mut dyn WasiFile, ud: Userdata) {
self.subs
.push((Subscription::Write(RwSubscription::new(file)), ud));
}
@@ -67,7 +73,7 @@ impl<'a> Poll<'a> {
pub fn is_empty(&self) -> bool {
self.subs.is_empty()
}
pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> {
pub fn earliest_clock_deadline(&self) -> Option<&MonotonicClockSubscription<'a>> {
self.subs
.iter()
.filter_map(|(s, _ud)| match s {
@@ -76,8 +82,8 @@ impl<'a> Poll<'a> {
})
.min_by(|a, b| a.deadline.cmp(&b.deadline))
}
pub fn rw_subscriptions(&'a self) -> impl Iterator<Item = &Subscription<'a>> {
self.subs.iter().filter_map(|(s, _ud)| match s {
pub fn rw_subscriptions<'b>(&'b mut self) -> impl Iterator<Item = &'b mut Subscription<'a>> {
self.subs.iter_mut().filter_map(|(s, _ud)| match s {
Subscription::Read { .. } | Subscription::Write { .. } => Some(s),
_ => None,
})

View File

@@ -3,7 +3,7 @@ use crate::file::WasiFile;
use crate::Error;
use bitflags::bitflags;
use cap_std::time::{Duration, Instant};
use std::cell::{Cell, Ref};
use std::cell::Cell;
bitflags! {
pub struct RwEventFlags: u32 {
@@ -12,12 +12,12 @@ bitflags! {
}
pub struct RwSubscription<'a> {
pub file: Ref<'a, dyn WasiFile>,
pub file: &'a mut dyn WasiFile,
status: Cell<Option<Result<(u64, RwEventFlags), Error>>>,
}
impl<'a> RwSubscription<'a> {
pub fn new(file: Ref<'a, dyn WasiFile>) -> Self {
pub fn new(file: &'a mut dyn WasiFile) -> Self {
Self {
file,
status: Cell::new(None),
@@ -29,8 +29,8 @@ impl<'a> RwSubscription<'a> {
pub fn error(&self, error: Error) {
self.status.set(Some(Err(error)))
}
pub fn result(self) -> Option<Result<(u64, RwEventFlags), Error>> {
self.status.into_inner()
pub fn result(&self) -> Option<Result<(u64, RwEventFlags), Error>> {
self.status.take()
}
}
@@ -62,6 +62,7 @@ pub enum Subscription<'a> {
MonotonicClock(MonotonicClockSubscription<'a>),
}
#[derive(Debug)]
pub enum SubscriptionResult {
Read(Result<(u64, RwEventFlags), Error>),
Write(Result<(u64, RwEventFlags), Error>),

View File

@@ -1,12 +1,14 @@
use crate::file::{FileCaps, FileEntryExt, TableFileExt};
use crate::file::{FileCaps, FileEntryExt, FileEntryMutExt, TableFileExt, WasiFile};
use crate::sched::{
subscription::{RwEventFlags, SubscriptionResult},
Poll,
Poll, Userdata,
};
use crate::snapshots::preview_1::types as snapshot1_types;
use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1;
use crate::{Error, ErrorExt, WasiCtx};
use cap_std::time::Duration;
use std::cell::RefMut;
use std::collections::HashSet;
use std::convert::{TryFrom, TryInto};
use std::io::{IoSlice, IoSliceMut};
use std::ops::Deref;
@@ -16,6 +18,7 @@ use wiggle::GuestPtr;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"],
errors: { errno => Error },
async: *,
});
impl wiggle::GuestErrorType for types::Errno {
@@ -332,79 +335,80 @@ convert_flags_bidirectional!(
// This implementation, wherever possible, delegates directly to the Snapshot1 implementation,
// performing the no-op type conversions along the way.
impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
fn args_get<'b>(
#[wiggle::async_trait]
impl wasi_unstable::WasiUnstable for WasiCtx {
async fn args_get<'a>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
argv: &GuestPtr<'a, GuestPtr<'a, u8>>,
argv_buf: &GuestPtr<'a, u8>,
) -> Result<(), Error> {
Snapshot1::args_get(self, argv, argv_buf)
Snapshot1::args_get(self, argv, argv_buf).await
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::args_sizes_get(self)
async fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::args_sizes_get(self).await
}
fn environ_get<'b>(
async fn environ_get<'a>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
environ: &GuestPtr<'a, GuestPtr<'a, u8>>,
environ_buf: &GuestPtr<'a, u8>,
) -> Result<(), Error> {
Snapshot1::environ_get(self, environ, environ_buf)
Snapshot1::environ_get(self, environ, environ_buf).await
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::environ_sizes_get(self)
async fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Snapshot1::environ_sizes_get(self).await
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp, Error> {
Snapshot1::clock_res_get(self, id.into())
async fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp, Error> {
Snapshot1::clock_res_get(self, id.into()).await
}
fn clock_time_get(
async fn clock_time_get(
&self,
id: types::Clockid,
precision: types::Timestamp,
) -> Result<types::Timestamp, Error> {
Snapshot1::clock_time_get(self, id.into(), precision)
Snapshot1::clock_time_get(self, id.into(), precision).await
}
fn fd_advise(
async fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
advice: types::Advice,
) -> Result<(), Error> {
Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into())
Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into()).await
}
fn fd_allocate(
async fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
len: types::Filesize,
) -> Result<(), Error> {
Snapshot1::fd_allocate(self, fd.into(), offset, len)
Snapshot1::fd_allocate(self, fd.into(), offset, len).await
}
fn fd_close(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_close(self, fd.into())
async fn fd_close(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_close(self, fd.into()).await
}
fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_datasync(self, fd.into())
async fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_datasync(self, fd.into()).await
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat, Error> {
Ok(Snapshot1::fd_fdstat_get(self, fd.into())?.into())
async fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat, Error> {
Ok(Snapshot1::fd_fdstat_get(self, fd.into()).await?.into())
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into())
async fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into()).await
}
fn fd_fdstat_set_rights(
async fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
@@ -416,24 +420,29 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
fs_rights_base.into(),
fs_rights_inheriting.into(),
)
.await
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat, Error> {
Ok(Snapshot1::fd_filestat_get(self, fd.into())?.into())
async fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat, Error> {
Ok(Snapshot1::fd_filestat_get(self, fd.into()).await?.into())
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<(), Error> {
Snapshot1::fd_filestat_set_size(self, fd.into(), size)
async fn fd_filestat_set_size(
&self,
fd: types::Fd,
size: types::Filesize,
) -> Result<(), Error> {
Snapshot1::fd_filestat_set_size(self, fd.into(), size).await
}
fn fd_filestat_set_times(
async fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
) -> Result<(), Error> {
Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into())
Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into()).await
}
// NOTE on fd_read, fd_pread, fd_write, fd_pwrite implementations:
@@ -444,7 +453,11 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
// The bodies of these functions is mostly about converting the GuestPtr and types::-based
// representation to a std::io::IoSlice(Mut) representation.
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size, Error> {
async fn fd_read<'a>(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'a>,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::READ)?;
@@ -462,14 +475,14 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored(&mut ioslices)?;
let bytes_read = f.read_vectored(&mut ioslices).await?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_pread(
async fn fd_pread<'a>(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
iovs: &types::IovecArray<'a>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
@@ -491,14 +504,14 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored_at(&mut ioslices, offset)?;
let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_write(
async fn fd_write<'a>(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
ciovs: &types::CiovecArray<'a>,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::WRITE)?;
@@ -516,15 +529,15 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored(&ioslices)?;
let bytes_written = f.write_vectored(&ioslices).await?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_pwrite(
async fn fd_pwrite<'a>(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
ciovs: &types::CiovecArray<'a>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
@@ -545,77 +558,81 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored_at(&ioslices, offset)?;
let bytes_written = f.write_vectored_at(&ioslices, offset).await?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat, Error> {
Ok(Snapshot1::fd_prestat_get(self, fd.into())?.into())
async fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat, Error> {
Ok(Snapshot1::fd_prestat_get(self, fd.into()).await?.into())
}
fn fd_prestat_dir_name(
async fn fd_prestat_dir_name<'a>(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path: &GuestPtr<'a, u8>,
path_max_len: types::Size,
) -> Result<(), Error> {
Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len)
Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len).await
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
Snapshot1::fd_renumber(self, from.into(), to.into())
async fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
Snapshot1::fd_renumber(self, from.into(), to.into()).await
}
fn fd_seek(
async fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
whence: types::Whence,
) -> Result<types::Filesize, Error> {
Snapshot1::fd_seek(self, fd.into(), offset, whence.into())
Snapshot1::fd_seek(self, fd.into(), offset, whence.into()).await
}
fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_sync(self, fd.into())
async fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> {
Snapshot1::fd_sync(self, fd.into()).await
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize, Error> {
Snapshot1::fd_tell(self, fd.into())
async fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize, Error> {
Snapshot1::fd_tell(self, fd.into()).await
}
fn fd_readdir(
async fn fd_readdir<'a>(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size, Error> {
Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie)
Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie).await
}
fn path_create_directory(
async fn path_create_directory<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_create_directory(self, dirfd.into(), path)
Snapshot1::path_create_directory(self, dirfd.into(), path).await
}
fn path_filestat_get(
async fn path_filestat_get<'a>(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<types::Filestat, Error> {
Ok(Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path)?.into())
Ok(
Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path)
.await?
.into(),
)
}
fn path_filestat_set_times(
async fn path_filestat_set_times<'a>(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
@@ -629,15 +646,16 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
mtim,
fst_flags.into(),
)
.await
}
fn path_link(
async fn path_link<'a>(
&self,
src_fd: types::Fd,
src_flags: types::Lookupflags,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
target_fd: types::Fd,
target_path: &GuestPtr<'_, str>,
target_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_link(
self,
@@ -647,13 +665,14 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
target_fd.into(),
target_path,
)
.await
}
fn path_open(
async fn path_open<'a>(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
@@ -668,49 +687,54 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
fs_rights_base.into(),
fs_rights_inheriting.into(),
fdflags.into(),
)?
)
.await?
.into())
}
fn path_readlink(
async fn path_readlink<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
path: &GuestPtr<'a, str>,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
) -> Result<types::Size, Error> {
Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len)
Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len).await
}
fn path_remove_directory(
async fn path_remove_directory<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_remove_directory(self, dirfd.into(), path)
Snapshot1::path_remove_directory(self, dirfd.into(), path).await
}
fn path_rename(
async fn path_rename<'a>(
&self,
src_fd: types::Fd,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
dest_fd: types::Fd,
dest_path: &GuestPtr<'_, str>,
dest_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path)
Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path).await
}
fn path_symlink(
async fn path_symlink<'a>(
&self,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
dirfd: types::Fd,
dest_path: &GuestPtr<'_, str>,
dest_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path)
Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path).await
}
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<(), Error> {
Snapshot1::path_unlink_file(self, dirfd.into(), path)
async fn path_unlink_file<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
Snapshot1::path_unlink_file(self, dirfd.into(), path).await
}
// NOTE on poll_oneoff implementation:
@@ -720,10 +744,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
// The implementations are identical, but the `types::` in scope locally is different.
// The bodies of these functions is mostly about converting the GuestPtr and types::-based
// representation to use the Poll abstraction.
fn poll_oneoff(
async fn poll_oneoff<'a>(
&self,
subs: &GuestPtr<types::Subscription>,
events: &GuestPtr<types::Event>,
subs: &GuestPtr<'a, types::Subscription>,
events: &GuestPtr<'a, types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size, Error> {
if nsubscriptions == 0 {
@@ -741,7 +765,9 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
self.sched.sleep(Duration::from_nanos(clocksub.timeout))?;
self.sched
.sleep(Duration::from_nanos(clocksub.timeout))
.await?;
events.write(types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
@@ -754,6 +780,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
}
let table = self.table();
let mut sub_fds: HashSet<types::Fd> = HashSet::new();
// We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside
let mut read_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new();
let mut write_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new();
let mut poll = Poll::new();
let subs = subs.as_array(nsubscriptions);
@@ -792,22 +822,34 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
},
types::SubscriptionU::FdRead(readsub) => {
let fd = readsub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
if sub_fds.contains(&fd) {
return Err(Error::invalid_argument()
.context("Fd can be subscribed to at most once per poll"));
} else {
sub_fds.insert(fd);
}
let file_ref = table
.get_file_mut(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_read(file, sub.userdata.into());
read_refs.push((file_ref, sub.userdata.into()));
}
types::SubscriptionU::FdWrite(writesub) => {
let fd = writesub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
if sub_fds.contains(&fd) {
return Err(Error::invalid_argument()
.context("Fd can be subscribed to at most once per poll"));
} else {
sub_fds.insert(fd);
}
let file_ref = table
.get_file_mut(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_write(file, sub.userdata.into());
write_refs.push((file_ref, sub.userdata.into()));
}
}
}
self.sched.poll_oneoff(&poll)?;
self.sched.poll_oneoff(&mut poll).await?;
let results = poll.results();
let num_results = results.len();
@@ -882,41 +924,45 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx {
Ok(num_results.try_into().expect("results fit into memory"))
}
fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
Snapshot1::proc_exit(self, status)
async fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
Snapshot1::proc_exit(self, status).await
}
fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> {
async fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> {
Err(Error::trap("proc_raise unsupported"))
}
fn sched_yield(&self) -> Result<(), Error> {
Snapshot1::sched_yield(self)
async fn sched_yield(&self) -> Result<(), Error> {
Snapshot1::sched_yield(self).await
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<(), Error> {
Snapshot1::random_get(self, buf, buf_len)
async fn random_get<'a>(
&self,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
) -> Result<(), Error> {
Snapshot1::random_get(self, buf, buf_len).await
}
fn sock_recv(
async fn sock_recv<'a>(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_data: &types::IovecArray<'a>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags), Error> {
Err(Error::trap("sock_recv unsupported"))
}
fn sock_send(
async fn sock_send<'a>(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_data: &types::CiovecArray<'a>,
_si_flags: types::Siflags,
) -> Result<types::Size, Error> {
Err(Error::trap("sock_send unsupported"))
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
async fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
Err(Error::trap("sock_shutdown unsupported"))
}
}

View File

@@ -2,17 +2,18 @@ use crate::{
dir::{DirCaps, DirEntry, DirEntryExt, DirFdStat, ReaddirCursor, ReaddirEntity, TableDirExt},
file::{
Advice, FdFlags, FdStat, FileCaps, FileEntry, FileEntryExt, FileEntryMutExt, FileType,
Filestat, OFlags, TableFileExt,
Filestat, OFlags, TableFileExt, WasiFile,
},
sched::{
subscription::{RwEventFlags, SubscriptionResult},
Poll,
Poll, Userdata,
},
Error, ErrorExt, ErrorKind, SystemTimeSpec, WasiCtx,
};
use anyhow::Context;
use cap_std::time::{Duration, SystemClock};
use std::cell::{Ref, RefMut};
use std::collections::HashSet;
use std::convert::{TryFrom, TryInto};
use std::io::{IoSlice, IoSliceMut};
use std::ops::{Deref, DerefMut};
@@ -22,6 +23,10 @@ use wiggle::GuestPtr;
wiggle::from_witx!({
witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
errors: { errno => Error },
// Note: not every function actually needs to be async, however, nearly all of them do, and
// keeping that set the same in this macro and the wasmtime_wiggle / lucet_wiggle macros is
// tedious, and there is no cost to having a sync function be async in this case.
async: *
});
impl wiggle::GuestErrorType for types::Errno {
@@ -188,8 +193,9 @@ impl TryFrom<std::io::Error> for types::Errno {
}
}
impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
fn args_get<'b>(
#[wiggle::async_trait]
impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
async fn args_get<'b>(
&self,
argv: &GuestPtr<'b, GuestPtr<'b, u8>>,
argv_buf: &GuestPtr<'b, u8>,
@@ -197,11 +203,11 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
self.args.write_to_guest(argv_buf, argv)
}
fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
async fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Ok((self.args.number_elements(), self.args.cumulative_size()))
}
fn environ_get<'b>(
async fn environ_get<'b>(
&self,
environ: &GuestPtr<'b, GuestPtr<'b, u8>>,
environ_buf: &GuestPtr<'b, u8>,
@@ -209,11 +215,11 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
self.env.write_to_guest(environ_buf, environ)
}
fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
async fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> {
Ok((self.env.number_elements(), self.env.cumulative_size()))
}
fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp, Error> {
async fn clock_res_get(&self, id: types::Clockid) -> Result<types::Timestamp, Error> {
let resolution = match id {
types::Clockid::Realtime => Ok(self.clocks.system.resolution()),
types::Clockid::Monotonic => Ok(self.clocks.monotonic.resolution()),
@@ -224,7 +230,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(resolution.as_nanos().try_into()?)
}
fn clock_time_get(
async fn clock_time_get(
&self,
id: types::Clockid,
precision: types::Timestamp,
@@ -249,7 +255,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
}
fn fd_advise(
async fn fd_advise(
&self,
fd: types::Fd,
offset: types::Filesize,
@@ -259,11 +265,12 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
self.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::ADVISE)?
.advise(offset, len, advice.into())?;
.advise(offset, len, advice.into())
.await?;
Ok(())
}
fn fd_allocate(
async fn fd_allocate(
&self,
fd: types::Fd,
offset: types::Filesize,
@@ -272,11 +279,12 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
self.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::ALLOCATE)?
.allocate(offset, len)?;
.allocate(offset, len)
.await?;
Ok(())
}
fn fd_close(&self, fd: types::Fd) -> Result<(), Error> {
async fn fd_close(&self, fd: types::Fd) -> Result<(), Error> {
let mut table = self.table();
let fd = u32::from(fd);
@@ -302,20 +310,21 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(())
}
fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> {
async fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::DATASYNC)?
.datasync()?;
.datasync()
.await?;
Ok(())
}
fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat, Error> {
async fn fd_fdstat_get(&self, fd: types::Fd) -> Result<types::Fdstat, Error> {
let table = self.table();
let fd = u32::from(fd);
if table.is::<FileEntry>(fd) {
let file_entry: Ref<FileEntry> = table.get(fd)?;
let fdstat = file_entry.get_fdstat()?;
let fdstat = file_entry.get_fdstat().await?;
Ok(types::Fdstat::from(&fdstat))
} else if table.is::<DirEntry>(fd) {
let dir_entry: Ref<DirEntry> = table.get(fd)?;
@@ -326,14 +335,15 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
}
fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
async fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> {
self.table()
.get_file_mut(u32::from(fd))?
.get_cap(FileCaps::FDSTAT_SET_FLAGS)?
.set_fdflags(FdFlags::from(flags))
.await
}
fn fd_fdstat_set_rights(
async fn fd_fdstat_set_rights(
&self,
fd: types::Fd,
fs_rights_base: types::Rights,
@@ -355,35 +365,42 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
}
fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat, Error> {
async fn fd_filestat_get(&self, fd: types::Fd) -> Result<types::Filestat, Error> {
let table = self.table();
let fd = u32::from(fd);
if table.is::<FileEntry>(fd) {
let filestat = table
.get_file(fd)?
.get_cap(FileCaps::FILESTAT_GET)?
.get_filestat()?;
.get_filestat()
.await?;
Ok(filestat.into())
} else if table.is::<DirEntry>(fd) {
let filestat = table
.get_dir(fd)?
.get_cap(DirCaps::FILESTAT_GET)?
.get_filestat()?;
.get_filestat()
.await?;
Ok(filestat.into())
} else {
Err(Error::badf())
}
}
fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<(), Error> {
async fn fd_filestat_set_size(
&self,
fd: types::Fd,
size: types::Filesize,
) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::FILESTAT_SET_SIZE)?
.set_filestat_size(size)?;
.set_filestat_size(size)
.await?;
Ok(())
}
fn fd_filestat_set_times(
async fn fd_filestat_set_times(
&self,
fd: types::Fd,
atim: types::Timestamp,
@@ -407,18 +424,24 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.expect("checked that entry is file")
.get_cap(FileCaps::FILESTAT_SET_TIMES)?
.set_times(atim, mtim)
.await
} else if table.is::<DirEntry>(fd) {
table
.get_dir(fd)
.expect("checked that entry is dir")
.get_cap(DirCaps::FILESTAT_SET_TIMES)?
.set_times(".", atim, mtim, false)
.await
} else {
Err(Error::badf())
}
}
fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result<types::Size, Error> {
async fn fd_read<'a>(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'a>,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::READ)?;
@@ -436,14 +459,14 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored(&mut ioslices)?;
let bytes_read = f.read_vectored(&mut ioslices).await?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_pread(
async fn fd_pread<'a>(
&self,
fd: types::Fd,
iovs: &types::IovecArray<'_>,
iovs: &types::IovecArray<'a>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
@@ -465,14 +488,14 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.map(|s| IoSliceMut::new(&mut *s))
.collect();
let bytes_read = f.read_vectored_at(&mut ioslices, offset)?;
let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?;
Ok(types::Size::try_from(bytes_read)?)
}
fn fd_write(
async fn fd_write<'a>(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
ciovs: &types::CiovecArray<'a>,
) -> Result<types::Size, Error> {
let table = self.table();
let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::WRITE)?;
@@ -490,15 +513,15 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored(&ioslices)?;
let bytes_written = f.write_vectored(&ioslices).await?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_pwrite(
async fn fd_pwrite<'a>(
&self,
fd: types::Fd,
ciovs: &types::CiovecArray<'_>,
ciovs: &types::CiovecArray<'a>,
offset: types::Filesize,
) -> Result<types::Size, Error> {
let table = self.table();
@@ -519,12 +542,12 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.iter()
.map(|s| IoSlice::new(s.deref()))
.collect();
let bytes_written = f.write_vectored_at(&ioslices, offset)?;
let bytes_written = f.write_vectored_at(&ioslices, offset).await?;
Ok(types::Size::try_from(bytes_written)?)
}
fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat, Error> {
async fn fd_prestat_get(&self, fd: types::Fd) -> Result<types::Prestat, Error> {
let table = self.table();
let dir_entry: Ref<DirEntry> = table.get(u32::from(fd)).map_err(|_| Error::badf())?;
if let Some(ref preopen) = dir_entry.preopen_path() {
@@ -536,10 +559,10 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
}
fn fd_prestat_dir_name(
async fn fd_prestat_dir_name<'a>(
&self,
fd: types::Fd,
path: &GuestPtr<u8>,
path: &GuestPtr<'a, u8>,
path_max_len: types::Size,
) -> Result<(), Error> {
let table = self.table();
@@ -560,7 +583,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Err(Error::not_supported())
}
}
fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
async fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> {
let mut table = self.table();
let from = u32::from(from);
let to = u32::from(to);
@@ -577,7 +600,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(())
}
fn fd_seek(
async fn fd_seek(
&self,
fd: types::Fd,
offset: types::Filedelta,
@@ -600,32 +623,35 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.table()
.get_file(u32::from(fd))?
.get_cap(required_caps)?
.seek(whence)?;
.seek(whence)
.await?;
Ok(newoffset)
}
fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> {
async fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> {
self.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::SYNC)?
.sync()?;
.sync()
.await?;
Ok(())
}
fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize, Error> {
async fn fd_tell(&self, fd: types::Fd) -> Result<types::Filesize, Error> {
// XXX should this be stream_position?
let offset = self
.table()
.get_file(u32::from(fd))?
.get_cap(FileCaps::TELL)?
.seek(std::io::SeekFrom::Current(0))?;
.seek(std::io::SeekFrom::Current(0))
.await?;
Ok(offset)
}
fn fd_readdir(
async fn fd_readdir<'a>(
&self,
fd: types::Fd,
buf: &GuestPtr<u8>,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
cookie: types::Dircookie,
) -> Result<types::Size, Error> {
@@ -635,7 +661,8 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.table()
.get_dir(u32::from(fd))?
.get_cap(DirCaps::READDIR)?
.readdir(ReaddirCursor::from(cookie))?
.readdir(ReaddirCursor::from(cookie))
.await?
{
let entity = entity?;
let dirent_raw = dirent_bytes(types::Dirent::try_from(&entity)?);
@@ -675,22 +702,23 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(bufused)
}
fn path_create_directory(
async fn path_create_directory<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.get_cap(DirCaps::CREATE_DIRECTORY)?
.create_dir(path.as_str()?.deref())
.await
}
fn path_filestat_get(
async fn path_filestat_get<'a>(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<types::Filestat, Error> {
let filestat = self
.table()
@@ -699,15 +727,16 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.get_path_filestat(
path.as_str()?.deref(),
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)?;
)
.await?;
Ok(types::Filestat::from(filestat))
}
fn path_filestat_set_times(
async fn path_filestat_set_times<'a>(
&self,
dirfd: types::Fd,
flags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
atim: types::Timestamp,
mtim: types::Timestamp,
fst_flags: types::Fstflags,
@@ -728,15 +757,16 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
mtim,
flags.contains(types::Lookupflags::SYMLINK_FOLLOW),
)
.await
}
fn path_link(
async fn path_link<'a>(
&self,
src_fd: types::Fd,
src_flags: types::Lookupflags,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
target_fd: types::Fd,
target_path: &GuestPtr<'_, str>,
target_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
let table = self.table();
let src_dir = table
@@ -751,18 +781,20 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.context("symlink following on path_link is not supported"));
}
src_dir.hard_link(
src_path.as_str()?.deref(),
target_dir.deref(),
target_path.as_str()?.deref(),
)
src_dir
.hard_link(
src_path.as_str()?.deref(),
target_dir.deref(),
target_path.as_str()?.deref(),
)
.await
}
fn path_open(
async fn path_open<'a>(
&self,
dirfd: types::Fd,
dirflags: types::Lookupflags,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
oflags: types::Oflags,
fs_rights_base: types::Rights,
fs_rights_inheriting: types::Rights,
@@ -790,7 +822,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base));
let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting));
let dir = dir_entry.get_cap(DirCaps::OPEN)?;
let child_dir = dir.open_dir(symlink_follow, path.deref())?;
let child_dir = dir.open_dir(symlink_follow, path.deref()).await?;
drop(dir);
let fd = table.push(Box::new(DirEntry::new(
dir_caps, file_caps, None, child_dir,
@@ -808,25 +840,28 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
let write = file_caps.contains(FileCaps::WRITE)
|| file_caps.contains(FileCaps::ALLOCATE)
|| file_caps.contains(FileCaps::FILESTAT_SET_SIZE);
let file = dir.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)?;
let file = dir
.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)
.await?;
drop(dir);
let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?;
Ok(types::Fd::from(fd))
}
}
fn path_readlink(
async fn path_readlink<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
buf: &GuestPtr<u8>,
path: &GuestPtr<'a, str>,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
) -> Result<types::Size, Error> {
let link = self
.table()
.get_dir(u32::from(dirfd))?
.get_cap(DirCaps::READLINK)?
.read_link(path.as_str()?.deref())?
.read_link(path.as_str()?.deref())
.await?
.into_os_string()
.into_string()
.map_err(|_| Error::illegal_byte_sequence().context("link contents"))?;
@@ -840,23 +875,24 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(link_len as types::Size)
}
fn path_remove_directory(
async fn path_remove_directory<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'_, str>,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.get_cap(DirCaps::REMOVE_DIRECTORY)?
.remove_dir(path.as_str()?.deref())
.await
}
fn path_rename(
async fn path_rename<'a>(
&self,
src_fd: types::Fd,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
dest_fd: types::Fd,
dest_path: &GuestPtr<'_, str>,
dest_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
let table = self.table();
let src_dir = table
@@ -865,36 +901,44 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
let dest_dir = table
.get_dir(u32::from(dest_fd))?
.get_cap(DirCaps::RENAME_TARGET)?;
src_dir.rename(
src_path.as_str()?.deref(),
dest_dir.deref(),
dest_path.as_str()?.deref(),
)
src_dir
.rename(
src_path.as_str()?.deref(),
dest_dir.deref(),
dest_path.as_str()?.deref(),
)
.await
}
fn path_symlink(
async fn path_symlink<'a>(
&self,
src_path: &GuestPtr<'_, str>,
src_path: &GuestPtr<'a, str>,
dirfd: types::Fd,
dest_path: &GuestPtr<'_, str>,
dest_path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.get_cap(DirCaps::SYMLINK)?
.symlink(src_path.as_str()?.deref(), dest_path.as_str()?.deref())
.await
}
fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<(), Error> {
async fn path_unlink_file<'a>(
&self,
dirfd: types::Fd,
path: &GuestPtr<'a, str>,
) -> Result<(), Error> {
self.table()
.get_dir(u32::from(dirfd))?
.get_cap(DirCaps::UNLINK_FILE)?
.unlink_file(path.as_str()?.deref())
.await
}
fn poll_oneoff(
async fn poll_oneoff<'a>(
&self,
subs: &GuestPtr<types::Subscription>,
events: &GuestPtr<types::Event>,
subs: &GuestPtr<'a, types::Subscription>,
events: &GuestPtr<'a, types::Event>,
nsubscriptions: types::Size,
) -> Result<types::Size, Error> {
if nsubscriptions == 0 {
@@ -912,7 +956,9 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
.flags
.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME)
{
self.sched.sleep(Duration::from_nanos(clocksub.timeout))?;
self.sched
.sleep(Duration::from_nanos(clocksub.timeout))
.await?;
events.write(types::Event {
userdata: sub.userdata,
error: types::Errno::Success,
@@ -925,6 +971,10 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
let table = self.table();
let mut sub_fds: HashSet<types::Fd> = HashSet::new();
// We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside
let mut read_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new();
let mut write_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new();
let mut poll = Poll::new();
let subs = subs.as_array(nsubscriptions);
@@ -963,22 +1013,41 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
},
types::SubscriptionU::FdRead(readsub) => {
let fd = readsub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
if sub_fds.contains(&fd) {
return Err(Error::invalid_argument()
.context("Fd can be subscribed to at most once per poll"));
} else {
sub_fds.insert(fd);
}
let file_ref = table
.get_file_mut(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_read(file, sub.userdata.into());
read_refs.push((file_ref, sub.userdata.into()));
}
types::SubscriptionU::FdWrite(writesub) => {
let fd = writesub.file_descriptor;
let file = table
.get_file(u32::from(fd))?
if sub_fds.contains(&fd) {
return Err(Error::invalid_argument()
.context("Fd can be subscribed to at most once per poll"));
} else {
sub_fds.insert(fd);
}
let file_ref = table
.get_file_mut(u32::from(fd))?
.get_cap(FileCaps::POLL_READWRITE)?;
poll.subscribe_write(file, sub.userdata.into());
write_refs.push((file_ref, sub.userdata.into()));
}
}
}
self.sched.poll_oneoff(&poll)?;
for (f, ud) in read_refs.iter_mut() {
poll.subscribe_read(f.deref_mut(), *ud);
}
for (f, ud) in write_refs.iter_mut() {
poll.subscribe_write(f.deref_mut(), *ud);
}
self.sched.poll_oneoff(&mut poll).await?;
let results = poll.results();
let num_results = results.len();
@@ -1053,7 +1122,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
Ok(num_results.try_into().expect("results fit into memory"))
}
fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
async fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap {
// Check that the status is within WASI's range.
if status < 126 {
wiggle::Trap::I32Exit(status as i32)
@@ -1062,39 +1131,43 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx {
}
}
fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> {
async fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> {
Err(Error::trap("proc_raise unsupported"))
}
fn sched_yield(&self) -> Result<(), Error> {
self.sched.sched_yield()
async fn sched_yield(&self) -> Result<(), Error> {
self.sched.sched_yield().await
}
fn random_get(&self, buf: &GuestPtr<u8>, buf_len: types::Size) -> Result<(), Error> {
async fn random_get<'a>(
&self,
buf: &GuestPtr<'a, u8>,
buf_len: types::Size,
) -> Result<(), Error> {
let mut buf = buf.as_array(buf_len).as_slice_mut()?;
self.random.borrow_mut().try_fill_bytes(buf.deref_mut())?;
Ok(())
}
fn sock_recv(
async fn sock_recv<'a>(
&self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_data: &types::IovecArray<'a>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags), Error> {
Err(Error::trap("sock_recv unsupported"))
}
fn sock_send(
async fn sock_send<'a>(
&self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_data: &types::CiovecArray<'a>,
_si_flags: types::Siflags,
) -> Result<types::Size, Error> {
Err(Error::trap("sock_send unsupported"))
}
fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
async fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> {
Err(Error::trap("sock_shutdown unsupported"))
}
}

View File

@@ -0,0 +1,42 @@
[package]
name = "wasi-tokio"
version = "0.26.0"
authors = ["The Wasmtime Project Developers"]
description = "WASI implementation in Rust"
license = "Apache-2.0 WITH LLVM-exception"
categories = ["wasm"]
keywords = ["webassembly", "wasm"]
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition = "2018"
include = ["src/**/*", "LICENSE" ]
[dependencies]
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", "io-std", "rt-multi-thread"] }
cap-std = "0.13.7"
cap-fs-ext = "0.13.7"
cap-time-ext = "0.13.7"
fs-set-times = "0.3.1"
unsafe-io = "0.6.5"
system-interface = { version = "0.6.3", features = ["cap_std_impls"] }
tracing = "0.1.19"
bitflags = "1.2"
anyhow = "1"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
posish = "0.6.1"
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
lazy_static = "1.4"
[dev-dependencies]
tempfile = "3.1.0"
tokio = { version = "1.5.0", features = [ "macros" ] }
anyhow = "1"
cap-tempfile = "0.13.7"

View File

@@ -0,0 +1,209 @@
use crate::{block_on_dummy_executor, file::File};
use std::any::Any;
use std::path::PathBuf;
use wasi_common::{
dir::{ReaddirCursor, ReaddirEntity, WasiDir},
file::{FdFlags, Filestat, OFlags, WasiFile},
Error, ErrorExt,
};
pub struct Dir(wasi_cap_std_sync::dir::Dir);
impl Dir {
pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self {
Dir(wasi_cap_std_sync::dir::Dir::from_cap_std(dir))
}
}
#[wiggle::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<Box<dyn WasiFile>, Error> {
let f = block_on_dummy_executor(move || async move {
self.0
.open_file_(symlink_follow, path, oflags, read, write, fdflags)
})?;
Ok(Box::new(File::from_inner(f)))
}
async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, Error> {
let d =
block_on_dummy_executor(move || async move { self.0.open_dir_(symlink_follow, path) })?;
Ok(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<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error> {
struct I(Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>);
impl Iterator for I {
type Item = Result<ReaddirEntity, Error>;
fn next(&mut self) -> Option<Self::Item> {
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<PathBuf, Error> {
block_on_dummy_executor(move || self.0.read_link(path))
}
async fn get_filestat(&self) -> Result<Filestat, Error> {
block_on_dummy_executor(|| self.0.get_filestat())
}
async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, Error> {
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::<Self>()
.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::<Self>()
.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<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks))
}
}
#[cfg(test)]
mod test {
use super::Dir;
#[tokio::test(flavor = "multi_thread")]
async fn scratch_dir() {
let tempdir = tempfile::Builder::new()
.prefix("cap-std-sync")
.tempdir()
.expect("create temporary dir");
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) }
.expect("open ambient temporary dir");
let preopen_dir = Dir::from_cap_std(preopen_dir);
wasi_common::WasiDir::open_dir(&preopen_dir, false, ".")
.await
.expect("open the same directory via WasiDir abstraction");
}
// Readdir does not work on windows, so we won't test it there.
#[cfg(not(windows))]
#[tokio::test(flavor = "multi_thread")]
async fn readdir() {
use std::collections::HashMap;
use wasi_common::dir::{ReaddirCursor, ReaddirEntity, WasiDir};
use wasi_common::file::{FdFlags, FileType, OFlags};
async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap<String, ReaddirEntity> {
let mut out = HashMap::new();
for readdir_result in dir
.readdir(ReaddirCursor::from(0))
.await
.expect("readdir succeeds")
{
let entity = readdir_result.expect("readdir entry is valid");
out.insert(entity.name.clone(), entity);
}
out
}
let tempdir = tempfile::Builder::new()
.prefix("cap-std-sync")
.tempdir()
.expect("create temporary dir");
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) }
.expect("open ambient temporary dir");
let preopen_dir = Dir::from_cap_std(preopen_dir);
let entities = readdir_into_map(&preopen_dir).await;
assert_eq!(
entities.len(),
2,
"should just be . and .. in empty dir: {:?}",
entities
);
assert!(entities.get(".").is_some());
assert!(entities.get("..").is_some());
preopen_dir
.open_file(
false,
"file1",
OFlags::CREATE,
true,
false,
FdFlags::empty(),
)
.await
.expect("create file1");
let entities = readdir_into_map(&preopen_dir).await;
assert_eq!(entities.len(), 3, "should be ., .., file1 {:?}", entities);
assert_eq!(
entities.get(".").expect(". entry").filetype,
FileType::Directory
);
assert_eq!(
entities.get("..").expect(".. entry").filetype,
FileType::Directory
);
assert_eq!(
entities.get("file1").expect("file1 entry").filetype,
FileType::RegularFile
);
}
}

View File

@@ -0,0 +1,186 @@
use crate::block_on_dummy_executor;
use std::any::Any;
use std::io;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use wasi_common::{
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
Error,
};
pub struct File(wasi_cap_std_sync::file::File);
impl File {
pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self {
File(file)
}
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
Self::from_inner(wasi_cap_std_sync::file::File::from_cap_std(file))
}
}
pub struct Stdin(wasi_cap_std_sync::stdio::Stdin);
pub fn stdin() -> Stdin {
Stdin(wasi_cap_std_sync::stdio::stdin())
}
pub struct Stdout(wasi_cap_std_sync::stdio::Stdout);
pub fn stdout() -> Stdout {
Stdout(wasi_cap_std_sync::stdio::stdout())
}
pub struct Stderr(wasi_cap_std_sync::stdio::Stderr);
pub fn stderr() -> Stderr {
Stderr(wasi_cap_std_sync::stdio::stderr())
}
macro_rules! wasi_file_impl {
($ty:ty) => {
#[wiggle::async_trait]
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
self
}
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<FileType, Error> {
block_on_dummy_executor(|| self.0.get_filetype())
}
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
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<Filestat, Error> {
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 allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.allocate(offset, len))
}
async fn read_vectored<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
) -> Result<u64, Error> {
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<u64, Error> {
block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset))
}
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.write_vectored(bufs))
}
async fn write_vectored_at<'a>(
&self,
bufs: &[io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset))
}
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.seek(pos))
}
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.peek(buf))
}
async fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.set_times(atime, mtime))
}
async fn num_ready_bytes(&self) -> Result<u64, Error> {
block_on_dummy_executor(|| self.0.num_ready_bytes())
}
#[cfg(not(windows))]
async fn readable(&mut 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 tokio::io::{unix::AsyncFd, Interest};
use unsafe_io::os::posish::AsRawFd;
let rawfd = self.0.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 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> {
// Windows uses a rawfd based scheduler :(
use wasi_common::ErrorExt;
Err(Error::badf())
}
#[cfg(not(windows))]
async fn writable(&mut 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 tokio::io::{unix::AsyncFd, Interest};
use unsafe_io::os::posish::AsRawFd;
let rawfd = self.0.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 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> {
// Windows uses a rawfd based scheduler :(
use wasi_common::ErrorExt;
Err(Error::badf())
}
}
#[cfg(windows)]
impl AsRawHandle for $ty {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
};
}
wasi_file_impl!(File);
wasi_file_impl!(Stdin);
wasi_file_impl!(Stdout);
wasi_file_impl!(Stderr);

View File

@@ -0,0 +1,114 @@
mod dir;
mod file;
pub mod sched;
pub mod stdio;
use std::cell::RefCell;
use std::future::Future;
use std::path::Path;
use std::rc::Rc;
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);
impl WasiCtxBuilder {
pub fn new() -> Self {
WasiCtxBuilder(WasiCtx::builder(
random_ctx(),
clocks_ctx(),
sched_ctx(),
Rc::new(RefCell::new(Table::new())),
))
}
pub fn env(self, var: &str, value: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.env(var, value)?;
Ok(WasiCtxBuilder(s))
}
pub fn envs(self, env: &[(String, String)]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for (k, v) in env {
s = s.env(k, v)?;
}
Ok(s)
}
pub fn inherit_env(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for (key, value) in std::env::vars() {
s = s.env(&key, &value)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn arg(self, arg: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.arg(arg)?;
Ok(WasiCtxBuilder(s))
}
pub fn args(self, arg: &[String]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for a in arg {
s = s.arg(&a)?;
}
Ok(s)
}
pub fn inherit_args(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for arg in std::env::args() {
s = s.arg(&arg)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn stdin(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdin(f))
}
pub fn stdout(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdout(f))
}
pub fn stderr(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stderr(f))
}
pub fn inherit_stdin(self) -> Self {
self.stdin(Box::new(crate::stdio::stdin()))
}
pub fn inherit_stdout(self) -> Self {
self.stdout(Box::new(crate::stdio::stdout()))
}
pub fn inherit_stderr(self) -> Self {
self.stderr(Box::new(crate::stdio::stderr()))
}
pub fn inherit_stdio(self) -> Self {
self.inherit_stdin().inherit_stdout().inherit_stderr()
}
pub fn preopened_dir(
self,
dir: cap_std::fs::Dir,
guest_path: impl AsRef<Path>,
) -> Result<Self, wasi_common::Error> {
let dir = Box::new(Dir::from_cap_std(dir));
Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?))
}
pub fn build(self) -> Result<WasiCtx, wasi_common::Error> {
self.0.build()
}
}
// Much of this crate is implemented in terms of `async` methods from the
// wasi-cap-std-sync crate. 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<T, Error>
where
F: FnOnce() -> Fut + Send + 'a,
Fut: Future<Output = Result<T, Error>>,
T: Send + 'static,
{
tokio::task::block_in_place(move || wiggle::run_in_dummy_executor(f()))
}

View File

@@ -0,0 +1,35 @@
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::poll_oneoff;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::poll_oneoff;
use wasi_common::{
sched::{Duration, Poll, WasiSched},
Error,
};
pub fn sched_ctx() -> Box<dyn wasi_common::WasiSched> {
struct AsyncSched;
#[wiggle::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)
}

View File

@@ -0,0 +1,91 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll as FPoll};
use wasi_common::{
sched::{
subscription::{RwEventFlags, Subscription},
Poll,
},
Context as _, Error,
};
struct FirstReady<'a, T>(Vec<Pin<Box<dyn Future<Output = T> + 'a>>>);
impl<'a, T> FirstReady<'a, T> {
fn new() -> Self {
FirstReady(Vec::new())
}
fn push(&mut self, f: impl Future<Output = T> + '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<T> {
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.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(async move {
f.file.writable().await.context("writable future")?;
f.complete(0, RwEventFlags::empty());
Ok(())
});
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if let Some(Some(remaining_duration)) = duration {
match tokio::time::timeout(remaining_duration, futures).await {
Ok(r) => r?,
Err(_deadline_elapsed) => {}
}
} else {
futures.await?;
}
Ok(())
}

View File

@@ -0,0 +1,47 @@
use crate::block_on_dummy_executor;
use std::os::windows::io::{AsRawHandle, RawHandle};
use wasi_cap_std_sync::sched::windows::poll_oneoff_;
use wasi_common::{file::WasiFile, sched::Poll, Error};
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-cap-std-crate.
// We provide a function specific to this crate's WasiFile types for downcasting
// to a RawHandle.
block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle))
}
pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
f.as_any().is::<crate::stdio::Stdin>()
}
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(
a.downcast_ref::<crate::file::File>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdin>() {
Some(
a.downcast_ref::<crate::stdio::Stdin>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdout>() {
Some(
a.downcast_ref::<crate::stdio::Stdout>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stderr>() {
Some(
a.downcast_ref::<crate::stdio::Stderr>()
.unwrap()
.as_raw_handle(),
)
} else {
None
}
}

View File

@@ -0,0 +1 @@
pub use crate::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout};

View File

@@ -0,0 +1,158 @@
use anyhow::{Context, Error};
use cap_std::time::Duration;
use std::collections::HashMap;
use wasi_common::{
file::{FdFlags, OFlags},
sched::{Poll, RwEventFlags, SubscriptionResult, Userdata},
WasiDir, WasiFile,
};
use wasi_tokio::{clocks_ctx, sched::poll_oneoff, Dir};
#[tokio::test(flavor = "multi_thread")]
async fn empty_file_readable() -> Result<(), Error> {
let clocks = clocks_ctx();
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 f = d
.open_file(false, "f", OFlags::CREATE, false, true, FdFlags::empty())
.await
.context("create writable file f")?;
let to_write: Vec<u8> = vec![0];
f.write_vectored(&vec![std::io::IoSlice::new(&to_write)])
.await
.context("write to f")?;
drop(f);
let mut f = d
.open_file(false, "f", OFlags::empty(), true, false, FdFlags::empty())
.await
.context("open f as readable")?;
let mut poll = Poll::new();
poll.subscribe_read(&mut *f, Userdata::from(123));
// Timeout bounds time in poll_oneoff
poll.subscribe_monotonic_clock(
&*clocks.monotonic,
clocks
.monotonic
.now(clocks.monotonic.resolution())
.checked_add(Duration::from_millis(10))
.unwrap(),
clocks.monotonic.resolution(),
Userdata::from(0),
);
poll_oneoff(&mut poll).await?;
let events = poll.results();
match events.get(0).expect("at least one event") {
(SubscriptionResult::Read(Ok((1, flags))), ud) => {
assert_eq!(*flags, RwEventFlags::empty());
assert_eq!(*ud, Userdata::from(123));
}
_ => panic!("expected (Read(Ok(1, empty), 123), got: {:?}", events[0]),
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn empty_file_writable() -> Result<(), Error> {
let clocks = clocks_ctx();
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 writable_f = d
.open_file(false, "f", OFlags::CREATE, true, true, FdFlags::empty())
.await
.context("create writable file")?;
let mut poll = Poll::new();
poll.subscribe_write(&mut *writable_f, Userdata::from(123));
// Timeout bounds time in poll_oneoff
poll.subscribe_monotonic_clock(
&*clocks.monotonic,
clocks
.monotonic
.now(clocks.monotonic.resolution())
.checked_add(Duration::from_millis(10))
.unwrap(),
clocks.monotonic.resolution(),
Userdata::from(0),
);
poll_oneoff(&mut poll).await?;
let events = poll.results();
match events.get(0).expect("at least one event") {
(SubscriptionResult::Write(Ok((0, flags))), ud) => {
assert_eq!(*flags, RwEventFlags::empty());
assert_eq!(*ud, Userdata::from(123));
}
_ => panic!(""),
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn stdio_readable() -> Result<(), Error> {
let clocks = clocks_ctx();
let deadline = clocks
.monotonic
.now(clocks.monotonic.resolution())
.checked_add(Duration::from_millis(10))
.unwrap();
let mut waiting_on: HashMap<u64, Box<dyn WasiFile>> = vec![
(
1,
Box::new(wasi_tokio::stdio::stdout()) as Box<dyn WasiFile>,
),
(2, Box::new(wasi_tokio::stdio::stderr())),
]
.into_iter()
.collect();
while !waiting_on.is_empty() {
let mut poll = Poll::new();
for (ix, file) in waiting_on.iter_mut() {
poll.subscribe_write(&mut **file, Userdata::from(*ix));
}
// Timeout bounds time in poll_oneoff
poll.subscribe_monotonic_clock(
&*clocks.monotonic,
deadline,
clocks.monotonic.resolution(),
Userdata::from(999),
);
poll_oneoff(&mut poll).await?;
let events = poll.results();
for e in events {
match e {
(SubscriptionResult::Write(Ok(_)), ud) => {
let _ = waiting_on.remove(&u64::from(ud));
}
(SubscriptionResult::Write(Err(_)), ud) => {
panic!("error on ix {}", u64::from(ud))
}
(SubscriptionResult::Read { .. }, _) => unreachable!(),
(SubscriptionResult::MonotonicClock { .. }, _) => {
panic!("timed out before stdin and stdout ready for reading")
}
}
}
}
Ok(())
}