Merge pull request #2832 from bytecodealliance/pch/wiggle_sync_shimming

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

View File

@@ -208,6 +208,7 @@ jobs:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
build: [stable, beta, nightly, windows, macos]
include:
@@ -382,6 +383,7 @@ jobs:
name: Build wasmtime
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- build: x86_64-linux

121
Cargo.lock generated
View File

@@ -261,6 +261,12 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cap-fs-ext"
version = "0.13.9"
@@ -315,6 +321,17 @@ dependencies = [
"unsafe-io",
]
[[package]]
name = "cap-tempfile"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d2f6f45ddb06ff26f4cf2ba9838d5826d52e1a5f6b321d71f114bb38cf34a57"
dependencies = [
"cap-std",
"rand 0.8.3",
"uuid",
]
[[package]]
name = "cap-time-ext"
version = "0.13.9"
@@ -1073,6 +1090,10 @@ dependencies = [
name = "example-fib-debug-wasm"
version = "0.0.0"
[[package]]
name = "example-tokio-wasm"
version = "0.0.0"
[[package]]
name = "example-wasi-wasm"
version = "0.0.0"
@@ -1551,6 +1572,28 @@ dependencies = [
"autocfg 1.0.1",
]
[[package]]
name = "mio"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "more-asserts"
version = "0.2.1"
@@ -1580,6 +1623,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
@@ -2732,6 +2784,7 @@ dependencies = [
"pretty_env_logger",
"target-lexicon",
"tempfile",
"tokio",
"wasi-cap-std-sync",
"wasi-common",
"wasmtime",
@@ -2787,6 +2840,33 @@ dependencies = [
"winapi",
]
[[package]]
name = "tokio"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
dependencies = [
"autocfg 1.0.1",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.8"
@@ -2938,9 +3018,9 @@ dependencies = [
[[package]]
name = "unsafe-io"
version = "0.6.2"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0301dd0f2c21baed606faa2717fbfbb1a68b7e289ea29b40bc21a16f5ae9f5aa"
checksum = "fe39acfe60d3754452ea6881613c3240100b23ffd94a627c138863f8cd314b1b"
dependencies = [
"rustc_version",
"winapi",
@@ -2970,6 +3050,15 @@ dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.2",
]
[[package]]
name = "vec_map"
version = "0.8.2"
@@ -3025,6 +3114,7 @@ name = "wasi-cap-std-sync"
version = "0.26.0"
dependencies = [
"anyhow",
"async-trait",
"bitflags",
"cap-fs-ext",
"cap-rand",
@@ -3085,6 +3175,31 @@ dependencies = [
"zeroize",
]
[[package]]
name = "wasi-tokio"
version = "0.26.0"
dependencies = [
"anyhow",
"bitflags",
"cap-fs-ext",
"cap-std",
"cap-tempfile",
"cap-time-ext",
"fs-set-times",
"lazy_static",
"libc",
"posish",
"system-interface",
"tempfile",
"tokio",
"tracing",
"unsafe-io",
"wasi-cap-std-sync",
"wasi-common",
"wiggle",
"winapi",
]
[[package]]
name = "wasm-encoder"
version = "0.4.1"
@@ -3263,6 +3378,7 @@ dependencies = [
"target-lexicon",
"tempfile",
"test-programs",
"tokio",
"tracing-subscriber",
"wasmparser",
"wasmtime",
@@ -3491,6 +3607,7 @@ dependencies = [
"anyhow",
"wasi-cap-std-sync",
"wasi-common",
"wasi-tokio",
"wasmtime",
"wasmtime-wiggle",
"wiggle",

View File

@@ -54,6 +54,7 @@ tempfile = "3.1.0"
test-programs = { path = "crates/test-programs" }
wasmtime-fuzzing = { path = "crates/fuzzing" }
wasmtime-runtime = { path = "crates/runtime" }
tokio = { version = "1.5.0", features = ["rt", "time", "macros", "rt-multi-thread"] }
tracing-subscriber = "0.2.16"
wast = "35.0.0"
@@ -78,8 +79,10 @@ members = [
"crates/wiggle/wasmtime",
"crates/wasi-common",
"crates/wasi-common/cap-std-sync",
"crates/wasi-common/tokio",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
"examples/tokio/wasm",
"fuzz",
]
@@ -107,5 +110,9 @@ maintenance = { status = "actively-developed" }
name = "host_segfault"
harness = false
[[example]]
name = "tokio"
required-features = ["wasmtime-wasi/tokio"]
[profile.dev.package.backtrace]
debug = false # FIXME(#1813)

View File

@@ -83,9 +83,8 @@ use std::env;
use std::os::raw::{c_int, c_void};
use std::path::Path;
use std::slice;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
use wasmtime_wasi::Wasi;
use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder};
pub type ExitCode = c_int;
pub const OK: ExitCode = 0;

View File

@@ -12,9 +12,10 @@ use std::slice;
use std::str;
use wasmtime::{Extern, Linker, Trap};
use wasmtime_wasi::{
snapshots::preview_0::Wasi as WasiSnapshot0,
snapshots::preview_1::Wasi as WasiPreview1,
sync::{Dir, WasiCtxBuilder},
sync::{
snapshots::preview_0::Wasi as WasiSnapshot0, snapshots::preview_1::Wasi as WasiPreview1,
Dir, WasiCtxBuilder,
},
WasiCtx,
};

View File

@@ -45,10 +45,13 @@ fn main() -> anyhow::Result<()> {
.arg(target))?;
}
println!("======== Rust example `{}` ============", example);
run(Command::new("cargo")
.arg("run")
.arg("--example")
.arg(&example))?;
let mut cargo_cmd = Command::new("cargo");
cargo_cmd.arg("run").arg("--example").arg(&example);
if example.contains("tokio") {
cargo_cmd.arg("--features").arg("wasmtime-wasi/tokio");
}
run(&mut cargo_cmd)?;
println!("======== C/C++ example `{}` ============", example);
for extension in ["c", "cc"].iter() {

View File

@@ -14,7 +14,7 @@ cfg-if = "1.0"
wasi-common = { path = "../wasi-common", version = "0.26.0" }
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0" }
wasmtime = { path = "../wasmtime", version = "0.26.0" }
wasmtime-wasi = { path = "../wasi", version = "0.26.0" }
wasmtime-wasi = { path = "../wasi", version = "0.26.0", features = ["tokio"] }
target-lexicon = "0.12.0"
pretty_env_logger = "0.4.0"
tempfile = "3.1.0"
@@ -22,6 +22,7 @@ os_pipe = "0.9"
anyhow = "1.0.19"
wat = "1.0.37"
cap-std = "0.13"
tokio = { version = "1.5.0", features = ["rt-multi-thread"] }
[features]
test_programs = []

View File

@@ -40,7 +40,9 @@ mod wasi_tests {
File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file");
build_tests("wasi-tests", &out_dir).expect("building tests");
test_directory(&mut out, "wasi-cap-std-sync", "cap_std_sync", &out_dir)
.expect("generating tests");
.expect("generating wasi-cap-std-sync tests");
test_directory(&mut out, "wasi-tokio", "tokio", &out_dir)
.expect("generating wasi-tokio tests");
}
fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
@@ -173,6 +175,7 @@ mod wasi_tests {
match testsuite {
"wasi-cap-std-sync" => cap_std_sync_ignore(name),
"wasi-virtfs" => virtfs_ignore(name),
"wasi-tokio" => tokio_ignore(name),
_ => panic!("unknown test suite: {}", testsuite),
}
}
@@ -200,6 +203,10 @@ mod wasi_tests {
.contains(&name)
}
/// Tokio should support the same things as cap_std_sync
fn tokio_ignore(name: &str) -> bool {
cap_std_sync_ignore(name)
}
/// Virtfs barely works at all and is not suitable for any purpose
fn virtfs_ignore(name: &str) -> bool {
[
@@ -260,7 +267,7 @@ mod wasi_tests {
/// Mark tests which require inheriting parent process stdio
fn inherit_stdio(testsuite: &str, name: &str) -> bool {
match testsuite {
"wasi-cap-std-sync" => match name {
"wasi-cap-std-sync" | "wasi-tokio" => match name {
"poll_oneoff_stdio" => true,
_ => false,
},

View File

@@ -1,10 +1,26 @@
use anyhow::Context;
use std::path::Path;
use wasi_cap_std_sync::WasiCtxBuilder;
use wasi_common::pipe::WritePipe;
use wasmtime::{Linker, Module, Store};
use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder};
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> {
run(data, bin_name, workspace, false)
}
pub fn instantiate_inherit_stdio(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
) -> anyhow::Result<()> {
run(data, bin_name, workspace, true)
}
fn run(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
inherit_stdio: bool,
) -> anyhow::Result<()> {
let stdout = WritePipe::new_in_memory();
let stderr = WritePipe::new_in_memory();
@@ -15,41 +31,29 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any
// Additionally register any preopened directories if we have them.
let mut builder = WasiCtxBuilder::new();
builder = builder
.arg(bin_name)?
.arg(".")?
.stdout(Box::new(stdout.clone()))
.stderr(Box::new(stderr.clone()));
if inherit_stdio {
builder = builder.inherit_stdio();
} else {
builder = builder
.stdout(Box::new(stdout.clone()))
.stderr(Box::new(stderr.clone()));
}
builder = builder.arg(bin_name)?.arg(".")?;
if let Some(workspace) = workspace {
println!("preopen: {:?}", workspace);
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?;
builder = builder.preopened_dir(preopen_dir, ".")?;
}
#[cfg(windows)]
{
builder = builder
.env("ERRNO_MODE_WINDOWS", "1")?
.env("NO_DANGLING_FILESYSTEM", "1")?
.env("NO_FD_ALLOCATE", "1")?
.env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")?
}
#[cfg(all(unix, not(target_os = "macos")))]
{
builder = builder.env("ERRNO_MODE_UNIX", "1")?;
}
#[cfg(target_os = "macos")]
{
builder = builder
.env("ERRNO_MODE_MACOS", "1")?
.env("NO_FD_ALLOCATE", "1")?;
for (var, val) in super::test_suite_environment() {
builder = builder.env(var, val)?;
}
// cap-std-sync does not yet support the sync family of fdflags
builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?;
let wasi = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let wasi = Wasi::new(&store, builder.build()?);
let mut linker = Linker::new(&store);
@@ -82,41 +86,3 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any
}
}
}
pub fn instantiate_inherit_stdio(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
) -> anyhow::Result<()> {
let r = {
let store = Store::default();
// Create our wasi context.
// Additionally register any preopened directories if we have them.
let mut builder = WasiCtxBuilder::new();
builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio();
if let Some(workspace) = workspace {
println!("preopen: {:?}", workspace);
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?;
builder = builder.preopened_dir(preopen_dir, ".")?;
}
let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?);
let mut linker = Linker::new(&store);
snapshot1.add_to_linker(&mut linker)?;
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
let instance = linker.instantiate(&module)?;
let start = instance.get_typed_func::<(), ()>("_start")?;
start.call(()).map_err(anyhow::Error::from)
};
match r {
Ok(()) => Ok(()),
Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))),
}
}

View File

@@ -1 +1,34 @@
pub mod cap_std_sync;
pub mod tokio;
// Configure the test suite environment.
// Test programs use these environment variables to determine what behavior
// is expected: different errnos are expected on windows, mac, and other unixes,
// and other filesystem operations are supported or not.
pub fn test_suite_environment() -> &'static [(&'static str, &'static str)] {
#[cfg(windows)]
{
&[
("ERRNO_MODE_WINDOWS", "1"),
// Windows does not support dangling links or symlinks in the filesystem.
("NO_DANGLING_FILESYSTEM", "1"),
// Windows does not support fd_allocate.
("NO_FD_ALLOCATE", "1"),
// Windows does not support renaming a directory to an empty directory -
// empty directory must be deleted.
("NO_RENAME_DIR_TO_EMPTY_DIR", "1"),
]
}
#[cfg(all(unix, not(target_os = "macos")))]
{
&[("ERRNO_MODE_UNIX", "1")]
}
#[cfg(target_os = "macos")]
{
&[
("ERRNO_MODE_MACOS", "1"),
// MacOS does not support fd_allocate
("NO_FD_ALLOCATE", "1"),
]
}
}

View File

@@ -0,0 +1,96 @@
use anyhow::Context;
use std::path::Path;
use wasi_common::pipe::WritePipe;
use wasmtime::{Config, Engine, Linker, Module, Store};
use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder};
pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> {
run(data, bin_name, workspace, false)
}
pub fn instantiate_inherit_stdio(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
) -> anyhow::Result<()> {
run(data, bin_name, workspace, true)
}
fn run(
data: &[u8],
bin_name: &str,
workspace: Option<&Path>,
inherit_stdio: bool,
) -> anyhow::Result<()> {
let stdout = WritePipe::new_in_memory();
let stdout_ = stdout.clone();
let stderr = WritePipe::new_in_memory();
let stderr_ = stderr.clone();
let r = tokio::runtime::Runtime::new()
.expect("create runtime")
.block_on(async move {
let mut config = Config::new();
config.async_support(true);
Wasi::add_to_config(&mut config);
let engine = Engine::new(&config)?;
let store = Store::new(&engine);
// Create our wasi context.
let mut builder = WasiCtxBuilder::new();
if inherit_stdio {
builder = builder.inherit_stdio();
} else {
builder = builder
.stdout(Box::new(stdout_.clone()))
.stderr(Box::new(stderr_.clone()));
}
builder = builder.arg(bin_name)?.arg(".")?;
if let Some(workspace) = workspace {
println!("preopen: {:?}", workspace);
let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?;
builder = builder.preopened_dir(preopen_dir, ".")?;
}
for (var, val) in super::test_suite_environment() {
builder = builder.env(var, val)?;
}
// tokio does not yet support the sync family of fdflags, because cap-std-sync
// does not.
builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?;
Wasi::set_context(&store, builder.build()?)
.map_err(|_| anyhow::anyhow!("wasi set_context failed"))?;
let module =
Module::new(store.engine(), &data).context("failed to create wasm module")?;
let linker = Linker::new(&store);
let instance = linker.instantiate_async(&module).await?;
let start = instance.get_typed_func::<(), ()>("_start")?;
start.call_async(()).await.map_err(anyhow::Error::from)
});
match r {
Ok(()) => Ok(()),
Err(trap) => {
let stdout = stdout
.try_into_inner()
.expect("sole ref to stdout")
.into_inner();
if !stdout.is_empty() {
println!("guest stdout:\n{}\n===", String::from_utf8_lossy(&stdout));
}
let stderr = stderr
.try_into_inner()
.expect("sole ref to stderr")
.into_inner();
if !stderr.is_empty() {
println!("guest stderr:\n{}\n===", String::from_utf8_lossy(&stderr));
}
Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,)))
}
}
}

View File

@@ -55,7 +55,10 @@ unsafe fn test_timeout() {
event.userdata, CLOCK_ID,
"the event.userdata should contain clock_id specified by the user"
);
assert!(after - before >= timeout, "poll_oneoff should sleep for the specified interval");
assert!(
after - before >= timeout,
"poll_oneoff should sleep for the specified interval"
);
}
// Like test_timeout, but uses `CLOCKID_REALTIME`, as WASI libc's sleep
@@ -90,20 +93,22 @@ unsafe fn test_sleep() {
event.userdata, CLOCK_ID,
"the event.userdata should contain clock_id specified by the user"
);
assert!(after - before >= timeout, "poll_oneoff should sleep for the specified interval");
assert!(
after - before >= timeout,
"poll_oneoff should sleep for the specified interval"
);
}
unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
let fd_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: fd,
};
unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_code: wasi::Errno) {
let r#in = [
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_READ,
u: wasi::SubscriptionUU {
fd_read: fd_readwrite,
fd_read: wasi::SubscriptionFdReadwrite {
file_descriptor: readable_fd,
},
},
},
},
@@ -112,13 +117,15 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: fd_readwrite,
fd_write: wasi::SubscriptionFdReadwrite {
file_descriptor: writable_fd,
},
},
},
},
];
let out = poll_oneoff_impl(&r#in).unwrap();
assert_eq!(out.len(), 2, "should return 2 events");
assert_eq!(out.len(), 2, "should return 2 events, got: {:?}", out);
assert_eq!(
out[0].userdata, 1,
"the event.userdata should contain fd userdata specified by the user"
@@ -143,26 +150,64 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) {
unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasi::Fd) {
// Create a file in the scratch directory.
let file_fd = wasi::path_open(
let nonempty_file = wasi::path_open(
dir_fd,
0,
"file",
"readable_file",
wasi::OFLAGS_CREAT,
wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_POLL_FD_READWRITE,
wasi::RIGHTS_FD_WRITE,
0,
0,
)
.expect("opening a file");
.expect("create writable file");
// Write to file
let contents = &[1u8];
let ciovec = wasi::Ciovec {
buf: contents.as_ptr() as *const _,
buf_len: contents.len(),
};
wasi::fd_write(nonempty_file, &[ciovec]).expect("write");
wasi::fd_close(nonempty_file).expect("close");
// Now open the file for reading
let readable_fd = wasi::path_open(
dir_fd,
0,
"readable_file",
0,
wasi::RIGHTS_FD_READ | wasi::RIGHTS_POLL_FD_READWRITE,
0,
0,
)
.expect("opening a readable file");
assert_gt!(
file_fd,
readable_fd,
libc::STDERR_FILENO as wasi::Fd,
"file descriptor range check",
);
// Create a file in the scratch directory.
let writable_fd = wasi::path_open(
dir_fd,
0,
"writable_file",
wasi::OFLAGS_CREAT,
wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_POLL_FD_READWRITE,
0,
0,
)
.expect("opening a writable file");
assert_gt!(
writable_fd,
libc::STDERR_FILENO as wasi::Fd,
"file descriptor range check",
);
test_fd_readwrite(file_fd, wasi::ERRNO_SUCCESS);
test_fd_readwrite(readable_fd, writable_fd, wasi::ERRNO_SUCCESS);
wasi::fd_close(file_fd).expect("closing a file");
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
wasi::fd_close(readable_fd).expect("closing readable_file");
wasi::path_unlink_file(dir_fd, "readable_file").expect("removing readable_file");
wasi::path_unlink_file(dir_fd, "writable_file").expect("removing writable_file");
}
unsafe fn test_fd_readwrite_invalid_fd() {

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::mem::MaybeUninit;
use wasi_tests::{assert_errno, STDERR_FD, STDIN_FD, STDOUT_FD};
@@ -46,7 +47,7 @@ unsafe fn test_stdin_read() {
let out = poll_oneoff_impl(&r#in).unwrap();
// The result should be either a timeout, or that stdin is ready for reading.
// Both are valid behaviors that depend on the test environment.
assert!(out.len() >= 1, "should return at least 1 event");
assert!(out.len() >= 1, "stdin read should return at least 1 event");
for event in out {
if event.r#type == wasi::EVENTTYPE_CLOCK {
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
@@ -66,55 +67,61 @@ unsafe fn test_stdin_read() {
}
}
fn writable_subs(h: &HashMap<u64, wasi::Fd>) -> Vec<wasi::Subscription> {
println!("writable subs: {:?}", h);
h.iter()
.map(|(ud, fd)| wasi::Subscription {
userdata: *ud,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: wasi::SubscriptionFdReadwrite {
file_descriptor: *fd,
},
},
},
})
.collect()
}
unsafe fn test_stdout_stderr_write() {
let stdout_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDOUT_FD,
};
let stderr_readwrite = wasi::SubscriptionFdReadwrite {
file_descriptor: STDERR_FD,
};
let r#in = [
wasi::Subscription {
userdata: 1,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stdout_readwrite,
let mut writable: HashMap<u64, wasi::Fd> =
vec![(1, STDOUT_FD), (2, STDERR_FD)].into_iter().collect();
let clock = wasi::Subscription {
userdata: CLOCK_ID,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_CLOCK,
u: wasi::SubscriptionUU {
clock: wasi::SubscriptionClock {
id: wasi::CLOCKID_MONOTONIC,
timeout: 10_000_000u64, // 10 milliseconds
precision: 0,
flags: 0,
},
},
},
wasi::Subscription {
userdata: 2,
u: wasi::SubscriptionU {
tag: wasi::EVENTTYPE_FD_WRITE,
u: wasi::SubscriptionUU {
fd_write: stderr_readwrite,
},
},
},
];
let out = poll_oneoff_impl(&r#in).unwrap();
assert_eq!(out.len(), 2, "should return 2 events");
assert_eq!(
out[0].userdata, 1,
"the event.userdata should contain fd userdata specified by the user"
);
assert_errno!(out[0].error, wasi::ERRNO_SUCCESS);
assert_eq!(
out[0].r#type,
wasi::EVENTTYPE_FD_WRITE,
"the event.type should equal FD_WRITE"
);
assert_eq!(
out[1].userdata, 2,
"the event.userdata should contain fd userdata specified by the user"
);
assert_errno!(out[1].error, wasi::ERRNO_SUCCESS);
assert_eq!(
out[1].r#type,
wasi::EVENTTYPE_FD_WRITE,
"the event.type should equal FD_WRITE"
);
};
while !writable.is_empty() {
let mut subs = writable_subs(&writable);
subs.push(clock.clone());
let out = poll_oneoff_impl(&subs).unwrap();
for event in out {
match event.userdata {
CLOCK_ID => {
panic!("timed out with the following pending subs: {:?}", writable)
}
ud => {
if let Some(_) = writable.remove(&ud) {
assert_eq!(event.r#type, wasi::EVENTTYPE_FD_WRITE);
assert_errno!(event.error, wasi::ERRNO_SUCCESS);
} else {
panic!("Unknown userdata {}, pending sub: {:?}", ud, writable)
}
}
}
}
}
}
unsafe fn test_poll_oneoff() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,13 @@
// The windows scheduler is unmaintained and due for a rewrite.
//
// Rather than use a polling mechanism for file read/write readiness,
// it checks readiness just once, before sleeping for any timer subscriptions.
// Checking stdin readiness uses a worker thread which, once started, lives for the
// lifetime of the process.
//
// We suspect there are bugs in this scheduler, however, we have not
// taken the time to improve it. See bug #2880.
use anyhow::Context;
use std::ops::Deref;
use std::os::windows::io::{AsRawHandle, RawHandle};
@@ -9,132 +19,127 @@ use wasi_common::{
file::WasiFile,
sched::{
subscription::{RwEventFlags, Subscription},
Poll, WasiSched,
Poll,
},
Error, ErrorExt,
};
pub struct SyncSched {}
impl SyncSched {
pub fn new() -> Self {
Self {}
}
pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle).await
}
impl WasiSched for SyncSched {
fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
// For reuse by wasi-tokio, which has a different WasiFile -> RawHandle translator.
pub async fn poll_oneoff_<'a>(
poll: &mut Poll<'a>,
file_is_stdin: impl Fn(&dyn WasiFile) -> bool,
file_to_handle: impl Fn(&dyn WasiFile) -> Option<RawHandle>,
) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
let mut ready = false;
let waitmode = if let Some(t) = poll.earliest_clock_deadline() {
if let Some(duration) = t.duration_until() {
WaitMode::Timeout(duration)
} else {
WaitMode::Immediate
}
} else {
if ready {
WaitMode::Immediate
} else {
WaitMode::Infinite
}
};
let mut ready = false;
let timeout = poll.earliest_clock_deadline();
let mut stdin_read_subs = Vec::new();
let mut immediate_subs = Vec::new();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(r) if r.file.as_any().is::<crate::stdio::Stdin>() => {
let mut stdin_read_subs = Vec::new();
let mut immediate_reads = Vec::new();
let mut immediate_writes = Vec::new();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(r) => {
if file_is_stdin(r.file.deref()) {
stdin_read_subs.push(r);
} else if file_to_handle(r.file.deref()).is_some() {
immediate_reads.push(r);
} else {
return Err(
Error::invalid_argument().context("read subscription fd downcast failed")
);
}
Subscription::Read(rw) | Subscription::Write(rw) => {
if wasi_file_raw_handle(rw.file.deref()).is_some() {
immediate_subs.push(s);
} else {
return Err(Error::invalid_argument()
.context("read/write subscription fd downcast failed"));
}
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
Subscription::Write(w) => {
if file_to_handle(w.file.deref()).is_some() {
immediate_writes.push(w);
} else {
return Err(
Error::invalid_argument().context("write subscription fd downcast failed")
);
}
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !stdin_read_subs.is_empty() {
let waitmode = if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
WaitMode::Timeout(duration)
} else {
WaitMode::Immediate
}
} else {
if ready {
WaitMode::Immediate
} else {
WaitMode::Infinite
}
};
let state = STDIN_POLL
.lock()
.map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))?
.poll(waitmode)?;
for readsub in stdin_read_subs.into_iter() {
match state {
PollState::Ready => {
readsub.complete(1, RwEventFlags::empty());
ready = true;
}
PollState::NotReady | PollState::TimedOut => {}
PollState::Error(ref e) => {
// Unfortunately, we need to deliver the Error to each of the
// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the
// kind, and then back to std::io::Error, and finally to anyhow::Error.
// When its time to turn this into an errno elsewhere, the error kind will
// be inspected.
let ekind = e.kind();
let ioerror = std::io::Error::from(ekind);
readsub.error(ioerror.into());
ready = true;
}
}
}
}
for sub in immediate_subs {
match sub {
Subscription::Read(r) => {
// XXX This doesnt strictly preserve the behavior in the earlier
// implementation, which would always do complete(0) for reads from
// stdout/err.
match r.file.num_ready_bytes() {
Ok(ready_bytes) => {
r.complete(ready_bytes, RwEventFlags::empty());
ready = true;
}
Err(e) => {
r.error(e);
ready = true;
}
}
}
Subscription::Write(w) => {
// Everything is always ready for writing, apparently?
w.complete(0, RwEventFlags::empty());
if !stdin_read_subs.is_empty() {
let state = STDIN_POLL
.lock()
.map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))?
.poll(waitmode)?;
for readsub in stdin_read_subs.into_iter() {
match state {
PollState::Ready => {
readsub.complete(1, RwEventFlags::empty());
ready = true;
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if !ready {
if let Some(t) = timeout {
if let Some(duration) = t.duration_until() {
thread::sleep(duration);
PollState::NotReady | PollState::TimedOut => {}
PollState::Error(ref e) => {
// Unfortunately, we need to deliver the Error to each of the
// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the
// kind, and then back to std::io::Error, and finally to anyhow::Error.
// When its time to turn this into an errno elsewhere, the error kind will
// be inspected.
let ekind = e.kind();
let ioerror = std::io::Error::from(ekind);
readsub.error(ioerror.into());
ready = true;
}
}
}
}
for r in immediate_reads {
match r.file.num_ready_bytes().await {
Ok(ready_bytes) => {
r.complete(ready_bytes, RwEventFlags::empty());
ready = true;
}
Err(e) => {
r.error(e);
ready = true;
}
}
}
for w in immediate_writes {
// Everything is always ready for writing, apparently?
w.complete(0, RwEventFlags::empty());
ready = true;
}
Ok(())
}
fn sched_yield(&self) -> Result<(), Error> {
thread::yield_now();
Ok(())
}
fn sleep(&self, duration: Duration) -> Result<(), Error> {
std::thread::sleep(duration);
Ok(())
if !ready {
if let WaitMode::Timeout(duration) = waitmode {
thread::sleep(duration);
}
}
Ok(())
}
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
f.as_any().is::<crate::stdio::Stdin>()
}
pub fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(
@@ -172,6 +177,7 @@ enum PollState {
Error(std::io::Error),
}
#[derive(Copy, Clone)]
enum WaitMode {
Timeout(Duration),
Infinite,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,186 @@
use crate::block_on_dummy_executor;
use std::any::Any;
use std::io;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use wasi_common::{
file::{Advice, FdFlags, FileType, Filestat, WasiFile},
Error,
};
pub struct File(wasi_cap_std_sync::file::File);
impl File {
pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self {
File(file)
}
pub fn from_cap_std(file: cap_std::fs::File) -> Self {
Self::from_inner(wasi_cap_std_sync::file::File::from_cap_std(file))
}
}
pub struct Stdin(wasi_cap_std_sync::stdio::Stdin);
pub fn stdin() -> Stdin {
Stdin(wasi_cap_std_sync::stdio::stdin())
}
pub struct Stdout(wasi_cap_std_sync::stdio::Stdout);
pub fn stdout() -> Stdout {
Stdout(wasi_cap_std_sync::stdio::stdout())
}
pub struct Stderr(wasi_cap_std_sync::stdio::Stderr);
pub fn stderr() -> Stderr {
Stderr(wasi_cap_std_sync::stdio::stderr())
}
macro_rules! wasi_file_impl {
($ty:ty) => {
#[wiggle::async_trait]
impl WasiFile for $ty {
fn as_any(&self) -> &dyn Any {
self
}
async fn datasync(&self) -> Result<(), Error> {
block_on_dummy_executor(|| self.0.datasync())
}
async fn sync(&self) -> Result<(), Error> {
block_on_dummy_executor(|| self.0.sync())
}
async fn get_filetype(&self) -> Result<FileType, Error> {
block_on_dummy_executor(|| self.0.get_filetype())
}
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
block_on_dummy_executor(|| self.0.get_fdflags())
}
async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> {
block_on_dummy_executor(|| self.0.set_fdflags(fdflags))
}
async fn get_filestat(&self) -> Result<Filestat, Error> {
block_on_dummy_executor(|| self.0.get_filestat())
}
async fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.set_filestat_size(size))
}
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.advise(offset, len, advice))
}
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.allocate(offset, len))
}
async fn read_vectored<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.read_vectored(bufs))
}
async fn read_vectored_at<'a>(
&self,
bufs: &mut [io::IoSliceMut<'a>],
offset: u64,
) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset))
}
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.write_vectored(bufs))
}
async fn write_vectored_at<'a>(
&self,
bufs: &[io::IoSlice<'a>],
offset: u64,
) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset))
}
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.seek(pos))
}
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
block_on_dummy_executor(move || self.0.peek(buf))
}
async fn set_times(
&self,
atime: Option<wasi_common::SystemTimeSpec>,
mtime: Option<wasi_common::SystemTimeSpec>,
) -> Result<(), Error> {
block_on_dummy_executor(move || self.0.set_times(atime, mtime))
}
async fn num_ready_bytes(&self) -> Result<u64, Error> {
block_on_dummy_executor(|| self.0.num_ready_bytes())
}
#[cfg(not(windows))]
async fn readable(&mut self) -> Result<(), Error> {
// The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object.
// AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce
// mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this
// async method to ensure this is the only Future which can access the RawFd during the
// lifetime of the AsyncFd.
use tokio::io::{unix::AsyncFd, Interest};
use unsafe_io::os::posish::AsRawFd;
let rawfd = self.0.as_raw_fd();
match AsyncFd::with_interest(rawfd, Interest::READABLE) {
Ok(asyncfd) => {
let _ = asyncfd.readable().await?;
Ok(())
}
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
// if e is EPERM, this file isnt supported by epoll because it is immediately
// available for reading:
Ok(())
}
Err(e) => Err(e.into()),
}
}
#[cfg(windows)]
async fn readable(&mut self) -> Result<(), Error> {
// Windows uses a rawfd based scheduler :(
use wasi_common::ErrorExt;
Err(Error::badf())
}
#[cfg(not(windows))]
async fn writable(&mut self) -> Result<(), Error> {
// The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object.
// AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce
// mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this
// async method to ensure this is the only Future which can access the RawFd during the
// lifetime of the AsyncFd.
use tokio::io::{unix::AsyncFd, Interest};
use unsafe_io::os::posish::AsRawFd;
let rawfd = self.0.as_raw_fd();
match AsyncFd::with_interest(rawfd, Interest::WRITABLE) {
Ok(asyncfd) => {
let _ = asyncfd.writable().await?;
Ok(())
}
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
// if e is EPERM, this file isnt supported by epoll because it is immediately
// available for writing:
Ok(())
}
Err(e) => Err(e.into()),
}
}
#[cfg(windows)]
async fn writable(&mut self) -> Result<(), Error> {
// Windows uses a rawfd based scheduler :(
use wasi_common::ErrorExt;
Err(Error::badf())
}
}
#[cfg(windows)]
impl AsRawHandle for $ty {
fn as_raw_handle(&self) -> RawHandle {
self.0.as_raw_handle()
}
}
};
}
wasi_file_impl!(File);
wasi_file_impl!(Stdin);
wasi_file_impl!(Stdout);
wasi_file_impl!(Stderr);

View File

@@ -0,0 +1,114 @@
mod dir;
mod file;
pub mod sched;
pub mod stdio;
use std::cell::RefCell;
use std::future::Future;
use std::path::Path;
use std::rc::Rc;
pub use wasi_cap_std_sync::{clocks_ctx, random_ctx};
use wasi_common::{Error, Table, WasiCtx};
pub use dir::Dir;
pub use file::File;
use crate::sched::sched_ctx;
pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder);
impl WasiCtxBuilder {
pub fn new() -> Self {
WasiCtxBuilder(WasiCtx::builder(
random_ctx(),
clocks_ctx(),
sched_ctx(),
Rc::new(RefCell::new(Table::new())),
))
}
pub fn env(self, var: &str, value: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.env(var, value)?;
Ok(WasiCtxBuilder(s))
}
pub fn envs(self, env: &[(String, String)]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for (k, v) in env {
s = s.env(k, v)?;
}
Ok(s)
}
pub fn inherit_env(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for (key, value) in std::env::vars() {
s = s.env(&key, &value)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn arg(self, arg: &str) -> Result<Self, wasi_common::StringArrayError> {
let s = self.0.arg(arg)?;
Ok(WasiCtxBuilder(s))
}
pub fn args(self, arg: &[String]) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self;
for a in arg {
s = s.arg(&a)?;
}
Ok(s)
}
pub fn inherit_args(self) -> Result<Self, wasi_common::StringArrayError> {
let mut s = self.0;
for arg in std::env::args() {
s = s.arg(&arg)?;
}
Ok(WasiCtxBuilder(s))
}
pub fn stdin(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdin(f))
}
pub fn stdout(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stdout(f))
}
pub fn stderr(self, f: Box<dyn wasi_common::WasiFile>) -> Self {
WasiCtxBuilder(self.0.stderr(f))
}
pub fn inherit_stdin(self) -> Self {
self.stdin(Box::new(crate::stdio::stdin()))
}
pub fn inherit_stdout(self) -> Self {
self.stdout(Box::new(crate::stdio::stdout()))
}
pub fn inherit_stderr(self) -> Self {
self.stderr(Box::new(crate::stdio::stderr()))
}
pub fn inherit_stdio(self) -> Self {
self.inherit_stdin().inherit_stdout().inherit_stderr()
}
pub fn preopened_dir(
self,
dir: cap_std::fs::Dir,
guest_path: impl AsRef<Path>,
) -> Result<Self, wasi_common::Error> {
let dir = Box::new(Dir::from_cap_std(dir));
Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?))
}
pub fn build(self) -> Result<WasiCtx, wasi_common::Error> {
self.0.build()
}
}
// Much of this crate is implemented in terms of `async` methods from the
// wasi-cap-std-sync crate. These methods may be async in signature, however,
// they are synchronous in implementation (always Poll::Ready on first poll)
// and perform blocking syscalls.
//
// This function takes this blocking code and executes it using a dummy executor
// to assert its immediate readiness. We tell tokio this is a blocking operation
// with the block_in_place function.
pub(crate) fn block_on_dummy_executor<'a, F, Fut, T>(f: F) -> Result<T, Error>
where
F: FnOnce() -> Fut + Send + 'a,
Fut: Future<Output = Result<T, Error>>,
T: Send + 'static,
{
tokio::task::block_in_place(move || wiggle::run_in_dummy_executor(f()))
}

View File

@@ -0,0 +1,35 @@
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use unix::poll_oneoff;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use windows::poll_oneoff;
use wasi_common::{
sched::{Duration, Poll, WasiSched},
Error,
};
pub fn sched_ctx() -> Box<dyn wasi_common::WasiSched> {
struct AsyncSched;
#[wiggle::async_trait]
impl WasiSched for AsyncSched {
async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> {
poll_oneoff(poll).await
}
async fn sched_yield(&self) -> Result<(), Error> {
tokio::task::yield_now().await;
Ok(())
}
async fn sleep(&self, duration: Duration) -> Result<(), Error> {
tokio::time::sleep(duration).await;
Ok(())
}
}
Box::new(AsyncSched)
}

View File

@@ -0,0 +1,91 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll as FPoll};
use wasi_common::{
sched::{
subscription::{RwEventFlags, Subscription},
Poll,
},
Context as _, Error,
};
struct FirstReady<'a, T>(Vec<Pin<Box<dyn Future<Output = T> + 'a>>>);
impl<'a, T> FirstReady<'a, T> {
fn new() -> Self {
FirstReady(Vec::new())
}
fn push(&mut self, f: impl Future<Output = T> + 'a) {
self.0.push(Box::pin(f));
}
}
impl<'a, T> Future for FirstReady<'a, T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> FPoll<T> {
let mut result = FPoll::Pending;
for f in self.as_mut().0.iter_mut() {
match f.as_mut().poll(cx) {
FPoll::Ready(r) => match result {
// First ready gets to set the result. But, continue the loop so all futures
// which are ready simultaneously (often on first poll) get to report their
// readiness.
FPoll::Pending => {
result = FPoll::Ready(r);
}
_ => {}
},
_ => continue,
}
}
return result;
}
}
pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
if poll.is_empty() {
return Ok(());
}
let duration = poll
.earliest_clock_deadline()
.map(|sub| sub.duration_until());
let mut futures = FirstReady::new();
for s in poll.rw_subscriptions() {
match s {
Subscription::Read(f) => {
futures.push(async move {
f.file.readable().await.context("readable future")?;
f.complete(
f.file
.num_ready_bytes()
.await
.context("read num_ready_bytes")?,
RwEventFlags::empty(),
);
Ok::<(), Error>(())
});
}
Subscription::Write(f) => {
futures.push(async move {
f.file.writable().await.context("writable future")?;
f.complete(0, RwEventFlags::empty());
Ok(())
});
}
Subscription::MonotonicClock { .. } => unreachable!(),
}
}
if let Some(Some(remaining_duration)) = duration {
match tokio::time::timeout(remaining_duration, futures).await {
Ok(r) => r?,
Err(_deadline_elapsed) => {}
}
} else {
futures.await?;
}
Ok(())
}

View File

@@ -0,0 +1,47 @@
use crate::block_on_dummy_executor;
use std::os::windows::io::{AsRawHandle, RawHandle};
use wasi_cap_std_sync::sched::windows::poll_oneoff_;
use wasi_common::{file::WasiFile, sched::Poll, Error};
pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
// Tokio doesn't provide us the AsyncFd primitive on Windows, so instead
// we use the blocking poll_oneoff implementation from the wasi-cap-std-crate.
// We provide a function specific to this crate's WasiFile types for downcasting
// to a RawHandle.
block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle))
}
pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
f.as_any().is::<crate::stdio::Stdin>()
}
fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option<RawHandle> {
let a = f.as_any();
if a.is::<crate::file::File>() {
Some(
a.downcast_ref::<crate::file::File>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdin>() {
Some(
a.downcast_ref::<crate::stdio::Stdin>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stdout>() {
Some(
a.downcast_ref::<crate::stdio::Stdout>()
.unwrap()
.as_raw_handle(),
)
} else if a.is::<crate::stdio::Stderr>() {
Some(
a.downcast_ref::<crate::stdio::Stderr>()
.unwrap()
.as_raw_handle(),
)
} else {
None
}
}

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ build = "build.rs"
[dependencies]
wasi-common = { path = "../wasi-common", version = "0.26.0" }
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0", optional = true }
wasi-tokio = { path = "../wasi-common/tokio", version = "0.26.0", optional = true }
wiggle = { path = "../wiggle", default-features = false, version = "0.26.0" }
wasmtime-wiggle = { path = "../wiggle/wasmtime", default-features = false, version = "0.26.0" }
wasmtime = { path = "../wasmtime", default-features = false, version = "0.26.0" }
@@ -23,3 +24,4 @@ anyhow = "1.0"
[features]
default = ["sync"]
sync = ["wasi-cap-std-sync"]
tokio = ["wasi-tokio", "wasmtime/async", "wasmtime-wiggle/async"]

View File

@@ -7,10 +7,7 @@
//! Individual snapshots are available through
//! `wasmtime_wasi::snapshots::preview_{0, 1}::Wasi::new(&Store, Rc<RefCell<WasiCtx>>)`.
use std::cell::RefCell;
use std::rc::Rc;
pub use wasi_common::{Error, WasiCtx, WasiCtxBuilder, WasiDir, WasiFile};
use wasmtime::{Config, Linker, Store};
/// Re-export the commonly used wasi-cap-std-sync crate here. This saves
/// consumers of this library from having to keep additional dependencies
@@ -18,8 +15,36 @@ use wasmtime::{Config, Linker, Store};
#[cfg(feature = "sync")]
pub mod sync {
pub use wasi_cap_std_sync::*;
super::define_wasi!(block_on);
}
/// Sync mode is the "default" of this crate, so we also export it at the top
/// level.
#[cfg(feature = "sync")]
pub use sync::*;
/// Re-export the wasi-tokio crate here. This saves consumers of this library from having
/// to keep additional dependencies in sync.
#[cfg(feature = "tokio")]
pub mod tokio {
pub use wasi_tokio::*;
super::define_wasi!(async);
}
// The only difference between these definitions for sync vs async is whether
// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run)
// or whether they have an internal "dummy executor" that expects the implementation of all
// the async funcs to poll to Ready immediately.
#[doc(hidden)]
#[macro_export]
macro_rules! define_wasi {
($async_mode: tt) => {
use std::cell::RefCell;
use std::rc::Rc;
use wasmtime::{Config, Linker, Store};
use wasi_common::WasiCtx;
/// An instantiated instance of all available wasi exports. Presently includes
/// both the "preview1" snapshot and the "unstable" (preview0) snapshot.
pub struct Wasi {
@@ -79,6 +104,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based
resolution.",
},
},
$async_mode: *
});
}
pub mod preview_0 {
@@ -106,6 +132,9 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based
resolution.",
},
},
$async_mode: *
});
}
}
}
}

View File

@@ -47,7 +47,9 @@ impl Parse for ConfigField {
} else if lookahead.peek(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(input.parse()?))
Ok(ConfigField::Async(AsyncConf {
functions: input.parse()?,
}))
} else {
Err(lookahead.error())
}
@@ -280,40 +282,64 @@ impl Parse for ErrorConfField {
}
#[derive(Clone, Default, Debug)]
/// Modules and funcs that should be async
pub struct AsyncConf(HashMap<String, Vec<String>>);
/// Modules and funcs that have async signatures
pub struct AsyncConf {
functions: AsyncFunctions,
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> bool {
self.0
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
#[derive(Clone, Debug)]
pub enum AsyncFunctions {
Some(HashMap<String, Vec<String>>),
All,
}
impl Default for AsyncFunctions {
fn default() -> Self {
AsyncFunctions::Some(HashMap::default())
}
}
impl Parse for AsyncConf {
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> bool {
match &self.functions {
AsyncFunctions::Some(fs) => fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some(),
AsyncFunctions::All => true,
}
}
}
impl Parse for AsyncFunctions {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let _ = braced!(content in input);
let items: Punctuated<AsyncConfField, Token![,]> =
content.parse_terminated(Parse::parse)?;
let mut m: HashMap<String, Vec<String>> = HashMap::new();
use std::collections::hash_map::Entry;
for i in items {
let function_names = i
.function_names
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>();
match m.entry(i.module_name.to_string()) {
Entry::Occupied(o) => o.into_mut().extend(function_names),
Entry::Vacant(v) => {
v.insert(function_names);
let lookahead = input.lookahead1();
if lookahead.peek(syn::token::Brace) {
let _ = braced!(content in input);
let items: Punctuated<AsyncConfField, Token![,]> =
content.parse_terminated(Parse::parse)?;
let mut functions: HashMap<String, Vec<String>> = HashMap::new();
use std::collections::hash_map::Entry;
for i in items {
let function_names = i
.function_names
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>();
match functions.entry(i.module_name.to_string()) {
Entry::Occupied(o) => o.into_mut().extend(function_names),
Entry::Vacant(v) => {
v.insert(function_names);
}
}
}
Ok(AsyncFunctions::Some(functions))
} else if lookahead.peek(Token![*]) {
let _: Token![*] = input.parse().unwrap();
Ok(AsyncFunctions::All)
} else {
Err(lookahead.error())
}
Ok(AsyncConf(m))
}
}

View File

@@ -953,3 +953,40 @@ impl From<GuestError> for Trap {
Trap::String(err.to_string())
}
}
pub fn run_in_dummy_executor<F: std::future::Future>(future: F) -> F::Output {
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
let mut f = Pin::from(Box::new(future));
let waker = dummy_waker();
let mut cx = Context::from_waker(&waker);
match f.as_mut().poll(&mut cx) {
Poll::Ready(val) => return val,
Poll::Pending => {
panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store")
}
}
fn dummy_waker() -> Waker {
return unsafe { Waker::from_raw(clone(5 as *const _)) };
unsafe fn clone(ptr: *const ()) -> RawWaker {
assert_eq!(ptr as usize, 5);
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
RawWaker::new(ptr, &VTABLE)
}
unsafe fn wake(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
unsafe fn wake_by_ref(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
unsafe fn drop(ptr: *const ()) {
assert_eq!(ptr as usize, 5);
}
}
}

View File

@@ -7,9 +7,7 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx};
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
async: {
atoms::{int_float_args, double_int_return_float}
}
async: *,
});
impl_errno!(types::Errno);

View File

@@ -26,6 +26,11 @@ name = "atoms_async"
path = "tests/atoms_async.rs"
required-features = ["async", "wasmtime/wat"]
[[test]]
name = "atoms_sync"
path = "tests/atoms_sync.rs"
required-features = ["wasmtime/wat"]
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -1,4 +1,4 @@
pub use wiggle_generate::config::AsyncConf;
use wiggle_generate::config::AsyncFunctions;
use {
proc_macro2::Span,
std::collections::HashMap,
@@ -16,7 +16,6 @@ pub struct Config {
pub witx: WitxConf,
pub ctx: CtxConf,
pub modules: ModulesConf,
#[cfg(feature = "async")]
pub async_: AsyncConf,
}
@@ -26,7 +25,6 @@ pub enum ConfigField {
Witx(WitxConf),
Ctx(CtxConf),
Modules(ModulesConf),
#[cfg(feature = "async")]
Async(AsyncConf),
}
@@ -39,6 +37,7 @@ mod kw {
syn::custom_keyword!(name);
syn::custom_keyword!(docs);
syn::custom_keyword!(function_override);
syn::custom_keyword!(block_on);
}
impl Parse for ConfigField {
@@ -67,17 +66,17 @@ impl Parse for ConfigField {
} else if lookahead.peek(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
#[cfg(feature = "async")]
{
Ok(ConfigField::Async(input.parse()?))
}
#[cfg(not(feature = "async"))]
{
Err(syn::Error::new(
input.span(),
"async not supported, enable cargo feature \"async\"",
))
}
Ok(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: true,
functions: input.parse()?,
}))
} else {
Err(lookahead.error())
}
@@ -90,7 +89,6 @@ impl Config {
let mut witx = None;
let mut ctx = None;
let mut modules = None;
#[cfg(feature = "async")]
let mut async_ = None;
for f in fields {
match f {
@@ -118,7 +116,6 @@ impl Config {
}
modules = Some(c);
}
#[cfg(feature = "async")]
ConfigField::Async(c) => {
if async_.is_some() {
return Err(Error::new(err_loc, "duplicate `async` field"));
@@ -132,7 +129,6 @@ impl Config {
witx: witx.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
ctx: ctx.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?,
modules: modules.ok_or_else(|| Error::new(err_loc, "`modules` field required"))?,
#[cfg(feature = "async")]
async_: async_.unwrap_or_default(),
})
}
@@ -276,3 +272,53 @@ impl Parse for ModulesConf {
})
}
}
#[derive(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
functions: AsyncFunctions,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Asyncness {
/// Wiggle function is synchronous, wasmtime Func is synchronous
Sync,
/// Wiggle function is asynchronous, but wasmtime Func is synchronous
Blocking,
/// Wiggle function and wasmtime Func are asynchronous.
Async,
}
impl Asyncness {
pub fn is_sync(&self) -> bool {
match self {
Asyncness::Sync => true,
_ => false,
}
}
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> Asyncness {
let a = if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
};
match &self.functions {
AsyncFunctions::Some(fs) => {
if fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
{
a
} else {
Asyncness::Sync
}
}
AsyncFunctions::All => a,
}
}
}

View File

@@ -6,7 +6,7 @@ use wiggle_generate::Names;
mod config;
use config::{AsyncConf, ModuleConf, TargetConf};
use config::{AsyncConf, Asyncness, ModuleConf, TargetConf};
/// Define the structs required to integrate a Wiggle implementation with Wasmtime.
///
@@ -48,11 +48,6 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
let doc = config.load_document();
let names = Names::new(quote!(wasmtime_wiggle));
#[cfg(feature = "async")]
let async_config = config.async_.clone();
#[cfg(not(feature = "async"))]
let async_config = AsyncConf::default();
let modules = config.modules.iter().map(|(name, module_conf)| {
let module = doc
.module(&witx::Id::new(name))
@@ -63,7 +58,7 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
&names,
&config.target,
&config.ctx.name,
&async_config,
&config.async_,
)
});
quote!( #(#modules)* ).into()
@@ -107,13 +102,24 @@ fn generate_module(
let mut host_funcs = Vec::new();
for f in module.funcs() {
let asyncness = async_conf.is_async(module.name.as_str(), f.name.as_str());
match asyncness {
Asyncness::Blocking => {}
Asyncness::Async => {
assert!(
cfg!(feature = "async"),
"generating async wasmtime Funcs requires cargo feature \"async\""
);
}
_ => {}
}
generate_func(
&module_id,
&f,
names,
&target_module,
ctx_type,
async_conf.is_async(module.name.as_str(), f.name.as_str()),
asyncness,
&mut fns,
&mut ctor_externs,
&mut host_funcs,
@@ -229,11 +235,12 @@ fn generate_func(
names: &Names,
target_module: &TokenStream2,
ctx_type: &syn::Type,
is_async: bool,
asyncness: Asyncness,
fns: &mut Vec<TokenStream2>,
ctors: &mut Vec<TokenStream2>,
host_funcs: &mut Vec<(witx::Id, TokenStream2)>,
) {
let rt = names.runtime_mod();
let name_ident = names.func(&func.name);
let (params, results) = func.wasm_signature();
@@ -257,8 +264,16 @@ fn generate_func(
_ => unimplemented!(),
};
let async_ = if is_async { quote!(async) } else { quote!() };
let await_ = if is_async { quote!(.await) } else { quote!() };
let async_ = if asyncness.is_sync() {
quote!()
} else {
quote!(async)
};
let await_ = if asyncness.is_sync() {
quote!()
} else {
quote!(.await)
};
let runtime = names.runtime_mod();
let fn_ident = format_ident!("{}_{}", module_ident, name_ident);
@@ -282,9 +297,10 @@ fn generate_func(
}
});
if is_async {
let wrapper = format_ident!("wrap{}_async", params.len());
ctors.push(quote! {
match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("wrap{}_async", params.len());
ctors.push(quote! {
let #name_ident = wasmtime::Func::#wrapper(
store,
ctx.clone(),
@@ -293,9 +309,24 @@ fn generate_func(
Box::new(async move { Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*).await })
}
);
});
} else {
ctors.push(quote! {
});
}
Asyncness::Blocking => {
// Emit a synchronous function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap.
ctors.push(quote! {
let my_ctx = ctx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
#rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*))
}
);
});
}
Asyncness::Sync => {
ctors.push(quote! {
let my_ctx = ctx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
@@ -304,40 +335,62 @@ fn generate_func(
}
);
});
}
}
let host_wrapper = if is_async {
let wrapper = format_ident!("wrap{}_host_func_async", params.len());
quote! {
config.#wrapper(
module,
field,
move |caller #(,#arg_decls)*|
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
Box::new(async move {
let ctx = caller.store()
let host_wrapper = match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("wrap{}_host_func_async", params.len());
quote! {
config.#wrapper(
module,
field,
move |caller #(,#arg_decls)*|
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
Box::new(async move {
let ctx = caller.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await;
result
})
}
);
}
}
Asyncness::Blocking => {
// Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap.
quote! {
config.wrap_host_func(
module,
field,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
let ctx = caller
.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await;
result
})
}
);
#rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*))
},
);
}
}
} else {
quote! {
config.wrap_host_func(
module,
field,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
let ctx = caller
.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*);
result
},
);
Asyncness::Sync => {
quote! {
config.wrap_host_func(
module,
field,
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
let ctx = caller
.store()
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)
},
);
}
}
};
host_funcs.push((func.name.clone(), host_wrapper));

View File

@@ -0,0 +1,176 @@
use std::cell::RefCell;
use std::rc::Rc;
wasmtime_wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
async: {
atoms::{double_int_return_float}
}
});
wasmtime_wiggle::wasmtime_integration!({
target: crate,
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
ctx: Ctx,
modules: { atoms => { name: Atoms } },
block_on: {
atoms::double_int_return_float
}
});
pub struct Ctx;
impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
types::Errno::Ok
}
}
#[wasmtime_wiggle::async_trait]
impl atoms::Atoms for Ctx {
fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("INT FLOAT ARGS: {} {}", an_int, an_float);
Ok(())
}
async fn double_int_return_float(
&self,
an_int: u32,
) -> Result<types::AliasToFloat, types::Errno> {
Ok((an_int as f32) * 2.0)
}
}
fn run_int_float_args(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = linker.instantiate(&shim_mod).unwrap();
let results = shim_inst
.get_func("int_float_args_shim")
.unwrap()
.call(&[0i32.into(), 123.45f32.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"int_float_args errno"
);
}
fn run_double_int_return_float(linker: &wasmtime::Linker) {
let shim_mod = shim_module(linker.store());
let shim_inst = linker.instantiate(&shim_mod).unwrap();
let input: i32 = 123;
let result_location: i32 = 0;
let results = shim_inst
.get_func("double_int_return_float_shim")
.unwrap()
.call(&[input.into(), result_location.into()])
.unwrap();
assert_eq!(results.len(), 1, "one return value");
assert_eq!(
results[0].unwrap_i32(),
types::Errno::Ok as i32,
"double_int_return_float errno"
);
// The actual result is in memory:
let mem = shim_inst.get_memory("memory").unwrap();
let mut result_bytes: [u8; 4] = [0, 0, 0, 0];
mem.read(result_location as usize, &mut result_bytes)
.unwrap();
let result = f32::from_le_bytes(result_bytes);
assert_eq!((input * 2) as f32, result);
}
#[test]
fn test_sync_host_func() {
let store = store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_int_float_args(&linker);
}
#[test]
fn test_async_host_func() {
let store = store();
let ctx = Rc::new(RefCell::new(Ctx));
let atoms = Atoms::new(&store, ctx.clone());
let mut linker = wasmtime::Linker::new(&store);
atoms.add_to_linker(&mut linker).unwrap();
run_double_int_return_float(&linker);
}
#[test]
fn test_sync_config_host_func() {
let mut config = wasmtime::Config::new();
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_int_float_args(&linker);
}
#[test]
fn test_async_config_host_func() {
let mut config = wasmtime::Config::new();
Atoms::add_to_config(&mut config);
let engine = wasmtime::Engine::new(&config).unwrap();
let store = wasmtime::Store::new(&engine);
assert!(Atoms::set_context(&store, Ctx).is_ok());
let linker = wasmtime::Linker::new(&store);
run_double_int_return_float(&linker);
}
fn store() -> wasmtime::Store {
wasmtime::Store::new(&wasmtime::Engine::new(&wasmtime::Config::new()).unwrap())
}
// Wiggle expects the caller to have an exported memory. Wasmtime can only
// provide this if the caller is a WebAssembly module, so we need to write
// a shim module:
fn shim_module(store: &wasmtime::Store) -> wasmtime::Module {
wasmtime::Module::new(
store.engine(),
r#"
(module
(memory 1)
(export "memory" (memory 0))
(import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32)))
(import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32)))
(func $int_float_args_shim (param i32 f32) (result i32)
local.get 0
local.get 1
call $int_float_args
)
(func $double_int_return_float_shim (param i32 i32) (result i32)
local.get 0
local.get 1
call $double_int_return_float
)
(export "int_float_args_shim" (func $int_float_args_shim))
(export "double_int_return_float_shim" (func $double_int_return_float_shim))
)
"#,
)
.unwrap()
}

View File

@@ -4,7 +4,7 @@
use anyhow::Result;
use wasmtime::*;
use wasmtime_wasi::{sync::WasiCtxBuilder, Wasi};
use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder};
fn main() -> Result<()> {
let engine = Engine::default();

5
examples/tokio/main.c Normal file
View File

@@ -0,0 +1,5 @@
int main(int argc, char *argv[]) {
// This example is specific to integrating with Rust's tokio ecosystem, so
// it isnt applicable to C/C++.
return 0;
}

163
examples/tokio/main.rs Normal file
View File

@@ -0,0 +1,163 @@
use anyhow::{anyhow, Error};
use std::future::Future;
use tokio::time::Duration;
use wasmtime::{Config, Engine, Linker, Module, Store};
// For this example we want to use the async version of wasmtime_wasi.
// Notably, this version of wasi uses a scheduler that will async yield
// when sleeping in `poll_oneoff`.
use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder};
#[tokio::main]
async fn main() -> Result<(), Error> {
// Create an environment shared by all wasm execution. This contains
// the `Engine` and the `Module` we are executing.
let env = Environment::new()?;
// The inputs to run_wasm are `Send`: we can create them here and send
// them to a new task that we spawn.
let inputs1 = Inputs::new(env.clone(), "Gussie");
let inputs2 = Inputs::new(env.clone(), "Willa");
let inputs3 = Inputs::new(env, "Sparky");
// Spawn some tasks. Insert sleeps before run_wasm so that the
// interleaving is easy to observe.
let join1 = tokio::task::spawn(async move { run_wasm(inputs1).await });
let join2 = tokio::task::spawn(async move {
tokio::time::sleep(Duration::from_millis(750)).await;
run_wasm(inputs2).await
});
let join3 = tokio::task::spawn(async move {
tokio::time::sleep(Duration::from_millis(1250)).await;
run_wasm(inputs3).await
});
// All tasks should join successfully.
join1.await??;
join2.await??;
join3.await??;
Ok(())
}
#[derive(Clone)]
struct Environment {
engine: Engine,
module: Module,
}
impl Environment {
pub fn new() -> Result<Self, Error> {
let mut config = Config::new();
// We need this engine's `Store`s to be async, and consume fuel, so
// that they can co-operatively yield during execution.
config.async_support(true);
config.consume_fuel(true);
// Install the host functions for `Wasi`.
Wasi::add_to_config(&mut config);
let engine = Engine::new(&config)?;
let module = Module::from_file(&engine, "target/wasm32-wasi/debug/tokio-wasi.wasm")?;
Ok(Self { engine, module })
}
}
struct Inputs {
env: Environment,
name: String,
}
impl Inputs {
fn new(env: Environment, name: &str) -> Self {
Self {
env,
name: name.to_owned(),
}
}
}
fn run_wasm(inputs: Inputs) -> impl Future<Output = Result<(), Error>> {
use std::pin::Pin;
use std::task::{Context, Poll};
// IMPORTANT: The current wasmtime API is very challenging to use safely
// on an async runtime. This RFC describes a redesign of the API that will
// resolve these safety issues:
// https://github.com/alexcrichton/rfcs-2/blob/new-api/accepted/new-api.md
// This is a "marker type future" which simply wraps some other future and
// the only purpose it serves is to forward the implementation of `Future`
// as well as have `unsafe impl Send` for itself, regardless of the
// underlying type.
//
// Note that the qctual safety of this relies on the fact that the inputs
// here are `Send`, the outputs (just () in this case) are `Send`, and the
// future itself is safe tu resume on other threads.
//
// For an in-depth discussion of the safety of moving Wasmtime's `Store`
// between threads, see
// https://docs.wasmtime.dev/examples-rust-multithreading.html.
struct UnsafeSend<T>(T);
// Note the `where` cause specifically ensures the output of the future to
// be `Send` is required. We specifically dont require `T` to be `Send`
// since that's the whole point of this function, but we require that
// everything used to construct `T` is `Send` below.
unsafe impl<T> Send for UnsafeSend<T>
where
T: Future,
T::Output: Send,
{
}
impl<T: Future> Future for UnsafeSend<T> {
type Output = T::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T::Output> {
// Note that this `unsafe` is unrelated to `Send`, it only has to do with "pin
// projection" and should be safe since it's all we do with the `Pin`.
unsafe { self.map_unchecked_mut(|p| &mut p.0).poll(cx) }
}
}
// This is a crucial assertion that needs to be here. The compiler
// typically checks this for us, but do to our `UnsafeSend` type the
// compiler isn't automatically checking this. The assertion here must
// assert that all arguments to this function are indeed `Send` because
// we're closing over them and sending them to other threads. It's only
// everything *internal* to the computation of this function which doesn't
// have to be `Send`.
fn assert_send<T: Send>(_t: &T) {}
assert_send(&inputs);
// Wrap up the `_run_wasm` function, which is *not* `Send`, but is safe to
// resume on other threads.
UnsafeSend(_run_wasm(inputs))
}
async fn _run_wasm(inputs: Inputs) -> Result<(), Error> {
let store = Store::new(&inputs.env.engine);
// WebAssembly execution will be paused for an async yield every time it
// consumes 10000 fuel. Fuel will be refilled u32::MAX times.
store.out_of_fuel_async_yield(u32::MAX, 10000);
Wasi::set_context(
&store,
WasiCtxBuilder::new()
// Let wasi print to this process's stdout.
.inherit_stdout()
// Set an environment variable so the wasm knows its name.
.env("NAME", &inputs.name)?
.build()?,
)
.map_err(|_| anyhow!("setting wasi context"))?;
let linker = Linker::new(&store);
// Instantiate
let instance = linker.instantiate_async(&inputs.env.module).await?;
instance
.get_typed_func::<(), ()>("_start")?
.call_async(())
.await?;
Ok(())
}

View File

@@ -0,0 +1,10 @@
[package]
name = "example-tokio-wasm"
version = "0.0.0"
authors = ["The Wasmtime Project Developers"]
edition = "2018"
publish = false
[[bin]]
path = "tokio-wasi.rs"
name = "tokio-wasi"

View File

@@ -0,0 +1,6 @@
fn main() {
let name = std::env::var("NAME").unwrap();
println!("Hello, world! My name is {}", name);
std::thread::sleep(std::time::Duration::from_secs(1));
println!("Goodbye from {}", name);
}

View File

@@ -5,7 +5,7 @@
use anyhow::Result;
use wasmtime::*;
use wasmtime_wasi::{sync::WasiCtxBuilder, Wasi};
use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder};
fn main() -> Result<()> {
tracing_subscriber::FmtSubscriber::builder()

View File

@@ -49,6 +49,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
// wasi-common
"wasi-common",
"wasi-cap-std-sync",
"wasi-tokio",
// wasmtime
"lightbeam",
"wasmtime-fiber",

View File

@@ -11,10 +11,7 @@ use std::{
};
use structopt::{clap::AppSettings, StructOpt};
use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType};
use wasmtime_wasi::{
sync::{Dir, WasiCtxBuilder},
Wasi,
};
use wasmtime_wasi::sync::{Dir, Wasi, WasiCtxBuilder};
#[cfg(feature = "wasi-nn")]
use wasmtime_wasi_nn::{WasiNn, WasiNnCtx};