diff --git a/Cargo.lock b/Cargo.lock index b60980de94ff..5c5df4fc7584 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,18 +316,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "cap-tempfile" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d0d2d91fc0da2e0d811289a0d3518b8aba8a122fc0014f702feb3967fbe857" -dependencies = [ - "cap-std", - "rand", - "rustix", - "uuid", -] - [[package]] name = "cap-time-ext" version = "2.0.1" @@ -2868,9 +2856,6 @@ name = "uuid" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" -dependencies = [ - "getrandom", -] [[package]] name = "v8" @@ -2941,11 +2926,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi-cap-std-sync" +name = "wasi-common" version = "18.0.0" dependencies = [ "anyhow", - "async-trait", + "bitflags 2.4.1", "cap-fs-ext", "cap-rand", "cap-std", @@ -2953,27 +2938,12 @@ dependencies = [ "fs-set-times", "io-extras", "io-lifetimes", + "libc", + "log", "once_cell", "rustix", "system-interface", "tempfile", - "tracing", - "wasi-common", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasi-common" -version = "18.0.0" -dependencies = [ - "anyhow", - "bitflags 2.4.1", - "cap-rand", - "cap-std", - "io-extras", - "log", - "rustix", - "tempfile", "test-log", "test-programs-artifacts", "thiserror", @@ -2981,7 +2951,6 @@ dependencies = [ "tracing", "tracing-subscriber", "wasmtime", - "wasmtime-wasi", "wiggle", "windows-sys 0.52.0", ] @@ -3006,23 +2975,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasi-tokio" -version = "18.0.0" -dependencies = [ - "anyhow", - "cap-std", - "cap-tempfile", - "io-extras", - "io-lifetimes", - "rustix", - "tempfile", - "tokio", - "wasi-cap-std-sync", - "wasi-common", - "wiggle", -] - [[package]] name = "wasm-bindgen" version = "0.2.87" @@ -3245,7 +3197,7 @@ dependencies = [ "serde_json", "target-lexicon", "tempfile", - "wasi-cap-std-sync", + "wasi-common", "wasm-encoder 0.41.0", "wasmparser 0.121.0", "wasmtime-cache", @@ -3257,7 +3209,6 @@ dependencies = [ "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", "wasmtime-runtime", - "wasmtime-wasi", "wasmtime-winch", "wat", "windows-sys 0.52.0", @@ -3279,10 +3230,9 @@ dependencies = [ "clap", "shuffling-allocator", "target-lexicon", - "wasi-cap-std-sync", + "wasi-common", "wasmtime", "wasmtime-cli-flags", - "wasmtime-wasi", "wasmtime-wasi-nn", "wat", ] @@ -3305,7 +3255,6 @@ dependencies = [ "log", "once_cell", "tracing", - "wasi-cap-std-sync", "wasi-common", "wasmtime", "wasmtime-c-api-macros", @@ -3352,6 +3301,7 @@ dependencies = [ "bstr", "bytes", "bytesize", + "cfg-if", "clap", "component-macro-test", "component-test-util", @@ -3379,6 +3329,7 @@ dependencies = [ "tokio", "tracing", "walkdir", + "wasi-common", "wasmparser 0.121.0", "wasmtime", "wasmtime-cache", @@ -3676,11 +3627,11 @@ dependencies = [ "cap-rand", "cap-std", "cap-time-ext", + "cfg-if", "fs-set-times", "futures", "io-extras", "io-lifetimes", - "libc", "log", "once_cell", "rustix", @@ -3693,9 +3644,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", - "wasi-cap-std-sync", "wasi-common", - "wasi-tokio", "wasmtime", "wiggle", "windows-sys 0.52.0", @@ -3738,8 +3687,8 @@ dependencies = [ "thiserror", "tracing", "walkdir", + "wasi-common", "wasmtime", - "wasmtime-wasi", "wiggle", ] @@ -3752,7 +3701,6 @@ dependencies = [ "rand", "wasi-common", "wasmtime", - "wasmtime-wasi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6b01f8282fef..be00325fd75d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,9 @@ wasmtime-cranelift = { workspace = true, optional = true } wasmtime-environ = { workspace = true } wasmtime-explorer = { workspace = true, optional = true } wasmtime-wast = { workspace = true, optional = true } +wasi-common = { workspace = true, default-features = true, features = [ + "exit", +] } wasmtime-wasi = { workspace = true, default-features = true, features = [ "exit", ] } @@ -55,13 +58,14 @@ humantime = { workspace = true } async-trait = { workspace = true } bytes = { workspace = true } +cfg-if = { workspace = true } tokio = { workspace = true, optional = true, features = [ "signal", "macros" ] } hyper = { workspace = true, optional = true } http = { workspace = true, optional = true } http-body-util = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["mm", "param"] } +rustix = { workspace = true, features = ["mm", "param", "process"] } [dev-dependencies] # depend again on wasmtime to activate its default features for tests @@ -181,8 +185,6 @@ wiggle = { path = "crates/wiggle", version = "=18.0.0", default-features = false wiggle-macro = { path = "crates/wiggle/macro", version = "=18.0.0" } wiggle-generate = { path = "crates/wiggle/generate", version = "=18.0.0" } wasi-common = { path = "crates/wasi-common", version = "=18.0.0", default-features = false } -wasi-tokio = { path = "crates/wasi-common/tokio", version = "=18.0.0" } -wasi-cap-std-sync = { path = "crates/wasi-common/cap-std-sync", version = "=18.0.0" } wasmtime-fuzzing = { path = "crates/fuzzing" } wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=18.0.0" } wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=18.0.0" } @@ -358,7 +360,7 @@ disable-logging = ["log/max_level_off", "tracing/max_level_off"] # the internal mapping for what they enable in Wasmtime itself. wasi-nn = ["dep:wasmtime-wasi-nn"] wasi-threads = ["dep:wasmtime-wasi-threads"] -wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper", "wasmtime-wasi-http?/sync"] +wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"] pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"] component-model = [ "wasmtime/component-model", diff --git a/benches/instantiation.rs b/benches/instantiation.rs index e43bd650545e..d4ced3fca053 100644 --- a/benches/instantiation.rs +++ b/benches/instantiation.rs @@ -6,8 +6,8 @@ use std::process::Command; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use std::thread; +use wasi_common::{sync::WasiCtxBuilder, WasiCtx}; use wasmtime::*; -use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; fn store(engine: &Engine) -> Store { let wasi = WasiCtxBuilder::new().build(); @@ -48,7 +48,7 @@ fn bench_sequential(c: &mut Criterion, path: &Path) { // benchmark programs. linker.func_wrap("bench", "start", || {}).unwrap(); linker.func_wrap("bench", "end", || {}).unwrap(); - wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); let pre = linker .instantiate_pre(&module) .expect("failed to pre-instantiate"); @@ -82,7 +82,7 @@ fn bench_parallel(c: &mut Criterion, path: &Path) { // benchmark programs. linker.func_wrap("bench", "start", || {}).unwrap(); linker.func_wrap("bench", "end", || {}).unwrap(); - wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); let pre = Arc::new( linker .instantiate_pre(&module) diff --git a/benches/wasi.rs b/benches/wasi.rs index 41dc25e3bd8a..77944071ba9e 100644 --- a/benches/wasi.rs +++ b/benches/wasi.rs @@ -2,8 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::{fs::File, path::Path, time::Instant}; +use wasi_common::{sync::WasiCtxBuilder, WasiCtx}; use wasmtime::{Engine, Linker, Module, Store, TypedFunc}; -use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; criterion_group!(benches, bench_wasi); criterion_main!(benches); @@ -53,7 +53,7 @@ fn instantiate(wat: &[u8]) -> (Store, TypedFunc) { let mut store = Store::new(&engine, wasi); let module = Module::new(&engine, wat).unwrap(); let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); let instance = linker.instantiate(&mut store, &module).unwrap(); let run = instance.get_typed_func(&mut store, "run").unwrap(); (store, run) @@ -77,9 +77,9 @@ fn wasi_context() -> WasiCtx { ]) .unwrap() .preopened_dir( - wasmtime_wasi::Dir::open_ambient_dir( + wasi_common::sync::Dir::open_ambient_dir( "benches/wasi", - wasmtime_wasi::ambient_authority(), + wasi_common::sync::ambient_authority(), ) .unwrap(), "/", diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml index 2170938b35e8..3cee803f77e5 100644 --- a/crates/bench-api/Cargo.toml +++ b/crates/bench-api/Cargo.toml @@ -25,9 +25,8 @@ wasmtime = { workspace = true, default-features = true } wasmtime-cli-flags = { workspace = true, default-features = true, features = [ "cranelift", ] } -wasmtime-wasi = { workspace = true, default-features = true } +wasi-common = { workspace = true, default-features = true } wasmtime-wasi-nn = { workspace = true, optional = true } -wasi-cap-std-sync = { workspace = true } cap-std = { workspace = true } clap = { workspace = true } diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index bb4f6ed551ba..ebcef9ac6b38 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -142,9 +142,9 @@ use std::os::raw::{c_int, c_void}; use std::slice; use std::{env, path::PathBuf}; use target_lexicon::Triple; +use wasi_common::{sync::WasiCtxBuilder, I32Exit, WasiCtx}; use wasmtime::{Engine, Instance, Linker, Module, Store}; use wasmtime_cli_flags::CommonOptions; -use wasmtime_wasi::{sync::WasiCtxBuilder, I32Exit, WasiCtx}; pub type ExitCode = c_int; pub const OK: ExitCode = 0; @@ -304,20 +304,20 @@ pub extern "C" fn wasm_bench_create( let stdout = std::fs::File::create(&stdout_path) .with_context(|| format!("failed to create {}", stdout_path.display()))?; let stdout = cap_std::fs::File::from_std(stdout); - let stdout = wasi_cap_std_sync::file::File::from_cap_std(stdout); + let stdout = wasi_common::sync::file::File::from_cap_std(stdout); cx.stdout(Box::new(stdout)); let stderr = std::fs::File::create(&stderr_path) .with_context(|| format!("failed to create {}", stderr_path.display()))?; let stderr = cap_std::fs::File::from_std(stderr); - let stderr = wasi_cap_std_sync::file::File::from_cap_std(stderr); + let stderr = wasi_common::sync::file::File::from_cap_std(stderr); cx.stderr(Box::new(stderr)); if let Some(stdin_path) = &stdin_path { let stdin = std::fs::File::open(stdin_path) .with_context(|| format!("failed to open {}", stdin_path.display()))?; let stdin = cap_std::fs::File::from_std(stdin); - let stdin = wasi_cap_std_sync::file::File::from_cap_std(stdin); + let stdin = wasi_common::sync::file::File::from_cap_std(stdin); cx.stdin(Box::new(stdin)); } @@ -460,7 +460,7 @@ impl BenchState { let fuel = options.wasm.fuel; if options.wasi.common != Some(false) { - wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + wasi_common::sync::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; } #[cfg(feature = "wasi-nn")] diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 3ef655e0443e..a4b8e063931b 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -31,10 +31,9 @@ tracing = { workspace = true } wat = { workspace = true, optional = true } # Optional dependencies for the `wasi` feature -wasi-cap-std-sync = { workspace = true, optional = true } wasmtime-wasi = { workspace = true, default-features = true, optional = true } cap-std = { workspace = true, optional = true } -wasi-common = { workspace = true, optional = true } +wasi-common = { workspace = true, optional = true, features = ["sync"] } # Optional dependencies for the `async` feature futures = { workspace = true, optional = true } @@ -44,7 +43,7 @@ async = ['wasmtime/async', 'futures'] profiling = ["wasmtime/profiling"] cache = ["wasmtime/cache"] parallel-compilation = ['wasmtime/parallel-compilation'] -wasi = ['wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std', 'wasi-common'] +wasi = ['wasmtime-wasi', 'cap-std', 'wasi-common'] logging = ['dep:env_logger'] disable-logging = ["log/max_level_off", "tracing/max_level_off"] coredump = ["wasmtime/coredump"] diff --git a/crates/c-api/src/error.rs b/crates/c-api/src/error.rs index 0c9dde87900b..9ebc389a7372 100644 --- a/crates/c-api/src/error.rs +++ b/crates/c-api/src/error.rs @@ -56,7 +56,7 @@ pub extern "C" fn wasmtime_error_message(error: &wasmtime_error_t, message: &mut #[no_mangle] pub extern "C" fn wasmtime_error_exit_status(raw: &wasmtime_error_t, status: &mut i32) -> bool { #[cfg(feature = "wasi")] - if let Some(exit) = raw.error.downcast_ref::() { + if let Some(exit) = raw.error.downcast_ref::() { *status = exit.0; return true; } diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs index 72954ddeb572..32ff1a3d8f26 100644 --- a/crates/c-api/src/store.rs +++ b/crates/c-api/src/store.rs @@ -71,7 +71,7 @@ pub type CStoreContextMut<'a> = StoreContextMut<'a, StoreData>; pub struct StoreData { foreign: crate::ForeignData, #[cfg(feature = "wasi")] - pub(crate) wasi: Option, + pub(crate) wasi: Option, /// Temporary storage for usage during a wasm->host call to store values /// in a slice we pass to the C API. diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index 848409bcc866..7061eacc030e 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -9,8 +9,8 @@ use std::fs::File; use std::os::raw::{c_char, c_int}; use std::path::{Path, PathBuf}; use std::slice; -use wasi_common::pipe::ReadPipe; -use wasmtime_wasi::{ +use wasi_common::{ + pipe::ReadPipe, sync::{Dir, TcpListener, WasiCtxBuilder}, WasiCtx, }; @@ -100,7 +100,7 @@ impl wasi_config_t { } WasiConfigReadPipe::File(file) => { let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); + let file = wasi_common::sync::file::File::from_cap_std(file); builder.stdin(Box::new(file)); } WasiConfigReadPipe::Bytes(binary) => { @@ -115,7 +115,7 @@ impl wasi_config_t { } WasiConfigWritePipe::File(file) => { let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); + let file = wasi_common::sync::file::File::from_cap_std(file); builder.stdout(Box::new(file)); } }; @@ -126,7 +126,7 @@ impl wasi_config_t { } WasiConfigWritePipe::File(file) => { let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); + let file = wasi_common::sync::file::File::from_cap_std(file); builder.stderr(Box::new(file)); } }; diff --git a/crates/test-programs/src/bin/api_proxy.rs b/crates/test-programs/src/bin/api_proxy.rs index a0b26cfd722c..e9887ff1f191 100644 --- a/crates/test-programs/src/bin/api_proxy.rs +++ b/crates/test-programs/src/bin/api_proxy.rs @@ -20,6 +20,8 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T { assert!(request.authority().is_some()); assert!(request.path_with_query().is_some()); + test_filesystem(); + let header = String::from("custom-forbidden-header"); let req_hdrs = request.headers(); @@ -55,3 +57,7 @@ impl bindings::exports::wasi::http::incoming_handler::Guest for T { // Technically this should not be here for a proxy, but given the current // framework for tests it's required since this file is built as a `bin` fn main() {} + +fn test_filesystem() { + assert!(std::fs::File::open(".").is_err()); +} diff --git a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs index 240664dd9cd3..d950d7df6d78 100644 --- a/crates/test-programs/src/bin/preview1_fd_filestat_set.rs +++ b/crates/test-programs/src/bin/preview1_fd_filestat_set.rs @@ -1,9 +1,10 @@ use std::{env, process, time::Duration}; -use test_programs::preview1::{assert_fs_time_eq, open_scratch_directory, TestConfig}; +use test_programs::preview1::{ + assert_errno, assert_fs_time_eq, open_scratch_directory, TestConfig, +}; -unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { - let cfg = TestConfig::from_env(); - // Create a file in the scratch directory. +unsafe fn test_fd_filestat_set_size_rw(dir_fd: wasi::Fd) { + // Create a file in the scratch directory, opened read/write let file_fd = wasi::path_open( dir_fd, 0, @@ -29,6 +30,56 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2"); assert_eq!(stat.size, 100, "file size should be 100"); + wasi::fd_close(file_fd).expect("failed to close fd"); + wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file"); +} + +unsafe fn test_fd_filestat_set_size_ro(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. Creating a file implies opening it for writing, so + // we have to close and re-open read-only to observe read-only behavior. + let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0) + .expect("failed to create file"); + wasi::fd_close(file_fd).expect("failed to close fd"); + + // Open the created file read-only + let file_fd = wasi::path_open(dir_fd, 0, "file", 0, wasi::RIGHTS_FD_READ, 0, 0) + .expect("failed to create file"); + + // Check file size + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat"); + assert_eq!(stat.size, 0, "file size should be 0"); + + // Check fd_filestat_set_size on a file opened read-only fails with EINVAL, like ftruncate is defined to do on posix + assert_errno!( + wasi::fd_filestat_set_size(file_fd, 100) + .expect_err("fd_filestat_set_size should error when file is opened read-only"), + windows => wasi::ERRNO_ACCES, + wasi::ERRNO_INVAL + ); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2"); + assert_eq!(stat.size, 0, "file size should remain 0"); + + wasi::fd_close(file_fd).expect("failed to close fd"); + wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file"); +} + +unsafe fn test_fd_filestat_set_times(dir_fd: wasi::Fd, rights: wasi::Rights) { + let cfg = TestConfig::from_env(); + + // Create a file in the scratch directory. OFLAGS_CREAT implies opening for writing, so we will + // close it and re-open with the desired rights (FD_READ for read only, FD_READ | FD_WRITE for + // readwrite) + let file_fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0) + .expect("failed to create file"); + wasi::fd_close(file_fd).expect("failed to close fd"); + + // Open the file with the rights given. + let file_fd = + wasi::path_open(dir_fd, 0, "file", 0, rights, 0, 0).expect("failed to create file"); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2"); + // Check fd_filestat_set_times let old_atim = Duration::from_nanos(stat.atim); let new_mtim = Duration::from_nanos(stat.mtim) - cfg.fs_time_precision() * 2; @@ -41,7 +92,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { .expect("fd_filestat_set_times"); let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 3"); - assert_eq!(stat.size, 100, "file size should remain unchanged at 100"); + assert_eq!(stat.size, 0, "file size should remain unchanged at 0"); // Support accuracy up to at least 1ms assert_fs_time_eq!( @@ -59,7 +110,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { // assert_eq!(status, wasi::EINVAL, "ATIM & ATIM_NOW can't both be set"); wasi::fd_close(file_fd).expect("failed to close fd"); - wasi::path_unlink_file(dir_fd, "file").expect("failed to remove dir"); + wasi::path_unlink_file(dir_fd, "file").expect("failed to remove file"); } fn main() { let mut args = env::args(); @@ -81,5 +132,18 @@ fn main() { }; // Run the tests. - unsafe { test_fd_filestat_set(dir_fd) } + + unsafe { test_fd_filestat_set_size_rw(dir_fd) } + unsafe { test_fd_filestat_set_size_ro(dir_fd) } + + // The fd_filestat_set_times function should behave the same whether the file + // descriptor is open for read-only or read-write, because the underlying + // permissions of the file determine whether or not the filestat can be + // set or not, not than the open mode. + if test_programs::preview1::config().support_dangling_filesystem() { + // Guarding to run on non-windows filesystems. Windows rejects set-times on read-only + // files. + unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ) } + } + unsafe { test_fd_filestat_set_times(dir_fd, wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE) } } diff --git a/crates/test-programs/src/bin/preview1_path_link.rs b/crates/test-programs/src/bin/preview1_path_link.rs index b087e0740003..cc14a0a01ac5 100644 --- a/crates/test-programs/src/bin/preview1_path_link.rs +++ b/crates/test-programs/src/bin/preview1_path_link.rs @@ -1,89 +1,82 @@ use std::{env, process}; use test_programs::preview1::{assert_errno, config, create_file, open_scratch_directory}; -unsafe fn create_or_open(dir_fd: wasi::Fd, name: &str, flags: wasi::Oflags) -> wasi::Fd { - let file_fd = wasi::path_open(dir_fd, 0, name, flags, 0, 0, 0) - .unwrap_or_else(|_| panic!("opening '{}'", name)); - assert!( - file_fd > libc::STDERR_FILENO as wasi::Fd, - "file descriptor range check", - ); - file_fd -} - -unsafe fn open_link(dir_fd: wasi::Fd, name: &str) -> wasi::Fd { - let file_fd = wasi::path_open(dir_fd, 0, name, 0, 0, 0, 0) - .unwrap_or_else(|_| panic!("opening a link '{}'", name)); - assert!( - file_fd > libc::STDERR_FILENO as wasi::Fd, - "file descriptor range check", - ); - file_fd -} - -// This is temporary until `wasi` implements `Debug` and `PartialEq` for -// `wasi::Filestat`. -fn filestats_assert_eq(left: wasi::Filestat, right: wasi::Filestat) { - assert_eq!(left.dev, right.dev, "dev should be equal"); - assert_eq!(left.ino, right.ino, "ino should be equal"); - assert_eq!(left.atim, right.atim, "atim should be equal"); - assert_eq!(left.ctim, right.ctim, "ctim should be equal"); - assert_eq!(left.mtim, right.mtim, "mtim should be equal"); - assert_eq!(left.size, right.size, "size should be equal"); - assert_eq!(left.nlink, right.nlink, "nlink should be equal"); - assert_eq!(left.filetype, right.filetype, "filetype should be equal"); +// These are all macro-rules so the panic line number shows us where +// things went wrong. + +macro_rules! filestats_assert_eq { + ($left:ident, $right:ident) => { + assert_eq!($left.dev, $right.dev, "dev should be equal"); + assert_eq!($left.ino, $right.ino, "ino should be equal"); + assert_eq!($left.atim, $right.atim, "atim should be equal"); + assert_eq!($left.ctim, $right.ctim, "ctim should be equal"); + assert_eq!($left.mtim, $right.mtim, "mtim should be equal"); + assert_eq!($left.size, $right.size, "size should be equal"); + assert_eq!($left.nlink, $right.nlink, "nlink should be equal"); + assert_eq!($left.filetype, $right.filetype, "filetype should be equal"); + }; } -// This is temporary until `wasi` implements `Debug` and `PartialEq` for -// `wasi::Fdstat`. -fn fdstats_assert_eq(left: wasi::Fdstat, right: wasi::Fdstat) { - assert_eq!(left.fs_flags, right.fs_flags, "fs_flags should be equal"); - assert_eq!( - left.fs_filetype, right.fs_filetype, - "fs_filetype should be equal" - ); - assert_eq!( - left.fs_rights_base, right.fs_rights_base, - "fs_rights_base should be equal" - ); - assert_eq!( - left.fs_rights_inheriting, right.fs_rights_inheriting, - "fs_rights_inheriting should be equal" - ); +macro_rules! fdstats_assert_eq { + ($left:ident, $right:ident) => { + assert_eq!($left.fs_flags, $right.fs_flags, "fs_flags should be equal"); + assert_eq!( + $left.fs_filetype, $right.fs_filetype, + "fs_filetype should be equal" + ); + assert_eq!( + $left.fs_rights_base, $right.fs_rights_base, + "fs_rights_base should be equal" + ); + assert_eq!( + $left.fs_rights_inheriting, $right.fs_rights_inheriting, + "fs_rights_inheriting should be equal" + ); + }; } -unsafe fn check_rights(orig_fd: wasi::Fd, link_fd: wasi::Fd) { - // Compare Filestats - let orig_filestat = wasi::fd_filestat_get(orig_fd).expect("reading filestat of the source"); - let link_filestat = wasi::fd_filestat_get(link_fd).expect("reading filestat of the link"); - filestats_assert_eq(orig_filestat, link_filestat); - - // Compare Fdstats - let orig_fdstat = wasi::fd_fdstat_get(orig_fd).expect("reading fdstat of the source"); - let link_fdstat = wasi::fd_fdstat_get(link_fd).expect("reading fdstat of the link"); - fdstats_assert_eq(orig_fdstat, link_fdstat); +macro_rules! check_rights { + ($orig_fd:ident, $link_fd:ident) => { + let orig_filestat = + wasi::fd_filestat_get($orig_fd).expect("reading filestat of the source"); + let link_filestat = wasi::fd_filestat_get($link_fd).expect("reading filestat of the link"); + filestats_assert_eq!(orig_filestat, link_filestat); + + // Compare Fdstats + let orig_fdstat = wasi::fd_fdstat_get($orig_fd).expect("reading fdstat of the source"); + let link_fdstat = wasi::fd_fdstat_get($link_fd).expect("reading fdstat of the link"); + fdstats_assert_eq!(orig_fdstat, link_fdstat); + }; } - // Extra calls of fd_close are needed for Windows, which will not remove // the directory until all handles are closed. unsafe fn test_path_link(dir_fd: wasi::Fd) { // Create a file - let file_fd = create_or_open(dir_fd, "file", wasi::OFLAGS_CREAT); + let create_fd = + wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).expect("create file"); + wasi::fd_close(create_fd).unwrap(); + + // Open a fresh descriptor to the file. We won't have a write right that was implied by OFLAGS_CREAT + // above. + let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("open file"); // Create a link in the same directory and compare rights wasi::path_link(dir_fd, 0, "file", dir_fd, "link") .expect("creating a link in the same directory"); - let link_fd = open_link(dir_fd, "link"); - check_rights(file_fd, link_fd); + + let link_fd = wasi::path_open(dir_fd, 0, "link", 0, 0, 0, 0).expect("open link"); + + check_rights!(file_fd, link_fd); wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows wasi::path_unlink_file(dir_fd, "link").expect("removing a link"); // Create a link in a different directory and compare rights wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory"); - let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY); + let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("open subdir directory"); wasi::path_link(dir_fd, 0, "file", subdir_fd, "link").expect("creating a link in subdirectory"); - let link_fd = open_link(subdir_fd, "link"); - check_rights(file_fd, link_fd); + let link_fd = wasi::path_open(subdir_fd, 0, "link", 0, 0, 0, 0).expect("open link in subdir"); + check_rights!(file_fd, link_fd); wasi::fd_close(link_fd).expect("Closing link_fd"); // needed for Windows wasi::path_unlink_file(subdir_fd, "link").expect("removing a link"); wasi::fd_close(subdir_fd).expect("Closing subdir_fd"); // needed for Windows @@ -118,7 +111,8 @@ unsafe fn test_path_link(dir_fd: wasi::Fd) { // Create a link to a directory wasi::path_create_directory(dir_fd, "subdir").expect("creating a subdirectory"); - let subdir_fd = create_or_open(dir_fd, "subdir", wasi::OFLAGS_DIRECTORY); + let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("open new descriptor to subdir"); assert_errno!( wasi::path_link(dir_fd, 0, "subdir", dir_fd, "link") diff --git a/crates/test-programs/src/bin/preview1_path_open_read_write.rs b/crates/test-programs/src/bin/preview1_path_open_read_write.rs index 3d19daffcbd9..ccee4f5d626c 100644 --- a/crates/test-programs/src/bin/preview1_path_open_read_write.rs +++ b/crates/test-programs/src/bin/preview1_path_open_read_write.rs @@ -30,10 +30,14 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) { buf: write_buffer.as_ptr(), buf_len: write_buffer.len(), }; + // PERM is only the failure on windows under wasmtime-wasi. wasi-common + // fails on windows with BADF, so we cant use the `windows =>` syntax + // because that doesnt support alternatives like the agnostic syntax does. assert_errno!( wasi::fd_write(f_readonly, &[ciovec]) .err() .expect("read of writeonly fails"), + wasi::ERRNO_PERM, wasi::ERRNO_BADF ); @@ -53,10 +57,12 @@ unsafe fn test_path_open_read_write(dir_fd: wasi::Fd) { "writeonly has write right" ); + // See above for description of PERM assert_errno!( wasi::fd_read(f_writeonly, &[iovec]) .err() .expect("read of writeonly fails"), + wasi::ERRNO_PERM, wasi::ERRNO_BADF ); let bytes_written = wasi::fd_write(f_writeonly, &[ciovec]).expect("write to writeonly"); diff --git a/crates/wasi-common/Cargo.toml b/crates/wasi-common/Cargo.toml index c0479b9bef94..e230f59f42b3 100644 --- a/crates/wasi-common/Cargo.toml +++ b/crates/wasi-common/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["webassembly", "wasm"] repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" edition.workspace = true -include = ["src/**/*", "WASI/phases/**/*", "README.md", "LICENSE", "build.rs"] +include = ["src/**/*", "tests/**/*", "WASI/phases/**/*", "README.md", "LICENSE", "build.rs"] build = "build.rs" # This doesn't actually link to a native library, but it allows us to set env @@ -24,18 +24,33 @@ workspace = true anyhow = { workspace = true } thiserror = { workspace = true } wiggle = { workspace = true } -wasmtime = { workspace = true, optional = true } tracing = { workspace = true } cap-std = { workspace = true } cap-rand = { workspace = true } bitflags = { workspace = true } log = { workspace = true } +# Optional, enabled by wasmtime feature: +wasmtime = { workspace = true, optional = true, features = ['runtime'] } +# Optional, enabled by sync feature: +cap-fs-ext = { workspace = true, optional = true } +cap-time-ext = { workspace = true, optional = true } +fs-set-times = { workspace = true, optional = true } +system-interface = { workspace = true, features = ["cap_std_impls"], optional = true } +io-lifetimes = { workspace = true, optional = true } +# Optional, enabled by tokio feature: +tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"], optional = true } + +# Optional, enabled by exit feature: +libc = { workspace = true, optional = true } + [target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["fs"] } +rustix = { workspace = true, features = ["fs", "event"] } [target.'cfg(windows)'.dependencies] +once_cell = { workspace = true } io-extras = { workspace = true } +rustix = { workspace = true, features = ["net"] } [target.'cfg(windows)'.dependencies.windows-sys] workspace = true @@ -48,13 +63,12 @@ features = [ tempfile = { workspace = true } test-log = { workspace = true } tracing-subscriber = { workspace = true } -wasmtime = { workspace = true, features = ['cranelift', 'async'] } -wasmtime-wasi = { path = '../wasi', features = ['sync', 'tokio'] } +wasmtime = { workspace = true, features = ['cranelift', 'async', 'runtime'] } test-programs-artifacts = { workspace = true } tokio = { workspace = true, features = ['macros', 'rt-multi-thread'] } [features] -default = ["trace_log", "wasmtime"] +default = ["trace_log", "wasmtime", "sync"] # This feature enables the `tracing` logs in the calls to target the `log` # ecosystem of backends (e.g. `env_logger`. Disable this if you want to use # `tracing-subscriber`. @@ -63,4 +77,28 @@ trace_log = [ "wiggle/tracing_log", "tracing/log" ] # crate if they want the snapshots to have metadata available. wiggle_metadata = ["wiggle/wiggle_metadata"] # This feature enables integration with wasmtime. -wasmtime = ["dep:wasmtime", "wiggle/wasmtime"] \ No newline at end of file +wasmtime = [ + "dep:wasmtime", + "wiggle/wasmtime", +] +# This feature enables an implementation of the Wasi traits for a +# synchronous wasmtime embedding. +sync = [ + "wasmtime", + "dep:cap-fs-ext", + "dep:cap-time-ext", + "dep:fs-set-times", + "dep:system-interface", + "dep:io-lifetimes", +] +tokio = [ + "sync", + "wasmtime/async", + "wiggle/wasmtime_async", + "dep:tokio", +] +exit = [ "wasmtime", "dep:libc" ] + +[[test]] +name = "all" +required-features = ["wasmtime", "sync", "tokio"] diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml deleted file mode 100644 index 7eeec693f285..000000000000 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "wasi-cap-std-sync" -version.workspace = true -authors.workspace = true -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.workspace = true -include = ["src/**/*", "README.md", "LICENSE" ] - -[lints] -workspace = true - -[dependencies] -wasi-common = { workspace = true } -async-trait = { workspace = true } -anyhow = { workspace = true } -cap-std = { workspace = true } -cap-fs-ext = { workspace = true } -cap-time-ext = { workspace = true } -cap-rand = { workspace = true } -fs-set-times = { workspace = true } -system-interface = { workspace = true, features = ["cap_std_impls"] } -tracing = { workspace = true } -io-lifetimes = { workspace = true } - -[target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["fs", "event"] } - -[target.'cfg(windows)'.dependencies] -once_cell = { workspace = true } -io-extras = { workspace = true } -rustix = { workspace = true, features = ["net"] } - -[target.'cfg(windows)'.dependencies.windows-sys] -workspace = true -features = [ - "Win32_Foundation", -] - -[dev-dependencies] -tempfile = "3.1.0" diff --git a/crates/wasi-common/cap-std-sync/LICENSE b/crates/wasi-common/cap-std-sync/LICENSE deleted file mode 100644 index f9d81955f4bc..000000000000 --- a/crates/wasi-common/cap-std-sync/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/crates/wasi-common/cap-std-sync/README.md b/crates/wasi-common/cap-std-sync/README.md deleted file mode 100644 index 76497dbcf332..000000000000 --- a/crates/wasi-common/cap-std-sync/README.md +++ /dev/null @@ -1 +0,0 @@ -WASI implementation in Rust, using cap-std. diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 9428b2cc4df3..e3c18d6c94ce 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -1,3 +1,15 @@ +//! # wasi-common +//! +//! This is Wasmtime's legacy implementation of WASI 0.1 (Preview 1). The +//! Wasmtime maintainers suggest all users upgrade to the implementation +//! of WASI 0.1 and 0.2 provided by the `wasmtime-wasi` crate. This +//! implementation remains in the wasmtime tree because it is required to use +//! the `wasmtime-wasi-threads` crate, an implementation of the `wasi-threads` +//! proposal which is not compatible with WASI 0.2. +//! +//! In addition to integration with Wasmtime, this implementation may be used +//! by other runtimes by disabling the `wasmtime` feature on this crate. +//! //! ## The `WasiFile` and `WasiDir` traits //! //! The WASI specification only defines one `handle` type, `fd`, on which all @@ -28,7 +40,11 @@ //! reason about access to the local filesystem by examining what impls are //! linked into an application. We found that this separation of concerns also //! makes it pretty enjoyable to write alternative implementations, e.g. a -//! virtual filesystem (which will land in a future PR). +//! virtual filesystem. +//! +//! Implementations of the `WasiFile` and `WasiDir` traits are provided +//! for synchronous embeddings (i.e. Config::async_support(false)) in +//! `wasi_common::sync` and for Tokio embeddings in `wasi_common::tokio`. //! //! ## Traits for the rest of WASI's features //! @@ -63,7 +79,11 @@ pub mod random; pub mod sched; pub mod snapshots; mod string_array; +#[cfg(feature = "sync")] +pub mod sync; pub mod table; +#[cfg(feature = "tokio")] +pub mod tokio; pub use cap_rand::RngCore; pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock}; @@ -74,3 +94,100 @@ pub use file::WasiFile; pub use sched::{Poll, WasiSched}; pub use string_array::{StringArray, StringArrayError}; pub use table::Table; + +// 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. +#[cfg(feature = "wasmtime")] +#[doc(hidden)] +#[macro_export] +macro_rules! define_wasi { + ($async_mode:tt $($bounds:tt)*) => { + + use wasmtime::Linker; + + pub fn add_to_linker( + linker: &mut Linker, + get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, + ) -> anyhow::Result<()> + where U: Send + + crate::snapshots::preview_0::wasi_unstable::WasiUnstable + + crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1, + $($bounds)* + { + snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?; + snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?; + Ok(()) + } + + pub mod snapshots { + pub mod preview_1 { + wiggle::wasmtime_integration!({ + // The wiggle code to integrate with lives here: + target: crate::snapshots::preview_1, + // This must be the same witx document as used above. This should be ensured by + // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`. + witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], + errors: { errno => trappable Error }, + $async_mode: * + }); + } + pub mod preview_0 { + wiggle::wasmtime_integration!({ + // The wiggle code to integrate with lives here: + target: crate::snapshots::preview_0, + // This must be the same witx document as used above. This should be ensured by + // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`. + witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"], + errors: { errno => trappable Error }, + $async_mode: * + }); + } + } +}} + +/// Exit the process with a conventional OS error code as long as Wasmtime +/// understands the error. If the error is not an `I32Exit` or `Trap`, return +/// the error back to the caller for it to decide what to do. +/// +/// Note: this function is designed for usage where it is acceptable for +/// Wasmtime failures to terminate the parent process, such as in the Wasmtime +/// CLI; this would not be suitable for use in multi-tenant embeddings. +#[cfg(feature = "exit")] +pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error { + use std::process; + use wasmtime::Trap; + + // If a specific WASI error code was requested then that's + // forwarded through to the process here without printing any + // extra error information. + let code = e.downcast_ref::().map(|e| e.0); + if let Some(exit) = code { + // Print the error message in the usual way. + // On Windows, exit status 3 indicates an abort (see below), + // so return 1 indicating a non-zero status to avoid ambiguity. + if cfg!(windows) && exit >= 3 { + process::exit(1); + } + process::exit(exit); + } + + // If the program exited because of a trap, return an error code + // to the outside environment indicating a more severe problem + // than a simple failure. + if e.is::() { + eprintln!("Error: {:?}", e); + + if cfg!(unix) { + // On Unix, return the error code of an abort. + process::exit(128 + libc::SIGABRT); + } else if cfg!(windows) { + // On Windows, return 3. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 + process::exit(3); + } + } + + e +} diff --git a/crates/wasi-common/cap-std-sync/src/clocks.rs b/crates/wasi-common/src/sync/clocks.rs similarity index 94% rename from crates/wasi-common/cap-std-sync/src/clocks.rs rename to crates/wasi-common/src/sync/clocks.rs index 71eb34252bb0..1b85fe1140a4 100644 --- a/crates/wasi-common/cap-std-sync/src/clocks.rs +++ b/crates/wasi-common/src/sync/clocks.rs @@ -1,7 +1,7 @@ +use crate::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock}; use cap_std::time::{Duration, Instant, SystemTime}; use cap_std::{ambient_authority, AmbientAuthority}; use cap_time_ext::{MonotonicClockExt, SystemClockExt}; -use wasi_common::clocks::{WasiClocks, WasiMonotonicClock, WasiSystemClock}; pub struct SystemClock(cap_std::time::SystemClock); diff --git a/crates/wasi-common/cap-std-sync/src/dir.rs b/crates/wasi-common/src/sync/dir.rs similarity index 92% rename from crates/wasi-common/cap-std-sync/src/dir.rs rename to crates/wasi-common/src/sync/dir.rs index c4ba736502d8..b54e88e8b894 100644 --- a/crates/wasi-common/cap-std-sync/src/dir.rs +++ b/crates/wasi-common/src/sync/dir.rs @@ -1,14 +1,14 @@ -use crate::file::{filetype_from, File}; +use crate::sync::file::{filetype_from, File}; +use crate::{ + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{FdFlags, FileType, Filestat, OFlags}, + Error, ErrorExt, +}; use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, OpenOptionsMaybeDirExt, SystemTimeSpec}; use cap_std::fs; use std::any::Any; use std::path::{Path, PathBuf}; use system_interface::fs::GetSetFdFlags; -use wasi_common::{ - dir::{ReaddirCursor, ReaddirEntity, WasiDir}, - file::{FdFlags, FileType, Filestat, OFlags}, - Error, ErrorExt, -}; pub struct Dir(fs::Dir); @@ -71,9 +71,7 @@ impl Dir { // ideally OpenOptions would just support this though: // https://github.com/bytecodealliance/cap-std/issues/146 if fdflags.intersects( - wasi_common::file::FdFlags::DSYNC - | wasi_common::file::FdFlags::SYNC - | wasi_common::file::FdFlags::RSYNC, + crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC, ) { return Err(Error::not_supported().context("SYNC family of FdFlags")); } @@ -96,7 +94,7 @@ impl Dir { Err(Error::not_dir().context("expected directory but got file")) } else { // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: - if fdflags.contains(wasi_common::file::FdFlags::NONBLOCK) { + if fdflags.contains(crate::file::FdFlags::NONBLOCK) { let set_fd_flags = f.new_set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?; f.set_fd_flags(set_fd_flags)?; } @@ -122,7 +120,7 @@ impl Dir { } } -#[async_trait::async_trait] +#[wiggle::async_trait] impl WasiDir for Dir { fn as_any(&self) -> &dyn Any { self @@ -135,11 +133,11 @@ impl WasiDir for Dir { read: bool, write: bool, fdflags: FdFlags, - ) -> Result { + ) -> Result { let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?; match f { - OpenResult::File(f) => Ok(wasi_common::dir::OpenResult::File(Box::new(f))), - OpenResult::Dir(d) => Ok(wasi_common::dir::OpenResult::Dir(Box::new(d))), + OpenResult::File(f) => Ok(crate::dir::OpenResult::File(Box::new(f))), + OpenResult::Dir(d) => Ok(crate::dir::OpenResult::Dir(Box::new(d))), } } @@ -307,8 +305,8 @@ impl WasiDir for Dir { async fn set_times( &self, path: &str, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, follow_symlinks: bool, ) -> Result<(), Error> { if follow_symlinks { @@ -328,10 +326,10 @@ impl WasiDir for Dir { } } -fn convert_systimespec(t: Option) -> Option { +fn convert_systimespec(t: Option) -> Option { match t { - Some(wasi_common::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), - Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), + Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), None => None, } } @@ -339,8 +337,8 @@ fn convert_systimespec(t: Option) -> Option HashMap { let mut out = HashMap::new(); diff --git a/crates/wasi-common/cap-std-sync/src/file.rs b/crates/wasi-common/src/sync/file.rs similarity index 83% rename from crates/wasi-common/cap-std-sync/src/file.rs rename to crates/wasi-common/src/sync/file.rs index e986c173404d..6a4f7a3d8f55 100644 --- a/crates/wasi-common/cap-std-sync/src/file.rs +++ b/crates/wasi-common/src/sync/file.rs @@ -1,3 +1,7 @@ +use crate::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, ErrorExt, +}; use cap_fs_ext::MetadataExt; use fs_set_times::{SetTimes, SystemTimeSpec}; use io_lifetimes::AsFilelike; @@ -8,10 +12,6 @@ use system_interface::{ fs::{FileIoExt, GetSetFdFlags}, io::{IoExt, ReadReady}, }; -use wasi_common::{ - file::{Advice, FdFlags, FileType, Filestat, WasiFile}, - Error, ErrorExt, -}; pub struct File(cap_std::fs::File); @@ -21,7 +21,7 @@ impl File { } } -#[async_trait::async_trait] +#[wiggle::async_trait] impl WasiFile for File { fn as_any(&self) -> &dyn Any { self @@ -52,9 +52,7 @@ impl WasiFile for File { } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { if fdflags.intersects( - wasi_common::file::FdFlags::DSYNC - | wasi_common::file::FdFlags::SYNC - | wasi_common::file::FdFlags::RSYNC, + crate::file::FdFlags::DSYNC | crate::file::FdFlags::SYNC | crate::file::FdFlags::RSYNC, ) { return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); } @@ -85,8 +83,8 @@ impl WasiFile for File { } async fn set_times( &self, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> Result<(), Error> { self.0 .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; @@ -186,33 +184,29 @@ impl AsFd for File { } } -pub(crate) fn convert_systimespec( - t: Option, -) -> Option { +pub(crate) fn convert_systimespec(t: Option) -> Option { match t { - Some(wasi_common::SystemTimeSpec::Absolute(t)) => { - Some(SystemTimeSpec::Absolute(t.into_std())) - } - Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + Some(crate::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t.into_std())), + Some(crate::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), None => None, } } -pub(crate) fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interface::fs::FdFlags { +pub(crate) fn to_sysif_fdflags(f: crate::file::FdFlags) -> system_interface::fs::FdFlags { let mut out = system_interface::fs::FdFlags::empty(); - if f.contains(wasi_common::file::FdFlags::APPEND) { + if f.contains(crate::file::FdFlags::APPEND) { out |= system_interface::fs::FdFlags::APPEND; } - if f.contains(wasi_common::file::FdFlags::DSYNC) { + if f.contains(crate::file::FdFlags::DSYNC) { out |= system_interface::fs::FdFlags::DSYNC; } - if f.contains(wasi_common::file::FdFlags::NONBLOCK) { + if f.contains(crate::file::FdFlags::NONBLOCK) { out |= system_interface::fs::FdFlags::NONBLOCK; } - if f.contains(wasi_common::file::FdFlags::RSYNC) { + if f.contains(crate::file::FdFlags::RSYNC) { out |= system_interface::fs::FdFlags::RSYNC; } - if f.contains(wasi_common::file::FdFlags::SYNC) { + if f.contains(crate::file::FdFlags::SYNC) { out |= system_interface::fs::FdFlags::SYNC; } out @@ -221,23 +215,23 @@ pub(crate) fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interfac /// Return the file-descriptor flags for a given file-like object. /// /// This returns the flags needed to implement [`WasiFile::get_fdflags`]. -pub fn get_fd_flags(f: Filelike) -> io::Result { +pub fn get_fd_flags(f: Filelike) -> io::Result { let f = f.as_filelike().get_fd_flags()?; - let mut out = wasi_common::file::FdFlags::empty(); + let mut out = crate::file::FdFlags::empty(); if f.contains(system_interface::fs::FdFlags::APPEND) { - out |= wasi_common::file::FdFlags::APPEND; + out |= crate::file::FdFlags::APPEND; } if f.contains(system_interface::fs::FdFlags::DSYNC) { - out |= wasi_common::file::FdFlags::DSYNC; + out |= crate::file::FdFlags::DSYNC; } if f.contains(system_interface::fs::FdFlags::NONBLOCK) { - out |= wasi_common::file::FdFlags::NONBLOCK; + out |= crate::file::FdFlags::NONBLOCK; } if f.contains(system_interface::fs::FdFlags::RSYNC) { - out |= wasi_common::file::FdFlags::RSYNC; + out |= crate::file::FdFlags::RSYNC; } if f.contains(system_interface::fs::FdFlags::SYNC) { - out |= wasi_common::file::FdFlags::SYNC; + out |= crate::file::FdFlags::SYNC; } Ok(out) } diff --git a/crates/wasi-common/cap-std-sync/src/lib.rs b/crates/wasi-common/src/sync/mod.rs similarity index 62% rename from crates/wasi-common/cap-std-sync/src/lib.rs rename to crates/wasi-common/src/sync/mod.rs index 832c77cfa496..32ea8a14cd80 100644 --- a/crates/wasi-common/cap-std-sync/src/lib.rs +++ b/crates/wasi-common/src/sync/mod.rs @@ -7,29 +7,12 @@ //! and windows in separate modules. //! //! Any `wasi_common::{WasiCtx, WasiCtxBuilder}` is interoperable with the -//! `wasi-cap-std-sync` crate. However, for convenience, `wasi-cap-std-sync` -//! provides its own `WasiCtxBuilder` that hooks up to all of the crate's -//! components, i.e. it fills in all of the arguments to +//! implementations provided in `wasi_common::sync`. However, for convenience, +//! this module provides its own `WasiCtxBuilder` that hooks up to all of the +//! crate's components, i.e. it fills in all of the arguments to //! `WasiCtx::builder(...)`, presents `preopen_dir` in terms of //! `cap_std::fs::Dir`, and provides convenience methods for inheriting the //! parent process's stdio, args, and env. -//! -//! For the convenience of consumers, `cap_std::fs::Dir` is re-exported from -//! this crate. This saves consumers tracking an additional dep on the exact -//! version of cap_std used by this crate, if they want to avoid it. -//! -//! The only place we expect to run into long-term compatibility issues -//! between `wasi-cap-std-sync` and the other impl crates that will come later -//! is in the `Sched` abstraction. Once we can build an async scheduler based -//! on Rust `Future`s, async impls will be able to interoperate, but the -//! synchronous scheduler depends on downcasting the `WasiFile` type down to -//! concrete types it knows about (which in turn impl `AsFd` for passing to -//! unix `poll`, or the analogous traits on windows). -//! -//! Why is this impl suffixed with `-sync`? Because `async` is coming soon! -//! The async impl may end up depending on tokio or other relatively heavy -//! deps, so we will retain a sync implementation so that wasi-common users -//! have an option of not pulling in an async runtime. #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] @@ -46,11 +29,11 @@ pub use cap_std::net::TcpListener; pub use clocks::clocks_ctx; pub use sched::sched_ctx; -use crate::net::Socket; +use self::net::Socket; +use crate::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile}; use cap_rand::{Rng, RngCore, SeedableRng}; use std::mem; use std::path::Path; -use wasi_common::{file::FileAccessMode, table::Table, Error, WasiCtx, WasiFile}; pub struct WasiCtxBuilder { ctx: WasiCtx, @@ -64,40 +47,33 @@ impl WasiCtxBuilder { built: false, } } - pub fn env( - &mut self, - var: &str, - value: &str, - ) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> { self.ctx.push_env(var, value)?; Ok(self) } - pub fn envs( - &mut self, - env: &[(String, String)], - ) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> { for (k, v) in env { self.ctx.push_env(k, v)?; } Ok(self) } - pub fn inherit_env(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> { for (key, value) in std::env::vars() { self.ctx.push_env(&key, &value)?; } Ok(self) } - pub fn arg(&mut self, arg: &str) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> { self.ctx.push_arg(arg)?; Ok(self) } - pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> { for a in arg { self.ctx.push_arg(&a)?; } Ok(self) } - pub fn inherit_args(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> { for arg in std::env::args() { self.ctx.push_arg(&arg)?; } @@ -116,13 +92,13 @@ impl WasiCtxBuilder { self } pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(Box::new(crate::stdio::stdin())) + self.stdin(Box::new(crate::sync::stdio::stdin())) } pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(Box::new(crate::stdio::stdout())) + self.stdout(Box::new(crate::sync::stdio::stdout())) } pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(Box::new(crate::stdio::stderr())) + self.stderr(Box::new(crate::sync::stdio::stderr())) } pub fn inherit_stdio(&mut self) -> &mut Self { self.inherit_stdin().inherit_stdout().inherit_stderr() @@ -132,7 +108,7 @@ impl WasiCtxBuilder { dir: Dir, guest_path: impl AsRef, ) -> Result<&mut Self, Error> { - let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); + let dir = Box::new(crate::sync::dir::Dir::from_cap_std(dir)); self.ctx.push_preopened_dir(dir, guest_path)?; Ok(self) } @@ -159,3 +135,6 @@ pub fn random_ctx() -> Box { let mut rng = cap_rand::thread_rng(cap_rand::ambient_authority()); Box::new(cap_rand::rngs::StdRng::from_seed(rng.gen())) } + +#[cfg(feature = "wasmtime")] +super::define_wasi!(block_on); diff --git a/crates/wasi-common/cap-std-sync/src/net.rs b/crates/wasi-common/src/sync/net.rs similarity index 91% rename from crates/wasi-common/cap-std-sync/src/net.rs rename to crates/wasi-common/src/sync/net.rs index c0750cd83e46..14dcaa14d482 100644 --- a/crates/wasi-common/cap-std-sync/src/net.rs +++ b/crates/wasi-common/src/sync/net.rs @@ -1,3 +1,7 @@ +use crate::{ + file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile}, + Error, ErrorExt, +}; #[cfg(windows)] use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; use io_lifetimes::AsSocketlike; @@ -13,10 +17,6 @@ use system_interface::fs::GetSetFdFlags; use system_interface::io::IoExt; use system_interface::io::IsReadWrite; use system_interface::io::ReadReady; -use wasi_common::{ - file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags, WasiFile}, - Error, ErrorExt, -}; pub enum Socket { TcpListener(cap_std::net::TcpListener), @@ -57,10 +57,10 @@ impl From for Socket { impl From for Box { fn from(listener: Socket) -> Self { match listener { - Socket::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)), - Socket::UnixListener(l) => Box::new(crate::net::UnixListener::from_cap_std(l)), - Socket::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)), - Socket::UnixStream(l) => Box::new(crate::net::UnixStream::from_cap_std(l)), + Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), + Socket::UnixListener(l) => Box::new(crate::sync::net::UnixListener::from_cap_std(l)), + Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), + Socket::UnixStream(l) => Box::new(crate::sync::net::UnixStream::from_cap_std(l)), } } } @@ -69,15 +69,15 @@ impl From for Box { impl From for Box { fn from(listener: Socket) -> Self { match listener { - Socket::TcpListener(l) => Box::new(crate::net::TcpListener::from_cap_std(l)), - Socket::TcpStream(l) => Box::new(crate::net::TcpStream::from_cap_std(l)), + Socket::TcpListener(l) => Box::new(crate::sync::net::TcpListener::from_cap_std(l)), + Socket::TcpStream(l) => Box::new(crate::sync::net::TcpStream::from_cap_std(l)), } } } macro_rules! wasi_listen_write_impl { ($ty:ty, $stream:ty) => { - #[async_trait::async_trait] + #[wiggle::async_trait] impl WasiFile for $ty { fn as_any(&self) -> &dyn Any { self @@ -105,7 +105,7 @@ macro_rules! wasi_listen_write_impl { Ok(fdflags) } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags == wasi_common::file::FdFlags::NONBLOCK { + if fdflags == crate::file::FdFlags::NONBLOCK { self.0.set_nonblocking(true)?; } else if fdflags.is_empty() { self.0.set_nonblocking(false)?; @@ -170,7 +170,7 @@ wasi_listen_write_impl!(UnixListener, UnixStream); macro_rules! wasi_stream_write_impl { ($ty:ty, $std_ty:ty) => { - #[async_trait::async_trait] + #[wiggle::async_trait] impl WasiFile for $ty { fn as_any(&self) -> &dyn Any { self @@ -192,7 +192,7 @@ macro_rules! wasi_stream_write_impl { Ok(fdflags) } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags == wasi_common::file::FdFlags::NONBLOCK { + if fdflags == crate::file::FdFlags::NONBLOCK { self.0.set_nonblocking(true)?; } else if fdflags.is_empty() { self.0.set_nonblocking(false)?; @@ -356,18 +356,16 @@ pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { /// Return the file-descriptor flags for a given file-like object. /// /// This returns the flags needed to implement [`WasiFile::get_fdflags`]. -pub fn get_fd_flags( - f: Socketlike, -) -> io::Result { +pub fn get_fd_flags(f: Socketlike) -> io::Result { // On Unix-family platforms, we can use the same system call that we'd use // for files on sockets here. #[cfg(not(windows))] { - let mut out = wasi_common::file::FdFlags::empty(); + let mut out = crate::file::FdFlags::empty(); if f.get_fd_flags()? .contains(system_interface::fs::FdFlags::NONBLOCK) { - out |= wasi_common::file::FdFlags::NONBLOCK; + out |= crate::file::FdFlags::NONBLOCK; } Ok(out) } @@ -377,8 +375,8 @@ pub fn get_fd_flags( // by testing whether a zero-length `recv` appears to block. #[cfg(windows)] match rustix::net::recv(f, &mut [], rustix::net::RecvFlags::empty()) { - Ok(_) => Ok(wasi_common::file::FdFlags::empty()), - Err(rustix::io::Errno::WOULDBLOCK) => Ok(wasi_common::file::FdFlags::NONBLOCK), + Ok(_) => Ok(crate::file::FdFlags::empty()), + Err(rustix::io::Errno::WOULDBLOCK) => Ok(crate::file::FdFlags::NONBLOCK), Err(e) => Err(e.into()), } } diff --git a/crates/wasi-common/cap-std-sync/src/sched.rs b/crates/wasi-common/src/sync/sched.rs similarity index 94% rename from crates/wasi-common/cap-std-sync/src/sched.rs rename to crates/wasi-common/src/sync/sched.rs index df8622f07d8b..e49ef46428fc 100644 --- a/crates/wasi-common/cap-std-sync/src/sched.rs +++ b/crates/wasi-common/src/sync/sched.rs @@ -8,12 +8,12 @@ pub mod windows; #[cfg(windows)] pub use windows::poll_oneoff; -use std::thread; -use std::time::Duration; -use wasi_common::{ +use crate::{ sched::{Poll, WasiSched}, Error, }; +use std::thread; +use std::time::Duration; pub struct SyncSched {} impl SyncSched { @@ -21,7 +21,7 @@ impl SyncSched { Self {} } } -#[async_trait::async_trait] +#[wiggle::async_trait] impl WasiSched for SyncSched { async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { poll_oneoff(poll).await diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/src/sync/sched/unix.rs similarity index 96% rename from crates/wasi-common/cap-std-sync/src/sched/unix.rs rename to crates/wasi-common/src/sync/sched/unix.rs index 77176a7e272b..0d9db86ae311 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/src/sync/sched/unix.rs @@ -1,8 +1,8 @@ +use crate::sched::subscription::{RwEventFlags, Subscription}; +use crate::{sched::Poll, Error, ErrorExt}; use cap_std::time::Duration; use rustix::event::{PollFd, PollFlags}; use std::convert::TryInto; -use wasi_common::sched::subscription::{RwEventFlags, Subscription}; -use wasi_common::{sched::Poll, Error, ErrorExt}; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/src/sync/sched/windows.rs similarity index 97% rename from crates/wasi-common/cap-std-sync/src/sched/windows.rs rename to crates/wasi-common/src/sync/sched/windows.rs index d67d30d922d1..6a5499c8f5d2 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/src/sync/sched/windows.rs @@ -8,13 +8,13 @@ // We suspect there are bugs in this scheduler, however, we have not // taken the time to improve it. See bug #2880. +use crate::sched::subscription::{RwEventFlags, Subscription}; +use crate::{file::WasiFile, sched::Poll, Error, ErrorExt}; use once_cell::sync::Lazy; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; use std::sync::Mutex; use std::thread; use std::time::Duration; -use wasi_common::sched::subscription::{RwEventFlags, Subscription}; -use wasi_common::{file::WasiFile, sched::Poll, Error, ErrorExt}; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { poll_oneoff_(poll, wasi_file_is_stdin).await @@ -122,7 +122,7 @@ pub async fn poll_oneoff_<'a>( } pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { - f.as_any().is::() + f.as_any().is::() } enum PollState { diff --git a/crates/wasi-common/cap-std-sync/src/stdio.rs b/crates/wasi-common/src/sync/stdio.rs similarity index 94% rename from crates/wasi-common/cap-std-sync/src/stdio.rs rename to crates/wasi-common/src/sync/stdio.rs index af4d52faab28..70ce38c33afb 100644 --- a/crates/wasi-common/cap-std-sync/src/stdio.rs +++ b/crates/wasi-common/src/sync/stdio.rs @@ -1,20 +1,20 @@ -use crate::file::convert_systimespec; +use crate::sync::file::convert_systimespec; use fs_set_times::SetTimes; use std::any::Any; use std::convert::TryInto; use std::io::{self, IsTerminal, Read, Write}; use system_interface::io::ReadReady; +use crate::{ + file::{FdFlags, FileType, WasiFile}, + Error, ErrorExt, +}; #[cfg(windows)] use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; #[cfg(unix)] use io_lifetimes::{AsFd, BorrowedFd}; #[cfg(windows)] use io_lifetimes::{AsHandle, BorrowedHandle}; -use wasi_common::{ - file::{FdFlags, FileType, WasiFile}, - Error, ErrorExt, -}; pub struct Stdin(std::io::Stdin); @@ -22,7 +22,7 @@ pub fn stdin() -> Stdin { Stdin(std::io::stdin()) } -#[async_trait::async_trait] +#[wiggle::async_trait] impl WasiFile for Stdin { fn as_any(&self) -> &dyn Any { self @@ -64,8 +64,8 @@ impl WasiFile for Stdin { } async fn set_times( &self, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> Result<(), Error> { self.0 .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; @@ -103,7 +103,7 @@ impl AsFd for Stdin { macro_rules! wasi_file_write_impl { ($ty:ty, $ident:ident) => { - #[async_trait::async_trait] + #[wiggle::async_trait] impl WasiFile for $ty { fn as_any(&self) -> &dyn Any { self @@ -149,8 +149,8 @@ macro_rules! wasi_file_write_impl { } async fn set_times( &self, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> Result<(), Error> { self.0 .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/src/tokio/dir.rs similarity index 88% rename from crates/wasi-common/tokio/src/dir.rs rename to crates/wasi-common/src/tokio/dir.rs index 5482a08c7619..22bf16a9dd13 100644 --- a/crates/wasi-common/tokio/src/dir.rs +++ b/crates/wasi-common/src/tokio/dir.rs @@ -1,17 +1,17 @@ -use crate::{block_on_dummy_executor, file::File}; -use std::any::Any; -use std::path::PathBuf; -use wasi_common::{ +use crate::tokio::{block_on_dummy_executor, file::File}; +use crate::{ dir::{ReaddirCursor, ReaddirEntity, WasiDir}, file::{FdFlags, Filestat, OFlags}, Error, ErrorExt, }; +use std::any::Any; +use std::path::PathBuf; -pub struct Dir(wasi_cap_std_sync::dir::Dir); +pub struct Dir(crate::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)) + Dir(crate::sync::dir::Dir::from_cap_std(dir)) } } @@ -28,17 +28,17 @@ impl WasiDir for Dir { read: bool, write: bool, fdflags: FdFlags, - ) -> Result { + ) -> Result { let f = block_on_dummy_executor(move || async move { self.0 .open_file_(symlink_follow, path, oflags, read, write, fdflags) })?; match f { - wasi_cap_std_sync::dir::OpenResult::File(f) => Ok(wasi_common::dir::OpenResult::File( - Box::new(File::from_inner(f)), - )), - wasi_cap_std_sync::dir::OpenResult::Dir(d) => { - Ok(wasi_common::dir::OpenResult::Dir(Box::new(Dir(d)))) + crate::sync::dir::OpenResult::File(f) => { + Ok(crate::dir::OpenResult::File(Box::new(File::from_inner(f)))) + } + crate::sync::dir::OpenResult::Dir(d) => { + Ok(crate::dir::OpenResult::Dir(Box::new(Dir(d)))) } } } @@ -116,8 +116,8 @@ impl WasiDir for Dir { async fn set_times( &self, path: &str, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, follow_symlinks: bool, ) -> Result<(), Error> { block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks)) @@ -127,8 +127,8 @@ impl WasiDir for Dir { #[cfg(test)] mod test { use super::Dir; + use crate::file::{FdFlags, OFlags}; use cap_std::ambient_authority; - use wasi_common::file::{FdFlags, OFlags}; #[tokio::test(flavor = "multi_thread")] async fn scratch_dir() { @@ -139,7 +139,7 @@ mod test { let preopen_dir = cap_std::fs::Dir::open_ambient_dir(tempdir.path(), ambient_authority()) .expect("open ambient temporary dir"); let preopen_dir = Dir::from_cap_std(preopen_dir); - wasi_common::WasiDir::open_file( + crate::WasiDir::open_file( &preopen_dir, false, ".", @@ -156,9 +156,9 @@ mod test { #[cfg(not(windows))] #[tokio::test(flavor = "multi_thread")] async fn readdir() { + use crate::dir::{ReaddirCursor, ReaddirEntity, WasiDir}; + use crate::file::{FdFlags, FileType, OFlags}; 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 { let mut out = HashMap::new(); diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/src/tokio/file.rs similarity index 83% rename from crates/wasi-common/tokio/src/file.rs rename to crates/wasi-common/src/tokio/file.rs index 2fa8ddaf8639..b4149f7ea546 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/src/tokio/file.rs @@ -1,4 +1,8 @@ -use crate::block_on_dummy_executor; +use crate::tokio::block_on_dummy_executor; +use crate::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, +}; #[cfg(windows)] use io_extras::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; #[cfg(not(windows))] @@ -6,86 +10,82 @@ use io_lifetimes::AsFd; use std::any::Any; use std::borrow::Borrow; use std::io; -use wasi_common::{ - file::{Advice, FdFlags, FileType, Filestat, WasiFile}, - Error, -}; -pub struct File(wasi_cap_std_sync::file::File); +pub struct File(crate::sync::file::File); impl File { - pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self { + pub(crate) fn from_inner(file: crate::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)) + Self::from_inner(crate::sync::file::File::from_cap_std(file)) } } -pub struct TcpListener(wasi_cap_std_sync::net::TcpListener); +pub struct TcpListener(crate::sync::net::TcpListener); impl TcpListener { - pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::TcpListener) -> Self { + pub(crate) fn from_inner(listener: crate::sync::net::TcpListener) -> Self { TcpListener(listener) } pub fn from_cap_std(listener: cap_std::net::TcpListener) -> Self { - Self::from_inner(wasi_cap_std_sync::net::TcpListener::from_cap_std(listener)) + Self::from_inner(crate::sync::net::TcpListener::from_cap_std(listener)) } } -pub struct TcpStream(wasi_cap_std_sync::net::TcpStream); +pub struct TcpStream(crate::sync::net::TcpStream); impl TcpStream { - pub(crate) fn from_inner(stream: wasi_cap_std_sync::net::TcpStream) -> Self { + pub(crate) fn from_inner(stream: crate::sync::net::TcpStream) -> Self { TcpStream(stream) } pub fn from_cap_std(stream: cap_std::net::TcpStream) -> Self { - Self::from_inner(wasi_cap_std_sync::net::TcpStream::from_cap_std(stream)) + Self::from_inner(crate::sync::net::TcpStream::from_cap_std(stream)) } } #[cfg(unix)] -pub struct UnixListener(wasi_cap_std_sync::net::UnixListener); +pub struct UnixListener(crate::sync::net::UnixListener); #[cfg(unix)] impl UnixListener { - pub(crate) fn from_inner(listener: wasi_cap_std_sync::net::UnixListener) -> Self { + pub(crate) fn from_inner(listener: crate::sync::net::UnixListener) -> Self { UnixListener(listener) } pub fn from_cap_std(listener: cap_std::os::unix::net::UnixListener) -> Self { - Self::from_inner(wasi_cap_std_sync::net::UnixListener::from_cap_std(listener)) + Self::from_inner(crate::sync::net::UnixListener::from_cap_std(listener)) } } #[cfg(unix)] -pub struct UnixStream(wasi_cap_std_sync::net::UnixStream); +pub struct UnixStream(crate::sync::net::UnixStream); #[cfg(unix)] impl UnixStream { - fn from_inner(stream: wasi_cap_std_sync::net::UnixStream) -> Self { + fn from_inner(stream: crate::sync::net::UnixStream) -> Self { UnixStream(stream) } pub fn from_cap_std(stream: cap_std::os::unix::net::UnixStream) -> Self { - Self::from_inner(wasi_cap_std_sync::net::UnixStream::from_cap_std(stream)) + Self::from_inner(crate::sync::net::UnixStream::from_cap_std(stream)) } } -pub struct Stdin(wasi_cap_std_sync::stdio::Stdin); +pub struct Stdin(crate::sync::stdio::Stdin); pub fn stdin() -> Stdin { - Stdin(wasi_cap_std_sync::stdio::stdin()) + Stdin(crate::sync::stdio::stdin()) } -pub struct Stdout(wasi_cap_std_sync::stdio::Stdout); +pub struct Stdout(crate::sync::stdio::Stdout); pub fn stdout() -> Stdout { - Stdout(wasi_cap_std_sync::stdio::stdout()) + Stdout(crate::sync::stdio::stdout()) } -pub struct Stderr(wasi_cap_std_sync::stdio::Stderr); +pub struct Stderr(crate::sync::stdio::Stderr); pub fn stderr() -> Stderr { - Stderr(wasi_cap_std_sync::stdio::stderr()) + Stderr(crate::sync::stdio::stderr()) } macro_rules! wasi_file_impl { @@ -158,8 +158,8 @@ macro_rules! wasi_file_impl { } async fn set_times( &self, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> Result<(), Error> { block_on_dummy_executor(move || self.0.set_times(atime, mtime)) } diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/src/tokio/mod.rs similarity index 73% rename from crates/wasi-common/tokio/src/lib.rs rename to crates/wasi-common/src/tokio/mod.rs index a65021c19af5..9042eb4d671d 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/src/tokio/mod.rs @@ -6,16 +6,16 @@ pub mod net; pub mod sched; pub mod stdio; -use crate::sched::sched_ctx; +use self::sched::sched_ctx; +use crate::sync::net::Socket; +pub use crate::sync::{clocks_ctx, random_ctx}; +use crate::{file::FileAccessMode, Error, Table, WasiCtx, WasiFile}; pub use dir::Dir; pub use file::File; pub use net::*; use std::future::Future; use std::mem; use std::path::Path; -use wasi_cap_std_sync::net::Socket; -pub use wasi_cap_std_sync::{clocks_ctx, random_ctx}; -use wasi_common::{file::FileAccessMode, Error, Table, WasiCtx, WasiFile}; pub struct WasiCtxBuilder { ctx: WasiCtx, @@ -29,40 +29,33 @@ impl WasiCtxBuilder { built: false, } } - pub fn env( - &mut self, - var: &str, - value: &str, - ) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn env(&mut self, var: &str, value: &str) -> Result<&mut Self, crate::StringArrayError> { self.ctx.push_env(var, value)?; Ok(self) } - pub fn envs( - &mut self, - env: &[(String, String)], - ) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn envs(&mut self, env: &[(String, String)]) -> Result<&mut Self, crate::StringArrayError> { for (k, v) in env { self.ctx.push_env(k, v)?; } Ok(self) } - pub fn inherit_env(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn inherit_env(&mut self) -> Result<&mut Self, crate::StringArrayError> { for (key, value) in std::env::vars() { self.ctx.push_env(&key, &value)?; } Ok(self) } - pub fn arg(&mut self, arg: &str) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn arg(&mut self, arg: &str) -> Result<&mut Self, crate::StringArrayError> { self.ctx.push_arg(arg)?; Ok(self) } - pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn args(&mut self, arg: &[String]) -> Result<&mut Self, crate::StringArrayError> { for a in arg { self.ctx.push_arg(&a)?; } Ok(self) } - pub fn inherit_args(&mut self) -> Result<&mut Self, wasi_common::StringArrayError> { + pub fn inherit_args(&mut self) -> Result<&mut Self, crate::StringArrayError> { for arg in std::env::args() { self.ctx.push_arg(&arg)?; } @@ -81,13 +74,13 @@ impl WasiCtxBuilder { self } pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(Box::new(crate::stdio::stdin())) + self.stdin(Box::new(crate::tokio::stdio::stdin())) } pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(Box::new(crate::stdio::stdout())) + self.stdout(Box::new(crate::tokio::stdio::stdout())) } pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(Box::new(crate::stdio::stderr())) + self.stderr(Box::new(crate::tokio::stdio::stderr())) } pub fn inherit_stdio(&mut self) -> &mut Self { self.inherit_stdin().inherit_stdout().inherit_stderr() @@ -97,7 +90,7 @@ impl WasiCtxBuilder { dir: cap_std::fs::Dir, guest_path: impl AsRef, ) -> Result<&mut Self, Error> { - let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); + let dir = Box::new(crate::tokio::dir::Dir::from_cap_std(dir)); self.ctx.push_preopened_dir(dir, guest_path)?; Ok(self) } @@ -121,8 +114,8 @@ impl WasiCtxBuilder { } } -// 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, +// Much of this mod is implemented in terms of `async` methods from the +// wasmtime_wasi::sync module. These methods may be async in signature, however, // they are synchronous in implementation (always Poll::Ready on first poll) // and perform blocking syscalls. // @@ -139,3 +132,6 @@ where wiggle::run_in_dummy_executor(f()).expect("wrapped operation should be synchronous") }) } + +#[cfg(feature = "wasmtime")] +super::define_wasi!(async T: Send); diff --git a/crates/wasi-common/src/tokio/net.rs b/crates/wasi-common/src/tokio/net.rs new file mode 100644 index 000000000000..93a807ec8294 --- /dev/null +++ b/crates/wasi-common/src/tokio/net.rs @@ -0,0 +1,6 @@ +pub use super::file::TcpListener; +pub use super::file::TcpStream; +#[cfg(unix)] +pub use super::file::UnixListener; +#[cfg(unix)] +pub use super::file::UnixStream; diff --git a/crates/wasi-common/tokio/src/sched.rs b/crates/wasi-common/src/tokio/sched.rs similarity index 90% rename from crates/wasi-common/tokio/src/sched.rs rename to crates/wasi-common/src/tokio/sched.rs index 1b971666415b..e5c5a42a789d 100644 --- a/crates/wasi-common/tokio/src/sched.rs +++ b/crates/wasi-common/src/tokio/sched.rs @@ -8,12 +8,12 @@ mod windows; #[cfg(windows)] pub use windows::poll_oneoff; -use wasi_common::{ +use crate::{ sched::{Duration, Poll, WasiSched}, Error, }; -pub fn sched_ctx() -> Box { +pub fn sched_ctx() -> Box { struct AsyncSched; #[wiggle::async_trait] diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/src/tokio/sched/unix.rs similarity index 99% rename from crates/wasi-common/tokio/src/sched/unix.rs rename to crates/wasi-common/src/tokio/sched/unix.rs index 4fd47d1cb248..ca28242342fb 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/src/tokio/sched/unix.rs @@ -1,13 +1,13 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll as FPoll}; -use wasi_common::{ +use crate::{ sched::{ subscription::{RwEventFlags, Subscription}, Poll, }, Error, }; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll as FPoll}; struct FirstReady<'a, T>(Vec + Send + 'a>>>); diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/src/tokio/sched/windows.rs similarity index 55% rename from crates/wasi-common/tokio/src/sched/windows.rs rename to crates/wasi-common/src/tokio/sched/windows.rs index 54cf8615b69f..55b4f5f5374e 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/src/tokio/sched/windows.rs @@ -1,15 +1,15 @@ -use crate::block_on_dummy_executor; -use wasi_cap_std_sync::sched::windows::poll_oneoff_; -use wasi_common::{file::WasiFile, sched::Poll, Error}; +use crate::sync::sched::windows::poll_oneoff_; +use crate::tokio::block_on_dummy_executor; +use crate::{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 + // we use the blocking poll_oneoff implementation from the wasi_common::sync impl. + // We provide a function specific to this impl's WasiFile types for downcasting // to a RawHandle. block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin)) } pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { - f.as_any().is::() + f.as_any().is::() } diff --git a/crates/wasi-common/src/tokio/stdio.rs b/crates/wasi-common/src/tokio/stdio.rs new file mode 100644 index 000000000000..91590363ede3 --- /dev/null +++ b/crates/wasi-common/src/tokio/stdio.rs @@ -0,0 +1 @@ +pub use super::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; diff --git a/crates/wasi-common/tests/all/async_.rs b/crates/wasi-common/tests/all/async_.rs index 4622da8fa60b..4bcf8e828dd8 100644 --- a/crates/wasi-common/tests/all/async_.rs +++ b/crates/wasi-common/tests/all/async_.rs @@ -1,6 +1,6 @@ use super::*; use test_programs_artifacts::*; -use wasmtime_wasi::tokio::{add_to_linker, WasiCtxBuilder}; +use wasi_common::tokio::{add_to_linker, WasiCtxBuilder}; foreach_preview1!(assert_test_exists); diff --git a/crates/wasi-common/tests/all/main.rs b/crates/wasi-common/tests/all/main.rs index 5324e8656e7d..2746f8c8884e 100644 --- a/crates/wasi-common/tests/all/main.rs +++ b/crates/wasi-common/tests/all/main.rs @@ -5,7 +5,7 @@ use wasi_common::pipe::WritePipe; use wasmtime::{Config, Engine, Linker, Module, Store}; pub fn prepare_workspace(exe_name: &str) -> Result { - let prefix = format!("wasi_cap_std_sync_{}_", exe_name); + let prefix = format!("wasi_common_{}_", exe_name); let tempdir = tempfile::Builder::new().prefix(&prefix).tempdir()?; Ok(tempdir) } diff --git a/crates/wasi-common/tests/all/sync.rs b/crates/wasi-common/tests/all/sync.rs index ce4d37c1101c..4af52d33a1cb 100644 --- a/crates/wasi-common/tests/all/sync.rs +++ b/crates/wasi-common/tests/all/sync.rs @@ -1,6 +1,6 @@ use super::*; use test_programs_artifacts::*; -use wasmtime_wasi::sync::{add_to_linker, WasiCtxBuilder}; +use wasi_common::sync::{add_to_linker, WasiCtxBuilder}; foreach_preview1!(assert_test_exists); diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml deleted file mode 100644 index 451e451a3087..000000000000 --- a/crates/wasi-common/tokio/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "wasi-tokio" -version.workspace = true -authors.workspace = true -description = "WASI implementation in Rust" -license = "Apache-2.0 WITH LLVM-exception" -categories = ["wasm"] -keywords = ["webassembly", "wasm"] -repository = "https://github.com/bytecodealliance/wasmtime" -edition.workspace = true -include = ["src/**/*", "LICENSE" ] - -[lints] -workspace = true - -[dependencies] -wasi-common = { workspace = true } -wasi-cap-std-sync = { workspace = true } -wiggle = { workspace = true } -tokio = { workspace = true, features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"] } -cap-std = { workspace = true } -anyhow = { workspace = true } -io-lifetimes = { workspace = true } - -[target.'cfg(unix)'.dependencies] -rustix = { workspace = true, features = ["fs"] } - -[target.'cfg(windows)'.dependencies] -io-extras = { workspace = true } - -[dev-dependencies] -tempfile = "3.1.0" -tokio = { workspace = true, features = [ "macros" ] } -cap-tempfile = { workspace = true } diff --git a/crates/wasi-common/tokio/LICENSE b/crates/wasi-common/tokio/LICENSE deleted file mode 100644 index f9d81955f4bc..000000000000 --- a/crates/wasi-common/tokio/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/crates/wasi-common/tokio/src/net.rs b/crates/wasi-common/tokio/src/net.rs deleted file mode 100644 index 03e390a83bcd..000000000000 --- a/crates/wasi-common/tokio/src/net.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub use crate::file::TcpListener; -pub use crate::file::TcpStream; -#[cfg(unix)] -pub use crate::file::UnixListener; -#[cfg(unix)] -pub use crate::file::UnixStream; diff --git a/crates/wasi-common/tokio/src/stdio.rs b/crates/wasi-common/tokio/src/stdio.rs deleted file mode 100644 index dd23c0555aab..000000000000 --- a/crates/wasi-common/tokio/src/stdio.rs +++ /dev/null @@ -1 +0,0 @@ -pub use crate::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs deleted file mode 100644 index 852abce2aad2..000000000000 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ /dev/null @@ -1,176 +0,0 @@ -use anyhow::{Context, Error}; -use cap_std::time::Duration; -use std::collections::HashMap; -use wasi_common::{ - dir::OpenResult, - file::{FdFlags, OFlags}, - sched::{Poll, RwEventFlags, SubscriptionResult, Userdata}, - WasiDir, WasiFile, -}; -use wasi_tokio::{clocks_ctx, sched::poll_oneoff, Dir}; - -const TIMEOUT: Duration = Duration::from_millis(200); // Required for slow execution in CI - -#[tokio::test(flavor = "multi_thread")] -async fn empty_file_readable() -> Result<(), Error> { - let clocks = clocks_ctx(); - - let workspace = - cap_tempfile::tempdir(cap_tempfile::ambient_authority()).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 = vec![0]; - if let OpenResult::File(ref f) = f { - f.write_vectored(&vec![std::io::IoSlice::new(&to_write)]) - .await - .context("write to f")?; - } else { - unreachable!(); - } - drop(f); - - let f = d - .open_file(false, "f", OFlags::empty(), true, false, FdFlags::empty()) - .await - .context("open f as readable")?; - - let mut poll = Poll::new(); - if let OpenResult::File(ref f) = f { - poll.subscribe_read(f.as_ref(), Userdata::from(123)); - } else { - unreachable!(); - } - // Timeout bounds time in poll_oneoff - let monotonic = &*clocks.monotonic()?.abs_clock; - poll.subscribe_monotonic_clock( - monotonic, - monotonic - .now(monotonic.resolution()) - .checked_add(TIMEOUT) - .unwrap(), - 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 = - cap_tempfile::tempdir(cap_tempfile::ambient_authority()).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 writable_f = d - .open_file(false, "f", OFlags::CREATE, true, true, FdFlags::empty()) - .await - .context("create writable file")?; - - let mut poll = Poll::new(); - if let OpenResult::File(ref writable_f) = writable_f { - poll.subscribe_write(writable_f.as_ref(), Userdata::from(123)); - } else { - unreachable!(); - } - // Timeout bounds time in poll_oneoff - let monotonic = &*clocks.monotonic()?.abs_clock; - poll.subscribe_monotonic_clock( - monotonic, - monotonic - .now(monotonic.resolution()) - .checked_add(TIMEOUT) - .unwrap(), - 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 monotonic = &*clocks.monotonic()?.abs_clock; - let deadline = monotonic - .now(monotonic.resolution()) - .checked_add(TIMEOUT) - .unwrap(); - - let mut waiting_on: HashMap> = vec![ - ( - 1, - Box::new(wasi_tokio::stdio::stdout()) as Box, - ), - (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 - let monotonic = &*clocks.monotonic()?.abs_clock; - poll.subscribe_monotonic_clock( - monotonic, - deadline, - 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(()) -} diff --git a/crates/wasi-http/Cargo.toml b/crates/wasi-http/Cargo.toml index e95203c0b9f0..996d8c8aa88d 100644 --- a/crates/wasi-http/Cargo.toml +++ b/crates/wasi-http/Cargo.toml @@ -45,7 +45,3 @@ tokio = { workspace = true, features = ['macros'] } futures = { workspace = true, default-features = false, features = ['alloc'] } sha2 = "0.10.2" base64 = "0.21.0" - -[features] -default = ["sync"] -sync = ["wasmtime-wasi/sync"] diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs index 221e97bd3ae0..268c67c50898 100644 --- a/crates/wasi-http/tests/all/main.rs +++ b/crates/wasi-http/tests/all/main.rs @@ -174,20 +174,20 @@ async fn run_wasi_http( use http_body_util::BodyExt; let (parts, body) = resp.into_parts(); let collected = BodyExt::collect(body).await?; - Ok(hyper::Response::from_parts(parts, collected)) + Some(Ok(hyper::Response::from_parts(parts, collected))) } + Ok(Err(e)) => Some(Err(e)), - Ok(Err(e)) => Err(e), - - // This happens if the wasm never calls `set-response-outparam` - Err(e) => panic!("Failed to receive a response: {e:?}"), + // Fall through below to the `resp.expect(...)` which will hopefully + // return a more specific error from `handle.await`. + Err(_) => None, }; - // Now that the response has been processed, we can wait on the wasm to finish without - // deadlocking. + // Now that the response has been processed, we can wait on the wasm to + // finish without deadlocking. handle.await.context("Component execution")?; - Ok(resp) + Ok(resp.expect("wasm never called set-response-outparam")) } #[test_log::test(tokio::test)] diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml index 62f582cce21c..4c8734eec9b7 100644 --- a/crates/wasi-nn/Cargo.toml +++ b/crates/wasi-nn/Cargo.toml @@ -33,5 +33,5 @@ walkdir = { workspace = true } [dev-dependencies] cap-std = { workspace = true } test-programs-artifacts = { workspace = true } -wasmtime-wasi = { workspace = true, features = ["sync"] } +wasi-common = { workspace = true, features = ["sync"] } wasmtime = { workspace = true, features = ["cranelift"] } diff --git a/crates/wasi-nn/tests/all.rs b/crates/wasi-nn/tests/all.rs index de1f9f507f17..185daec9fbf5 100644 --- a/crates/wasi-nn/tests/all.rs +++ b/crates/wasi-nn/tests/all.rs @@ -3,9 +3,9 @@ use anyhow::Result; use std::path::Path; use test_programs_artifacts::*; +use wasi_common::sync::{Dir, WasiCtxBuilder}; +use wasi_common::WasiCtx; use wasmtime::{Config, Engine, Linker, Module, Store}; -use wasmtime_wasi::sync::{Dir, WasiCtxBuilder}; -use wasmtime_wasi::WasiCtx; use wasmtime_wasi_nn::{backend, testing, InMemoryRegistry, WasiNnCtx}; const PREOPENED_DIR_NAME: &str = "fixture"; @@ -20,7 +20,7 @@ fn run(path: &str, preload_model: bool) -> Result<()> { let engine = Engine::new(&config)?; let mut linker = Linker::new(&engine); wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi_nn)?; - wasmtime_wasi::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi)?; + wasi_common::sync::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi)?; let module = Module::from_file(&engine, path)?; let mut store = Store::new(&engine, Ctx::new(&testing::artifacts_dir(), preload_model)?); let instance = linker.instantiate(&mut store, &module)?; diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 0df1b5e1f86c..3c86683f3d32 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -908,6 +908,13 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { return ERRNO_BADF; } + // For the proxy adapter don't return `ERRNO_NOTSUP` through below, instead + // always return `ERRNO_BADF` which is the indicator that prestats aren't + // available. + if cfg!(feature = "proxy") { + return ERRNO_BADF; + } + cfg_filesystem_available! { State::with(|state| { let ds = state.descriptors(); diff --git a/crates/wasi-threads/Cargo.toml b/crates/wasi-threads/Cargo.toml index ea56f00fecef..1f42ba9f3210 100644 --- a/crates/wasi-threads/Cargo.toml +++ b/crates/wasi-threads/Cargo.toml @@ -18,8 +18,5 @@ workspace = true anyhow = { workspace = true } log = { workspace = true } rand = "0.8" -wasi-common = { workspace = true } +wasi-common = { workspace = true, features = ["exit"]} wasmtime = { workspace = true } -wasmtime-wasi = { workspace = true, default-features = true, features = [ - "exit", -] } diff --git a/crates/wasi-threads/src/lib.rs b/crates/wasi-threads/src/lib.rs index 76f12edae86a..5246d91fe11b 100644 --- a/crates/wasi-threads/src/lib.rs +++ b/crates/wasi-threads/src/lib.rs @@ -8,7 +8,6 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::Arc; use std::thread; use wasmtime::{Caller, ExternType, InstancePre, Linker, Module, SharedMemory, Store, ValType}; -use wasmtime_wasi::maybe_exit_on_error; // This name is a function export designated by the wasi-threads specification: // https://github.com/WebAssembly/wasi-threads/#detailed-design-discussion @@ -74,7 +73,7 @@ impl WasiThreadsCtx { Ok(_) => log::trace!("exiting thread id = {} normally", wasi_thread_id), Err(e) => { log::trace!("exiting thread id = {} due to error", wasi_thread_id); - let e = maybe_exit_on_error(e); + let e = wasi_common::maybe_exit_on_error(e); eprintln!("Error: {:?}", e); std::process::exit(1); } diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 28559f932eea..00ecc334d1d0 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["webassembly", "wasm"] repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" edition.workspace = true -include = ["src/**/*", "README.md", "LICENSE", "build.rs", "witx/*", "wit/**/*", "tests/*"] +include = ["src/**/*", "README.md", "LICENSE", "witx/*", "wit/**/*", "tests/*"] [lints] workspace = true @@ -17,11 +17,7 @@ workspace = true [dependencies] wasmtime = { workspace = true } anyhow = { workspace = true } -wasi-common = { workspace = true } -wasi-cap-std-sync = { workspace = true, optional = true } -wasi-tokio = { workspace = true, optional = true } wiggle = { workspace = true, optional = true, features = ["wasmtime"] } -libc = { workspace = true } once_cell = { workspace = true } log = { workspace = true } url = { workspace = true } @@ -41,6 +37,8 @@ bitflags = { workspace = true, optional = true } async-trait = { workspace = true, optional = true } system-interface = { workspace = true, optional = true} futures = { workspace = true, optional = true } +wasi-common = { workspace = true, optional = true, features = ["sync"] } +cfg-if = { workspace = true, optional = true } [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros"] } @@ -53,19 +51,16 @@ wasmtime = { workspace = true, features = ['cranelift'] } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["event", "fs", "net"], optional = true } -[target.'cfg(unix)'.dev-dependencies] -libc = { workspace = true } - [target.'cfg(windows)'.dependencies] io-extras = { workspace = true } windows-sys = { workspace = true } rustix = { workspace = true, features = ["event", "net"], optional = true } [features] -default = ["sync", "preview2", "preview1-on-preview2"] -sync = ["wasi-cap-std-sync", "wiggle"] -tokio = ["wasi-tokio", "wasmtime/async", "wiggle/wasmtime_async" ] -exit = [] +default = ["wasi-common-deprecations", "preview2", "preview1-on-preview2"] +wasi-common-deprecations = [ "dep:wasi-common" ] +tokio = [ "wasi-common?/tokio", "wasi-common-deprecations" ] +exit = [ "wasi-common-deprecations", "dep:cfg-if" ] preview2 = [ 'wasmtime/component-model', 'wasmtime/async', diff --git a/crates/wasi/build.rs b/crates/wasi/build.rs deleted file mode 100644 index 12ace81f5b13..000000000000 --- a/crates/wasi/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - // wasi-common's links & build.rs ensure this variable points to the wasi root: - let wasi_root = std::env::var("DEP_WASI_COMMON_19_WASI").unwrap(); - // Make it available as WASI_ROOT: - println!("cargo:rustc-env=WASI_ROOT={}", wasi_root); -} diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index a6a1e0b712a2..3dcb8d271df7 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -12,80 +12,36 @@ #[cfg(feature = "preview2")] pub mod preview2; -pub use wasi_common::{Error, I32Exit, WasiCtx, WasiDir, WasiFile}; - -/// Re-export the commonly used wasi-cap-std-sync crate here. This saves -/// consumers of this library from having to keep additional dependencies -/// in sync. -#[cfg(feature = "sync")] +#[cfg(feature = "wasi-common-deprecations")] +#[deprecated( + since = "18.0.0", + note = "The wasmtime_wasi::sync module's functionalty has been moved to + wasi_common::sync. This re-export will be removed from wasmtime_wasi in + 19.0" +)] pub mod sync { - pub use wasi_cap_std_sync::*; - super::define_wasi!(block_on); + pub use wasi_common::sync::*; } -/// Sync mode is the "default" of this crate, so we also export it at the top -/// level. -#[cfg(feature = "sync")] +#[cfg(feature = "wasi-common-deprecations")] +#[allow(deprecated)] // Satisfy linter locally +#[deprecated( + since = "18.0.0", + note = "The wasmtime_wasi module's root export of wasmtime_wasi::sync has + been moved to wasi_common::sync. This re-export will be removed from + wasmtime_wasi in 19.0" +)] 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")] +#[deprecated( + since = "18.0.0", + note = "The wasmtime_wasi::tokio module's functionalty has been moved to + wasi_common::tokio. This re-export will be removed from wasmtime_wasi in + 19.0" +)] pub mod tokio { - pub use wasi_tokio::*; - super::define_wasi!(async T: Send); -} - -// 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 $($bounds:tt)*) => { - -use wasmtime::Linker; - -pub fn add_to_linker( - linker: &mut Linker, - get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, -) -> anyhow::Result<()> - where U: Send - + wasi_common::snapshots::preview_0::wasi_unstable::WasiUnstable - + wasi_common::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1, - $($bounds)* -{ - snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(linker, get_cx)?; - snapshots::preview_0::add_wasi_unstable_to_linker(linker, get_cx)?; - Ok(()) -} - -pub mod snapshots { - pub mod preview_1 { - wiggle::wasmtime_integration!({ - // The wiggle code to integrate with lives here: - target: wasi_common::snapshots::preview_1, - // This must be the same witx document as used above. This should be ensured by - // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`. - witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], - errors: { errno => trappable Error }, - $async_mode: * - }); - } - pub mod preview_0 { - wiggle::wasmtime_integration!({ - // The wiggle code to integrate with lives here: - target: wasi_common::snapshots::preview_0, - // This must be the same witx document as used above. This should be ensured by - // the `WASI_ROOT` env variable, which is set in wasi-common's `build.rs`. - witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"], - errors: { errno => trappable Error }, - $async_mode: * - }); - } -} -} + pub use wasi_common::tokio::*; } /// Exit the process with a conventional OS error code as long as Wasmtime @@ -96,17 +52,25 @@ pub mod snapshots { /// Wasmtime failures to terminate the parent process, such as in the Wasmtime /// CLI; this would not be suitable for use in multi-tenant embeddings. #[cfg(feature = "exit")] +#[deprecated( + since = "18.0.0", + note = "This legacy functionality is migrated to the wasi-common crate, and will be removed in 19.0." +)] pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error { use std::process; use wasmtime::Trap; + if let Some(exit) = e + .downcast_ref::() + .map(|exit| exit.process_exit_code()) + { + process::exit(exit) + } + // If a specific WASI error code was requested then that's // forwarded through to the process here without printing any // extra error information. - let code = e - .downcast_ref::() - .map(|e| e.0) - .or_else(|| e.downcast_ref::().map(|e| e.0)); + let code = e.downcast_ref::().map(|e| e.0); if let Some(exit) = code { // Print the error message in the usual way. // On Windows, exit status 3 indicates an abort (see below), @@ -123,13 +87,15 @@ pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error { if e.is::() { eprintln!("Error: {:?}", e); - if cfg!(unix) { - // On Unix, return the error code of an abort. - process::exit(128 + libc::SIGABRT); - } else if cfg!(windows) { - // On Windows, return 3. - // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 - process::exit(3); + cfg_if::cfg_if! { + if #[cfg(unix)] { + // On Unix, return the error code of an abort. + process::exit(rustix::process::EXIT_SIGNALED_SIGABRT); + } else if #[cfg(windows)] { + // On Windows, return 3. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 + process::exit(3); + } } } diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs index 97c0119f3878..f440fa4e5658 100644 --- a/crates/wasi/src/preview2/ctx.rs +++ b/crates/wasi/src/preview2/ctx.rs @@ -3,7 +3,7 @@ use crate::preview2::{ host::{monotonic_clock, wall_clock}, HostMonotonicClock, HostWallClock, }, - filesystem::Dir, + filesystem::{Dir, OpenMode}, network::{SocketAddrCheck, SocketAddrUse}, pipe, random, stdio, stdio::{StdinStream, StdoutStream}, @@ -144,8 +144,17 @@ impl WasiCtxBuilder { file_perms: FilePerms, path: impl AsRef, ) -> &mut Self { - self.preopens - .push((Dir::new(dir, perms, file_perms), path.as_ref().to_owned())); + let mut open_mode = OpenMode::empty(); + if perms.contains(DirPerms::READ) { + open_mode |= OpenMode::READ; + } + if perms.contains(DirPerms::MUTATE) { + open_mode |= OpenMode::WRITE; + } + self.preopens.push(( + Dir::new(dir, perms, file_perms, open_mode), + path.as_ref().to_owned(), + )); self } diff --git a/crates/wasi/src/preview2/error.rs b/crates/wasi/src/preview2/error.rs index f92317c1fef7..a019b4a7a2e7 100644 --- a/crates/wasi/src/preview2/error.rs +++ b/crates/wasi/src/preview2/error.rs @@ -9,6 +9,20 @@ use std::marker; #[derive(Debug)] pub struct I32Exit(pub i32); +impl I32Exit { + /// Accessor for an exit code appropriate for calling `std::process::exit` with, + /// when interpreting this `I32Exit` as an exit for the parent process. + /// + /// This method masks off exit codes which are illegal on Windows. + pub fn process_exit_code(&self) -> i32 { + if cfg!(windows) && self.0 >= 3 { + 1 + } else { + self.0 + } + } +} + impl fmt::Display for I32Exit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Exited with i32 exit status {}", self.0) diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs index 1f1d149dd93f..3b54797f65bd 100644 --- a/crates/wasi/src/preview2/filesystem.rs +++ b/crates/wasi/src/preview2/filesystem.rs @@ -67,20 +67,40 @@ bitflags::bitflags! { } } +bitflags::bitflags! { + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct OpenMode: usize { + const READ = 0b1; + const WRITE = 0b10; + } +} + pub struct File { + /// The operating system File this struct is mediating access to. + /// /// Wrapped in an Arc because the same underlying file is used for - /// implementing the stream types. Also needed for [`spawn_blocking`]. + /// implementing the stream types. A copy is also needed for + /// [`spawn_blocking`]. /// /// [`spawn_blocking`]: Self::spawn_blocking pub file: Arc, + /// Permissions to enforce on access to the file. These permissions are + /// specified by a user of the `crate::preview2::WasiCtxBuilder`, and are + /// enforced prior to any enforced by the underlying operating system. pub perms: FilePerms, + /// The mode the file was opened under: bits for reading, and writing. + /// Required to correctly report the DescriptorFlags, because cap-std + /// doesn't presently provide a cross-platform equivelant of reading the + /// oflags back out using fcntl. + pub open_mode: OpenMode, } impl File { - pub fn new(file: cap_std::fs::File, perms: FilePerms) -> Self { + pub fn new(file: cap_std::fs::File, perms: FilePerms, open_mode: OpenMode) -> Self { Self { file: Arc::new(file), perms, + open_mode, } } @@ -106,17 +126,41 @@ bitflags::bitflags! { #[derive(Clone)] pub struct Dir { + /// The operating system file descriptor this struct is mediating access + /// to. + /// + /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`]. + /// + /// [`spawn_blocking`]: Self::spawn_blocking pub dir: Arc, + /// Permissions to enforce on access to this directory. These permissions + /// are specified by a user of the `crate::preview2::WasiCtxBuilder`, and + /// are enforced prior to any enforced by the underlying operating system. + /// + /// These permissions are also enforced on any directories opened under + /// this directory. pub perms: DirPerms, + /// Permissions to enforce on any files opened under this directory. pub file_perms: FilePerms, + /// The mode the directory was opened under: bits for reading, and writing. + /// Required to correctly report the DescriptorFlags, because cap-std + /// doesn't presently provide a cross-platform equivelant of reading the + /// oflags back out using fcntl. + pub open_mode: OpenMode, } impl Dir { - pub fn new(dir: cap_std::fs::Dir, perms: DirPerms, file_perms: FilePerms) -> Self { + pub fn new( + dir: cap_std::fs::Dir, + perms: DirPerms, + file_perms: FilePerms, + open_mode: OpenMode, + ) -> Self { Dir { dir: Arc::new(dir), perms, file_perms, + open_mode, } } diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs index f2b67eca0de0..c6d93713f561 100644 --- a/crates/wasi/src/preview2/host/filesystem.rs +++ b/crates/wasi/src/preview2/host/filesystem.rs @@ -4,8 +4,9 @@ use crate::preview2::bindings::filesystem::types::{ self, ErrorCode, HostDescriptor, HostDirectoryEntryStream, }; use crate::preview2::bindings::io::streams::{InputStream, OutputStream}; -use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator}; -use crate::preview2::filesystem::{FileInputStream, FileOutputStream}; +use crate::preview2::filesystem::{ + Descriptor, Dir, File, FileInputStream, FileOutputStream, OpenMode, ReaddirIterator, +}; use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, WasiView}; use anyhow::Context; use wasmtime::component::Resource; @@ -130,10 +131,10 @@ impl HostDescriptor for T { Descriptor::File(f) => { let flags = f.spawn_blocking(|f| f.get_fd_flags()).await?; let mut flags = get_from_fdflags(flags); - if f.perms.contains(FilePerms::READ) { + if f.open_mode.contains(OpenMode::READ) { flags |= DescriptorFlags::READ; } - if f.perms.contains(FilePerms::WRITE) { + if f.open_mode.contains(OpenMode::WRITE) { flags |= DescriptorFlags::WRITE; } Ok(flags) @@ -141,10 +142,10 @@ impl HostDescriptor for T { Descriptor::Dir(d) => { let flags = d.spawn_blocking(|d| d.get_fd_flags()).await?; let mut flags = get_from_fdflags(flags); - if d.perms.contains(DirPerms::READ) { + if d.open_mode.contains(OpenMode::READ) { flags |= DescriptorFlags::READ; } - if d.perms.contains(DirPerms::MUTATE) { + if d.open_mode.contains(OpenMode::WRITE) { flags |= DescriptorFlags::MUTATE_DIRECTORY; } Ok(flags) @@ -507,28 +508,40 @@ impl HostDescriptor for T { } } + // Track whether we are creating file, for permission check: + let mut create = false; + // Track open mode, for permission check and recording in created descriptor: + let mut open_mode = OpenMode::empty(); + // Construct the OpenOptions to give the OS: let mut opts = cap_std::fs::OpenOptions::new(); opts.maybe_dir(true); - if oflags.contains(OpenFlags::CREATE | OpenFlags::EXCLUSIVE) { - opts.create_new(true); - opts.write(true); - } else if oflags.contains(OpenFlags::CREATE) { - opts.create(true); + if oflags.contains(OpenFlags::CREATE) { + if oflags.contains(OpenFlags::EXCLUSIVE) { + opts.create_new(true); + } else { + opts.create(true); + } + create = true; opts.write(true); + open_mode |= OpenMode::WRITE; } + if oflags.contains(OpenFlags::TRUNCATE) { opts.truncate(true); } if flags.contains(DescriptorFlags::READ) { opts.read(true); + open_mode |= OpenMode::READ; } if flags.contains(DescriptorFlags::WRITE) { opts.write(true); + open_mode |= OpenMode::WRITE; } else { // If not opened write, open read. This way the OS lets us open // the file, but we can use perms to reject use of the file later. opts.read(true); + open_mode |= OpenMode::READ; } if symlink_follow(path_flags) { opts.follow(FollowSymlinks::Yes); @@ -538,8 +551,8 @@ impl HostDescriptor for T { // These flags are not yet supported in cap-std: if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC) - | flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) - | flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) + || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) + || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) { Err(ErrorCode::Unsupported)?; } @@ -553,6 +566,15 @@ impl HostDescriptor for T { } } + // Now enforce this WasiCtx's permissions before letting the OS have + // its shot: + if !d.perms.contains(DirPerms::MUTATE) && create { + Err(ErrorCode::NotPermitted)?; + } + if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) { + Err(ErrorCode::NotPermitted)?; + } + // Represents each possible outcome from the spawn_blocking operation. // This makes sure we don't have to give spawn_blocking any way to // manipulate the table. @@ -582,15 +604,17 @@ impl HostDescriptor for T { .await?; match opened { - OpenResult::Dir(dir) => { - Ok(table.push(Descriptor::Dir(Dir::new(dir, d.perms, d.file_perms)))?) - } - - OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new( - file, - mask_file_perms(d.file_perms, flags), + OpenResult::Dir(dir) => Ok(table.push(Descriptor::Dir(Dir::new( + dir, + d.perms, + d.file_perms, + open_mode, )))?), + OpenResult::File(file) => { + Ok(table.push(Descriptor::File(File::new(file, d.file_perms, open_mode)))?) + } + OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()), } } @@ -1040,18 +1064,6 @@ fn symlink_follow(path_flags: types::PathFlags) -> bool { path_flags.contains(types::PathFlags::SYMLINK_FOLLOW) } -fn mask_file_perms(p: FilePerms, flags: types::DescriptorFlags) -> FilePerms { - use types::DescriptorFlags; - let mut out = FilePerms::empty(); - if p.contains(FilePerms::READ) && flags.contains(DescriptorFlags::READ) { - out |= FilePerms::READ; - } - if p.contains(FilePerms::WRITE) && flags.contains(DescriptorFlags::WRITE) { - out |= FilePerms::WRITE; - } - out -} - #[cfg(test)] mod test { use super::*; diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 748030977fbd..bd8f48ebd13d 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -71,8 +71,7 @@ rustix = { workspace = true, features = ['thread'] } [dev-dependencies] tempfile = "3.0" -wasmtime-wasi = { path = "../wasi", default-features = true } -wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync" } +wasi-common = { path = "../wasi-common", default-features = true } # ============================================================================= # diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index e4278148c466..2813eb61836b 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -327,7 +327,7 @@ //! //! ```no_run //! # use wasmtime::*; -//! use wasmtime_wasi::sync::WasiCtxBuilder; +//! use wasi_common::sync::WasiCtxBuilder; //! //! # fn main() -> wasmtime::Result<()> { //! // Compile our module and create a `Linker` which has WASI functions defined @@ -335,7 +335,7 @@ //! let engine = Engine::default(); //! let module = Module::from_file(&engine, "foo.wasm")?; //! let mut linker = Linker::new(&engine); -//! wasmtime_wasi::add_to_linker(&mut linker, |cx| cx)?; +//! wasi_common::sync::add_to_linker(&mut linker, |cx| cx)?; //! //! // Configure and create a `WasiCtx`, which WASI functions need access to //! // through the host state of the store (which in this case is the host state diff --git a/docs/examples-rust-wasi.md b/docs/examples-rust-wasi.md index bce8009c4ef0..61b4c1251810 100644 --- a/docs/examples-rust-wasi.md +++ b/docs/examples-rust-wasi.md @@ -5,11 +5,11 @@ repository to run the example locally. [code]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi/main.rs -This example shows how to use the [`wasmtime-wasi`] crate to define WASI +This example shows how to use the [`wasi-common`] crate to define WASI functions within a [`Linker`] which can then be used to instantiate a WebAssembly module. -[`wasmtime-wasi`]: https://crates.io/crates/wasmtime-wasi +[`wasi-common`]: https://crates.io/crates/wasi-common [`Linker`]: https://docs.rs/wasmtime/*/wasmtime/struct.Linker.html ### WebAssembly module source code @@ -39,19 +39,19 @@ WasiCtx` from within the `T` stored in the `Store` itself. In the above example this is trivial because the `T` in `Store` is `WasiCtx` itself, but you can also store other state in `Store` like so: -[`add_to_linker`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/sync/fn.add_to_linker.html -[`Store`]: https://docs.rs/wasmtime/0.26.0/wasmtime/struct.Store.html +[`add_to_linker`]: https://docs.rs/wasi-common/*/wasi_common/sync/fn.add_to_linker.html +[`Store`]: https://docs.rs/wasmtime/*/wasmtime/struct.Store.html [`BorrowMut`]: https://doc.rust-lang.org/stable/std/borrow/trait.BorrowMut.html -[`WasiCtx`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/struct.WasiCtx.html +[`WasiCtx`]: https://docs.rs/wasi-common/*/wasi_common/struct.WasiCtx.html ```rust # extern crate wasmtime; -# extern crate wasmtime_wasi; +# extern crate wasi_common; # extern crate anyhow; use anyhow::Result; use std::borrow::{Borrow, BorrowMut}; use wasmtime::*; -use wasmtime_wasi::{WasiCtx, sync::WasiCtxBuilder}; +use wasi_common::{WasiCtx, sync::WasiCtxBuilder}; struct MyState { message: String, @@ -61,7 +61,7 @@ struct MyState { fn main() -> Result<()> { let engine = Engine::default(); let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?; + wasi_common::sync::add_to_linker(&mut linker, |state: &mut MyState| &mut state.wasi)?; let wasi = WasiCtxBuilder::new() .inherit_stdio() @@ -91,7 +91,7 @@ execute the same WASI Preview 1 WebAssembly module from the example above. This This does not require any change to the WebAssembly module, it's just the WASI API host functions which are implemented to be async. See [wasmtime async support](https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.async_support). [code2]: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi-async/main.rs -[`preview2`]: https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/preview2/index.html +[`preview2`]: https://docs.rs/wasmtime-wasi/*/wasmtime_wasi/preview2/index.html ```rust,ignore {{#include ../examples/wasi-async/main.rs}} diff --git a/examples/linking.rs b/examples/linking.rs index a2d75605725a..c3c9502880a8 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -3,8 +3,8 @@ // You can execute this example with `cargo run --example linking` use anyhow::Result; +use wasi_common::sync::WasiCtxBuilder; use wasmtime::*; -use wasmtime_wasi::sync::WasiCtxBuilder; fn main() -> Result<()> { let engine = Engine::default(); @@ -12,7 +12,7 @@ fn main() -> Result<()> { // First set up our linker which is going to be linking modules together. We // want our linker to have wasi available, so we set that up here as well. let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + wasi_common::sync::add_to_linker(&mut linker, |s| s)?; // Load and compile our two modules let linking1 = Module::from_file(&engine, "examples/linking1.wat")?; diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index 2073f38886fa..b4a3d1724ada 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -2,10 +2,10 @@ use anyhow::Error; use std::sync::Arc; use tokio::time::Duration; use wasmtime::{Config, Engine, Linker, Module, Store}; -// For this example we want to use the async version of wasmtime_wasi. +// For this example we want to use the async version of wasi_common. // Notably, this version of wasi uses a scheduler that will async yield // when sleeping in `poll_oneoff`. -use wasmtime_wasi::{tokio::WasiCtxBuilder, WasiCtx}; +use wasi_common::{tokio::WasiCtxBuilder, WasiCtx}; #[tokio::main] async fn main() -> Result<(), Error> { @@ -61,7 +61,7 @@ impl Environment { // adds WASI functions to the linker, notably the async versions built // on tokio. let mut linker = Linker::new(&engine); - wasmtime_wasi::tokio::add_to_linker(&mut linker, |cx| cx)?; + wasi_common::tokio::add_to_linker(&mut linker, |cx| cx)?; Ok(Self { engine, diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs index 69ff61756980..4c4cd7caa88c 100644 --- a/examples/wasi/main.rs +++ b/examples/wasi/main.rs @@ -7,14 +7,14 @@ You can execute this example with: */ use anyhow::Result; +use wasi_common::sync::WasiCtxBuilder; use wasmtime::*; -use wasmtime_wasi::sync::WasiCtxBuilder; fn main() -> Result<()> { // Define the WASI functions globally on the `Config`. let engine = Engine::default(); let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + wasi_common::sync::add_to_linker(&mut linker, |s| s)?; // Create a WASI context and put it in a Store; all instances in the store // share this context. `WasiCtxBuilder` provides a number of ways to diff --git a/scripts/publish.rs b/scripts/publish.rs index b5d2dbc36789..3707da75449e 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -63,8 +63,6 @@ const CRATES_TO_PUBLISH: &[&str] = &[ // wasi-common/wiggle "wiggle", "wasi-common", - "wasi-cap-std-sync", - "wasi-tokio", // other misc wasmtime crates "wasmtime-wasi", "wasmtime-wasi-http", diff --git a/src/commands/run.rs b/src/commands/run.rs index 1fdf32a954a9..90ec42b84587 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -13,10 +13,9 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::thread; +use wasi_common::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder}; use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType}; -use wasmtime_wasi::maybe_exit_on_error; use wasmtime_wasi::preview2; -use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder}; #[cfg(feature = "wasi-nn")] use wasmtime_wasi_nn::WasiNnCtx; @@ -223,7 +222,30 @@ impl RunCommand { // Exit the process if Wasmtime understands the error; // otherwise, fall back on Rust's default error printing/return // code. - return Err(maybe_exit_on_error(e)); + if store.data().preview1_ctx.is_some() { + return Err(wasi_common::maybe_exit_on_error(e)); + } else if store.data().preview2_ctx.is_some() { + if let Some(exit) = e + .downcast_ref::() + .map(|c| c.process_exit_code()) + { + std::process::exit(exit); + } + if e.is::() { + eprintln!("Error: {e:?}"); + cfg_if::cfg_if! { + if #[cfg(unix)] { + std::process::exit(rustix::process::EXIT_SIGNALED_SIGABRT); + } else if #[cfg(windows)] { + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019 + std::process::exit(3); + } + } + } + return Err(e); + } else { + unreachable!("either preview1_ctx or preview2_ctx present") + } } } @@ -464,7 +486,7 @@ impl RunCommand { // explicit exit here with status 1 if `Err(())` is returned. result.and_then(|wasm_result| match wasm_result { Ok(()) => Ok(()), - Err(()) => Err(wasmtime_wasi::I32Exit(1).into()), + Err(()) => Err(wasmtime_wasi::preview2::I32Exit(1).into()), }) } }; @@ -589,7 +611,7 @@ impl RunCommand { // are enabled, then use the historical preview1 // implementation. (Some(false), _) | (None, Some(true)) => { - wasmtime_wasi::add_to_linker(linker, |host| { + wasi_common::sync::add_to_linker(linker, |host| { host.preview1_ctx.as_mut().unwrap() })?; self.set_preview1_ctx(store)?; @@ -792,7 +814,7 @@ impl RunCommand { #[derive(Default, Clone)] struct Host { - preview1_ctx: Option, + preview1_ctx: Option, // The Mutex is only needed to satisfy the Sync constraint but we never // actually perform any locking on it as we use Mutex::get_mut for every diff --git a/supply-chain/config.toml b/supply-chain/config.toml index bd757b98a2e1..a0d11626ab89 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -76,15 +76,9 @@ audit-as-crates-io = true [policy.isle-fuzz] criteria = "safe-to-run" -[policy.wasi-cap-std-sync] -audit-as-crates-io = true - [policy.wasi-common] audit-as-crates-io = true -[policy.wasi-tokio] -audit-as-crates-io = true - [policy.wasmtime] audit-as-crates-io = true diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 76033acd62ca..f002a47aaca6 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -1,8 +1,8 @@ use anyhow::{bail, Result}; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; +use wasi_common::sync::WasiCtxBuilder; +use wasi_common::I32Exit; use wasmtime::*; -use wasmtime_wasi::sync::WasiCtxBuilder; -use wasmtime_wasi::I32Exit; #[test] #[should_panic = "cannot use `func_new_async` without enabling async support"] @@ -708,7 +708,7 @@ fn store_with_context() -> Result<()> { fn wasi_imports() -> Result<()> { let engine = Engine::default(); let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + wasi_common::sync::add_to_linker(&mut linker, |s| s)?; let wasm = wat::parse_str( r#" diff --git a/tests/all/traps.rs b/tests/all/traps.rs index 9b3ae2764c11..500891ba1e9a 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -754,10 +754,10 @@ fn parse_dwarf_info() -> Result<()> { let engine = Engine::new(&config)?; let module = Module::new(&engine, &wasm)?; let mut linker = Linker::new(&engine); - wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + wasi_common::sync::add_to_linker(&mut linker, |s| s)?; let mut store = Store::new( &engine, - wasmtime_wasi::sync::WasiCtxBuilder::new() + wasi_common::sync::WasiCtxBuilder::new() .inherit_stdio() .build(), );