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:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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>),
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
42
crates/wasi-common/tokio/Cargo.toml
Normal file
42
crates/wasi-common/tokio/Cargo.toml
Normal 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"
|
||||
209
crates/wasi-common/tokio/src/dir.rs
Normal file
209
crates/wasi-common/tokio/src/dir.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
186
crates/wasi-common/tokio/src/file.rs
Normal file
186
crates/wasi-common/tokio/src/file.rs
Normal 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);
|
||||
114
crates/wasi-common/tokio/src/lib.rs
Normal file
114
crates/wasi-common/tokio/src/lib.rs
Normal 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()))
|
||||
}
|
||||
35
crates/wasi-common/tokio/src/sched.rs
Normal file
35
crates/wasi-common/tokio/src/sched.rs
Normal 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)
|
||||
}
|
||||
91
crates/wasi-common/tokio/src/sched/unix.rs
Normal file
91
crates/wasi-common/tokio/src/sched/unix.rs
Normal 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(())
|
||||
}
|
||||
47
crates/wasi-common/tokio/src/sched/windows.rs
Normal file
47
crates/wasi-common/tokio/src/sched/windows.rs
Normal 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
|
||||
}
|
||||
}
|
||||
1
crates/wasi-common/tokio/src/stdio.rs
Normal file
1
crates/wasi-common/tokio/src/stdio.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub use crate::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout};
|
||||
158
crates/wasi-common/tokio/tests/poll_oneoff.rs
Normal file
158
crates/wasi-common/tokio/tests/poll_oneoff.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user