move wasi-c2 into wasi-common

This commit is contained in:
Pat Hickey
2021-01-28 15:15:50 -08:00
parent dd005208b6
commit 47fec44c10
29 changed files with 3 additions and 11 deletions

View File

@@ -0,0 +1,48 @@
[package]
name = "wasi-c2"
version = "0.22.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", "build.rs"]
build = "build.rs"
publish = false
# This doesn't actually link to a native library, but it allows us to set env
# vars like `DEP_WASI_C2_19_*` for crates that have build scripts and depend
# on this crate, allowing other crates to use the same witx files.
links = "wasi-c2-19"
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
tracing = "0.1.19"
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }
cap-std = "0.11"
cap-rand = "0.11"
bitflags = "1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
[badges]
maintenance = { status = "actively-developed" }
[features]
default = ["trace_log"]
# This feature enables the `tracing` logs in the calls to target the `log`
# ecosystem of backends (e.g. `env_logger`. Disable this if you want to use
# `tracing-subscriber`.
trace_log = [ "wiggle/tracing_log", "tracing/log" ]
# Need to make the wiggle_metadata feature available to consumers of this
# crate if they want the snapshots to have metadata available.
wiggle_metadata = ["wiggle/wiggle_metadata"]

View File

@@ -2,7 +2,9 @@
// use the same witx files if they want.
fn main() {
let cwd = std::env::current_dir().unwrap();
let wasi = cwd.join("WASI");
let wasi = cwd.join("..").join("wasi-common").join("WASI");
// this will be available to dependent crates via the DEP_WASI_C2_19_WASI env var:
println!("cargo:wasi={}", wasi.display());
// and available to our own crate as WASI_ROOT:
println!("cargo:rustc-env=WASI_ROOT={}", wasi.display());
}

View File

@@ -0,0 +1,33 @@
[package]
name = "wasi-c2-cap-std-sync"
version = "0.22.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" ]
publish = false
[dependencies]
wasi-c2 = { path = "../" }
anyhow = "1.0"
cap-std = "0.11"
cap-fs-ext = "0.11"
cap-time-ext = "0.11"
cap-rand = "0.11"
fs-set-times = "0.2.2"
unsafe-io = "0.3"
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }
tracing = "0.1.19"
bitflags = "1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
lazy_static = "1.4"

View File

@@ -0,0 +1,46 @@
use cap_std::time::{Duration, Instant, SystemTime};
use cap_time_ext::{MonotonicClockExt, SystemClockExt};
use wasi_c2::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock};
pub struct SystemClock(cap_std::time::SystemClock);
impl SystemClock {
pub unsafe fn new() -> Self {
SystemClock(cap_std::time::SystemClock::new())
}
}
impl WasiSystemClock for SystemClock {
fn resolution(&self) -> Duration {
self.0.resolution()
}
fn now(&self, precision: Duration) -> SystemTime {
self.0.now_with(precision)
}
}
pub struct MonotonicClock(cap_std::time::MonotonicClock);
impl MonotonicClock {
pub unsafe fn new() -> Self {
MonotonicClock(cap_std::time::MonotonicClock::new())
}
}
impl WasiMonotonicClock for MonotonicClock {
fn resolution(&self) -> Duration {
self.0.resolution()
}
fn now(&self, precision: Duration) -> Instant {
self.0.now_with(precision)
}
}
pub fn clocks() -> WasiClocks {
let system = Box::new(unsafe { SystemClock::new() });
let monotonic = unsafe { cap_std::time::MonotonicClock::new() };
let creation_time = monotonic.now();
let monotonic = Box::new(MonotonicClock(monotonic));
WasiClocks {
system,
monotonic,
creation_time,
}
}

View File

@@ -0,0 +1,249 @@
use crate::file::{filetype_from, File};
use cap_fs_ext::{DirExt, MetadataExt, SystemTimeSpec};
use std::any::Any;
use std::convert::TryInto;
use std::path::{Path, PathBuf};
use wasi_c2::{
dir::{ReaddirCursor, ReaddirEntity, WasiDir},
file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile},
Error, ErrorExt,
};
pub struct Dir(cap_std::fs::Dir);
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(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
caps: FileCaps,
fdflags: FdFlags,
) -> Result<Box<dyn WasiFile>, Error> {
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt};
let mut opts = cap_std::fs::OpenOptions::new();
if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) {
opts.create_new(true);
opts.write(true);
} else if oflags.contains(OFlags::CREATE) {
opts.create(true);
opts.write(true);
}
if oflags.contains(OFlags::TRUNCATE) {
opts.truncate(true);
}
if caps.contains(FileCaps::WRITE)
|| caps.contains(FileCaps::DATASYNC)
|| caps.contains(FileCaps::ALLOCATE)
|| caps.contains(FileCaps::FILESTAT_SET_SIZE)
{
opts.write(true);
} else {
// If not opened write, open read. This way the OS lets us open the file.
// If FileCaps::READ is not set, read calls will be rejected at the
// get_cap check.
opts.read(true);
}
if caps.contains(FileCaps::READ) {
opts.read(true);
}
if fdflags.contains(FdFlags::APPEND) {
opts.append(true);
}
// XXX what about rest of fdflags - dsync, sync become oflags.
// what do we do with nonblock?
// what do we do with rsync?
if symlink_follow {
opts.follow(FollowSymlinks::Yes);
} else {
opts.follow(FollowSymlinks::No);
}
let f = self.0.open_with(Path::new(path), &opts)?;
Ok(Box::new(File::from_cap_std(f)))
}
fn open_dir(&self, symlink_follow: bool, path: &str) -> Result<Box<dyn WasiDir>, 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)))
}
fn create_dir(&self, path: &str) -> Result<(), Error> {
self.0.create_dir(Path::new(path))?;
Ok(())
}
fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<(ReaddirEntity, String), Error>>>, 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.
let dir_meta = self.0.dir_metadata()?;
let rd = vec![
{
let name = ".".to_owned();
let namelen = name.as_bytes().len().try_into().expect("1 wont overflow");
Ok((FileType::Directory, dir_meta.ino(), namelen, name))
},
{
// XXX if parent dir is mounted it *might* be possible to give its inode, but we
// don't know that in this context.
let name = "..".to_owned();
let namelen = name.as_bytes().len().try_into().expect("2 wont overflow");
Ok((FileType::Directory, dir_meta.ino(), namelen, name))
},
]
.into_iter()
.chain(
// Now process the `DirEntry`s:
self.0.entries()?.map(|entry| {
let entry = entry?;
let meta = entry.metadata()?;
let inode = meta.ino();
let filetype = filetype_from(&meta.file_type());
let name = entry
.file_name()
.into_string()
.map_err(|_| Error::illegal_byte_sequence().context("filename"))?;
let namelen = name.as_bytes().len().try_into()?;
Ok((filetype, inode, namelen, name))
}),
)
// Enumeration of the iterator makes it possible to define the ReaddirCursor
.enumerate()
.map(|(ix, r)| match r {
Ok((filetype, inode, namelen, name)) => Ok((
ReaddirEntity {
next: ReaddirCursor::from(ix as u64 + 1),
filetype,
inode,
namelen,
},
name,
)),
Err(e) => Err(e),
})
.skip(u64::from(cursor) as usize);
Ok(Box::new(rd))
}
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> {
self.0.remove_dir(Path::new(path))?;
Ok(())
}
fn unlink_file(&self, path: &str) -> Result<(), Error> {
self.0.remove_file(Path::new(path))?;
Ok(())
}
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> {
let meta = self.0.dir_metadata()?;
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
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 {
self.0.symlink_metadata(Path::new(path))?
};
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
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(())
}
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 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(())
}
fn set_times(
&self,
path: &str,
atime: Option<wasi_c2::SystemTimeSpec>,
mtime: Option<wasi_c2::SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error> {
if follow_symlinks {
self.0.set_times(
Path::new(path),
convert_systimespec(atime),
convert_systimespec(mtime),
)?;
} else {
self.0.set_symlink_times(
Path::new(path),
convert_systimespec(atime),
convert_systimespec(mtime),
)?;
}
Ok(())
}
}
fn convert_systimespec(t: Option<wasi_c2::SystemTimeSpec>) -> Option<SystemTimeSpec> {
match t {
Some(wasi_c2::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)),
Some(wasi_c2::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
None => None,
}
}

View File

@@ -0,0 +1,193 @@
use cap_fs_ext::MetadataExt;
use fs_set_times::{SetTimes, SystemTimeSpec};
use std::any::Any;
use std::convert::TryInto;
use std::io;
use system_interface::{
fs::{Advice, FileIoExt, GetSetFdFlags},
io::ReadReady,
};
use wasi_c2::{
file::{FdFlags, FileType, Filestat, WasiFile},
Error,
};
pub struct File(cap_std::fs::File);
impl File {
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
File(file)
}
}
impl WasiFile for File {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
self.0.sync_data()?;
Ok(())
}
fn sync(&self) -> Result<(), Error> {
self.0.sync_all()?;
Ok(())
}
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> {
let fdflags = self.0.get_fd_flags()?;
Ok(from_sysif_fdflags(fdflags))
}
fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
Ok(self.0.set_fd_flags(to_sysif_fdflags(fdflags))?)
}
fn get_filestat(&self) -> Result<Filestat, Error> {
let meta = self.0.metadata()?;
Ok(Filestat {
device_id: meta.dev(),
inode: meta.ino(),
filetype: filetype_from(&meta.file_type()),
nlink: meta.nlink(),
size: meta.len(),
atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None),
mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None),
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
})
}
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> {
self.0.advise(offset, len, advice)?;
Ok(())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
self.0.allocate(offset, len)?;
Ok(())
}
fn set_times(
&self,
atime: Option<wasi_c2::SystemTimeSpec>,
mtime: Option<wasi_c2::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> 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> {
let n = self.0.read_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> 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> {
let n = self.0.write_vectored_at(bufs, offset)?;
Ok(n.try_into()?)
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Ok(self.0.seek(pos)?)
}
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> {
Ok(self.0.num_ready_bytes()?)
}
}
pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType {
use cap_fs_ext::FileTypeExt;
if ft.is_dir() {
FileType::Directory
} else if ft.is_symlink() {
FileType::SymbolicLink
} else if ft.is_socket() {
if ft.is_block_device() {
FileType::SocketDgram
} else {
FileType::SocketStream
}
} else if ft.is_block_device() {
FileType::BlockDevice
} else if ft.is_char_device() {
FileType::CharacterDevice
} else if ft.is_file() {
FileType::RegularFile
} else {
FileType::Unknown
}
}
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
#[cfg(windows)]
impl AsRawHandle for File {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(unix)]
impl AsRawFd for File {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
pub fn convert_systimespec(t: Option<wasi_c2::SystemTimeSpec>) -> Option<SystemTimeSpec> {
match t {
Some(wasi_c2::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())),
Some(wasi_c2::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow),
None => None,
}
}
pub fn to_sysif_fdflags(f: wasi_c2::file::FdFlags) -> system_interface::fs::FdFlags {
let mut out = system_interface::fs::FdFlags::empty();
if f.contains(wasi_c2::file::FdFlags::APPEND) {
out |= system_interface::fs::FdFlags::APPEND;
}
if f.contains(wasi_c2::file::FdFlags::DSYNC) {
out |= system_interface::fs::FdFlags::DSYNC;
}
if f.contains(wasi_c2::file::FdFlags::NONBLOCK) {
out |= system_interface::fs::FdFlags::NONBLOCK;
}
if f.contains(wasi_c2::file::FdFlags::RSYNC) {
out |= system_interface::fs::FdFlags::RSYNC;
}
if f.contains(wasi_c2::file::FdFlags::SYNC) {
out |= system_interface::fs::FdFlags::SYNC;
}
out
}
pub fn from_sysif_fdflags(f: system_interface::fs::FdFlags) -> wasi_c2::file::FdFlags {
let mut out = wasi_c2::file::FdFlags::empty();
if f.contains(system_interface::fs::FdFlags::APPEND) {
out |= wasi_c2::file::FdFlags::APPEND;
}
if f.contains(system_interface::fs::FdFlags::DSYNC) {
out |= wasi_c2::file::FdFlags::DSYNC;
}
if f.contains(system_interface::fs::FdFlags::NONBLOCK) {
out |= wasi_c2::file::FdFlags::NONBLOCK;
}
if f.contains(system_interface::fs::FdFlags::RSYNC) {
out |= wasi_c2::file::FdFlags::RSYNC;
}
if f.contains(system_interface::fs::FdFlags::SYNC) {
out |= wasi_c2::file::FdFlags::SYNC;
}
out
}

View File

@@ -0,0 +1,61 @@
pub mod clocks;
pub mod dir;
pub mod file;
pub mod sched;
pub mod stdio;
use cap_rand::RngCore;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use wasi_c2::{table::Table, Error, WasiCtx, WasiFile};
pub struct WasiCtxBuilder(wasi_c2::WasiCtxBuilder);
impl WasiCtxBuilder {
pub fn new() -> Self {
WasiCtxBuilder(WasiCtx::builder(
random(),
clocks::clocks(),
Box::new(sched::SyncSched::new()),
Rc::new(RefCell::new(Table::new())),
))
}
pub fn env(self, var: &str, value: &str) -> Result<Self, wasi_c2::StringArrayError> {
let s = self.0.env(var, value)?;
Ok(WasiCtxBuilder(s))
}
pub fn arg(self, arg: &str) -> Result<Self, wasi_c2::StringArrayError> {
let s = self.0.arg(arg)?;
Ok(WasiCtxBuilder(s))
}
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdin(f))
}
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdout(f))
}
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
WasiCtxBuilder(self.0.stderr(f))
}
pub fn inherit_stdio(self) -> Self {
self.stdin(Box::new(crate::stdio::stdin()))
.stdout(Box::new(crate::stdio::stdout()))
.stderr(Box::new(crate::stdio::stderr()))
}
pub fn preopened_dir(
self,
dir: cap_std::fs::Dir,
path: impl AsRef<Path>,
) -> Result<Self, Error> {
let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
Ok(WasiCtxBuilder(self.0.preopened_dir(dir, path)?))
}
pub fn build(self) -> Result<WasiCtx, Error> {
self.0.build()
}
}
pub fn random() -> RefCell<Box<dyn RngCore>> {
RefCell::new(Box::new(unsafe { cap_rand::rngs::OsRng::default() }))
}

View File

@@ -0,0 +1,9 @@
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::*;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::*;

View File

@@ -0,0 +1,190 @@
use cap_std::time::Duration;
use std::convert::TryInto;
use std::ops::Deref;
use std::os::unix::io::{AsRawFd, RawFd};
use wasi_c2::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
},
Error, ErrorExt,
};
use poll::{PollFd, PollFlags};
pub struct SyncSched;
impl SyncSched {
pub fn new() -> Self {
SyncSched
}
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
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());
};
}
}
} else {
timeout
.expect("timed out")
.result()
.expect("timer deadline is past")
.unwrap()
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
std::thread::yield_now();
Ok(())
}
}
fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option<RawFd> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(a.downcast_ref::<crate::file::File>().unwrap().as_raw_fd())
} else if a.is::<crate::stdio::Stdin>() {
Some(a.downcast_ref::<crate::stdio::Stdin>().unwrap().as_raw_fd())
} else if a.is::<crate::stdio::Stdout>() {
Some(
a.downcast_ref::<crate::stdio::Stdout>()
.unwrap()
.as_raw_fd(),
)
} else if a.is::<crate::stdio::Stderr>() {
Some(
a.downcast_ref::<crate::stdio::Stderr>()
.unwrap()
.as_raw_fd(),
)
} else {
None
}
}
mod poll {
use bitflags::bitflags;
use std::convert::TryInto;
use std::os::unix::io::RawFd;
bitflags! {
pub struct PollFlags: libc::c_short {
const POLLIN = libc::POLLIN;
const POLLPRI = libc::POLLPRI;
const POLLOUT = libc::POLLOUT;
const POLLRDNORM = libc::POLLRDNORM;
const POLLWRNORM = libc::POLLWRNORM;
const POLLRDBAND = libc::POLLRDBAND;
const POLLWRBAND = libc::POLLWRBAND;
const POLLERR = libc::POLLERR;
const POLLHUP = libc::POLLHUP;
const POLLNVAL = libc::POLLNVAL;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PollFd(libc::pollfd);
impl PollFd {
pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self {
Self(libc::pollfd {
fd,
events: events.bits(),
revents: PollFlags::empty().bits(),
})
}
pub fn revents(self) -> Option<PollFlags> {
PollFlags::from_bits(self.0.revents)
}
}
pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result<usize, std::io::Error> {
let nready = unsafe {
libc::poll(
fds.as_mut_ptr() as *mut libc::pollfd,
fds.len() as libc::nfds_t,
timeout,
)
};
if nready == -1 {
Err(std::io::Error::last_os_error())
} else {
// When poll doesn't fail, its return value is a non-negative int, which will
// always be convertable to usize, so we can unwrap() here.
Ok(nready.try_into().unwrap())
}
}
}

View File

@@ -0,0 +1,256 @@
use anyhow::Context;
use std::ops::Deref;
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use wasi_c2::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
},
Error, ErrorExt,
};
pub struct SyncSched {}
impl SyncSched {
pub fn new() -> Self {
Self {}
}
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
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>() => {
stdin_read_subs.push(r);
}
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!(),
}
}
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());
ready = true;
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !ready {
if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
thread::sleep(duration);
}
}
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
thread::yield_now();
Ok(())
}
}
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
}
}
enum PollState {
Ready,
NotReady, // Not ready, but did not wait
TimedOut, // Not ready, waited until timeout
Error(std::io::Error),
}
enum WaitMode {
Timeout(Duration),
Infinite,
Immediate,
}
struct StdinPoll {
request_tx: Sender<()>,
notify_rx: Receiver<PollState>,
}
lazy_static::lazy_static! {
static ref STDIN_POLL: Mutex<StdinPoll> = StdinPoll::new();
}
impl StdinPoll {
pub fn new() -> Mutex<Self> {
let (request_tx, request_rx) = mpsc::channel();
let (notify_tx, notify_rx) = mpsc::channel();
thread::spawn(move || Self::event_loop(request_rx, notify_tx));
Mutex::new(StdinPoll {
request_tx,
notify_rx,
})
}
// This function should not be used directly.
// Correctness of this function crucially depends on the fact that
// mpsc::Receiver is !Sync.
fn poll(&self, wait_mode: WaitMode) -> Result<PollState, Error> {
match self.notify_rx.try_recv() {
// Clean up possibly unread result from previous poll.
Ok(_) | Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => {
return Err(Error::trap("StdinPoll notify_rx channel closed"))
}
}
// Notify the worker thread to poll stdin
self.request_tx
.send(())
.context("request_tx channel closed")?;
// Wait for the worker thread to send a readiness notification
match wait_mode {
WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {
Ok(r) => Ok(r),
Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),
Err(RecvTimeoutError::Disconnected) => {
Err(Error::trap("StdinPoll notify_rx channel closed"))
}
},
WaitMode::Infinite => self
.notify_rx
.recv()
.context("StdinPoll notify_rx channel closed"),
WaitMode::Immediate => match self.notify_rx.try_recv() {
Ok(r) => Ok(r),
Err(TryRecvError::Empty) => Ok(PollState::NotReady),
Err(TryRecvError::Disconnected) => {
Err(Error::trap("StdinPoll notify_rx channel closed"))
}
},
}
}
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
use std::io::BufRead;
loop {
// Wait on a request:
request_rx.recv().expect("request_rx channel");
// Wait for data to appear in stdin. If fill_buf returns any slice, it means
// that either:
// (a) there is some data in stdin, if non-empty,
// (b) EOF was recieved, if its empty
// Linux returns `POLLIN` in both cases, so we imitate this behavior.
let resp = match std::io::stdin().lock().fill_buf() {
Ok(_) => PollState::Ready,
Err(e) => PollState::Error(e),
};
// Notify about data in stdin. If the read on this channel has timed out, the
// next poller will have to clean the channel.
notify_tx.send(resp).expect("notify_tx channel");
}
}
}

View File

@@ -0,0 +1,217 @@
use crate::file::convert_systimespec;
use fs_set_times::SetTimes;
use std::any::Any;
use std::convert::TryInto;
use std::io;
use std::io::{Read, Write};
use system_interface::{fs::Advice, io::ReadReady};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use unsafe_io::AsUnsafeFile;
use wasi_c2::{
file::{FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt,
};
pub struct Stdin(std::io::Stdin);
pub fn stdin() -> Stdin {
Stdin(std::io::stdin())
}
impl WasiFile for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
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()?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
mtim: meta.modified().ok(),
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> 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> {
Err(Error::seek_pipe())
}
fn write_vectored(&self, _bufs: &[io::IoSlice]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn set_times(
&self,
atime: Option<wasi_c2::SystemTimeSpec>,
mtime: Option<wasi_c2::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(self.0.num_ready_bytes()?)
}
}
#[cfg(windows)]
impl AsRawHandle for Stdin {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
impl AsRawFd for Stdin {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
macro_rules! wasi_file_write_impl {
($ty:ty) => {
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Unknown)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
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()?,
nlink: 0,
size: meta.len(),
atim: meta.accessed().ok(),
mtim: meta.modified().ok(),
ctim: meta.created().ok(),
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
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(
&self,
_bufs: &mut [io::IoSliceMut],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> 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> {
Err(Error::seek_pipe())
}
fn seek(&self, _pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::seek_pipe())
}
fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<wasi_c2::SystemTimeSpec>,
mtime: Option<wasi_c2::SystemTimeSpec>,
) -> Result<(), Error> {
self.0
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
Ok(())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}
#[cfg(windows)]
impl AsRawHandle for $ty {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
#[cfg(unix)]
impl AsRawFd for $ty {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
};
}
pub struct Stdout(std::io::Stdout);
pub fn stdout() -> Stdout {
Stdout(std::io::stdout())
}
wasi_file_write_impl!(Stdout);
pub struct Stderr(std::io::Stderr);
pub fn stderr() -> Stderr {
Stderr(std::io::stderr())
}
wasi_file_write_impl!(Stderr);

View File

@@ -0,0 +1,22 @@
use cap_std::time::{Duration, Instant, SystemTime};
pub enum SystemTimeSpec {
SymbolicNow,
Absolute(SystemTime),
}
pub trait WasiSystemClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> SystemTime;
}
pub trait WasiMonotonicClock {
fn resolution(&self) -> Duration;
fn now(&self, precision: Duration) -> Instant;
}
pub struct WasiClocks {
pub system: Box<dyn WasiSystemClock>,
pub monotonic: Box<dyn WasiMonotonicClock>,
pub creation_time: cap_std::time::Instant,
}

View File

@@ -0,0 +1,122 @@
use crate::clocks::WasiClocks;
use crate::dir::{DirCaps, DirEntry, WasiDir};
use crate::file::{FileCaps, FileEntry, WasiFile};
use crate::sched::WasiSched;
use crate::string_array::{StringArray, StringArrayError};
use crate::table::Table;
use crate::Error;
use cap_rand::RngCore;
use std::cell::{RefCell, RefMut};
use std::path::{Path, PathBuf};
use std::rc::Rc;
pub struct WasiCtx {
pub args: StringArray,
pub env: StringArray,
pub random: RefCell<Box<dyn RngCore>>,
pub clocks: WasiClocks,
pub sched: Box<dyn WasiSched>,
pub table: Rc<RefCell<Table>>,
}
impl WasiCtx {
pub fn builder(
random: RefCell<Box<dyn RngCore>>,
clocks: WasiClocks,
sched: Box<dyn WasiSched>,
table: Rc<RefCell<Table>>,
) -> WasiCtxBuilder {
WasiCtxBuilder(WasiCtx {
args: StringArray::new(),
env: StringArray::new(),
random,
clocks,
sched,
table,
})
}
pub fn insert_file(&self, fd: u32, file: Box<dyn WasiFile>, caps: FileCaps) {
self.table()
.insert_at(fd, Box::new(FileEntry::new(caps, file)));
}
pub fn insert_dir(
&self,
fd: u32,
dir: Box<dyn WasiDir>,
caps: DirCaps,
file_caps: FileCaps,
path: PathBuf,
) {
self.table().insert_at(
fd,
Box::new(DirEntry::new(caps, file_caps, Some(path), dir)),
);
}
pub fn table(&self) -> RefMut<Table> {
self.table.borrow_mut()
}
}
pub struct WasiCtxBuilder(WasiCtx);
impl WasiCtxBuilder {
pub fn build(self) -> Result<WasiCtx, Error> {
Ok(self.0)
}
pub fn arg(mut self, arg: &str) -> Result<Self, StringArrayError> {
self.0.args.push(arg.to_owned())?;
Ok(self)
}
pub fn env(mut self, var: &str, value: &str) -> Result<Self, StringArrayError> {
self.0.env.push(format!("{}={}", var, value))?;
Ok(self)
}
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
0,
f,
FileCaps::READ | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is read-only
);
self
}
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
1,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
self.0.insert_file(
2,
f,
FileCaps::WRITE | FileCaps::POLL_READWRITE, // XXX fixme: more rights are ok, but this is append only
);
self
}
pub fn preopened_dir(
self,
dir: Box<dyn WasiDir>,
path: impl AsRef<Path>,
) -> Result<Self, Error> {
let caps = DirCaps::all();
let file_caps = FileCaps::all();
self.0.table().push(Box::new(DirEntry::new(
caps,
file_caps,
Some(path.as_ref().to_owned()),
dir,
)))?;
Ok(self)
}
}

View File

@@ -0,0 +1,182 @@
use crate::file::{FdFlags, FileCaps, FileType, Filestat, OFlags, WasiFile};
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::Ref;
use std::ops::Deref;
use std::path::PathBuf;
pub trait WasiDir {
fn as_any(&self) -> &dyn Any;
fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
caps: FileCaps,
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(
&self,
cursor: ReaddirCursor,
) -> Result<Box<dyn Iterator<Item = Result<(ReaddirEntity, String), 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(
&self,
path: &str,
target_dir: &dyn WasiDir,
target_path: &str,
) -> Result<(), Error>;
fn set_times(
&self,
path: &str,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
follow_symlinks: bool,
) -> Result<(), Error>;
}
pub(crate) struct DirEntry {
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>, // precondition: PathBuf is valid unicode
dir: Box<dyn WasiDir>,
}
impl DirEntry {
pub fn new(
caps: DirCaps,
file_caps: FileCaps,
preopen_path: Option<PathBuf>,
dir: Box<dyn WasiDir>,
) -> Self {
DirEntry {
caps,
file_caps,
preopen_path,
dir,
}
}
pub fn capable_of_dir(&self, caps: DirCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn capable_of_file(&self, caps: FileCaps) -> Result<(), Error> {
if self.file_caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable()
.context(format!("desired {:?}, has {:?}", caps, self.file_caps)))
}
}
pub fn drop_caps_to(&mut self, caps: DirCaps, file_caps: FileCaps) -> Result<(), Error> {
self.capable_of_dir(caps)?;
self.capable_of_file(file_caps)?;
self.caps = caps;
self.file_caps = file_caps;
Ok(())
}
pub fn child_dir_caps(&self, desired_caps: DirCaps) -> DirCaps {
self.caps & desired_caps
}
pub fn child_file_caps(&self, desired_caps: FileCaps) -> FileCaps {
self.file_caps & desired_caps
}
pub fn get_dir_fdstat(&self) -> DirFdStat {
DirFdStat {
dir_caps: self.caps,
file_caps: self.file_caps,
}
}
pub fn preopen_path(&self) -> &Option<PathBuf> {
&self.preopen_path
}
}
pub trait DirEntryExt<'a> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error>;
}
impl<'a> DirEntryExt<'a> for Ref<'a, DirEntry> {
fn get_cap(self, caps: DirCaps) -> Result<Ref<'a, dyn WasiDir>, Error> {
self.capable_of_dir(caps)?;
Ok(Ref::map(self, |r| r.dir.deref()))
}
}
bitflags! {
pub struct DirCaps: u32 {
const CREATE_DIRECTORY = 0b1;
const CREATE_FILE = 0b10;
const LINK_SOURCE = 0b100;
const LINK_TARGET = 0b1000;
const OPEN = 0b10000;
const READDIR = 0b100000;
const READLINK = 0b1000000;
const RENAME_SOURCE = 0b10000000;
const RENAME_TARGET = 0b100000000;
const SYMLINK = 0b1000000000;
const REMOVE_DIRECTORY = 0b10000000000;
const UNLINK_FILE = 0b100000000000;
const PATH_FILESTAT_GET = 0b1000000000000;
const PATH_FILESTAT_SET_TIMES = 0b10000000000000;
const FILESTAT_GET = 0b100000000000000;
const FILESTAT_SET_TIMES = 0b1000000000000000;
}
}
#[derive(Debug, Clone)]
pub struct DirFdStat {
pub file_caps: FileCaps,
pub dir_caps: DirCaps,
}
pub(crate) trait TableDirExt {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error>;
fn is_preopen(&self, fd: u32) -> bool;
}
impl TableDirExt for crate::table::Table {
fn get_dir(&self, fd: u32) -> Result<Ref<DirEntry>, Error> {
self.get(fd)
}
fn is_preopen(&self, fd: u32) -> bool {
if self.is::<DirEntry>(fd) {
let dir_entry: std::cell::Ref<DirEntry> = self.get(fd).unwrap();
dir_entry.preopen_path.is_some()
} else {
false
}
}
}
pub struct ReaddirEntity {
pub next: ReaddirCursor,
pub inode: u64,
pub namelen: u32,
pub filetype: FileType,
}
#[derive(Debug, Copy, Clone)]
pub struct ReaddirCursor(u64);
impl From<u64> for ReaddirCursor {
fn from(c: u64) -> ReaddirCursor {
ReaddirCursor(c)
}
}
impl From<ReaddirCursor> for u64 {
fn from(c: ReaddirCursor) -> u64 {
c.0
}
}

View File

@@ -0,0 +1,110 @@
pub use anyhow::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
/// the crate. Not all values are represented presently.
#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
/// Errno::TooBig: Argument list too long
#[error("TooBig: Argument list too long")]
TooBig,
/// Errno::Badf: Bad file descriptor
#[error("Badf: Bad file descriptor")]
Badf,
/// Errno::Exist: File exists
#[error("Exist: File exists")]
Exist,
/// Errno::Ilseq: Illegal byte sequence
#[error("Ilseq: Illegal byte sequence")]
Ilseq,
/// Errno::Inval: Invalid argument
#[error("Inval: Invalid argument")]
Inval,
/// Errno::Io: I/O error
#[error("Io: I/O error")]
Io,
/// Errno::Nametoolong: Filename too long
#[error("Nametoolong: Filename too long")]
Nametoolong,
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
#[error("Notdir: Not a directory or a symbolic link to a directory")]
Notdir,
/// Errno::Notsup: Not supported, or operation not supported on socket.
#[error("Notsup: Not supported, or operation not supported on socket")]
Notsup,
/// Errno::Overflow: Value too large to be stored in data type.
#[error("Overflow: Value too large to be stored in data type")]
Overflow,
/// Errno::Range: Result too large
#[error("Range: Result too large")]
Range,
/// Errno::Spipe: Invalid seek
#[error("Spipe: Invalid seek")]
Spipe,
/// Errno::NotCapable: Not capable
#[error("Not capable")]
NotCapable,
}
pub trait ErrorExt {
fn trap(msg: impl Into<String>) -> Self;
fn too_big() -> Self;
fn badf() -> Self;
fn exist() -> Self;
fn illegal_byte_sequence() -> Self;
fn invalid_argument() -> Self;
fn io() -> Self;
fn name_too_long() -> Self;
fn not_dir() -> Self;
fn not_supported() -> Self;
fn overflow() -> Self;
fn range() -> Self;
fn seek_pipe() -> Self;
fn not_capable() -> Self;
}
impl ErrorExt for Error {
fn trap(msg: impl Into<String>) -> Self {
anyhow::anyhow!(msg.into())
}
fn too_big() -> Self {
ErrorKind::TooBig.into()
}
fn badf() -> Self {
ErrorKind::Badf.into()
}
fn exist() -> Self {
ErrorKind::Exist.into()
}
fn illegal_byte_sequence() -> Self {
ErrorKind::Ilseq.into()
}
fn invalid_argument() -> Self {
ErrorKind::Inval.into()
}
fn io() -> Self {
ErrorKind::Io.into()
}
fn name_too_long() -> Self {
ErrorKind::Nametoolong.into()
}
fn not_dir() -> Self {
ErrorKind::Notdir.into()
}
fn not_supported() -> Self {
ErrorKind::Notsup.into()
}
fn overflow() -> Self {
ErrorKind::Overflow.into()
}
fn range() -> Self {
ErrorKind::Range.into()
}
fn seek_pipe() -> Self {
ErrorKind::Spipe.into()
}
fn not_capable() -> Self {
ErrorKind::NotCapable.into()
}
}

View File

@@ -0,0 +1,172 @@
use crate::{Error, ErrorExt, SystemTimeSpec};
use bitflags::bitflags;
use std::any::Any;
use std::cell::{Ref, RefMut};
use std::ops::{Deref, DerefMut};
pub trait WasiFile {
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: system_interface::fs::Advice,
) -> Result<(), Error>; // file op
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op
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
}
#[derive(Debug, Copy, Clone)]
pub enum FileType {
Unknown,
BlockDevice,
CharacterDevice,
Directory,
RegularFile,
SocketDgram,
SocketStream,
SymbolicLink,
Pipe,
}
bitflags! {
pub struct FdFlags: u32 {
const APPEND = 0b1;
const DSYNC = 0b10;
const NONBLOCK = 0b100;
const RSYNC = 0b1000;
const SYNC = 0b10000;
}
}
bitflags! {
pub struct OFlags: u32 {
const CREATE = 0b1;
const DIRECTORY = 0b10;
const EXCLUSIVE = 0b100;
const TRUNCATE = 0b1000;
}
}
#[derive(Debug, Clone)]
pub struct Filestat {
pub device_id: u64,
pub inode: u64,
pub filetype: FileType,
pub nlink: u64,
pub size: u64, // this is a read field, the rest are file fields
pub atim: Option<std::time::SystemTime>,
pub mtim: Option<std::time::SystemTime>,
pub ctim: Option<std::time::SystemTime>,
}
pub(crate) trait TableFileExt {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error>;
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error>;
}
impl TableFileExt for crate::table::Table {
fn get_file(&self, fd: u32) -> Result<Ref<FileEntry>, Error> {
self.get(fd)
}
fn get_file_mut(&self, fd: u32) -> Result<RefMut<FileEntry>, Error> {
self.get_mut(fd)
}
}
pub(crate) struct FileEntry {
caps: FileCaps,
file: Box<dyn WasiFile>,
}
impl FileEntry {
pub fn new(caps: FileCaps, file: Box<dyn WasiFile>) -> Self {
FileEntry { caps, file }
}
pub fn capable_of(&self, caps: FileCaps) -> Result<(), Error> {
if self.caps.contains(caps) {
Ok(())
} else {
Err(Error::not_capable().context(format!("desired {:?}, has {:?}", caps, self.caps,)))
}
}
pub fn drop_caps_to(&mut self, caps: FileCaps) -> Result<(), Error> {
self.capable_of(caps)?;
self.caps = caps;
Ok(())
}
pub fn get_fdstat(&self) -> Result<FdStat, Error> {
Ok(FdStat {
filetype: self.file.get_filetype()?,
caps: self.caps,
flags: self.file.get_fdflags()?,
})
}
}
pub trait FileEntryExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryExt<'a> for Ref<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<Ref<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(Ref::map(self, |r| r.file.deref()))
}
}
pub trait FileEntryMutExt<'a> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error>;
}
impl<'a> FileEntryMutExt<'a> for RefMut<'a, FileEntry> {
fn get_cap(self, caps: FileCaps) -> Result<RefMut<'a, dyn WasiFile>, Error> {
self.capable_of(caps)?;
Ok(RefMut::map(self, |r| r.file.deref_mut()))
}
}
bitflags! {
pub struct FileCaps : u32 {
const DATASYNC = 0b1;
const READ = 0b10;
const SEEK = 0b100;
const FDSTAT_SET_FLAGS = 0b1000;
const SYNC = 0b10000;
const TELL = 0b100000;
const WRITE = 0b1000000;
const ADVISE = 0b10000000;
const ALLOCATE = 0b100000000;
const FILESTAT_GET = 0b1000000000;
const FILESTAT_SET_SIZE = 0b10000000000;
const FILESTAT_SET_TIMES = 0b100000000000;
const POLL_READWRITE = 0b1000000000000;
}
}
#[derive(Debug, Clone)]
pub struct FdStat {
pub filetype: FileType,
pub caps: FileCaps,
pub flags: FdFlags,
}

View File

@@ -0,0 +1,20 @@
#![cfg_attr(feature = "nightly", feature(windows_by_handle))]
pub mod clocks;
mod ctx;
pub mod dir;
mod error;
pub mod file;
pub mod pipe;
pub mod random;
pub mod sched;
pub mod snapshots;
mod string_array;
pub mod table;
pub use clocks::SystemTimeSpec;
pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use dir::{DirCaps, ReaddirCursor, ReaddirEntity, WasiDir};
pub use error::{Error, ErrorExt, ErrorKind};
pub use file::{FdFlags, FileCaps, Filestat, OFlags, WasiFile};
pub use string_array::StringArrayError;

View File

@@ -0,0 +1,306 @@
// This is mostly stubs
#![allow(unused_variables, dead_code)]
//! Virtual pipes.
//!
//! These types provide easy implementations of `WasiFile` that mimic much of the behavior of Unix
//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other
//! than OS files.
//!
//! Some convenience constructors are included for common backing types like `Vec<u8>` and `String`,
//! but the virtual pipes can be instantiated with any `Read` or `Write` type.
//!
use crate::{
file::{FdFlags, FileType, Filestat, WasiFile},
Error, ErrorExt, SystemTimeSpec,
};
use std::any::Any;
use std::convert::TryInto;
use std::io::{self, Read, Write};
use std::sync::{Arc, RwLock};
use system_interface::fs::Advice;
/// A virtual pipe read end.
///
/// A variety of `From` impls are provided so that common pipe types are easy to create. For example:
///
/// ```
/// # use wasi_c2::WasiCtx;
/// # use wasi_c2::virt::pipe::ReadPipe;
/// let stdin = ReadPipe::from("hello from stdin!");
/// let ctx = WasiCtx::builder().stdin(Box::new(stdin)).build();
/// ```
#[derive(Debug)]
pub struct ReadPipe<R: Read> {
reader: Arc<RwLock<R>>,
}
impl<R: Read> Clone for ReadPipe<R> {
fn clone(&self) -> Self {
Self {
reader: self.reader.clone(),
}
}
}
impl<R: Read> ReadPipe<R> {
/// Create a new pipe from a `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn new(r: R) -> Self {
Self::from_shared(Arc::new(RwLock::new(r)))
}
/// Create a new pipe from a shareable `Read` type.
///
/// All `Handle` read operations delegate to reading from this underlying reader.
pub fn from_shared(reader: Arc<RwLock<R>>) -> Self {
Self { reader }
}
/// Try to convert this `ReadPipe<R>` back to the underlying `R` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `R` exist.
pub fn try_into_inner(mut self) -> Result<R, Self> {
match Arc::try_unwrap(self.reader) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(reader) => {
self.reader = reader;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<R> {
RwLock::write(&self.reader).unwrap()
}
}
impl From<Vec<u8>> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: Vec<u8>) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&[u8]> for ReadPipe<io::Cursor<Vec<u8>>> {
fn from(r: &[u8]) -> Self {
Self::from(r.to_vec())
}
}
impl From<String> for ReadPipe<io::Cursor<String>> {
fn from(r: String) -> Self {
Self::new(io::Cursor::new(r))
}
}
impl From<&str> for ReadPipe<io::Cursor<String>> {
fn from(r: &str) -> Self {
Self::from(r.to_string())
}
}
impl<R: Read + Any> WasiFile for ReadPipe<R> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(()) // trivial: no implementation needed
}
fn sync(&self) -> Result<(), Error> {
Ok(()) // trivial
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::empty())
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Read :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> 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> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}
/// A virtual pipe write end.
///
/// ```
/// # use wasi_c2::WasiCtx;
/// # use wasi_c2::virt::pipe::WritePipe;
/// let stdout = WritePipe::new_in_memory();
/// let ctx = WasiCtx::builder().stdout(Box::new(stdout.clone())).build();
/// // use ctx in an instance, then make sure it is dropped:
/// drop(ctx);
/// let contents: Vec<u8> = stdout.try_into_inner().expect("sole remaining reference to WritePipe").into_inner();
/// println!("contents of stdout: {:?}", contents);
/// ```
#[derive(Debug)]
pub struct WritePipe<W: Write> {
writer: Arc<RwLock<W>>,
}
impl<W: Write> Clone for WritePipe<W> {
fn clone(&self) -> Self {
Self {
writer: self.writer.clone(),
}
}
}
impl<W: Write> WritePipe<W> {
/// Create a new pipe from a `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn new(w: W) -> Self {
Self::from_shared(Arc::new(RwLock::new(w)))
}
/// Create a new pipe from a shareable `Write` type.
///
/// All `Handle` write operations delegate to writing to this underlying writer.
pub fn from_shared(writer: Arc<RwLock<W>>) -> Self {
Self { writer }
}
/// Try to convert this `WritePipe<W>` back to the underlying `W` type.
///
/// This will fail with `Err(self)` if multiple references to the underlying `W` exist.
pub fn try_into_inner(mut self) -> Result<W, Self> {
match Arc::try_unwrap(self.writer) {
Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()),
Err(writer) => {
self.writer = writer;
Err(self)
}
}
}
fn borrow(&self) -> std::sync::RwLockWriteGuard<W> {
RwLock::write(&self.writer).unwrap()
}
}
impl WritePipe<io::Cursor<Vec<u8>>> {
/// Create a new writable virtual pipe backed by a `Vec<u8>` buffer.
pub fn new_in_memory() -> Self {
Self::new(io::Cursor::new(vec![]))
}
}
impl<W: Write + Any> WasiFile for WritePipe<W> {
fn as_any(&self) -> &dyn Any {
self
}
fn datasync(&self) -> Result<(), Error> {
Ok(())
}
fn sync(&self) -> Result<(), Error> {
Ok(())
}
fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype()?,
nlink: 0,
size: 0, // XXX no way to get a size out of a Write :(
atim: None,
mtim: None,
ctim: None,
})
}
fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
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(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result<u64, Error> {
Err(Error::badf())
}
fn write_vectored(&self, bufs: &[io::IoSlice]) -> 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> {
Err(Error::badf())
}
fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
fn set_times(
&self,
atime: Option<SystemTimeSpec>,
mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
}

View File

@@ -0,0 +1,46 @@
use cap_rand::RngCore;
/// Implement `WasiRandom` using a deterministic cycle of bytes.
pub struct Deterministic {
cycle: std::iter::Cycle<std::vec::IntoIter<u8>>,
}
impl Deterministic {
pub fn new(bytes: Vec<u8>) -> Self {
Deterministic {
cycle: bytes.into_iter().cycle(),
}
}
}
impl RngCore for Deterministic {
fn next_u32(&mut self) -> u32 {
todo!()
}
fn next_u64(&mut self) -> u64 {
todo!()
}
fn fill_bytes(&mut self, buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = self.cycle.next().expect("infinite sequence");
}
}
fn try_fill_bytes(&mut self, buf: &mut [u8]) -> Result<(), cap_rand::Error> {
self.fill_bytes(buf);
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deterministic() {
let mut det = Deterministic::new(vec![1, 2, 3, 4]);
let mut buf = vec![0; 1024];
det.try_fill_bytes(&mut buf).expect("get randomness");
for (ix, b) in buf.iter().enumerate() {
assert_eq!(*b, (ix % 4) as u8 + 1)
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use cap_std::time::{Duration, Instant};
use std::cell::Ref;
pub mod subscription;
use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult};
pub trait WasiSched {
fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>;
fn sched_yield(&self) -> Result<(), Error>;
}
pub struct Userdata(u64);
impl From<u64> for Userdata {
fn from(u: u64) -> Userdata {
Userdata(u)
}
}
impl From<Userdata> for u64 {
fn from(u: Userdata) -> u64 {
u.0
}
}
pub struct Poll<'a> {
subs: Vec<(Subscription<'a>, Userdata)>,
}
impl<'a> Poll<'a> {
pub fn new() -> Self {
Self { subs: Vec::new() }
}
pub fn subscribe_monotonic_clock(
&mut self,
clock: &'a dyn WasiMonotonicClock,
deadline: Instant,
precision: Duration,
ud: Userdata,
) {
self.subs.push((
Subscription::MonotonicClock(MonotonicClockSubscription {
clock,
deadline,
precision,
}),
ud,
));
}
pub fn subscribe_read(&mut self, file: Ref<'a, 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) {
self.subs
.push((Subscription::Write(RwSubscription::new(file)), ud));
}
pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> {
self.subs
.into_iter()
.filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud)))
.collect()
}
pub fn is_empty(&self) -> bool {
self.subs.is_empty()
}
pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> {
let mut subs = self
.subs
.iter()
.filter_map(|(s, _ud)| match s {
Subscription::MonotonicClock(t) => Some(t),
_ => None,
})
.collect::<Vec<&MonotonicClockSubscription<'a>>>();
subs.sort_by(|a, b| a.deadline.cmp(&b.deadline));
subs.into_iter().next() // First element is earliest
}
pub fn rw_subscriptions(&'a self) -> impl Iterator<Item = &Subscription<'a>> {
self.subs.iter().filter_map(|(s, _ud)| match s {
Subscription::Read { .. } | Subscription::Write { .. } => Some(s),
_ => None,
})
}
}

View File

@@ -0,0 +1,79 @@
use crate::clocks::WasiMonotonicClock;
use crate::file::WasiFile;
use crate::Error;
use bitflags::bitflags;
use cap_std::time::{Duration, Instant};
use std::cell::{Cell, Ref};
bitflags! {
pub struct RwEventFlags: u32 {
const HANGUP = 0b1;
}
}
pub struct RwSubscription<'a> {
pub file: Ref<'a, dyn WasiFile>,
status: Cell<Option<Result<(u64, RwEventFlags), Error>>>,
}
impl<'a> RwSubscription<'a> {
pub fn new(file: Ref<'a, dyn WasiFile>) -> Self {
Self {
file,
status: Cell::new(None),
}
}
pub fn complete(&self, size: u64, flags: RwEventFlags) {
self.status.set(Some(Ok((size, flags))))
}
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 struct MonotonicClockSubscription<'a> {
pub clock: &'a dyn WasiMonotonicClock,
pub deadline: Instant,
pub precision: Duration,
}
impl<'a> MonotonicClockSubscription<'a> {
pub fn now(&self) -> Instant {
self.clock.now(self.precision)
}
pub fn duration_until(&self) -> Option<Duration> {
self.deadline.checked_duration_since(self.now())
}
pub fn result(&self) -> Option<Result<(), Error>> {
if self.now().checked_duration_since(self.deadline).is_some() {
Some(Ok(()))
} else {
None
}
}
}
pub enum Subscription<'a> {
Read(RwSubscription<'a>),
Write(RwSubscription<'a>),
MonotonicClock(MonotonicClockSubscription<'a>),
}
pub enum SubscriptionResult {
Read(Result<(u64, RwEventFlags), Error>),
Write(Result<(u64, RwEventFlags), Error>),
MonotonicClock(Result<(), Error>),
}
impl SubscriptionResult {
pub fn from_subscription(s: Subscription) -> Option<SubscriptionResult> {
match s {
Subscription::Read(s) => s.result().map(SubscriptionResult::Read),
Subscription::Write(s) => s.result().map(SubscriptionResult::Write),
Subscription::MonotonicClock(s) => s.result().map(SubscriptionResult::MonotonicClock),
}
}
}

View File

@@ -0,0 +1 @@
pub mod preview_1;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
use crate::{Error, ErrorExt};
use wiggle::GuestPtr;
pub struct StringArray {
elems: Vec<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum StringArrayError {
#[error("Number of elements exceeds 2^32")]
NumberElements,
#[error("Element size exceeds 2^32")]
ElementSize,
#[error("Cumulative size exceeds 2^32")]
CumulativeSize,
}
impl StringArray {
pub fn new() -> Self {
StringArray { elems: Vec::new() }
}
pub fn push(&mut self, elem: String) -> Result<(), StringArrayError> {
if self.elems.len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::NumberElements);
}
if elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::ElementSize);
}
if self.cumulative_size() as usize + elem.as_bytes().len() + 1 > std::u32::MAX as usize {
return Err(StringArrayError::CumulativeSize);
}
self.elems.push(elem);
Ok(())
}
pub fn number_elements(&self) -> u32 {
self.elems.len() as u32
}
pub fn cumulative_size(&self) -> u32 {
self.elems
.iter()
.map(|e| e.as_bytes().len() + 1)
.sum::<usize>() as u32
}
pub fn write_to_guest<'a>(
&self,
buffer: &GuestPtr<'a, u8>,
element_heads: &GuestPtr<'a, GuestPtr<'a, u8>>,
) -> Result<(), Error> {
let element_heads = element_heads.as_array(self.number_elements());
let buffer = buffer.as_array(self.cumulative_size());
let mut cursor = 0;
for (elem, head) in self.elems.iter().zip(element_heads.iter()) {
let bytes = elem.as_bytes();
let len = bytes.len() as u32;
{
let elem_buffer = buffer
.get_range(cursor..(cursor + len))
.ok_or(Error::invalid_argument())?; // Elements don't fit in buffer provided
elem_buffer.copy_from_slice(bytes)?;
}
buffer
.get(cursor + len)
.ok_or(Error::invalid_argument())?
.write(0)?; // 0 terminate
head?.write(buffer.get(cursor).expect("already validated"))?;
cursor += len + 1;
}
Ok(())
}
}

View File

@@ -0,0 +1,105 @@
use crate::{Error, ErrorExt};
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
pub struct Table {
map: HashMap<u32, RefCell<Box<dyn Any>>>,
next_key: u32,
}
impl Table {
pub fn new() -> Self {
Table {
map: HashMap::new(),
next_key: 3, // 0, 1 and 2 are reserved for stdio
}
}
pub fn insert_at(&mut self, key: u32, a: Box<dyn Any>) {
self.map.insert(key, RefCell::new(a));
}
pub fn push(&mut self, a: Box<dyn Any>) -> Result<u32, Error> {
loop {
let key = self.next_key;
// XXX this is not correct. The table may still have empty entries, but our
// linear search strategy is quite bad
self.next_key = self
.next_key
.checked_add(1)
.ok_or_else(|| Error::trap("out of keys in table"))?;
if self.map.contains_key(&key) {
continue;
}
self.map.insert(key, RefCell::new(a));
return Ok(key);
}
}
pub fn contains_key(&self, key: u32) -> bool {
self.map.contains_key(&key)
}
pub fn is<T: Any + Sized>(&self, key: u32) -> bool {
if let Some(refcell) = self.map.get(&key) {
if let Ok(refmut) = refcell.try_borrow_mut() {
refmut.is::<T>()
} else {
false
}
} else {
false
}
}
pub fn get<T: Any + Sized>(&self, key: u32) -> Result<Ref<T>, Error> {
if let Some(refcell) = self.map.get(&key) {
if let Ok(r) = refcell.try_borrow() {
if r.is::<T>() {
Ok(Ref::map(r, |r| r.downcast_ref::<T>().unwrap()))
} else {
Err(Error::badf().context("element is a different type"))
}
} else {
Err(Error::trap("table get of mutably borrowed element"))
}
} else {
Err(Error::badf().context("key not in table"))
}
}
pub fn get_mut<T: Any + Sized>(&self, key: u32) -> Result<RefMut<T>, Error> {
if let Some(refcell) = self.map.get(&key) {
if let Ok(r) = refcell.try_borrow_mut() {
if r.is::<T>() {
Ok(RefMut::map(r, |r| r.downcast_mut::<T>().unwrap()))
} else {
Err(Error::badf().context("element is a different type"))
}
} else {
Err(Error::trap("table get_mut of borrowed element"))
}
} else {
Err(Error::badf().context("key not in table"))
}
}
pub fn delete(&mut self, key: u32) -> Option<Box<dyn Any>> {
self.map.remove(&key).map(|rc| RefCell::into_inner(rc))
}
pub fn update_in_place<T, F>(&mut self, key: u32, f: F) -> Result<(), Error>
where
T: Any + Sized,
F: FnOnce(T) -> Result<T, Error>,
{
let entry = self.delete(key).ok_or(Error::badf())?;
let downcast = entry
.downcast::<T>()
.map_err(|_| Error::exist().context("element is a different type"))?;
let new = f(*downcast)?;
self.insert_at(key, Box::new(new));
Ok(())
}
}

View File

@@ -0,0 +1,21 @@
[package]
name = "wasi-c2-wasmtime"
version = "0.22.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", "build.rs"]
build = "build.rs"
publish = false
[dependencies]
wasi-c2 = { path = "../" }
wiggle = { path = "../../wiggle", default-features = false, version = "0.22.0" }
wasmtime-wiggle = { path = "../../wiggle/wasmtime", default-features = false, version = "0.22.0" }
wasmtime = { path = "../../wasmtime" }
anyhow = "1.0"

View File

@@ -0,0 +1,6 @@
fn main() {
// wasi-c2's links & build.rs ensure this variable points to the wasi root:
let wasi_root = std::env::var("DEP_WASI_C2_19_WASI").unwrap();
// Make it available as WASI_ROOT:
println!("cargo:rustc-env=WASI_ROOT={}", wasi_root);
}

View File

@@ -0,0 +1,29 @@
pub use wasi_c2::{
Error, FdFlags, FileCaps, Filestat, OFlags, ReaddirCursor, ReaddirEntity, SystemTimeSpec,
WasiCtx, WasiCtxBuilder, WasiDir, WasiFile,
};
// Defines a `struct Wasi` with member fields and appropriate APIs for dealing
// with all the various WASI exports.
wasmtime_wiggle::wasmtime_integration!({
// The wiggle code to integrate with lives here:
target: wasi_c2::snapshots::preview_1,
// This must be the same witx document as used above. This should be ensured by
// the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`.
witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"],
// This must be the same ctx type as used for the target:
ctx: WasiCtx,
// This macro will emit a struct to represent the instance,
// with this name and docs:
modules: { wasi_snapshot_preview1 =>
{ name: Wasi,
docs: "An instantiated instance of the wasi exports.
This represents a wasi module which can be used to instantiate other wasm
modules. This structure exports all that various fields of the wasi instance
as fields which can be used to implement your own instantiation logic, if
necessary. Additionally [`Wasi::get_export`] can be used to do name-based
resolution.",
},
},
});